/* An actor that iterates a contained actor over input arrays.
Copyright (c) 2007-2014 The Regents of the University of California.
All rights reserved.
Permission is hereby granted, without written agreement and without
license or royalty fees, to use, copy, modify, and distribute this
software and its documentation for any purpose, provided that the above
copyright notice and the following two paragraphs appear in all copies
of this software.
IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
ENHANCEMENTS, OR MODIFICATIONS.
PT_COPYRIGHT_VERSION_2
COPYRIGHTENDKEY
*/
package ptolemy.actor.lib.hoc;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;
import ptolemy.actor.Actor;
import ptolemy.actor.Director;
import ptolemy.actor.Executable;
import ptolemy.actor.IOPort;
import ptolemy.actor.NoTokenException;
import ptolemy.actor.QueueReceiver;
import ptolemy.actor.Receiver;
import ptolemy.actor.util.Time;
import ptolemy.data.DoubleToken;
import ptolemy.data.Token;
import ptolemy.data.expr.Parameter;
import ptolemy.data.type.BaseType;
import ptolemy.kernel.ComponentEntity;
import ptolemy.kernel.CompositeEntity;
import ptolemy.kernel.util.Attribute;
import ptolemy.kernel.util.IllegalActionException;
import ptolemy.kernel.util.InternalErrorException;
import ptolemy.kernel.util.NameDuplicationException;
import ptolemy.kernel.util.Settable;
import ptolemy.kernel.util.Workspace;
import ptolemy.util.MessageHandler;
///////////////////////////////////////////////////////////////////
//// RealTimeComposite
/**
This is a container for another actor that fires that other actor
at real times corresponding to the input time stamps. Its
ports are those of the contained actor. Given one or more events
with time stamp t at the input ports, it queues the events
to provide to a firing of the contained actor that is deferred to
occur when real time (since start of execution, in seconds) exceeds
or matches t. If real time already exceeds t, then the firing
may occur immediately.
In addition to the parameters of the contained actor, this actor
has a delay parameter. The value of this parameter is
the minimum delay (in model time) between an input event and
an output event that results from that input event.
If the enclosed actor produces no output, or if the time
of the outputs can be arbitrarily whatever current time
is in the model when they are produced, then delay
should be set to UNDEFINED. This is the default value.
With this value, the enclosed actor is
executed in a separate thread.
If the firing produces output events, then those are given time
stamps equal to the greater of the current model time of the
enclosing model and the current real time at which the outputs
are produced (in seconds since the start of execution). In
this case, the enclosed actor
does not regulate in any way the passage of time of the
enclosing model, so the time stamps of the enclosing model
could get arbitrarily far ahead of real time.
If the value of delay is 0.0 (zero), then the inside
model is run in the same thread as the enclosing model.
When this RealTimeComposite fires, the fire() method stalls
until real time matches the current time of the model, and
then invokes the enclosed model. If the enclosed model produces
any outputs, then those outputs have time stamps equal to the
time stamps of the input. Hence, from the perspective of DE
semantics, this actor has zero delay, even though it can
introduce real-time delay (which is indistinguishable from
just taking a long time to evaluate the fire() method).
Note that with delay = 0.0, this actor affects the
model in way similar to the synchronizeToRealTime
parameter of the director, except that only the events
provided to this actor are synchronized to real time, rather
than all events.
If the value of delay is positive, then the inside
model is run in a separate thread, just as if the value
were UNDEFINED, but in this case, this actor does
regulate the passage of time of the enclosing model.
In particular, given an event with time stamp t
it prevents model time from advancing past t
+ delay until the firing triggered by the event
has completed (which will be at some real time greater
than t). Any outputs produced by that firing are
assigned time stamps equal to the greater of t
+ delay and the current real time at which the
output is produced.
For various reasons, this actor is tricky to use. The most natural
domain to use it in is DE, providing it with input events with time
stamps that specify when to perform some action, such as an actuator
or display action. However, if the DE system is an open-loop system,
then model time of the DE system can get very far ahead of the
RealTimeComposite. It is helpful to use a feedback loop including
this RealTimeComposite to keep the DE model from getting ahead,
and to use the delay parameter judiciously as explained
above.
This actor may also be used in SDF and SR if the period parameter
of the director is set to something greater than zero.
This actor consumes its inputs and schedules execution in
its postfire() method, and hence in SR will behave as a strict
actor (all inputs must be known for anything to happen).
FIXME: For actors that are triggered by internal calls to fireAt(),
it seems that the delay needs to be no larger than the smallest
increment between calls to fireAt(). Is this correct? Why?
FIXME: If there is a PortParameter, the parameter gets updated when the
fire() method of this composite is invoked, which creates a nondeterminate
interaction with the deferred execution. See CompositeActor.fire().
@author Edward A. Lee
@version $Id: RealTimeComposite.java 70402 2014-10-23 00:52:20Z cxh $
@since Ptolemy II 6.1
@deprecated Use {@link ptolemy.actor.lib.hoc.ThreadedComposite} instead
@Pt.ProposedRating Yellow (eal)
@Pt.AcceptedRating Red (neuendor)
@deprecated Use ThreadedComposite instead.
*/
@Deprecated
public class RealTimeComposite extends MirrorComposite {
/** Create an actor with a name and a container.
* The container argument must not be null, or a
* NullPointerException will be thrown. This actor will use the
* workspace of the container for synchronization and version counts.
* If the name argument is null, then the name is set to the empty string.
* Increment the version of the workspace.
* @param container The container actor.
* @param name The name of this actor.
* @exception IllegalActionException If the container is incompatible
* with this actor.
* @exception NameDuplicationException If the name coincides with
* an actor already in the container.
*/
public RealTimeComposite(CompositeEntity container, String name)
throws IllegalActionException, NameDuplicationException {
super(container, name);
setClassName("ptolemy.actor.lib.hoc.RealTimeComposite");
new RealTimeDirector(this, "RealTimeDirector");
// Hidden parameter defining "UNDEFINED".
Parameter UNDEFINED = new Parameter(this, "UNDEFINED");
UNDEFINED.setVisibility(Settable.EXPERT);
UNDEFINED.setPersistent(false);
UNDEFINED.setExpression("-1.0");
delay = new Parameter(this, "delay");
delay.setTypeEquals(BaseType.DOUBLE);
delay.setExpression("UNDEFINED");
}
///////////////////////////////////////////////////////////////////
//// parameters ////
/** The maximum model-time delay between the input events and the
* output events. This is a double that defaults to UNDEFINED.
*/
public Parameter delay;
///////////////////////////////////////////////////////////////////
//// public methods ////
/** React to a change in an attribute. This method is called by
* a contained attribute when its value changes. In this base class,
* the method does nothing. In derived classes, this method may
* throw an exception, indicating that the new attribute value
* is invalid. It is up to the caller to restore the attribute
* to a valid value if an exception is thrown.
* @param attribute The attribute that changed.
* @exception IllegalActionException If the change is not acceptable
* to this container (not thrown in this base class).
*/
@Override
public void attributeChanged(Attribute attribute)
throws IllegalActionException {
if (attribute == delay) {
_delayValue = ((DoubleToken) delay.getToken()).doubleValue();
} else {
super.attributeChanged(attribute);
}
}
/** Clone the object into the specified workspace. This overrides
* the base class to instantiate a new RealTimeDirector.
* @param workspace The workspace for the new object.
* @return A new NamedObj.
* @exception CloneNotSupportedException If any of the attributes
* cannot be cloned.
* @see #exportMoML(java.io.Writer, int, String)
*/
@Override
public Object clone(Workspace workspace) throws CloneNotSupportedException {
RealTimeComposite result = (RealTimeComposite) super.clone(workspace);
try {
// Remove the old inner RealTimeDirector(s) that is(are) in the wrong workspace.
String realTimeDirectorName = null;
Iterator realTimeDirectors = result.attributeList(
RealTimeDirector.class).iterator();
while (realTimeDirectors.hasNext()) {
RealTimeDirector oldRealTimeDirector = (RealTimeDirector) realTimeDirectors
.next();
if (realTimeDirectorName == null) {
realTimeDirectorName = oldRealTimeDirector.getName();
}
oldRealTimeDirector.setContainer(null);
}
// Create a new RealTimeDirector that is in the right workspace.
RealTimeDirector realTimeDirector = result.new RealTimeDirector(
workspace);
realTimeDirector.setContainer(result);
realTimeDirector.setName(realTimeDirectorName);
} catch (Throwable throwable) {
throw new CloneNotSupportedException("Could not clone: "
+ throwable);
}
return result;
}
/** Invoke iterations on the contained actor of the
* container of this director repeatedly until either it runs out
* of input data or prefire() returns false. If postfire() of any
* actor returns false, then return false. Otherwise, return true.
* @return True to allow the thread to continue executing.
* @exception IllegalActionException If any called method of
* of the contained actor throws it, or if the contained
* actor is not opaque.
*/
public boolean fireContainedActors() throws IllegalActionException {
// Don't call "super.fire();" here, this actor contains its
// own director.
Iterator actors = entityList().iterator();
boolean postfireReturns = true;
while (actors.hasNext() && !_stopRequested) {
Actor actor = (Actor) actors.next();
if (!((ComponentEntity) actor).isOpaque()) {
throw new IllegalActionException(this,
"Inside actor is not opaque "
+ "(perhaps it needs a director).");
}
int result = Executable.COMPLETED;
while (result != Executable.NOT_READY) {
if (_debugging) {
_debug("Iterating actor: " + actor.getFullName());
}
if (_debugging) {
_debug("---- Iterating actor in associated thread: "
+ actor.getFullName());
}
result = actor.iterate(1);
// Should return if there are no more input data,
// irrespective of return value of prefire() of
// the actor, which is not reliable.
boolean outOfData = true;
Iterator inPorts = actor.inputPortList().iterator();
while (inPorts.hasNext()) {
IOPort port = (IOPort) inPorts.next();
for (int i = 0; i < port.getWidth(); i++) {
if (port.hasToken(i)) {
outOfData = false;
break;
}
}
}
if (outOfData) {
break;
}
if (result == Executable.STOP_ITERATING) {
if (_debugging) {
_debug("---- Actor requests halt: "
+ actor.getFullName());
}
postfireReturns = false;
break;
}
}
}
return postfireReturns;
}
///////////////////////////////////////////////////////////////////
//// private variables ////
/** The cached value of the delay parameter. */
private double _delayValue = 0.0;
/** Queue of times at which inside actors have requested firings.
* This queue is accessed from multiple threads, so it must be
* thread safe.
*/
private List