Commits

Michael Ludwig committed e2c7df9

Refactor Controller and ControllerManager into a thread-safe Task, Job, and Scheduler API.

Comments (0)

Files changed (13)

src/main/java/com/lhkbob/entreri/ComponentRepository.java

             store.clone(fromTemplate.index, store.property, instance.index);
         }
 
-        // fire add-event listener after cloning is completed
-        system.getControllerManager().fireComponentAdd(instance);
         return instance;
     }
 
      */
     public Component<T> addComponent(int entityIndex) {
         Component<T> instance = allocateComponent(entityIndex);
-
-        // fire add-event listener after initialization is completed
-        system.getControllerManager().fireComponentAdd(instance);
         return instance;
     }
 
         // This code works even if componentIndex is 0
         Component<T> oldComponent = components[componentIndex];
         if (oldComponent != null) {
-            // perform component clean up before data is invalidated
-            system.getControllerManager().fireComponentRemove(oldComponent);
             oldComponent.index = 0;
         }
 

src/main/java/com/lhkbob/entreri/Controller.java

-/*
- * Entreri, an entity-component framework in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * 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.
- *
- * 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 HOLDER 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.
- */
-package com.lhkbob.entreri;
-
-/**
- * <p>
- * Controllers are functional processors of the entities and components within
- * an EntitySystem. Different Controller implementations have different
- * purposes, such as updating transforms, computing physics or AI, or rendering
- * a scene. Generally controller implementations should be as small and as
- * independent as possible to allow for reuse and easy composability.
- * </p>
- * <p>
- * Controllers also have event listener method hooks so that they can be
- * notified when they are added or removed from an EntitySystem, or when an
- * Entity or component in a system they're attached to is added or removed.
- * </p>
- * <p>
- * Controller instances should only be added to a single EntitySystem at a time.
- * If a controller is later removed, it can safely be added to a new
- * EntitySystem. Controller implementations are responsible for ensuring this in
- * their {@link #init(EntitySystem)} and {@link #destroy()} methods. It is
- * recommended to extend {@link SimpleController} because it handles the
- * necessary logic.
- * </p>
- * 
- * @author Michael Ludwig
- */
-public interface Controller {
-    /**
-     * Invoke operations on the controller's EntitySystem that must occur before
-     * the main processing. All controllers in a system will have their
-     * preProcess() method called before the first process() method is called.
-     * 
-     * @param dt The elapsed time since the last processing
-     */
-    public void preProcess(double dt);
-
-    /**
-     * Invoke controller specific operations to process the EntitySystem. All
-     * controllers will have already had their preProcess() method invoked and
-     * none will have postProcess() invoked until process() has completed for
-     * all controllers in the system.
-     * 
-     * @param dt The elapsed time since the last processing
-     */
-    public void process(double dt);
-
-    /**
-     * Invoked at the end of a processing phase after all controllers in a
-     * system have completed their process() methods.
-     * 
-     * @param dt The elapsed time since the last processing
-     */
-    public void postProcess(double dt);
-
-    /**
-     * Invoked when a Controller is added to an EntitySystem. This method should
-     * initialize any system-specific state used by the controller, such as
-     * ComponentIterators, ComponentData instances, or decorated properties.
-     * 
-     * @param system The new EntitySystem this controller is attached to
-     * @throws IllegalStateException if the controller is already attached to
-     *             another entity system
-     */
-    public void init(EntitySystem system);
-
-    /**
-     * Invoked when a Controller is detached from its EntitySystem. This should
-     * clean up any decorated properties added to the system. It should also
-     * update its internal state to make it safe to call
-     * {@link #init(EntitySystem)} later.
-     */
-    public void destroy();
-
-    /**
-     * Invoked when an Entity is added to the Controller's EntitySystem. If the
-     * Entity was added with the {@link EntitySystem#addEntity(Entity)} method,
-     * this is invoked before template's components have been copied to the
-     * entity.
-     * 
-     * @param e The new entity
-     */
-    public void onEntityAdd(Entity e);
-
-    /**
-     * <p>
-     * Invoked when an Entity is removed from the Controller's EntitySystem. The
-     * Entity is still alive when this is called, but will be removed completely
-     * after all Controllers have processed the remove.
-     * </p>
-     * <p>
-     * When an entity is removed, all of its components are removed first, so
-     * {@link #onComponentRemove(Component)} will be invoked on all attached
-     * components before this listener method is caled.
-     * </p>
-     * 
-     * @param e The entity being removed
-     */
-    public void onEntityRemove(Entity e);
-
-    /**
-     * Invoked when the given Component is added to an Entity. The component is
-     * alive so its Entity can be queried in the usual manner.
-     * 
-     * @param c The new component
-     */
-    public void onComponentAdd(Component<?> c);
-
-    /**
-     * Invoked when the given Component is about to be removed. At the time this
-     * is invoked, the Component is still alive. This may be invoked in response
-     * to removing the Component from the entity, or if the component's owning
-     * entity is removed.
-     * 
-     * @param c The component being removed
-     */
-    public void onComponentRemove(Component<?> c);
-
-    /**
-     * Invoked when any other Controller reports a Result to the
-     * ControllerManager of this controller. Implementations may filter based on
-     * the type or value of the result. Results will only be reported during the
-     * processing of a phase. Singleton results will be reported at most once.
-     * 
-     * @param r The result being reported
-     */
-    public void report(Result r);
-
-    /**
-     * @return The current EntitySystem processed by this controller, or null if
-     *         it is not added to any EntitySystem
-     */
-    public EntitySystem getEntitySystem();
-}

