public void initialize() {
   lock = LockFactory.makeLock("BaseBehaviorLock");
   long oid = obj.getOid();
   SubjectFilter filter = new SubjectFilter(oid);
   filter.addType(Behavior.MSG_TYPE_COMMAND);
   filter.addType(WorldManagerClient.MSG_TYPE_MOB_PATH_CORRECTION);
   pathState = new PathState(oid, pathObjectTypeName, true);
   commandSub = Engine.getAgent().createSubscription(filter, this);
 }
/**
 * Manages hooks for processing messages coming in from a subscription. Hooks are associated with
 * message types, which is a property in a message.
 *
 * <p>The EnginePlugin's onMessage() callback processes an incoming message by calling into its
 * local HookManager and finding all matching hooks for the incoming message's message type for all
 * the hooks. For each matching hook, it calls the hook's processMessage() method.
 *
 * @see EnginePlugin#handleMessageImpl
 */
public class HookManager {
  /**
   * Normally created by the EnginePlugin class.
   *
   * <p>In order to avoid copying the list when getHooks is called, we copy it when we're adding to
   * it. We do lock around addHook, so that different callers to addHook are synchronized against
   * each other, but we don't lock in getHooks.
   */
  public HookManager() {}

  /**
   * Adds a hook to the HookManager. You can associate more than one hook with a given message type
   * which will be returned in order by getHooks().
   *
   * @param msgType the message type to match
   * @param hook the hook to be called for matching messages
   * @see EnginePlugin#handleMessageImpl
   */
  public void addHook(MessageType msgType, Hook hook) {
    lock.lock();
    try {
      List<Hook> hookList = hooks.get(msgType);
      if (hookList == null) {
        hookList = new LinkedList<Hook>();
        hookList.add(hook);
        hooks.put(msgType, hookList);
      } else {
        hookList = new LinkedList<Hook>(hookList);
        hookList.add(hook);
        hooks.put(msgType, hookList);
      }
    } finally {
      lock.unlock();
    }
  }

  /**
   * Returns this list of all hooks matching the message type. They are returned in the order they
   * were added. The returned list should be treated as read-only.
   *
   * @param msgType the message type to match
   * @return A list of all hooks matching the passed in message type.
   */
  public List<Hook> getHooks(MessageType msgType) {
    List<Hook> hookList = hooks.get(msgType);
    if (hookList == null) return nullList;
    else return hookList;
  }

  private LinkedList<Hook> nullList = new LinkedList<Hook>();
  private Lock lock = LockFactory.makeLock("HookManager");
  private Map<MessageType, List<Hook>> hooks = new HashMap<MessageType, List<Hook>>();
}
/**
 * This class is similar to the ObjectTracker, but is used in cases where there is no local/remote
 * distinction between objects. Instead, the class maintains it's own set of InterpolatedWorldNodes,
 * and an update thread to keep them current, and a mapping from perceiver objects to perceived
 * objects. When one of those objects moves, the mapping is updated, and if they have moved in or
 * out of range, the class either sends a NotifyReactionRadius message, or
 */
public class ProximityTracker implements MessageDispatch {

  public ProximityTracker(Namespace namespace, long instanceOid) {
    initialize(namespace, instanceOid);
  }

  public ProximityTracker(
      Namespace namespace,
      long instanceOid,
      float hystericalMargin,
      ObjectTracker.NotifyReactionRadiusCallback notifyCallback,
      ObjectTracker.RemoteObjectFilter remoteObjectFilter) {
    this.hystericalMargin = hystericalMargin;
    this.notifyCallback = notifyCallback;
    this.remoteObjectFilter = remoteObjectFilter;
    initialize(namespace, instanceOid);
  }

  private void initialize(Namespace namespace, long instanceOid) {
    this.namespace = namespace;
    this.instanceOid = instanceOid;
    updater = new Updater();
    Thread updaterThread = new Thread(updater);
    updaterThread.start();
  }

  public long getInstanceOid() {
    return instanceOid;
  }

