/** This method is called when connection to client is closed */
  public void onTimeout(RPObject object) throws RPObjectNotFoundException {
    DebugInterface.get().onTimeout(object);
    scheduler.clearRPActions(object);
    contentsToTransfer.remove(object);

    ruleProcessor.onTimeout(object);
  }
  /**
   * Adds an action for the next turn
   *
   * @param object the object that casted the action
   * @param action the action itself
   * @throws ActionInvalidException
   */
  public void addRPAction(RPObject object, RPAction action) throws ActionInvalidException {
    if (logger.isDebugEnabled()) {
      logger.debug("Added action: " + action);
    }

    scheduler.addRPAction(object, action, ruleProcessor);
  }
  /**
   * This method is called when a player leaves the game
   *
   * @param object player object
   * @return true, to continue, false to prevent logout
   */
  public boolean onExit(RPObject object) throws RPObjectNotFoundException {
    scheduler.clearRPActions(object);
    contentsToTransfer.remove(object);

    if (!DebugInterface.get().onExit(object)) {
      return false;
    }
    return ruleProcessor.onExit(object);
  }
  @Override
  public void run() {
    try {
      long start = System.nanoTime();
      long stop;
      long delay;
      long timeStart = 0;
      long[] timeEnds = new long[12];

      while (keepRunning) {
        stop = System.nanoTime();

        logger.debug("Turn time elapsed: " + ((stop - start) / 1000) + " microsecs");
        delay = turnDuration - ((stop - start) / 1000000);
        if (delay < 0) {
          StringBuilder sb = new StringBuilder();
          for (long timeEnd : timeEnds) {
            sb.append(" " + (timeEnd - timeStart));
          }

          logger.warn("Turn duration overflow by " + (-delay) + " ms: " + sb.toString());
        } else if (delay > turnDuration) {
          logger.error(
              "Delay bigger than Turn duration. [delay: "
                  + delay
                  + "] [turnDuration:"
                  + turnDuration
                  + "]");
          delay = 0;
        }

        // only sleep when the turn delay is > 0
        if (delay > 0) {
          try {
            Thread.sleep(delay);
          } catch (InterruptedException e) {
            // ignore
          }
        }

        start = System.nanoTime();
        timeStart = System.currentTimeMillis();

        playerContainer.getLock().requestWriteLock();

        try {
          timeEnds[0] = System.currentTimeMillis();

          /* Get actions that players send */
          scheduler.nextTurn();
          timeEnds[1] = System.currentTimeMillis();

          /* Execute them all */
          scheduler.visit(ruleProcessor);
          timeEnds[2] = System.currentTimeMillis();

          /* Compute game RP rules to move to the next turn */
          ruleProcessor.endTurn();
          timeEnds[3] = System.currentTimeMillis();

          /* Send content that is waiting to players */
          deliverTransferContent();
          timeEnds[4] = System.currentTimeMillis();

          /* Tell player what happened */
          buildPerceptions();
          timeEnds[5] = System.currentTimeMillis();

          /* save players regularly to the db */
          savePlayersPeriodicly();
          timeEnds[6] = System.currentTimeMillis();

          /* Move zone to the next turn */
          world.nextTurn();
          timeEnds[7] = System.currentTimeMillis();

          turn++;

          ruleProcessor.beginTurn();
          timeEnds[8] = System.currentTimeMillis();
        } finally {
          playerContainer.getLock().releaseLock();
          timeEnds[9] = System.currentTimeMillis();
        }
        try {
          stats.set("Objects now", world.size());
        } catch (ConcurrentModificationException e) {
          // TODO: size is obviously not threadsafe as it asks the underlying zone.objects for its
          // sizes, which are not threadsafe.
        }
        timeEnds[10] = System.currentTimeMillis();
        TransactionPool.get().kickHangingTransactionsOfThisThread();
        timeEnds[11] = System.currentTimeMillis();
      }
    } catch (Throwable e) {
      logger.error("Unhandled exception, server will shut down.", e);
    } finally {
      isfinished = true;
    }
  }