package jalse.actions;
import static jalse.actions.Actions.emptyActionContext;
import static jalse.actions.Actions.unschedulableActionContext;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* A {@link ActionScheduler} implementation that schedules all actions against the supplied actor.
* Weak references are kept against all scheduled tasks so they can be bulk cancelled (these are
* also cleared on {@link ActionEngine} change).<br>
* <br>
* By default if no {@link ActionEngine} is supplied {@link ForkJoinActionEngine#commonPoolEngine()}
* will be used.
*
* @author Elliot Ford
*
* @param <T>
* Actor type.
*/
public class DefaultActionScheduler<T> implements ActionScheduler<T> {
private final T actor;
private ActionEngine engine;
private final Set<ActionContext<T>> contexts;
/**
* Creates a DefaultScheduler for the supplied actor.
*
* @param actor
* Actor to schedule actions against.
*/
public DefaultActionScheduler(final T actor) {
this.actor = Objects.requireNonNull(actor);
engine = ForkJoinActionEngine.commonPoolEngine(); // Defaults use common engine
contexts = new HashSet<>();
}
/**
* Cancel all tasks scheduled to the current engine for the actor by this scheduler.
*/
@Override
public void cancelAllScheduledForActor() {
final Set<ActionContext<T>> toCancel;
synchronized (contexts) {
toCancel = new HashSet<>(contexts);
contexts.clear();
}
// Cancel all
toCancel.forEach(cxt -> {
if (!cxt.isDone()) {
cxt.cancel();
}
});
}
/**
* Gets the action Actor.
*
* @return Actor to schedule events against.
*/
public T getActor() {
return actor;
}
/**
* Gets the associated engine.
*
* @return Associated engine or null if it has not been set.
*/
public ActionEngine getEngine() {
return engine;
}
@Override
public SchedulableActionContext<T> newContextForActor(final Action<T> action) {
// Check engine running
if (engine.isStopped()) {
return emptyActionContext();
}
// New context for actor
return new UnmodifiableActorDelegateActionContext<>(newContextForActor0(action));
}
private SchedulableActionContext<T> newContextForActor0(final Action<T> action) {
// Create new context
final SchedulableActionContext<T> context = engine.newContext(action);
context.setActor(actor);
// Add then purge
synchronized (contexts) {
contexts.add(context);
contexts.removeIf(ActionContext<T>::isDone);
}
return context;
}
@Override
public ActionContext<T> scheduleForActor(final Action<T> action, final long initialDelay, final long period,
final TimeUnit unit) {
// Check engine running
if (engine.isStopped()) {
return emptyActionContext();
}
// Create new actor context
final SchedulableActionContext<T> context = newContextForActor0(action);
context.setInitialDelay(initialDelay, unit);
context.setPeriod(period, unit);
context.schedule();
// Don't allow for mutation (it's running)
return unschedulableActionContext(context);
}
/**
* Associates a engine to this scheduler (if the engine changes all task references are lost).
*
* @param engine
* Engine to schedule actions against.
*/
public void setEngine(final ActionEngine engine) {
if (!Objects.equals(this.engine, engine)) { // Only if changed
synchronized (contexts) {
contexts.clear();
}
}
this.engine = engine;
}
}