Extracted server side of table management from the parlor manager and put

it into its own table manager class so that each place manager that wishes
to perform table management can instantiate a table manager which can then
more easily track tables for that particular place.

Now handle occupants disappearing from pending tables properly. Still need
to sort out game object life-cycle tracking.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@532 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2001-10-23 23:47:02 +00:00
parent 095cbfb970
commit a4ceafd268
7 changed files with 500 additions and 329 deletions
@@ -0,0 +1,348 @@
//
// $Id: TableManager.java,v 1.1 2001/10/23 23:47:02 mdb 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.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.client.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 TableLobbyManager} 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);
}
// 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);
}
// documentation inherited
public void objectAdded (ObjectAddedEvent event)
{
// nothing doing
}
// documentation inherited
public void objectRemoved (ObjectRemovedEvent event)
{
Log.info("Object removed: " + 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);
}
}
/**
* 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();
// and then update the lobby object that contains the table
_tlobj.updateTables(table);
}
}
/**
* 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;
}
/** 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();
}