diff --git a/src/java/com/threerings/whirled/Log.java b/src/java/com/threerings/whirled/Log.java new file mode 100644 index 000000000..0d6134ed9 --- /dev/null +++ b/src/java/com/threerings/whirled/Log.java @@ -0,0 +1,38 @@ +// +// $Id: Log.java,v 1.1 2001/08/11 04:09:50 mdb Exp $ + +package com.threerings.whirled; + +/** + * A placeholder class that contains a reference to the log object used by + * the Whirled services. + */ +public class Log +{ + public static com.samskivert.util.Log log = + new com.samskivert.util.Log("whirled"); + + /** Convenience function. */ + public static void debug (String message) + { + log.debug(message); + } + + /** Convenience function. */ + public static void info (String message) + { + log.info(message); + } + + /** Convenience function. */ + public static void warning (String message) + { + log.warning(message); + } + + /** Convenience function. */ + public static void logStackTrace (Throwable t) + { + log.logStackTrace(com.samskivert.util.Log.WARNING, t); + } +} diff --git a/src/java/com/threerings/whirled/client/SceneService.java b/src/java/com/threerings/whirled/client/SceneService.java new file mode 100644 index 000000000..51355b44c --- /dev/null +++ b/src/java/com/threerings/whirled/client/SceneService.java @@ -0,0 +1,19 @@ +// +// $Id: SceneService.java,v 1.1 2001/08/11 04:09:50 mdb Exp $ + +package com.threerings.whirled.client; + +import com.threerings.cocktail.cher.client.Client; +import com.threerings.cocktail.cher.client.InvocationManager; + +import com.threerings.whirled.Log; + +/** + * The scene service class provides the client interface to the scene + * related invocation services. + */ +public class SceneService +{ + /** The module name for the scene services. */ + public static final String MODULE = "whirled!scene"; +} diff --git a/src/java/com/threerings/whirled/client/persist/SceneRepository.java b/src/java/com/threerings/whirled/client/persist/SceneRepository.java new file mode 100644 index 000000000..f1fd9abc6 --- /dev/null +++ b/src/java/com/threerings/whirled/client/persist/SceneRepository.java @@ -0,0 +1,32 @@ +// +// $Id: SceneRepository.java,v 1.1 2001/08/11 04:09:50 mdb Exp $ + +package com.threerings.whirled.client.persist; + +import java.io.IOException; + +import com.threerings.whirled.data.Scene; +import com.threerings.whirled.util.NoSuchSceneException; + +/** + * The scene repository provides access to a persistent repository of + * scene information. The scenes in the repository can be updated with + * individual scenes fetched from the server as well as with new scene + * 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 + */ +public interface SceneRepository +{ + /** + * Fetches the scene with the specified scene id. + * + * @exception IOException thrown if an error occurs attempting to load + * a scene. + * @exception NoSuchSceneException thrown if no scene exists with the + * specified scene id. + */ + public Scene getScene (int sceneid) + throws IOException, NoSuchSceneException; +} diff --git a/src/java/com/threerings/whirled/data/Scene.java b/src/java/com/threerings/whirled/data/Scene.java new file mode 100644 index 000000000..7490148f4 --- /dev/null +++ b/src/java/com/threerings/whirled/data/Scene.java @@ -0,0 +1,17 @@ +// +// $Id: Scene.java,v 1.1 2001/08/11 04:09:50 mdb Exp $ + +package com.threerings.whirled.data; + +/** + * The base scene interface. This encapsulates the minimum information + * needed about a scene in the Whirled system. + */ +public interface Scene +{ + public int getId (); + + public int getVersion (); + + public int[] getExitIds (); +} diff --git a/src/java/com/threerings/whirled/data/SceneObject.java b/src/java/com/threerings/whirled/data/SceneObject.java new file mode 100644 index 000000000..10e910594 --- /dev/null +++ b/src/java/com/threerings/whirled/data/SceneObject.java @@ -0,0 +1,10 @@ +// +// $Id: SceneObject.java,v 1.1 2001/08/11 04:09:50 mdb Exp $ + +package com.threerings.whirled.data; + +import com.threerings.cocktail.party.data.PlaceObject; + +public class SceneObject extends PlaceObject +{ +} diff --git a/src/java/com/threerings/whirled/server/SceneManager.java b/src/java/com/threerings/whirled/server/SceneManager.java new file mode 100644 index 000000000..2cdd840bd --- /dev/null +++ b/src/java/com/threerings/whirled/server/SceneManager.java @@ -0,0 +1,39 @@ +// +// $Id: SceneManager.java,v 1.1 2001/08/11 04:09:50 mdb Exp $ + +package com.threerings.whirled.server; + +import com.threerings.cocktail.party.server.PlaceManager; +import com.threerings.whirled.data.Scene; + +public class SceneManager extends PlaceManager +{ + public Scene getScene () + { + return _scene; + } + + /** + * Called by the scene registry once the scene manager has been + * created (and initialized), but before it is started up. + */ + protected void postInit (Scene scene, SceneRegistry screg) + { + _scene = scene; + _screg = screg; + } + + /** + * We're fully ready to go, so now we register ourselves with the + * scene registry which will make us available to the clients and + * system at large. + */ + protected void didStartup () + { + super.didStartup(); + _screg.sceneManagerDidInit(this); + } + + protected Scene _scene; + protected SceneRegistry _screg; +} diff --git a/src/java/com/threerings/whirled/server/SceneProvider.java b/src/java/com/threerings/whirled/server/SceneProvider.java new file mode 100644 index 000000000..f56ecf31d --- /dev/null +++ b/src/java/com/threerings/whirled/server/SceneProvider.java @@ -0,0 +1,83 @@ +// +// $Id: SceneProvider.java,v 1.1 2001/08/11 04:09:50 mdb Exp $ + +package com.threerings.whirled.server; + +import com.threerings.cocktail.cher.server.InvocationProvider; +import com.threerings.cocktail.cher.util.Codes; + +import com.threerings.cocktail.party.data.BodyObject; +import com.threerings.cocktail.party.data.PlaceObject; +import com.threerings.cocktail.party.server.LocationProvider; + +import com.threerings.whirled.Log; + +/** + * The scene provider handles the server side of the scene related + * invocation services (e.g. moving from scene to scene). + */ +public class SceneProvider extends InvocationProvider +{ + /** + * Processes a request from a client to move to a new scene. + */ + public void handleMoveToRequest (BodyObject source, int invid, + int sceneId, int sceneVersion) + { + // avoid cluttering up the method declaration with final keywords + final BodyObject fsource = source; + final int finvid = invid; + final int fsceneVer = sceneVersion; + + // create a callback object that will handle the resolution or + // failed resolution of the scene + SceneRegistry.ResolutionListener rl = + new SceneRegistry.ResolutionListener() + { + public void sceneWasResolved (SceneManager scmgr) + { + finishMoveToRequest(fsource, finvid, scmgr, fsceneVer); + } + + public void sceneFailedToResolve ( + int rsceneId, Exception reason) + { + Log.warning("Unable to resolve scene [sceneid=" + rsceneId + + ", reason=" + reason + "]."); + // pretend like the scene doesn't exist to the client + sendResponse(fsource, finvid, "MoveFailed", + "m.no_such_place"); + } + }; + + // make sure the scene they are headed to is actually loaded into + // the server + WhirledServer.screg.resolveScene(sceneId, rl); + } + + /** + * This is called after the scene to which we are moving is guaranteed + * to have been loaded into the server. + */ + protected void finishMoveToRequest (BodyObject source, int invid, + SceneManager scmgr, int sceneVersion) + { + // move to the place object associated with this scene + PlaceObject plobj = scmgr.getPlaceObject(); + int ploid = plobj.getOid(); + + // try doing the actual move + String rcode = LocationProvider.moveTo(source, ploid); + + // if the move failed, let them know + if (!rcode.equals(Codes.SUCCESS)) { + sendResponse(source, invid, "MoveFailed", rcode); + return; + } + + // otherwise check to see if they need a newer version of the + // scene data + + sendResponse(source, invid, "MoveSucceeded", new Integer(ploid)); + } +} diff --git a/src/java/com/threerings/whirled/server/SceneRegistry.java b/src/java/com/threerings/whirled/server/SceneRegistry.java new file mode 100644 index 000000000..1bd314f28 --- /dev/null +++ b/src/java/com/threerings/whirled/server/SceneRegistry.java @@ -0,0 +1,223 @@ +// +// $Id: SceneRegistry.java,v 1.1 2001/08/11 04:09:50 mdb Exp $ + +package com.threerings.whirled.server; + +import java.util.ArrayList; +import java.util.Properties; + +import com.threerings.cocktail.cher.util.Invoker; +import com.threerings.cocktail.cher.util.IntMap; + +import com.threerings.cocktail.party.server.PartyServer; + +import com.threerings.whirled.Log; +import com.threerings.whirled.data.Scene; +import com.threerings.whirled.data.SceneObject; +import com.threerings.whirled.server.persist.SceneRepository; + +/** + * The scene registry is responsible for the management of all runtime + * scenes. It handles interaction with the scene repository and ensures + * that scenes are loaded into memory when needed and flushed from memory + * when not needed. + * + *
The scene repository also takes care of bridging from the blocking, + * synchronous world of the scene repository to the non-blocking + * asynchronous world of the distributed object event queue. Thus its + * interfaces for accessing scenes are structured so as to not block the + * dobjmgr thread while waiting for scenes to be read from or written to + * the repository. + * + *
Note: All access to the scene registry should take place
+ * from the dobjmgr thread.
+ */
+public class SceneRegistry
+{
+ /**
+ * Constructs a scene registry, instructing it to load and store
+ * scenes using the supplied scene repository.
+ */
+ public SceneRegistry (SceneRepository screp)
+ {
+ _screp = screp;
+
+ // we'll use this to do database stuff
+ _invoker = new Invoker();
+ }
+
+ /**
+ * Because scenes must be loaded from the scene repository and this
+ * must not be done on the dobjmgr thread, the interface for resolving
+ * scenes requires that the entity that wishes for a scene to be
+ * resolved implement this callback interface so that it can be
+ * notified when a scene has been loaded and initialized.
+ */
+ public static interface ResolutionListener
+ {
+ /**
+ * Called when the scene has been successfully resolved. The scene
+ * manager instance provided can be used to obtain a reference to
+ * the scene, or the scene distributed object.
+ */
+ public void sceneWasResolved (SceneManager scmgr);
+
+ /**
+ * Called if some failure occurred in the scene resolution
+ * process.
+ */
+ public void sceneFailedToResolve (int sceneId, Exception reason);
+ }
+
+ /**
+ * Requests that the specified scene be resolved, which means loaded
+ * into the server and initialized if the scene is not currently
+ * active. The supplied callback instance will be notified, on the
+ * dobjmgr thread, when the scene has been resolved. If the scene is
+ * already active, it will be notified immediately (before the call to
+ * resolveScene returns).
+ *
+ * @param sceneId the id of the scene to resolve.
+ * @param resolver a reference to a callback instance that will be
+ * notified when the scene has been resolved (which may be immediately
+ * if the scene is already active).
+ */
+ public void resolveScene (int sceneId, ResolutionListener target)
+ {
+ SceneManager mgr = (SceneManager)_scenemgrs.get(sceneId);
+ if (mgr != null) {
+ // if the scene is already resolved, we're ready to roll
+ target.sceneWasResolved(mgr);
+ return;
+ }
+
+ // 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
+ ArrayList penders = (ArrayList)_penders.get(sceneId);
+
+ // if we're already in the process of resolving this scene, just
+ // add these guys to the list to be notified when it finally is
+ // resolved
+ if (penders != null) {
+ penders.add(target);
+
+ } else {
+ // otherwise we've got to initiate the resolution process.
+ // first we create the penders list
+ penders = new ArrayList();
+ _penders.put(sceneId, penders);
+ penders.add(target);
+
+ // i don't like cluttering up method declarations with final
+ // keywords...
+ final int fsceneId = sceneId;
+
+ // then we queue up an execution unit that'll load the scene
+ // and initialize it and all that
+ _invoker.postUnit(new Invoker.Unit() {
+ // this is run on the invoker thread
+ public boolean invoke ()
+ {
+ try {
+ _scene = _screp.getScene(fsceneId);
+ } catch (Exception e) {
+ _cause = e;
+ }
+ return true;
+ }
+
+ // this is run on the dobjmgr thread
+ public void handleResult ()
+ {
+ if (_scene != null) {
+ processSuccessfulResolution(_scene);
+ } else if (_cause != null) {
+ processFailedResolution(fsceneId, _cause);
+ } else {
+ Log.warning("Scene loading unit finished with " +
+ "neither a scene nor a reason for " +
+ "failure!?");
+ }
+ }
+
+ protected Scene _scene;
+ protected Exception _cause;
+ });
+ }
+ }
+
+ protected void processSuccessfulResolution (Scene scene)
+ {
+ // now that the scene is loaded, we can create a scene manager for
+ // it. that will be initialized by the place registry and when
+ // that is finally complete, then we can let our penders know
+ // what's up
+
+ // we'll eventually want to load up the proper properties file for
+ // the scene, but for now let's punt
+ try {
+ Properties props = new Properties();
+ SceneManager scmgr = (SceneManager)
+ PartyServer.plreg.createPlace(
+ SceneObject.class, SceneManager.class, props);
+ // configure the scene manager with references to useful stuff
+ scmgr.postInit(scene, this);
+
+ // when the scene manager completes its startup procedings, it
+ // will call back to the scene registry and let us know that
+ // we can turn the penders loose
+
+ } catch (InstantiationException ie) {
+ // so close, but no cigar
+ processFailedResolution(scene.getId(), ie);
+ }
+ }
+
+ protected void processFailedResolution (int sceneId, Exception cause)
+ {
+ // alas things didn't work out, notify our penders
+ ArrayList penders = (ArrayList)_penders.remove(sceneId);
+ if (penders != null) {
+ for (int i = 0; i < penders.size(); i++) {
+ ResolutionListener rl = (ResolutionListener)penders.get(i);
+ try {
+ rl.sceneFailedToResolve(sceneId, cause);
+ } catch (Exception e) {
+ Log.warning("Resolution listener choked.");
+ Log.logStackTrace(e);
+ }
+ }
+ }
+ }
+
+ protected void sceneManagerDidInit (SceneManager scmgr)
+ {
+ // register this scene manager in our table
+ int sceneId = scmgr.getScene().getId();
+ _scenemgrs.put(sceneId, scmgr);
+
+ Log.info("Registering scene manager [scid=" + sceneId +
+ ", scmgr=" + scmgr + "].");
+
+ // now notify any penders
+ ArrayList penders = (ArrayList)_penders.remove(sceneId);
+ if (penders != null) {
+ for (int i = 0; i < penders.size(); i++) {
+ ResolutionListener rl = (ResolutionListener)penders.get(i);
+ try {
+ rl.sceneWasResolved(scmgr);
+ } catch (Exception e) {
+ Log.warning("Resolution listener choked.");
+ Log.logStackTrace(e);
+ }
+ }
+ }
+ }
+
+ protected SceneRepository _screp;
+ protected Invoker _invoker;
+
+ protected IntMap _scenemgrs = new IntMap();
+ protected IntMap _penders = new IntMap();
+}
diff --git a/src/java/com/threerings/whirled/server/WhirledServer.java b/src/java/com/threerings/whirled/server/WhirledServer.java
new file mode 100644
index 000000000..9d5385a46
--- /dev/null
+++ b/src/java/com/threerings/whirled/server/WhirledServer.java
@@ -0,0 +1,81 @@
+//
+// $Id: WhirledServer.java,v 1.1 2001/08/11 04:09:50 mdb Exp $
+
+package com.threerings.whirled.server;
+
+import java.io.IOException;
+
+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;
+
+/**
+ * The whirled server extends the party server and provides access to
+ * managers and the like that are needed by the whirled serviecs.
+ */
+public class WhirledServer extends PartyServer
+{
+ /** The namespace used for server config properties. */
+ public static final String CONFIG_KEY = "whirled";
+
+ /** The scene registry. */
+ public static SceneRegistry screg;
+
+ /**
+ * Initializes all of the server services and prepares for operation.
+ */
+ public void init ()
+ throws IOException
+ {
+ // do the cher server initialization
+ super.init();
+
+ // bind the whirled server config into the namespace
+ config.bindProperties(CONFIG_KEY, CONFIG_PATH, true);
+
+ // instantiate the scene repository
+ SceneRepository screp = null;
+ try {
+ screp = (SceneRepository)config.instantiateValue(
+ SCENEREP_KEY, DEF_SCENEREP);
+ } catch (Exception e) {
+ Log.warning("Unable to instantiate scene repository " +
+ "[error=" + e + "].");
+ throw new IOException("Fatal init failure");
+ }
+
+ // create our scene registry
+ screg = new SceneRegistry(screp);
+
+ // register our invocation service providers
+ registerProviders(config.getValue(PROVIDERS_KEY, (String[])null));
+
+ Log.info("Whirled server initialized.");
+ }
+
+ public static void main (String[] args)
+ {
+ WhirledServer server = new WhirledServer();
+ 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/whirled/server";
+
+ // the config key for our list of invocation provider mappings
+ 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 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
new file mode 100644
index 000000000..f12cec170
--- /dev/null
+++ b/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java
@@ -0,0 +1,30 @@
+//
+// $Id: DummySceneRepository.java,v 1.1 2001/08/11 04:09:50 mdb Exp $
+
+package com.threerings.whirled.server.persist;
+
+import java.io.IOException;
+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 getScene (int sceneid)
+ throws IOException, NoSuchSceneException
+ {
+ return null;
+ }
+
+ // documentation inherited
+ public void updateScene (Scene scene)
+ throws IOException
+ {
+ // nothing doing
+ }
+}
diff --git a/src/java/com/threerings/whirled/server/persist/SceneRepository.java b/src/java/com/threerings/whirled/server/persist/SceneRepository.java
new file mode 100644
index 000000000..739050bb2
--- /dev/null
+++ b/src/java/com/threerings/whirled/server/persist/SceneRepository.java
@@ -0,0 +1,32 @@
+//
+// $Id: SceneRepository.java,v 1.1 2001/08/11 04:09:50 mdb Exp $
+
+package com.threerings.whirled.server.persist;
+
+import java.io.IOException;
+import com.threerings.whirled.data.Scene;
+import com.threerings.whirled.util.NoSuchSceneException;
+
+public interface SceneRepository
+{
+ /**
+ * Fetches the scene with the specified scene id.
+ *
+ * @exception IOException thrown if an error occurs attempting to load
+ * a scene.
+ * @exception NoSuchSceneException thrown if no scene exists with the
+ * specified scene id.
+ */
+ public Scene getScene (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/util/NoSuchSceneException.java b/src/java/com/threerings/whirled/util/NoSuchSceneException.java
new file mode 100644
index 000000000..a57274e6c
--- /dev/null
+++ b/src/java/com/threerings/whirled/util/NoSuchSceneException.java
@@ -0,0 +1,24 @@
+//
+// $Id: NoSuchSceneException.java,v 1.1 2001/08/11 04:09:51 mdb Exp $
+
+package com.threerings.whirled.util;
+
+/**
+ * Thrown when an attempt to load a non-existent scene is made on the
+ * repository.
+ */
+public class NoSuchSceneException extends Exception
+{
+ public NoSuchSceneException (int sceneid)
+ {
+ super("No such scene [sceneid=" + sceneid + "]");
+ _sceneid = sceneid;
+ }
+
+ public int getSceneId ()
+ {
+ return _sceneid;
+ }
+
+ protected int _sceneid;
+}