An Executor-like class that executes
animation tasks serially in the AWT Event Thread.
This class allows you to manage type-ahead or click-ahead
in situations where each user input starts an animation.
An animation is typically a chain of runnables,
starting with a runnable that runs in the Event thread,
which chains to a runnable that does a delay in another thread,
which chains to another runnable that runs in the Event thread
(using invokeLater),
and so on until a final runnable runs in the Event thread.
Here is a scenario to show how this class works.
This executor doesn't run a Runnable.
Instead it runs an AnimationExecutor.Task,
which has a run method
that takes two arguments.
The first argument tells the task whether any other animations are queued.
The second argument is a Runnable the task must run when it completes.
See the Javadoc
Public Domain
package com.yost.util;
import javax.swing.*;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* An {@link java.util.concurrent.Executor}-like class that executes
* animation tasks serially in the AWT Event Thread.
* This class allows you to manage type-ahead or click-ahead
* in situations where each user input starts an animation.
* </p><p>
* An animation is typically a chain of runnables,
* starting with a runnable that runs in the Event thread,
* which chains to a runnable that does a delay in another thread,
* which chains to another runnable that runs in the Event thread
* (using {@link SwingUtilities#invokeLater(Runnable) invokeLater}),
* and so on until a final runnable runs in the Event thread.
* </p><p>
* Here is a scenario to show how this class works.
* <ul>
* <li>A user clicks a button, and the executor starts an animation in the Event thread.
* <li>Before this first animation has finished,
* the user quickly clicks twice more,
* queueing up two more animations.
* <li>The executor calls a method on the currently-running first animation
* to notify it that another animation is waiting to run.
* <li>The running animation takes the hint and cuts its animation short.
* <li>The first animation completes and runs the completion runnable it was given
* by the executor.
* <li>This runnable starts the second animation with an argument
* indicating that there is another animation (the third one) waiting to run.
* <li>The second animation takes the hint and does minimal, if any, animation.
* <li>When the second animation completes,
* its completion runnable starts the third animation
* with an argument indicating that there are no other queued animations,
* so the third animation runs normally.
* </ul>
* </p><p>
* This executor doesn't run a {@link java.lang.Runnable}.
* Instead it runs an {@link AnimationExecutor.Task},
* which has a {@link Task#run(boolean, Runnable) run} method
* that takes two arguments.
* The first argument tells the task whether any other animations are queued.
* The second argument is a {@link Runnable} the task must run when it completes.
* </p><p>
* <a href="http://Yost.com/computers/java/AnimationExecutor">
* http://Yost.com/computers/java/AnimationExecutor</a>
* Public Domain
*
* @author Dave@Yost.com
* @version 2007-08-17
* @license Public Domain
*/
public class AnimationExecutor {
private final Queue<Task> tasks = new ConcurrentLinkedQueue<Task>();
private final TaskIsDone taskIsDone = new TaskIsDone();
private Task activeTask;
/**
* Execute a task in the Event thread, serialized with all other tasks
* submitted via this executor.
*
* @param task EventThreadSerializedExecutor.Task
*/
public synchronized void execute(Task task) {
if (!isActive()) {
start(task);
} else {
activeTask.thereAreTasksWaiting();
tasks.add(task);
}
}
//----------------------------------------------------------------------------
private synchronized void schedule() {
if (!isActive()) {
Task task = tasks.poll();
if (task != null) {
start(task);
}
}
}
private void start(Task task) {
setActiveTask(task);
if (!SwingUtilities.isEventDispatchThread()) {
activeTask.run(tasks.isEmpty(), taskIsDone);
} else {
SwingUtilities.invokeLater(new Runnable() { public void run() {
activeTask.run(tasks.isEmpty(), taskIsDone);
}});
}
}
private synchronized boolean isActive() {
return activeTask != null;
}
private synchronized void setActiveTask(Task task) {
activeTask = task;
}
//============================================================================
private class TaskIsDone implements Runnable {
public void run() {
setActiveTask(null);
schedule();
}
}
//============================================================================
/**
* The animation task to be run.
*/
public interface Task {
/**
* Run a task in the Event thread.
*
* @param isQueueEmpty the executor's queue is empty at the time of this call
* @param taskIsDone {@code Runnable} that must be called when the task is done
*/
void run(boolean isQueueEmpty, final Runnable taskIsDone);
/**
* Called to notify the task that at least one task is now waiting
* in the executor's queue.
* If an animation is in progress, it should probably be cut short.
*/
void thereAreTasksWaiting();
}
}
|