1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.g2d.participant;
14 import java.util.HashSet;
16 import java.util.concurrent.ScheduledFuture;
17 import java.util.concurrent.TimeUnit;
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;
35 * Time Participant sends time pulses by setting KEY_TIME hint. The value of
36 * KEY_TIME is system current time in milliseconds.
38 * The hint KEY_TIME_PULSE_INTERVAL controls the interval of time pulse events.
40 * Time pulse events can be listened by listening modifications of KEY_TIME
43 * KEY_TIMER_ENABLED controls the enabled state of the timer, if false, no
44 * events will be sent even if there are recipients.
46 * To be able receive timer events, add a handler like this to your canvas
48 * @EventHandler(priority = 0)
49 * public boolean handleTimeEvent(TimeEvent e) {
51 * // Don't eat the event, let others see it too.
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.
60 * @author Toni Kalajainen
61 * @author Tuukka Lehtonen
63 * @see ElementHeartbeater
67 public class TimeParticipant extends AbstractCanvasParticipant {
69 /** Key for global timer enable state */
70 public static final Key KEY_TIMER_ENABLED = new KeyOf(Boolean.class);
72 /** Key for time code */
73 public static final Key KEY_TIME = new KeyOf(Long.class);
75 /** Key for timer interval in milliseconds */
76 public static final Key KEY_TIME_PULSE_INTERVAL = new KeyOf(Long.class);
78 /** Default interval in milliseconds */
79 public static final long DEFAULT_INTERVAL = 100L;
81 ScheduledFuture<?> future = null;
83 Set<Class<?>> eventRecipients = new HashSet<Class<?>>();
85 IHintListener hintChangeListener = new HintListenerAdapter() {
87 public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
88 if (key == KEY_TIME_PULSE_INTERVAL || key == KEY_TIMER_ENABLED) {
94 public void registerForEvents(Class<?> clazz) {
95 boolean isEmpty = eventRecipients.isEmpty();
96 boolean added = eventRecipients.add(clazz);
98 // We've got a recipient, start sending timer events.
102 public void unregisterForEvents(Class<?> clazz) {
103 eventRecipients.remove(clazz);
104 if (eventRecipients.isEmpty()) {
105 // No more event recipients, stop sending timer events.
110 private boolean hasRecipients() {
111 return !eventRecipients.isEmpty();
114 public boolean isTimerEnabled() {
115 return getHint(KEY_TIMER_ENABLED);
118 public void setTimerEnabled(boolean enabled) {
119 setHint(KEY_TIMER_ENABLED, Boolean.valueOf(enabled));
122 public long getInterval()
124 Long interval = getHint(KEY_TIME_PULSE_INTERVAL);
125 if (interval == null)
126 return DEFAULT_INTERVAL;
130 public void setInterval(long interval)
132 setHint(KEY_TIME_PULSE_INTERVAL, interval);
136 public void addedToContext(ICanvasContext ctx) {
137 super.addedToContext(ctx);
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);
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);
152 eventRecipients.clear();
154 super.removedFromContext(ctx);
157 private boolean commandInQueue = false;
159 @EventHandler(priority = Integer.MAX_VALUE)
160 public boolean handleTimeEvent(TimeEvent e) {
161 commandInQueue = false;
165 private long prevTime = System.currentTimeMillis();
167 private final Runnable onEvent = new Runnable() {
170 if (isRemoved() || !hasRecipients() || !isTimerEnabled()) {
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;
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);
190 Runnable onTimer = new Runnable() {
198 private void updateInterval()
205 boolean timerEnabled = isTimerEnabled();
209 long interval = getInterval();
210 future = ThreadUtils.getNonBlockingWorkExecutor().scheduleAtFixedRate(onTimer, DEFAULT_INTERVAL, interval, TimeUnit.MILLISECONDS);
213 private void cancelTimer() {
214 if (future != null) {
215 future.cancel(false);