/* * Created on Oct 21, 2006 Copyright (C) 2001-6, Anthony Harrison anh23@pitt.edu * (jactr.org) This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of the License, * or (at your option) any later version. This library is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See * the GNU Lesser General Public License for more details. You should have * received a copy of the GNU Lesser General Public License along with this * library; if not, write to the Free Software Foundation, Inc., 59 Temple * Place, Suite 330, Boston, MA 02111-1307 USA */ package org.jactr.core.runtime; import java.io.PrintWriter; import java.io.StringWriter; import java.text.NumberFormat; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.commonreality.time.IAuthoritativeClock; import org.commonreality.time.IClock; import org.commonreality.time.impl.BasicClock; import org.jactr.core.buffer.IActivationBuffer; import org.jactr.core.logging.Logger; import org.jactr.core.model.ICycleProcessor; import org.jactr.core.model.IModel; import org.jactr.core.model.ModelTerminatedException; import org.jactr.core.model.basic.BasicModel; import org.jactr.core.model.event.ModelEvent; import org.jactr.core.queue.TimedEventQueue; import org.jactr.core.runtime.event.ACTRRuntimeEvent; import org.jactr.core.utils.Diagnostics; /** * basic model runner, handles all events except disconnected which will be * handled by the controller * * @author developer */ public class DefaultModelRunner implements Runnable { /** * logger definition */ static private final Log LOGGER = LogFactory .getLog(DefaultModelRunner.class); static boolean _enableTimeDiagnostics = Boolean .getBoolean("jactr.enableTimeDiagnostics"); protected ExecutorService _service; protected BasicModel _model; protected ICycleProcessor _cycleRunner; private NumberFormat _format; private boolean _firstRun = true; public DefaultModelRunner(ExecutorService service, IModel model, ICycleProcessor cycleRunner) { if (!(model instanceof BasicModel)) throw new RuntimeException(getClass().getSimpleName() + " only supports subclasses of " + BasicModel.class.getName()); _model = (BasicModel) model; _cycleRunner = cycleRunner; _service = service; _format = NumberFormat.getNumberInstance(); _format.setMaximumFractionDigits(3); _format.setMinimumFractionDigits(3); } protected void startUp() { /** * this will connect, and once it has gotten the go ahead to initialize from * common reality, it will call model.initialize(). we then block until it * gets the start command */ ACTRRuntime runtime = ACTRRuntime.getRuntime(); runtime.getConnector().connect(_model); IClock clock = runtime.getClock(_model); double timeShift = _model.getAge() - clock.getTime(); Optional<IAuthoritativeClock> auth = clock.getAuthority(); if (auth.isPresent()) { auth.get().setLocalTimeShift(timeShift); if (LOGGER.isDebugEnabled()) LOGGER.debug("Shifted clock to " + timeShift); } else if (LOGGER.isWarnEnabled()) LOGGER.warn(String.format("Cannot set time shift")); /** * let anyone who depends on commonreality know that we have connected */ _model.dispatch(new ModelEvent(_model, ModelEvent.Type.CONNECTED)); if (runtime.hasListeners()) runtime.dispatch(new ACTRRuntimeEvent(_model, ACTRRuntimeEvent.Type.MODEL_STARTED, null)); } protected void shutDown(Exception deferred) { try { _model .dispatch(new ModelEvent(_model, ModelEvent.Type.STOPPED, deferred)); double age = ACTRRuntime.getRuntime().getClock(_model).getTime(); if (LOGGER.isDebugEnabled()) LOGGER.debug("Set model age to " + age); _model.setAge(age); ACTRRuntime.getRuntime().getConnector().disconnect(_model); Thread.currentThread().setName("dead-" + _model.getName()); } catch (Exception e) { deferred = e; } finally { _model.dispatch(new ModelEvent(_model, ModelEvent.Type.DISCONNECTED, deferred)); } } protected void preEventFiring() { } protected boolean firePendingEvents(double now) { /* * fire all the events that may have transpired by now */ TimedEventQueue queue = _model.getTimedEventQueue(); boolean anyFired = false; while (queue.fireExpiredEvents(now)) anyFired = true; return anyFired; } protected void postEventFiring() { } /** * run a single cycle of the model * * @return */ protected double cycle(boolean eventsHaveFired) { // make sure we're within the precision bounds bounds. return _cycleRunner.cycle(_model, eventsHaveFired); } /** * wait for the clock to reach this time * * @param waitForTime * @throws InterruptedException */ protected double waitForClock(double waitForTime) throws InterruptedException, ExecutionException { if (Double.isNaN(waitForTime)) { LOGGER .error("Requested NaN? This should not happen unless we have no time control."); return 0; } IClock clock = ACTRRuntime.getRuntime().getClock(_model); double now = clock.getTime(); if (waitForTime <= now && !_firstRun) { LOGGER .warn(String .format( "WARNING: Time discrepancy detected. Clock regression requested : %.10f(desired) < %.10f(current). Should be >=. Incrementing request by 0.05", waitForTime, now)); waitForTime = now + 0.05; if (_enableTimeDiagnostics) Diagnostics.timeSanityCheck(waitForTime); } _firstRun = false; waitForTime = BasicClock.constrainPrecision(waitForTime); Optional<IAuthoritativeClock> auth = clock.getAuthority(); double rtn = waitForTime; if (auth.isPresent()) rtn = auth.get().requestAndWaitForTime(waitForTime, _model).get(); else { LOGGER.warn("Cannot fully participant in time control "); rtn = clock.waitForTime(waitForTime).get(); } if (_enableTimeDiagnostics) Diagnostics.timeSanityCheck(rtn); // double rtn = ACTRRuntime.getRuntime().getClock(_model) // .waitForTime(waitForTime).get(); if (rtn < waitForTime) LOGGER .error(String .format( "WARNING: Time discrepancy detected. Clock regression : %.10f(returned) < %.10f(desired). Should be >=", rtn, waitForTime)); return rtn; } /** * called before blocking on waitForClock */ protected void preClock() { } /** * called after waitForClock returns */ protected void postClock(double currentSimulatedTime) { } /** * called before each cycle starts */ protected void preCycle(double currentSimulatedTime) { } /** * called after each cycle */ protected void postCycle(double nextTime) { } /** * run the model in loop * * @param clock */ protected void runModelLoop() { try { double startTime = waitForClock(_model.getAge()); if (LOGGER.isDebugEnabled()) LOGGER.debug("Starting @ " + startTime + " age:" + _model.getAge()); /* * signal the start to the runtime and the model */ _model.dispatch(new ModelEvent(_model, ModelEvent.Type.STARTED)); double nextTime = startTime; while (!_service.isShutdown() && !Double.isNaN(nextTime)) { /* * fire the time event */ if (Logger.hasLoggers(_model)) Logger.log(_model, Logger.Stream.TIME, _format.format(nextTime)); preEventFiring(); boolean eventsHaveFired = firePendingEvents(nextTime); postEventFiring(); preCycle(nextTime); if (LOGGER.isDebugEnabled()) LOGGER.debug("set age to " + nextTime); _model.setAge(nextTime); double priorTime = nextTime; nextTime = cycle(eventsHaveFired); if (nextTime <= priorTime) { if (LOGGER.isWarnEnabled()) LOGGER .warn(String .format( "WARNING: Time discrepancy detected. Cycle time error : %.6f(next) <= %.6f(prior). Should be >. Incrementing", nextTime, priorTime)); if (_enableTimeDiagnostics) Diagnostics.timeSanityCheck(nextTime); // nextTime = priorTime + 0.001; } postCycle(priorTime); if (!Double.isNaN(nextTime)) { preClock(); nextTime = waitForClock(nextTime); postClock(nextTime); } else postClock(Double.NaN); } } catch (ModelTerminatedException mte) { LOGGER.debug("Model threw termination"); // perfectly normal // thi } catch (InterruptedException ie) { // perfectly normal LOGGER.warn("Interrupted, assuming termination ", ie); } catch (ExecutionException ee) { LOGGER.error("Execution exception, terminating ", ee); } catch (Exception e) { LOGGER.error("Unknown exception, terminating ", e); } finally { /* * pop all the buffer contents */ for (IActivationBuffer buffer : _model.getActivationBuffers()) buffer.clear(); } } public void run() { Exception deferred = null; try { startUp(); runModelLoop(); } catch (Exception e) { deferred = e; if (LOGGER.isErrorEnabled()) LOGGER.error("Model:" + _model + " has thrown an exception", e); if (Logger.hasLoggers(_model)) { /* * build the message.. */ StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); Logger.log(_model, Logger.Stream.EXCEPTION, sw.toString()); } try { _model.dispatch(new ModelEvent(_model, e)); } catch (Exception e2) { // just in case } } finally { shutDown(deferred); } } }