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}