diff --git a/docs/whirled/design.txt b/docs/whirled/design.txt index 9c4a9e14a..4fbae4d4f 100644 --- a/docs/whirled/design.txt +++ b/docs/whirled/design.txt @@ -1,18 +1,9 @@ Whirled Design -*- mode: outline -*- * Notes -Scenes map to places. - -Scene manager extends place manager, handles scene related stuff. - Scene manager registers a timer when it becomes empty and flushes itself from memory after timeout period. -Location manager is used to navigate between scenes on the client; -location services will need extensions to support delivering scene version -with moveTo request and serialized scene data with moveTo response -(desirable to avoid extra server round-trips). - Client should avoid unsubscribing from scene objects straight away so that it can avoid resubscribing if they bounce back and forth between scenes. @@ -20,6 +11,10 @@ We'll want the scenes to adhere to some sort of hierarchy so that we can associate properties files with classes of scenes which will be used to configure the scene managers when they are initialized. +Add code to deal with receiving updated scenes from the server. + +Add code to flush scenes from the client scene manager cache. + * Overview Whirled builds on top of the Cocktail platform and provides a framework for building a virtual world made up of a node network of individual diff --git a/rsrc/config/whirled/server.properties b/rsrc/config/whirled/server.properties new file mode 100644 index 000000000..a5d2f7068 --- /dev/null +++ b/rsrc/config/whirled/server.properties @@ -0,0 +1,9 @@ +# +# $Id: server.properties,v 1.1 2001/08/14 06:51:07 mdb Exp $ +# +# Configuration for the Whirled server + +# These invocation service providers will be registered with the +# invocation manager during the server init process +providers = \ + whirled!scene = com.threerings.whirled.server.SceneProvider diff --git a/src/java/com/threerings/whirled/client/SceneDirector.java b/src/java/com/threerings/whirled/client/SceneDirector.java new file mode 100644 index 000000000..d15ad61fb --- /dev/null +++ b/src/java/com/threerings/whirled/client/SceneDirector.java @@ -0,0 +1,215 @@ +// +// $Id: SceneDirector.java,v 1.1 2001/08/14 06:51:07 mdb Exp $ + +package com.threerings.whirled.client; + +import java.io.IOException; + +import com.threerings.cocktail.cher.dobj.DObject; +import com.threerings.cocktail.cher.dobj.ObjectAccessException; +import com.threerings.cocktail.cher.util.IntMap; + +import com.threerings.cocktail.party.client.LocationManager; +import com.threerings.cocktail.party.client.LocationObserver; +import com.threerings.cocktail.party.data.PlaceObject; + +import com.threerings.whirled.Log; +import com.threerings.whirled.client.persist.SceneRepository; +import com.threerings.whirled.util.NoSuchSceneException; +import com.threerings.whirled.data.Scene; +import com.threerings.whirled.util.WhirledContext; + +/** + * The scene manager is the client's interface to all things scene + * related. It interfaces with the scene repository to ensure that scene + * objects are available when the client enters a particular scene. It + * handles moving from scene to scene (it extends and replaces the + * location manager in order to do this). + * + *
Note that when the scene manager is in use instead of the location
+ * manager, scene ids instead of place oids will be supplied to {@link
+ * com.threerings.cocktail.party.client.LocationObserver#locationMayChange}
+ * and {@link
+ * com.threerings.cocktail.party.client.LocationObserver#locationChangeFailed}.
+ */
+public class SceneManager extends LocationManager
+{
+ /**
+ * Creates a new scene manager with the specified context.
+ */
+ public SceneManager (WhirledContext ctx, SceneRepository screp)
+ {
+ super(ctx);
+
+ // we'll need these for later
+ _ctx = ctx;
+ _screp = screp;
+ }
+
+ /**
+ * Returns the scene object associated with the scene we currently
+ * occupy or null if we currently occupy no scene.
+ */
+ public Scene getScene ()
+ {
+ return _scene;
+ }
+
+ /**
+ * Requests that this client move the specified scene. A request will
+ * be made and when the response is received, the location observers
+ * will be notified of success or failure.
+ */
+ public void moveTo (int sceneId)
+ {
+ // first check to see if our observers are happy with this move
+ // request
+ if (!mayMoveTo(sceneId)) {
+ return;
+ }
+
+ // complain if we're over-writing a pending request
+ if (_pendingSceneId != -1) {
+ Log.warning("We appear to have a moveTo request outstanding " +
+ "[psid=" + _pendingSceneId +
+ ", nsid=" + sceneId + "].");
+ // but we're going to fall through and do it anyway because
+ // refusing to switch scenes at this point will inevitably
+ // result in some strange bug causing a move request to be
+ // dropped by the server and the client that did it to be
+ // totally hosed because they can no longer move to new scenes
+ // because they still have an outstanding request
+ }
+
+ // load up the pending scene so that we can communicate it's most
+ // recent version to the server
+ int sceneVers = 0;
+ _pendingScene = loadScene(sceneId);
+ // if we were unable to load it, assume a previous version of zero
+ if (_pendingScene != null) {
+ sceneVers = _pendingScene.getVersion();
+ }
+
+ // make a note of our pending scene id
+ _pendingSceneId = sceneId;
+
+ // issue a moveTo request
+ SceneService.moveTo(_ctx.getClient(), sceneId, sceneVers, this);
+ }
+
+ /**
+ * Called in response to a successful moveTo request.
+ */
+ public void handleMoveSucceeded (int invid, int placeId)
+ {
+ // our move request was successful, deal with subscribing to our
+ // new place object
+ didMoveTo(placeId);
+
+ // since we're committed to moving to the new scene, we'll
+ // parallelize and go ahead and load up the new scene now rather
+ // than wait until subscription to our place object succeeds
+
+ // release the old scene
+ releaseScene(_scene);
+
+ // update our scene id tracking fields
+ _previousSceneId = _sceneId;
+ _sceneId = _pendingSceneId;
+ _pendingSceneId = -1;
+
+ // and load the new scene
+ _scene = loadScene(_sceneId);
+ }
+
+ /**
+ * Called in response to a failed moveTo request.
+ */
+ public void handleMoveFailed (int invid, String reason)
+ {
+ // clear out our pending request oid
+ int sceneId = _pendingSceneId;
+ _pendingSceneId = -1;
+
+ // let our observers know that something has gone horribly awry
+ notifyFailure(sceneId, reason);
+ }
+
+ protected void recoverFailedMove (int placeId)
+ {
+ // clear out our now bogus scene tracking info
+ int sceneId = _sceneId;
+ _sceneId = -1;
+ releaseScene(_scene);
+ _scene = null;
+
+ // if we were previously somewhere (and that somewhere isn't where
+ // we just tried to go), try going back to that happy place
+ if (_previousSceneId != -1 && _previousSceneId != sceneId) {
+ moveTo(_previousSceneId);
+ }
+ }
+
+ /**
+ * Loads a scene from the repository. If the scene is cached, it will
+ * be returned from the cache instead.
+ */
+ protected Scene loadScene (int sceneId)
+ {
+ // first look in the cache
+ Scene scene = (Scene)_scache.get(sceneId);
+
+ // load from the repository if it's not cached
+ if (scene == null) {
+ try {
+ scene = _screp.loadScene(sceneId);
+ _scache.put(sceneId, scene);
+
+ } catch (NoSuchSceneException nsse) {
+ // nothing special here, just fall through and return null
+
+ } catch (IOException ioe) {
+ // complain first, then return null
+ Log.warning("Error loading scene [scid=" + sceneId +
+ ", error=" + ioe + "].");
+ }
+ }
+
+ return scene;
+ }
+
+ /**
+ * Unloads a scene that was previously loaded via {@link #loadScene}.
+ * The scene will probably continue to live in the cache for a while
+ * in case we quickly return to it.
+ */
+ protected void releaseScene (Scene scene)
+ {
+ // we're cool if we're called with null
+ if (scene == null) {
+ return;
+ }
+ }
+
+ protected WhirledContext _ctx;
+ protected SceneRepository _screp;
+ protected IntMap _scache = new IntMap();
+
+ /** The scene object of the scene we currently occupy. */
+ protected Scene _scene;
+
+ /** The id of the scene we currently occupy. */
+ protected int _sceneId = -1;
+
+ /** Our most recent copy of the scene we're about to enter. */
+ protected Scene _pendingScene;
+
+ /**
+ * The id of the scene for which we have an outstanding moveTo
+ * request, or -1 if we have no outstanding request.
+ */
+ protected int _pendingSceneId = -1;
+
+ /** The id of the scene we previously occupied. */
+ protected int _previousSceneId = -1;
+}
diff --git a/src/java/com/threerings/whirled/client/SceneService.java b/src/java/com/threerings/whirled/client/SceneService.java
index 51355b44c..bc3453653 100644
--- a/src/java/com/threerings/whirled/client/SceneService.java
+++ b/src/java/com/threerings/whirled/client/SceneService.java
@@ -1,5 +1,5 @@
//
-// $Id: SceneService.java,v 1.1 2001/08/11 04:09:50 mdb Exp $
+// $Id: SceneService.java,v 1.2 2001/08/14 06:51:07 mdb Exp $
package com.threerings.whirled.client;
@@ -10,10 +10,29 @@ import com.threerings.whirled.Log;
/**
* The scene service class provides the client interface to the scene
- * related invocation services.
+ * related invocation services (e.g. moving from scene to scene).
*/
public class SceneService
{
/** The module name for the scene services. */
public static final String MODULE = "whirled!scene";
+
+ /**
+ * Requests that that this client's body be moved to the specified
+ * scene.
+ *
+ * @param sceneId the scene id to which we want to move.
+ * @param sceneVers the version number of the scene object that we
+ * have in our local repository.
+ */
+ public static void moveTo (Client client, int sceneId,
+ int sceneVers, SceneManager rsptarget)
+ {
+ InvocationManager invmgr = client.getInvocationManager();
+ Object[] args = new Object[] {
+ new Integer(sceneId), new Integer(sceneVers) };
+ invmgr.invoke(MODULE, "MoveTo", args, rsptarget);
+ Log.info("Sent moveTo request [scene=" + sceneId +
+ ", version=" + sceneVers + "].");
+ }
}
diff --git a/src/java/com/threerings/whirled/client/persist/SceneRepository.java b/src/java/com/threerings/whirled/client/persist/SceneRepository.java
index f1fd9abc6..5a27f0773 100644
--- a/src/java/com/threerings/whirled/client/persist/SceneRepository.java
+++ b/src/java/com/threerings/whirled/client/persist/SceneRepository.java
@@ -1,5 +1,5 @@
//
-// $Id: SceneRepository.java,v 1.1 2001/08/11 04:09:50 mdb Exp $
+// $Id: SceneRepository.java,v 1.2 2001/08/14 06:51:07 mdb Exp $
package com.threerings.whirled.client.persist;
@@ -15,7 +15,7 @@ import com.threerings.whirled.util.NoSuchSceneException;
* bundles that are periodically distributed to bring all clients into
* sync with the latest snapshot of the scene database.
*
- * @see com.threerings.whirled.client.Scene
+ * @see com.threerings.whirled.data.Scene
*/
public interface SceneRepository
{
@@ -27,6 +27,16 @@ public interface SceneRepository
* @exception NoSuchSceneException thrown if no scene exists with the
* specified scene id.
*/
- public Scene getScene (int sceneid)
+ public Scene loadScene (int sceneId)
throws IOException, NoSuchSceneException;
+
+ /**
+ * Updates the specified scene in the repository with the information
+ * provided in the scene object.
+ *
+ * @exception IOException thrown if an error occurs attempting to
+ * update the scene.
+ */
+ public void updateScene (Scene scene)
+ throws IOException;
}
diff --git a/src/java/com/threerings/whirled/data/DummyScene.java b/src/java/com/threerings/whirled/data/DummyScene.java
new file mode 100644
index 000000000..2608cf3b8
--- /dev/null
+++ b/src/java/com/threerings/whirled/data/DummyScene.java
@@ -0,0 +1,31 @@
+//
+// $Id: DummyScene.java,v 1.1 2001/08/14 06:51:07 mdb Exp $
+
+package com.threerings.whirled.client.test;
+
+import com.threerings.whirled.data.Scene;
+
+public class DummyScene implements Scene
+{
+ public DummyScene (int sceneId)
+ {
+ _sceneId = sceneId;
+ }
+
+ public int getId ()
+ {
+ return _sceneId;
+ }
+
+ public int getVersion ()
+ {
+ return 1;
+ }
+
+ public int[] getExitIds ()
+ {
+ return null;
+ }
+
+ protected int _sceneId;
+}
diff --git a/src/java/com/threerings/whirled/server/SceneRegistry.java b/src/java/com/threerings/whirled/server/SceneRegistry.java
index 1bd314f28..7ec1f804d 100644
--- a/src/java/com/threerings/whirled/server/SceneRegistry.java
+++ b/src/java/com/threerings/whirled/server/SceneRegistry.java
@@ -1,5 +1,5 @@
//
-// $Id: SceneRegistry.java,v 1.1 2001/08/11 04:09:50 mdb Exp $
+// $Id: SceneRegistry.java,v 1.2 2001/08/14 06:51:07 mdb Exp $
package com.threerings.whirled.server;
@@ -44,6 +44,7 @@ public class SceneRegistry
// we'll use this to do database stuff
_invoker = new Invoker();
+ _invoker.start();
}
/**
@@ -91,6 +92,8 @@ public class SceneRegistry
return;
}
+ Log.info("Resolving scene [id=" + sceneId + "].");
+
// otherwise we've got to resolve the scene and call them back
// later; we can manipulate the penders table with impunity here
// because we only do so on the dobjmgr thread
@@ -113,6 +116,8 @@ public class SceneRegistry
// keywords...
final int fsceneId = sceneId;
+ Log.info("Invoking scene repository [id=" + sceneId + "].");
+
// then we queue up an execution unit that'll load the scene
// and initialize it and all that
_invoker.postUnit(new Invoker.Unit() {
@@ -120,7 +125,7 @@ public class SceneRegistry
public boolean invoke ()
{
try {
- _scene = _screp.getScene(fsceneId);
+ _scene = _screp.loadScene(fsceneId);
} catch (Exception e) {
_cause = e;
}
diff --git a/src/java/com/threerings/whirled/server/WhirledServer.java b/src/java/com/threerings/whirled/server/WhirledServer.java
index 9d5385a46..b4e7d4fc7 100644
--- a/src/java/com/threerings/whirled/server/WhirledServer.java
+++ b/src/java/com/threerings/whirled/server/WhirledServer.java
@@ -1,5 +1,5 @@
//
-// $Id: WhirledServer.java,v 1.1 2001/08/11 04:09:50 mdb Exp $
+// $Id: WhirledServer.java,v 1.2 2001/08/14 06:51:07 mdb Exp $
package com.threerings.whirled.server;
@@ -9,7 +9,7 @@ import com.threerings.cocktail.party.server.PartyServer;
import com.threerings.whirled.Log;
import com.threerings.whirled.server.persist.SceneRepository;
-import com.threerings.whirled.server.persist.DummySceneRepository;
+import com.threerings.whirled.server.test.DummySceneRepository;
/**
* The whirled server extends the party server and provides access to
@@ -75,7 +75,7 @@ public class WhirledServer extends PartyServer
protected final static String PROVIDERS_KEY = CONFIG_KEY + ".providers";
// scene repository related configuration info
- protected final static String SCENEREP_KEY = "scene_rep";
+ protected final static String SCENEREP_KEY = CONFIG_KEY + ".scene_rep";
protected final static String DEF_SCENEREP =
DummySceneRepository.class.getName();
}
diff --git a/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java b/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java
index f12cec170..142c2a164 100644
--- a/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java
+++ b/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java
@@ -1,10 +1,13 @@
//
-// $Id: DummySceneRepository.java,v 1.1 2001/08/11 04:09:50 mdb Exp $
+// $Id: DummySceneRepository.java,v 1.2 2001/08/14 06:51:07 mdb Exp $
-package com.threerings.whirled.server.persist;
+package com.threerings.whirled.server.test;
import java.io.IOException;
+
+import com.threerings.whirled.Log;
import com.threerings.whirled.data.Scene;
+import com.threerings.whirled.server.persist.SceneRepository;
import com.threerings.whirled.util.NoSuchSceneException;
/**
@@ -15,10 +18,11 @@ import com.threerings.whirled.util.NoSuchSceneException;
public class DummySceneRepository implements SceneRepository
{
// documentation inherited
- public Scene getScene (int sceneid)
+ public Scene loadScene (int sceneId)
throws IOException, NoSuchSceneException
{
- return null;
+ Log.info("Creating dummy scene [id=" + sceneId + "].");
+ return new DummyScene(sceneId);
}
// documentation inherited
diff --git a/src/java/com/threerings/whirled/server/persist/SceneRepository.java b/src/java/com/threerings/whirled/server/persist/SceneRepository.java
index 739050bb2..12562f3c3 100644
--- a/src/java/com/threerings/whirled/server/persist/SceneRepository.java
+++ b/src/java/com/threerings/whirled/server/persist/SceneRepository.java
@@ -1,5 +1,5 @@
//
-// $Id: SceneRepository.java,v 1.1 2001/08/11 04:09:50 mdb Exp $
+// $Id: SceneRepository.java,v 1.2 2001/08/14 06:51:07 mdb Exp $
package com.threerings.whirled.server.persist;
@@ -17,7 +17,7 @@ public interface SceneRepository
* @exception NoSuchSceneException thrown if no scene exists with the
* specified scene id.
*/
- public Scene getScene (int sceneid)
+ public Scene loadScene (int sceneId)
throws IOException, NoSuchSceneException;
/**
diff --git a/src/java/com/threerings/whirled/util/WhirledContext.java b/src/java/com/threerings/whirled/util/WhirledContext.java
new file mode 100644
index 000000000..4947ec62b
--- /dev/null
+++ b/src/java/com/threerings/whirled/util/WhirledContext.java
@@ -0,0 +1,19 @@
+//
+// $Id: WhirledContext.java,v 1.1 2001/08/14 06:51:07 mdb Exp $
+
+package com.threerings.whirled.util;
+
+import com.threerings.cocktail.party.util.PartyContext;
+import com.threerings.whirled.client.SceneManager;
+
+/**
+ * The whirled context provides access to the various managers, etc. that
+ * are needed by the whirled client code.
+ */
+public interface WhirledContext extends PartyContext
+{
+ /**
+ * Returns a reference to the scene manager.
+ */
+ public SceneManager getSceneManager ();
+}
diff --git a/tests/src/java/com/threerings/whirled/DummyClientSceneRepository.java b/tests/src/java/com/threerings/whirled/DummyClientSceneRepository.java
new file mode 100644
index 000000000..49cca6d69
--- /dev/null
+++ b/tests/src/java/com/threerings/whirled/DummyClientSceneRepository.java
@@ -0,0 +1,34 @@
+//
+// $Id: DummyClientSceneRepository.java,v 1.1 2001/08/14 06:51:07 mdb Exp $
+
+package com.threerings.whirled.client.test;
+
+import java.io.IOException;
+
+import com.threerings.whirled.Log;
+import com.threerings.whirled.client.persist.SceneRepository;
+import com.threerings.whirled.data.Scene;
+import com.threerings.whirled.util.NoSuchSceneException;
+
+/**
+ * The dummy scene repository just pretends to load and store scenes, but
+ * in fact it just creates new blank scenes when requested to load a scene
+ * and does nothing when requested to save one.
+ */
+public class DummySceneRepository implements SceneRepository
+{
+ // documentation inherited
+ public Scene loadScene (int sceneId)
+ throws IOException, NoSuchSceneException
+ {
+ Log.info("Creating dummy scene [id=" + sceneId + "].");
+ return new DummyScene(sceneId);
+ }
+
+ // documentation inherited
+ public void updateScene (Scene scene)
+ throws IOException
+ {
+ // nothing doing
+ }
+}
diff --git a/tests/src/java/com/threerings/whirled/TestClient.java b/tests/src/java/com/threerings/whirled/TestClient.java
new file mode 100644
index 000000000..4f8dda648
--- /dev/null
+++ b/tests/src/java/com/threerings/whirled/TestClient.java
@@ -0,0 +1,159 @@
+//
+// $Id: TestClient.java,v 1.1 2001/08/14 06:51:07 mdb Exp $
+
+package com.threerings.whirled.client.test;
+
+import com.samskivert.util.Config;
+import com.samskivert.util.Queue;
+
+import com.threerings.cocktail.cher.client.*;
+import com.threerings.cocktail.cher.dobj.DObjectManager;
+import com.threerings.cocktail.cher.net.*;
+
+import com.threerings.cocktail.party.Log;
+import com.threerings.cocktail.party.client.LocationManager;
+import com.threerings.cocktail.party.client.LocationObserver;
+import com.threerings.cocktail.party.data.PlaceObject;
+
+import com.threerings.whirled.client.SceneManager;
+import com.threerings.whirled.client.persist.SceneRepository;
+import com.threerings.whirled.util.WhirledContext;
+
+public class TestClient
+ implements Client.Invoker, ClientObserver, LocationObserver
+{
+ public TestClient (Credentials creds)
+ {
+ // create our context
+ _ctx = new WhirledContextImpl();
+
+ // create the handles for our various services
+ _config = new Config();
+ _client = new Client(creds, this);
+ _screp = new DummySceneRepository();
+ _scmgr = new SceneManager(_ctx, _screp);
+
+ // we want to know about logon/logoff
+ _client.addObserver(this);
+
+ // we want to know about location changes
+ _scmgr.addLocationObserver(this);
+
+ // for test purposes, hardcode the server info
+ _client.setServer("localhost", 4007);
+ }
+
+ public void run ()
+ {
+ // log on
+ _client.logon();
+
+ // loop over our queue, running the runnables
+ while (true) {
+ Runnable run = (Runnable)_queue.get();
+ run.run();
+ }
+ }
+
+ public void invokeLater (Runnable run)
+ {
+ // queue it on up
+ _queue.append(run);
+ }
+
+ public void clientDidLogon (Client client)
+ {
+ Log.info("Client did logon [client=" + client + "].");
+
+ // request to move to scene 0
+ _ctx.getSceneManager().moveTo(0);
+ }
+
+ public void clientFailedToLogon (Client client, Exception cause)
+ {
+ Log.info("Client failed to logon [client=" + client +
+ ", cause=" + cause + "].");
+ }
+
+ public void clientConnectionFailed (Client client, Exception cause)
+ {
+ Log.info("Client connection failed [client=" + client +
+ ", cause=" + cause + "].");
+ }
+
+ public boolean clientWillLogoff (Client client)
+ {
+ Log.info("Client will logoff [client=" + client + "].");
+ return true;
+ }
+
+ public void clientDidLogoff (Client client)
+ {
+ Log.info("Client did logoff [client=" + client + "].");
+ System.exit(0);
+ }
+
+ public boolean locationMayChange (int placeId)
+ {
+ // we're easy
+ return true;
+ }
+
+ public void locationDidChange (PlaceObject place)
+ {
+ Log.info("At new location [plobj=" + place +
+ ", scene=" + _scmgr.getScene() + "].");
+ }
+
+ public void locationChangeFailed (int placeId, String reason)
+ {
+ Log.warning("Location change failed [plid=" + placeId +
+ ", reason=" + reason + "].");
+ }
+
+ public static void main (String[] args)
+ {
+ UsernamePasswordCreds creds =
+ new UsernamePasswordCreds("test", "test");
+ // create our test client
+ TestClient tclient = new TestClient(creds);
+ // start it running
+ tclient.run();
+ }
+
+ protected class WhirledContextImpl implements WhirledContext
+ {
+ public Config getConfig ()
+ {
+ return _config;
+ }
+
+ public Client getClient ()
+ {
+ return _client;
+ }
+
+ public DObjectManager getDObjectManager ()
+ {
+ return _client.getDObjectManager();
+ }
+
+ public LocationManager getLocationManager ()
+ {
+ return _scmgr;
+ }
+
+ public SceneManager getSceneManager ()
+ {
+ return _scmgr;
+ }
+ }
+
+ protected Config _config;
+ protected Client _client;
+ protected SceneManager _scmgr;
+ protected SceneRepository _screp;
+ protected WhirledContext _ctx;
+
+ protected Queue _queue = new Queue();
+}