  public void addTrackedPerceiver(
      Long perceiverOid, InterpolatedWorldNode wnode, Integer reactionRadius) {
    lock.lock();
    try {
      if (perceiverDataMap.containsKey(perceiverOid)) {
        // Don't add the object more than once.
        Log.error(
            "ProximityTracker.addTrackedPerceiver: perceiverOid "
                + perceiverOid
                + " is already in the set of local objects, for ProximityTracker instance "
                + this);
        return;
      }
      PerceiverData perceiverData = new PerceiverData(perceiverOid, reactionRadius, wnode);
      perceiverDataMap.put(perceiverOid, perceiverData);
    } finally {
      lock.unlock();
    }
    if (Log.loggingDebug)
      Log.debug(
          "ProximityTracker.addTrackedPerceiver: perceiverOid="
              + perceiverOid
              + " reactionRadius="
              + reactionRadius
              + " instanceOid="
              + instanceOid);
  }

  public boolean hasTrackedPerceiver(Long oid) {
    lock.lock();
    try {
      return perceiverDataMap.containsKey(oid);
    } finally {
      lock.unlock();
    }
  }

  public void removeTrackedPerceiver(Long perceiverOid) {
    lock.lock();
    try {
      // Iterate over perceived objects, removing our
      // perceiverOid from their oid sets.
      PerceiverData perceiverData = perceiverDataMap.get(perceiverOid);
      if (perceiverData != null) {
        if (Log.loggingDebug)
          Log.debug(
              "ProximityTracker.removeTrackedPerceiver: perceiverOid "
                  + perceiverOid
                  + ", inRangeOids count "
                  + perceiverData.inRangeOids.size());
        // Iterate over perceived objects, removing our
        // perceiverOid from their oid sets.
        for (Long perceivedOid : perceiverData.perceivedOids) {
          PerceiverData perceivedData = perceiverDataMap.get(perceivedOid);
          if (perceivedData != null) {
            perceivedData.perceivedOids.remove(perceiverOid);
            if (perceivedData.inRangeOids.contains(perceiverOid)) {
              perceivedData.inRangeOids.remove(perceiverOid);
              performNotification(perceiverOid, perceivedOid, false, true);
            }
          }
        }
        perceiverData.perceivedOids.clear();
        perceiverData.inRangeOids.clear();
        perceiverDataMap.remove(perceiverOid);
      } else
        Log.warn(
            "ProximityTracker.removeTrackedPerceiver: For oid="
                + perceiverOid
                + ", didn't find PerceiverData");
    } finally {
      lock.unlock();
    }
    if (Log.loggingDebug)
      Log.debug(
          "ProximityTracker.removeTrackedPerceiver: oid="
              + perceiverOid
              + " instanceOid="
              + instanceOid);
  }

  public List<Long> getOidsInRadius(long perceiverOid) {
    lock.lock();
    try {
      PerceiverData perceiverData = perceiverDataMap.get(perceiverOid);
      if (perceiverData == null) {
        Log.error(
            "ProximityTracker.getOidsInRadius: perceptionData for oid "
                + perceiverOid
                + " is null");
        return new LinkedList<Long>();
      } else return new LinkedList<Long>(perceiverData.inRangeOids);
    } finally {
      lock.unlock();
    }
  }

  public void dispatchMessage(Message message, int flags, MessageCallback callback) {
    Engine.defaultDispatchMessage(message, flags, callback);
  }

  protected boolean maybeAddPerceivedObject(PerceptionMessage.ObjectNote objectNote) {
    ObjectType objType = (ObjectType) objectNote.getObjectType();
    long perceivedOid = objectNote.getSubject();
    long perceiverOid = objectNote.getTarget();
    if (perceivedOid == perceiverOid) return true;
    boolean callbackNixedIt = false;
    if (remoteObjectFilter != null)
      callbackNixedIt = !remoteObjectFilter.objectShouldBeTracked(perceivedOid, objectNote);
    if (callbackNixedIt || !(objType.isMob())) {
      //             if (Log.loggingDebug)
      //                 Log.debug("ProximityTracker.maybeAddPerceivedObject: ignoring oid=" +
      // perceivedOid
      //                     + " objType=" + objType
      //                     + " detected by " + perceiverOid
      //                     + ", instanceOid=" + instanceOid);
      return false;
    }

    if (Log.loggingDebug)
      Log.debug(
          "ProximityTracker.maybeAddPerceivedObject: oid="
              + perceivedOid
              + " objType="
              + objType
              + " detected by "
              + perceiverOid
              + ", instanceOid="
              + instanceOid);
    lock.lock();
    try {
      PerceiverData perceiverData = perceiverDataMap.get(perceiverOid);
      if (perceiverData == null) {
        Log.error(
            "ProximityTracker.maybeAddPerceivedObject: got perception msg with perceived obj oid="
                + perceivedOid
                + " for unknown perceiver="
                + perceiverOid);
        return false;
      }
      perceiverData.perceivedOids.add(perceivedOid);
      PerceiverData perceivedData = perceiverDataMap.get(perceivedOid);
      if (perceivedData != null) testProximity(perceiverData, perceivedData, true, false);
    } finally {
      lock.unlock();
    }
    return true;
  }

