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 (); +}