/*
* Copyright (c) 2002-@year@, University of Maryland
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided
* that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this list of conditions
* and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions
* and the following disclaimer in the documentation and/or other materials provided with the
* distribution.
*
* Neither the name of the University of Maryland nor the names of its contributors may be used to
* endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
* TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* Piccolo was written at the Human-Computer Interaction Laboratory www.cs.umd.edu/hcil by Jesse Grosjean
* under the supervision of Ben Bederson. The Piccolo website is www.cs.umd.edu/hcil/piccolo.
*/
package edu.umd.cs.piccolo.activities;
import edu.umd.cs.piccolo.util.PUtil;
/**
* <b>PActivity</b> controls some time dependent aspect of Piccolo, such
* as animation. Once created activities must be scheduled with the
* PActivityScheduler managed by the PRoot to run. They are automatically
* removed from the scheduler when the animation has finished.
* <P>
* See the PNode.animate*() methods for an example of how to set up and run
* different activities.
* <P>
* @version 1.0
* @author Jesse Grosjean
*/
public class PActivity {
public static final int TERMINATE_WITHOUT_FINISHING = 0;
public static final int TERMINATE_AND_FINISH = 1;
public static final int TERMINATE_AND_FINISH_IF_STEPPING = 2;
private PActivityScheduler scheduler;
private long startTime;
private long duration;
private long stepRate;
private PActivityDelegate delegate;
private boolean stepping;
private long nextStepTime;
/**
* <b>PActivityDelegate</b> is used by classes to learn about and act on the
* different states that a PActivity goes through, such as when the activity
* starts and stops stepping.
*/
public interface PActivityDelegate {
public void activityStarted(PActivity activity);
public void activityStepped(PActivity activity);
public void activityFinished(PActivity activity);
}
/**
* Constructs a new PActivity.
*
* @param aDuration the amount of time that this activity should take to complete, -1 for infinite.
*/
public PActivity(long aDuration) {
this(aDuration, PUtil.DEFAULT_ACTIVITY_STEP_RATE);
}
/**
* Constructs a new PActivity.
*
* @param aDuration the amount of time that this activity should take to complete, -1 for infinite.
* @param aStepRate the maximum rate that this activity should receive step events.
*/
public PActivity(long aDuration, long aStepRate) {
this(aDuration, aStepRate, System.currentTimeMillis());
}
/**
* Constructs a new PActivity.
*
* @param aDuration the amount of time that this activity should take to complete, -1 for infinite.
* @param aStepRate the maximum rate that this activity should receive step events.
* @param aStartTime the time (relative to System.currentTimeMillis()) that
* this activity should start.
*/
public PActivity(long aDuration, long aStepRate, long aStartTime) {
duration = aDuration;
stepRate = aStepRate;
startTime = aStartTime;
nextStepTime = aStartTime;
stepping = false;
}
//****************************************************************
// Basics
//****************************************************************
/**
* Return the time that this activity should start running in PRoot
* global time. When this time is reached (or soon after) this activity
* will have its startStepping() method called.
*/
public long getStartTime() {
return startTime;
}
/**
* Set the time that this activity should start running in PRoot
* global time. When this time is reached (or soon after) this activity
* will have its startStepping() method called.
*/
public void setStartTime(long aTriggerTime) {
startTime = aTriggerTime;
}
/**
* Return the amount of time that this activity should delay
* between steps.
*/
public long getStepRate() {
return stepRate;
}
/**
* Set the amount of time that this activity should delay
* between steps.
*/
public void setStepRate(long aStepRate) {
stepRate = aStepRate;
}
public long getNextStepTime() {
return nextStepTime;
}
/**
* Return the amount of time that this activity should take to complete,
* after the startStepping method is called.
*/
public long getDuration() {
return duration;
}
/**
* Set the amount of time that this activity should take to complete,
* after the startStepping method is called.
*/
public void setDuration(long aDuration) {
duration = aDuration;
}
public PActivityScheduler getActivityScheduler() {
return scheduler;
}
public void setActivityScheduler(PActivityScheduler aScheduler) {
scheduler = aScheduler;
}
//****************************************************************
// Stepping
//****************************************************************
/**
* Return true if this activity is stepping.
*/
public boolean isStepping() {
return stepping;
}
/**
* Return true if this activity is performing an animation. This is used
* by the PCanvas to determine if it should set the render quality to
* PCanvas.animatingRenderQuality or not for each frame it renders.
*/
protected boolean isAnimation() {
return false;
}
/**
* This method is called right before an activity is scheduled to start
* running. After this method is called step() will be called until the
* activity finishes.
*/
protected void activityStarted() {
if (delegate != null)
delegate.activityStarted(this);
}
/**
* This is the method that most activities override to perform their
* behavior. It will be called repeatedly when the activity is running.
*
* @param elapsedTime the amount of time that has passed relative to the activities startTime.
*/
protected void activityStep(long elapsedTime) {
if (delegate != null)
delegate.activityStepped(this);
}
/**
* This method is called after an activity is has finished running and the
* activity has been removed from the PActivityScheduler queue.
*/
protected void activityFinished() {
if (delegate != null)
delegate.activityFinished(this);
}
/**
* Get the delegate for this activity. The delegate is notified when
* the activity starts and stops stepping.
*/
public PActivityDelegate getDelegate() {
return delegate;
}
/**
* Set the delegate for this activity. The delegate is notified when
* the activity starts and stops stepping.
*/
public void setDelegate(PActivityDelegate delegate) {
this.delegate = delegate;
}
//****************************************************************
// Controlling
//****************************************************************
/**
* Schedules this activity to start after the first activity has finished.
* Note that no link is created between these activities, if the startTime
* or duration of the first activity is later changed this activities start
* time will not be updated to reflect that change.
*/
public void startAfter(PActivity first) {
setStartTime(first.getStartTime() + first.getDuration());
}
/**
* Stop this activity immediately, and remove it from the activity
* scheduler. The default termination behavior is call activityFinished
* if the activity is currently stepping. Use terminate(terminationBehavior)
* use a different termination behavior.
*/
public void terminate() {
terminate(TERMINATE_AND_FINISH_IF_STEPPING);
}
/**
* Stop this activity immediately, and remove it from the activity
* scheduler. The termination behavior determines when and if activityStarted
* and activityFinished get called. The possible termination behaviors are as
* follow:
*
* TERMINATE_WITHOUT_FINISHING - The method activityFinished will never get called and
* so the activity will be terminated midway.
* TERMINATE_AND_FINISH - The method activityFinished will always get called. And so the
* activity will always end in it's completed state. If the activity has not yet started
* the method activityStarted will also be called.
* TERMINATE_AND_FINISH_IF_STEPPING - The method activityFinished will only be called
* if the activity has previously started.
*/
public void terminate(int terminationBehavior) {
if (scheduler != null) {
scheduler.removeActivity(this);
}
switch (terminationBehavior) {
case TERMINATE_WITHOUT_FINISHING:
stepping = false;
break;
case TERMINATE_AND_FINISH:
if (stepping) {
stepping = false;
activityFinished();
} else {
activityStarted();
activityFinished();
}
break;
case TERMINATE_AND_FINISH_IF_STEPPING:
if (stepping) {
stepping = false;
activityFinished();
}
break;
}
}
/**
* The activity scheduler calls this method and it is here
* that the activity decides if it should do a step or not for the
* given time.
*/
public long processStep(long currentTime) {
// if before start time
if (currentTime < startTime) {
return startTime - currentTime;
}
// if past stop time
if (currentTime > getStopTime()) {
if (stepping) {
stepping = false;
scheduler.removeActivity(this);
activityFinished();
} else {
activityStarted();
scheduler.removeActivity(this);
activityFinished();
}
return -1;
}
// else should be stepping
if (!stepping) {
activityStarted();
stepping = true;
}
if (currentTime >= nextStepTime) {
activityStep(currentTime - startTime);
nextStepTime = currentTime + stepRate;
}
return stepRate;
}
/**
* Return the time when this activity should finish running. At this time
* (or soon after) the stoppedStepping method will be called
*/
public long getStopTime() {
if (duration == -1) {
return Long.MAX_VALUE;
}
return startTime + duration;
}
//****************************************************************
// Debugging - methods for debugging
//****************************************************************
/**
* Returns a string representation of this object for debugging purposes.
*/
public String toString() {
String result = super.toString().replaceAll(".*\\.", "");
return result + "[" + paramString() + "]";
}
/**
* Returns a string representing the state of this node. This method is
* intended to be used only for debugging purposes, and the content and
* format of the returned string may vary between implementations. The
* returned string may be empty but may not be <code>null</code>.
*
* @return a string representation of this node's state
*/
protected String paramString() {
StringBuffer result = new StringBuffer();
result.append("startTime=" + startTime);
result.append(",duration=" + duration);
result.append(",stepRate=" + stepRate);
if (stepping) result.append(",stepping");
result.append(",nextStepTime=" + nextStepTime);
return result.toString();
}
}