Package marauroa.server.game.rp

Source Code of marauroa.server.game.rp.MarauroaRPZone

/* $Id: MarauroaRPZone.java,v 1.39 2010/12/20 22:51:47 nhnb Exp $ */
/***************************************************************************
*                      (C) Copyright 2003 - Marauroa                      *
***************************************************************************
***************************************************************************
*                                                                         *
*   This program is free software; you can redistribute it and/or modify  *
*   it under the terms of the GNU General Public License as published by  *
*   the Free Software Foundation; either version 2 of the License, or     *
*   (at your option) any later version.                                   *
*                                                                         *
***************************************************************************/
package marauroa.server.game.rp;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import marauroa.common.Log4J;
import marauroa.common.game.IRPZone;
import marauroa.common.game.Perception;
import marauroa.common.game.RPObject;
import marauroa.common.game.RPObjectInvalidException;
import marauroa.server.db.command.DBCommandQueue;
import marauroa.server.game.db.DAORegister;
import marauroa.server.game.db.RPZoneDAO;
import marauroa.server.game.dbcommand.StoreZoneCommand;

/**
* Default implementation of <code>IRPZone</code>. This class implements the
* Delta^2 algorithm to save bandwidth.
* <p>
* The idea behind the DPA is to avoid sending ALL the objects to a client each
* time, but only those that have been modified. Imagine that we have 1000
* objects, and only Object 1 and Object 505 are active objects that are
* modified each turn.
*
* <pre>
*   The Traditional method:
*
*   - Get objects that our player should see ( 1000 objects )
*   - Send them to player ( 1000 objects )
*   - Next turn
*   - Get objects that our player should see ( 1000 objects )
*   - Send them to player
*   - Next turn
*   ...
* </pre>
*
* I hope you see the problem... we are sending objects that haven't changed
* each turn.
*
* <pre>
*   The delta perception algorithm:
*
*   - Get objects that our player should see ( 1000 objects )
*   - Reduce the list to the modified ones ( 1000 objects )
*   - Store also the objects that are not longer visible ( 0 objects )
*   - Send them to player ( 1000 objects )
*   - Next turn
*   - Get objects that our player should see ( 1000 objects )
*   - Reduce the list to the modified ones ( 2 objects )
*   - Store also the objects that are not longer visible ( 0 objects )
*   - Send them to player ( 2 objects )
*   - Next turn
*   ...
* </pre>
*
* The next step of the delta perception algorithm is pretty clear: delta2<br>
* The idea is to send only what changes of the objects that changed. This way
* we save even more bandwidth, making perceptions around 20% of the original
* delta perception size.
* <p>
* The delta2 algorithm is based on four containers:
* <ul>
* <li>List of added objects
* <li>List of modified added attributes of objects
* <li>List of modified deleted attributes of objects
* <li>List of deleted objects
* </ul>
* To make perceptions work, it is important to call the modify method in
* RPZone, so this way objects modified are stored in the modified list.
*
* @author miguel
*/
public class MarauroaRPZone implements IRPZone {

  /** the logger instance. */
  private static final marauroa.common.Logger logger = Log4J.getLogger(MarauroaRPZone.class);

  /** Name of the zone */
  protected ID zoneid;

  /** Objects contained by the zone indexed by its id. */
  protected Map<RPObject.ID, RPObject> objects;

  /**
   * Objects that has been modified on zone since last turn. This information
   * is useful for Delta^2 algorithm.
   */
  private Set<RPObject> modified;

  /** This is the perception for the current turn. */
  private Perception perception;

  /** This is a cache for the perception for this turn. */
  private Perception prebuildDeltaPerception = null;

  /** This is a sync perception cache */
  private Perception prebuildSyncPerception = null;

  /** This variable stores the last assigned id, that is unique per zone. */
  private int lastNonPermanentIdAssigned = 0;

  private static Random rand = new Random();

  /**
   * Creates a new MarauroaRPZone
   *
   * @param zoneid name of zone
   */
  public MarauroaRPZone(String zoneid) {
    this.zoneid = new ID(zoneid);
    rand.setSeed(new Date().getTime());

    objects = new LinkedHashMap<RPObject.ID, RPObject>();
    modified = new HashSet<RPObject>();

    perception = new Perception(Perception.DELTA, this.zoneid);
  }

  /** Returns the zoneid */
  public ID getID() {
    return zoneid;
  }

  public void onFinish() throws Exception {
    storeToDatabase();
  }

  /**
   * Store objects that has been tagged as storable to database asynchronously.
   * Note: This methods returns before the saving is completed.
   */
  public void storeToDatabase() {
    List<RPObject> list = new LinkedList<RPObject>();
    for (RPObject object : objects.values()) {
      list.add((RPObject) object.clone());
    }
    DBCommandQueue.get().enqueue(new StoreZoneCommand(this, list));
  }


  /**
   * Load objects in database for this zone that were stored
   * and waits for the database operation to complete.
   */
  public void onInit() throws Exception {
    DAORegister.get().get(RPZoneDAO.class).loadRPZone(this);
  }

  /**
   * This method adds an object to this zone.
   *
   * @param object
   *            object to add.
   * @throws RPObjectInvalidException
   *             if it lacks of mandatory attributes.
   */
  public void add(RPObject object) throws RPObjectInvalidException {
    try {
      RPObject.ID id = object.getID();

      object.resetAddedAndDeleted();
      objects.put(id, object);

      if (!object.isHidden()) {
        perception.added(object);
      }
    } catch (Exception e) {
      throw new RPObjectInvalidException(e);
    }
  }

