001/* Produce an output after the time specified on the input has elapsed.
002
003 Copyright (c) 1998-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
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.actor.lib;
029
030import ptolemy.actor.Director;
031import ptolemy.actor.SuperdenseTimeDirector;
032import ptolemy.actor.util.CalendarQueue;
033import ptolemy.actor.util.Time;
034import ptolemy.actor.util.TimedEvent;
035import ptolemy.data.BooleanToken;
036import ptolemy.data.DoubleToken;
037import ptolemy.data.Token;
038import ptolemy.data.expr.Parameter;
039import ptolemy.data.type.BaseType;
040import ptolemy.kernel.CompositeEntity;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.NameDuplicationException;
043import ptolemy.kernel.util.Workspace;
044
045///////////////////////////////////////////////////////////////////
046//// ResettableTimer
047
048/**
049 Produce an output after the time specified on the input has elapsed.
050 If the input value is 0.0, then the output will be produced at the
051 next superdense time index (i.e., on the next firing, but at the current
052 time). If the input is negative, this actor will cancel the previously
053 requested output, if it has not yet been produced by the time the
054 negative input is received.
055 The value of the output is specified by the <i>value</i> parameter.
056 <p>
057 If the <i>preemptive</i> parameter is true (the default), then if
058 a new input arrives before the previous timer request has expired,
059 then that timer request is canceled. If an input arrives at the same
060 time that the previous timer request expires, an output is produced
061 immediately. The timer request is not cancelled.
062 <p>
063 If the <i>preemptive</i> parameter is
064 false, then the new input will cause the timer to start only after
065 the currently pending timer (if any is pending) expires.
066 <p>
067 When the <i>preemptive</i> parameter is true,
068 this actor resembles the VariableDelay actor in the DE domain, except that
069 arrivals of new inputs before the delay has expired causes the
070 previously scheduled output to be canceled. Also, the output value
071 is given in this actor
072 by the <i>value</i> parameter instead of by the input.
073 <p>
074 When the <i>preemptive</i> parameter is false,
075 this actor resembles the Server actor in the DE domain, except that
076 the time delay is specified by the single input.
077 The Server actor, by contrast, has separate inputs for service time and
078 payload, and the service time experienced by a payload depends
079 on the most recently arrived service time input <i>at the time
080 that the payload service begins</i>, not at the time the payload
081 arrives.
082 <p>
083 If this actor is used in a modal model and is in a mode that is
084 not active for some time, then no outputs will be produced for
085 the times it is inactive. If it becomes active again before the
086 scheduled time to produce an output, then it will produce that
087 output. If it is not preemptive, then upon
088 becoming active again, it will behave as if it had been active
089 during the intervening time, calculating when the outputs should
090 have been produced, and discarding them if the calculated time
091 falls in the inactive period.
092
093 @author Edward A. Lee
094 @version $Id$
095 @since Ptolemy II 8.0
096 @Pt.ProposedRating Yellow (eal)
097 @Pt.AcceptedRating Red (eal)
098 */
099public class ResettableTimer extends Transformer {
100    /** Construct an actor with the specified container and name.
101     *  Declare that the input can only receive double tokens and the output
102     *  has a data type the same as the value parameter.
103     *  @param container The container.
104     *  @param name The name of this actor.
105     *  @exception IllegalActionException If the entity cannot be contained
106     *   by the proposed container.
107     *  @exception NameDuplicationException If the container already has an
108     *   actor with this name.
109     */
110    public ResettableTimer(CompositeEntity container, String name)
111            throws NameDuplicationException, IllegalActionException {
112        super(container, name);
113        value = new Parameter(this, "value", new BooleanToken(true));
114        preemptive = new Parameter(this, "preemptive", new BooleanToken(true));
115        preemptive.setTypeEquals(BaseType.BOOLEAN);
116
117        input.setTypeEquals(BaseType.DOUBLE);
118        output.setTypeSameAs(value);
119    }
120
121    ///////////////////////////////////////////////////////////////////
122    ////                      ports and parameters                 ////
123
124    /** Indicator of whether new inputs cancel previous requests.
125     *  This is a boolean that defaults to true.
126     */
127    public Parameter preemptive;
128
129    /** The value produced at the output.  This can have any type,
130     *  and it defaults to a boolean token with value <i>true</i>.
131     */
132    public Parameter value;
133
134    ///////////////////////////////////////////////////////////////////
135    ////                         public methods                    ////
136
137    /** Clone the actor into the specified workspace. This calls the
138     *  base class and links the type of the <i>value</i> parameter
139     *  to the output.
140     *  @param workspace The workspace for the new object.
141     *  @return A new actor.
142     *  @exception CloneNotSupportedException If a derived class has
143     *   has an attribute that cannot be cloned.
144     */
145    @Override
146    public Object clone(Workspace workspace) throws CloneNotSupportedException {
147        ResettableTimer newObject = (ResettableTimer) super.clone(workspace);
148        newObject.output.setTypeSameAs(newObject.value);
149        return newObject;
150    }
151
152    /** Declare that the output does not immediately depend on the input.
153     *  @exception IllegalActionException If causality interface
154     *  cannot be computed.
155     *  @see #getCausalityInterface()
156     */
157    @Override
158    public void declareDelayDependency() throws IllegalActionException {
159        // Declare that output does not immediately depend on the input,
160        // though there is no lower bound on the time delay.
161        _declareDelayDependency(input, output, 0.0);
162    }
163
164    /** If an output is scheduled to be produced, then produce it.
165     *  @exception IllegalActionException If there is no director, or can not
166     *  send or get tokens from ports.
167     */
168    @Override
169    public void fire() throws IllegalActionException {
170        super.fire();
171        Director director = getDirector();
172        Time currentTime = director.getModelTime();
173        int currentMicrostep = 0;
174        if (director instanceof SuperdenseTimeDirector) {
175            currentMicrostep = ((SuperdenseTimeDirector) director).getIndex();
176        }
177        if (_debugging) {
178            _debug("Fire at time " + currentTime + ", microstep "
179                    + currentMicrostep);
180        }
181        int comparison = currentTime.compareTo(_pendingOutputTime);
182        if (comparison == 0 && currentMicrostep == _pendingOutputMicrostep) {
183            // Current pending requests matches current time.
184            if (_debugging) {
185                _debug("Time matches. Sending output.");
186            }
187            output.send(0, value.getToken());
188        } else if (_pendingOutputTime == Time.NEGATIVE_INFINITY) {
189            // No pending requests.
190            if (_debugging) {
191                _debug("No pending requests.");
192            }
193            return;
194        } else if (!((BooleanToken) preemptive.getToken()).booleanValue()) {
195            // Non-preemptive behavior. May need to catch up.
196            while (comparison > 0 || comparison == 0
197                    && currentMicrostep > _pendingOutputMicrostep) {
198                // Current time has passed the pending output time.
199                if (_debugging) {
200                    _debug("Time passed expected output time of "
201                            + _pendingOutputTime + ", microstep "
202                            + _pendingOutputMicrostep);
203                }
204                // May need to catch up.
205                if (_pendingRequests == null || _pendingRequests.size() == 0) {
206                    // No more pending requests.
207                    if (_debugging) {
208                        _debug("No more pending requests.");
209                    }
210                    _pendingOutputTime = Time.NEGATIVE_INFINITY;
211                    _pendingOutputMicrostep = 1;
212                    break;
213                }
214                // NOTE: The following changes the state of the actor, but this is
215                // safe as long as time does not roll back upon re-activation in
216                // a modal model.
217                TimedEvent event = (TimedEvent) _pendingRequests.take();
218                // Check for possible cancel event.
219                if (_pendingRequests.size() > 0) {
220                    TimedEvent possibleCancel = (TimedEvent) _pendingRequests
221                            .get();
222                    if (possibleCancel.contents == null) {
223                        // Found a cancel event.
224                        _pendingRequests.take();
225                        // Skip this event and look to see whether there is another.
226                        continue;
227                    }
228                }
229                // The time stamp of the event is the time the input
230                // arrived, and its value is the value of the input.
231                // Calculate the time at which the first pending event should be produced.
232                double delayValue = ((DoubleToken) event.contents)
233                        .doubleValue();
234                _pendingOutputTime = _pendingOutputTime.add(delayValue);
235                if (delayValue > 0) {
236                    _pendingOutputMicrostep = 1;
237                } else {
238                    _pendingOutputMicrostep = currentMicrostep + 1;
239                }
240                comparison = currentTime.compareTo(_pendingOutputTime);
241                if (comparison == 0
242                        && currentMicrostep == _pendingOutputMicrostep) {
243                    // Next pending request matches current time.
244                    if (_debugging) {
245                        _debug("Time matches pending output. Sending output.");
246                    }
247                    output.send(0, value.getToken());
248                    break;
249                }
250                // If the next pending request is still in the past,
251                // repeat by looking at the next event.
252            }
253        }
254    }
255
256    /** Initialize the internal states of this actor.
257     *  @exception IllegalActionException If a derived class throws it.
258     */
259    @Override
260    public void initialize() throws IllegalActionException {
261        super.initialize();
262        _pendingOutputTime = Time.NEGATIVE_INFINITY;
263        _pendingOutputMicrostep = 1;
264        if (_pendingRequests != null) {
265            _pendingRequests.clear();
266        }
267    }
268
269    /** Read the input (if any) and schedule a future output.
270     *  @exception IllegalActionException If reading the input,
271     *   or requesting a refiring throws it.
272     */
273    @Override
274    public boolean postfire() throws IllegalActionException {
275        Token inputToken = null;
276        double delayValue = -1;
277        boolean isPreemptive = ((BooleanToken) preemptive.getToken())
278                .booleanValue();
279        Director director = getDirector();
280        Time currentTime = director.getModelTime();
281        int currentMicrostep = 0;
282        if (director instanceof SuperdenseTimeDirector) {
283            currentMicrostep = ((SuperdenseTimeDirector) director).getIndex();
284        }
285        if (_debugging) {
286            _debug("Postfire at time " + currentTime + ", microstep "
287                    + currentMicrostep);
288        }
289        // Since postfire concludes the iteration, discard pending data if it was produced
290        // in fire().
291        if (currentTime.equals(_pendingOutputTime)
292                && currentMicrostep == _pendingOutputMicrostep) {
293            _pendingOutputTime = Time.NEGATIVE_INFINITY;
294            _pendingOutputMicrostep = 1;
295        }
296        if (input.hasToken(0)) {
297            inputToken = input.get(0);
298            delayValue = ((DoubleToken) inputToken).doubleValue();
299            if (_debugging) {
300                _debug("Read input " + delayValue);
301            }
302            if (delayValue < 0) {
303                // Cancel the previous request.
304                if (!isPreemptive && _pendingRequests != null
305                        && _pendingRequests.size() > 0) {
306                    // Append a cancel request to the event queue.
307                    TimedEvent cancelEvent = new TimedEvent(currentTime, null);
308                    _pendingRequests.put(cancelEvent);
309                } else {
310                    // Cancel the currently pending output value, if there one.
311                    if (_pendingOutputTime.compareTo(currentTime) >= 0) {
312                        _pendingOutputTime = Time.NEGATIVE_INFINITY;
313                        _pendingOutputMicrostep = 1;
314                    }
315                }
316                // Continue with the code below as if no new input has arrived.
317                inputToken = null;
318            }
319        }
320        if (isPreemptive) {
321            // Preemptive behavior.
322            if (inputToken != null) {
323                // If there is an input, update the pending output time.
324                _pendingOutputTime = currentTime.add(delayValue);
325                if (delayValue != 0.0) {
326                    _pendingOutputMicrostep = 1;
327                } else {
328                    _pendingOutputMicrostep = currentMicrostep + 1;
329                }
330                _fireAt(_pendingOutputTime);
331                if (_debugging) {
332                    _debug("Requesting refiring at " + _pendingOutputTime
333                            + ", microstep " + _pendingOutputMicrostep);
334                }
335            } else {
336                // There is no input. If the pending output matches the current time
337                // but the current microstep is too small, request refiring at the current
338                // time.
339                if (currentTime.equals(_pendingOutputTime)
340                        && currentMicrostep < _pendingOutputMicrostep) {
341                    // The firing is in response to a previous request, but the microstep is too early.
342                    // Note that this should not happen, but we are begin paranoid here.
343                    if (_debugging) {
344                        _debug("Microstep is too early. Refire at "
345                                + currentTime + ", microstep "
346                                + _pendingOutputMicrostep);
347                    }
348                    _fireAt(currentTime);
349                }
350            }
351        } else {
352            // Nonpreemptive behavior. If there is a new input,
353            // stick on the queue.
354            // First, make sure we have a queue.
355            if (_pendingRequests == null) {
356                // Non-preemptive behavior is the same if there is no
357                // pending request in the future.
358                _pendingRequests = new CalendarQueue(
359                        new TimedEvent.TimeComparator());
360            }
361            if (inputToken != null) {
362                if (_debugging) {
363                    _debug("Deferring start of timer with value " + inputToken);
364                }
365                _pendingRequests.put(new TimedEvent(currentTime, inputToken));
366            }
367            while (_pendingRequests.size() > 0
368                    && _pendingOutputTime == Time.NEGATIVE_INFINITY) {
369                // Get the first pending request and schedule a future firing,
370                // but only if there isn't already one pending.
371                TimedEvent event = (TimedEvent) _pendingRequests.take();
372                // Check for possible cancel event.
373                if (_pendingRequests.size() > 0) {
374                    TimedEvent possibleCancel = (TimedEvent) _pendingRequests
375                            .get();
376                    if (possibleCancel.contents == null) {
377                        // Found a cancel event.
378                        _pendingRequests.take();
379                        // Skip this event and look to see whether there is another.
380                        continue;
381                    }
382                }
383                delayValue = ((DoubleToken) event.contents).doubleValue();
384                _pendingOutputTime = currentTime.add(delayValue);
385                if (delayValue != 0.0) {
386                    _pendingOutputMicrostep = 1;
387                } else {
388                    _pendingOutputMicrostep = currentMicrostep + 1;
389                }
390                if (_debugging) {
391                    _debug("Requesting refiring at time " + _pendingOutputTime
392                            + ", microstep " + _pendingOutputMicrostep);
393                }
394                _fireAt(_pendingOutputTime);
395                break;
396            }
397        }
398        return super.postfire();
399    }
400
401    /** Override the base class to declare that the <i>output</i>
402     *  does not depend on the <i>input</i> in a firing.
403     *  @exception IllegalActionException If the superclass throws it.
404     */
405    @Override
406    public void preinitialize() throws IllegalActionException {
407        super.preinitialize();
408    }
409
410    ///////////////////////////////////////////////////////////////////
411    ////                         private variables                 ////
412
413    /** Pending output time. */
414    private Time _pendingOutputTime;
415
416    /** Pending output microstep. */
417    private int _pendingOutputMicrostep;
418
419    /** A local queue to store the pending requests. */
420    private CalendarQueue _pendingRequests;
421}