]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/participant/TimeParticipant.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / participant / TimeParticipant.java
1 /*******************************************************************************\r
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     VTT Technical Research Centre of Finland - initial API and implementation\r
11  *******************************************************************************/\r
12 package org.simantics.g2d.participant;\r
13 \r
14 import java.util.HashSet;\r
15 import java.util.Set;\r
16 import java.util.concurrent.ScheduledFuture;\r
17 import java.util.concurrent.TimeUnit;\r
18 \r
19 import org.simantics.g2d.canvas.ICanvasContext;\r
20 import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant;\r
21 import org.simantics.g2d.diagram.participant.ElementHeartbeater;\r
22 import org.simantics.g2d.element.handler.Heartbeat;\r
23 import org.simantics.scenegraph.g2d.events.Event;\r
24 import org.simantics.scenegraph.g2d.events.TimeEvent;\r
25 import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler;\r
26 import org.simantics.utils.datastructures.disposable.DisposeState;\r
27 import org.simantics.utils.datastructures.hints.HintListenerAdapter;\r
28 import org.simantics.utils.datastructures.hints.IHintListener;\r
29 import org.simantics.utils.datastructures.hints.IHintObservable;\r
30 import org.simantics.utils.datastructures.hints.IHintContext.Key;\r
31 import org.simantics.utils.datastructures.hints.IHintContext.KeyOf;\r
32 import org.simantics.utils.threads.ThreadUtils;\r
33 \r
34 /**\r
35  * Time Participant sends time pulses by setting KEY_TIME hint. The value of\r
36  * KEY_TIME is system current time in milliseconds.\r
37  * \r
38  * The hint KEY_TIME_PULSE_INTERVAL controls the interval of time pulse events.\r
39  * \r
40  * Time pulse events can be listened by listening modifications of KEY_TIME\r
41  * hint.\r
42  * \r
43  * KEY_TIMER_ENABLED controls the enabled state of the timer, if false, no\r
44  * events will be sent even if there are recipients.\r
45  * \r
46  * To be able receive timer events, add a handler like this to your canvas\r
47  * participant:<pre>\r
48  * &#064;EventHandler(priority = 0)\r
49  * public boolean handleTimeEvent(TimeEvent e) {\r
50  *     // do something\r
51  *     // Don't eat the event, let others see it too.\r
52  *     return false;\r
53  * }</pre>\r
54  * When you need to receive time events, inform TimeParticipant by invoking\r
55  * <code>timeParticipant.registerForEvents(getClass());</code> and when you no\r
56  * longer need the events, use\r
57  * <code>timeParticipant.unregisterForEvents(getClass());</code>. This allows\r
58  * TimeParticipant to optimize its internal behavior.\r
59  *\r
60  * @author Toni Kalajainen\r
61  * @author Tuukka Lehtonen\r
62  *\r
63  * @see ElementHeartbeater\r
64  * @see Heartbeat\r
65  * @see Notifications\r
66  */\r
67 public class TimeParticipant extends AbstractCanvasParticipant {\r
68 \r
69     /** Key for global timer enable state */\r
70     public static final Key KEY_TIMER_ENABLED = new KeyOf(Boolean.class);\r
71 \r
72     /** Key for time code */\r
73     public static final Key KEY_TIME = new KeyOf(Long.class);\r
74 \r
75     /** Key for timer interval in milliseconds */\r
76     public static final Key KEY_TIME_PULSE_INTERVAL = new KeyOf(Long.class);\r
77 \r
78     /** Default interval in milliseconds */\r
79     public static final long DEFAULT_INTERVAL = 100L;\r
80 \r
81     ScheduledFuture<?> future = null;\r
82 \r
83     Set<Class<?>> eventRecipients = new HashSet<Class<?>>();\r
84 \r
85     IHintListener hintChangeListener = new HintListenerAdapter() {\r
86         @Override\r
87         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {\r
88             if (key == KEY_TIME_PULSE_INTERVAL || key == KEY_TIMER_ENABLED) {\r
89                 updateInterval();\r
90             }\r
91         }\r
92     };\r
93 \r
94     public void registerForEvents(Class<?> clazz) {\r
95         boolean isEmpty = eventRecipients.isEmpty();\r
96         boolean added = eventRecipients.add(clazz);\r
97         if (isEmpty && added)\r
98             // We've got a recipient, start sending timer events.\r
99             updateInterval();\r
100     }\r
101 \r
102     public void unregisterForEvents(Class<?> clazz) {\r
103         eventRecipients.remove(clazz);\r
104         if (eventRecipients.isEmpty()) {\r
105             // No more event recipients, stop sending timer events.\r
106             cancelTimer();\r
107         }\r
108     }\r
109 \r
110     private boolean hasRecipients() {\r
111         return !eventRecipients.isEmpty();\r
112     }\r
113 \r
114     public boolean isTimerEnabled() {\r
115         return getHint(KEY_TIMER_ENABLED);\r
116     }\r
117 \r
118     public void setTimerEnabled(boolean enabled) {\r
119         setHint(KEY_TIMER_ENABLED, Boolean.valueOf(enabled));\r
120     }\r
121 \r
122     public long getInterval()\r
123     {\r
124         Long interval = getHint(KEY_TIME_PULSE_INTERVAL);\r
125         if (interval == null)\r
126             return DEFAULT_INTERVAL;\r
127         return interval;\r
128     }\r
129 \r
130     public void setInterval(long interval)\r
131     {\r
132         setHint(KEY_TIME_PULSE_INTERVAL, interval);\r
133     }\r
134 \r
135     @Override\r
136     public void addedToContext(ICanvasContext ctx) {\r
137         super.addedToContext(ctx);\r
138 \r
139         setHint(KEY_TIMER_ENABLED, Boolean.FALSE);\r
140         setHint(KEY_TIME_PULSE_INTERVAL, DEFAULT_INTERVAL);\r
141         ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), KEY_TIME_PULSE_INTERVAL, hintChangeListener);\r
142         ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), KEY_TIMER_ENABLED, hintChangeListener);\r
143         updateInterval();\r
144     }\r
145 \r
146     @Override\r
147     public void removedFromContext(ICanvasContext ctx) {\r
148         ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), KEY_TIMER_ENABLED, hintChangeListener);\r
149         ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), KEY_TIME_PULSE_INTERVAL, hintChangeListener);\r
150         cancelTimer();\r
151 \r
152         eventRecipients.clear();\r
153 \r
154         super.removedFromContext(ctx);\r
155     }\r
156 \r
157     private boolean commandInQueue = false;\r
158 \r
159     @EventHandler(priority = Integer.MAX_VALUE)\r
160     public boolean handleTimeEvent(TimeEvent e) {\r
161         commandInQueue = false;\r
162         return false;\r
163     }\r
164 \r
165     private long prevTime = System.currentTimeMillis();\r
166 \r
167     private final Runnable onEvent = new Runnable() {\r
168         @Override\r
169         public void run() {\r
170             if (isRemoved() || !hasRecipients() || !isTimerEnabled()) {\r
171                 cancelTimer();\r
172                 return;\r
173             }\r
174 \r
175             if (commandInQueue) return;\r
176             ICanvasContext ctx = getContext();\r
177             if (ctx==null || ctx.getDisposeState()!=DisposeState.Alive) return;\r
178             long pTime = prevTime;\r
179             Long time = System.currentTimeMillis();\r
180             long interval = time - pTime;\r
181             prevTime = time;\r
182             setHint(KEY_TIME, time);\r
183             Event e = new TimeEvent(getContext(), pTime, interval);\r
184             commandInQueue = true;\r
185             //System.out.println("sending time event: " + e);\r
186             getContext().getEventQueue().queueFirst(e);\r
187         }\r
188     };\r
189 \r
190     Runnable onTimer = new Runnable() {\r
191         @Override\r
192         public void run() {\r
193             // On time pulse\r
194             asyncExec(onEvent);\r
195         }\r
196     };\r
197 \r
198     private void updateInterval()\r
199     {\r
200         // cancel old timer\r
201         cancelTimer();\r
202         if (isRemoved())\r
203             return;\r
204 \r
205         boolean timerEnabled = isTimerEnabled();\r
206         if (!timerEnabled)\r
207             return;\r
208 \r
209         long interval = getInterval();\r
210         future = ThreadUtils.getNonBlockingWorkExecutor().scheduleAtFixedRate(onTimer, DEFAULT_INTERVAL, interval, TimeUnit.MILLISECONDS);\r
211     }\r
212 \r
213     private void cancelTimer() {\r
214         if (future != null) {\r
215             future.cancel(false);\r
216             future = null;\r
217         }\r
218     }\r
219 \r
220 }\r