/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.g2d.participant; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.simantics.g2d.canvas.ICanvasContext; import org.simantics.g2d.canvas.impl.AbstractCanvasParticipant; import org.simantics.g2d.diagram.participant.ElementHeartbeater; import org.simantics.g2d.element.handler.Heartbeat; import org.simantics.scenegraph.g2d.events.Event; import org.simantics.scenegraph.g2d.events.TimeEvent; import org.simantics.scenegraph.g2d.events.EventHandlerReflection.EventHandler; import org.simantics.utils.datastructures.disposable.DisposeState; import org.simantics.utils.datastructures.hints.HintListenerAdapter; import org.simantics.utils.datastructures.hints.IHintListener; import org.simantics.utils.datastructures.hints.IHintObservable; import org.simantics.utils.datastructures.hints.IHintContext.Key; import org.simantics.utils.datastructures.hints.IHintContext.KeyOf; import org.simantics.utils.threads.ThreadUtils; /** * Time Participant sends time pulses by setting KEY_TIME hint. The value of * KEY_TIME is system current time in milliseconds. * * The hint KEY_TIME_PULSE_INTERVAL controls the interval of time pulse events. * * Time pulse events can be listened by listening modifications of KEY_TIME * hint. * * KEY_TIMER_ENABLED controls the enabled state of the timer, if false, no * events will be sent even if there are recipients. * * To be able receive timer events, add a handler like this to your canvas * participant:
 * @EventHandler(priority = 0)
 * public boolean handleTimeEvent(TimeEvent e) {
 *     // do something
 *     // Don't eat the event, let others see it too.
 *     return false;
 * }
* When you need to receive time events, inform TimeParticipant by invoking * timeParticipant.registerForEvents(getClass()); and when you no * longer need the events, use * timeParticipant.unregisterForEvents(getClass());. This allows * TimeParticipant to optimize its internal behavior. * * @author Toni Kalajainen * @author Tuukka Lehtonen * * @see ElementHeartbeater * @see Heartbeat * @see Notifications */ public class TimeParticipant extends AbstractCanvasParticipant { /** Key for global timer enable state */ public static final Key KEY_TIMER_ENABLED = new KeyOf(Boolean.class); /** Key for time code */ public static final Key KEY_TIME = new KeyOf(Long.class); /** Key for timer interval in milliseconds */ public static final Key KEY_TIME_PULSE_INTERVAL = new KeyOf(Long.class); /** Default interval in milliseconds */ public static final long DEFAULT_INTERVAL = 100L; ScheduledFuture future = null; Set> eventRecipients = new HashSet>(); IHintListener hintChangeListener = new HintListenerAdapter() { @Override public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) { if (key == KEY_TIME_PULSE_INTERVAL || key == KEY_TIMER_ENABLED) { updateInterval(); } } }; public void registerForEvents(Class clazz) { boolean isEmpty = eventRecipients.isEmpty(); boolean added = eventRecipients.add(clazz); if (isEmpty && added) // We've got a recipient, start sending timer events. updateInterval(); } public void unregisterForEvents(Class clazz) { eventRecipients.remove(clazz); if (eventRecipients.isEmpty()) { // No more event recipients, stop sending timer events. cancelTimer(); } } private boolean hasRecipients() { return !eventRecipients.isEmpty(); } public boolean isTimerEnabled() { return getHint(KEY_TIMER_ENABLED); } public void setTimerEnabled(boolean enabled) { setHint(KEY_TIMER_ENABLED, Boolean.valueOf(enabled)); } public long getInterval() { Long interval = getHint(KEY_TIME_PULSE_INTERVAL); if (interval == null) return DEFAULT_INTERVAL; return interval; } public void setInterval(long interval) { setHint(KEY_TIME_PULSE_INTERVAL, interval); } @Override public void addedToContext(ICanvasContext ctx) { super.addedToContext(ctx); setHint(KEY_TIMER_ENABLED, Boolean.FALSE); setHint(KEY_TIME_PULSE_INTERVAL, DEFAULT_INTERVAL); ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), KEY_TIME_PULSE_INTERVAL, hintChangeListener); ctx.getHintStack().addKeyHintListener(getContext().getThreadAccess(), KEY_TIMER_ENABLED, hintChangeListener); updateInterval(); } @Override public void removedFromContext(ICanvasContext ctx) { ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), KEY_TIMER_ENABLED, hintChangeListener); ctx.getHintStack().removeKeyHintListener(getContext().getThreadAccess(), KEY_TIME_PULSE_INTERVAL, hintChangeListener); cancelTimer(); eventRecipients.clear(); super.removedFromContext(ctx); } private boolean commandInQueue = false; @EventHandler(priority = Integer.MAX_VALUE) public boolean handleTimeEvent(TimeEvent e) { commandInQueue = false; return false; } private long prevTime = System.currentTimeMillis(); private final Runnable onEvent = new Runnable() { @Override public void run() { if (isRemoved() || !hasRecipients() || !isTimerEnabled()) { cancelTimer(); return; } if (commandInQueue) return; ICanvasContext ctx = getContext(); if (ctx==null || ctx.getDisposeState()!=DisposeState.Alive) return; long pTime = prevTime; Long time = System.currentTimeMillis(); long interval = time - pTime; prevTime = time; setHint(KEY_TIME, time); Event e = new TimeEvent(getContext(), pTime, interval); commandInQueue = true; //System.out.println("sending time event: " + e); getContext().getEventQueue().queueFirst(e); } }; Runnable onTimer = new Runnable() { @Override public void run() { // On time pulse asyncExec(onEvent); } }; private void updateInterval() { // cancel old timer cancelTimer(); if (isRemoved()) return; boolean timerEnabled = isTimerEnabled(); if (!timerEnabled) return; long interval = getInterval(); future = ThreadUtils.getNonBlockingWorkExecutor().scheduleWithFixedDelay(onTimer, DEFAULT_INTERVAL, interval, TimeUnit.MILLISECONDS); } private void cancelTimer() { if (future != null) { future.cancel(false); future = null; } } }