src/main/java/com/lhkbob/entreri/ControllerManager.java

-/*
- * Entreri, an entity-component framework in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * 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.
- *
- * 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 HOLDER 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.
- */
-package com.lhkbob.entreri;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * <p>
- * ControllerManager is a utility that manages the list of Controllers that can
- * process an EntitySystem.
- * </p>
- * <p>
- * Additionally, the ControllerManager is used to invoke the phase processing
- * methods on each Controller on its configured EntitySystem.
- * </p>
- * 
- * @author Michael Ludwig
- */
-public class ControllerManager {
-    /**
-     * The Phase enum represents the different phases of processing that an
-     * EntitySystem can go through during what is often considered a "frame".
-     */
-    public static enum Phase {
-        /**
-         * The PREPROCESS phase is invoked before all other phases. All
-         * controllers in a manager will have their
-         * {@link Controller#preProcess(double)} method called before moving to
-         * the next phase.
-         */
-        PREPROCESS,
-
-        /**
-         * The PROCESS phase is invoked between PREPROCESS and POSTPROCESS. All
-         * controllers in the manager will have their
-         * {@link Controller#process(double)} method called before moving to the
-         * next phase.
-         */
-        PROCESS,
-
-        /**
-         * <p>
-         * The POSTPROCESS phase is invoked after PREPROCESS and POSTPROCESS.
-         * All controllers in their manager will have their
-         * {@link Controller#postProcess(double)} method called before the frame
-         * is completed.
-         * </p>
-         * <p>
-         * Singleton result tracking is reset after this phase completes so that
-         * singleton results can be reported at the start of the next frame.
-         * Execution timing is also completed at the end of this phase.
-         * </p>
-         */
-        POSTPROCESS
-    }
-
-    private final List<Controller> controllers;
-    private final Map<Controller, ProfileData> profile;
-
-    private final EntitySystem system;
-
-    private long lastProcessTime;
-    private final Set<Class<?>> singletonResults;
-
-    private Phase currentPhase;
-
-    /**
-     * Create a new ControllerManager that will store controllers and controller
-     * data for processing the given EntitySystem.
-     * 
-     * @param system The EntitySystem that is managed by this controller manager
-     * @throws NullPointerException if system is null
-     */
-    public ControllerManager(EntitySystem system) {
-        if (system == null) {
-            throw new NullPointerException("EntitySystem cannot be null");
-        }
-
-        this.system = system;
-        controllers = new ArrayList<Controller>();
-
-        profile = new HashMap<Controller, ProfileData>();
-        singletonResults = new HashSet<Class<?>>();
-        lastProcessTime = -1L;
-
-        currentPhase = null;
-    }
-
-    /**
-     * Get an unmodifiable view of the controllers registered with this manager.
-     * 
-     * @return The controllers in the manager
-     */
-    public List<Controller> getControllers() {
-        return Collections.unmodifiableList(controllers);
-    }
-
-    /**
-     * Report the given Result to all registered Controllers in this manager.
-     * This can only be called while a Phase is being processed.
-     * 
-     * @see Result
-     * @param result The result to supply to registered controllers
-     * @throws NullPointerException if result is null
-     * @throws IllegalStateException if not processing a phase, or if a
-     *             singleton result of the same type has already been reported
-     *             this frame
-     */
-    public void report(Result result) {
-        if (currentPhase == null) {
-            throw new IllegalStateException("Can only report results while processing a phase");
-        }
-        if (result.isSingleton()) {
-            if (!singletonResults.add(result.getClass())) {
-                throw new IllegalStateException("Singleton result of type " + result.getClass() + " already reported this frame");
-            }
-        }
-
-        int ct = controllers.size();
-        for (int i = 0; i < ct; i++) {
-            controllers.get(i).report(result);
-        }
-    }
-
-    /**
-     * <p>
-     * Add a Controller to this manager so that subsequent calls to
-     * {@link #process()} and its varieties will invoke the process hooks on the
-     * controller. The new controller is invoked after all already added
-     * controllers.
-     * </p>
-     * <p>
-     * If this controller was already added to the manager, it will be moved to
-     * the end of the list.
-     * </p>
-     * 
-     * @param controller The controller to add
-     * @throws NullPointerException if controller is null
-     */
-    public void addController(Controller controller) {
-        if (controller == null) {
-            throw new NullPointerException("Controller cannot be null");
-        }
-
-        // remove it first - which does nothing if not in the list
-        boolean removed = controllers.remove(controller);
-        // now add it to the end
-        controllers.add(controller);
-
-        if (!removed) {
-            // perform initialization steps if we've never seen the controller
-            profile.put(controller, new ProfileData());
-            controller.init(system);
-        }
-    }
-
-    /**
-     * Remove a controller from the manager so that it is no longer invoked when
-     * {@link #process()} and its related functions are called. If the
-     * controller has not been added to the manager, this does nothing.
-     * 
-     * @param controller The controller to remove
-     * @throws NullPointerException if controller is null
-     */
-    public void removeController(Controller controller) {
-        if (controller == null) {
-            throw new NullPointerException("Controller cannot be null");
-        }
-        boolean removed = controllers.remove(controller);
-        if (removed) {
-            controller.destroy();
-            profile.remove(controller);
-        }
-    }
-
-    /**
-     * Return the last execution time for the given controller and phase. This
-     * will return 0 if the controller has not been added to the manager.
-     * 
-     * @param controller The controller whose time is looked up
-     * @param phase The phase that whose timing is returned
-     * @return The last execution time of the controller in nanoseconds.
-     */
-    public long getExecutionTime(Controller controller, Phase phase) {
-        if (controller == null || phase == null) {
-            throw new NullPointerException("Arguments cannot be null");
-        }
-
-        ProfileData c = profile.get(controller);
-
-        if (c != null) {
-            switch (phase) {
-            case POSTPROCESS:
-                return c.postprocessTime;
-            case PREPROCESS:
-                return c.preprocessTime;
-            case PROCESS:
-                return c.processTime;
-            }
-        }
-
-        return 0L;
-    }
-
-    /**
-     * Return the last execution time for the given controller, for all its
-     * phases, in nanoseconds.
-     * 
-     * @param controller The controller whose time is looked up
-     * @return The last total execution time of the controller
-     */
-    public long getExecutionTime(Controller controller) {
-        return getExecutionTime(controller, Phase.PREPROCESS) + getExecutionTime(controller,
-                                                                                 Phase.POSTPROCESS) + getExecutionTime(controller,
-                                                                                                                       Phase.PROCESS);
-    }
-
-    /**
-     * Run all phases of the manager using the time delta from the last time the
-     * post-process phase was executed. This means that the time delta is
-     * reasonably defined even if {@link #process(double)} and
-     * {@link #process(Phase, double)} are used in addition to this process()
-     * call.
-     */
-    public void process() {
-        if (lastProcessTime < 0) {
-            process(0);
-        } else {
-            process((System.nanoTime() - lastProcessTime) / 1e9);
-        }
-    }
-
-    /**
-     * Run all phases of the manager using the specified frame time delta,
-     * <tt>dt</tt>.
-     * 
-     * @param dt The time delta for the frame, or the amount of time since the
-     *            start of the last frame and this one
-     */
-    public void process(double dt) {
-        process(Phase.PREPROCESS, dt);
-        process(Phase.PROCESS, dt);
-        process(Phase.POSTPROCESS, dt);
-    }
-
-    /**
-     * Run the processing of a particular phase for this manager, using the
-     * specified time delta. If the phase is {@link Phase#ALL}, all phases will
-     * be run in their proper order.
-     * 
-     * @param phase The specific phase to run, or ALL to specify all phases
-     * @param dt The time delta for the frame, or the amount of time since the
-     *            start of the last frame and this one
-     * @throws NullPointerException if phase is null
-     */
-    public void process(Phase phase, double dt) {
-        if (phase == null) {
-            throw new NullPointerException("Phase cannot be null");
-        }
-
-        currentPhase = phase;
-        switch (phase) {
-        case PREPROCESS:
-            firePreProcess(dt);
-            break;
-        case PROCESS:
-            fireProcess(dt);
-            break;
-        case POSTPROCESS:
-            firePostProcess(dt);
-            break;
-        }
-        currentPhase = null;
-    }
-
-    /**
-     * Invoke onEntityAdd() for all controllers.
-     * 
-     * @param e The entity being added
-     */
-    void fireEntityAdd(Entity e) {
-        for (int i = 0; i < controllers.size(); i++) {
-            controllers.get(i).onEntityAdd(e);
-        }
-    }
-
-    /**
-     * Invoke onEntityRemove() for all controllers.
-     * 
-     * @param e The entity being removed
-     */
-    void fireEntityRemove(Entity e) {
-        for (int i = 0; i < controllers.size(); i++) {
-            controllers.get(i).onEntityRemove(e);
-        }
-    }
-
-    /**
-     * Invoke onComponentAdd() for all controllers.
-     * 
-     * @param c The component being added
-     */
-    void fireComponentAdd(Component<?> c) {
-        for (int i = 0; i < controllers.size(); i++) {
-            controllers.get(i).onComponentAdd(c);
-        }
-    }
-
-    /**
-     * Invoke onComponentRemove() for all controllers.
-     * 
-     * @param c The component being removed
-     */
-    void fireComponentRemove(Component<?> c) {
-        for (int i = 0; i < controllers.size(); i++) {
-            controllers.get(i).onComponentRemove(c);
-        }
-    }
-
-    private void firePreProcess(double dt) {
-        lastProcessTime = System.nanoTime();
-
-        for (int i = 0; i < controllers.size(); i++) {
-            long start = System.nanoTime();
-            controllers.get(i).preProcess(dt);
-            profile.get(controllers.get(i)).preprocessTime = System.nanoTime() - start;
-        }
-    }
-
-    private void fireProcess(double dt) {
-        for (int i = 0; i < controllers.size(); i++) {
-            long start = System.nanoTime();
-            controllers.get(i).process(dt);
-            profile.get(controllers.get(i)).processTime = System.nanoTime() - start;
-        }
-    }
-
-    private void firePostProcess(double dt) {
-        for (int i = 0; i < controllers.size(); i++) {
-            long start = System.nanoTime();
-            controllers.get(i).postProcess(dt);
-            profile.get(controllers.get(i)).postprocessTime = System.nanoTime() - start;
-        }
-
-        singletonResults.clear();
-    }
-
-    private static class ProfileData {
-        private long processTime;
-        private long preprocessTime;
-        private long postprocessTime;
-
-        public ProfileData() {
-            processTime = 0;
-            preprocessTime = 0;
-            postprocessTime = 0;
-        }
-    }
-}

