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