// // $Id$ // // Vilya library - tools for developing networked games // Copyright (C) 2002-2007 Three Rings Design, Inc., All Rights Reserved // http://www.threerings.net/code/vilya/ // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published // by the Free Software Foundation; either version 2.1 of the License, or // (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA package com.threerings.whirled.server; import java.util.ArrayList; import com.samskivert.util.HashIntMap; import com.samskivert.util.Invoker; import com.threerings.presents.data.ClientObject; import com.threerings.presents.data.InvocationMarshaller; import com.threerings.presents.server.InvocationException; import com.threerings.presents.server.InvocationManager; import com.threerings.crowd.data.BodyObject; import com.threerings.crowd.data.PlaceConfig; import com.threerings.crowd.server.CrowdServer; import com.threerings.crowd.server.PlaceManager; import com.threerings.crowd.server.PlaceRegistry; import com.threerings.whirled.Log; import com.threerings.whirled.client.SceneService; import com.threerings.whirled.data.Scene; import com.threerings.whirled.data.SceneCodes; import com.threerings.whirled.data.SceneModel; import com.threerings.whirled.data.SceneUpdate; import com.threerings.whirled.server.persist.SceneRepository; import com.threerings.whirled.util.SceneFactory; import com.threerings.whirled.util.UpdateList; /** * The scene registry is responsible for the management of all 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 implements SceneCodes, SceneProvider { /** * Used to create {@link PlaceConfig} instances for scenes. */ public static interface ConfigFactory { /** * Creates the place config instance appropriate to the specified scene. */ PlaceConfig createPlaceConfig (SceneModel model); } /** * 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); } /** * Constructs a scene registry, instructing it to load and store scenes using the supplied * scene repository. */ public SceneRegistry (InvocationManager invmgr, SceneRepository screp, SceneFactory scfact, ConfigFactory confact) { _screp = screp; _scfact = scfact; _confact = confact; // register our scene service invmgr.registerDispatcher(new SceneDispatcher(this), SceneCodes.WHIRLED_GROUP); } /** * Fetches the scene manager assosciated with the specified scene. * * @return the scene manager for the specified scene or null if no scene manager is loaded for * that scene. */ public SceneManager getSceneManager (int sceneId) { return (SceneManager)_scenemgrs.get(sceneId); } /** * Returns a reference to the scene repository in use by this registry. */ public SceneRepository getSceneRepository () { return _screp; } /** * Returns {@link SceneManager#where} for the specified scene or null:sceneId if * no scene manager exists for that scene. */ public String where (int sceneId) { SceneManager scmgr = getSceneManager(sceneId); return (scmgr == null) ? ("null:" + sceneId) : scmgr.where(); } /** * 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 {@link #resolveScene} returns). * * @param sceneId the id of the scene to resolve. * @param target 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; } if (Log.debug()) { Log.debug("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 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, create the penders list _penders.put(sceneId, penders = new ArrayList()); penders.add(target); // i don't like cluttering up method declarations with final keywords... final int fsceneId = sceneId; if (Log.debug()) { Log.debug("Invoking scene lookup [id=" + sceneId + "]."); } // then we queue up an execution unit that'll load the scene and initialize it etc. WhirledServer.invoker.postUnit(new Invoker.Unit() { // this is run on the invoker thread public boolean invoke () { try { _model = _screp.loadSceneModel(fsceneId); _updates = _screp.loadUpdates(fsceneId); } catch (Exception e) { _cause = e; } return true; } // this is run on the dobjmgr thread public void handleResult () { if (_cause != null) { processFailedResolution(fsceneId, _cause); } else if (_model != null) { processSuccessfulResolution(_model, _updates); } else { Log.warning("Scene loading unit finished with neither a scene nor a " + "reason for failure!?"); } } public String toString () { return "SceneRegistry.SceneLoader " + (_model == null ? "" : _model.name) + "(" + fsceneId + ")"; } protected SceneModel _model; protected UpdateList _updates; protected Exception _cause; }); } } // from interface SceneService public void moveTo (ClientObject caller, int sceneId, int sceneVer, SceneService.SceneMoveListener listener) { BodyObject body = (BodyObject)caller; resolveScene(sceneId, new SceneMoveHandler(body, sceneVer, listener)); } /** * Ejects the specified body from their current scene and sends them a request to move to the * specified new scene. This is the scene-equivalent to {@link LocationProvider#moveBody}. */ public void moveBody (BodyObject source, int sceneId) { // first remove them from their old place CrowdServer.plreg.locprov.leaveOccupiedPlace(source); // then send a forced move notification SceneSender.forcedMove(source, sceneId); } /** * Ejects the specified body from their current scene and zone. This is the zone equivalent to * {@link LocationProvider#leaveOccupiedPlace}. */ public void leaveOccupiedScene (BodyObject source) { // remove them from their occupied place (clears out scene info as well) CrowdServer.plreg.locprov.leaveOccupiedPlace(source); } /** * Called when the scene resolution has completed successfully. */ protected void processSuccessfulResolution (SceneModel model, final UpdateList updates) { // 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 try { // first create our scene instance final Scene scene = _scfact.createScene(model, _confact.createPlaceConfig(model)); // now create our scene manager CrowdServer.plreg.createPlace( scene.getPlaceConfig(), new PlaceRegistry.PreStartupHook() { public void invoke (PlaceManager pmgr) { ((SceneManager)pmgr).setSceneData(scene, updates, SceneRegistry.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 (Exception e) { // so close, but no cigar processFailedResolution(model.sceneId, e); } } /** * Called if resolving the scene fails for some reason. */ protected void processFailedResolution (int sceneId, Exception cause) { Log.info("Failed to resolve scene [sceneId=" + sceneId + ", cause=" + cause + "]."); Log.logStackTrace(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); } } } } /** * Called by the scene manager once it has started up (meaning that it has its place object and * is ready to roll). */ protected void sceneManagerDidStart (SceneManager scmgr) { // register this scene manager in our table int sceneId = scmgr.getScene().getId(); _scenemgrs.put(sceneId, scmgr); if (Log.debug()) { Log.debug("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); } } } } /** * Called by the scene manager when it is shut down. */ protected void unmapSceneManager (SceneManager scmgr) { if (_scenemgrs.remove(scmgr.getScene().getId()) == null) { Log.warning("Requested to unmap unmapped scene manager [scmgr=" + scmgr + "]."); return; } if (Log.debug()) { Log.debug("Unmapped scene manager " + scmgr + "."); } } /** The entity from which we load scene models. */ protected SceneRepository _screp; /** Used to generate place configs for our scenes. */ protected ConfigFactory _confact; /** The entity via which we create scene instances from scene models. */ protected SceneFactory _scfact; /** A mapping from scene ids to scene managers. */ protected HashIntMap _scenemgrs = new HashIntMap(); /** The table of pending resolution listeners. */ protected HashIntMap _penders = new HashIntMap(); }