cf1ad4ac4d
git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@840 c613c5cb-e716-0410-b11b-feb51c14d237
673 lines
24 KiB
Java
673 lines
24 KiB
Java
//
|
|
// $Id$
|
|
//
|
|
// Vilya library - tools for developing networked games
|
|
// Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved
|
|
// http://www.threerings.net/code/vilya/
|
|
//
|
|
// This library is free software; you can redistribute it and/or modify it
|
|
// under the terms of the GNU Lesser General Public License as published
|
|
// by the Free Software Foundation; either version 2.1 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
// Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public
|
|
// License along with this library; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
package com.threerings.parlor.server;
|
|
|
|
import com.samskivert.util.IntMap;
|
|
import com.samskivert.util.IntMaps;
|
|
import com.samskivert.util.ListUtil;
|
|
|
|
import com.threerings.util.Name;
|
|
|
|
import com.threerings.presents.data.ClientObject;
|
|
import com.threerings.presents.dobj.AttributeChangeListener;
|
|
import com.threerings.presents.dobj.AttributeChangedEvent;
|
|
import com.threerings.presents.dobj.ChangeListener;
|
|
import com.threerings.presents.dobj.DObject;
|
|
import com.threerings.presents.dobj.NamedEvent;
|
|
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.dobj.RootDObjectManager;
|
|
import com.threerings.presents.server.InvocationException;
|
|
import com.threerings.presents.server.InvocationManager;
|
|
|
|
import com.threerings.crowd.data.BodyObject;
|
|
import com.threerings.crowd.data.OccupantInfo;
|
|
import com.threerings.crowd.data.PlaceObject;
|
|
import com.threerings.crowd.server.PlaceRegistry;
|
|
|
|
import com.threerings.parlor.client.TableService;
|
|
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.data.GameConfig;
|
|
import com.threerings.parlor.game.data.GameObject;
|
|
import com.threerings.parlor.game.server.GameManager;
|
|
|
|
import static com.threerings.parlor.Log.log;
|
|
|
|
/**
|
|
* A table manager can be used by a place manager (or other entity) to take care of the management
|
|
* of a table matchmaking service on a particular distributed object.
|
|
*/
|
|
public class TableManager
|
|
implements ParlorCodes, TableProvider
|
|
{
|
|
/**
|
|
* Creates a table manager that will manage tables in the supplied distributed object (which
|
|
* must implement {@link TableLobbyObject}.
|
|
*/
|
|
public TableManager (RootDObjectManager omgr, InvocationManager invmgr, PlaceRegistry plreg,
|
|
DObject tableObject)
|
|
{
|
|
_omgr = omgr;
|
|
_invmgr = invmgr;
|
|
_plreg = plreg;
|
|
|
|
// set up our object references
|
|
_tlobj = (TableLobbyObject)tableObject;
|
|
_tlobj.setTableService(invmgr.registerDispatcher(new TableDispatcher(this)));
|
|
_dobj = tableObject;
|
|
|
|
// if our table is in a "place" add ourselves as a listener so that we can tell if a user
|
|
// leaves the place without leaving their table
|
|
if (_dobj instanceof PlaceObject) {
|
|
_dobj.addListener(_placeListener);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the number of tables being managed currently.
|
|
*/
|
|
public int getTableCount ()
|
|
{
|
|
return _tables.size();
|
|
}
|
|
|
|
/**
|
|
* This must be called when the table manager is no longer needed.
|
|
*/
|
|
public void shutdown ()
|
|
{
|
|
if (_tlobj != null) {
|
|
_invmgr.clearDispatcher(_tlobj.getTableService());
|
|
_tlobj.setTableService(null);
|
|
}
|
|
if (_dobj instanceof PlaceObject) {
|
|
_dobj.removeListener(_placeListener);
|
|
}
|
|
_tlobj = null;
|
|
_dobj = null;
|
|
}
|
|
|
|
/**
|
|
* Set the subclass of Table that this instance should generate.
|
|
*/
|
|
public void setTableClass (Class<? extends Table> tableClass)
|
|
{
|
|
_tableClass = tableClass;
|
|
}
|
|
|
|
/**
|
|
* Allow a player in the first position of a table to boot others.
|
|
*/
|
|
public void setAllowBooting (boolean allowBooting)
|
|
{
|
|
_allowBooting = allowBooting;
|
|
}
|
|
|
|
/**
|
|
* Creates a table for the specified creator and returns said table.
|
|
*
|
|
* @exception InvocationException thrown if the table could not be created for any reason.
|
|
*/
|
|
public Table createTable (BodyObject creator, TableConfig tableConfig, GameConfig config)
|
|
throws InvocationException
|
|
{
|
|
// make sure the caller is not already in a table
|
|
if (_boidMap.containsKey(creator.getOid())) {
|
|
throw new InvocationException(ALREADY_AT_TABLE);
|
|
}
|
|
|
|
// create a brand spanking new table
|
|
Table table;
|
|
try {
|
|
table = _tableClass.newInstance();
|
|
} catch (Exception e) {
|
|
log.warning("Table.newInstance() failed [class=" + _tableClass + ", e=" + e + "].");
|
|
throw new InvocationException(INTERNAL_ERROR);
|
|
}
|
|
table.init(_dobj.getOid(), tableConfig, config);
|
|
|
|
if (table.bodyOids != null && table.bodyOids.length > 0) {
|
|
// stick the creator into the first non-AI position
|
|
int cpos = (config.ais == null) ? 0 : config.ais.length;
|
|
String error = table.setPlayer(cpos, creator);
|
|
if (error != null) {
|
|
log.warning("Unable to add creator to position zero of table!? [table=" + table +
|
|
", creator=" + creator + ", error=" + error + "].");
|
|
// bail out now and abort the table creation process
|
|
throw new InvocationException(error);
|
|
}
|
|
|
|
// make a mapping from the creator to this table
|
|
notePlayerAdded(table, creator);
|
|
}
|
|
|
|
// stick the table into our tables tables
|
|
_tables.put(table.tableId, table);
|
|
|
|
// if the table has only one seat, start the game immediately
|
|
if (table.shouldBeStarted()) {
|
|
createGame(table);
|
|
}
|
|
|
|
// wire our table into our bits
|
|
tableCreated(table);
|
|
|
|
return table;
|
|
}
|
|
|
|
// from interface TableProvider
|
|
public void createTable (ClientObject caller, TableConfig tableConfig, GameConfig config,
|
|
TableService.ResultListener listener)
|
|
throws InvocationException
|
|
{
|
|
BodyObject creator = (BodyObject)caller;
|
|
|
|
// if we're managing tables in a place, make sure the creator is an occupant of the place
|
|
// in which they are requesting to create a table
|
|
if (_dobj instanceof PlaceObject &&
|
|
!((PlaceObject)_dobj).occupants.contains(creator.getOid())) {
|
|
log.warning("Requested to create a table in a place not occupied by the creator " +
|
|
"[creator=" + creator + ", ploid=" + _dobj.getOid() + "].");
|
|
throw new InvocationException(INTERNAL_ERROR);
|
|
}
|
|
|
|
// if createTable() returns, we're good to go
|
|
listener.requestProcessed(createTable(creator, tableConfig, config).tableId);
|
|
}
|
|
|
|
// from interface TableProvider
|
|
public void joinTable (ClientObject caller, int tableId, int position,
|
|
TableService.InvocationListener listener)
|
|
throws InvocationException
|
|
{
|
|
BodyObject joiner = (BodyObject)caller;
|
|
|
|
// make sure the caller is not already in a table
|
|
if (_boidMap.containsKey(joiner.getOid())) {
|
|
throw new InvocationException(ALREADY_AT_TABLE);
|
|
}
|
|
|
|
// look the table up
|
|
Table table = _tables.get(tableId);
|
|
if (table == null) {
|
|
throw new InvocationException(NO_SUCH_TABLE);
|
|
}
|
|
|
|
// request that the user be added to the table at that position
|
|
String error = table.setPlayer(position, joiner);
|
|
// if that failed, report the error
|
|
if (error != null) {
|
|
throw new InvocationException(error);
|
|
}
|
|
|
|
// if the table is sufficiently full, start the game automatically
|
|
if (table.shouldBeStarted()) {
|
|
createGame(table);
|
|
} else {
|
|
// make a mapping from this player to this table
|
|
notePlayerAdded(table, joiner);
|
|
}
|
|
|
|
// update the table in the lobby
|
|
updateTable(table);
|
|
|
|
// there is normally no success response. the client will see
|
|
// themselves show up in the table that they joined
|
|
}
|
|
|
|
// from interface TableProvider
|
|
public void leaveTable (ClientObject caller, int tableId,
|
|
TableService.InvocationListener listener)
|
|
throws InvocationException
|
|
{
|
|
BodyObject leaver = (BodyObject)caller;
|
|
|
|
// look the table up
|
|
Table table = _tables.get(tableId);
|
|
if (table == null) {
|
|
throw new InvocationException(NO_SUCH_TABLE);
|
|
}
|
|
|
|
// if the table is in play, the user is not allowed to leave (he must leave the game)
|
|
if (table.inPlay()) {
|
|
throw new InvocationException(GAME_ALREADY_STARTED);
|
|
}
|
|
|
|
// request that the user be removed from the table
|
|
if (!table.clearPlayer(leaver.getVisibleName())) {
|
|
throw new InvocationException(NOT_AT_TABLE);
|
|
}
|
|
|
|
// remove the mapping from this user to the table
|
|
if (null == notePlayerRemoved(leaver.getOid(), leaver)) {
|
|
log.warning("No body to table mapping to clear? [leaver=" + leaver.who() +
|
|
", table=" + table + "].");
|
|
}
|
|
|
|
// either update or delete the table depending on whether or not we just removed the last
|
|
// player
|
|
if (table.isEmpty()) {
|
|
purgeTable(table);
|
|
} else {
|
|
updateTable(table);
|
|
}
|
|
|
|
// there is normally no success response. the client will see
|
|
// themselves removed from the table they just left
|
|
}
|
|
|
|
// from interface TableProvider
|
|
public void startTableNow (ClientObject caller, int tableId,
|
|
TableService.InvocationListener listener)
|
|
throws InvocationException
|
|
{
|
|
BodyObject starter = (BodyObject)caller;
|
|
Table table = _tables.get(tableId);
|
|
if (table == null) {
|
|
throw new InvocationException(NO_SUCH_TABLE);
|
|
} else if (starter.getOid() != table.bodyOids[0]) {
|
|
throw new InvocationException(MUST_BE_CREATOR);
|
|
} else if (!table.mayBeStarted()) {
|
|
throw new InvocationException(INTERNAL_ERROR);
|
|
}
|
|
createGame(table);
|
|
}
|
|
|
|
// from interface TableProvider
|
|
public void bootPlayer (ClientObject caller, int tableId, Name target,
|
|
TableService.InvocationListener listener)
|
|
throws InvocationException
|
|
{
|
|
BodyObject booter = (BodyObject) caller;
|
|
Table table = _tables.get(tableId);
|
|
|
|
if (table == null) {
|
|
throw new InvocationException(NO_SUCH_TABLE);
|
|
} else if ( ! _allowBooting) {
|
|
throw new InvocationException(INTERNAL_ERROR);
|
|
}
|
|
|
|
int position = ListUtil.indexOf(table.players, target);
|
|
if (position < 0) {
|
|
throw new InvocationException(NOT_AT_TABLE);
|
|
} else if (booter.getOid() != table.bodyOids[0]) {
|
|
throw new InvocationException(MUST_BE_CREATOR);
|
|
} else if (booter.getOid() == table.bodyOids[position]) {
|
|
throw new InvocationException(NO_SELF_BOOT);
|
|
}
|
|
|
|
// Remember to keep him banned
|
|
table.addBannedUser(target);
|
|
|
|
// Remove the player from the table
|
|
if (notePlayerRemoved(table.bodyOids[position]) == null) {
|
|
log.warning("No body to table mapping to clear? [position=" + position +
|
|
", table=" + table + "].");
|
|
}
|
|
table.clearPlayerPos(position);
|
|
updateTable(table);
|
|
}
|
|
|
|
/**
|
|
* Publishes a newly created table to the lobby object. Can also be overridden by custom
|
|
* managers that want to react to table creation.
|
|
*/
|
|
protected void tableCreated (Table table)
|
|
{
|
|
// publish the table in the lobby object if desired
|
|
if (shouldPublish(table)) {
|
|
_tlobj.addToTables(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.tableId);
|
|
|
|
// clear out all matching entries in the boid map
|
|
if (table.bodyOids != null) {
|
|
for (int bodyOid : table.bodyOids) {
|
|
if (bodyOid != 0) {
|
|
notePlayerRemoved(bodyOid);
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove the mapping by gameOid
|
|
Table removed = _goidMap.remove(table.gameOid); // no-op if gameOid == 0
|
|
|
|
// remove the table from the lobby object (the table may not have been published if a
|
|
// derived class decided it was not worth publishing, see shouldPublish())
|
|
if (_tlobj.getTables().containsKey(table.tableId)) {
|
|
_tlobj.removeFromTables(table.tableId);
|
|
}
|
|
|
|
// remove the listener too so we do not get a request later on to update the occupants or
|
|
// unmap this table
|
|
if (removed != null) {
|
|
DObject gameObj = _omgr.getObject(table.gameOid);
|
|
if (gameObj != null) {
|
|
gameObj.removeListener(_gameListener);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when a player is added to a table to set up our mappings.
|
|
*/
|
|
protected void notePlayerAdded (Table table, BodyObject body)
|
|
{
|
|
_boidMap.put(body.getOid(), table);
|
|
body.addListener(_userListener);
|
|
}
|
|
|
|
/**
|
|
* Called when a player leaves the room and we're not sure if the user is still online.
|
|
*/
|
|
final protected Table notePlayerRemoved (int playerOid)
|
|
{
|
|
return notePlayerRemoved(playerOid, (BodyObject)_omgr.getObject(playerOid));
|
|
}
|
|
|
|
/**
|
|
* Called when a player leaves a table to clear our mappings.
|
|
* @param body will be non-null if the user is still online.
|
|
*/
|
|
protected Table notePlayerRemoved (int playerOid, BodyObject body)
|
|
{
|
|
if (body != null) {
|
|
body.removeListener(_userListener);
|
|
}
|
|
return _boidMap.remove(playerOid);
|
|
}
|
|
|
|
/**
|
|
* 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 should be reported in
|
|
* the logs.
|
|
*
|
|
* @return the oid of the newly-created game.
|
|
*/
|
|
protected int createGame (final Table table)
|
|
throws InvocationException
|
|
{
|
|
try {
|
|
GameManager gmgr = createGameManager(createConfig(table));
|
|
GameObject gobj = (GameObject)gmgr.getPlaceObject();
|
|
gameCreated(table, gobj, gmgr);
|
|
return gobj.getOid();
|
|
|
|
} catch (Throwable t) {
|
|
log.warning("Failed to create manager for game [config=" + table.config + "]: " + t);
|
|
throw new InvocationException(INTERNAL_ERROR);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This method should validate that the (client provided) configuration in the supplied {@link
|
|
* Table} object is valid and fill in any extra information that is the purview of the server.
|
|
*/
|
|
protected GameConfig createConfig (Table table)
|
|
{
|
|
// fill the players array into the game config
|
|
table.config.players = table.getPlayers();
|
|
// we just trust the rest by default, yay!
|
|
return table.config;
|
|
}
|
|
|
|
/**
|
|
* Creates a {@link GameManager} using the supplied config. Used by {@link #createGame}, but
|
|
* extracted into a method to allow customization of this process.
|
|
*/
|
|
protected GameManager createGameManager (GameConfig config)
|
|
throws InstantiationException, InvocationException
|
|
{
|
|
return (GameManager)_plreg.createPlace(config);
|
|
}
|
|
|
|
/**
|
|
* Called when our game has been created, we take this opportunity to clean up the table and
|
|
* transition it to "in play" mode.
|
|
*/
|
|
protected void gameCreated (Table table, GameObject gameobj, GameManager gmgr)
|
|
{
|
|
// update the table with the newly created game object
|
|
table.gameOid = gameobj.getOid();
|
|
|
|
// add it to the gameOid map
|
|
_goidMap.put(table.gameOid, table);
|
|
|
|
// configure the privacy of the game
|
|
gameobj.setIsPrivate(table.tconfig.privateTable);
|
|
|
|
if (table.bodyOids != null) {
|
|
// clear the player to table mappings as this game is underway
|
|
for (int bodyOid : table.bodyOids) {
|
|
if (bodyOid != 0) {
|
|
notePlayerRemoved(bodyOid);
|
|
}
|
|
}
|
|
}
|
|
|
|
// add an object death listener to unmap the table when the game finally goes away
|
|
gameobj.addListener(_gameListener);
|
|
|
|
// and then update the lobby object that contains the table
|
|
if (shouldPublish(table)) {
|
|
updateTable(table);
|
|
} else if (_tlobj.getTables().containsKey(table.tableId)) {
|
|
// or remove it if the table is no longer "interesting"
|
|
_tlobj.removeFromTables(table.tableId);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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)
|
|
{
|
|
// if we've been shutdown, then we've got nothing to worry about
|
|
if (_tlobj == null) {
|
|
return;
|
|
}
|
|
|
|
Table table = _goidMap.get(gameOid);
|
|
if (table != null) {
|
|
purgeTable(table);
|
|
} else {
|
|
log.warning("Requested to unmap table that wasn't mapped [gameOid=" + gameOid + "].");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Called when the occupants in a game change: publishes new info.
|
|
*/
|
|
protected void updateOccupants (int gameOid)
|
|
{
|
|
// if we've been shutdown, then we've got nothing to worry about
|
|
if (_tlobj == null) {
|
|
return;
|
|
}
|
|
|
|
Table table = _goidMap.get(gameOid);
|
|
if (table == null) {
|
|
log.warning("Unable to find table for running game [gameOid=" + gameOid + "].");
|
|
return;
|
|
}
|
|
|
|
// update this table's occupants information and update the table
|
|
GameObject gameObj = (GameObject)_omgr.getObject(gameOid);
|
|
table.updateOccupants(gameObj);
|
|
updateTable(table);
|
|
}
|
|
|
|
/**
|
|
* Called when a body is known to have left either the room that contains our tables or logged
|
|
* off of the server.
|
|
*/
|
|
protected void bodyLeft (int bodyOid)
|
|
{
|
|
// look up the table to which this player is mapped
|
|
Table pender = notePlayerRemoved(bodyOid);
|
|
if (pender == null) {
|
|
return;
|
|
}
|
|
|
|
// remove this player from the table
|
|
if (!pender.clearPlayerByOid(bodyOid)) {
|
|
log.warning("Attempt to remove body from mapped table failed [table=" + pender +
|
|
", bodyOid=" + bodyOid + "].");
|
|
return;
|
|
}
|
|
|
|
// update or delete the table depending on whether or not we just removed the last player
|
|
if (pender.isEmpty()) {
|
|
purgeTable(pender);
|
|
} else {
|
|
updateTable(pender);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Derived classes can override this method to filter certain tables from being published in
|
|
* the lobby object. Such tables are probably unwatchable and unjoinable and thus just take up
|
|
* space in the lobby object for no good reason. This will be checked when the table is first
|
|
* created and when the table's game transitions to in-play.
|
|
*/
|
|
protected boolean shouldPublish (Table table)
|
|
{
|
|
return true; // we publish all tables by default
|
|
}
|
|
|
|
protected void updateTable (Table table)
|
|
{
|
|
// the table may not have been published, see shouldPublish()
|
|
if (_tlobj.getTables().containsKey(table.tableId)) {
|
|
_tlobj.updateTables(table);
|
|
}
|
|
}
|
|
|
|
/** Listens to all games and updates the table objects as necessary. */
|
|
protected class GameListener
|
|
implements ObjectDeathListener, OidListListener
|
|
{
|
|
// from ObjectDeathListener
|
|
public void objectDestroyed (ObjectDestroyedEvent event) {
|
|
unmapTable(event.getTargetOid());
|
|
}
|
|
|
|
// from OidListListener
|
|
public void objectAdded (ObjectAddedEvent event) {
|
|
maybeCheckOccupants(event);
|
|
}
|
|
|
|
// from OidListListener
|
|
public void objectRemoved (ObjectRemovedEvent event) {
|
|
maybeCheckOccupants(event);
|
|
}
|
|
|
|
/** Check to see if the set event causes us to update the table. */
|
|
protected void maybeCheckOccupants (NamedEvent event) {
|
|
if (GameObject.OCCUPANTS.equals(event.getName())) {
|
|
updateOccupants(event.getTargetOid());
|
|
}
|
|
}
|
|
} // END: class GameDeathListener
|
|
|
|
/** Listens to all users who have joined a table, takes care of removing them
|
|
* as necessary. */
|
|
protected class UserListener
|
|
implements AttributeChangeListener, ObjectDeathListener
|
|
{
|
|
// from AttributeChangeListener
|
|
public void attributeChanged (AttributeChangedEvent event) {
|
|
// Remove folks from tables when they disconnect
|
|
if (BodyObject.STATUS.equals(event.getName())) {
|
|
if (event.getByteValue() == OccupantInfo.DISCONNECTED) {
|
|
bodyLeft(event.getTargetOid());
|
|
}
|
|
}
|
|
}
|
|
|
|
// from ObjectDeathListener
|
|
public void objectDestroyed (ObjectDestroyedEvent event) {
|
|
// Remove folks from tables when they die
|
|
bodyLeft(event.getTargetOid());
|
|
}
|
|
} // END: class UserListener
|
|
|
|
/** Listens for players leaving the place that contains our tables. */
|
|
protected ChangeListener _placeListener = new OidListListener() {
|
|
public void objectAdded (ObjectAddedEvent event) {
|
|
// nothing doing
|
|
}
|
|
public void objectRemoved (ObjectRemovedEvent event) {
|
|
// if an occupant departed, see if they are in a pending table
|
|
if (event.getName().equals(PlaceObject.OCCUPANTS)) {
|
|
bodyLeft(event.getOid());
|
|
}
|
|
}
|
|
};
|
|
|
|
/** A reference to the distributed object in which we're managing tables. */
|
|
protected DObject _dobj;
|
|
|
|
/** A reference to our distributed object casted to a table lobby object. */
|
|
protected TableLobbyObject _tlobj;
|
|
|
|
/** The class of table we instantiate. */
|
|
protected Class<? extends Table> _tableClass = Table.class;
|
|
|
|
/** The table of pending tables. */
|
|
protected IntMap<Table> _tables = IntMaps.newHashIntMap();
|
|
|
|
/** A mapping from body oid to table. */
|
|
protected IntMap<Table> _boidMap = IntMaps.newHashIntMap();
|
|
|
|
/** Once a game starts, a mapping from gameOid to table. */
|
|
protected IntMap<Table> _goidMap = IntMaps.newHashIntMap();
|
|
|
|
/** A listener that prunes tables after the game dies. */
|
|
protected ChangeListener _gameListener = new GameListener();
|
|
|
|
/** A listener that removes users from tables when they're no longer able to play. */
|
|
protected ChangeListener _userListener = new UserListener();
|
|
|
|
/** Whether or not tables should support booting. */
|
|
protected boolean _allowBooting = false;
|
|
|
|
protected RootDObjectManager _omgr;
|
|
protected InvocationManager _invmgr;
|
|
protected PlaceRegistry _plreg;
|
|
}
|