/*
* 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.commonreality.time.impl.BasicClock;
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;
private final double TEMPORAL_TOLERANCE = 0.0001;
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 (nextWaitTime <= now)
LOGGER
.error(String
.format(
"WARNING: Time discrepancy detected. Next cycle time : %.10f(next) <= %.10f(current). Should be >",
nextWaitTime, now));
if (LOGGER.isDebugEnabled())
LOGGER.debug("nextWaitTime : " + nextWaitTime);
}
catch (InterruptedException ie)
{
if (LOGGER.isWarnEnabled())
LOGGER.warn("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 <br/>
*
* @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;
if (!queue.isEmpty()) nextEventFiringTime = queue.getNextEndTime(); // already
// constrained
double nextWaitTime = Math.min(nextProductionFiringTime,
nextEventFiringTime);
/*
* no production fired
*/
if (Double.isInfinite(productionFiringTime)
|| Double.isNaN(productionFiringTime))
if (queue.isEmpty() && !eventsHaveFired)
{
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 != null && goalBuffer.getSourceChunk() != null)
goalBuffer.clear();
else
return Double.NaN; // signal quit
}
}
/*
* we only skip cycles if no events have fired. If events have fired, then
* productions might be able to fire..
*/
else if (model.isCycleSkippingEnabled())
{
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) < TEMPORAL_TOLERANCE)
{
nextWaitTime = Math.max(nextProductionFiringTime, nextEventFiringTime);
if (LOGGER.isDebugEnabled())
LOGGER
.warn(String
.format(
"Dangerously close timing : nextProd (%.5f) and nextEvent (%.5f) are insanely close, using larger (%.5f)",
nextProductionFiringTime, nextEventFiringTime, nextWaitTime));
}
if (nextWaitTime <= now)
{
double newWaitTime = Math
.nextAfter(now + 0.001,
Double.POSITIVE_INFINITY);
if (LOGGER.isWarnEnabled())
LOGGER
.warn(String
.format(
"nextWaitTime (%.5f) is less than or equal to the time (%.5f), incrementing to (%.5f). eventsFired=%s nextEvent=%.2f productionFiringTime=%.2f",
nextWaitTime, now, newWaitTime, eventsHaveFired,
queue.getNextEndTime(), productionFiringTime));
nextWaitTime = newWaitTime;
}
/*
* if we didn't fire a production, we might be able to right after the event
* has fired
*/
if (Double.isNaN(productionFiringTime))
_nextPossibleProductionFiringTime = nextWaitTime;
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 -inf if no production can fire yet, NaN if conflict
* resolution ran but nothing was fired
*/
protected double evaluateAndFireProduction(BasicModel model,
double currentTime) throws InterruptedException, ExecutionException
{
/*
* if current time less than nextpossible (w/ in tolerance) we know no
* production could fire. We need the tolerance just in case nextpossible is
* 0.1000000002 and current is 0.100000
*/
if (_nextPossibleProductionFiringTime - currentTime > TEMPORAL_TOLERANCE)
{
if (LOGGER.isDebugEnabled())
LOGGER
.debug(String
.format(
"nextPossibleFiringTime (%.4f) is greater than current time (%.4f), no production may fire yet.",
_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 Nan if no production was
* fired
*/
double firingDuration = Double.NaN;
if (instantiation != null)
try
{
if (LOGGER.isDebugEnabled()) LOGGER.debug("Firing " + instantiation);
firingDuration = procMod.fireProduction(instantiation, currentTime)
.get();
_nextPossibleProductionFiringTime = BasicClock
.constrainPrecision(currentTime + firingDuration);
}
catch (ExecutionException e)
{
throw new ExecutionException("Failed to execute " + instantiation,
e.getCause());
}
else
_nextPossibleProductionFiringTime = BasicClock
.constrainPrecision(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;
}
}