src/main/java/com/lhkbob/entreri/EntitySystem.java

 import java.util.List;
 import java.util.NoSuchElementException;
 
+import com.lhkbob.entreri.task.Scheduler;
+import com.lhkbob.entreri.task.Task;
+
 /**
  * <p>
  * EntitySystem is the main container for the entities within a logical system
  * used to process your data.
  * </p>
  * <p>
- * The {@link ControllerManager} of an EntitySystem can be used to register
- * Controllers that will process the entities within an entity system in an
- * organized fashion. Generally, the processing of all controllers through their
- * different phases constitutes a complete "frame".
+ * The {@link Scheduler} of an EntitySystem can be used to register Controllers
+ * that will process the entities within an entity system in an organized
+ * fashion. Generally, the processing of all controllers through their different
+ * phases constitutes a complete "frame".
  * </p>
  * <p>
  * When Entities are created by an EntitySystem, the created instance is
     private int entityInsert;
     private int entityIdSeq;
 
-    private final ControllerManager manager;
+    private final Scheduler manager;
 
     /**
      * Create a new EntitySystem that has no entities added.
      */
     public EntitySystem() {
-        manager = new ControllerManager(this);
+        manager = new Scheduler(this);
         entities = new Entity[1];
         componentRepositories = new ComponentRepository[0];
 
 
     /**
      * Return the ControllerManager for this EntitySystem that can be used to
-     * organize processing of the system using {@link Controller}
-     * implementations.
+     * organize processing of the system using {@link Task} implementations.
      * 
      * @return The ControllerManager for this system
      */
-    public ControllerManager getControllerManager() {
+    public Scheduler getControllerManager() {
         return manager;
     }
 
         Entity newEntity = new Entity(this, entityIndex, entityIdSeq++);
         entities[entityIndex] = newEntity;
 
-        // invoke add-event listeners now before we invoke any listeners
-        // due to templating so events have proper sequencing
-        manager.fireEntityAdd(newEntity);
-
         if (template != null) {
             for (Component<?> c : template) {
                 addFromTemplate(entityIndex, c.getTypeId(), c);
         }
 
         // clear out the entity
-        manager.fireEntityRemove(e);
         entities[e.index] = null;
         e.index = 0;
     }

src/main/java/com/lhkbob/entreri/Result.java

-/*
- * Entreri, an entity-component framework in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * 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.
- *
- * 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 HOLDER 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.
- */
-package com.lhkbob.entreri;
-
-import com.lhkbob.entreri.ControllerManager.Phase;
-
-/**
- * <p>
- * Result represents a computed result, preferably that of a bulk computation,
- * performed by a Controller. Results allow Controllers to easily pass data to
- * other controllers that might be interested in their computations.
- * </p>
- * <p>
- * Controllers that wish to expose results define classes that implement Result
- * and expose the actual result data. During processing of a controller, result
- * instances are created and supplied to all controllers by calling
- * {@link ControllerManager#report(Result)}
- * </p>
- * <p>
- * To receive computed results, Controllers override their
- * {@link Controller#report(Result)} and check incoming results for the desired
- * type of result. Every controller is notified of all results, they are
- * responsible for ignoring results they are not interested in.
- * </p>
- * 
- * @author Michael Ludwig
- */
-public interface Result {
-    /**
-     * <p>
-     * Return true if this result is a "singleton" result. A singleton result is
-     * a type of result that is only supplied once during the processing of a
-     * frame (i.e. at the end of the {@link Phase#POSTPROCESS} phase, it is
-     * reset).The ControllerManager verifies that singleton results are supplied
-     * at most once. Most results should return false. The returned value should
-     * be the same for every instance of a type, it should not depend on the
-     * state of the instance.
-     * </p>
-     * <p>
-     * Singleton results should only be used when the computation of the result
-     * produces all of the result data. As an example, a 3D engine might assign
-     * entities to lights, and each unique configuration of lights on an entity
-     * is a "light group". It makes more sense to provide a single result that
-     * describes all light groups than individual results for each group. They
-     * are packed into a single result because each group is dependent on the
-     * others to guarantee its uniqueness.
-     * </p>
-     * <p>
-     * As a counter example, computing potentially visible sets for a 3D engine
-     * should not be a singleton result. A result that contains the view and set
-     * of visible entities is a self-contained result, other views or cameras do
-     * not affect the PVS results.
-     * </p>
-     * 
-     * @return True if this result should only be supplied at most once during
-     *         each frame
-     */
-    public boolean isSingleton();
-}

src/main/java/com/lhkbob/entreri/SimpleController.java

-/*
- * Entreri, an entity-component framework in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * 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.
- *
- * 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 HOLDER 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.
- */
-package com.lhkbob.entreri;
-
-/**
- * <p>
- * SimpleController implements Controller by performing no action on each of
- * Controller's process or event hooks. Subclasses can override just the methods
- * they are interested in implementing. It implements the necessary logic for
- * recording its attached EntitySystem and validating its initialization and
- * destruction life-cycle.
- * </p>
- * <p>
- * If subclasses override {@link #init(EntitySystem)}, they must invoke
- * super.init() before running their code. Similarly, if they override
- * {@link #destroy()}, super.destroy() must be called last.
- * </p>
- * 
- * @author Michael Ludwig
- */
-public class SimpleController implements Controller {
-    private EntitySystem system;
-
-    @Override
-    public void preProcess(double dt) {
-        // do nothing in base class
-    }
-
-    @Override
-    public void process(double dt) {
-        // do nothing in base class
-    }
-
-    @Override
-    public void postProcess(double dt) {
-        // do nothing in base class
-    }
-
-    @Override
-    public void init(EntitySystem system) {
-        if (this.system != null) {
-            throw new IllegalStateException("Controller is already used in another EntitySystem");
-        }
-        this.system = system;
-    }
-
-    @Override
-    public void destroy() {
-        system = null;
-    }
-
-    @Override
-    public void onEntityAdd(Entity e) {
-        // do nothing in base class
-    }
-
-    @Override
-    public void onEntityRemove(Entity e) {
-        // do nothing in base class
-    }
-
-    @Override
-    public void onComponentAdd(Component<?> c) {
-        // do nothing in base class
-    }
-
-    @Override
-    public void onComponentRemove(Component<?> c) {
-        // do nothing in base class
-    }
-
-    @Override
-    public void report(Result result) {
-        // do nothing in base class
-    }
-
-    @Override
-    public EntitySystem getEntitySystem() {
-        return system;
-    }
-}

src/main/java/com/lhkbob/entreri/task/AbstractTask.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2012, Michael Ludwig
+ * 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.
+ *
+ * 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 HOLDER 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.
+ */
+package com.lhkbob.entreri.task;
+
+import com.lhkbob.entreri.EntitySystem;
+
+/**
+ * <p>
+ * SimpleController implements Controller by performing no action on each of
+ * Controller's process or event hooks. Subclasses can override just the methods
+ * they are interested in implementing. It implements the necessary logic for
+ * recording its attached EntitySystem and validating its initialization and
+ * destruction life-cycle.
+ * </p>
+ * <p>
+ * If subclasses override {@link #init(EntitySystem)}, they must invoke
+ * super.init() before running their code. Similarly, if they override
+ * {@link #destroy()}, super.destroy() must be called last.
+ * </p>
+ * 
+ * @author Michael Ludwig
+ */
+public abstract class AbstractTask implements Task {
+
+    @Override
+    public Task process(EntitySystem system, Job job) {
+        // TODO Auto-generated method stub
+        return null;
+    }
+
+    @Override
+    public void reset() {
+        // TODO Auto-generated method stub
+
+    }
+
+}

src/main/java/com/lhkbob/entreri/task/Job.java

+package com.lhkbob.entreri.task;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.lhkbob.entreri.TypeId;
+
+public class Job implements Runnable {
+    private final Task[] tasks;
+    private final Map<Class<? extends Result>, List<ResultReporter>> resultMethods;
+
+    private final boolean needsExclusiveLock;
+    private final List<TypeId<?>> locks;
+
+    private final Scheduler scheduler;
+    private final String name;
+
+    private final Set<Class<? extends Result>> singletonResults;
+    private int taskIndex;
+
+    Job(String name, Scheduler scheduler, Task... tasks) {
+        this.scheduler = scheduler;
+        this.tasks = new Task[tasks.length];
+        this.name = name;
+
+        singletonResults = new HashSet<Class<? extends Result>>();
+        resultMethods = new HashMap<Class<? extends Result>, List<ResultReporter>>();
+
+        boolean exclusive = false;
+        Set<TypeId<?>> typeLocks = new HashSet<TypeId<?>>();
+        for (int i = 0; i < tasks.length; i++) {
+            this.tasks[i] = tasks[i];
+
+            // collect parallelization info (which should not change over
+            // a task's lifetime)
+            if (tasks[i] instanceof ParallelAware) {
+                ParallelAware pa = (ParallelAware) tasks[i];
+                exclusive |= pa.isEntitySetModified();
+                typeLocks.addAll(pa.getAccessedComponentes());
+            } else {
+                // must assume it could touch anything
+                exclusive = true;
+            }
+
+            // record all result report methods exposed by this task
+            for (Method m : tasks[i].getClass().getMethods()) {
+                if (m.getName().equals("report")) {
+                    if (m.getReturnType().equals(void.class) && m.getParameterTypes().length == 1 && Result.class.isAssignableFrom(m.getParameterTypes()[0])) {
+                        // found a valid report method
+                        ResultReporter reporter = new ResultReporter(m, i);
+                        Class<? extends Result> type = reporter.getResultType();
+
+                        List<ResultReporter> all = resultMethods.get(type);
+                        if (all == null) {
+                            all = new ArrayList<ResultReporter>();
+                            resultMethods.put(type, all);
+                        }
+
+                        all.add(reporter);
+                    }
+                }
+            }
+        }
+
+        if (exclusive) {
+            needsExclusiveLock = true;
+            locks = null;
+        } else {
+            needsExclusiveLock = false;
+            locks = new ArrayList<TypeId<?>>(typeLocks);
+            // give locks a consistent ordering
+            Collections.sort(locks, new Comparator<TypeId<?>>() {
+                @Override
+                public int compare(TypeId<?> o1, TypeId<?> o2) {
+                    return o1.getId() - o2.getId();
+                }
+            });
+        }
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public Scheduler getScheduler() {
+        return scheduler;
+    }
+
+    @Override
+    public void run() {
+        // repeatedly run jobs until no task produces a post-process task
+        Job toInvoke = this;
+        while (toInvoke != null) {
+            toInvoke = toInvoke.runJob();
+        }
+    }
+
+    private Job runJob() {
+        // acquire locks (either exclusive or per type in order)
+        if (needsExclusiveLock) {
+            scheduler.getEntitySystemLock().writeLock().lock();
+        } else {
+            scheduler.getEntitySystemLock().readLock().lock();
+            for (int i = 0; i < locks.size(); i++) {
+                scheduler.getTypeLock(locks.get(i)).lock();
+            }
+        }
+
+        try {
+            // reset all tasks and the job
+            taskIndex = 0;
+            singletonResults.clear();
+            for (int i = 0; i < tasks.length; i++) {
+                tasks[i].reset();
+            }
+
+            // process all tasks and collect all returned tasks, in order
+            List<Task> postProcess = new ArrayList<Task>();
+            for (int i = 0; i < tasks.length; i++) {
+                taskIndex = i;
+                Task after = tasks[i].process(scheduler.getEntitySystem(), this);
+                if (after != null) {
+                    postProcess.add(after);
+                }
+            }
+
+            if (postProcess.isEmpty()) {
+                // nothing to process afterwards
+                return null;
+            } else {
+                Task[] tasks = postProcess.toArray(new Task[postProcess.size()]);
+                return new Job(name + "-postprocess", scheduler, tasks);
+            }
+        } finally {
+            // unlock
+            if (needsExclusiveLock) {
+                scheduler.getEntitySystemLock().writeLock().unlock();
+            } else {
+                for (int i = locks.size() - 1; i >= 0; i--) {
+                    scheduler.getTypeLock(locks.get(i)).unlock();
+                }
+                scheduler.getEntitySystemLock().readLock().unlock();
+            }
+        }
+    }
+
+    public void report(Result r) {
+        if (r.isSingleton()) {
+            // make sure this is the first we've seen the result
+            if (!singletonResults.add(r.getClass())) {
+                throw new IllegalStateException("Singleton result of type: " + r.getClass() + " has already been reported during " + name + "'s execution");
+            }
+        }
+
+        Class<?> type = r.getClass();
+        while (Result.class.isAssignableFrom(type)) {
+            // report to all methods that receive the type
+            List<ResultReporter> all = resultMethods.get(type);
+            if (all != null) {
+                int ct = all.size();
+                for (int i = 0; i < ct; i++) {
+                    // this will filter on the current task index to only report
+                    // results to future tasks
+                    all.get(i).report(r);
+                }
+            }
+
+            type = type.getSuperclass();
+        }
+    }
+
+    private class ResultReporter {
+        private final Method reportMethod;
+        private final int taskIndex;
+
+        public ResultReporter(Method reportMethod, int taskIndex) {
+            this.reportMethod = reportMethod;
+            this.taskIndex = taskIndex;
+        }
+
+        public void report(Result r) {
+            try {
+                if (taskIndex > Job.this.taskIndex) {
+                    reportMethod.invoke(Job.this.tasks[taskIndex], r);
+                }
+            } catch (IllegalArgumentException e) {
+                // shouldn't happen, since we check the type before invoking
+                throw new RuntimeException(e);
+            } catch (IllegalAccessException e) {
+                // shouldn't happen since we only use public methods
+                throw new RuntimeException(e);
+            } catch (InvocationTargetException e) {
+                throw new RuntimeException("Error reporting result", e.getCause());
+            }
+        }
+
+        @SuppressWarnings("unchecked")
+        public Class<? extends Result> getResultType() {
+            return (Class<? extends Result>) reportMethod.getParameterTypes()[0];
+        }
+    }
+}

src/main/java/com/lhkbob/entreri/task/ParallelAware.java

+package com.lhkbob.entreri.task;
+
+import java.util.Set;
+
+import com.lhkbob.entreri.TypeId;
+
+public interface ParallelAware {
+    // FIXME make sure to document that these cannot change for an instance
+    public Set<TypeId<?>> getAccessedComponentes();
+
+    public boolean isEntitySetModified();
+}

src/main/java/com/lhkbob/entreri/task/Result.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2012, Michael Ludwig
+ * 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.
+ *
+ * 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 HOLDER 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.
+ */
+package com.lhkbob.entreri.task;
+
+
+/**
+ * <p>
+ * Result represents a computed result, preferably that of a bulk computation,
+ * performed by a Controller. Results allow Controllers to easily pass data to
+ * other controllers that might be interested in their computations.
+ * </p>
+ * <p>
+ * Controllers that wish to expose results define classes that implement Result
+ * and expose the actual result data. During processing of a controller, result
+ * instances are created and supplied to all controllers by calling
+ * {@link Scheduler#report(Result)}
+ * </p>
+ * <p>
+ * To receive computed results, Controllers override their
+ * {@link Task#report(Result)} and check incoming results for the desired type
+ * of result. Every controller is notified of all results, they are responsible
+ * for ignoring results they are not interested in.
+ * </p>
+ * 
+ * @author Michael Ludwig
+ */
+public interface Result {
+    /**
+     * <p>
+     * Return true if this result is a "singleton" result. A singleton result is
+     * a type of result that is can only be reported once per execution of a
+     * job. The ControllerManager verifies that singleton results are supplied
+     * at most once. Most results should return false. The returned value should
+     * be the same for every instance of a type, it should not depend on the
+     * state of the instance.
+     * </p>
+     * <p>
+     * Singleton results should only be used when the computation of the result
+     * produces all of the result data. As an example, a 3D engine might assign
+     * entities to lights, and each unique configuration of lights on an entity
+     * is a "light group". It makes more sense to provide a single result that
+     * describes all light groups than individual results for each group. They
+     * are packed into a single result because each group is dependent on the
+     * others to guarantee its uniqueness.
+     * </p>
+     * <p>
+     * As a counter example, computing potentially visible sets for a 3D engine
+     * should not be a singleton result. A result that contains the view and set
+     * of visible entities is a self-contained result, other views or cameras do
+     * not affect the PVS results.
+     * </p>
+     * 
+     * @return True if this result should only be supplied at most once during
+     *         each frame
+     */
+    public boolean isSingleton();
+}

src/main/java/com/lhkbob/entreri/task/Scheduler.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2012, Michael Ludwig
+ * 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.
+ *
+ * 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 HOLDER 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.
+ */
+package com.lhkbob.entreri.task;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+import com.lhkbob.entreri.EntitySystem;
+import com.lhkbob.entreri.TypeId;
+
+public class Scheduler {
+    private final ThreadGroup schedulerGroup;
+
+    // write lock is for tasks that add/remove entities, 
+    // read lock is for all other tasks
+    private final ReentrantReadWriteLock exclusiveLock;
+
+    // locks per component data type, this map is filled
+    // dynamically the first time each type is requested
+    private final ConcurrentHashMap<TypeId<?>, ReentrantLock> typeLocks;
+
+    private final EntitySystem system;
+
+    public Scheduler(EntitySystem system) {
+        this.system = system;
+
+        schedulerGroup = new ThreadGroup("job-scheduler");
+        exclusiveLock = new ReentrantReadWriteLock();
+        typeLocks = new ConcurrentHashMap<TypeId<?>, ReentrantLock>();
+    }
+
+    public EntitySystem getEntitySystem() {
+        return system;
+    }
+
+    ReentrantReadWriteLock getEntitySystemLock() {
+        return exclusiveLock;
+    }
+
+    ReentrantLock getTypeLock(TypeId<?> id) {
+        ReentrantLock lock = typeLocks.get(id);
+        if (lock == null) {
+            // this will either return the newly constructed lock, or 
+            // the lock inserted from another thread after we tried to fetch it,
+            // in either case, the lock is valid
+            lock = typeLocks.putIfAbsent(id, new ReentrantLock());
+        }
+        return lock;
+    }
+
+    public Job createJob(String name, Task... tasks) {
+        return new Job(name, this, tasks);
+    }
+
+    public void runOnCurrentThread(Job job) {
+        if (job == null) {
+            throw new NullPointerException("Job cannot be null");
+        }
+        if (job.getScheduler() != this) {
+            throw new IllegalArgumentException("Job was created by a different scheduler");
+        }
+
+        // the job will handle all locking logic
+        job.run();
+    }
+
+    public void runOnSeparateThread(Job job) {
+        if (job == null) {
+            throw new NullPointerException("Job cannot be null");
+        }
+        if (job.getScheduler() != this) {
+            throw new IllegalArgumentException("Job was created by a different scheduler");
+        }
+
+        // spawn a new thread that will terminate when the job completes
+        Thread jobThread = new Thread(schedulerGroup, job, "job-" + job.getName());
+        jobThread.start();
+    }
+
+    public ExecutorService runEvery(double dt, Job job) {
+        final String name = String.format("job-%s-every-%.2fs", job.getName(), dt);
+        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
+            @Override
+            public Thread newThread(Runnable r) {
+                return new Thread(schedulerGroup, r, name);
+            }
+        });
+        service.scheduleAtFixedRate(job, 0L, (long) (dt * 1e9), TimeUnit.NANOSECONDS);
+        return Executors.unconfigurableExecutorService(service);
+    }
+
+    public ExecutorService runContinuously(Job job) {
+        final String name = String.format("job-%s-as-fast-as-possible", job.getName());
+        ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
+            @Override
+            public Thread newThread(Runnable r) {
+                return new Thread(schedulerGroup, r, name);
+            }
+        });
+
+        // ScheduledExecutorService has no way to just specify run-as-fast-as-possible.
+        // However, if a task takes longer than its fixed-rate, that is the resulting,
+        // behavior. There is a strong probability that all jobs will take longer
+        // than a single nanosecond, so this should do the trick.
+        service.scheduleAtFixedRate(job, 0L, 1L, TimeUnit.NANOSECONDS);
+        return Executors.unconfigurableExecutorService(service);
+    }
+}

