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}