  /**
   * Test if the perceived object has come in or out of range of the perceiver object; if so, we
   * change the inRangeOids set for the perceiver, and notify the perceiver.
   */
  protected void testProximity(
      PerceiverData perceiverData,
      PerceiverData perceivedData,
      boolean interpolatePerceiver,
      boolean interpolatePerceived) {
    Point perceiverLoc =
        interpolatePerceiver ? perceiverData.wnode.getLoc() : perceiverData.lastLoc;
    Point perceivedLoc =
        interpolatePerceived ? perceivedData.wnode.getLoc() : perceivedData.lastLoc;
    float distance = Point.distanceTo(perceiverLoc, perceivedLoc);
    float reactionRadius = perceiverData.reactionRadius;
    long perceiverInstance = perceiverData.wnode.getInstanceOid();
    long perceivedInstance = perceivedData.wnode.getInstanceOid();
    boolean sameInstance = perceiverInstance == perceivedInstance;
    boolean inRadius = sameInstance && (distance < reactionRadius);
    boolean wasInRadius = perceiverData.inRangeOids.contains(perceivedData.perceiverOid);
    //         if (Log.loggingDebug)
    //             Log.debug("ProximityTracker.testProximity: perceiver " +
    // perceiverData.perceiverOid + ", perceiverLoc = " + perceiverLoc +
    //                 ", perceived " + perceivedData.perceiverOid + ", perceivedLoc = " +
    // perceivedLoc +
    //                 ", distance " + distance + ", reactionRadius " + reactionRadius + ",
    // perceiverInstance " + perceiverInstance +
    //                 ", perceivedInstance " + perceivedInstance + ", inRadius " + inRadius + ",
    // wasInRadius " + wasInRadius);
    if (inRadius == wasInRadius) return;
    if (sameInstance && hystericalMargin != 0f) {
      if (wasInRadius) inRadius = distance < (reactionRadius + hystericalMargin);
      else inRadius = distance < (reactionRadius - hystericalMargin);
      // If they are the same after hysteresis was applied, skip.
      if (inRadius == wasInRadius) return;
    }
    if (inRadius) {
      perceiverData.inRangeOids.add(perceivedData.perceiverOid);
      perceivedData.inRangeOids.add(perceiverData.perceiverOid);
    } else {
      perceiverData.inRangeOids.remove(perceivedData.perceiverOid);
      perceivedData.inRangeOids.remove(perceiverData.perceiverOid);
    }
    performNotification(
        perceiverData.perceiverOid, perceivedData.perceiverOid, inRadius, wasInRadius);
  }

  protected void performNotification(
      long perceiverOid, long perceivedOid, boolean inRadius, boolean wasInRadius) {
    if (Log.loggingDebug)
      Log.debug(
          "ProximityTracker.performNotification: perceiverOid "
              + perceiverOid
              + ", perceivedOid "
              + perceivedOid
              + ", inRadius "
              + inRadius
              + ", wasInRadius "
              + wasInRadius);
    if (notifyCallback != null) {
      notifyCallback.notifyReactionRadius(perceivedOid, perceiverOid, inRadius, wasInRadius);
      notifyCallback.notifyReactionRadius(perceiverOid, perceivedOid, inRadius, wasInRadius);
    } else {
      ObjectTracker.NotifyReactionRadiusMessage nmsg =
          new ObjectTracker.NotifyReactionRadiusMessage(
              perceivedOid, perceiverOid, inRadius, wasInRadius);
      Engine.getAgent().sendBroadcast(nmsg);
      nmsg =
          new ObjectTracker.NotifyReactionRadiusMessage(
              perceiverOid, perceivedOid, inRadius, wasInRadius);
      Engine.getAgent().sendBroadcast(nmsg);
    }
  }

