From a4ceafd268303ad6fb3da5e847a548087a2558ad Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Tue, 23 Oct 2001 23:47:02 +0000 Subject: [PATCH] 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 --- .../parlor/client/ParlorService.java | 64 ++-- .../parlor/client/TableDirector.java | 11 +- .../com/threerings/parlor/data/Table.java | 39 +- .../parlor/server/ParlorManager.java | 281 +------------- .../parlor/server/ParlorProvider.java | 67 +++- .../parlor/server/TableManager.java | 348 ++++++++++++++++++ .../parlor/server/TableManagerProvider.java | 19 + 7 files changed, 500 insertions(+), 329 deletions(-) create mode 100644 src/java/com/threerings/parlor/server/TableManager.java create mode 100644 src/java/com/threerings/parlor/server/TableManagerProvider.java diff --git a/src/java/com/threerings/parlor/client/ParlorService.java b/src/java/com/threerings/parlor/client/ParlorService.java index 638eaf878..ec363b6fd 100644 --- a/src/java/com/threerings/parlor/client/ParlorService.java +++ b/src/java/com/threerings/parlor/client/ParlorService.java @@ -1,5 +1,5 @@ // -// $Id: ParlorService.java,v 1.9 2001/10/23 20:23:29 mdb Exp $ +// $Id: ParlorService.java,v 1.10 2001/10/23 23:47:01 mdb Exp $ package com.threerings.parlor.client; @@ -32,19 +32,17 @@ public class ParlorService implements ParlorCodes * @param invitee the username of the user to be invited. * @param config a game config object detailing the type and * configuration of the game to be created. - * @param rsptarget the object reference that will receive and process + * @param rsptgt the object reference that will receive and process * the response. * * @return the invocation request id of the generated request. */ public static int invite (Client client, String invitee, - GameConfig config, Object rsptarget) + GameConfig config, Object rsptgt) { InvocationDirector invdir = client.getInvocationDirector(); Object[] args = new Object[] { invitee, config }; - Log.info("Sending invite request [to=" + invitee + - ", cfg=" + config + "]."); - return invdir.invoke(MODULE_NAME, INVITE_ID, args, rsptarget); + return invdir.invoke(MODULE_NAME, INVITE_ID, args, rsptgt); } /** @@ -63,23 +61,20 @@ public class ParlorService implements ParlorCodes * the case of an invitation refusal or an updated game configuration * object in the case of a counter-invitation, or null in the case of * an accepted invitation). - * @param rsptarget the object reference that will receive and process + * @param rsptgt the object reference that will receive and process * the response. * * @return the invocation request id of the generated request. */ public static int respond (Client client, int inviteId, int code, - Object arg, Object rsptarget) + Object arg, Object rsptgt) { InvocationDirector invdir = client.getInvocationDirector(); Object[] args = new Object[] { new Integer(inviteId), new Integer(code), null }; // we can't have a null argument so we use the empty string args[2] = (arg == null) ? "" : arg; - Log.info("Sending invitation response [inviteId=" + inviteId + - ", code=" + code + ", arg=" + arg + "]."); - return invdir.invoke( - MODULE_NAME, RESPOND_INVITE_ID, args, rsptarget); + return invdir.invoke(MODULE_NAME, RESPOND_INVITE_ID, args, rsptgt); } /** @@ -90,18 +85,16 @@ public class ParlorService implements ParlorCodes * @param client a connected, operational client instance. * @param inviteId the unique id previously assigned by the server to * this invitation. - * @param rsptarget the object reference that will receive and process + * @param rsptgt the object reference that will receive and process * the response. * * @return the invocation request id of the generated request. */ - public static int cancel (Client client, int inviteId, Object rsptarget) + public static int cancel (Client client, int inviteId, Object rsptgt) { InvocationDirector invdir = client.getInvocationDirector(); Object[] args = new Object[] { new Integer(inviteId) }; - Log.info("Sending invitation cancellation " + - "[inviteId=" + inviteId + "]."); - return invdir.invoke(MODULE_NAME, CANCEL_INVITE_ID, args, rsptarget); + return invdir.invoke(MODULE_NAME, CANCEL_INVITE_ID, args, rsptgt); } /** @@ -114,20 +107,17 @@ public class ParlorService implements ParlorCodes * created table. * @param config the game config for the game to be matchmade by the * table. - * @param rsptarget the object reference that will receive and process + * @param rsptgt the object reference that will receive and process * the response. * * @return the invocation request id of the generated request. */ public static int createTable ( - Client client, int lobbyOid, GameConfig config, Object rsptarget) + Client client, int lobbyOid, GameConfig config, Object rsptgt) { InvocationDirector invdir = client.getInvocationDirector(); Object[] args = new Object[] { new Integer(lobbyOid), config }; - Log.info("Sending table creation request " + - "[lobbyOid=" + lobbyOid + ", config=" + config + "]."); - return invdir.invoke( - MODULE_NAME, CREATE_TABLE_REQUEST, args, rsptarget); + return invdir.invoke(MODULE_NAME, CREATE_TABLE_REQUEST, args, rsptgt); } /** @@ -136,25 +126,24 @@ public class ParlorService implements ParlorCodes * be added to the specified table at the specified position. * * @param client a connected, operational client instance. + * @param lobbyOid the oid of the lobby that contains the table. * @param tableId the unique id of the table to which this user wishes * to be added. * @param position the position at the table to which this user desires * to be added. - * @param rsptarget the object reference that will receive and process + * @param rsptgt the object reference that will receive and process * the response. * * @return the invocation request id of the generated request. */ - public static int joinTable ( - Client client, int tableId, int position, Object rsptarget) + public static int joinTable (Client client, int lobbyOid, int tableId, + int position, Object rsptgt) { InvocationDirector invdir = client.getInvocationDirector(); - Object[] args = new Object[] { new Integer(tableId), + Object[] args = new Object[] { new Integer(lobbyOid), + new Integer(tableId), new Integer(position) }; - Log.info("Sending join table request " + - "[tableId=" + tableId + ", position=" + position + "]."); - return invdir.invoke( - MODULE_NAME, JOIN_TABLE_REQUEST, args, rsptarget); + return invdir.invoke(MODULE_NAME, JOIN_TABLE_REQUEST, args, rsptgt); } /** @@ -163,21 +152,20 @@ public class ParlorService implements ParlorCodes * be removed from the specified table. * * @param client a connected, operational client instance. + * @param lobbyOid the oid of the lobby that contains the table. * @param tableId the unique id of the table from which this user * wishes to be removed. - * @param rsptarget the object reference that will receive and process + * @param rsptgt the object reference that will receive and process * the response. * * @return the invocation request id of the generated request. */ public static int leaveTable ( - Client client, int tableId, Object rsptarget) + Client client, int lobbyOid, int tableId, Object rsptgt) { InvocationDirector invdir = client.getInvocationDirector(); - Object[] args = new Object[] { new Integer(tableId) }; - Log.info("Sending leave table request " + - "[tableId=" + tableId + "]."); - return invdir.invoke( - MODULE_NAME, LEAVE_TABLE_REQUEST, args, rsptarget); + Object[] args = new Object[] { new Integer(lobbyOid), + new Integer(tableId) }; + return invdir.invoke(MODULE_NAME, LEAVE_TABLE_REQUEST, args, rsptgt); } } diff --git a/src/java/com/threerings/parlor/client/TableDirector.java b/src/java/com/threerings/parlor/client/TableDirector.java index ae22b1598..d2504b0bc 100644 --- a/src/java/com/threerings/parlor/client/TableDirector.java +++ b/src/java/com/threerings/parlor/client/TableDirector.java @@ -1,5 +1,5 @@ // -// $Id: TableDirector.java,v 1.3 2001/10/23 20:26:30 mdb Exp $ +// $Id: TableDirector.java,v 1.4 2001/10/23 23:47:02 mdb Exp $ package com.threerings.parlor.client; @@ -165,9 +165,9 @@ public class TableManager return; } - // go ahead and issue the create request + // issue the join request ParlorService.joinTable( - _ctx.getClient(), tableId, position, this); + _ctx.getClient(), _lobby.getOid(), tableId, position, this); } /** @@ -184,8 +184,9 @@ public class TableManager return; } - // go ahead and issue the create request - ParlorService.leaveTable(_ctx.getClient(), tableId, this); + // issue the leave request + ParlorService.leaveTable( + _ctx.getClient(), _lobby.getOid(), tableId, this); } // documentation inherited diff --git a/src/java/com/threerings/parlor/data/Table.java b/src/java/com/threerings/parlor/data/Table.java index 566b250fc..f64ccbce7 100644 --- a/src/java/com/threerings/parlor/data/Table.java +++ b/src/java/com/threerings/parlor/data/Table.java @@ -1,5 +1,5 @@ // -// $Id: Table.java,v 1.5 2001/10/23 20:23:29 mdb Exp $ +// $Id: Table.java,v 1.6 2001/10/23 23:47:02 mdb Exp $ package com.threerings.parlor.data; @@ -37,6 +37,10 @@ public class Table * slots may not be filled. */ public String[] occupants; + /** The body oids of the occupants of this table. (This is not + * propagated to remote instances.) */ + public int[] bodyOids; + /** The game config for the game that is being matchmade. This config * instance will also implement {@link TableConfig}. */ public GameConfig config; @@ -66,6 +70,7 @@ public class Table // make room for the maximum number of players occupants = new String[_tconfig.getMaximumPlayers()]; + bodyOids = new int[occupants.length]; } /** @@ -115,11 +120,15 @@ public class Table * Requests to seat the specified user at the specified position in * this table. * + * @param position the position in which to seat the user. + * @param username the username of the user to be set. + * @param bodyOid the body object id of the user to be set. + * * @return null if the user was successfully seated, a string error * code explaining the failure if the user was not able to be seated * at that position. */ - public String setOccupant (int position, String username) + public String setOccupant (int position, String username, int bodyOid) { // find out how many positions we have for occupation int maxpos = _tconfig.getDesiredPlayers(); @@ -140,6 +149,7 @@ public class Table // otherwise all is well, stick 'em in occupants[position] = username; + bodyOids[position] = bodyOid; return null; } @@ -156,6 +166,27 @@ public class Table for (int i = 0; i < occupants.length; i++) { if (username.equals(occupants[i])) { occupants[i] = ""; + bodyOids[i] = 0; + return true; + } + } + return false; + } + + /** + * Requests that the user identified by the specified body object id + * be removed from their seat at this table. + * + * @return true if the user was seated at the table and has now been + * removed, false if the user was never seated at the table in the + * first place. + */ + public boolean clearOccupant (int bodyOid) + { + for (int i = 0; i < bodyOids.length; i++) { + if (bodyOid == bodyOids[i]) { + occupants[i] = ""; + bodyOids[i] = 0; return true; } } @@ -168,7 +199,7 @@ public class Table */ public boolean readyToStart () { - int need = _tconfig.getMinimumPlayers(); + int need = _tconfig.getDesiredPlayers(); if (need == -1) { need = _tconfig.getMaximumPlayers(); } @@ -230,6 +261,7 @@ public class Table lobbyOid = in.readInt(); gameOid = in.readInt(); occupants = (String[])ValueMarshaller.readFrom(in); + // bodyOids = new int[occupants.length]; // not used config = (GameConfig)ValueMarshaller.readFrom(in); _tconfig = (TableConfig)config; } @@ -256,6 +288,7 @@ public class Table ", lobbyOid=" + lobbyOid + ", gameOid=" + gameOid + ", occupants=" + StringUtil.toString(occupants) + + ", bodyOids=" + StringUtil.toString(bodyOids) + ", config=" + config + "]"; } diff --git a/src/java/com/threerings/parlor/server/ParlorManager.java b/src/java/com/threerings/parlor/server/ParlorManager.java index 797fb8b85..1d3bea2f5 100644 --- a/src/java/com/threerings/parlor/server/ParlorManager.java +++ b/src/java/com/threerings/parlor/server/ParlorManager.java @@ -1,28 +1,19 @@ // -// $Id: ParlorManager.java,v 1.14 2001/10/23 20:23:29 mdb Exp $ +// $Id: ParlorManager.java,v 1.15 2001/10/23 23:47:02 mdb Exp $ package com.threerings.parlor.server; -import java.util.HashMap; - import com.samskivert.util.Config; import com.samskivert.util.HashIntMap; -import com.samskivert.util.StringUtil; import com.threerings.presents.server.InvocationManager; 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; @@ -34,7 +25,7 @@ import com.threerings.parlor.game.GameManager; * game. */ public class ParlorManager - implements ParlorCodes, PlaceRegistry.CreationObserver + implements ParlorCodes { /** * Initializes the parlor manager. This should be called by the server @@ -171,10 +162,22 @@ public class ParlorManager } } + /** + * Requests that an outstanding invitation be cancelled. + * + * @param source the body object of the user that is making the + * request. + * @param inviteId the unique id of the invitation to be cancelled. + */ public void cancelInvite (BodyObject source, int inviteId) { + // TBD } + /** + * Starts up and configures the game manager for an accepted + * invitation. + */ protected void processAcceptedInvitation (Invitation invite) { try { @@ -188,10 +191,9 @@ public class ParlorManager GameManager gmgr = (GameManager) CrowdServer.plreg.createPlace(invite.config, null); - // provide the game manager with some initialization info - String[] players = new String[] { - invite.invitee.username, invite.inviter.username }; - gmgr.setPlayers(players); + // provide the game manager with the player list + gmgr.setPlayers(new String[] { + invite.invitee.username, invite.inviter.username }); } catch (Exception e) { Log.warning("Unable to create game manager [invite=" + invite + @@ -200,248 +202,6 @@ public class ParlorManager } } - /** - * 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 lobbyOid the place object id of the lobby in which this - * table should be created. The place object specified must implement - * the {@link TableLobbyObject} interface. - * @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, int lobbyOid, - GameConfig config) - throws ServiceFailedException - { - // fetch the place object in which we'll be creating a table - try { - TableLobbyObject tlobj = (TableLobbyObject) - CrowdServer.omgr.getObject(lobbyOid); - - // 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 + - ", loboid=" + lobbyOid + - ", config=" + config + "]."); - throw new ServiceFailedException(INTERNAL_ERROR); - } - - // create a brand spanking new table - Table table = new Table(lobbyOid, config); - - // stick the creator into position zero - String error = table.setOccupant(0, creator.username); - 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); - - // also stick it into our tables table - _tables.put(table.getTableId(), table); - - // finally let the caller know what the new table id is - return table.getTableId(); - - } catch (ClassCastException cce) { - Log.warning("Requested to create table in non-table-lobby " + - "[creator=" + creator + ", loboid=" + lobbyOid + - ", config=" + config + ", cce=" + cce + "]."); - throw new ServiceFailedException(INTERNAL_ERROR); - } - } - - /** - * 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); - } - - // make sure the lobby for this table is still around - TableLobbyObject tlobj = (TableLobbyObject) - CrowdServer.omgr.getObject(table.lobbyOid); - if (tlobj == null) { - Log.warning("User tried to join table whose lobby has " + - "disappeared [table=" + table + - ", joiner=" + joiner + "]."); - throw new ServiceFailedException(INTERNAL_ERROR); - } - - // request that the user be added to the table at that position - String error = table.setOccupant(position, joiner.username); - // 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); - } - - // 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); - } - - // make sure the lobby for this table is still around - TableLobbyObject tlobj = (TableLobbyObject) - CrowdServer.omgr.getObject(table.lobbyOid); - if (tlobj == null) { - Log.warning("User tried to leave table whose lobby has " + - "disappeared [table=" + table + - ", leaver=" + leaver + "]."); - throw new ServiceFailedException(INTERNAL_ERROR); - } - - // request that the user be removed from the table - if (!table.clearOccupant(leaver.username)) { - throw new ServiceFailedException(NOT_AT_TABLE); - } - - // check to see if we just removed the last person from the table - if (table.isEmpty()) { - Log.info("Clearing empty table."); - // remove the table from our tables table and from the lobby - _tables.remove(tableId); - tlobj.removeFromTables(table.tableId); - - } else { - Log.info("Updating sparser table."); - // update the table in the lobby - 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; - } - - /** - * 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 - TableLobbyObject tlobj = (TableLobbyObject) - CrowdServer.omgr.getObject(table.lobbyOid); - if (tlobj == null) { - Log.warning("Can't update in-play table as lobby's gone " + - "missing [table=" + table + "]."); - return; - } - tlobj.updateTables(table); - } - } - /** * The invitation record is used by the parlor manager to keep track * of pending invitations. @@ -490,13 +250,6 @@ public class ParlorManager /** The table of pending invitations. */ protected HashIntMap _invites = new HashIntMap(); - /** The table of pending tables. */ - protected HashIntMap _tables = new HashIntMap(); - - /** A table of tables that have games that have been created but not - * yet started. */ - protected HashMap _pendingTables = new HashMap(); - /** A counter used to generate unique identifiers for invitation * records. */ protected static int _nextInviteId = 0; diff --git a/src/java/com/threerings/parlor/server/ParlorProvider.java b/src/java/com/threerings/parlor/server/ParlorProvider.java index f372cfd24..ef6012bb7 100644 --- a/src/java/com/threerings/parlor/server/ParlorProvider.java +++ b/src/java/com/threerings/parlor/server/ParlorProvider.java @@ -1,12 +1,14 @@ // -// $Id: ParlorProvider.java,v 1.9 2001/10/23 20:23:29 mdb Exp $ +// $Id: ParlorProvider.java,v 1.10 2001/10/23 23:47:02 mdb Exp $ package com.threerings.parlor.server; import com.threerings.presents.server.InvocationProvider; import com.threerings.presents.server.ServiceFailedException; + import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.server.CrowdServer; +import com.threerings.crowd.server.PlaceManager; import com.threerings.parlor.Log; import com.threerings.parlor.client.ParlorCodes; @@ -93,17 +95,16 @@ public class ParlorProvider * Processes a request from the client to create a new table. */ public void handleCreateTableRequest ( - BodyObject source, int invid, int placeOid, GameConfig config) + BodyObject source, int invid, int lobbyOid, GameConfig config) { Log.info("Handling create table request [source=" + source + - ", invid=" + invid + ", placeOid=" + placeOid + + ", invid=" + invid + ", lobbyOid=" + lobbyOid + ", config=" + config + "]."); - String rsp = null; - try { // pass the creation request on to the table manager - int tableId = _pmgr.createTable(source, placeOid, config); + TableManager tmgr = getTableManager(lobbyOid); + int tableId = tmgr.createTable(source, config); sendResponse(source, invid, TABLE_CREATED_RESPONSE, new Integer(tableId)); @@ -119,17 +120,16 @@ public class ParlorProvider * Processes a request from the client to join an existing table. */ public void handleJoinTableRequest ( - BodyObject source, int invid, int tableId, int position) + BodyObject source, int invid, int lobbyOid, int tableId, int position) { Log.info("Handling join table request [source=" + source + - ", invid=" + invid + ", tableId=" + tableId + - ", position=" + position + "]."); - - String rsp = null; + ", invid=" + invid + ", lobbyOid=" + lobbyOid + + ", tableId=" + tableId + ", position=" + position + "]."); try { - // pass the creation request on to the table manager - _pmgr.joinTable(source, tableId, position); + // pass the join request on to the table manager + TableManager tmgr = getTableManager(lobbyOid); + tmgr.joinTable(source, tableId, position); // there is normally no response. the client will see // themselves show up in the table that they joined @@ -145,16 +145,16 @@ public class ParlorProvider * Processes a request from the client to leave an existing table. */ public void handleLeaveTableRequest ( - BodyObject source, int invid, int tableId) + BodyObject source, int invid, int lobbyOid, int tableId) { Log.info("Handling leave table request [source=" + source + - ", invid=" + invid + ", tableId=" + tableId + "]."); - - String rsp = null; + ", invid=" + invid + ", lobbyOid=" + lobbyOid + + ", tableId=" + tableId + "]."); try { - // pass the request on to the table manager - _pmgr.leaveTable(source, tableId); + // pass the join request on to the table manager + TableManager tmgr = getTableManager(lobbyOid); + tmgr.leaveTable(source, tableId); // there is normally no response. the client will see // themselves removed from the table they just left @@ -166,6 +166,35 @@ public class ParlorProvider } } + /** + * Looks up the place manager associated with the supplied lobby oid, + * casts it to a table lobby manager and obtains the associated table + * manager reference. + * + * @exception ServiceFailedException thrown if something goes wrong + * along the way like no place manager exists or the place manager + * that does exist doesn't implement table lobby manager. + */ + protected TableManager getTableManager (int lobbyOid) + throws ServiceFailedException + { + PlaceManager plmgr = CrowdServer.plreg.getPlaceManager(lobbyOid); + if (plmgr == null) { + Log.warning("No place manager exists from which to obtain " + + "table manager reference [ploid=" + lobbyOid + "]."); + throw new ServiceFailedException(INTERNAL_ERROR); + } + + // sanity check + if (!(plmgr instanceof TableManagerProvider)) { + Log.warning("Place manager not a table lobby manager " + + "[plmgr=" + plmgr + "]."); + throw new ServiceFailedException(INTERNAL_ERROR); + } + + return ((TableManagerProvider)plmgr).getTableManager(); + } + /** A reference to the parlor manager we're working with. */ protected ParlorManager _pmgr; } diff --git a/src/java/com/threerings/parlor/server/TableManager.java b/src/java/com/threerings/parlor/server/TableManager.java new file mode 100644 index 000000000..e624b2a84 --- /dev/null +++ b/src/java/com/threerings/parlor/server/TableManager.java @@ -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(); +} diff --git a/src/java/com/threerings/parlor/server/TableManagerProvider.java b/src/java/com/threerings/parlor/server/TableManagerProvider.java new file mode 100644 index 000000000..55012e81e --- /dev/null +++ b/src/java/com/threerings/parlor/server/TableManagerProvider.java @@ -0,0 +1,19 @@ +// +// $Id: TableManagerProvider.java,v 1.1 2001/10/23 23:47:02 mdb Exp $ + +package com.threerings.parlor.server; + +/** + * A place manager that wishes to provide table matchmaking services in + * its place needs to create a table manager and make it available by + * implementing this interface. The table invocation services and the + * table manager will take care of the rest. + */ +public interface TableManagerProvider +{ + /** + * Returns a reference to the table manager that is responsible for + * table management in this lobby. + */ + public TableManager getTableManager (); +}