package org.jactr.tools.experiment.triggers; /* * default logging */ import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.CancellationException; import java.util.concurrent.CompletableFuture; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.commonreality.time.IClock; import org.jactr.tools.experiment.IExperiment; import org.jactr.tools.experiment.actions.IAction; import org.w3c.dom.Element; /** * trigger that fires after relative or absolute time has elapsed * * @author harrison */ public class TimeTrigger implements ITrigger { /** * Logger definition */ static private final transient Log LOGGER = LogFactory .getLog(TimeTrigger.class); static public final String RELATIVE_ATTR = "relative"; static public final String ABSOLUTE_ATTR = "absolute"; private final Collection<IAction> _actions = new ArrayList<IAction>(); private Collection<ITrigger> _triggers = new ArrayList<ITrigger>(); private CompletableFuture<Double> _trigger; private boolean _isRelative = false; private double _fireAt = 0; private IExperiment _experiment; public void add(IAction action) { _actions.add(action); } public void add(ITrigger trigger) { _triggers.add(trigger); } public TimeTrigger(Element trigger, IExperiment experiment) { _experiment = experiment; try { if (trigger.hasAttribute(RELATIVE_ATTR)) { _isRelative = true; _fireAt = Double.parseDouble(experiment .getVariableResolver() .resolve(trigger.getAttribute(RELATIVE_ATTR), experiment.getVariableContext()).toString()); } else _fireAt = Double.parseDouble(experiment .getVariableResolver() .resolve(trigger.getAttribute(ABSOLUTE_ATTR), experiment.getVariableContext()).toString()); } catch (NumberFormatException e) { throw new IllegalArgumentException("Could not get valid number from " + RELATIVE_ATTR + " or " + ABSOLUTE_ATTR + " attributes of " + trigger.getTagName(), e); } } public TimeTrigger(double timeDelay, boolean delayIsRelative, IExperiment experiment) { _experiment = experiment; _isRelative = delayIsRelative; _fireAt = timeDelay; } public boolean isArmed() { return _trigger != null; } public void setArmed(boolean arm) { if (isArmed() && arm) return; if (!isArmed() && !arm) return; if (!arm && _trigger != null) { if (!_trigger.isDone() && !_trigger.isCancelled()) { if (LOGGER.isDebugEnabled()) LOGGER .debug(String.format("Canceling trigger future [%s]", _trigger)); _trigger.cancel(false); } if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("disarmed [%s]", _trigger)); _trigger = null; } else if (arm && _trigger == null) { IClock clock = _experiment.getClock(); if (clock == null) throw new IllegalStateException( "No clock has been assigned to the experiment"); double start = _fireAt; double now = clock.getTime(); if (_isRelative) start += now; if (LOGGER.isDebugEnabled()) LOGGER.debug("Time trigger should fire @ " + start + " isRelative:" + _isRelative + " now:" + now); Runnable runner = () -> { /* * zip through and fire all the actions */ if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("timed trigger hit @ %.4f", clock.getTime())); try { _trigger = null; for (ITrigger trigger : _triggers) trigger.setArmed(true); for (IAction action : _actions) action.fire(_experiment.getVariableContext()); } catch (Exception e) { LOGGER.error(String.format("Failed to fire after time update "), e); } }; _trigger = clock.waitForTime(start); if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Using trigger future [%s]", _trigger)); _trigger.handle((d, t) -> { if (t != null && !(t instanceof CancellationException)) LOGGER.error( String.format("[%s] Failed to wait for time? ", _trigger), t); return null; }); _trigger.thenRun(runner); } } }