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; +}