  protected void updateEntity(PerceiverData perceiverData) {
    long perceiverOid = perceiverData.perceiverOid;
    lock.lock();
    try {
      for (long perceivedOid : perceiverData.perceivedOids) {
        if (perceiverOid == perceivedOid) continue;
        PerceiverData perceivedData = perceiverDataMap.get(perceivedOid);
        if (perceivedData != null) testProximity(perceiverData, perceivedData, false, true);
      }
    } finally {
      lock.unlock();
    }
  }

  public void handlePerception(PerceptionMessage perceptionMessage) {
    long targetOid = perceptionMessage.getTarget();
    List<PerceptionMessage.ObjectNote> gain = perceptionMessage.getGainObjects();
    List<PerceptionMessage.ObjectNote> lost = perceptionMessage.getLostObjects();

    if (Log.loggingDebug)
      Log.debug(
          "ProximityTracker.handlePerception: targetOid + "
              + targetOid
              + ", instanceOid="
              + instanceOid
              + " "
              + ((gain == null) ? 0 : gain.size())
              + " gain and "
              + ((lost == null) ? 0 : lost.size())
              + " lost");

    if (gain != null) for (PerceptionMessage.ObjectNote note : gain) maybeAddPerceivedObject(note);

    if (lost != null)
      for (PerceptionMessage.ObjectNote note : lost)
        maybeRemovePerceivedObject(note.getSubject(), note, targetOid);
  }

  public void handleUpdateWorldNode(long oid, WorldManagerClient.UpdateWorldNodeMessage wnodeMsg) {
    PerceiverData perceiverData = perceiverDataMap.get(oid);
    if (perceiverData == null) {
      if (Log.loggingDebug)
        Log.debug(
            "ProximityTracker.handleMessage: ignoring updateWNMsg for oid "
                + oid
                + " because PerceptionData for oid not found");
      return;
    }
    BasicWorldNode bwnode = wnodeMsg.getWorldNode();
    if (Log.loggingDebug)
      Log.debug(
          "ProximityTracker.handleMessage: UpdateWnode for "
              + oid
              + ", loc "
              + bwnode.getLoc()
              + ", dir "
              + bwnode.getDir());
    if (perceiverData.wnode != null) {
      perceiverData.previousLoc = perceiverData.lastLoc;
      perceiverData.wnode.setDirLocOrient(bwnode);
      perceiverData.wnode.setInstanceOid(bwnode.getInstanceOid());
      perceiverData.lastLoc = perceiverData.wnode.getLoc();
    } else
      Log.error(
          "ProximityTracker.handleMessage: In UpdateWorldNodeMessage for oid "
              + oid
              + ", perceiverData.wnode is null!");
    updateEntity(perceiverData);
  }

  protected void maybeRemovePerceivedObject(
      long perceivedOid, PerceptionMessage.ObjectNote objectNote, long perceiverOid) {
    if (remoteObjectFilter != null
        && remoteObjectFilter.objectShouldBeTracked(perceivedOid, objectNote)) return;
    else removePerceivedObject(perceiverOid, perceivedOid);
  }

  protected void removePerceivedObject(long perceiverOid, long perceivedOid) {
    lock.lock();
    try {
      PerceiverData perceiverData = perceiverDataMap.get(perceiverOid);
      if (perceiverData == null) {
        if (Log.loggingDebug)
          Log.debug(
              "ProximityTracker.removePerceivedObject: No perceiverData for oid " + perceiverOid);
        return;
      }
      perceiverData.perceivedOids.remove(perceivedOid);
      if (perceiverData.inRangeOids.contains(perceivedOid)) {
        performNotification(perceiverOid, perceivedOid, true, false);
        perceiverData.inRangeOids.remove(perceivedOid);
      }
    } finally {
      lock.unlock();
    }
  }

