diff --git a/src/java/com/threerings/presents/client/ClientDObjectMgr.java b/src/java/com/threerings/presents/client/ClientDObjectMgr.java
index de9fed78f..be8be5772 100644
--- a/src/java/com/threerings/presents/client/ClientDObjectMgr.java
+++ b/src/java/com/threerings/presents/client/ClientDObjectMgr.java
@@ -1,5 +1,5 @@
//
-// $Id: ClientDObjectMgr.java,v 1.4 2001/07/19 07:09:16 mdb Exp $
+// $Id: ClientDObjectMgr.java,v 1.5 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.client;
@@ -75,6 +75,13 @@ public class ClientDObjectMgr
_comm.postMessage(new ForwardEventRequest(tevent));
}
+ // inherit documentation from the interface
+ public void destroyObject (int oid)
+ {
+ // forward an object destroyed event to the server
+ postEvent(new ObjectDestroyedEvent(oid));
+ }
+
// inherit documentation from the interface
public void removedLastSubscriber (DObject obj)
{
@@ -154,8 +161,29 @@ public class ClientDObjectMgr
return;
}
- // have the object pass this event on to its subscribers
- target.notifySubscribers(event);
+ try {
+ // apply the event to the object
+ boolean notify = event.applyToObject(target);
+
+ // if this is an object destroyed event, we need to remove the
+ // object from our object table
+ if (event instanceof ObjectDestroyedEvent) {
+ Log.info("Uncaching destroyed object " +
+ "[oid=" + target.getOid() + "].");
+ _ocache.remove(target.getOid());
+ }
+
+ // have the object pass this event on to its subscribers if
+ // desired
+ if (notify) {
+ target.notifySubscribers(event);
+ }
+
+ } catch (Exception e) {
+ Log.warning("Failure processing event [event=" + event +
+ ", target=" + target + "].");
+ Log.logStackTrace(e);
+ }
}
/**
@@ -193,7 +221,19 @@ public class ClientDObjectMgr
*/
protected void notifyFailure (int oid)
{
- Log.info("Get failed: " + oid);
+ // let the penders know that the object is not available
+ PendingRequest req = (PendingRequest)_penders.remove(oid);
+ if (req == null) {
+ Log.warning("Failed to get object, but no one cares?! " +
+ "[oid=" + oid + "].");
+ return;
+ }
+
+ for (int i = 0; i < req.targets.size(); i++) {
+ Subscriber target = (Subscriber)req.targets.get(i);
+ // and let them know that the object is in
+ target.requestFailed(oid, null);
+ }
}
/**
diff --git a/src/java/com/threerings/presents/client/Communicator.java b/src/java/com/threerings/presents/client/Communicator.java
index a89316eb1..867bf43bc 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.12 2001/08/03 02:11:20 mdb Exp $
+// $Id: Communicator.java,v 1.13 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.client;
@@ -91,6 +91,9 @@ public class Communicator
return;
}
+ // post a logoff message
+ postMessage(new LogoffRequest());
+
// let our reader and writer know that it's time to go
if (_reader != null) {
// if logoff() is being called by the client as part of a
@@ -110,28 +113,12 @@ public class Communicator
if (_writer != null) {
// shutting down the writer thread is simpler because we can
// post a termination message on the queue and be sure that it
- // will receive it. we do run the risk that it is in the
- // middle of trying to send a message when we close the
- // socket, but in theory the send will fail, it will complain
- // and then it will cleanly exit. if we were uber paranoid
- // about JVMs misbehaving on simultaneous close()/write(), we
- // could wait here for the writer thread to exit, but that
- // makes me even more nervous because I know that some JVMs
- // don't handle Thread.join() properly (hopefully no one is
- // still using those JVMs but one can never be sure)
+ // will receive it. when the writer thread has delivered our
+ // logoff request and exited, we will complete the logoff
+ // process by closing our socket and invoking the
+ // clientDidLogoff callback
_writer.shutdown();
}
-
- // close down our socket
- try {
- _socket.close();
- } catch (IOException cle) {
- Log.warning("Error closing failed socket: " + cle);
- }
- _socket = null;
-
- // let the client observers know that we're logged off
- _client.notifyObservers(Client.CLIENT_DID_LOGOFF, null);
}
/**
@@ -226,8 +213,20 @@ public class Communicator
{
// clear out our writer reference
_writer = null;
-
Log.info("Writer thread exited.");
+
+ // now that the writer thread has gone away, we can safely close
+ // our socket and let the client know that the logoff process has
+ // completed
+ try {
+ _socket.close();
+ } catch (IOException cle) {
+ Log.warning("Error closing failed socket: " + cle);
+ }
+ _socket = null;
+
+ // let the client observers know that we're logged off
+ _client.notifyObservers(Client.CLIENT_DID_LOGOFF, null);
}
/**
diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java
index 3ed20f68e..081741af7 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.19 2001/08/04 00:39:44 mdb Exp $
+// $Id: DObject.java,v 1.20 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.dobj;
@@ -233,6 +233,16 @@ public class DObject
}
}
+ /**
+ * Requests that this distributed object be destroyed. It does so by
+ * queueing up an object destroyed event which the server will
+ * validate and process.
+ */
+ public void destroy ()
+ {
+ _mgr.postEvent(new ObjectDestroyedEvent(_oid));
+ }
+
/**
* Checks to ensure that the specified subscriber has access to this
* object. This will be called before satisfying a subscription
diff --git a/src/java/com/threerings/presents/dobj/DObjectManager.java b/src/java/com/threerings/presents/dobj/DObjectManager.java
index de8e7cab2..7624fab9c 100644
--- a/src/java/com/threerings/presents/dobj/DObjectManager.java
+++ b/src/java/com/threerings/presents/dobj/DObjectManager.java
@@ -1,5 +1,5 @@
//
-// $Id: DObjectManager.java,v 1.7 2001/08/02 04:49:08 mdb Exp $
+// $Id: DObjectManager.java,v 1.8 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.dobj;
@@ -59,6 +59,15 @@ public interface DObjectManager
*/
public void unsubscribeFromObject (int oid, Subscriber target);
+ /**
+ * Requests that the specified object be destroyed. Once destroyed an
+ * object is removed from the runtime system and may no longer have
+ * events dispatched on it.
+ *
+ * @param oid The object id of the distributed object to be destroyed.
+ */
+ public void destroyObject (int oid);
+
/**
* Posts a distributed object event into the system. Instead of
* requesting the modification of a distributed object attribute by
diff --git a/src/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java b/src/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java
new file mode 100644
index 000000000..c21755221
--- /dev/null
+++ b/src/java/com/threerings/presents/dobj/ObjectDestroyedEvent.java
@@ -0,0 +1,63 @@
+//
+// $Id: ObjectDestroyedEvent.java,v 1.1 2001/08/07 20:38:58 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;
+
+/**
+ * An object destroyed event is dispatched when an object has been removed
+ * from the distributed object system. It can also be constructed to
+ * request an attribute change on an object and posted to the dobjmgr.
+ *
+ * @see DObjectManager#postEvent
+ */
+public class ObjectDestroyedEvent extends TypedEvent
+{
+ /** The typed object code for this event. */
+ public static final short TYPE = TYPE_BASE + 7;
+
+ /**
+ * Constructs a new object destroyed event for the specified
+ * distributed object.
+ *
+ * @param targetOid the object id of the object that will be destroyed.
+ */
+ public ObjectDestroyedEvent (int targetOid)
+ {
+ super(targetOid);
+ }
+
+ /**
+ * Constructs a blank instance of this event in preparation for
+ * unserialization from the network.
+ */
+ public ObjectDestroyedEvent ()
+ {
+ }
+
+ /**
+ * Applies this attribute change to the object.
+ */
+ public boolean applyToObject (DObject target)
+ throws ObjectAccessException
+ {
+ // nothing to do in preparation for destruction, the omgr will
+ // have to recognize this type of event and do the right thing
+ return true;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ protected void toString (StringBuffer buf)
+ {
+ buf.append("DESTROY:");
+ super.toString(buf);
+ }
+}
diff --git a/src/java/com/threerings/presents/dobj/io/DObjectFactory.java b/src/java/com/threerings/presents/dobj/io/DObjectFactory.java
index 3c6b194da..925269c0b 100644
--- a/src/java/com/threerings/presents/dobj/io/DObjectFactory.java
+++ b/src/java/com/threerings/presents/dobj/io/DObjectFactory.java
@@ -1,5 +1,5 @@
//
-// $Id: DObjectFactory.java,v 1.6 2001/08/03 02:11:40 mdb Exp $
+// $Id: DObjectFactory.java,v 1.7 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.dobj.io;
@@ -49,7 +49,7 @@ public class DObjectFactory
Class clazz = Class.forName(in.readUTF());
DObject dobj = (DObject)clazz.newInstance();
dobj.setOid(in.readInt()); // read and set the oid
- Log.info("Unmarshalling object: " + dobj);
+ // Log.info("Unmarshalling object: " + dobj);
// look up the marshaller for that class
Marshaller marsh = getMarshaller(clazz);
diff --git a/src/java/com/threerings/presents/io/TypedObjectRegistry.java b/src/java/com/threerings/presents/io/TypedObjectRegistry.java
index f463aec98..4d1ce1a5f 100644
--- a/src/java/com/threerings/presents/io/TypedObjectRegistry.java
+++ b/src/java/com/threerings/presents/io/TypedObjectRegistry.java
@@ -1,5 +1,5 @@
//
-// $Id: TypedObjectRegistry.java,v 1.4 2001/08/04 01:05:25 mdb Exp $
+// $Id: TypedObjectRegistry.java,v 1.5 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.io;
@@ -64,5 +64,7 @@ public class TypedObjectRegistry
ObjectRemovedEvent.class);
TypedObjectFactory.registerClass(ReleaseLockEvent.TYPE,
ReleaseLockEvent.class);
+ TypedObjectFactory.registerClass(ObjectDestroyedEvent.TYPE,
+ ObjectDestroyedEvent.class);
}
}
diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
index 92c314159..42e8361d8 100644
--- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
+++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
@@ -1,8 +1,11 @@
//
-// $Id: PresentsDObjectMgr.java,v 1.9 2001/07/23 21:12:55 mdb Exp $
+// $Id: PresentsDObjectMgr.java,v 1.10 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.server;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+
import com.samskivert.util.Queue;
import com.threerings.cocktail.cher.Log;
@@ -61,6 +64,13 @@ public class CherDObjectMgr implements DObjectManager
AccessObjectEvent.UNSUBSCRIBE));
}
+ // inherit documentation from the interface
+ public void destroyObject (int oid)
+ {
+ // queue up an object destroyed event
+ postEvent(new ObjectDestroyedEvent(oid));
+ }
+
// inherit documentation from the interface
public void postEvent (DEvent event)
{
@@ -121,7 +131,10 @@ public class CherDObjectMgr implements DObjectManager
// do any internal management necessary based on this
// event
- // **TBD**
+ Method helper = (Method)_helpers.get(event.getClass());
+ if (helper != null) {
+ helper.invoke(this, new Object[] { event, target });
+ }
// if the event returns false from applyToObject, this
// means it's a silent event and we shouldn't notify the
@@ -153,6 +166,17 @@ public class CherDObjectMgr implements DObjectManager
_evqueue.append(new ShutdownEvent());
}
+ /**
+ * Called as a helper for ObjectDestroyedEvent events. It
+ * removes the object from the object table.
+ */
+ public void objectDestroyed (DEvent event, DObject target)
+ {
+ Log.info("Removing destroyed object from table " +
+ "[oid=" + target.getOid() + "].");
+ _objects.remove(target.getOid());
+ }
+
protected synchronized boolean isRunning ()
{
return _running;
@@ -170,11 +194,6 @@ public class CherDObjectMgr implements DObjectManager
return _nextOid;
}
- protected boolean _running = true;
- protected Queue _evqueue = new Queue();
- protected IntMap _objects = new IntMap();
- protected int _nextOid = 0;
-
/**
* Used to create a distributed object and register it with the
* system.
@@ -318,4 +337,35 @@ public class CherDObjectMgr implements DObjectManager
return false;
}
}
+
+ /**
+ * Registers our event helper methods.
+ */
+ protected static void registerEventHelpers ()
+ {
+ Class[] ptypes = new Class[] { DEvent.class, DObject.class };
+ Class omgrcl = CherDObjectMgr.class;
+ Method method;
+
+ try {
+ method = omgrcl.getMethod("objectDestroyed", ptypes);
+ _helpers.put(ObjectDestroyedEvent.class, method);
+
+ } catch (Exception e) {
+ Log.warning("Unable to register event helpers " +
+ "[error=" + e + "].");
+ }
+ }
+
+ protected boolean _running = true;
+ protected Queue _evqueue = new Queue();
+ protected IntMap _objects = new IntMap();
+ protected int _nextOid = 0;
+
+ /**
+ * This table maps event classes to helper methods that perform some
+ * additional processing for particular events.
+ */
+ protected static HashMap _helpers = new HashMap();
+ static { registerEventHelpers(); }
}
diff --git a/src/java/com/threerings/presents/server/net/ConnectionManager.java b/src/java/com/threerings/presents/server/net/ConnectionManager.java
index 3677bcf23..7b2664873 100644
--- a/src/java/com/threerings/presents/server/net/ConnectionManager.java
+++ b/src/java/com/threerings/presents/server/net/ConnectionManager.java
@@ -1,5 +1,5 @@
//
-// $Id: ConnectionManager.java,v 1.7 2001/08/03 02:12:52 mdb Exp $
+// $Id: ConnectionManager.java,v 1.8 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.server.net;
@@ -216,7 +216,7 @@ public class ConnectionManager extends LoopingThread
if (socket == null) {
// in theory this shouldn't happen because we got an
// ACCEPT_READY event, but better safe than sorry
- Log.info("Psych! Got ACCEPT_READY, but no connection.");
+ // Log.info("Psych! Got ACCEPT_READY, but no connection.");
return;
}
diff --git a/src/java/com/threerings/presents/server/net/RunningConnection.java b/src/java/com/threerings/presents/server/net/RunningConnection.java
index cc5a22a3f..98357b320 100644
--- a/src/java/com/threerings/presents/server/net/RunningConnection.java
+++ b/src/java/com/threerings/presents/server/net/RunningConnection.java
@@ -1,5 +1,5 @@
//
-// $Id: RunningConnection.java,v 1.3 2001/06/02 01:30:37 mdb Exp $
+// $Id: RunningConnection.java,v 1.4 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.server.net;
@@ -33,6 +33,10 @@ public class RunningConnection extends Connection
public String toString ()
{
- return "[mode=RUNNING, addr=" + _socket.getInetAddress() + "]";
+ if (_socket != null) {
+ return "[mode=RUNNING, addr=" + _socket.getInetAddress() + "]";
+ } else {
+ return "[mode=RUNNING, addr=]";
+ }
}
}
diff --git a/tests/rsrc/config/presents/server.properties b/tests/rsrc/config/presents/server.properties
new file mode 100644
index 000000000..e76d61acd
--- /dev/null
+++ b/tests/rsrc/config/presents/server.properties
@@ -0,0 +1,9 @@
+#
+# $Id: server.properties,v 1.1 2001/08/07 20:38:58 mdb Exp $
+#
+# Configuration for the Cher test server
+
+# These invocation service providers will be registered with the
+# invocation manager during the server init process
+providers = \
+ test = com.threerings.cocktail.cher.server.test.TestProvider
diff --git a/tests/src/java/com/threerings/presents/client/TestClient.java b/tests/src/java/com/threerings/presents/client/TestClient.java
index 404051ba7..273071ec0 100644
--- a/tests/src/java/com/threerings/presents/client/TestClient.java
+++ b/tests/src/java/com/threerings/presents/client/TestClient.java
@@ -1,5 +1,5 @@
//
-// $Id: TestClient.java,v 1.7 2001/07/19 19:18:06 mdb Exp $
+// $Id: TestClient.java,v 1.8 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.client.test;
@@ -18,6 +18,11 @@ import com.threerings.cocktail.cher.server.test.TestObject;
public class TestClient
implements Client.Invoker, ClientObserver, Subscriber
{
+ public void setClient (Client client)
+ {
+ _client = client;
+ }
+
public void invokeLater (Runnable run)
{
// queue it on up
@@ -36,13 +41,11 @@ public class TestClient
public void clientDidLogon (Client client)
{
Log.info("Client did logon [client=" + client + "].");
- // try subscribing to a test object
- client.getDObjectManager().subscribeToObject(2, this);
// register our test notification receiver
client.getInvocationManager().registerReceiver(TestService.MODULE,
new TestReceiver());
- // issue a test invocation request
- TestService.test(client, "foo", 1, this);
+ // get the test object id
+ TestService.getTestOid(client, this);
}
public void clientFailedToLogon (Client client, Exception cause)
@@ -79,28 +82,41 @@ public class TestClient
{
Log.info("Object unavailable [oid=" + oid +
", reason=" + cause + "].");
+ // nothing to do, so might as well logoff
+ _client.logoff(true);
}
public boolean handleEvent (DEvent event, DObject target)
{
Log.info("Got event [event=" + event + ", target=" + target + "].");
- // dispatch a second event
- ((TestObject)target).setBar("rofl!");
- // unsubscribe to the object to make sure we don't get the event
- return false;
+ if (event instanceof AttributeChangedEvent) {
+ // request to destroy the object
+ target.destroy();
+ } else {
+ // request that we log off
+ _client.logoff(true);
+ }
+ return true;
}
- public void handleTestSucceeded (String one, int two)
+ public void handleTestSucceeded (int invid, String one, int two)
{
Log.info("Got test response [one=" + one + ", two=" + two + "].");
}
+ public void handleGotTestOid (int invid, int oid)
+ {
+ // subscribe to the test object
+ _client.getDObjectManager().subscribeToObject(oid, this);
+ }
+
public static void main (String[] args)
{
TestClient tclient = new TestClient();
UsernamePasswordCreds creds =
new UsernamePasswordCreds("test", "test");
Client client = new Client(creds, tclient);
+ tclient.setClient(client);
client.addObserver(tclient);
client.setServer("localhost", 4007);
client.logon();
@@ -109,4 +125,5 @@ public class TestClient
}
protected Queue _queue = new Queue();
+ protected Client _client;
}
diff --git a/tests/src/java/com/threerings/presents/client/TestService.java b/tests/src/java/com/threerings/presents/client/TestService.java
index 3f320faaa..7da48394d 100644
--- a/tests/src/java/com/threerings/presents/client/TestService.java
+++ b/tests/src/java/com/threerings/presents/client/TestService.java
@@ -1,5 +1,5 @@
//
-// $Id: TestService.java,v 1.1 2001/07/19 07:48:25 mdb Exp $
+// $Id: TestService.java,v 1.2 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.client.test;
@@ -22,4 +22,11 @@ public class TestService
invmgr.invoke(MODULE, "Test", args, rsptarget);
Log.info("Sent test request [one=" + one + ", two=" + two + "].");
}
+
+ public static void getTestOid (Client client, Object rsptarget)
+ {
+ InvocationManager invmgr = client.getInvocationManager();
+ Object[] args = new Object[0];
+ invmgr.invoke(MODULE, "GetTestOid", args, rsptarget);
+ }
}
diff --git a/tests/src/java/com/threerings/presents/server/TestProvider.java b/tests/src/java/com/threerings/presents/server/TestProvider.java
index a3141b168..659f8f0a5 100644
--- a/tests/src/java/com/threerings/presents/server/TestProvider.java
+++ b/tests/src/java/com/threerings/presents/server/TestProvider.java
@@ -1,5 +1,5 @@
//
-// $Id: TestProvider.java,v 1.4 2001/07/19 19:18:07 mdb Exp $
+// $Id: TestProvider.java,v 1.5 2001/08/07 20:38:58 mdb Exp $
package com.threerings.cocktail.cher.server.test;
@@ -27,4 +27,10 @@ public class TestProvider extends InvocationProvider
// and issue a response to this invocation request
return createResponse("TestSucceeded", one, new Integer(two));
}
+
+ public Object[] handleGetTestOidRequest (ClientObject source)
+ {
+ int oid = TestServer.testobj.getOid();
+ return createResponse("GotTestOid", new Integer(oid));
+ }
}
diff --git a/tests/src/java/com/threerings/presents/server/TestServer.java b/tests/src/java/com/threerings/presents/server/TestServer.java
new file mode 100644
index 000000000..5f72cb26a
--- /dev/null
+++ b/tests/src/java/com/threerings/presents/server/TestServer.java
@@ -0,0 +1,70 @@
+//
+// $Id: TestServer.java,v 1.1 2001/08/07 20:38:58 mdb Exp $
+
+package com.threerings.cocktail.cher.server.test;
+
+import java.io.IOException;
+
+import com.threerings.cocktail.cher.Log;
+import com.threerings.cocktail.cher.dobj.*;
+import com.threerings.cocktail.cher.server.*;
+
+public class TestServer extends CherServer
+{
+ /** The namespace used for server config properties. */
+ public static final String CONFIG_KEY = "test";
+
+ public static TestObject testobj;
+
+ public void init ()
+ throws IOException
+ {
+ super.init();
+
+ // bind the party server config into the namespace
+ config.bindProperties(CONFIG_KEY, CONFIG_PATH);
+
+ // register our invocation service providers
+ registerProviders(config.getValue(PROVIDERS_KEY, (String[])null));
+
+ // create a test object
+ Subscriber sub = new Subscriber()
+ {
+ public void objectAvailable (DObject object)
+ {
+ testobj = (TestObject)object;
+ }
+
+ public void requestFailed (int oid, ObjectAccessException cause)
+ {
+ Log.warning("Unable to create test object " +
+ "[error=" + cause + "].");
+ }
+
+ public boolean handleEvent (DEvent event, DObject target)
+ {
+ return false;
+ }
+ };
+ omgr.createObject(TestObject.class, sub, false);
+ }
+
+ public static void main (String[] args)
+ {
+ TestServer server = new TestServer();
+ try {
+ server.init();
+ server.run();
+ } catch (Exception e) {
+ Log.warning("Unable to initialize server.");
+ Log.logStackTrace(e);
+ }
+ }
+
+ // the path to the config file
+ protected final static String CONFIG_PATH =
+ "rsrc/config/cocktail/cher/test/server";
+
+ // the config key for our list of invocation provider mappings
+ protected final static String PROVIDERS_KEY = CONFIG_KEY + ".providers";
+}