package org.jactr.core.buffer.delegate;
/*
* default logging
*/
import org.jactr.core.buffer.IActivationBuffer;
import org.jactr.core.buffer.six.AbstractActivationBuffer6;
import org.jactr.core.buffer.six.IStatusBuffer;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.model.IModel;
import org.jactr.core.module.procedural.IProceduralModule;
import org.jactr.core.production.request.IRequest;
import org.jactr.core.queue.ITimedEvent;
import org.jactr.core.queue.timedevents.AbstractTimedEvent;
import org.jactr.core.queue.timedevents.BlockingTimedEvent;
import org.jactr.core.queue.timedevents.IBufferBasedTimedEvent;
/**
* abstract request delegate that splits requests into two part process
* {@link #startRequest(IRequest, IActivationBuffer, double)} and
* {@link #finishRequest(IRequest, IActivationBuffer, Object)}. This makes it
* easier to implement asynchronous requests that start a computation and then
* later harvest the results.<br/>
* <br/>
* To make derived classes asynchronous, merely override
* {@link #isAsynchronous()} to return true, and use
* {@link #computeCompletionTime(double, IRequest, IActivationBuffer)} to return
* when the system should call
* {@link #finishRequest(IRequest, IActivationBuffer, Object)}.<br/>
* <br/>
* If {@link #finishRequest(IRequest, IActivationBuffer, Object)} requires some
* value from {@link #startRequest(IRequest, IActivationBuffer, double)} they
* both have an object that can be passed from one to the other.<br/>
* <br/>
* The asynchrony is implemented by calling
* {@link #startRequest(IRequest, IActivationBuffer, double)} and then queueing
* up a {@link ITimedEvent} that will fire at
* {@link #computeCompletionTime(double, IRequest, IActivationBuffer)} which
* will then call {@link #finishRequest(IRequest, IActivationBuffer, Object)}. <br/>
* <br/>
* You can also configure the delegate to use blocking timed events to ensure
* that the model clock does not advance beyond the harvest time (instead of
* relying upon some blocking mechanism in
* {@link #finishRequest(IRequest, IActivationBuffer, Object)}. However, if you
* do enable blocking timed events ({@link #setUseBlockingTimedEvents(boolean)}
* ), you must be sure that you call {@link #release()} in response to some
* event, otherwise the model will deadlock.<br/>
* <br/>
* <br/>
* This is for requested that are delayed in time (beyond the current cycle),
* such as visual requests. As opposed to buffers that accept new chunk
* insertions directly and immediately (like goal).
*
* @author harrison
*/
public abstract class AsynchronousRequestDelegate implements IRequestDelegate
{
private boolean _isAsynchronous = false;
private ITimedEvent _currentTimedEvent;
private IRequest _previousRequest;
/**
* canonical ACT-R doesn't fire requests until after the production has fired,
* this will tack 50ms onto the start time
*/
private boolean _delayRequestStart = true;
private BlockingTimedEvent _previousBlockingTimedEvent;
private boolean _useBlockingTimedEvents = false;
/**
* release the current blocking timed event, if any
*/
protected void release()
{
if (_previousBlockingTimedEvent != null
&& !_previousBlockingTimedEvent.hasAborted())
_previousBlockingTimedEvent.abort();
}
public void setUseBlockingTimedEvents(boolean use)
{
_useBlockingTimedEvents = use;
}
public boolean isUsingBlockingTimedEvents()
{
return _useBlockingTimedEvents;
}
public boolean isDelayingStart()
{
return _delayRequestStart;
}
public void setDelayStart(boolean delayStart)
{
_delayRequestStart = delayStart;
}
/**
* called the be sure the request is valid. If the request is valid but can't
* be accepted, return false. If the request is invalid, throw an exception
*
* @param request
* @param buffer
* TODO
* @return
*/
abstract protected boolean isValid(IRequest request, IActivationBuffer buffer)
throws IllegalArgumentException;
/**
* Start the buffer request, optimally returning an object for the
* {@link #finishRequest(IRequest, IActivationBuffer, Object)} to inspect. If
* this is an asynchronous request, this will be fired and finish will be
* fired at
* {@link #computeCompletionTime(double, IRequest, IActivationBuffer)}
*
* @param request
* @param buffer
* @param requestTime
* TODO
*/
abstract protected Object startRequest(IRequest request,
IActivationBuffer buffer, double requestTime);
/**
* finish the request
*
* @param request
* @param buffer
* @param startValue
*/
abstract protected void finishRequest(IRequest request,
IActivationBuffer buffer, Object startValue);
/**
* called if the timedevent is aborted
*
* @param request
* @param buffer
* @param startValue
*/
protected void abortRequest(IRequest request, IActivationBuffer buffer,
Object startValue)
{
release();
}
public void clear()
{
ITimedEvent event = getCurrentTimedEvent();
setCurrentTimedEvent(null);
if (event != null && !event.hasAborted() && !event.hasFired())
event.abort();
release();
}
final public void setAsynchronous(boolean isAsynch)
{
_isAsynchronous = isAsynch;
}
/**
* override to return true if the request should be split in time. be sure to
* override
* {@link #computeCompletionTime(double, IRequest, IActivationBuffer)} as well
*
* @return
*/
final public boolean isAsynchronous()
{
return _isAsynchronous;
}
/**
* default impl returns now +
* {@link IProceduralModule#getDefaultProductionFiringTime()}
*
* @param now
* @param request
* @param buffer
* TODO
* @return
*/
protected double computeCompletionTime(double startTime, IRequest request,
IActivationBuffer buffer)
{
return startTime
+ buffer.getModel().getProceduralModule()
.getDefaultProductionFiringTime();
}
/**
* expand requests gives subclassers the chance to modify the request after it
* has been validated but before the request is actually made. Subclassers are
* free to change the request in anyway. This impl merely passes the request
* through unchanged.
*
* @param request
* @return
*/
protected IRequest expandRequest(IRequest request)
{
return request;
}
/**
* make the buffer request. If synchronous
* {@link #startRequest(IRequest, IActivationBuffer, double)} and
* {@link #finishRequest(IRequest, IActivationBuffer, Object)} will be called
* immediately. If not, start will be called and a timed event will be posted
* that will handle
* {@link #finishRequest(IRequest, IActivationBuffer, Object)}
*
* @param request
* @param buffer
* @return
* @see org.jactr.core.buffer.delegate.IRequestDelegate#request(org.jactr.core.production.request.IRequest,
* org.jactr.core.buffer.IActivationBuffer)
*/
final public boolean request(IRequest request, IActivationBuffer buffer,
double requestTime)
{
if (!isValid(request, buffer)) return false;
request = expandRequest(request);
IModel model = buffer.getModel();
double start = requestTime;
/*
* lisp delays firing requests until after the production has completed, so
* start times need to be offset by def act time
*/
if (isDelayingStart())
start += model.getProceduralModule().getDefaultProductionFiringTime();
double finish = computeCompletionTime(start, request, buffer);
preStart(request, buffer, start, finish);
final Object startReturn = startRequest(request, buffer, requestTime);
postStart(request, buffer, start, finish, startReturn);
_previousRequest = request;
if (!isAsynchronous())
finishRequest(request, buffer, startReturn);
else
{
ITimedEvent timedEvent = createFinishTimedEvent(start, finish, request,
buffer, startReturn);
setCurrentTimedEvent(timedEvent);
model.getTimedEventQueue().enqueue(timedEvent);
}
return true;
}
/**
* called just before
* {@link #startRequest(IRequest, IActivationBuffer, double)} is called
*
* @param request
* @param buffer
* @param startTime
* @param finishTime
*/
protected void preStart(IRequest request, IActivationBuffer buffer,
double startTime, double finishTime)
{
if (!isAsynchronous()) return;
release();
if (isUsingBlockingTimedEvents())
{
/*
* we want to push finish time forward a smidgen.
*/
_previousBlockingTimedEvent = new BlockingTimedEvent(this, startTime,
finishTime);
buffer.getModel().getTimedEventQueue()
.enqueue(_previousBlockingTimedEvent);
}
}
protected void postStart(IRequest request, IActivationBuffer buffer,
double startTime, double finishTime, Object startReturn)
{
}
/**
* creates the timed event that will fire the finish method. If you want more
* control, override this method
*
* @param start
* @param finish
* @param request
* @param buffer
* @param startValue
* @return
*/
protected ITimedEvent createFinishTimedEvent(double start, double finish,
IRequest request, IActivationBuffer buffer, Object startValue)
{
return new FinishRequestTimedEvent(start, finish, request, buffer,
startValue);
}
/**
* returns the last timed event queued. This will only be changed after each
* request is called. it will not be nulled out after the event has fired
*
* @return
*/
protected ITimedEvent getCurrentTimedEvent()
{
return _currentTimedEvent;
}
protected void setCurrentTimedEvent(ITimedEvent event)
{
_currentTimedEvent = event;
}
protected IRequest getPreviousRequest()
{
return _previousRequest;
}
/**
* utility method to test the state, only works if this buffer extends
* {@link IStatusBuffer}
*
* @param buffer
* @return
*/
protected boolean isBusy(IActivationBuffer buffer)
{
return ((IStatusBuffer) buffer).isStateBusy();
}
/**
* utility method, only works if buffer extends
* {@link AbstractActivationBuffer6}
*
* @param buffer
*/
protected void setBusy(IActivationBuffer buffer)
{
AbstractActivationBuffer6 ab = (AbstractActivationBuffer6) buffer;
ab.setStateChunk(ab.getBusyChunk());
}
/**
* utility method, only works if buffer extends
* {@link AbstractActivationBuffer6}
*
* @param buffer
*/
protected void setFree(IActivationBuffer buffer)
{
AbstractActivationBuffer6 ab = (AbstractActivationBuffer6) buffer;
ab.setStateChunk(ab.getFreeChunk());
ab.setErrorChunk(null);
}
/**
* utility method, only works if buffer extends
* {@link AbstractActivationBuffer6}
*
* @param buffer
*/
protected void setError(IActivationBuffer buffer)
{
AbstractActivationBuffer6 ab = (AbstractActivationBuffer6) buffer;
ab.setStateChunk(ab.getErrorChunk());
}
protected void setError(IActivationBuffer buffer, IChunk errorCode)
{
AbstractActivationBuffer6 ab = (AbstractActivationBuffer6) buffer;
ab.setStateChunk(ab.getErrorChunk());
ab.setErrorChunk(errorCode);
}
private class FinishRequestTimedEvent extends AbstractTimedEvent implements
IBufferBasedTimedEvent
{
final private IActivationBuffer _buffer;
final private IRequest _request;
final private Object _startValue;
public FinishRequestTimedEvent(double start, double stop, IRequest request,
IActivationBuffer buffer, Object startValue)
{
super(start, stop);
_buffer = buffer;
_request = request;
_startValue = startValue;
}
public IChunk getBoundChunk()
{
return null;
}
public IActivationBuffer getBuffer()
{
return _buffer;
}
@Override
public void fire(double currentTime)
{
super.fire(currentTime);
finishRequest(_request, _buffer, _startValue);
}
@Override
public void abort()
{
super.abort();
abortRequest(_request, _buffer, _startValue);
}
}
}