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}