if (_eventQueue == null) {
throw new IllegalActionException(
"Fire method called before the preinitialize method.");
}
Actor actorToFire = null;
DEEvent lastFoundEvent = null;
DEEvent nextEvent = null;
// Keep taking events out until there are no more events that have the
// same tag and go to the same destination actor, or until the queue is
// empty, or until a stop is requested.
// LOOPLABEL::GetNextEvent
while (!_stopRequested) {
// Get the next event from the event queue.
if (_stopWhenQueueIsEmpty) {
if (_eventQueue.isEmpty()) {
// If the event queue is empty,
// jump out of the loop: LOOPLABEL::GetNextEvent
break;
}
}
if (!_isTopLevel()) {
// If the director is not at the top level.
if (_eventQueue.isEmpty()) {
// This could happen if the container simply fires
// this composite at times it chooses. Most directors
// do this (SDF, SR, Continuous, etc.). It can also
// happen if an input is provided to a parameter port
// and the container is DE.
// In all these cases, no actors inside need to be
// fired.
break;
} else {
// For an embedded DE director, the following code prevents
// the director from reacting to future events with bigger
// time values in their tags.
// For a top-level DE director, there is no such constraint
// because the top-level director is responsible to advance
// simulation by increasing the model tag.
nextEvent = _eventQueue.get();
// An embedded director should process events
// that only happen at the current tag.
// If the event is in the past, that is an error,
// because the event should have been consumed in prefire().
if ((nextEvent.timeStamp().compareTo(getModelTime()) < 0)) {
// missed an event
throw new IllegalActionException(
"Fire: Missed an event: the next event tag "
+ nextEvent.timeStamp()
+ " :: "
+ nextEvent.microstep()
+ " is earlier than the current model tag "
+ getModelTime() + " :: " + _microstep
+ " !");
}
// If the event is in the future time, it is ignored
// and will be processed later.
// Note that it is fine for the new event to have a bigger
// microstep. This indicates that the embedded director is
// going to advance microstep.
// Note that conceptually, the outside and inside DE models
// share the same microstep and the current design and
// implementation assures that. However, the embedded DE
// director does ask for the microstep of the upper level
// DE director. They keep their own count of their
// microsteps. The reason for this is to avoid the
// difficulties caused by passing information across modal
// model layers.
if ((nextEvent.timeStamp().compareTo(getModelTime()) > 0)) {
// reset the next event
nextEvent = null;
// jump out of the loop: LOOPLABEL::GetNextEvent
break;
}
}
} else { // if (!topLevel)
// If the director is at the top level
// If the event queue is empty, normally
// a blocking read is performed on the queue.
// However, there are two conditions that the blocking
// read is not performed, which are checked below.
if (_eventQueue.isEmpty()) {
// The two conditions are:
// 1. An actor to be fired has been found; or
// 2. There are no more events in the event queue,
// and the current time is equal to the stop time.
if ((actorToFire != null)
|| (getModelTime().equals(getModelStopTime()))) {
// jump out of the loop: LOOPLABEL::GetNextEvent
break;
}
}
// Otherwise, if the event queue is empty,
// a blocking read is performed on the queue.
// stopFire() needs to also cause this to fall out!
while (_eventQueue.isEmpty() && !_stopRequested
&& !_stopFireRequested) {
if (_debugging) {
_debug("Queue is empty. Waiting for input events.");
}
Thread.yield();
synchronized (_eventQueue) {
// Need to check _stopFireRequested again inside
// the synchronized block, because it may have changed.
if (_eventQueue.isEmpty() && !_stopFireRequested) {
try {
// NOTE: Release the read access held
// by this thread to prevent deadlocks.
workspace().wait(_eventQueue);
} catch (InterruptedException e) {
// If the wait is interrupted,
// then stop waiting.
break;
}
}
} // Close synchronized block
} // Close the blocking read while loop
// To reach this point, either the event queue is not empty,
// or _stopRequested or _stopFireRequested is true, or an interrupted exception
// happened.
if (_eventQueue.isEmpty()) {
// Stop is requested or this method is interrupted.
// jump out of the loop: LOOPLABEL::GetNextEvent
break;
} else {
// At least one event is found in the event queue.
nextEvent = _eventQueue.get();
}
}
// This is the end of the different behaviors of embedded and
// top-level directors on getting the next event.
// When this point is reached, the nextEvent can not be null.
// In the rest of this method, this is not checked any more.
if (nextEvent == null) {
throw new IllegalActionException("The event to be handled"
+ " can not be null!");
}
// If the actorToFire is null, find the destination actor associated
// with the event just found. Store this event as lastFoundEvent and
// go back to continue the GetNextEvent loop.
// Otherwise, check whether the event just found goes to the
// same actor to be fired. If so, dequeue that event and continue
// the GetNextEvent loop. Otherwise, jump out of the GetNextEvent
// loop.
// TESTIT
if (actorToFire == null) {
// If the actorToFire is not set yet,
// find the actor associated with the event just found,
// and update the current tag with the event tag.
Time currentTime;
if (_synchronizeToRealTime) {
// If synchronized to the real time.
synchronized (_eventQueue) {
while (!_stopRequested && !_stopFireRequested) {
lastFoundEvent = _eventQueue.get();
currentTime = lastFoundEvent.timeStamp();
long elapsedTime = System.currentTimeMillis()
- _realStartTime;
// NOTE: We assume that the elapsed time can be
// safely cast to a double. This means that
// the DE domain has an upper limit on running
// time of Double.MAX_VALUE milliseconds.
double elapsedTimeInSeconds = elapsedTime / 1000.0;
ptolemy.actor.util.Time elapsed
= new ptolemy.actor.util.Time(this, elapsedTimeInSeconds);
if (currentTime.compareTo(elapsed) <= 0) {
break;
}
// NOTE: We used to do the following, but it had a limitation.
// In particular, if any user code also calculated the elapsed
// time and then constructed a Time object to post an event
// on the event queue, there was no assurance that the quantization
// would be the same, and hence it was possible for that event
// to be in the past when posted, even if done in the same thread.
// To ensure that the comparison of current time against model time
// always yields the same result, we have to do the comparison using
// the Time class, which is what the event queue does.
/*
if (currentTime.getDoubleValue() <= elapsedTimeInSeconds) {
break;
}*/
long timeToWait = (long) (currentTime.subtract(
elapsed).getDoubleValue() * 1000.0);
if (timeToWait > 0) {
if (_debugging) {
_debug("Waiting for real time to pass: "
+ timeToWait);
}
try {
// NOTE: The built-in Java wait() method
// does not release the
// locks on the workspace, which would block
// UI interactions and may cause deadlocks.
// SOLUTION: workspace.wait(object, long).
_workspace.wait(_eventQueue, timeToWait);
// If we get here and either stop() or stopFire()
// was called, then it is not time to process any event,
// so we should leave it in the event queue.
if (_stopRequested || _stopFireRequested) {
return null;
}
} catch (InterruptedException ex) {
// Continue executing.
}
}
} // while
} // sync
} // if (_synchronizeToRealTime)
// Consume the earliest event from the queue. The event must be
// obtained here, since a new event could have been enqueued
// into the queue while the queue was waiting. For example,
// an IO interrupt event.
// FIXME: The above statement is misleading. How could the
// newly inserted event happen earlier than the previously
// first event in the queue? It may be possible in the
// distributed DE models, but should not happen in DE models.
// Will this cause problems, such as setting time backwards?
// TESTIT How to??
synchronized (_eventQueue) {
lastFoundEvent = _eventQueue.take();
currentTime = lastFoundEvent.timeStamp();
actorToFire = lastFoundEvent.actor();
// NOTE: The _enqueueEvent method discards the events
// for disabled actors.
if ((_disabledActors != null)
&& _disabledActors.contains(actorToFire)) {
// This actor has requested not to be fired again.
if (_debugging) {
_debug("Skipping disabled actor: ",
((Nameable) actorToFire).getFullName());
}
actorToFire = null;
// start a new iteration of the loop:
// LOOPLABEL::GetNextEvent
continue;
}
// Advance the current time to the event time.
// NOTE: This is the only place that the model time changes.
setModelTime(currentTime);
// Advance the current microstep to the event microstep.
_microstep = lastFoundEvent.microstep();
}
// Exceeding stop time means the current time is strictly
// bigger than the model stop time.
if (currentTime.compareTo(getModelStopTime()) > 0) {
if (_debugging) {
_debug("Current time has passed the stop time.");
}
_exceedStopTime = true;
return null;
}
} else { // if (actorToFire == null)
// We have already found an event and the actor to react to it.
// Check whether the newly found event has the same tag
// and depth. If so, the destination actor should be the same,
// and they are handled at the same time. For example, a pure
// event and a trigger event that go to the same actor.
if (nextEvent.hasTheSameTagAndDepthAs(lastFoundEvent)) {
// Consume the event from the queue and discard it.
// FIXME: This isn't right! The microstep is only incremented
// by fireAt() calls. The Repeat actor, for one, produces a sequence
// of outputs, each of which will have the same microstep.
_eventQueue.take();
} else if (nextEvent.hasTheSameTagAs(lastFoundEvent)) {
// The actor to be fired is the container, we remove all
// the trigger events with the same tag from the event
// queue such that the executive director of this DE model
// can react to these events.
Actor actor = nextEvent.actor();
if (actor == actorToFire) {
_eventQueue.take();
} else {
// Next event has a future tag or a different destination.
break;