From 23ccfde6c85a8eb05f804095e0acee028c98e696 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Mon, 1 Oct 2001 05:07:13 +0000 Subject: [PATCH] More progress on parlor services (mostly invitation handling). git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@365 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../parlor/client/InvitationHandler.java | 33 +++ .../client/InvitationResponseObserver.java | 11 +- .../parlor/client/ParlorDirector.java | 245 +++++++++++++++--- .../parlor/client/ParlorService.java | 94 +++++-- .../parlor/server/ParlorProvider.java | 20 +- 5 files changed, 344 insertions(+), 59 deletions(-) create mode 100644 src/java/com/threerings/parlor/client/InvitationHandler.java diff --git a/src/java/com/threerings/parlor/client/InvitationHandler.java b/src/java/com/threerings/parlor/client/InvitationHandler.java new file mode 100644 index 000000000..e9920bb97 --- /dev/null +++ b/src/java/com/threerings/parlor/client/InvitationHandler.java @@ -0,0 +1,33 @@ +// +// $Id: InvitationHandler.java,v 1.1 2001/10/01 05:07:13 mdb Exp $ + +package com.threerings.parlor.client; + +import com.threerings.parlor.data.GameConfig; + +/** + * A client entity that wishes to handle invitations received by other + * clients should implement this interface and register itself with the + * parlor director. It will subsequently be notified of any incoming + * invitations. It is also responsible for handling cancelled invitations. + */ +public interface InvitationHandler +{ + /** + * Called when an invitation is received from another player. + * + * @param inviteId this invitation's unique id. + * @param inviter the username of the user that sent the invitation. + * @param config the configuration of the game to which we are being + * invited. + */ + public void invitationReceived (int inviteId, String inviter, + GameConfig config); + + /** + * Called when an invitation is cancelled by the inviting player. + * + * @param inviteId this invitation's unique id. + */ + public void invitationCancelled (int inviteId); +} diff --git a/src/java/com/threerings/parlor/client/InvitationResponseObserver.java b/src/java/com/threerings/parlor/client/InvitationResponseObserver.java index 5611bfb74..59b3f08a3 100644 --- a/src/java/com/threerings/parlor/client/InvitationResponseObserver.java +++ b/src/java/com/threerings/parlor/client/InvitationResponseObserver.java @@ -1,5 +1,5 @@ // -// $Id: InvitationResponseObserver.java,v 1.2 2001/10/01 02:57:37 mdb Exp $ +// $Id: InvitationResponseObserver.java,v 1.3 2001/10/01 05:07:13 mdb Exp $ package com.threerings.parlor.client; @@ -12,16 +12,21 @@ import com.threerings.parlor.data.GameConfig; * is adjusted by the invited player and proposed back to the inviting * player. */ -public interface InvitationObserver +public interface InvitationResponseObserver { /** * Called if the invitation was accepted. + * + * @param inviteId the unique id of the invitation for which we + * received a response. */ public void invitationAccepted (int inviteId); /** * Called if the invitation was rejected. * + * @param inviteId the unique id of the invitation for which we + * received a response. * @param message a message provided by the rejecting user explaining * the reason for their rejection, or the empty string if no message * was provided. @@ -32,6 +37,8 @@ public interface InvitationObserver * Called if the invitation was countered with an alternate game * configuration. * + * @param inviteId the unique id of the invitation for which we + * received a response. * @param config the game configuration proposed by the invited * player. */ diff --git a/src/java/com/threerings/parlor/client/ParlorDirector.java b/src/java/com/threerings/parlor/client/ParlorDirector.java index 8bceafd3e..1e5701836 100644 --- a/src/java/com/threerings/parlor/client/ParlorDirector.java +++ b/src/java/com/threerings/parlor/client/ParlorDirector.java @@ -1,8 +1,10 @@ // -// $Id: ParlorDirector.java,v 1.1 2001/10/01 02:56:35 mdb Exp $ +// $Id: ParlorDirector.java,v 1.2 2001/10/01 05:07:13 mdb Exp $ package com.threerings.parlor.client; +import java.util.ArrayList; +import java.util.Iterator; import com.samskivert.util.HashIntMap; import com.threerings.parlor.Log; @@ -18,6 +20,32 @@ import com.threerings.parlor.util.ParlorContext; */ public class ParlorDirector { + /** + * Constructs a parlor director and provides it with the parlor + * context that it can use to access the client services that it needs + * to provide its own services. Only one parlor director should be + * active in the client at one time and it should be made available + * via the parlor context. + * + * @param ctx the parlor context in use by the client. + */ + public ParlorDirector (ParlorContext ctx) + { + _ctx = ctx; + } + + /** + * Sets the invitation handler, which is the entity that will be + * notified when we receive incoming invitation notifications and when + * invitations have been cancelled. + * + * @param handler our new invitation handler. + */ + public void setInvitationHandler (InvitationHandler handler) + { + _handler = handler; + } + /** * Requests that the named user be invited to a game described by the * supplied game config. @@ -25,29 +53,89 @@ public class ParlorDirector * @param invitee the user to invite. * @param config the configuration of the game to which the user is * being invited. - * @param observer the entity that wants to know about accepted, - * rejected or countered invitations. + * @param observer the entity that will be notified if this invitation + * is accepted, rejected or countered. * * @return a unique id associated with this invitation that can be * used to discern between various outstanding invitations by the * invitation observer. */ - public int invite (ParlorContext ctx, String invitee, - GameConfig config, InvitationObserver observer) + public int invite (String invitee, GameConfig config, + InvitationResponseObserver observer) { // generate the invocation service request int invid = ParlorService.invite( - ctx.getClient(), invitee, config, this); + _ctx.getClient(), invitee, config, this); // create an invitation record and put it in the submitted table Invitation invite = new Invitation(invitee, config, observer); _submittedInvites.put(invid, invite); - return invite.localId; + return invite.inviteId; } - public void counter (ParlorContext ctx, int inviteId, - GameConfig config, InvitationObserver observer) + /** + * Counters a received invitation with an invitation with different + * game configuration parameters. + * + * @param inviteId the id of the received invitation. + * @param config the updated game configuration. + * @param observer the entity that will be notified if this + * counter-invitation is accepted, rejected or countered. + */ + public void counter (int inviteId, GameConfig config, + InvitationResponseObserver observer) { + // look up the invitation record (oh the two separate key spaces + // humanity) + Invitation invite = getInviteByLocalId(inviteId); + if (invite == null) { + // complain if we didn't find a matching invitation + Log.warning("Received request to counter non-existent " + + "invitation [inviteId=" + inviteId + + ", config=" + config + "]."); + return; + } + + // update the invitation record with the observer (who will + // eventually be hearing back from the other client about their + // counter-invitation) + invite.observer = observer; + + // generate the invocation service request + ParlorService.respond(_ctx.getClient(), invite.remoteId, + ParlorService.INVITATION_COUNTERED, + config, this); + } + + /** + * Issues a request to cancel an outstanding invitation. + * + * @param inviteId the id of the invitation to cancel. + */ + public void cancel (int inviteId) + { + // look up the invitation record (oh the two separate key spaces + // humanity) + Invitation invite = getInviteByLocalId(inviteId); + if (invite == null) { + // complain if we didn't find a matching invitation + Log.warning("Received request to cancel non-existent " + + "invitation [inviteId=" + inviteId + "]."); + return; + } + + // if the invitation has not yet been acknowleged by the server, + // we make a note that it should be cancelled when we do receive + // the acknowlegement + if (invite.remoteId == -1) { + invite.cancelled = true; + + } else { + // otherwise, generate the invocation service request + ParlorService.cancel(_ctx.getClient(), invite.remoteId, this); + // and remove it from the pending table + _pendingInvites.remove(invite.remoteId); + } } /** @@ -63,23 +151,59 @@ public class ParlorDirector public void handleInviteNotification ( int remoteId, String inviter, GameConfig config) { + // create an invitation record for this invitation + Invitation invite = new Invitation(inviter, config, null); + invite.remoteId = remoteId; + + // put it in the pending invitations table + _pendingInvites.put(remoteId, invite); + + try { + // notify the invitation handler of the incoming invitation + _handler.invitationReceived(invite.inviteId, inviter, config); + + } catch (Exception e) { + Log.warning("Invitation handler choked on invite " + + "notification [inviteId=" + invite.inviteId + + ", inviter=" + inviter + + ", config=" + config + "]."); + Log.logStackTrace(e); + } } /** * Called by the invocation services when another user has responded - * to our invitation. + * to our invitation by either accepting, rejecting or countering it. * * @param remoteId the unique indentifier for the invitation. - * @param code the response code {@link + * @param code the response code, either {@link * ParlorService#INVITATION_ACCEPTED} or {@link - * ParlorService#INVITATION_REJECTED}. - * @param message in the case of a rejected invitation, a message - * provided by the invited user explaining the rejection. The empty - * string if no explanation was provided. + * ParlorService#INVITATION_REJECTED} or {@link + * ParlorService#INVITATION_COUNTERED}. + * @param arg in the case of a rejected invitation, a string + * containing a message provided by the invited user explaining the + * reason for rejection (the empty string if no explanation was + * provided). In the case of a countered invitation, a new game config + * object with the modified game configuration. */ - public void handleResponseNotification ( - int remoteId, int code, String message) + public void handleRespondInviteNotification ( + int remoteId, int code, Object arg) { + // look up the invitation record for this invitation + Invitation invite = (Invitation)_pendingInvites.get(remoteId); + if (invite == null) { + Log.warning("Have no record of invitation for which we " + + "received a response?! [remoteId=" + remoteId + + ", code=" + code + ", arg=" + arg + "]."); + return; + } + + // make sure we have an observer to notify + if (invite.observer == null) { + Log.warning("No observer registered for invitation " + + invite + "."); + return; + } } /** @@ -90,7 +214,7 @@ public class ParlorDirector * @param config the configuration information for the game to which * we've been invited. */ - public void handleInviteNotification (String inviter, GameConfig config) + public void handleCancelInviteNotification (String inviter, GameConfig config) { } @@ -104,7 +228,7 @@ public class ParlorDirector { // remove the invitation record from the submitted table and put // it in the pending table - Invitation invite = (Invitation)_submittedInvites.get(invid); + Invitation invite = (Invitation)_submittedInvites.remove(invid); if (invite == null) { Log.warning("Received accepted notification for non-existent " + "invitation request!? [invid=" + invid + @@ -115,8 +239,16 @@ public class ParlorDirector // now that we know the invitation's unique id, keep track of it invite.remoteId = remoteId; - // and put it in the new table - _pendingInvites.put(remoteId, invite); + // if the invitation was cancelled before we heard back about it, + // we need to send off a cancellation request now + if (invite.cancelled) { + // generate the invocation service request to cancel it + ParlorService.cancel(_ctx.getClient(), invite.remoteId, this); + + } else { + // otherwise, put it in the new table + _pendingInvites.put(remoteId, invite); + } } /** @@ -130,6 +262,10 @@ public class ParlorDirector { } + /** + * The invitation class is used to track information related to + * outstanding invitations generated by or targeted to this client. + */ protected static class Invitation { /** A unique id for this invitation assigned on the client which @@ -139,37 +275,82 @@ public class ParlorDirector * acknowlegedment from the server and we need to provide the * caller with some sort of unique id at the time the request is * generated. */ - public int localId = _localInvitationId++; + public int inviteId = _localInvitationId++; /** The unique id for this invitation (as assigned by the * server). This is -1 until we receive an acknowledgement from * the server that our invitation was delivered. */ public int remoteId = -1; - /** The name of the user that was invited. */ - public String invitee; + /** The name of the other user involved in this invitation. */ + public String opponent; /** The configuration of the game to be created. */ public GameConfig config; - /** The invitation observer that created this invitation. */ - public InvitationObserver observer; + /** The entity to notify when we receive a response for this + * invitation. */ + public InvitationResponseObserver observer; - /** A flag indicating that we were requested to abort this + /** A flag indicating that we were requested to cancel this * invitation before we even heard back with an acknowledgement * that it was received by the server. */ - public boolean aborted = false; + public boolean cancelled = false; - /** Constructs a new invitation request. */ - public Invitation (String invitee, GameConfig config, - InvitationObserver observer) + /** Constructs a new invitation record. */ + public Invitation (String opponent, GameConfig config, + InvitationResponseObserver observer) { - this.invitee = invitee; + this.opponent = opponent; this.config = config; this.observer = observer; } + + /** Returns a string representation of this invitation record. */ + public String toString () + { + return "[inviteId=" + inviteId + ", remoteId=" + remoteId + + ", opponent=" + opponent + ", config=" + config + + ", observer=" + observer + ", cancelled=" + cancelled + "]"; + } } + /** + * Looks up an invitation by its local unique identifier. Oh the dual + * uid space humanity! + */ + protected Invitation getInviteByLocalId (int inviteId) + { + // first search the pending invites which is where we're most + // likely to find it + Iterator iter = _pendingInvites.values().iterator(); + while (iter.hasNext()) { + Invitation match = (Invitation)iter.next(); + if (match.inviteId == inviteId) { + return match; + } + } + + // if that didn't work, look in the submitted invites + iter = _submittedInvites.values().iterator(); + while (iter.hasNext()) { + Invitation match = (Invitation)iter.next(); + if (match.inviteId == inviteId) { + return match; + } + } + + // il n'existe pas + return null; + } + + /** An active parlor context. */ + protected ParlorContext _ctx; + + /** The entity that has registered itself to handle incoming + * invitation notifications. */ + protected InvitationHandler _handler; + /** A table of submitted (but not acknowledged) invitation requests, * keyed on invocation request id. */ protected HashIntMap _submittedInvites = new HashIntMap(); diff --git a/src/java/com/threerings/parlor/client/ParlorService.java b/src/java/com/threerings/parlor/client/ParlorService.java index 8c3bf302c..c55475dfc 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.1 2001/10/01 02:56:35 mdb Exp $ +// $Id: ParlorService.java,v 1.2 2001/10/01 05:07:13 mdb Exp $ package com.threerings.parlor.client; @@ -24,17 +24,17 @@ public class ParlorService /** The module name for the parlor services. */ public static final String MODULE = "parlor"; - /** The message identifier for an invitation creation request. */ - public static final String INVITE_REQUEST = "Invite"; + /** The message identifier for an invitation creation request or + * notification. */ + public static final String INVITE_ID = "Invite"; - /** The message identifier for an invitation notification. */ - public static final String INVITE_NOTIFICATION = "Invite"; + /** The message identifier for an invitation cancellation request or + * notification. */ + public static final String CANCEL_INVITE_ID = "CancelInvite"; - /** The message identifier for an invitation response request. */ - public static final String RESPONSE_REQUEST = "Response"; - - /** The message identifier for an invitation response notification. */ - public static final String RESPONSE_NOTIFICATION = "Response"; + /** The message identifier for an invitation response request or + * notification. */ + public static final String RESPOND_INVITE_ID = "RespondInvite"; /** The response code for an accepted invitation. */ public static final int INVITATION_ACCEPTED = 0; @@ -42,17 +42,8 @@ public class ParlorService /** The response code for a rejected invitation. */ public static final int INVITATION_REJECTED = 1; - /** The message identifier for an counter invitation request. */ - public static final String COUNTER_REQUEST = "Counter"; - - /** The message identifier for an countered invitation notification. */ - public static final String COUNTER_NOTIFICATION = "Counter"; - - /** The message identifier for an rescind invitation request. */ - public static final String RESCIND_REQUEST = "Rescind"; - - /** The message identifier for an rescinded invitation notification. */ - public static final String RESCIND_NOTIFICATION = "Rescind"; + /** The response code for a countered invitation. */ + public static final int INVITATION_COUNTERED = 2; /** * You probably don't want to call this directly, but want to generate @@ -65,8 +56,8 @@ public class ParlorService * @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 parlor director reference that will receive and - * process the response. + * @param rsptarget the object reference that will receive and process + * the response. * * @return the invocation request id of the generated request. */ @@ -77,6 +68,61 @@ public class ParlorService Object[] args = new Object[] { invitee, config }; Log.info("Sending invite request [to=" + invitee + ", cfg=" + config + "]."); - return invmgr.invoke(MODULE, INVITE_REQUEST, args, rsptarget); + return invmgr.invoke(MODULE, INVITE_ID, args, rsptarget); + } + + /** + * You probably don't want to call this directly, but want to call + * {@link ParlorDirector#counter}. Requests that a counter-invitation be + * delivered with the specified parameters. + * + * @param client a connected, operational client instance. + * @param inviteId the unique id previously assigned by the server to + * this invitation. + * @param code the response code to use in responding to the + * invitation. + * @param arg the argument associated with the response (a string + * message from the player explaining why the response was refused in + * 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 + * 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) + { + InvocationManager invmgr = client.getInvocationManager(); + 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 invmgr.invoke(MODULE, RESPOND_INVITE_ID, args, rsptarget); + } + + /** + * You probably don't want to call this directly, but want to call + * {@link ParlorDirector#cancel}. Requests that an outstanding + * invitation be cancelled. + * + * @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 + * the response. + * + * @return the invocation request id of the generated request. + */ + public static int cancel (Client client, int inviteId, Object rsptarget) + { + InvocationManager invmgr = client.getInvocationManager(); + Object[] args = new Object[] { new Integer(inviteId) }; + Log.info("Sending invitation cancellation " + + "[inviteId=" + inviteId + "]."); + return invmgr.invoke(MODULE, CANCEL_INVITE_ID, args, rsptarget); } } diff --git a/src/java/com/threerings/parlor/server/ParlorProvider.java b/src/java/com/threerings/parlor/server/ParlorProvider.java index 1c5a1600d..f8405d153 100644 --- a/src/java/com/threerings/parlor/server/ParlorProvider.java +++ b/src/java/com/threerings/parlor/server/ParlorProvider.java @@ -1,5 +1,5 @@ // -// $Id: ParlorProvider.java,v 1.1 2001/10/01 02:56:35 mdb Exp $ +// $Id: ParlorProvider.java,v 1.2 2001/10/01 05:07:13 mdb Exp $ package com.threerings.parlor.server; @@ -38,6 +38,24 @@ public class ParlorProvider extends InvocationProvider { } + /** + * Processes a request from the client to respond to an outstanding + * invitation by accepting, refusing, or countering it. + */ + public void handleRepsondInviteRequest ( + BodyObject source, int invid, int inviteId, int code, Object arg) + { + } + + /** + * Processes a request from the client to cancel an outstanding + * invitation. + */ + public void handleCancelInviteRequest ( + BodyObject source, int invid, int inviteId) + { + } + /** A reference to the parlor manager we're working with. */ protected ParlorManager _pmgr; }