001/* A clock that keeps track of model time at a level of the model hierarchy. 002 003 Copyright (c) 2012-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 */ 027package ptolemy.actor; 028 029import java.util.Collection; 030 031import ptolemy.actor.parameters.SharedParameter; 032import ptolemy.actor.util.Time; 033import ptolemy.data.DoubleToken; 034import ptolemy.data.expr.Parameter; 035import ptolemy.data.type.BaseType; 036import ptolemy.kernel.util.AbstractSettableAttribute; 037import ptolemy.kernel.util.Attribute; 038import ptolemy.kernel.util.IllegalActionException; 039import ptolemy.kernel.util.NameDuplicationException; 040import ptolemy.kernel.util.NamedObj; 041import ptolemy.kernel.util.Settable; 042import ptolemy.kernel.util.ValueListener; 043import ptolemy.kernel.util.Workspace; 044import ptolemy.math.ExtendedMath; 045 046/** A clock that keeps track of model time at a level of the model hierarchy 047 * and relates it to the time of the enclosing model, if there is one. The time 048 * of the enclosing model is referred to as the environment time. This 049 * clock has a notion of local time and committed time. The committed time 050 * is "simultaneous" with the environment time. 051 * 052 * <p>The local time is 053 * not allowed to move backwards past the committed time, but ahead 054 * of that time, it can move around at will. </p> 055 * <p> 056 * There is no way of explicitly committing time, but 057 * several methods have the side effect of committing the current 058 * local time. For example, {@link #setClockDrift(double)} will commit 059 * the current local time and change the clock drift. So will 060 * {@link #start()} and {@link #stop()} </p> 061 * 062 * <p> 063 * This class implements the AbstractSettableAttribute interface because 064 * we want the localClock to be shown as a parameter in the editor 065 * dialogue of a director. A better implementation would be to derive 066 * LocalClock from Attribute and make changes to vergil such that 067 * Attributes are displayed in the dialogue, however, for the moment, 068 * the required changes are too complex. 069 * The value of the clock is exposed as an attribute that, by default, 070 * is non editable. The clock drift is a contained attribute that can 071 * be modified. </p> 072 * 073 * <p> This class also specifies a <i>globalTimeResolution</i> 074 * parameter. This is a double with default 1E-10, which is 075 * 10<sup>-10</sup>. All time values are rounded to the nearest 076 * multiple of this value. If the value is changed during a run, an 077 * exception is thrown. This is a shared parameter, which means that 078 * all instances of Director in the model will have the same value 079 * for this parameter. Changing one of them changes all of them. </p> 080 * 081 * <p>FIXME: Setting of clock drift must be controlled because it commits 082 * time. </p> 083 * 084 * @author Ilge Akkaya, Patricia Derler, Edward A. Lee, Christos Stergiou, Michael Zimmer 085 * @version $Id$ 086 * @since Ptolemy II 10.0 087 * @Pt.ProposedRating yellow (eal) 088 * @Pt.AcceptedRating red (eal) 089 */ 090public class LocalClock extends AbstractSettableAttribute { 091 092 /** Construct an attribute with the given name contained by the specified 093 * entity. The container argument must not be null, or a 094 * NullPointerException will be thrown. This attribute will use the 095 * workspace of the container for synchronization and version counts. 096 * If the name argument is null, then the name is set to the empty string. 097 * Increment the version of the workspace. 098 * @param container The container. 099 * @param name The name of this attribute. 100 * @exception IllegalActionException If the attribute is not of an 101 * acceptable class for the container, or if the name contains a period. 102 * @exception NameDuplicationException If the name coincides with 103 * an attribute already in the container. 104 */ 105 public LocalClock(NamedObj container, String name) 106 throws IllegalActionException, NameDuplicationException { 107 super(container, name); 108 globalTimeResolution = new SharedParameter(this, "globalTimeResolution", 109 null, "1E-10"); 110 111 clockDrift = new Parameter(this, "clockRate"); 112 clockDrift.setExpression("1.0"); 113 clockDrift.setTypeEquals(BaseType.DOUBLE); 114 115 // Make sure getCurrentTime() never returns null. 116 _localTime = Time.NEGATIVE_INFINITY; 117 _drift = 1.0; 118 _visibility = Settable.NOT_EDITABLE; 119 } 120 121 /////////////////////////////////////////////////////////////////// 122 //// parameters //// 123 124 /** The time precision used by this director. All time values are 125 * rounded to the nearest multiple of this number. This is a double 126 * that defaults to "1E-10" which is 10<sup>-10</sup>. 127 * This is a shared parameter, meaning that changing one instance 128 * in a model results in all instances being changed. 129 */ 130 public SharedParameter globalTimeResolution; 131 132 /** The drift of the local clock with respect to the environment 133 * clock. If this is a top level director the clock drift has no 134 * consequence. The value is a double that is initialized to 135 * 1.0 which means that the local clock drift matches the one 136 * of the environment. 137 */ 138 public Parameter clockDrift; 139 140 /////////////////////////////////////////////////////////////////// 141 //// public method //// 142 143 /** This method has to be implemented for the AbstractSettableAttribute 144 * interface. This interface is only needed for the LocalClock to 145 * show up in the configuration dialogue of the container (the director). 146 * The method will not be used for this class so the implementation 147 * is empty. 148 * @param listener The listener to be added. 149 * @see #removeValueListener(ValueListener) 150 */ 151 @Override 152 public void addValueListener(ValueListener listener) { 153 // nothing to do. 154 } 155 156 /** Delegate the call to the director, which handles changes 157 * to the parameters of the clock. 158 * @param attribute The attribute that changed. 159 * @exception IllegalActionException If the director throws it. 160 */ 161 @Override 162 public void attributeChanged(Attribute attribute) 163 throws IllegalActionException { 164 if (attribute == clockDrift) { 165 double drift; 166 drift = ((DoubleToken) clockDrift.getToken()).doubleValue(); 167 if (drift != getClockDrift()) { 168 setClockDrift(drift); 169 } 170 } else if (attribute == globalTimeResolution) { 171 // This is extremely frequently used, so cache the value. 172 // Prevent this from changing during a run! 173 double newResolution = ((DoubleToken) globalTimeResolution 174 .getToken()).doubleValue(); 175 176 // FindBugs reports this comparison as a problem, but it 177 // is not an issue because we usually don't calculate 178 // _timeResolution, we set it. 179 if (newResolution != getTimeResolution()) { 180 NamedObj container = getContainer().getContainer(); 181 182 if (container instanceof Actor) { 183 Manager manager = ((Actor) container).getManager(); 184 185 if (manager != null) { 186 Manager.State state = manager.getState(); 187 188 if (state != Manager.IDLE 189 && state != Manager.PREINITIALIZING) { 190 throw new IllegalActionException(this, 191 "Cannot change timePrecision during a run."); 192 } 193 } 194 } 195 196 if (newResolution <= ExtendedMath.DOUBLE_PRECISION_SMALLEST_NORMALIZED_POSITIVE_DOUBLE) { 197 throw new IllegalActionException(this, 198 "Invalid timeResolution: " + newResolution 199 + "\n The value must be " 200 + "greater than the smallest, normalized, " 201 + "positive, double value with a double " 202 + "precision: " 203 + ExtendedMath.DOUBLE_PRECISION_SMALLEST_NORMALIZED_POSITIVE_DOUBLE); 204 } 205 206 setTimeResolution(newResolution); 207 } 208 } 209 super.attributeChanged(attribute); 210 } 211 212 /** Clone the object into the specified workspace. 213 * @param workspace The workspace for the cloned object. 214 * @return The cloned object. 215 * @exception CloneNotSupportedException If thrown by super class. 216 */ 217 @Override 218 public Object clone(Workspace workspace) throws CloneNotSupportedException { 219 LocalClock newObject = (LocalClock) super.clone(workspace); 220 newObject._localTime = Time.NEGATIVE_INFINITY; 221 newObject._offset = ((Director) getContainer())._zeroTime; 222 newObject._drift = 1.0; 223 return newObject; 224 } 225 226 /** Get clock drift. 227 * @return The clock drift. 228 * @see #setClockDrift(double) 229 */ 230 public double getClockDrift() { 231 // FIXME: This returns a double, what does 1.0 mean? 0.0? 232 return _drift; 233 } 234 235 /** Get the environment time that corresponds to the given local time. 236 * The given local time is required to be either equal to or 237 * greater than the committed time when this method is called. 238 * @param time The local Time. 239 * @return The corresponding environment Time. 240 * @exception IllegalActionException If the specified local time 241 * is in the past, or if Time objects cannot be created. 242 */ 243 public Time getEnvironmentTimeForLocalTime(Time time) 244 throws IllegalActionException { 245 if (time.compareTo(_lastCommitLocalTime) < 0) { 246 throw new IllegalActionException( 247 "Cannot compute environment time for local time " + time 248 + " because " 249 + "the last commit of the local time occurred at " 250 + "local time " + _lastCommitLocalTime); 251 } 252 Time localTimePassedSinceCommit = time.subtract(_lastCommitLocalTime); 253 Time environmentTimePassedSinceCommit = localTimePassedSinceCommit; 254 if (_drift != 1.0) { 255 double environmentTimePassedSinceCommitDoubleValue = environmentTimePassedSinceCommit 256 .getDoubleValue(); 257 environmentTimePassedSinceCommitDoubleValue = environmentTimePassedSinceCommitDoubleValue 258 / _drift; 259 environmentTimePassedSinceCommit = new Time( 260 (Director) getContainer(), 261 environmentTimePassedSinceCommitDoubleValue); 262 } 263 Time environmentTime = _lastCommitEnvironmentTime 264 .add(environmentTimePassedSinceCommit); 265 return environmentTime; 266 } 267 268 /** Return the local time. 269 * @return The local time as a string value. 270 */ 271 @Override 272 public String getExpression() { 273 if (_localTime == null) { 274 return ""; 275 } else { 276 return String.valueOf(_localTime); 277 } 278 } 279 280 /** Get current local time. If it has never been set, then this will return 281 * Time.NEGATIVE_INFINITY. The returned value may have been set by 282 * {@link #setLocalTime(Time)}. 283 * @return The current local time. 284 * @see #setLocalTime(Time) 285 */ 286 public Time getLocalTime() { 287 return _localTime; 288 } 289 290 /** Get the local time that corresponds to the current environment time. 291 * The current environment time is required to be greater than or equal 292 * to the environment time corresponding to the last committed local time. 293 * @return The corresponding local time. 294 * @exception IllegalActionException If Time objects cannot be created, or 295 * if the current environment time is less than the time 296 * corresponding to the last committed local time. 297 */ 298 public Time getLocalTimeForCurrentEnvironmentTime() 299 throws IllegalActionException { 300 return getLocalTimeForEnvironmentTime( 301 ((Director) getContainer()).getEnvironmentTime()); 302 } 303 304 /** Get the local time that corresponds to the given environment time. 305 * The given environment time is required to be greater than or equal 306 * to the environment time corresponding to the last committed local time. 307 * @param time The environment time. 308 * @return The corresponding local time. 309 * @exception IllegalActionException If the specified environment time 310 * is less than the environment time corresponding to the last 311 * committed local time, or if Time objects cannot be created. 312 */ 313 public Time getLocalTimeForEnvironmentTime(Time time) 314 throws IllegalActionException { 315 if (_lastCommitEnvironmentTime == null 316 || time.compareTo(_lastCommitEnvironmentTime) < 0) { 317 throw new IllegalActionException( 318 "Cannot compute local time for environment time " + time 319 + " because " 320 + "the last commit of the local time occurred at " 321 + "local time " + _lastCommitLocalTime + " which " 322 + "corresponds to environment time " 323 + _lastCommitEnvironmentTime); 324 } 325 326 Time environmentTimePassedSinceCommit = time 327 .subtract(_lastCommitEnvironmentTime); 328 Time localTimePassedSinceCommit = environmentTimePassedSinceCommit; 329 if (_drift != 1.0) { 330 double localTimePassedSinceCommitDoubleValue = environmentTimePassedSinceCommit 331 .getDoubleValue(); 332 localTimePassedSinceCommitDoubleValue = localTimePassedSinceCommitDoubleValue 333 * _drift; 334 localTimePassedSinceCommit = new Time((Director) getContainer(), 335 localTimePassedSinceCommitDoubleValue); 336 } 337 Time localTime = _lastCommitEnvironmentTime.subtract(_offset) 338 .add(localTimePassedSinceCommit); 339 return localTime; 340 } 341 342 /** Get the time resolution of the model. The time resolution is 343 * the value of the <i>timeResolution</i> parameter. This is the 344 * smallest time unit for the model. 345 * @return The time resolution of the model. 346 * @see #setTimeResolution(double) 347 */ 348 public final double getTimeResolution() { 349 // This method is final for performance reason. 350 return _timeResolution; 351 } 352 353 /** The LocalClock is not editable, thus visibility is 354 * always set to NOT_EDITABLE. 355 * @return NOT_EDITABLE. 356 * @see #setVisibility(Visibility) 357 */ 358 @Override 359 public Visibility getVisibility() { 360 return _visibility; 361 } 362 363 /** Initialize parameters that cannot be initialized in the 364 * constructor. For instance, Time objects cannot be created 365 * in the constructor because the time resolution might not be 366 * known yet. Older models have the timeResolution parameter 367 * specified in the director which will only be loaded by the 368 * MOMLParser after the director is initialized. 369 */ 370 public void initialize() { 371 _offset = ((Director) getContainer())._zeroTime; 372 } 373 374 /** This method has to be implemented for the AbstractSettableAttribute 375 * interface. This interface is only needed for the LocalClock to 376 * show up in the configuration dialogue of the container (the director). 377 * The method will not be used for this class so the implementation 378 * is empty. 379 * @param listener The listener to be removed. 380 * @see #addValueListener(ValueListener) 381 */ 382 @Override 383 public void removeValueListener(ValueListener listener) { 384 // nothing to do. 385 } 386 387 /** Set local time and commit. 388 * This is allowed to set time earlier than the 389 * last committed local time. 390 * @param time The new local time. 391 */ 392 public void resetLocalTime(Time time) { 393 if (_debugging) { 394 _debug("reset local time to " + time); 395 } 396 _localTime = time; 397 _commit(); 398 } 399 400 /** Set the new clock drift and commit it. 401 * @param drift New clock drift. 402 * @exception IllegalActionException If the specified drift is 403 * non-positive. 404 * @see #getClockDrift() 405 */ 406 public void setClockDrift(double drift) throws IllegalActionException { 407 // FIXME: This returns a double, what does 1.0 mean? 0.0? 408 if (drift <= 0.0) { 409 throw new IllegalActionException(getContainer(), 410 "Illegal clock drift: " + drift 411 + ". Clock drift is required to be positive."); 412 } 413 _drift = drift; 414 _commit(); 415 } 416 417 /** Set local time without committing. 418 * This is not allowed to set 419 * time earlier than the last committed local time. 420 * @param time The new local time. 421 * @exception IllegalActionException If the specified time is 422 * earlier than the current time. 423 * @see #getLocalTime() 424 */ 425 public void setLocalTime(Time time) throws IllegalActionException { 426 if (_lastCommitLocalTime != null 427 && time.compareTo(_lastCommitLocalTime) < 0) { 428 throw new IllegalActionException(getContainer(), 429 "Cannot set local time to " + time 430 + ", which is earlier than the last committed current time " 431 + _lastCommitLocalTime); 432 } 433 _localTime = time; 434 } 435 436 /** Set time resolution. 437 * @param timeResolution The new time resolution. 438 * @see #getTimeResolution() 439 */ 440 public void setTimeResolution(double timeResolution) { 441 _timeResolution = timeResolution; 442 } 443 444 /** This method has to be implemented for the AbstractSettableAttribute 445 * interface. This interface is only needed for the LocalClock to 446 * show up in the configuration dialogue of the container (the director). 447 * This method does not do anything because visibility is always 448 * NOT_EDITABLE. 449 * @param visibility The new visibility. 450 * @see #getVisibility() 451 */ 452 @Override 453 public void setVisibility(Visibility visibility) { 454 _visibility = visibility; 455 } 456 457 /** Start the clock with the current drift as specified by the 458 * last call to {@link #setClockDrift(double)}. 459 * If {@link #setClockDrift(double)} has never been called, then 460 * the drift is 1.0. 461 * This method commits current local time. 462 */ 463 public void start() { 464 _commit(); 465 } 466 467 /** Stop the clock. The current time will remain the 468 * same as its current value until the next call to 469 * {@link #start()}. 470 * This method commits current local time. 471 */ 472 public void stop() { 473 _commit(); 474 } 475 476 /** This method has to be implemented for the AbstractSettableAttribute 477 * interface. This interface is only needed for the LocalClock to 478 * show up in the configuration dialogue of the container (the director). 479 * The value of the LocalClock does not need validation, thus this method 480 * does not do anything. 481 * @return Null. 482 * @exception IllegalActionException Not thrown in this base class. 483 */ 484 @Override 485 public Collection validate() throws IllegalActionException { 486 return null; 487 } 488 489 /////////////////////////////////////////////////////////////////// 490 //// private methods //// 491 492 /** Commit the current local time. 493 */ 494 private void _commit() { 495 if (_offset == null || _localTime == null) { // not initialized. 496 return; 497 } 498 // skip if local time has never been set. 499 if (_localTime != Time.NEGATIVE_INFINITY) { 500 Time environmentTime = ((Director) getContainer()) 501 .getEnvironmentTime(); 502 if (environmentTime == null) { 503 _offset = ((Director) getContainer())._zeroTime; 504 } else { 505 _offset = environmentTime.subtract(_localTime); 506 } 507 _lastCommitEnvironmentTime = environmentTime; 508 _lastCommitLocalTime = _localTime; 509 } 510 } 511 512 /////////////////////////////////////////////////////////////////// 513 //// private variable //// 514 515 /** The current time of this clock. */ 516 private Time _localTime; 517 518 /** The current clock drift. 519 * The drift is initialized to 1.0 which means that the 520 * local time matches to the environment time. 521 */ 522 private double _drift; 523 524 /** The environment time at which a change to local time, drift, 525 * or resumption occurred. 526 */ 527 private Time _lastCommitEnvironmentTime; 528 529 /** The local time at which a change to local time, drift, 530 * or resumption occurred. 531 */ 532 private Time _lastCommitLocalTime; 533 534 /** The environment time minus the local time at the the point 535 * at which a commit occurred. 536 * By default, the offset is zero. 537 */ 538 private Time _offset; 539 540 private Visibility _visibility; 541 542 /** Time resolution cache, with a reasonable default value. */ 543 private double _timeResolution = 1E-10; 544 545}