package com.artemis.systems; import com.artemis.Aspect; import com.artemis.Entity; import com.artemis.EntitySystem; import com.artemis.utils.ImmutableBag; /** * The purpose of this class is to allow systems to execute at varying intervals. * * An example system would be an ExpirationSystem, that deletes entities after a certain * lifetime. Instead of running a system that decrements a timeLeft value for each * entity, you can simply use this system to execute in a future at a time of the shortest * lived entity, and then reset the system to run at a time in a future at a time of the * shortest lived entity, etc. * * Another example system would be an AnimationSystem. You know when you have to animate * a certain entity, e.g. in 300 milliseconds. So you can set the system to run in 300 ms. * to perform the animation. * * This will save CPU cycles in some scenarios. * * Implementation notes: * In order to start the system you need to override the inserted(Entity e) method, * look up the delay time from that entity and offer it to the system by using the * offerDelay(float delay) method. * Also, when processing the entities you must also call offerDelay(float delay) * for all valid entities. * * @author Arni Arent * */ public abstract class DelayedEntityProcessingSystem extends EntitySystem { private float delay; private boolean running; private float acc; public DelayedEntityProcessingSystem(Aspect aspect) { super(aspect); } @Override protected final void processEntities(ImmutableBag<Entity> entities) { for (int i = 0, s = entities.size(); s > i; i++) { Entity entity = entities.get(i); processDelta(entity, acc); float remaining = getRemainingDelay(entity); if(remaining <= 0) { processExpired(entity); } else { offerDelay(remaining); } } stop(); } @Override protected void inserted(Entity e) { float delay = getRemainingDelay(e); if(delay > 0) { offerDelay(delay); } } /** * Return the delay until this entity should be processed. * * @param e entity * @return delay */ protected abstract float getRemainingDelay(Entity e); @Override protected final boolean checkProcessing() { if(running) { acc += world.getDelta(); if(acc >= delay) { return true; } } return false; } /** * Process a entity this system is interested in. Substract the accumulatedDelta * from the entities defined delay. * * @param e the entity to process. * @param accumulatedDelta the delta time since this system was last executed. */ protected abstract void processDelta(Entity e, float accumulatedDelta); protected abstract void processExpired(Entity e); /** * Start processing of entities after a certain amount of delta time. * * Cancels current delayed run and starts a new one. * * @param delta time delay until processing starts. */ public void restart(float delay) { this.delay = delay; this.acc = 0; running = true; } /** * Restarts the system only if the delay offered is shorter than the * time that the system is currently scheduled to execute at. * * If the system is already stopped (not running) then the offered * delay will be used to restart the system with no matter its value. * * If the system is already counting down, and the offered delay is * larger than the time remaining, the system will ignore it. If the * offered delay is shorter than the time remaining, the system will * restart itself to run at the offered delay. * * @param delay */ public void offerDelay(float delay) { if(!running || delay < getRemainingTimeUntilProcessing()) { restart(delay); } } /** * Get the initial delay that the system was ordered to process entities after. * * @return the originally set delay. */ public float getInitialTimeDelay() { return delay; } /** * Get the time until the system is scheduled to run at. * Returns zero (0) if the system is not running. * Use isRunning() before checking this value. * * @return time when system will run at. */ public float getRemainingTimeUntilProcessing() { if(running) { return delay-acc; } return 0; } /** * Check if the system is counting down towards processing. * * @return true if it's counting down, false if it's not running. */ public boolean isRunning() { return running; } /** * Stops the system from running, aborts current countdown. * Call offerDelay or restart to run it again. */ public void stop() { this.running = false; this.acc = 0; } }