Files
narya/src/java/com/threerings/parlor/server/TableManager.java
T
Walter Korman a97e9db978 Moved remaining client.*Codes classes into their respective data packages
as they are intended to represent codes shared between the client and the
server.  Made correspondingly necessary changes throughout.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1240 542714f4-19e9-0310-aa3c-eee0fc999fb1
2002-04-15 16:28:05 +00:00

383 lines
14 KiB
Java

//
// $Id: TableManager.java,v 1.5 2002/04/15 16:28:02 shaper Exp $
package com.threerings.parlor.server;
import java.util.HashMap;
import java.util.Iterator;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.StringUtil;
import com.threerings.presents.dobj.ObjectAddedEvent;
import com.threerings.presents.dobj.ObjectDeathListener;
import com.threerings.presents.dobj.ObjectDestroyedEvent;
import com.threerings.presents.dobj.ObjectRemovedEvent;
import com.threerings.presents.dobj.OidListListener;
import com.threerings.presents.server.ServiceFailedException;
import com.threerings.crowd.data.BodyObject;
import com.threerings.crowd.data.PlaceObject;
import com.threerings.crowd.server.CrowdServer;
import com.threerings.crowd.server.PlaceManager;
import com.threerings.crowd.server.PlaceRegistry;
import com.threerings.parlor.Log;
import com.threerings.parlor.data.ParlorCodes;
import com.threerings.parlor.data.Table;
import com.threerings.parlor.data.TableConfig;
import com.threerings.parlor.data.TableLobbyObject;
import com.threerings.parlor.game.GameConfig;
import com.threerings.parlor.game.GameManager;
/**
* A table manager can be used by a place manager to take care of the
* management of a table matchmaking service in the place managed by the
* place manager. The place manager need instantiate a table manager and
* implement the {@link TableManagerProvider} interface to provide access
* to the table manager for the associated invocation services.
*/
public class TableManager
implements ParlorCodes, PlaceRegistry.CreationObserver, OidListListener
{
/**
* Creates a table manager that will work in tandem with the specified
* place manager to manage a table matchmaking service in this place.
*/
public TableManager (PlaceManager plmgr)
{
// get a reference to our place object
_plobj = plmgr.getPlaceObject();
// add ourselves as an oidlist listener to this lobby so that we
// can tell if a user leaves the lobby without leaving their table
_plobj.addListener(this);
// make sure it implements table lobby object
_tlobj = (TableLobbyObject)_plobj;
}
/**
* Requests that a new table be created to matchmake the game
* described by the supplied game config instance. The config instance
* provided must implement the {@link TableConfig} interface so that
* the parlor services can determine how to configure the table that
* will be created.
*
* @param creator the body object that will own the new table.
* @param config the configuration of the game to be created.
*
* @return the id of the newly created table.
*
* @exception ServiceFailedException thrown if the table creation was
* not able processed for some reason. The explanation will be
* provided in the message data of the exception.
*/
public int createTable (BodyObject creator, GameConfig config)
throws ServiceFailedException
{
// make sure the game config implements TableConfig
if (!(config instanceof TableConfig)) {
Log.warning("Requested to matchmake a non-table game " +
"using the table services [creator=" + creator +
", lobby=" + _plobj.getOid() +
", config=" + config + "].");
throw new ServiceFailedException(INTERNAL_ERROR);
}
// make sure the creator is an occupant of the lobby in which
// they are requesting to create a table
if (!_plobj.occupants.contains(creator.getOid())) {
Log.warning("Requested to create a table in a lobby not " +
"occupied by the creator [creator=" + creator +
", loboid=" + _plobj.getOid() + "].");
throw new ServiceFailedException(INTERNAL_ERROR);
}
// create a brand spanking new table
Table table = new Table(_plobj.getOid(), config);
// stick the creator into position zero
String error =
table.setOccupant(0, creator.username, creator.getOid());
if (error != null) {
Log.warning("Unable to add creator to position zero of " +
"table!? [table=" + table +
", creator=" + creator + "].");
// bail out now and abort the table creation process
throw new ServiceFailedException(error);
}
// stick the table into the table lobby object
_tlobj.addToTables(table);
// make a mapping from the creator to this table
_boidMap.put(creator.getOid(), table);
// also stick it into our tables tables
_tables.put(table.getTableId(), table);
// finally let the caller know what the new table id is
return table.getTableId();
}
/**
* Requests that the specified user be added to the specified table at
* the specified position. If the user successfully joins the table,
* the function will return normally. If they are not able to join for
* some reason (the slot is already full, etc.), a {@link
* ServiceFailedException} will be thrown with a message code that
* describes the reason for failure. If the user does successfully
* join, they will be added to the table entry in the tables set in
* the place object that is hosting the table.
*
* @param joiner the body object of the user that wishes to join the
* table.
* @param tableId the id of the table to be joined.
* @param position the position at which to join the table.
*
* @exception ServiceFailedException thrown if the joining was not
* able processed for some reason. The explanation will be provided in
* the message data of the exception.
*/
public void joinTable (BodyObject joiner, int tableId, int position)
throws ServiceFailedException
{
// look the table up
Table table = (Table)_tables.get(tableId);
if (table == null) {
throw new ServiceFailedException(NO_SUCH_TABLE);
}
// request that the user be added to the table at that position
String error =
table.setOccupant(position, joiner.username, joiner.getOid());
// if that failed, report the error
if (error != null) {
throw new ServiceFailedException(error);
}
// determine whether or not it's time to start the game
if (table.readyToStart()) {
// create the game manager
GameManager gmgr =
createGameManager(table.config, table.getPlayers());
// and map this table to this game manager so that we can
// update the table with the game in progress once it's
// created
_pendingTables.put(gmgr, table);
// clear out the occupant to table mappings because this game
// is underway
for (int i = 0; i < table.bodyOids.length; i++) {
_boidMap.remove(table.bodyOids[i]);
}
} else {
// make a mapping from this occupant to this table
_boidMap.put(joiner.getOid(), table);
}
// update the table in the lobby
_tlobj.updateTables(table);
}
/**
* Requests that the specified user be removed from the specified
* table. If the user successfully leaves the table, the function will
* return normally. If they are not able to leave for some reason
* (they aren't sitting at the table, etc.), a {@link
* ServiceFailedException} will be thrown with a message code that
* describes the reason for failure.
*
* @param leaver the body object of the user that wishes to leave the
* table.
* @param tableId the id of the table to be left.
*
* @exception ServiceFailedException thrown if the leaving was not
* able processed for some reason. The explanation will be provided in
* the message data of the exception.
*/
public void leaveTable (BodyObject leaver, int tableId)
throws ServiceFailedException
{
// look the table up
Table table = (Table)_tables.get(tableId);
if (table == null) {
throw new ServiceFailedException(NO_SUCH_TABLE);
}
// request that the user be removed from the table
if (!table.clearOccupant(leaver.username)) {
throw new ServiceFailedException(NOT_AT_TABLE);
}
// remove the mapping from this user to the table
if (_boidMap.remove(leaver.getOid()) == null) {
Log.warning("No body to table mapping to clear? " +
"[leaver=" + leaver + ", table=" + table + "].");
}
// either update or delete the table depending on whether or not
// we just removed the last occupant
if (table.isEmpty()) {
purgeTable(table);
} else {
_tlobj.updateTables(table);
}
}
/**
* Removes the table from all of our internal tables and from its
* lobby's distributed object.
*/
protected void purgeTable (Table table)
{
// remove the table from our tables table
_tables.remove(table.getTableId());
// clear out all matching entries in the boid map
for (int i = 0; i < table.bodyOids.length; i++) {
_boidMap.remove(table.bodyOids[i]);
}
// remove the table from the lobby object
_tlobj.removeFromTables(table.tableId);
}
/**
* Called when we're ready to create a game (either an invitation has
* been accepted or a table is ready to start. If there is a problem
* creating the game manager, it will be reported in the logs.
*
* @return a reference to the newly created game manager or null if
* something choked during the creation or initialization process.
*/
protected GameManager createGameManager (
GameConfig config, String[] players)
{
GameManager gmgr = null;
try {
Log.info("Creating game manager [config=" + config + "].");
// create the game manager and begin it's initialization
// process. the game manager will take care of notifying the
// players that the game has been created once it has been
// started up (which is done by the place registry once the
// game object creation has completed)
gmgr = (GameManager)CrowdServer.plreg.createPlace(config, this);
// provide the game manager with some initialization info
gmgr.setPlayers(players);
} catch (Exception e) {
Log.warning("Unable to create game manager [config=" + config +
", players=" + StringUtil.toString(players) + "].");
Log.logStackTrace(e);
}
return gmgr;
}
/**
* Called by the place registry when the game object has been created
* and provided to the game manager. We use this to map game object
* oids back to table records when we create games from tables.
*/
public void placeCreated (PlaceObject plobj, PlaceManager pmgr)
{
// see if this place manager is associated with a table
Table table = (Table)_pendingTables.remove(pmgr);
if (table != null) {
// update the table with the newly created game object
table.gameOid = plobj.getOid();
// add an object death listener to unmap the table when the
// game finally goes away
plobj.addListener(new ObjectDeathListener() {
public void objectDestroyed (ObjectDestroyedEvent event) {
unmapTable(event.getTargetOid());
}
});
// and then update the lobby object that contains the table
_tlobj.updateTables(table);
}
}
/**
* Called when a game created from a table managed by this table
* manager was destroyed. We remove the associated table.
*/
protected void unmapTable (int gameOid)
{
// look through our tables table for a table with a matching game
Iterator iter = _tables.values().iterator();
while (iter.hasNext()) {
Table table = (Table)iter.next();
if (table.gameOid == gameOid) {
purgeTable(table);
return; // all done
}
}
Log.warning("Requested to unmap table that wasn't mapped " +
"[gameOid=" + gameOid + "].");
}
// documentation inherited
public void objectAdded (ObjectAddedEvent event)
{
// nothing doing
}
// documentation inherited
public void objectRemoved (ObjectRemovedEvent event)
{
// if an occupant departed, see if they are in a pending table
if (!event.getName().equals(PlaceObject.OCCUPANTS)) {
return;
}
// look up the table to which this occupant is mapped
int bodyOid = event.getOid();
Table pender = (Table)_boidMap.remove(bodyOid);
if (pender == null) {
return;
}
// remove this occupant from the table
if (!pender.clearOccupant(bodyOid)) {
Log.warning("Attempt to remove body from mapped table failed " +
"[table=" + pender + ", bodyOid=" + bodyOid + "].");
return;
}
// either update or delete the table depending on whether or not
// we just removed the last occupant
if (pender.isEmpty()) {
purgeTable(pender);
} else {
_tlobj.updateTables(pender);
}
}
/** A reference to the place object in which we're managing tables. */
protected PlaceObject _plobj;
/** A reference to our place object casted to a table lobby object. */
protected TableLobbyObject _tlobj;
/** The table of pending tables. */
protected HashIntMap _tables = new HashIntMap();
/** A mapping from body oid to table. */
protected HashIntMap _boidMap = new HashIntMap();
/** A table of tables that have games that have been created but not
* yet started. */
protected HashMap _pendingTables = new HashMap();
}