001/** 002 * Copyright (c) 2011, The University of Southampton and the individual contributors. 003 * All rights reserved. 004 * 005 * Redistribution and use in source and binary forms, with or without modification, 006 * are permitted provided that the following conditions are met: 007 * 008 * * Redistributions of source code must retain the above copyright notice, 009 * this list of conditions and the following disclaimer. 010 * 011 * * Redistributions in binary form must reproduce the above copyright notice, 012 * this list of conditions and the following disclaimer in the documentation 013 * and/or other materials provided with the distribution. 014 * 015 * * Neither the name of the University of Southampton nor the names of its 016 * contributors may be used to endorse or promote products derived from this 017 * software without specific prior written permission. 018 * 019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 029 */ 030/** 031 * 032 */ 033package org.openimaj.time; 034 035import java.util.Iterator; 036import java.util.Timer; 037import java.util.TimerTask; 038import java.util.TreeSet; 039 040/** 041 * An object that will fire events at specific times thereby sequencing 042 * objects into a stream. Can be used to trigger anything, such as animations 043 * on a slideshow or sequencing audio samples. 044 * <p> 045 * The class is parameterised on a {@link Timecode} object which will be used 046 * to determine whether actions should be triggered. The accuracy of the 047 * triggers can be determined by changing the internal timer tick of the 048 * sequencer. For example, if you know that actions will never occur more 049 * than once a second, you can set the tick to something near a second. 050 * <p> 051 * This class will automatically start the {@link TimeKeeper} running when 052 * it is run. The time of firing of an event cannot be guaranteed and an event 053 * will always be fired if 054 * 055 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 056 * @created 27 Nov 2011 057 */ 058public class Sequencer implements Runnable 059{ 060 /** 061 * A class that can be sequenced by the sequencer can implement this 062 * interface. The only method, {@link #performAction()}, should 063 * execute the desired action. 064 * 065 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 066 * @created 29 Nov 2011 067 */ 068 static public interface SequencedAction 069 { 070 /** 071 * Perform the sequenced action. 072 * @return TRUE if the action trigger succeeded; FALSE otherwise. 073 */ 074 public boolean performAction(); 075 } 076 077 /** 078 * An event in the sequencer, this represents an action occurring at a 079 * specific time. 080 * 081 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 082 * 083 * @created 29 Nov 2011 084 */ 085 static public class SequencerEvent implements Comparable<SequencerEvent> 086 { 087 /** The sequenced action */ 088 public SequencedAction action = null; 089 090 /** The time at which the sequenced action should happen */ 091 public long timestamp = 0; 092 093 /** Whether the event has already been fired. */ 094 public boolean fired = false; 095 096 /** Whether the event failed to fire */ 097 public boolean failed = false; 098 099 /** 100 * Create a new sequencer event that occurs at a specific time. 101 * @param timestamp The time the sequencer event should occur. 102 * @param action The action that should happen at the given time. 103 */ 104 public SequencerEvent( final long timestamp, final SequencedAction action ) 105 { 106 this.timestamp = timestamp; 107 this.action = action; 108 } 109 110 /** 111 * Create a new sequencer event that occurs at a specific time. 112 * @param tc The time the sequencer event should occur. 113 * @param action The action that should happen at the given time. 114 */ 115 public SequencerEvent( final Timecode tc, final SequencedAction action ) 116 { 117 this.timestamp = tc.getTimecodeInMilliseconds(); 118 this.action = action; 119 } 120 121 /** 122 * {@inheritDoc} 123 * @see java.lang.Comparable#compareTo(java.lang.Object) 124 */ 125 @Override 126 public int compareTo( final SequencerEvent event ) 127 { 128 final int v = (int)(this.timestamp - event.timestamp); 129 if( v == 0 && event != this ) 130 return -1; 131 else return v; 132 } 133 134 /** 135 * {@inheritDoc} 136 * @see java.lang.Object#toString() 137 */ 138 @Override 139 public String toString() 140 { 141 return "@"+this.timestamp; 142 } 143 } 144 145 /** 146 * A simple {@link TimerTask} that calls the private method checkForActions() 147 * to check whether there are any actions that should be executed. 148 * 149 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 150 * 151 * @created 29 Nov 2011 152 */ 153 private class CheckActionTask extends TimerTask 154 { 155 @Override 156 public void run() 157 { 158 Sequencer.this.checkForActions(); 159 } 160 } 161 162 /** The timekeeper that can provide the current time */ 163 private TimeKeeper<? extends Timecode> timeKeeper = null; 164 165 /** Check for action triggers every 1000 milliseconds by default */ 166 private long tickAccuracyMillis = 1000; 167 168 /** The set of events - using a {@link TreeSet} so that they are ordered */ 169 private final TreeSet<SequencerEvent> events = new TreeSet<SequencerEvent>(); 170 171 /** Whether to remove the events from the sequencer when they are complete. */ 172 private boolean removeEventsWhenComplete = true; 173 174 /** Whether to retry failed events */ 175 private boolean retryFailedEvents = false; 176 177 /** The timer to use for event scheduling */ 178 private Timer timer = null; 179 180 /** 181 * Default constructor that instantiates a sequencer that will check 182 * for actions every 10 milliseconds using the given time keeper. 183 * 184 * @param timeKeeper The timekeeper to use 185 */ 186 public Sequencer( final TimeKeeper<? extends Timecode> timeKeeper ) 187 { 188 this.timeKeeper = timeKeeper; 189 } 190 191 /** 192 * Constructor that instantiates a sequencer that will check for actions 193 * at the given rate using the given time keeper. 194 * 195 * @param timeKeeper The timekeeper to use 196 * @param tickAccuracyMillis How often the sequencer will check for events. 197 */ 198 public Sequencer( final TimeKeeper<? extends Timecode> timeKeeper, final long tickAccuracyMillis ) 199 { 200 this.timeKeeper = timeKeeper; 201 this.tickAccuracyMillis = tickAccuracyMillis; 202 } 203 204 /** 205 * Add an event to the sequencer. 206 * @param event The event to add. 207 */ 208 public void addEvent( final SequencerEvent event ) 209 { 210 this.events.add( event ); 211 } 212 213 /** 214 * {@inheritDoc} 215 * @see java.lang.Runnable#run() 216 */ 217 @Override 218 public void run() 219 { 220 new Thread( this.timeKeeper ).start(); 221 this.timer = new Timer(); 222 this.timer.scheduleAtFixedRate( new CheckActionTask(), 0, 223 this.tickAccuracyMillis ); 224 } 225 226 /** 227 * Returns the set of events in this sequencer. 228 * @return The set of events in this sequencer. 229 */ 230 public TreeSet<SequencerEvent> getEvents() 231 { 232 return this.events; 233 } 234 235 /** 236 * Check whether any actions should be run. 237 */ 238 private void checkForActions() 239 { 240 // Time how long it takes to do the processing, so we can 241 // subtract this time from the next timer 242 final long startProcessingTime = System.currentTimeMillis(); 243 244 // Get the current time 245 final Timecode tc = this.timeKeeper.getTime(); 246 final long t = tc.getTimecodeInMilliseconds(); 247 248 final Iterator<SequencerEvent> eventIterator = this.events.iterator(); 249 while( eventIterator.hasNext() ) 250 { 251 // Get the next event. 252 final SequencerEvent event = eventIterator.next(); 253 254 // If the even was supposed to be fired in the past or now, 255 // then we better get on and fire it. 256 if( !event.fired && event.timestamp <= t ) 257 { 258 // Perform the action 259 final boolean success = event.action.performAction(); 260 261 // Remove the event if that's what we're to do... 262 if( (success || !this.retryFailedEvents) && this.removeEventsWhenComplete ) 263 eventIterator.remove(); 264 else 265 { 266 // Set the event information 267 if( this.retryFailedEvents ) 268 event.fired = success; 269 else event.fired = true; 270 271 event.failed = !success; 272 } 273 } 274 } 275 276 // Set a new timer 277 final long processingTime = System.currentTimeMillis() - startProcessingTime; 278 long nextTime = this.tickAccuracyMillis - processingTime; 279 while( nextTime < 0 ) 280 nextTime += this.tickAccuracyMillis; 281 282 } 283 284 /** 285 * Sets whether failed events will be retried at the next processing 286 * time. 287 * 288 * @param rfe TRUE to retry failed events. 289 */ 290 public void setRetryFailedEvents( final boolean rfe ) 291 { 292 this.retryFailedEvents = rfe; 293 } 294 295 /** 296 * Sets whether to remove events from the event list when they have 297 * been completed. Doing so will speed up the processing 298 * at each event check but events will be lost. A way around this is to 299 * set up all your events then use {@link #getEvents()} to retrieve the 300 * list of events and clone it. Failed events will be removed from 301 * the list only if retryFailedEvents is set to false. 302 * 303 * @param rewc TRUE to remove successfully completed events. 304 */ 305 public void setRemoveEventsWhenComplete( final boolean rewc ) 306 { 307 this.removeEventsWhenComplete = rewc; 308 } 309}