Files
vilya/src/java/com/threerings/whirled/server/SceneRegistry.java
T
Ray Greenwell 8703f5b0a8 Don't allow scene resolution to continue if the updates couldn't load.
They *are* just updates, but if it's ok for them to not load then a
game-specific scene repository needs to make that decision and just
return a blank update list rather than throwing an exception.


git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@68 c613c5cb-e716-0410-b11b-feb51c14d237
2006-09-01 21:07:27 +00:00

360 lines
13 KiB
Java

//
// $Id: SceneRegistry.java 3679 2005-08-11 22:30:40Z ray $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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.server.InvocationManager;
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.data.Scene;
import com.threerings.whirled.data.SceneModel;
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.
*
* <p> 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.
*
* <p><em>Note:</em> All access to the scene registry should take place
* from the dobjmgr thread.
*/
public class SceneRegistry
{
/**
* 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);
}
/** Used to provide scene-related server-side services. */
public SceneProvider sceneprov;
/**
* 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;
// create/register a scene provider with the invocation services
sceneprov = new SceneProvider(CrowdServer.plreg.locprov, this);
invmgr.registerDispatcher(new SceneDispatcher(sceneprov), true);
}
/**
* 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
* <code>null:sceneId</code> if no scene manager exists for that
* scene.
*/
public String where (int sceneId)
{
SceneManager scmgr = getSceneManager(sceneId);
return (scmgr == null) ? ("null:" + sceneId) : scmgr.where();
}
/**
* 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 {@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.
// first we 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 and all that
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;
});
}
}
/**
* 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();
}