001/* Helper class containing utility methods for directors with a period parameter.
002
003 Copyright (c) 2000-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023 PT_COPYRIGHT_VERSION_2
024 COPYRIGHTENDKEY
025
026 */
027
028package ptolemy.actor.util;
029
030import ptolemy.actor.Actor;
031import ptolemy.actor.CompositeActor;
032import ptolemy.actor.Director;
033import ptolemy.actor.Manager;
034import ptolemy.actor.SuperdenseTimeDirector;
035import ptolemy.kernel.util.IllegalActionException;
036
037///////////////////////////////////////////////////////////////////
038//// PeriodicDirectorHelper
039
040/**
041 This is a helper class for directors implementing PeriodicDirector.
042 It collects common functionality to avoid code duplication.
043
044 @see PeriodicDirector
045 @author Edward A. Lee
046 @version $Id$
047 @since Ptolemy II 8.0
048 @Pt.ProposedRating Yellow (eal)
049 @Pt.AcceptedRating Red (eal)
050 */
051public class PeriodicDirectorHelper {
052
053    /** Construct a new helper.
054     *  @param director The associated director.
055     *  @exception IllegalActionException If the argument is not an instance of
056     *   Director.
057     */
058    public PeriodicDirectorHelper(PeriodicDirector director)
059            throws IllegalActionException {
060        if (!(director instanceof Director)) {
061            throw new IllegalActionException(director,
062                    "Helper must be passed a Director");
063        }
064        _director = director;
065    }
066
067    ///////////////////////////////////////////////////////////////////
068    ////                         public methods                    ////
069
070    /** Request a firing of the given actor at the given absolute
071     *  time, and return the time at which the specified will be
072     *  fired. If the <i>period</i> is 0.0 and there is no enclosing
073     *  director, then this method returns the current time. If
074     *  the period is 0.0 and there is an enclosing director, then
075     *  this method delegates to the enclosing director, returning
076     *  whatever it returns. If the <i>period</i> is not 0.0, then
077     *  this method checks to see whether the
078     *  requested time is equal to the current time plus an integer
079     *  multiple of the period. If so, it returns the requested time.
080     *  If not, it returns the earliest future time that exceeds the
081     *  requested time.
082     *  @param actor The actor scheduled to be fired.
083     *  @param time The requested time.
084     *  @exception IllegalActionException If the operation is not
085     *    permissible (e.g. the given time is in the past).
086     *  @return Either the requested time or the current time plus the
087     *   period or whatever the enclosing director returns.
088     */
089    public Time fireAt(Actor actor, Time time) throws IllegalActionException {
090        // NOTE: It is not correct to just delegate to the enclosing director, because
091        // prefire() will refuse to fire at that time if it isn't a multiple of
092        // the period.
093        Actor container = (Actor) _director.getContainer();
094        double periodValue = _director.periodValue();
095        Time currentTime = ((Director) _director).getModelTime();
096
097        // Check the most common case first.
098        if (periodValue == 0.0) {
099            if (container != null) {
100                Director executiveDirector = container.getExecutiveDirector();
101                // Some composites, such as RunCompositeActor want to be treated
102                // as if they are at the top level even though they have an executive
103                // director, so be sure to check _isTopLevel().
104                if (executiveDirector != null && _director.isEmbedded()) {
105                    return executiveDirector.fireAt(container, time);
106                }
107            }
108            // All subsequent firings will be at the current time.
109            return currentTime;
110        }
111        // Now we know the period is not 0.0.
112        // Return current time plus the period,
113        // or some multiple of the period.
114        // NOTE: this is potentially very expensive to compute precisely
115        // because the Time class has an infinite range and only supports
116        // precise addition. Determining whether the argument satisfies
117        // the criterion seems difficult. Hence, we check to be sure
118        // that the test is worth doing.
119        // NOTE: If we switch to an integer time base, we don't have this
120        // problem. With an integer time based we can avoid to accumulate
121        // "futureTime = futureTime.add(periodValue)", but we can instead
122        // compute directly the final futureTime:
123        // futureTime = currentTime + ((time - currentTime) / periodValue + 1) * periodValue
124
125        // First check to see whether we are in the initialize phase, in
126        // which case, return the start time.
127        if (container != null) {
128            Manager manager = ((CompositeActor) container).getManager();
129            if (manager.getState().equals(Manager.INITIALIZING)) {
130                return currentTime;
131            }
132        }
133        // If the requested time is infinite, equal to current time, or in
134        // the past, then the respond with the next available firing.
135        if (time.isInfinite() || currentTime.compareTo(time) >= 0) {
136            // Either the requested time is infinite or it is in the past or present.
137            Time result = currentTime.add(periodValue);
138            return result;
139        }
140        Time futureTime = currentTime;
141        while (time.compareTo(futureTime) >= 0) {
142            futureTime = futureTime.add(periodValue);
143            if (futureTime.equals(time)) {
144                return time;
145            }
146        }
147        Time result = futureTime;
148        return result;
149    }
150
151    /** If the <i>period</i> parameter is greater than zero, then
152     *  request a first firing of the executive director, if there
153     *  is one.
154     *  @exception IllegalActionException If the superclass throws it.
155     */
156    public void initialize() throws IllegalActionException {
157        if (_director.periodValue() > 0.0) {
158            _nextFiringTime = ((Director) _director).getModelTime();
159
160            // In case we are embedded within a timed director, request a first
161            // firing.
162            Actor container = (Actor) _director.getContainer();
163            Director executiveDirector = container.getExecutiveDirector();
164            // Some composites, such as RunCompositeActor want to be treated
165            // as if they are at the top level even though they have an executive
166            // director, so be sure to check _isTopLevel().
167            if (executiveDirector != null && _director.isEmbedded()) {
168                executiveDirector.fireAtCurrentTime(container);
169            }
170        }
171    }
172
173    /** If the <i>period</i> parameter is greater than 0.0, then
174     *  if the associated director is at the top level, then increment
175     *  its time by the specified period, and otherwise request a refiring
176     *  at the current time plus the period.
177     *  @exception IllegalActionException If the <i>period</i> parameter
178     *   cannot be evaluated.
179     */
180    public void postfire() throws IllegalActionException {
181        double periodValue = _director.periodValue();
182        if (periodValue > 0.0) {
183            Actor container = (Actor) _director.getContainer();
184            Director executiveDirector = container.getExecutiveDirector();
185            Time currentTime = ((Director) _director).getModelTime();
186            _nextFiringTime = currentTime.add(periodValue);
187
188            // Some composites, such as RunCompositeActor want to be treated
189            // as if they are at the top level even though they have an executive
190            // director, so be sure to check _isTopLevel().
191            if (executiveDirector != null && _director.isEmbedded()) {
192                // Not at the top level.
193                // NOTE: The following throws an exception if the time
194                // requested cannot be honored by the enclosed director
195                // Presumably, if the user set the period, he or she wanted
196                // that behavior.
197                _fireContainerAt(_nextFiringTime);
198            } else {
199                // Increment time to the next cycle.
200                ((Director) _director).setModelTime(_nextFiringTime);
201            }
202            // Set the microstep to 1 for the next firing
203            // because the next firing will occur at
204            // a strictly greater time, and the microstep is always
205            // 1 when this director fires.
206            if (_director instanceof SuperdenseTimeDirector) {
207                ((SuperdenseTimeDirector) _director).setIndex(1);
208            }
209        }
210    }
211
212    /** If the <i>period</i> value is greater than zero, then return
213     *  true if the current time is a multiple of the value and the
214     *  current microstep is 1. The associated director expects
215     *  to always be fired at microstep 1.  If there is an enclosing
216     *  director that does not understand superdense time, then we
217     *  ignore that microstep and agree to fire anyway. This means
218     *  simply that we will fire the first time that current time
219     *  matches a multiple of the period.
220     *  @exception IllegalActionException If the <i>period</i>
221     *   parameter cannot be evaluated.
222     *  @return true If either the <i>period</i> has value 0.0 or
223     *   the current time is a multiple of the period.
224     */
225    public boolean prefire() throws IllegalActionException {
226        // If the superdense time index is zero
227        // then either this is the first iteration, or
228        // it was set to zero in the last call to postfire().
229        // In the latter case, we expect to be invoked at
230        // the previous time plus the non-zero period.
231        // If the enclosing time does not match or exceed
232        // that (the latter could occur if we have been
233        // dormant in an old-style modal model), then return false.
234        // If the enclosing time exceeds the local current
235        // time, then we must have been dormant. We need to
236        // catch up.
237        double periodValue = _director.periodValue();
238        if (periodValue > 0.0) {
239            Time enclosingTime = ((Director) _director).getModelTime();
240            int comparison = _nextFiringTime.compareTo(enclosingTime);
241            if (comparison == 0) {
242                // The enclosing time matches the time we expect to fire.
243                // If either these is no enclosing director or it does
244                // not understand superdense time, then we ignore the
245                // microstep. Otherwise, we insist that it be 1 in order
246                // to fire.
247                Director executiveDirector = ((Actor) _director.getContainer())
248                        .getExecutiveDirector();
249                // Some composites, such as RunCompositeActor want to be treated
250                // as if they are at the top level even though they have an executive
251                // director, so be sure to check _isTopLevel().
252                if (executiveDirector instanceof SuperdenseTimeDirector
253                        && _director.isEmbedded()) {
254                    int index = ((SuperdenseTimeDirector) executiveDirector)
255                            .getIndex();
256                    // NOTE: Normally, we expect the index to be 1 for a discrete
257                    // event, but it could be greater than 1.
258                    // E.g., if a destination mode contains a DE system with the period
259                    // parameter set to something non-zero, then it will want to fire
260                    // at the time that the transition is taken, but the microstep will
261                    // be 2, not 1, because the transition is taken in microstep 1.
262                    if (index < 1) {
263                        // No need to call fireContainerAt() because
264                        // presumably we already did that.
265                        return false;
266                    }
267                    return true;
268                }
269            } else if (comparison > 0) {
270                // If the enclosing director is a Ptides director, firing out of timestamp
271                // order is possible, thus return true here.
272                CompositeActor container = (CompositeActor) _director
273                        .getContainer();
274                while (container.getContainer() != null) {
275                    container = (CompositeActor) container.getContainer();
276                    if (container.getDirector().getName()
277                            .startsWith("Ptides")) {
278                        return true;
279                    }
280                }
281
282                // Enclosing time has not yet reached our expected firing time.
283                // No need to call fireAt(), since presumably we already
284                // did that in postfire().
285                return false;
286            } else {
287                // Enclosing time has exceeded our expected firing time.
288                // This should not happen with an enclosing director with
289                // full support for fireAt(). The enclosing director
290                // could be another periodic director. In this case,
291                // we should just increase the next firing time.
292                // Or alternatively, we might actually have been prefired
293                // at the expected firing time but refused to fire, e.g.,
294                // if there were not sufficient input tokens avaialble.
295                while (comparison < 0) {
296                    _nextFiringTime = _nextFiringTime.add(periodValue);
297                    comparison = _nextFiringTime.compareTo(enclosingTime);
298                }
299                if (comparison == 0) {
300                    return true;
301                } else {
302                    _fireContainerAt(_nextFiringTime);
303                    return false;
304                }
305                // An alternative would be to throw an exception.
306                /*
307                throw new IllegalActionException(_director,
308                        "Director expected to be fired at time "
309                        + _nextFiringTime
310                        + " but instead is being fired at time "
311                        + enclosingTime);
312                 */
313                // NOTE: An alternative would be to catch up. The code
314                // to do that is here.
315                /*
316                while (_nextFiringTime.compareTo(enclosingTime) < 0) {
317                    _nextFiringTime = _nextFiringTime.add(periodValue);
318                }
319                if (_nextFiringTime.compareTo(enclosingTime) == 0) {
320                    // The caught up time matches a multiple of the period.
321                    // If the enclosing director supports superdense time, then
322                    // make sure we are at index zero before agreeing to fire.
323                    if (executiveDirector instanceof SuperdenseTimeDirector) {
324                        int index = ((SuperdenseTimeDirector) executiveDirector)
325                                .getIndex();
326                        if (index == 0) {
327                            return true;
328                        }
329                        // If the index is not zero, do not agree to fire, but request
330                        // a refiring at the next multiple of the period.
331                        _nextFiringTime = _nextFiringTime.add(periodValue);
332                        _fireContainerAt(_nextFiringTime);
333                        return false;
334                    }
335                    // If the enclosing director does not support superdense time,
336                    // then agree to fire.
337                    return true;
338                } else {
339                    // NOTE: The following throws an exception if the time
340                    // requested cannot be honored by the enclosed director
341                    // Presumably, if the user set the period, he or she wanted
342                    // that behavior.
343                    _fireContainerAt(_nextFiringTime);
344                    return false;
345                }
346                 */
347            }
348        }
349        // If period is zero, then just return true.
350        return true;
351    }
352
353    ///////////////////////////////////////////////////////////////////
354    ////                         private methods                   ////
355
356    /** Request a firing of the container of the director at the specified time
357     *  and throw an exception if the executive director does not agree to
358     *  do it at the requested time. If there is no executive director (this
359     *  director is at the top level), then ignore the request.
360     *  This method is essentially a duplicate of the method in Director,
361     *  which is not accessible.
362     *  @param time The requested time.
363     *  @return The time that the executive director indicates it will fire this
364     *   director, or an instance of Time with value Double.NEGATIVE_INFINITY
365     *   if there is no executive director.
366     *  @exception IllegalActionException If the director does not
367     *   agree to fire the actor at the specified time, or if there
368     *   is no director.
369     */
370    private Time _fireContainerAt(Time time) throws IllegalActionException {
371        Actor container = (Actor) _director.getContainer();
372        if (container != null) {
373            // Use microstep 1 because periodic directors are always discrete.
374            Time result = ((Director) _director).fireContainerAt(time, 1);
375            if (!result.equals(time)) {
376                throw new IllegalActionException(_director,
377                        "Timing incompatibility error: "
378                                + " enclosing director is unable to fire "
379                                + container.getName()
380                                + " at the requested time: " + time
381                                + ". It responds it will fire it at: " + result
382                                + ".");
383            }
384            return result;
385        }
386        return new Time((Director) _director, Double.NEGATIVE_INFINITY);
387    }
388
389    ///////////////////////////////////////////////////////////////////
390    ////                         private variables                 ////
391
392    /** The associated director. */
393    private PeriodicDirector _director;
394
395    /** The expected next firing time. */
396    private Time _nextFiringTime;
397}