/*
* Created on Jun 26, 2007 Copyright (C) 2001-2007, 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.modules.pm.common.memory.map;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javolution.util.FastSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.commonreality.identifier.IIdentifier;
import org.commonreality.object.IAfferentObject;
import org.commonreality.object.delta.IObjectDelta;
import org.jactr.core.chunk.IChunk;
import org.jactr.core.event.ACTREventDispatcher;
import org.jactr.core.model.IModel;
import org.jactr.core.production.request.ChunkTypeRequest;
import org.jactr.core.queue.timedevents.AbstractTimedEvent;
import org.jactr.core.runtime.ACTRRuntime;
import org.jactr.core.slot.BasicSlot;
import org.jactr.core.slot.IConditionalSlot;
import org.jactr.core.slot.ISlot;
import org.jactr.modules.pm.common.memory.IPerceptualMemory;
/**
* FINST tracking feature map that is used by both the aural and visual modules.
* Unlike all the other {@link IFeatureMap}s, this one is accessed by both the
* common reality and model threads. As such, it is necessary to use a
* {@link ReentrantReadWriteLock} to protect the data. Commonreality accesses
* this feature map (like all the other feature maps) through the
* {@link #afferentObjectAdded(IAfferentObject)},
* {@link #afferentObjectRemoved(IAfferentObject)},
* {@link #afferentObjectUpdated(IAfferentObject, IObjectDelta)} methods. The
* model thread accesses it indirectly through the {@link FlagAsOld} timed event
* which is posted by any call to
* {@link #flagAsAttended(IIdentifier, IChunk, double)},
* {@link #flagAsNew(IIdentifier, IChunk, double)}, or
* {@link #flagAsOld(IIdentifier, IChunk)}.<br>
* <br>
* Without this thread synchronization, deadlock or concurrent modifications
* would occur.<br>
* <br>
* There is still much improvement that can be made in terms of the granularity
* and speed, the current thread safety is course and rather slow. [the old,
* unprotected version ran allowed models to run at 500x real time, this version
* brings it down to 250x] <br>
* <br>
* Note: this does not fire events
*
* @author developer
*/
public class DefaultFINSTFeatureMap implements IFINSTFeatureMap
{
/**
* logger definition
*/
static private final Log LOGGER = LogFactory
.getLog(DefaultFINSTFeatureMap.class);
private int _maximumFINSTs;
private String _attendedSlotName;
IModel _model;
IChunk _newChunk;
Map<IIdentifier, FINST> _attendedIdentifiers;
Map<IIdentifier, FINST> _oldIdentifiers;
Map<IIdentifier, FINST> _newIdentifiers;
@SuppressWarnings("unchecked")
private ACTREventDispatcher<IFeatureMap, IFeatureMapListener> _dispatcher = new ACTREventDispatcher<IFeatureMap, IFeatureMapListener>();
private IPerceptualMemory _memory;
private ReentrantReadWriteLock _lock = new ReentrantReadWriteLock();
public DefaultFINSTFeatureMap(IModel model, String attendedSlotName)
{
_attendedSlotName = attendedSlotName;
_model = model;
_newChunk = model.getDeclarativeModule().getNewChunk();
_newIdentifiers = new HashMap<IIdentifier, FINST>();
_oldIdentifiers = new HashMap<IIdentifier, FINST>();
_attendedIdentifiers = new HashMap<IIdentifier, FINST>();
}
public void addListener(IFeatureMapListener listener, Executor executor)
{
_dispatcher.addListener(listener, executor);
}
public void removeListener(IFeatureMapListener listener)
{
_dispatcher.removeListener(listener);
}
protected boolean hasListeners()
{
return _dispatcher.hasListeners();
}
protected void dispatch(FeatureMapEvent event)
{
_dispatcher.fire(event);
}
public void setPerceptualMemory(IPerceptualMemory memory)
{
_memory = memory;
}
public IPerceptualMemory getPerceptualMemory()
{
return _memory;
}
/**
* @return
* @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#getNew()
*/
public void getNew(Set<IIdentifier> destination)
{
try
{
_lock.readLock().lock();
destination.addAll(_newIdentifiers.keySet());
}
finally
{
_lock.readLock().unlock();
}
}
public boolean isNew(IIdentifier identifier)
{
try
{
_lock.readLock().lock();
return _newIdentifiers.containsKey(identifier);
}
finally
{
_lock.readLock().unlock();
}
}
public void getOld(Set<IIdentifier> destination)
{
try
{
_lock.readLock().lock();
destination.addAll(_oldIdentifiers.keySet());
}
finally
{
_lock.readLock().unlock();
}
}
public boolean isOld(IIdentifier identifier)
{
try
{
_lock.readLock().lock();
return _oldIdentifiers.containsKey(identifier);
}
finally
{
_lock.readLock().unlock();
}
}
public void getAttended(Set<IIdentifier> destination)
{
try
{
_lock.readLock().lock();
destination.addAll(_attendedIdentifiers.keySet());
}
finally
{
_lock.readLock().unlock();
}
}
public boolean isAttended(IIdentifier identifier)
{
try
{
_lock.readLock().lock();
return _attendedIdentifiers.containsKey(identifier);
}
finally
{
_lock.readLock().unlock();
}
}
protected FINST getFINST(IIdentifier identifier)
{
try
{
_lock.readLock().lock();
FINST finst = _newIdentifiers.get(identifier);
if (finst == null) finst = _oldIdentifiers.get(identifier);
if (finst == null) finst = _attendedIdentifiers.get(identifier);
return finst;
}
finally
{
_lock.readLock().unlock();
}
}
public int getMaximumFINSTs()
{
return _maximumFINSTs;
}
public void setMaximumFINSTs(int max)
{
_maximumFINSTs = max;
}
private void assignFINST(FINST finst)
{
IIdentifier identifier = finst.getIdentifier();
if (isAttended(identifier))
{
if (LOGGER.isDebugEnabled())
LOGGER.debug(identifier + " already has a finst assigned");
// do nothing..
return;
}
try
{
_lock.writeLock().lock();
while (_attendedIdentifiers.size() >= getMaximumFINSTs())
{
FINST oldFinst = findOldestFINST();
if (oldFinst != null)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("FINST limit reached, removing oldest : "
+ oldFinst.getIdentifier());
flagAsOld(oldFinst.getIdentifier(), oldFinst.getAfferentChunk());
}
}
_attendedIdentifiers.put(identifier, finst);
}
finally
{
_lock.writeLock().unlock();
}
if (LOGGER.isDebugEnabled())
LOGGER.debug("assigning finst to " + identifier);
}
/**
* find the oldest finst, this should only be called from assignFinst as it
* requires write lock
*
* @return
*/
private FINST findOldestFINST()
{
try
{
_lock.writeLock().lock();
FINST oldest = null;
double time = Double.POSITIVE_INFINITY;
for (FINST finst : _attendedIdentifiers.values())
if (finst.getTime() < time) oldest = finst;
return oldest;
}
finally
{
_lock.writeLock().unlock();
}
}
/**
* @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#flagAsAttended(org.commonreality.identifier.IIdentifier,
* org.jactr.core.chunk.IChunk)
*/
public void flagAsAttended(IIdentifier identifier, IChunk chunk,
double duration)
{
FINST finst = null;
FlagAsOld fao = this.new FlagAsOld(identifier, chunk, duration);
try
{
_lock.writeLock().lock();
finst = removeFINST(identifier);
if (finst == null) finst = _newIdentifiers.remove(identifier);
// already attended?
if (finst == null) finst = removeFINST(identifier);
if (finst == null)
{
LOGGER.warn("Flagging " + identifier
+ " as attended, but it was never new or old");
finst = new FINST(identifier, chunk);
}
finst.setAfferentChunk(chunk);
assignFINST(finst);
if (LOGGER.isDebugEnabled())
LOGGER.debug("Old: " + _oldIdentifiers.keySet() + " New: "
+ _newIdentifiers.keySet() + " Att:"
+ _attendedIdentifiers.keySet());
finst.setFlagAsOld(fao);
}
finally
{
_lock.writeLock().unlock();
}
// add the timed event to expire the attended
_model.getTimedEventQueue().enqueue(fao);
if (hasListeners())
dispatch(new FeatureMapEvent(this, ACTRRuntime.getRuntime()
.getClock(getPerceptualMemory().getModule().getModel()).getTime(),
FeatureMapEvent.Type.UPDATED, Collections.singleton(identifier)));
}
/**
* @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#flagAsNew(org.commonreality.identifier.IIdentifier,
* org.jactr.core.chunk.IChunk)
*/
public void flagAsNew(IIdentifier identifier, IChunk chunk, double duration)
{
FINST finst = null;
FlagAsOld fao = this.new FlagAsOld(identifier, chunk, duration);
try
{
_lock.writeLock().lock();
finst = removeFINST(identifier);
if (finst == null)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Created new finst object to track " + identifier);
finst = this.new FINST(identifier, chunk);
}
else
finst.setAfferentChunk(chunk);
_newIdentifiers.put(identifier, finst);
if (LOGGER.isDebugEnabled())
LOGGER.debug("Old: " + _oldIdentifiers.keySet() + " New: "
+ _newIdentifiers.keySet() + " Att:"
+ _attendedIdentifiers.keySet());
finst.setFlagAsOld(fao);
}
finally
{
_lock.writeLock().unlock();
}
// add the timed event to expire the new
_model.getTimedEventQueue().enqueue(fao);
if (hasListeners())
dispatch(new FeatureMapEvent(this, ACTRRuntime.getRuntime()
.getClock(getPerceptualMemory().getModule().getModel()).getTime(),
FeatureMapEvent.Type.UPDATED, Collections.singleton(identifier)));
}
/**
* @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#flagAsOld(org.commonreality.identifier.IIdentifier,
* org.jactr.core.chunk.IChunk)
*/
public void flagAsOld(IIdentifier identifier, IChunk chunk)
{
try
{
_lock.writeLock().lock();
FINST finst = removeFINST(identifier);
if (finst == null)
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Flagging " + identifier
+ " as old, but it was never new or attended");
finst = new FINST(identifier, chunk);
}
finst.setFlagAsOld(null);
_oldIdentifiers.put(identifier, finst);
if (LOGGER.isDebugEnabled())
LOGGER.debug("Old: " + _oldIdentifiers.keySet() + " New: "
+ _newIdentifiers.keySet() + " Att:"
+ _attendedIdentifiers.keySet());
}
finally
{
_lock.writeLock().unlock();
}
if (hasListeners())
dispatch(new FeatureMapEvent(this, ACTRRuntime.getRuntime()
.getClock(getPerceptualMemory().getModule().getModel()).getTime(),
FeatureMapEvent.Type.UPDATED, Collections.singleton(identifier)));
}
/**
* @see org.jactr.modules.pm.common.memory.map.IFINSTFeatureMap#reset()
*/
public void reset()
{
Collection<FINST> finsts = null;
try
{
_lock.readLock().lock();
finsts = new ArrayList<FINST>(_attendedIdentifiers.values());
}
finally
{
_lock.readLock().unlock();
}
for (FINST finst : finsts)
flagAsOld(finst.getIdentifier(), finst.getAfferentChunk());
}
/**
* @see org.jactr.modules.pm.common.memory.map.IFeatureMap#clear()
*/
public void clear()
{
try
{
_lock.writeLock().lock();
_newIdentifiers.clear();
_oldIdentifiers.clear();
_attendedIdentifiers.clear();
}
finally
{
_lock.writeLock().unlock();
}
}
/**
* @see org.jactr.modules.pm.common.memory.map.IFeatureMap#dispose()
*/
public void dispose()
{
clear();
}
/**
* @see org.jactr.modules.pm.common.memory.map.IFeatureMap#fillSlotValues(ChunkTypeRequest,
* org.commonreality.identifier.IIdentifier, IChunk, ChunkTypeRequest)
*/
public void fillSlotValues(ChunkTypeRequest mutableRequest,
IIdentifier identifier, IChunk encodedChunk,
ChunkTypeRequest originalSearchRequest)
{
boolean isNew = false;
boolean isAttended = false;
FINST finst = getFINST(identifier);
isNew = isNew(identifier);
isAttended = isAttended(identifier);
if (finst != null) if (isNew)
mutableRequest.addSlot(new BasicSlot(_attendedSlotName, _newChunk));
else if (isAttended)
mutableRequest.addSlot(new BasicSlot(_attendedSlotName, Boolean.TRUE));
else
mutableRequest.addSlot(new BasicSlot(_attendedSlotName, null));
}
public FINSTState getInformation(IIdentifier identifier)
{
try
{
_lock.readLock().lock();
if (_newIdentifiers.containsKey(identifier)) return FINSTState.NEW;
if (_oldIdentifiers.containsKey(identifier)) return FINSTState.OLD;
if (_attendedIdentifiers.containsKey(identifier))
return FINSTState.ATTENDED;
return FINSTState.UNKNOWN;
}
finally
{
_lock.readLock().unlock();
}
}
/**
* @see org.jactr.modules.pm.common.memory.map.IFeatureMap#getCandidateRealObjects(ChunkTypeRequest,
* Set)
*/
public void getCandidateRealObjects(ChunkTypeRequest request,
Set<IIdentifier> container)
{
FastSet<IIdentifier> tmp = FastSet.newInstance();
boolean firstInsertion = true;
for (IConditionalSlot slot : request.getConditionalSlots())
if (slot.getName().equalsIgnoreCase(_attendedSlotName))
{
tmp.clear();
Object value = slot.getValue();
switch (slot.getCondition())
{
case IConditionalSlot.NOT_EQUALS:
not(value, tmp);
break;
default:
equals(value, tmp);
break;
}
if (firstInsertion)
{
container.addAll(tmp);
firstInsertion = false;
}
else
container.retainAll(tmp);
}
FastSet.recycle(tmp);
}
private void equals(Object value, Set<IIdentifier> container)
{
if (_newChunk.equals(value))
getNew(container);
else if (Boolean.TRUE.equals(value))
getAttended(container);
else if (Boolean.FALSE.equals(value) || value == null) getOld(container);
if (LOGGER.isDebugEnabled())
LOGGER.debug("==" + value + " returning " + container);
}
private void not(Object value, Set<IIdentifier> container)
{
if (_newChunk.equals(value))
{
// all but new
getAttended(container);
getOld(container);
}
else if (Boolean.TRUE.equals(value))
{
getOld(container);
getNew(container);
}
else if (Boolean.FALSE.equals(value) || value == null)
{
getNew(container);
getAttended(container);
}
if (LOGGER.isDebugEnabled())
LOGGER.debug("!=" + value + " returning " + container);
}
/**
* @see org.jactr.modules.pm.common.afferent.IAfferentObjectListener#afferentObjectAdded(org.commonreality.object.IAfferentObject)
*/
public void afferentObjectAdded(IAfferentObject object)
{
// NoOp
}
/**
* @see org.jactr.modules.pm.common.afferent.IAfferentObjectListener#afferentObjectRemoved(org.commonreality.object.IAfferentObject)
*/
public void afferentObjectRemoved(IAfferentObject object)
{
IIdentifier identifier = object.getIdentifier();
FINST finst = removeFINST(identifier);
if (finst != null && finst.getFlagAsOld() != null)
finst.getFlagAsOld().abort();
if (LOGGER.isDebugEnabled())
LOGGER.debug("removed " + identifier + " old:" + _oldIdentifiers.keySet()
+ " new:" + _newIdentifiers.keySet() + " att:"
+ _attendedIdentifiers.keySet());
}
/**
* @see org.jactr.modules.pm.common.afferent.IAfferentObjectListener#afferentObjectUpdated(org.commonreality.object.IAfferentObject,
* org.commonreality.object.delta.IObjectDelta)
*/
public void afferentObjectUpdated(IAfferentObject object, IObjectDelta delta)
{
// NoOp?
}
/**
* @see org.jactr.modules.pm.common.afferent.IAfferentObjectListener#isInterestedIn(org.commonreality.object.IAfferentObject)
*/
public boolean isInterestedIn(IAfferentObject object)
{
return false;
}
public boolean isInterestedIn(ChunkTypeRequest request)
{
for (ISlot slot : request.getSlots())
if (slot.getName().equalsIgnoreCase(_attendedSlotName)) return true;
return false;
}
private FINST removeFINST(IIdentifier identifier)
{
try
{
_lock.writeLock().lock();
if (isAttended(identifier))
return _attendedIdentifiers.remove(identifier);
if (isOld(identifier)) return _oldIdentifiers.remove(identifier);
if (isNew(identifier)) return _newIdentifiers.remove(identifier);
return null;
}
finally
{
_lock.writeLock().unlock();
}
}
private class FlagAsOld extends AbstractTimedEvent
{
protected IIdentifier _identifier;
protected IChunk _afferentChunk;
public FlagAsOld(IIdentifier identifier, IChunk afferentChunk, double offset)
{
_identifier = identifier;
_afferentChunk = afferentChunk;
double start = ACTRRuntime.getRuntime().getClock(_model).getTime();
double end = start + offset;
setTimes(start, end);
}
@Override
public void fire(double currentTime)
{
if (LOGGER.isDebugEnabled()) LOGGER.debug("firing " + this);
if (!hasFired() && !hasAborted())
{
super.fire(currentTime);
flagAsOld(_identifier, _afferentChunk);
}
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder("(");
sb.append(getClass().getSimpleName());
sb.append(": start=").append(getStartTime());
sb.append(" end=").append(getEndTime());
sb.append(" id=").append(_identifier);
sb.append(" chunk=").append(_afferentChunk);
sb.append(" hash=").append(hashCode());
sb.append(")");
return sb.toString();
}
}
protected class FINST
{
protected double _time;
protected IChunk _afferentChunk;
protected IIdentifier _identifier;
protected FlagAsOld _timedEvent;
public FINST(IIdentifier identifier, IChunk afferentChunk)
{
_identifier = identifier;
setAfferentChunk(afferentChunk);
}
public void setFlagAsOld(FlagAsOld timedEvent)
{
if (_timedEvent != null && !_timedEvent.hasAborted()
&& !_timedEvent.hasFired())
{
if (LOGGER.isDebugEnabled())
LOGGER.debug("Aborting old timed event " + _timedEvent);
_timedEvent.abort();
}
_timedEvent = timedEvent;
}
public FlagAsOld getFlagAsOld()
{
return _timedEvent;
}
public IChunk getAfferentChunk()
{
return _afferentChunk;
}
public void setAfferentChunk(IChunk afferentChunk)
{
_afferentChunk = afferentChunk;
_time = ACTRRuntime.getRuntime().getClock(_model).getTime();
}
public IIdentifier getIdentifier()
{
return _identifier;
}
public double getTime()
{
return _time;
}
}
public void normalizeRequest(ChunkTypeRequest request)
{
}
}