  class Updater implements Runnable {
    public void run() {
      while (running) {
        try {
          update();
        } catch (MVRuntimeException e) {
          Log.exception("ProximityTracker.Updater.run caught MVRuntimeException", e);
        } catch (Exception e) {
          Log.exception("ProximityTracker.Updater.run caught exception", e);
        }

        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          Log.warn("Updater: " + e);
          e.printStackTrace();
        }
      }
    }

    protected void update() {
      Log.debug("Updater.update: in update");

      List<Long> perceiverOids = null;
      lock.lock();
      try {
        perceiverOids = new ArrayList<Long>(perceiverDataMap.keySet());
      } finally {
        lock.unlock();
      }
      // We loop over the copied perceiverOids causing
      // interpolation to happen, and capturing the location in
      // the PerceiverData, so we can later do comparisons
      // cheaply.  Note that underlying map can change while
      // we're doing so, so we don't raise errors if it happens.
      for (long perceiverOid : perceiverOids) {
        PerceiverData perceiverData = perceiverDataMap.get(perceiverOid);
        if (perceiverData != null) {
          perceiverData.previousLoc = perceiverData.lastLoc;
          //                    long lastInterp = perceiverData.wnode.getLastInterp();
          perceiverData.lastLoc = perceiverData.wnode.getLoc();
          //                     if (Log.loggingDebug)
          //                         Log.debug("Updater.update: perceiverOid " + perceiverOid + ",
          // previousLoc " + perceiverData.previousLoc +
          //                             ", lastLoc " + perceiverData.lastLoc + ", time since interp
          // " + (System.currentTimeMillis() - lastInterp));
        }
      }
      // Now actually do the double loop to check if inRange has
      // changed
      for (long perceiverOid : perceiverOids) {
        PerceiverData perceiverData = perceiverDataMap.get(perceiverOid);
        if (perceiverData == null) continue;
        // If the perceiver hasn't moved much, no need to
        // iterate over it's perceived entities
        if (perceiverData.previousLoc != null
            && Point.distanceToSquared(perceiverData.previousLoc, perceiverData.lastLoc) < 100f)
          continue;
        ArrayList<Long> perceivedOids = new ArrayList<Long>(perceiverData.perceivedOids);
        for (long perceivedOid : perceivedOids) {
          PerceiverData perceivedData = perceiverDataMap.get(perceivedOid);
          if (perceivedData == null) continue;
          // Invoke the testProximity method but tell it not
          // to interpolate, but instead get its location
          // from the PerceptionData.lastLoc members
          testProximity(perceiverData, perceivedData, false, false);
        }
      }
    }
  }

  public void setRunning(boolean running) {
    this.running = running;
  }

  protected Namespace namespace;
  protected long instanceOid;

  protected float hystericalMargin = 0f;

  protected ObjectTracker.NotifyReactionRadiusCallback notifyCallback = null;

  protected ObjectTracker.RemoteObjectFilter remoteObjectFilter = null;

  protected Updater updater = null;

  protected Thread updaterThread = null;

  protected boolean running = true;

  /**
   * This maps a perceiver oid into an object containing the list of perceived objects, and the list
   * of objects in range.
   */
  protected Map<Long, PerceiverData> perceiverDataMap = new HashMap<Long, PerceiverData>();

  protected class PerceiverData {
    long perceiverOid;
    // The reaction radius to be applied
    Integer reactionRadius;
    // The Entity associated with this PerceiverData
    Entity perceiverEntity;
    // The world node for this perceiver
    InterpolatedWorldNode wnode;
    // The last interpolated location of the entity
    Point lastLoc;
    // The previous interpolated location of the entity, used to
    // detect if the entity has moved
    Point previousLoc;

    // The set of object oids perceived by this perceiver
    Set<Long> perceivedOids = new HashSet<Long>();
    // The set of object oids in range of this object
    Set<Long> inRangeOids = new HashSet<Long>();

    public PerceiverData(long perceiverOid, Integer reactionRadius, InterpolatedWorldNode wnode) {
      this.perceiverOid = perceiverOid;
      this.reactionRadius = reactionRadius;
      this.wnode = wnode;
      this.lastLoc = wnode.getLoc();
    }
  }

  // protected Map<Long, ObjectTracker.NotifyData> reactionRadiusMap = new HashMap<Long,
  // ObjectTracker.NotifyData>();

  protected Lock lock = LockFactory.makeLock("ProximityTrackerLock");
}