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(); } } |