src/main/java/com/lhkbob/entreri/task/Task.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2012, Michael Ludwig
+ * 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.
+ *
+ * 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 HOLDER 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.
+ */
+package com.lhkbob.entreri.task;
+
+import com.lhkbob.entreri.EntitySystem;
+
+/**
+ * <p>
+ * Tasks are functional processors of the entities and components within an
+ * EntitySystem. Different Task implementations have different purposes, such as
+ * updating transforms, computing physics or AI, or rendering a scene. Generally
+ * task implementations should be as small and as independent as possible to
+ * allow for reuse and easy composability.
+ * <p>
+ * The {@link Job} and {@link Scheduler} work together to coordinate the
+ * execution of collections of tasks. Tasks are grouped into jobs, where a job
+ * represents a logical step such as "render frame", or "compute physics", even
+ * if it is split into multiple tasks. Within a job, tasks are executed
+ * serially, but the scheduler can run different jobs simultaneously.
+ * <p>
+ * The {@link ParallelAware} can be used with tasks to restrict the contention
+ * points of a job. This is important because a job acquires the locks for each
+ * of its tasks before invoking the first task. The locks are not released until
+ * the last task has terminated.
+ * <p>
+ * A task can communicate with the remaining tasks of a job by
+ * {@link Job#report(Result) reporting} results. The results are only reported
+ * to tasks within the owning job, and are executed after the current task. Thus
+ * tasks that have already completed their processing will not receive new
+ * results.
+ * <p>
+ * A task receives results by defining any number of methods with the signature
+ * <code>public void report(T extends Result)</code>. When a result is reported
+ * to the job, it will invoke using reflection any <code>report()</code> method
+ * that takes a result of compatible type.
+ * <p>
+ * Task instances should only ever be used with a single job and entity system.
+ * If the task needs to be performed in multiple jobs or systems, new instances
+ * of the same type should be created.
+ * 
+ * @author Michael Ludwig
+ */
+public interface Task {
+    /**
+     * <p>
+     * Invoke task specific operations to process the EntitySystem. This will be
+     * invoked on the task's parent job's thread, after the job has acquired any
+     * locks mandated by {@link ParallelAware}. If this task is not
+     * ParallelAware, the job acquires an exclusive lock with the assumption
+     * that it could modify anything.
+     * <p>
+     * A task can return another task to be invoked after the owning job
+     * completes. These returned tasks are executed within their own set of
+     * locks, so it can be used to segment how long, and which locks are held by
+     * a job.
+     * 
+     * @param system The EntitySystem being processed, which will always be the
+     *            same for a given Task instance
+     * @param job The Job this task belongs to
+     * @return A nullable task that is executed after the job is completed
+     */
+    public Task process(EntitySystem system, Job job);
+
+    /**
+     * <p>
+     * Reset any internal storage within this Task in preparation for the next
+     * execution of its owning job. This is used when jobs are scheduled to
+     * repeat at a given rate. Instead of instantiating new tasks every time,
+     * they can reset their tasks.
+     * <p>
+     * Examples of how this can be used is if a spatial index is built up
+     * per-frame, or if decorated properties cache computed results. By using
+     * reset(), it's not necessary to re-decorate or allocate again.
+     */
+    public void reset();
+}

src/test/java/com/lhkbob/entreri/ControllerManagerTest.java

 import org.junit.Assert;
 import org.junit.Test;
 
-import com.lhkbob.entreri.ControllerManager.Phase;
 import com.lhkbob.entreri.component.IntComponent;
+import com.lhkbob.entreri.task.Task;
+import com.lhkbob.entreri.task.Result;
+import com.lhkbob.entreri.task.AbstractTask;
+import com.lhkbob.entreri.task.Scheduler.Phase;
 
 public class ControllerManagerTest {
     @Test
         Assert.assertTrue(i == ctrl.lastRemovedComponent);
     }
 
-    private static class ResultSupplyingController extends SimpleController {
+    private static class ResultSupplyingController extends AbstractTask {
         private final Result[] resultsToReport;
 
         public ResultSupplyingController(Result... results) {
     }
 
     // explicitly not a listener
-    private static class ControllerImpl implements Controller {
+    private static class ControllerImpl implements Task {
         private boolean preprocessed;
         private boolean processed;
         private boolean postprocessed;