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:
@@ -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();
|
||||
}
|
||||
Reference in New Issue
Block a user