package org.jactr.modules.pm.common.memory.impl; /* * default logging */ import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javolution.util.FastList; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.commonreality.agents.IAgent; import org.commonreality.identifier.IIdentifier; import org.commonreality.object.IAfferentObject; import org.commonreality.object.delta.IObjectDelta; import org.jactr.core.buffer.BufferUtilities; import org.jactr.core.chunk.IChunk; import org.jactr.core.chunk.IllegalChunkStateException; import org.jactr.core.chunk.event.ChunkEvent; import org.jactr.core.chunk.event.IChunkListener; import org.jactr.core.concurrent.ExecutorServices; import org.jactr.core.module.declarative.IDeclarativeModule; import org.jactr.core.queue.timedevents.RunnableTimedEvent; import org.jactr.core.runtime.ACTRRuntime; import org.jactr.modules.pm.common.afferent.DefaultAfferentObjectListener; import org.jactr.modules.pm.common.afferent.IAfferentObjectListener; import org.jactr.modules.pm.common.memory.IPerceptualEncoder; import org.jactr.modules.pm.common.memory.PerceptualSearchResult; import org.jactr.modules.pm.common.memory.event.ActivePerceptEvent; /** * simple delegate that sits between the afferent object listener * {@link DefaultAfferentObjectListener} and the {@link IPerceptualEncoder} to * manage notification and caching.. * * @author harrison */ public class PerceptualEncoderBridge implements IAfferentObjectListener { /** * Logger definition */ static private final transient Log LOGGER = LogFactory .getLog(PerceptualEncoderBridge.class); private final IPerceptualEncoder _encoder; private final Map<IIdentifier, IChunk> _cache; private final Map<IIdentifier, Double> _onsetTime; private final IChunkListener _chunkListener; private final IDeclarativeModule _declarativeModule; private IChunk _removedErrorChunk; /** * to track add/remove of cached chunks */ // private final IActivationBufferListener _bufferListener; // private final ConcurrentHashMap<IChunk, AtomicInteger> _activeChunks; private final AbstractPerceptualMemory _memory; public PerceptualEncoderBridge(IPerceptualEncoder encoder, AbstractPerceptualMemory memory, IChunk removeErrorChunk) { _memory = memory; _encoder = encoder; _declarativeModule = _memory.getModule().getModel().getDeclarativeModule(); _cache = new HashMap<IIdentifier, IChunk>(); _onsetTime = new HashMap<IIdentifier, Double>(); _removedErrorChunk = removeErrorChunk; // _activeChunks = new ConcurrentHashMap<IChunk, AtomicInteger>(); /** * we remove the cached element when it is encoded */ _chunkListener = new IChunkListener() { public void chunkAccessed(ChunkEvent event) { } public void chunkEncoded(ChunkEvent event) { /* * remove from the cache. */ remove(getIdentifier(event.getSource()), true); } public void mergingInto(ChunkEvent event) { remove(getIdentifier(event.getSource()), true); } public void mergingWith(ChunkEvent event) { // noop } public void similarityChanged(ChunkEvent event) { } public void slotChanged(ChunkEvent event) { } }; } final public void clear() { // _activeChunks.clear(); FastList<IIdentifier> ids = FastList.newInstance(); ids.addAll(_cache.keySet()); for (IIdentifier id : ids) remove(id, true); _onsetTime.clear(); _cache.clear(); FastList.recycle(ids); } final public IPerceptualEncoder getEncoder() { return _encoder; } final public Set<IIdentifier> getCachedIdentifiers(Set<IIdentifier> container) { if (container == null) container = new HashSet<IIdentifier>(); container.addAll(_cache.keySet()); return container; } final public Set<IChunk> getCacheContents(Set<IChunk> container) { if (container == null) container = new HashSet<IChunk>(); container.addAll(_cache.values()); return container; } final public void afferentObjectAdded(IAfferentObject object) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Added %s", object.getIdentifier())); if (isInterestedIn(object)) { IIdentifier id = object.getIdentifier(); IChunk chunk = _encoder.encode(object, _memory); if (chunk != null) { _onsetTime.put(id, chunk.getModel().getAge()); add(id, chunk); if (_memory.hasListeners()) _memory.dispatch(new ActivePerceptEvent(_memory, ActivePerceptEvent.Type.NEW, id, chunk)); } } } final public void afferentObjectRemoved(IAfferentObject object) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("Removed %s", object.getIdentifier())); if (isInterestedIn(object)) { IIdentifier identifier = object.getIdentifier(); _onsetTime.remove(identifier); IChunk oldChunk = remove(identifier, true); if (oldChunk == null || oldChunk.hasBeenDisposed()) return; /* * currently in a buffer */ boolean removedFired = false; try { if (BufferUtilities.getContainingBuffers(oldChunk, true).size() != 0) if (_memory.hasListeners()) { _memory.dispatch(new ActivePerceptEvent(_memory, ActivePerceptEvent.Type.REMOVED, identifier, oldChunk)); removedFired = true; } } catch (NullPointerException e) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "priorPercept %s has already been disposed of, ignoring.", identifier)); removedFired = true; } catch (IllegalChunkStateException e) { if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format( "priorPercept %s has already been disposed of, ignoring.", identifier)); removedFired = true; } /* * is it part of a search result? */ FastList<PerceptualSearchResult> results = FastList.newInstance(); try { _memory.getRecentSearchResults(results); for (PerceptualSearchResult result : results) if (result.getPerceptIdentifier().equals(identifier)) { result.setErrorCode(_removedErrorChunk); if (!removedFired && _memory.hasListeners()) _memory.dispatch(new ActivePerceptEvent(_memory, ActivePerceptEvent.Type.REMOVED, identifier, oldChunk)); break; } } finally { FastList.recycle(results); } } else if (LOGGER.isDebugEnabled()) LOGGER.debug(String.format("%s doesn't care about %s, ignoring removal", _encoder, object.getIdentifier())); } final public void afferentObjectUpdated(IAfferentObject object, IObjectDelta delta) { if (isInterestedIn(object)) { IIdentifier id = object.getIdentifier(); IChunk oldChunk = get(id, false); /* * could be null if it was attended and the percept was encoded (cache * emptied), so we need to reencode it */ if (oldChunk == null || oldChunk.isEncoded() || _declarativeModule.willEncode(oldChunk) || oldChunk.hasBeenDisposed()) { oldChunk = _encoder.encode(object, _memory); if (oldChunk != null) { _onsetTime.put(id, oldChunk.getModel().getAge()); add(id, oldChunk); } } else { /* * we need to lock old chunk it we are going to update it */ boolean isDirty = _encoder.isDirty(object, oldChunk, _memory); if (isDirty) { IChunk updated = null; try { updated = _encoder.update(object, oldChunk, _memory); } catch (Exception e) { if (LOGGER.isDebugEnabled()) LOGGER.debug("Failed to update " + oldChunk + " reencoding instead", e); /* * it's not uncommon for this to fail because the chunk has been * encoded.. we could use the chunk lock but that is for fine-graned * locking.. instead, we'll just create a new chunk */ updated = _encoder.encode(object, _memory); } if (updated != oldChunk) { /* * the chunk has changed entirely. */ /* * since we are operating in a separate thread (CR), it is possible * that this condition will be true and then false by the time the * processing is actually completed. so, it's just an early test.. */ if (!oldChunk.isEncoded() && BufferUtilities.getContainingBuffers(oldChunk, true).size() != 0) if (_memory.hasListeners()) _memory.dispatch(new ActivePerceptEvent(_memory, id, updated, oldChunk)); _onsetTime.put(id, updated.getModel().getAge()); // remove from cache, but keep metadata around remove(id, false); add(id, updated); } else { // still record thec ahnge, but we've got to update the meta data // ourselves // since add isn't called double age = oldChunk.getModel().getAge(); _onsetTime.put(id, age); oldChunk.setMetaData( IPerceptualEncoder.COMMONREALITY_ONSET_TIME_KEY, age); if (BufferUtilities.getContainingBuffers(oldChunk, true).size() != 0) if (_memory.hasListeners()) _memory.dispatch(new ActivePerceptEvent(_memory, ActivePerceptEvent.Type.UPDATED, id, oldChunk)); } } } } } final public boolean isInterestedIn(IAfferentObject object) { return _cache.containsKey(object.getIdentifier()) || _encoder.isInterestedIn(object); } final protected void add(IIdentifier identifier, IChunk chunk) { chunk.setMetaData(IPerceptualEncoder.COMMONREALITY_ONSET_TIME_KEY, _onsetTime.get(identifier)); chunk.setMetaData(IPerceptualEncoder.COMMONREALITY_IDENTIFIER_META_KEY, identifier); chunk.addListener(_chunkListener, ExecutorServices.INLINE_EXECUTOR); _cache.put(identifier, chunk); } /** * if clearAndDetach is true, the identifier meta tag and listener are also * removed * * @param identifier * @param clearAndDetach * @return */ final protected IChunk remove(IIdentifier identifier, boolean clearAndDetach) { IChunk chunk = _cache.remove(identifier); if (chunk != null && !chunk.hasBeenDisposed()) try { if (clearAndDetach) { chunk.removeListener(_chunkListener); chunk.setMetaData( IPerceptualEncoder.COMMONREALITY_IDENTIFIER_META_KEY, null); chunk.setMetaData(IPerceptualEncoder.COMMONREALITY_ONSET_TIME_KEY, null); } if (!chunk.isEncoded() && BufferUtilities.getContainingBuffers(chunk, true).size() == 0) { /* * so there is a gap here. A model finds the chunk to * 'encode'(preencoded, actually), and then adds it to its buffer for * some theoretical amount time has passed. If it was removed in this * gap, we could dispose it just before adding to the buffer. The * buffer will catch this, but wouldn't it just be better to defer the * disposal? the declarative module does this already, but only * delaying up until the end of the cycle. */ // instead of doing this, we will delay it with a timed event.. // _memory.getModule().getModel().getDeclarativeModule().dispose(chunk); double oneSecond = _memory.getModule().getModel().getAge() + 1; RunnableTimedEvent rte = new RunnableTimedEvent( oneSecond, () -> { // double check. if (!chunk.isEncoded() && BufferUtilities.getContainingBuffers(chunk, true).size() == 0) _memory.getModule().getModel().getDeclarativeModule() .dispose(chunk); }); _memory.getModule().getModel().getTimedEventQueue().enqueue(rte); } } finally { } return chunk; } /** * fetch cached encoding * * @param identifier * @return */ final public IChunk get(IIdentifier identifier, boolean createIfAbsent) { IChunk chunk = _cache.get(identifier); /* * the chunk may have been scheduled for encoding or disposale before the * listener has been notified.. */ if (chunk != null) if (chunk.isEncoded() || chunk.hasBeenDisposed() || _declarativeModule.willEncode(chunk)) chunk = null; if (chunk == null && createIfAbsent) { IAgent agent = ACTRRuntime.getRuntime().getConnector() .getAgent(_memory.getModule().getModel()); if (agent == null) return null; IAfferentObject afferentObject = agent.getAfferentObjectManager().get( identifier); if (afferentObject == null) return null; if (_encoder.isInterestedIn(afferentObject)) { chunk = _encoder.encode(afferentObject, _memory); if (chunk != null) add(identifier, chunk); } } return chunk; } final protected IIdentifier getIdentifier(IChunk chunk) { return (IIdentifier) chunk .getMetaData(IPerceptualEncoder.COMMONREALITY_IDENTIFIER_META_KEY); } // final protected void checkContents(Collection<IChunk> sourceChunks, // boolean added) // { // for (IChunk chunk : sourceChunks) // { // IIdentifier identifier = getIdentifier(chunk); // if (identifier == null) continue; // // /* // * do we have this chunk cached? // */ // IChunk cached = get(identifier, false); // if (cached == null) continue; // // if (added) // { // AtomicInteger latch = _activeChunks.putIfAbsent(chunk, // new AtomicInteger(1)); // if (latch != null) latch.incrementAndGet(); // } // else // { // AtomicInteger latch = _activeChunks.get(chunk); // if (latch != null && latch.decrementAndGet() <= 0) // _activeChunks.remove(chunk); // } // // } // } }