A whirlwind of whirled updates. Moving from scene to scene seems to mostly

work now. Scene activation is implemented. Some tests are written.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@242 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2001-08-14 06:51:07 +00:00
parent 5f0e2e2570
commit 89293034c8
13 changed files with 525 additions and 25 deletions
+4 -9
View File
@@ -1,18 +1,9 @@
Whirled Design -*- mode: outline -*- Whirled Design -*- mode: outline -*-
* Notes * 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 Scene manager registers a timer when it becomes empty and flushes itself
from memory after timeout period. 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 Client should avoid unsubscribing from scene objects straight away so that
it can avoid resubscribing if they bounce back and forth between scenes. 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 associate properties files with classes of scenes which will be used to
configure the scene managers when they are initialized. 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 * Overview
Whirled builds on top of the Cocktail platform and provides a framework 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 for building a virtual world made up of a node network of individual
+9
View File
@@ -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
@@ -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).
*
* <p> 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 <code>moveTo</code> 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 <code>moveTo</code> 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;
}
@@ -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; 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 * 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 public class SceneService
{ {
/** The module name for the scene services. */ /** The module name for the scene services. */
public static final String MODULE = "whirled!scene"; 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 + "].");
}
} }
@@ -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; 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 * bundles that are periodically distributed to bring all clients into
* sync with the latest snapshot of the scene database. * sync with the latest snapshot of the scene database.
* *
* @see com.threerings.whirled.client.Scene * @see com.threerings.whirled.data.Scene
*/ */
public interface SceneRepository public interface SceneRepository
{ {
@@ -27,6 +27,16 @@ public interface SceneRepository
* @exception NoSuchSceneException thrown if no scene exists with the * @exception NoSuchSceneException thrown if no scene exists with the
* specified scene id. * specified scene id.
*/ */
public Scene getScene (int sceneid) public Scene loadScene (int sceneId)
throws IOException, NoSuchSceneException; 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;
} }
@@ -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;
}
@@ -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; package com.threerings.whirled.server;
@@ -44,6 +44,7 @@ public class SceneRegistry
// we'll use this to do database stuff // we'll use this to do database stuff
_invoker = new Invoker(); _invoker = new Invoker();
_invoker.start();
} }
/** /**
@@ -91,6 +92,8 @@ public class SceneRegistry
return; return;
} }
Log.info("Resolving scene [id=" + sceneId + "].");
// otherwise we've got to resolve the scene and call them back // otherwise we've got to resolve the scene and call them back
// later; we can manipulate the penders table with impunity here // later; we can manipulate the penders table with impunity here
// because we only do so on the dobjmgr thread // because we only do so on the dobjmgr thread
@@ -113,6 +116,8 @@ public class SceneRegistry
// keywords... // keywords...
final int fsceneId = sceneId; final int fsceneId = sceneId;
Log.info("Invoking scene repository [id=" + sceneId + "].");
// then we queue up an execution unit that'll load the scene // then we queue up an execution unit that'll load the scene
// and initialize it and all that // and initialize it and all that
_invoker.postUnit(new Invoker.Unit() { _invoker.postUnit(new Invoker.Unit() {
@@ -120,7 +125,7 @@ public class SceneRegistry
public boolean invoke () public boolean invoke ()
{ {
try { try {
_scene = _screp.getScene(fsceneId); _scene = _screp.loadScene(fsceneId);
} catch (Exception e) { } catch (Exception e) {
_cause = e; _cause = e;
} }
@@ -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; 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.Log;
import com.threerings.whirled.server.persist.SceneRepository; 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 * 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"; protected final static String PROVIDERS_KEY = CONFIG_KEY + ".providers";
// scene repository related configuration info // 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 = protected final static String DEF_SCENEREP =
DummySceneRepository.class.getName(); DummySceneRepository.class.getName();
} }
@@ -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 java.io.IOException;
import com.threerings.whirled.Log;
import com.threerings.whirled.data.Scene; import com.threerings.whirled.data.Scene;
import com.threerings.whirled.server.persist.SceneRepository;
import com.threerings.whirled.util.NoSuchSceneException; import com.threerings.whirled.util.NoSuchSceneException;
/** /**
@@ -15,10 +18,11 @@ import com.threerings.whirled.util.NoSuchSceneException;
public class DummySceneRepository implements SceneRepository public class DummySceneRepository implements SceneRepository
{ {
// documentation inherited // documentation inherited
public Scene getScene (int sceneid) public Scene loadScene (int sceneId)
throws IOException, NoSuchSceneException throws IOException, NoSuchSceneException
{ {
return null; Log.info("Creating dummy scene [id=" + sceneId + "].");
return new DummyScene(sceneId);
} }
// documentation inherited // documentation inherited
@@ -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; package com.threerings.whirled.server.persist;
@@ -17,7 +17,7 @@ public interface SceneRepository
* @exception NoSuchSceneException thrown if no scene exists with the * @exception NoSuchSceneException thrown if no scene exists with the
* specified scene id. * specified scene id.
*/ */
public Scene getScene (int sceneid) public Scene loadScene (int sceneId)
throws IOException, NoSuchSceneException; throws IOException, NoSuchSceneException;
/** /**
@@ -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 ();
}
@@ -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
}
}
@@ -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();
}