diff --git a/src/java/com/threerings/presents/client/Client.java b/src/java/com/threerings/presents/client/Client.java
index 40ce632cb..5938035df 100644
--- a/src/java/com/threerings/presents/client/Client.java
+++ b/src/java/com/threerings/presents/client/Client.java
@@ -1,5 +1,5 @@
//
-// $Id: Client.java,v 1.6 2001/06/11 17:44:03 mdb Exp $
+// $Id: Client.java,v 1.7 2001/07/19 05:56:20 mdb Exp $
package com.threerings.cocktail.cher.client;
@@ -141,6 +141,31 @@ public class Client
return (_comm != null) ? _comm.getDObjectManager() : null;
}
+ /**
+ * Every client has an associated ClientObject instance.
+ *
+ * @return the oid of the client object or -1 if we are not currently
+ * connected to the server.
+ */
+ public int getClientOid ()
+ {
+ return (_comm != null) ? _comm.getClientOid() : -1;
+ }
+
+ /**
+ * Returns the invocation manager associated with this session. This
+ * reference is only valid for the duration of the session and a new
+ * reference must be obtained if the client disconnects and reconnects
+ * to the server.
+ *
+ * @return the invocation manager in effect or null if we have no
+ * established connection to the server.
+ */
+ public InvocationManager getInvocationManager ()
+ {
+ return (_comm != null) ? _comm.getInvocationManager() : null;
+ }
+
/**
* Requests that this client connect and logon to the server with
* which it was previously configured.
diff --git a/src/java/com/threerings/presents/client/Communicator.java b/src/java/com/threerings/presents/client/Communicator.java
index 1bfdcd2e0..fbff0e83a 100644
--- a/src/java/com/threerings/presents/client/Communicator.java
+++ b/src/java/com/threerings/presents/client/Communicator.java
@@ -1,5 +1,5 @@
//
-// $Id: Communicator.java,v 1.7 2001/06/09 23:39:03 mdb Exp $
+// $Id: Communicator.java,v 1.8 2001/07/19 05:56:20 mdb Exp $
package com.threerings.cocktail.cher.client;
@@ -54,14 +54,31 @@ public class Communicator
/**
* Returns the distributed object manager in effect for this session.
* This instance is only valid while the client is connected to the
- * server. If we become disconnected and have to reconnect, a new omgr
- * instance should be obtained.
+ * server.
*/
public DObjectManager getDObjectManager ()
{
return _omgr;
}
+ /**
+ * Returns the oid of the client object associated with this session.
+ * It is only valid for the duration of the session.
+ */
+ public int getClientOid ()
+ {
+ return _cloid;
+ }
+
+ /**
+ * Returns the invocation manager associated with this session. This
+ * reference is only valid for the duration of the session.
+ */
+ public InvocationManager getInvocationManager ()
+ {
+ return _invmgr;
+ }
+
/**
* Logs on to the server and initiates our full-duplex message
* exchange.
@@ -155,10 +172,14 @@ public class Communicator
Log.info("Logon succeeded: " + data);
// extract bootstrap information
+ _cloid = data.clientOid;
// create our distributed object manager
_omgr = new ClientDObjectMgr(this, _client);
+ // create our invocation manager
+ _invmgr = new InvocationManager(_client, data.invOid);
+
// create a new writer thread and start it up
if (_writer != null) {
throw new RuntimeException("Writer already started!?");
@@ -481,4 +502,6 @@ public class Communicator
protected DataInputStream _din;
protected ClientDObjectMgr _omgr;
+ protected int _cloid;
+ protected InvocationManager _invmgr;
}
diff --git a/src/java/com/threerings/presents/client/InvocationDirector.java b/src/java/com/threerings/presents/client/InvocationDirector.java
new file mode 100644
index 000000000..516611bf5
--- /dev/null
+++ b/src/java/com/threerings/presents/client/InvocationDirector.java
@@ -0,0 +1,208 @@
+//
+// $Id: InvocationDirector.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
+
+package com.threerings.cocktail.cher.client;
+
+import java.lang.reflect.Method;
+import java.util.HashMap;
+
+import com.threerings.cocktail.cher.Log;
+import com.threerings.cocktail.cher.data.*;
+import com.threerings.cocktail.cher.dobj.*;
+import com.threerings.cocktail.cher.util.IntMap;
+
+/**
+ * The invocation services provide client to server invocations (service
+ * requests) and server to client invocations (responses and
+ * notifications). Via this mechanism, the client can make requests of the
+ * server, be notified of its response and the server can asynchronously
+ * invoke code on the client.
+ *
+ *
Invocations are like remote procedure calls in that they are named + * and take arguments. They are simple in that the arguments can only be + * of a small set of supported types (the set of distributed object field + * types) and there is no special facility provided for referencing + * non-local objects (it is assumed that the distributed object facility + * will already be in use for any objects that should be shared). + * + *
The client invocation manager delivers invocation requests to the
+ * server invocation manager and maps the responses back to the proper
+ * response target objects when they arrive. It also maintains the mapping
+ * of invocation receivers that can receive asynchronous invocation
+ * notifications at any time from the server.
+ */
+public class InvocationManager
+ implements Subscriber
+{
+ /**
+ * Constructs a new invocation manager with the specified invocation
+ * manager oid. It will obtain its distributed object manager and
+ * client object references from the supplied client instance. The
+ * invocation manager oid is the oid of the object on the server to
+ * which to deliver invocation requests.
+ */
+ public InvocationManager (Client client, int imoid)
+ {
+ _omgr = client.getDObjectManager();
+ _imoid = imoid;
+
+ // add ourselves as a subscriber to the client object
+ _omgr.subscribeToObject(client.getClientOid(), this);
+ }
+
+ /**
+ * Sends an invocation request to the server. If a response target is
+ * supplied, the response will be delivered to that object by calling
+ * a member function on it whose name is derived from the invocation
+ * name. For example, if the caller invoked a procedure named
+ * SwitchLocation, the response would be delivered via a
+ * call to the handleSwitchLocationResponse method on the
+ * response target object. The signature of that method would be
+ * defined by the arguments provided in the response message.
+ *
+ * @param name the name of the invocation.
+ * @param args the arguments of the invocation.
+ * @param rsptarget the object that will receive the response, or null
+ * if no response is desired.
+ */
+ public void invoke (String name, Object[] args, Object rsptarget)
+ {
+ int invid = nextInvocationId();
+
+ // we need an args array for a message that can contain the
+ // invocation name, an invocation id and the invocation arguments
+ Object[] iargs = new Object[args.length+2];
+ System.arraycopy(args, 0, iargs, 2, args.length);
+ iargs[0] = name;
+ iargs[1] = new Integer(invid);
+
+ // create a message event on the invocation manager object
+ MessageEvent event = new MessageEvent(
+ _imoid, InvocationObject.MESSAGE_NAME, iargs);
+
+ // if we have a response target, register that for later receipt
+ // of the response
+ if (rsptarget != null) {
+ _targets.put(invid, new Response(name, rsptarget));
+ }
+
+ // and finally ship off the invocation message
+ _omgr.postEvent(event);
+ }
+
+ public void objectAvailable (DObject object)
+ {
+ // nothing doing
+ }
+
+ public void requestFailed (int oid, ObjectAccessException cause)
+ {
+ // nothing doing
+ }
+
+ /**
+ * Process incoming message requests on user object.
+ */
+ public boolean handleEvent (DEvent event, DObject target)
+ {
+ // we only care about message events
+ if (!(event instanceof MessageEvent)) {
+ return true;
+ }
+
+ MessageEvent mevt = (MessageEvent)event;
+
+ // and only those of proper name
+ if (!mevt.getName().equals(InvocationObject.MESSAGE_NAME)) {
+ return true;
+ }
+
+ // we've got an invocation response, so we extract the args and
+ // process it
+ Object[] args = mevt.getArgs();
+ int invid = ((Integer)args[0]).intValue();
+ String name = (String)args[1];
+
+ Response rsp = (Response)_targets.get(invid);
+ if (rsp == null) {
+ Log.warning("No target for invocation response " +
+ "[rsp=" + mevt + "].");
+ return true;
+ }
+
+ // prune the method arguments from the full message arguments
+ Object[] rargs = new Object[args.length-2];
+ System.arraycopy(args, 2, rargs, 0, rargs.length);
+
+ // and invoke the response method
+ Method rspmeth = getResponseMethod(name, target, rargs);
+ if (rspmeth != null) {
+ try {
+ rspmeth.invoke(target, rargs);
+ } catch (Exception e) {
+ Log.warning("Error invoking response target method " +
+ "[target=" + target + ", method=" + rspmeth +
+ ", error=" + e + "].");
+ }
+ }
+
+ return true;
+ }
+
+ protected Method getResponseMethod (String name, Object target,
+ Object[] args)
+ {
+ Class tclass = target.getClass();
+ String key = tclass.getName() + ":" + name;
+ Method method = (Method)_methcache.get(key);
+
+ if (method == null) {
+ String methname = "handle" + name;
+ method = findMethod(tclass, name);
+ if (method == null) {
+ Log.warning("Unable to locate method on response target " +
+ "[rtclass=" + tclass.getName() +
+ ", method=" + methname + "].");
+ return null;
+ }
+ _methcache.put(key, method);
+ }
+
+ return method;
+ }
+
+ protected static Method findMethod (Class clazz, String name)
+ {
+ Method[] methods = clazz.getMethods();
+ for (int i = 0; i < methods.length; i++) {
+ if (methods[i].getName().equals(name)) {
+ return methods[i];
+ }
+ }
+ return null;
+ }
+
+ protected synchronized int nextInvocationId ()
+ {
+ return _invocationId++;
+ }
+
+ protected static class Response
+ {
+ public String name;
+ public Object target;
+ public Response (String name, Object target)
+ {
+ this.name = name;
+ this.target = target;
+ }
+ }
+
+ protected DObjectManager _omgr;
+ protected int _imoid;
+ protected ClientObject _clobj;
+
+ protected int _invocationId;
+ protected IntMap _targets = new IntMap();
+ protected HashMap _methcache = new HashMap();
+}
diff --git a/src/java/com/threerings/presents/data/ClientObject.java b/src/java/com/threerings/presents/data/ClientObject.java
new file mode 100644
index 000000000..e5d8ef6d0
--- /dev/null
+++ b/src/java/com/threerings/presents/data/ClientObject.java
@@ -0,0 +1,16 @@
+//
+// $Id: ClientObject.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
+
+package com.threerings.cocktail.cher.data;
+
+import com.threerings.cocktail.cher.dobj.DObject;
+
+/**
+ * Every client in the system has an associated client object to which
+ * only they subscribe. The client object can be used to deliver messages
+ * solely to a particular client as well as to publish client-specific
+ * data.
+ */
+public class ClientObject extends DObject
+{
+}
diff --git a/src/java/com/threerings/presents/data/InvocationObject.java b/src/java/com/threerings/presents/data/InvocationObject.java
new file mode 100644
index 000000000..63eff4622
--- /dev/null
+++ b/src/java/com/threerings/presents/data/InvocationObject.java
@@ -0,0 +1,21 @@
+//
+// $Id: InvocationObject.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
+
+package com.threerings.cocktail.cher.data;
+
+import com.threerings.cocktail.cher.dobj.DObject;
+
+/**
+ * A single invocation object is created by the server invocation manager
+ * and is used to receive invocation request messages from the client. The
+ * server presently delivers invocation messages to the client via the
+ * client object.
+ */
+public class InvocationObject extends DObject
+{
+ /**
+ * This constant is used to identify messages on both ends of the
+ * invocation services.
+ */
+ public static final String MESSAGE_NAME = "invoke";
+}
diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java
index 07fb8ef15..c19053cec 100644
--- a/src/java/com/threerings/presents/dobj/DObject.java
+++ b/src/java/com/threerings/presents/dobj/DObject.java
@@ -1,5 +1,5 @@
//
-// $Id: DObject.java,v 1.11 2001/06/13 05:17:55 mdb Exp $
+// $Id: DObject.java,v 1.12 2001/07/19 05:56:20 mdb Exp $
package com.threerings.cocktail.cher.dobj;
@@ -235,6 +235,19 @@ public class DObject
_oid = oid;
}
+ /**
+ * Called by derived instances when an attribute setter method was
+ * called.
+ */
+ protected void requestAttributeChange (String name, Object value)
+ {
+ // generate an attribute changed event
+ AttributeChangedEvent event = new AttributeChangedEvent(
+ _oid, name, value);
+ // and dispatch it to our dobjmgr
+ _mgr.postEvent(event);
+ }
+
protected int _oid;
protected DObjectManager _mgr;
protected ArrayList _subscribers = new ArrayList();
diff --git a/src/java/com/threerings/presents/dobj/MessageEvent.java b/src/java/com/threerings/presents/dobj/MessageEvent.java
new file mode 100644
index 000000000..2e55c0055
--- /dev/null
+++ b/src/java/com/threerings/presents/dobj/MessageEvent.java
@@ -0,0 +1,120 @@
+//
+// $Id: MessageEvent.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
+
+package com.threerings.cocktail.cher.dobj;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+import com.threerings.cocktail.cher.dobj.io.ValueMarshaller;
+
+/**
+ * A message event is used to dispatch a message to all subscribers of a
+ * distributed object without actually changing any of the fields of the
+ * object. A message has a name, by which different subscribers of the
+ * same object can distinguish their different messages, and an array of
+ * arguments by which any contents of the message can be delivered.
+ *
+ * @see DObjectManager.postEvent
+ */
+public class MessageEvent extends TypedEvent
+{
+ /** The typed object code for this event. */
+ public static final short TYPE = TYPE_BASE + 3;
+
+ /**
+ * Constructs a new message event on the specified target object with
+ * the supplied name and arguments.
+ *
+ * @param targetOid the object id of the object whose attribute has
+ * changed.
+ * @param name the name of the message event.
+ * @param args the arguments for this message. This array should
+ * contain only values of valid distributed object types.
+ */
+ public MessageEvent (int targetOid, String name, Object[] args)
+ {
+ super(targetOid);
+ _name = name;
+ _args = args;
+ }
+
+ /**
+ * Constructs a blank instance of this event in preparation for
+ * unserialization from the network.
+ */
+ public MessageEvent ()
+ {
+ }
+
+ /**
+ * Returns the name of the message.
+ */
+ public String getName ()
+ {
+ return _name;
+ }
+
+ /**
+ * Returns the arguments to this message.
+ */
+ public Object[] getArgs ()
+ {
+ return _args;
+ }
+
+ /**
+ * Applies this attribute change to the object.
+ */
+ public boolean applyToObject (DObject target)
+ throws ObjectAccessException
+ {
+ // nothing to do here
+ return true;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ out.writeUTF(_name);
+ if (_args != null) {
+ out.writeInt(_args.length);
+ for (int i = 0; i < _args.length; i++) {
+ ValueMarshaller.writeTo(out, _args[i]);
+ }
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ _name = in.readUTF();
+ int args = in.readInt();
+ if (args > 0) {
+ _args = new Object[args];
+ for (int i = 0; i < args; i++) {
+ _args[i] = ValueMarshaller.readFrom(in);
+ }
+ }
+ }
+
+ public String toString ()
+ {
+ return "[MSG:targetOid=" + _toid + ", name=" + _name +
+ ", args=" + _args + "]";
+ }
+
+ protected String _name;
+ protected Object[] _args;
+}
diff --git a/src/java/com/threerings/presents/net/AuthResponseData.java b/src/java/com/threerings/presents/net/AuthResponseData.java
index 3bc218f4b..7aab65303 100644
--- a/src/java/com/threerings/presents/net/AuthResponseData.java
+++ b/src/java/com/threerings/presents/net/AuthResponseData.java
@@ -1,5 +1,5 @@
//
-// $Id: AuthResponseData.java,v 1.4 2001/05/30 23:58:31 mdb Exp $
+// $Id: AuthResponseData.java,v 1.5 2001/07/19 05:56:20 mdb Exp $
package com.threerings.cocktail.cher.net;
@@ -23,6 +23,12 @@ public class AuthResponseData extends DObject
*/
public String code;
+ /** The oid of this client's associated distributed object. */
+ public int clientOid;
+
+ /** The oid to which to send invocation requests. */
+ public int invOid;
+
public String toString ()
{
return "[code=" + code + "]";
diff --git a/src/java/com/threerings/presents/server/InvocationManager.java b/src/java/com/threerings/presents/server/InvocationManager.java
new file mode 100644
index 000000000..2e2b26d68
--- /dev/null
+++ b/src/java/com/threerings/presents/server/InvocationManager.java
@@ -0,0 +1,77 @@
+//
+// $Id: InvocationManager.java,v 1.1 2001/07/19 05:56:20 mdb Exp $
+
+package com.threerings.cocktail.cher.server;
+
+import com.threerings.cocktail.cher.Log;
+import com.threerings.cocktail.cher.dobj.*;
+
+/**
+ * The invocation services provide client to server invocations (service
+ * requests) and server to client invocations (responses and
+ * notifications). Via this mechanism, the client can make requests of the
+ * server, be notified of its response and the server can asynchronously
+ * invoke code on the client.
+ *
+ *
Invocations are like remote procedure calls in that they are named + * and take arguments. They are simple in that the arguments can only be + * of a small set of supported types (the set of distributed object field + * types) and there is no special facility provided for referencing + * non-local objects (it is assumed that the distributed object facility + * will already be in use for any objects that should be shared). + * + *
The server invocation manager listens for invocation requests from + * the client and passes them on to the invocation provider registered for + * the requested invocation type. It also provides a mechanism by which + * responses and asynchronous notification invocations can be delivered to + * the client. + */ +public class InvocationManager + implements Subscriber +{ + public InvocationManager (DObjectManager omgr) + { + _omgr = omgr; + + // create the object on which we'll listen for invocation requests + omgr.createObject(DObject.class, this, true); + } + + public int getOid () + { + return _invoid; + } + + public void objectAvailable (DObject object) + { + // this must be our invocation object + _invoid = object.getOid(); + } + + public void requestFailed (int oid, ObjectAccessException cause) + { + // if for some reason we were unable to create our invocation + // object, we'll end up here + Log.warning("Unable to create invocation object " + + "[reason=" + cause + "]."); + _invoid = -1; + } + + public boolean handleEvent (DEvent event, DObject target) + { + // we shouldn't be getting non-message events, but check just to + // be sure + if (!(event instanceof MessageEvent)) { + Log.warning("Got non-message event!? [evt=" + event + "]."); + return true; + } + + MessageEvent mevt = (MessageEvent)event; + // make sure the name is proper just for sanities sake + + return true; + } + + protected DObjectManager _omgr; + protected int _invoid; +} diff --git a/tests/src/java/com/threerings/presents/server/TestObject.dobj b/tests/src/java/com/threerings/presents/server/TestObject.dobj index 03c41618d..5e8ac4487 100644 --- a/tests/src/java/com/threerings/presents/server/TestObject.dobj +++ b/tests/src/java/com/threerings/presents/server/TestObject.dobj @@ -1,5 +1,5 @@ // -// $Id: TestObject.dobj,v 1.1 2001/06/09 23:39:42 mdb Exp $ +// $Id: TestObject.dobj,v 1.2 2001/07/19 05:56:20 mdb Exp $ package com.threerings.cocktail.cher.server.test; @@ -21,20 +21,12 @@ public class TestObject extends DObject public void setFoo (int foo) { - // generate an attribute changed event - AttributeChangedEvent event = new AttributeChangedEvent( - _oid, FOO, new Integer(foo)); - // and dispatch it to our dobjmgr - _mgr.postEvent(event); + requestAttributeChange(FOO, new Integer(foo)); } public void setBar (String bar) { - // generate an attribute changed event - AttributeChangedEvent event = new AttributeChangedEvent( - _oid, BAR, bar); - // and dispatch it to our dobjmgr - _mgr.postEvent(event); + requestAttributeChange(BAR, bar); } public String toString ()