/* * Created on May 8, 2006 Copyright (C) 2001-5, 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.model.six; import java.util.Collection; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import javolution.util.FastList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.jactr.core.buffer.IActivationBuffer; import org.jactr.core.model.ICycleProcessor; import org.jactr.core.model.IModel; import org.jactr.core.model.basic.BasicModel; import org.jactr.core.model.event.ModelEvent; import org.jactr.core.module.procedural.IProceduralModule; import org.jactr.core.production.IInstantiation; import org.jactr.core.queue.TimedEventQueue; import org.jactr.core.runtime.ACTRRuntime; /** * default cycle control for the model * * @author developer */ public class DefaultCycleProcessor6 implements ICycleProcessor { /** * logger definition */ static private final Log LOGGER = LogFactory .getLog(DefaultCycleProcessor6.class); private double _nextPossibleProductionFiringTime = 0; private Collection<Runnable> _executeBefore; private Collection<Runnable> _executeAfter; private volatile boolean _isExecuting = false; public DefaultCycleProcessor6() { _executeAfter = FastList.newInstance(); _executeBefore = FastList.newInstance(); } private void execute(Collection<Runnable> source) { FastList<Runnable> container = FastList.newInstance(); synchronized (source) { container.addAll(source); source.clear(); } for (Runnable runner : container) try { runner.run(); } catch (Exception e) { LOGGER.error("Failed to execute " + runner, e); } FastList.recycle(container); } /** * run a single cycle of the model * * @see java.util.concurrent.Callable#call() */ public double cycle(IModel model, boolean eventsHaveFired) { BasicModel basicModel = (BasicModel) model; execute(_executeBefore); /* * what time is it? */ double now = ACTRRuntime.getRuntime().getClock(basicModel).getTime(); basicModel.setCycle(basicModel.getCycle() + 1); basicModel.dispatch(new ModelEvent(basicModel, ModelEvent.Type.CYCLE_STARTED)); double nextWaitTime = Double.NEGATIVE_INFINITY; _isExecuting = true; try { double productionFiringTime = evaluateAndFireProduction(basicModel, now); nextWaitTime = calculateNextWaitTime(now, productionFiringTime, basicModel, eventsHaveFired); if (LOGGER.isDebugEnabled()) LOGGER.debug("nextWaitTime : " + nextWaitTime); } catch (InterruptedException ie) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Interrupted while executing production. Terminating "); nextWaitTime = Double.NaN; } catch (ExecutionException e) { LOGGER.error("Failed to fire production ", e); throw new RuntimeException(e.getMessage(), e.getCause()); } finally { _isExecuting = false; /* * always fire the cycle stopped event */ basicModel.dispatch(new ModelEvent(basicModel, ModelEvent.Type.CYCLE_STOPPED)); execute(_executeAfter); } return nextWaitTime; } /** * using the current state, guestimate as to the how long this cycle will run * assuming that no production actually fired * * @return */ protected double calculateNextWaitTime(double now, double productionFiringTime, BasicModel model, boolean eventsHaveFired) { // IProceduralModule procMod = model.getProceduralModule(); // double cycleTime = procMod.getDefaultProductionFiringTime(); TimedEventQueue queue = model.getTimedEventQueue(); /* * if the production queued any events that should fire immediately, we need * to fire them before we guess what the next time should be. */ while (queue.fireExpiredEvents(now)) eventsHaveFired = true; // will already be now+cycleTime if a production fired double nextProductionFiringTime = _nextPossibleProductionFiringTime; double nextEventFiringTime = _nextPossibleProductionFiringTime; double nextWaitTime = nextProductionFiringTime; if (!queue.isEmpty()) nextEventFiringTime = queue.getNextEndTime(); /* * no production fired */ if (Double.isInfinite(productionFiringTime)) if (queue.isEmpty()) { if (!model.isPersistentExecutionEnabled()) { /* * nothing to do, no production fired, and we aren't required to stay * running. lets empty the goal buffer to permit empty productions (w/ * no goal) to fire. if the goal buffer is already empty, signal quit */ IActivationBuffer goalBuffer = model .getActivationBuffer(IActivationBuffer.GOAL); if (goalBuffer.getSourceChunk() != null) goalBuffer.clear(); else return Double.NaN; // signal quit } } else /* * we only skip cycles if no events have fired. If events have fired, * then productions might be able to fire.. */ if (model.isCycleSkippingEnabled() /* && !eventsHaveFired */) { if (eventsHaveFired) nextWaitTime = Math .min(nextEventFiringTime, nextProductionFiringTime); else { nextWaitTime = nextEventFiringTime; nextProductionFiringTime = nextEventFiringTime; } /* * increment the cycles */ long cycleDelta = (long) ((nextWaitTime - now) / model .getProceduralModule().getDefaultProductionFiringTime()); cycleDelta--; model.setCycle(model.getCycle() + cycleDelta); } /* * if the two are absurdly close, just take the larger of the two. this * prevents the occasional situation (w/o cycle skipping) where the * production may fire microseconds before the event is to expire. since no * production fires, the goal is cleared, but then the event fires and there * is no one left to handle it. this prevents the whacky duplicate time * display since the time display is rounded to the millisecond, we're * missing that these are just ever so slightly different */ if (nextEventFiringTime != nextProductionFiringTime && Math.abs(nextProductionFiringTime - nextEventFiringTime) < 0.001) nextWaitTime = Math.max(nextProductionFiringTime, nextEventFiringTime); // // // if (!Double.isInfinite(productionFiringTime)) // nextProductionFiringTime = productionFiringTime + now; // else if (queue.isEmpty()) // { // } // else // { // /* // * no production fired, but we have events to consider. nextWait time is // * minimum of the next event's firing time or the next production firing // * time // */ // nextWaitTime = queue.getNextEndTime(); // // if (!model.isCycleSkippingEnabled()) // { // /* // * if the two are absurdly close, just take the larger of the two. this // * prevents the occasional situation (w/o cycle skipping) where the // * production may fire microseconds before the event is to expire. since // * no production fires, the goal is cleared, but then the event fires // * and there is no one left to handle it. this prevents the whacky // * duplicate time display since the time display is rounded to the // * millisecond, we're missing that these are just ever so slightly // * different // */ // if (Math.abs(_nextPossibleProductionFiringTime - nextWaitTime) < 0.001) // nextWaitTime = Math.max(_nextPossibleProductionFiringTime, // nextWaitTime); // else // nextWaitTime = Math.min(_nextPossibleProductionFiringTime, // nextWaitTime); // } // else // { // // } // // } return nextWaitTime; } /** * using the current contents of the buffer, derive the conflict set and * select the best production. Request it to be fired, and eventually return * the result. * * @return firing time (or NaN if we should quit) or -inf if no production was * fired */ protected double evaluateAndFireProduction(BasicModel model, double currentTime) throws InterruptedException, ExecutionException { if (_nextPossibleProductionFiringTime > currentTime) return Double.NEGATIVE_INFINITY; /* * get the buffers and their contents */ Collection<IActivationBuffer> buffers = model.getActivationBuffers(); /* * and the procedural module since we'll be using him quite frequently */ IProceduralModule procMod = model.getProceduralModule(); /* * snag the conflict set, procMod should fire the notification events itself * this and selectInstantiation should not be separate methods, or better * yet, provide a third method */ Future<Collection<IInstantiation>> conflictSet = procMod .getConflictSet(buffers); /* * figure out who is the best production.. */ IInstantiation instantiation = null; instantiation = procMod.selectInstantiation(conflictSet.get()).get(); if (LOGGER.isDebugEnabled()) LOGGER.debug("Conflict resolution selected " + instantiation); /* * and fire his arse.. and snag his return time -inf if no production was * fired, NaN if we should quit */ double firingDuration = Double.NEGATIVE_INFINITY; if (instantiation != null) try { if (LOGGER.isDebugEnabled()) LOGGER.debug("Firing " + instantiation); firingDuration = procMod.fireProduction(instantiation, currentTime) .get(); _nextPossibleProductionFiringTime = currentTime + firingDuration; } catch (ExecutionException e) { throw new ExecutionException("Failed to execute " + instantiation, e .getCause()); } else _nextPossibleProductionFiringTime = currentTime + procMod.getDefaultProductionFiringTime(); return firingDuration; } public void executeAfter(Runnable runner) { synchronized (_executeAfter) { _executeAfter.add(runner); } } public void executeBefore(Runnable runner) { synchronized (_executeBefore) { _executeBefore.add(runner); } } public boolean isExecuting() { return _isExecuting; } }