package org.jactr.modules.pm.motor; /* * default logging */ import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.Future; import java.util.concurrent.FutureTask; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.commonreality.agents.IAgent; import org.commonreality.efferent.IEfferentCommand; import org.commonreality.identifier.IIdentifier; import org.commonreality.modalities.motor.MovementCommand; import org.commonreality.object.IEfferentObject; import org.jactr.core.chunk.IChunk; import org.jactr.core.chunktype.IChunkType; import org.jactr.core.concurrent.ExecutorServices; import org.jactr.core.concurrent.FutureManager; import org.jactr.core.event.ACTREventDispatcher; import org.jactr.core.logging.Logger; import org.jactr.core.model.IModel; import org.jactr.core.production.condition.ChunkPattern; import org.jactr.core.production.request.ChunkTypeRequest; import org.jactr.core.queue.ITimedEvent; import org.jactr.core.queue.timedevents.AbstractTimedEvent; import org.jactr.core.runtime.ACTRRuntime; import org.jactr.modules.pm.AbstractPerceptualModule; import org.jactr.modules.pm.motor.buffer.IMotorActivationBuffer; import org.jactr.modules.pm.motor.command.DefaultCommandTranslator; import org.jactr.modules.pm.motor.command.ICommandTranslator; import org.jactr.modules.pm.motor.command.ICommandTranslatorDelegate; import org.jactr.modules.pm.motor.command.IMotorTimeEquation; import org.jactr.modules.pm.motor.command.IMovement; import org.jactr.modules.pm.motor.event.IMotorModuleListener; import org.jactr.modules.pm.motor.event.MotorModuleEvent; import org.jactr.modules.pm.motor.managers.MotorCommandManager; import org.jactr.modules.pm.motor.managers.MuscleStateManager; /** * abstract motor module that handles just about everything one needs. It uses a * combination of the default {@link MotorManager} (which handles all the logic * of communicating with common reality, as well as the movement lifecylce), the * {@link DefaultCommandTranslator} to route {@link ChunkPattern} movement * requests to specific {@link ICommandTranslatorDelegate}s, and uses * {@link IMotorTimeEquation} to determine how long preparation and processing * phases last. <br> * <br> * The {@link ICommandTranslatorDelegate}s map {@link ChunkPattern}s and Strings * to specific muscles, as defined by {@link IEfferentObject}s, as well as * translating the {@link ChunkPattern}s into precise {@link IEfferentCommand}s * which will be passed to common reality by the {@link MotorManager}. There * should be a unique {@link ICommandTranslatorDelegate} for each and every * movement chunktype defined. <br> * <br> * One will notice that only preparation and processing times are directly * controled by the motor module. execution times can only be indirectly * specified since execution time is actually up to common reality and the * sensor that receives the command. It can be influenced by having the * {@link ICommandTranslatorDelegate} set the * {@link MovementCommand#MOVEMENT_RATE} property of the * {@link IEfferentCommand}. <br> * <br> * If StrictSynchronizationEnabled is false, preparation and processing times * may not match ACT-R's predictions. If it is enabled, processing will block * until a response is received from CR. However, run times may still not be * consistent since the sensor may require more time than predicted by ACT-R. * * @author harrison */ public abstract class AbstractMotorModule extends AbstractPerceptualModule implements IMotorModule { /** * Logger definition */ protected static final transient Log LOGGER = LogFactory .getLog(AbstractMotorModule.class); static public final String ENABLE_PARALLEL_MUSCLES_PARAM = "EnableMuscleLevelParallelism"; static public final String COMPOUND_MOTOR_COMMAND_CHUNK_TYPE = "compound-motor-command"; private IChunkType _movementChunkType; private IChunkType _compoundCommandChunkType; private IChunkType _abortChunkType; private IChunk _abortingChunk; private ICommandTranslator _commandTranslator; private IMotorTimeEquation _preparationTimeEquation; private IMotorTimeEquation _processingTimeEquation; private boolean _enableMuscleParallelism = false; private MuscleStateManager _muscleManager; private MotorCommandManager _motorCommandManager; private FutureManager<IMovement, IMovement> _futureManager = new FutureManager<IMovement, IMovement>(); private ACTREventDispatcher<IMotorModule, IMotorModuleListener> _listener; public AbstractMotorModule(String name) { super(name); _listener = new ACTREventDispatcher<IMotorModule, IMotorModuleListener>(); setCommandTranslator(new DefaultCommandTranslator()); addListener(new IMotorModuleListener() { public void movementAborted(MotorModuleEvent event) { _futureManager.release(event.getMovement(), event.getMovement()); } public void movementCompleted(MotorModuleEvent event) { _futureManager.release(event.getMovement(), event.getMovement()); } public void movementPrepared(MotorModuleEvent event) { _futureManager.release(event.getMovement(), event.getMovement()); } public void movementStarted(MotorModuleEvent event) { _futureManager.release(event.getMovement(), event.getMovement()); } public void movementRejected(MotorModuleEvent event) { _futureManager.release(event.getMovement(), event.getMovement()); } }, ExecutorServices.INLINE_EXECUTOR); } public void setCommandTranslator(ICommandTranslator translator) { _commandTranslator = translator; } public ICommandTranslator getCommandTranslator() { return _commandTranslator; } public void setProcessingTimeEquation(IMotorTimeEquation equation) { _processingTimeEquation = equation; } public IMotorTimeEquation getProcessingTimeEquation() { return _processingTimeEquation; } public void setPreparationTimeEquation(IMotorTimeEquation equation) { _preparationTimeEquation = equation; } public IMotorTimeEquation getPreparationTimeEquation() { return _preparationTimeEquation; } public IChunkType getMovementChunkType() { return _movementChunkType; } public IChunkType getAbortChunkType() { return _abortChunkType; } public IChunk getAbortingChunk() { return _abortingChunk; } public IChunkType getCompoundCommandChunkType() { return _compoundCommandChunkType; } @Override public void initialize() { super.initialize(); _movementChunkType = getNamedChunkType(MOVEMENT_CHUNK_TYPE); _abortChunkType = getNamedChunkType(ABORT_CHUNK_TYPE); _abortingChunk = getNamedChunk(ABORTING_CHUNK); _compoundCommandChunkType = getNamedChunkType(COMPOUND_MOTOR_COMMAND_CHUNK_TYPE); } /** * @return */ public boolean isMuscleParallelismEnabled() { return _enableMuscleParallelism; } public void setMuscleParallelismEnabled(boolean enable) { _enableMuscleParallelism = enable; } @Override protected void connectToCommonReality() { super.connectToCommonReality(); _muscleManager = new MuscleStateManager(this); _motorCommandManager = new MotorCommandManager(this); IAgent agent = ACTRRuntime.getRuntime().getConnector().getAgent(getModel()); if (LOGGER.isDebugEnabled()) LOGGER.debug("Attaching translator to common reality"); _muscleManager.install(agent); _motorCommandManager.install(agent); } @Override protected void disconnectFromCommonReality() { super.disconnectFromCommonReality(); IAgent agent = ACTRRuntime.getRuntime().getConnector().getAgent(getModel()); _muscleManager.uninstall(agent); _motorCommandManager.uninstall(agent); } public boolean canPrepare(ChunkTypeRequest request) { boolean prepareable = _motorCommandManager.canPrepare(request); if (!isMuscleParallelismEnabled()) prepareable &= !getBuffer().isPreparationBusy(); return prepareable; } public Future<IMovement> prepare(ChunkTypeRequest pattern, double requestTime, boolean prepareOnly) { try { IMovement movement = _motorCommandManager.prepare(pattern, requestTime, prepareOnly); return _futureManager.acquire(movement); } catch (Exception e) { String message = String.format( "Could not prepare motor command %s because %s.", pattern, e .getMessage()); IModel model = getModel(); if (Logger.hasLoggers(model)) Logger.log(model, Logger.Stream.MOTOR, message); if (LOGGER.isDebugEnabled()) LOGGER.debug(message, e); FutureTask<IMovement> nullMovement = new FutureTask<IMovement>( new Callable<IMovement>() { public IMovement call() throws Exception { return null; } }); nullMovement.run(); return nullMovement; } } public boolean canExecute(ChunkTypeRequest request) { boolean executable = _motorCommandManager.canExecute(request); if (!isMuscleParallelismEnabled()) { IMotorActivationBuffer buffer = getBuffer(); executable &= !buffer.isExecutionBusy(); executable &= !buffer.isProcessorBusy(); } return executable; } public Future<IMovement> execute(IMovement movement, double requestTime) { try { _futureManager.release(movement, movement); movement = _motorCommandManager.execute(movement, requestTime); return _futureManager.acquire(movement); } catch (Exception e) { String message = String.format( "Could not execute motor command %s because %s.", movement, e .getMessage()); IModel model = getModel(); if (Logger.hasLoggers(model)) Logger.log(model, Logger.Stream.MOTOR, message); if (LOGGER.isDebugEnabled()) LOGGER.debug(message, e); FutureTask<IMovement> nullMovement = new FutureTask<IMovement>( new Callable<IMovement>() { public IMovement call() throws Exception { return null; } }); nullMovement.run(); return nullMovement; } } public boolean canAdjust(ChunkTypeRequest request) { boolean adjustable = _motorCommandManager.canAdjust(request); if (!isMuscleParallelismEnabled()) adjustable &= getBuffer().isExecutionBusy(); return adjustable; } public boolean canAdjust(IMovement movement) { boolean adjustable = _motorCommandManager.canAdjust(movement); if (!isMuscleParallelismEnabled()) adjustable &= getBuffer().isExecutionBusy(); return adjustable; } public Future<IMovement> adjust(IMovement movement, ChunkTypeRequest request, double requestTime) { try { _futureManager.release(movement, movement); movement = _motorCommandManager.adjust(movement, request, requestTime); return _futureManager.acquire(movement); } catch (Exception e) { String message = String.format( "Could not adjust motor command %s because %s.", movement, e .getMessage()); IModel model = getModel(); if (Logger.hasLoggers(model)) Logger.log(model, Logger.Stream.MOTOR, message); if (LOGGER.isDebugEnabled()) LOGGER.debug(message, e); FutureTask<IMovement> nullMovement = new FutureTask<IMovement>( new Callable<IMovement>() { public IMovement call() throws Exception { return null; } }); nullMovement.run(); return nullMovement; } } public boolean canAbort(ChunkTypeRequest request) { boolean abortable = _motorCommandManager.canAbort(request); if (!isMuscleParallelismEnabled()) { IMotorActivationBuffer buffer = getBuffer(); abortable &= buffer.isExecutionBusy() || buffer.isPreparationBusy() || buffer.isProcessorBusy(); } return abortable; } public boolean canAbort(IMovement movement) { boolean abortable = _motorCommandManager.canAbort(movement); if (!isMuscleParallelismEnabled()) { IMotorActivationBuffer buffer = getBuffer(); abortable &= buffer.isExecutionBusy() || buffer.isPreparationBusy() || buffer.isProcessorBusy(); } return abortable; } public Future<IMovement> abort(IMovement movement, double requestTime) { // _motorManager.abort(movement); try { _futureManager.release(movement, movement); movement = _motorCommandManager.abort(movement, requestTime); return _futureManager.acquire(movement); } catch (Exception e) { String message = String.format( "Could not abort motor command %s because %s.", movement, e .getMessage()); IModel model = getModel(); if (Logger.hasLoggers(model)) Logger.log(model, Logger.Stream.MOTOR, message); if (LOGGER.isDebugEnabled()) LOGGER.debug(message, e); FutureTask<IMovement> nullMovement = new FutureTask<IMovement>( new Callable<IMovement>() { public IMovement call() throws Exception { return null; } }); nullMovement.run(); return nullMovement; } } public void reset() { double now = ACTRRuntime.getRuntime().getClock(getModel()).getTime(); final FutureTask<Object> clearIt = new FutureTask<Object>(new Runnable() { public void run() { _motorCommandManager.clear(); getBuffer().clear(); } }, null); ITimedEvent ite = new AbstractTimedEvent(now, now + 0.05) { @Override public void fire(double now) { super.fire(now); try { clearIt.get(); } catch (Exception e) { /** * Error : Co */ LOGGER.error("could not reset motor module! ", e); } } }; getCommonRealityExecutor().execute(clearIt); getModel().getTimedEventQueue().enqueue(ite); } public IMovement getLastMovement(IIdentifier muscle) { // return _motorManager.getLastPreparedMovement(muscle); return _motorCommandManager.getPreparedMovement(muscle); } public MotorCommandManager getCommandManager() { return _motorCommandManager; } public MuscleStateManager getMuscleManager() { return _muscleManager; } @Override public Collection<String> getSetableParameters() { ArrayList<String> rtn = new ArrayList<String>(super.getSetableParameters()); rtn.add(ENABLE_PARALLEL_MUSCLES_PARAM); /* * we also include all the class names of the delegates */ for (ICommandTranslatorDelegate delegate : ((DefaultCommandTranslator) getCommandTranslator()) .getDelegates()) rtn.add(delegate.getClass().getName()); return rtn; } @Override public void setParameter(String key, String value) { if (ENABLE_PARALLEL_MUSCLES_PARAM.equalsIgnoreCase(key)) setMuscleParallelismEnabled(Boolean.parseBoolean(value)); else if (isClassName(key)) { if (Boolean.parseBoolean(value)) try { /* * try to add */ ICommandTranslatorDelegate delegate = (ICommandTranslatorDelegate) getClass() .getClassLoader().loadClass(key).newInstance(); ((DefaultCommandTranslator) getCommandTranslator()).add(delegate); } catch (Exception e) { if (LOGGER.isWarnEnabled()) LOGGER.warn("Could not install ICommandTranslatorDelegate : " + value + " : ", e); } else /* * try to remove */ for (ICommandTranslatorDelegate delegate : ((DefaultCommandTranslator) getCommandTranslator()) .getDelegates()) if (delegate.getClass().getName().equals(key)) ((DefaultCommandTranslator) getCommandTranslator()) .remove(delegate); } else super.setParameter(key, value); } @Override public String getParameter(String key) { if (ENABLE_PARALLEL_MUSCLES_PARAM.equalsIgnoreCase(key)) return "" + isMuscleParallelismEnabled(); else if (isClassName(key)) { for (ICommandTranslatorDelegate delegate : ((DefaultCommandTranslator) getCommandTranslator()) .getDelegates()) if (delegate.getClass().getName().equals(key)) return "true"; return "false"; } else return super.getParameter(key); } protected boolean isClassName(String key) { try { getClass().getClassLoader().loadClass(key); return true; } catch (Exception e) { return false; } } public void addListener(IMotorModuleListener listener, Executor executor) { _listener.addListener(listener, executor); } public boolean hasListeners() { return _listener.hasListeners(); } public void removeListener(IMotorModuleListener listener) { _listener.removeListener(listener); } public void dispatch(MotorModuleEvent event) { if (_listener.hasListeners()) _listener.fire(event); } }