  /**
   * This method notify zone that the object has been modified. You should
   * call it only once per turn, even if inside the turn you modify it several
   * times.
   *
   * @param object
   *            object to modify.
   * @throws RPObjectInvalidException
   *             if it lacks of mandatory attributes.
   */
  public void modify(RPObject object) throws RPObjectInvalidException {
    try {
      modified.add(object);
    } catch (Exception e) {
      throw new RPObjectInvalidException(e.getMessage());
    }
  }

  /**
   * Removes the object from zone.
   *
   * @param id
   *            identified of the removed object
   * @return the removed object
   */
  public RPObject remove(RPObject.ID id) {
    RPObject object = objects.remove(id);

    if (object != null) {
      /* We create an empty copy of the object */
      RPObject deleted = new RPObject();
      deleted.setID(object.getID());
      deleted.setRPClass(object.getRPClass());

      perception.removed(deleted);
    }

    return object;
  }

  /**
   * Hide an object from the perceptions, but it doesn't remove it from world.
   * Any further calls to modify will be ignored.
   *
   * @param object
   *            the object to hide.
   */
  public void hide(RPObject object) {
    object.hide();

    /* We create an empty copy of the object */
    RPObject deleted = new RPObject();
    deleted.setID(object.getID());
    deleted.setRPClass(object.getRPClass());

    perception.removed(deleted);
  }

  /**
   * Makes a hidden object to be visible again. It will appear on the
   * perception as an added object.
   *
   * @param object
   *            the object to unhide.
   */
  public void unhide(RPObject object) {
    object.unhide();

    object.resetAddedAndDeleted();
    perception.added(object);
  }

  /**
   * Returns the object which id is id.
   *
   * @param id
   *            identified of the removed object
   * @return the object
   */
  public RPObject get(RPObject.ID id) {
    return objects.get(id);
  }

  /**
   * Returns true if the zone has that object.
   *
   * @param id
   *            identified of the removed object
   * @return true if object exists.
   */
  public boolean has(RPObject.ID id) {
    return objects.containsKey(id);
  }

  /**
   * This method assigns a valid id to the object.
   *
   * @param object
   *            the object that is going to obtain a new id
   */
  public void assignRPObjectID(RPObject object) {
    RPObject.ID id = new RPObject.ID(++lastNonPermanentIdAssigned, zoneid);
    while (has(id)) {
      id = new RPObject.ID(++lastNonPermanentIdAssigned, zoneid);
    }

    object.put("id", id.getObjectID());
    object.put("zoneid", zoneid.getID());
  }

  /**
   * Iterates over all the objects in the zone.
   *
   * @return an iterator
   */
  public Iterator<RPObject> iterator() {
    return objects.values().iterator();
  }

  /**
   * Returns the perception of given type for that object.
   *
   * @param player
   *            object whose perception we are going to build
   * @param type
   *            the type of perception:
   *            <ul>
   *            <li>SYNC
   *            <li>DELTA
   *            </ul>
   */
  public Perception getPerception(RPObject player, byte type) {
    if (type == Perception.DELTA) {
      if (prebuildDeltaPerception == null) {
        prebuildDeltaPerception = perception;

        for (RPObject modified_obj : modified) {
          if (modified_obj.isHidden()) {
            continue;
          }
          try {
            if (logger.isDebugEnabled()) {
              if(!has(modified_obj.getID())) {
                logger.debug("Modifying a non existing object: "+modified_obj);
              }
            }
           
            prebuildDeltaPerception.modified(modified_obj);
          } catch (Exception e) {
            logger.error("cannot add object to modified list (object is: ["
                    + modified_obj + "])", e);
          }
        }
      }

      return prebuildDeltaPerception;
    } else /* type==Perception.SYNC */{
      if (prebuildSyncPerception == null) {
        prebuildSyncPerception = new Perception(Perception.SYNC, getID());
        prebuildSyncPerception.addedList = new ArrayList<RPObject>(objects.size());
        for (RPObject obj : objects.values()) {
          if (!obj.isHidden()) {
            prebuildSyncPerception.addedList.add(obj);
          }
        }
      }

      return prebuildSyncPerception;
    }
  }

  /**
   * This methods resets the delta^2 information of objects.
   */
  protected void reset() {
    /*
     * We only reset the objects that have been modified because the rest should have been modified.
     */
    for (RPObject object : modified) {
      object.resetAddedAndDeleted();
    }
  }

  /**
   * This method returns the amount of objects in the zone.
   *
   * @return amount of objects.
   */
  public long size() {
    return objects.size();
  }

  /**
   * This method prints the whole zone. Handle it with care.
   *
   * @param out
   *            the PrintStream where zone is printed.
   */
  public void print(PrintStream out) {
    for (RPObject object : objects.values()) {
      out.println(object);
    }
  }

  /**
   * This method moves zone from this turn to the next turn. It is called by
   * RPWorld.
   */
  public void nextTurn() {
    reset();

    prebuildSyncPerception = null;
    prebuildDeltaPerception = null;

    modified.clear();
    perception.clear();
  }
}
TOP

Related Classes of marauroa.server.game.rp.MarauroaRPZone

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.