package jalse.actions; import static jalse.actions.Actions.emptyActionBindings; import static jalse.actions.Actions.unschedulableActionContext; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; import java.util.logging.Logger; /** * A abstract implementation of {@link SchedulableActionContext} that is designed to be used * manually. This class should be used whenever controlling the execution state of work is * important. The action should be performed with {@link #performAction()}. This is a convenience * class for creating an {@link ActionEngine}. * * @author Elliot Ford * * @param <T> * Actor type. * * @see ManualWorkQueue * @see ManualActionEngine * @see ForkJoinActionEngine */ public abstract class AbstractManualActionContext<T> extends BaseActionContext<T> implements Comparable<AbstractManualActionContext<?>> { private static final Logger logger = Logger.getLogger(AbstractManualActionContext.class.getName()); private final Lock lock; private final Condition ran; private final AtomicBoolean cancelled; private final AtomicBoolean done; private final AtomicBoolean performing; private final AtomicLong estimated; private final ActionContext<T> unschedulable; /** * Creates a new instance of AbstractManualActionContext with the supplied engine and action. * * @param engine * Parent engine. * @param action * Action this context is for. */ protected AbstractManualActionContext(final ActionEngine engine, final Action<T> action) { this(engine, action, emptyActionBindings()); } /** * Creates a new instance of AbstractManualActionContext with the supplied engine, action and * source bindings. * * @param engine * Parent engine. * @param action * The action this context is for. * @param sourceBindings * Bindings to shallow copy. */ protected AbstractManualActionContext(final ActionEngine engine, final Action<T> action, final ActionBindings sourceBindings) { super(engine, action, sourceBindings); lock = new ReentrantLock(); ran = lock.newCondition(); done = new AtomicBoolean(); performing = new AtomicBoolean(); cancelled = new AtomicBoolean(); estimated = new AtomicLong(); unschedulable = unschedulableActionContext(this); } /** * Used to add this context as work to a queue. */ protected abstract void addAsWork(); @Override public void await() throws InterruptedException { if (isPeriodic()) { throw new UnsupportedOperationException("Cannot await periodic actions"); } lock.lockInterruptibly(); try { while (!isDone()) { ran.await(); // Await signal } } finally { lock.unlock(); } } @Override public boolean cancel() { if (isDone()) { return false; } cancelled.set(true); done.set(true); removeAsWork(); // Remove from engines work queue if (!performing.get()) { // Wait if currently executing signalRan(); } return true; } @Override public int compareTo(final AbstractManualActionContext<?> o) { final long estimated = getEstimated(); final long otherEstimated = o.getEstimated(); return estimated < otherEstimated ? -1 : estimated == otherEstimated ? 0 : 1; } /** * Gets the ideal estimated execution time (nanos). * * @return gets the estimated time of execution. * * @see System#nanoTime() */ public long getEstimated() { return estimated.get(); } @Override public boolean isCancelled() { return cancelled.get(); } @Override public boolean isDone() { return done.get(); } /** * Whether the action is currently being performed. * * @return Whether performing action. */ public boolean isPeforming() { return performing.get(); } /** * Performs the action (setting context state). * * @throws InterruptedException * If action throws this or this is interrupted. */ public void performAction() throws InterruptedException { if (isDone()) { return; } performing.set(true); try { getAction().perform(unschedulable); // Execute action } catch (final InterruptedException e) { cancelled.set(true); throw e; } catch (final Exception e) { // Continue logger.log(Level.WARNING, "Error performing action", e); if (!isPeriodicOnException()) { cancelled.set(true); } } finally { performing.set(false); done.set(true); signalRan(); // Wake up awaiting } if (isCancelled()) { return; } if (isPeriodic()) { estimated.set(System.nanoTime() + getPeriod(TimeUnit.NANOSECONDS)); done.set(false); addAsWork(); // Add to engines work queue } } /** * Used to remove context as work from a queue. */ protected abstract void removeAsWork(); /** * Resets the context to its starting state. */ protected void reset() { done.set(false); performing.set(false); cancelled.set(false); estimated.set(0L); } @Override public void schedule() { if (!isDone()) { estimated.set(System.nanoTime() + getInitialDelay(TimeUnit.NANOSECONDS)); addAsWork(); // Add to engines work queue } } @Override public void scheduleAndAwait() throws InterruptedException { schedule(); await(); } private void signalRan() { lock.lock(); try { ran.signalAll(); } finally { lock.unlock(); } } }