AnimationExecutor - Serially execute Java animation tasks


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


AnimationExecutor.java

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




http://Yost.com/computers/java/AnimationExecutor/index.html - this page
2007-05-20 Created
2007-08-17 Modified