org.jactr.core.model.six.DefaultCycleProcessor6.java Source code

Java tutorial

Introduction

Here is the source code for org.jactr.core.model.six.DefaultCycleProcessor6.java

Source

/*
 * 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;
    }
}