// // $Id: SceneDirector.java,v 1.11 2002/04/15 16:28:03 shaper Exp $ package com.threerings.whirled.client; import java.io.IOException; import com.samskivert.util.HashIntMap; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.ObjectAccessException; import com.threerings.crowd.client.LocationDirector; import com.threerings.crowd.client.LocationObserver; import com.threerings.crowd.data.PlaceConfig; import com.threerings.crowd.data.PlaceObject; import com.threerings.whirled.Log; import com.threerings.whirled.client.persist.SceneRepository; import com.threerings.whirled.data.SceneCodes; import com.threerings.whirled.data.SceneModel; import com.threerings.whirled.util.NoSuchSceneException; import com.threerings.whirled.util.WhirledContext; /** * The scene director 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 coordinates with the {@link * LocationDirector} in order to do this). * *
Note that when the scene director is in use instead of the location
* director, scene ids instead of place oids will be supplied to {@link
* LocationObserver#locationMayChange} and {@link
* LocationObserver#locationChangeFailed}.
*/
public class SceneDirector
implements SceneCodes, LocationDirector.FailureHandler
{
/**
* Creates a new scene director with the specified context.
*
* @param ctx the active client context.
* @param locdir the location director in use on the client, with
* which the scene director will coordinate when changing location.
* @param screp the entity from which the scene director will load
* scene data from the local client scene storage.
* @param dsfact the factory that knows which derivation of {@link
* DisplayScene} to create for the current system.
*/
public SceneDirector (WhirledContext ctx, LocationDirector locdir,
SceneRepository screp, DisplaySceneFactory dsfact)
{
// we'll need these for later
_ctx = ctx;
_locdir = locdir;
_screp = screp;
_dsfact = dsfact;
// set ourselves up as a failure handler with the location
// director because we need to do special processing
_locdir.setFailureHandler(this);
}
/**
* Returns the dispaly scene object associated with the scene we
* currently occupy or null if we currently occupy no scene.
*/
public DisplayScene 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)
{
// prepare to move to this scene (sets up pending data)
if (!prepareMoveTo(sceneId)) {
return;
}
// check the version of our cached copy of the scene to which
// we're requesting to move; if we were unable to load it, assume
// a cached version of zero
int sceneVers = 0;
if (_pendingModel != null) {
sceneVers = _pendingModel.version;
}
// issue a moveTo request
SceneService.moveTo(_ctx.getClient(), sceneId, sceneVers, this);
}
/**
* Prepares to move to the requested scene. The location observers are
* asked to ratify the move and our pending scene mode is loaded from
* the scene repository. This can be called by cooperating directors
* that need to coopt the moveTo process.
*/
public boolean prepareMoveTo (int sceneId)
{
// first check to see if our observers are happy with this move
// request
if (!_locdir.mayMoveTo(sceneId)) {
return false;
}
// 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
_pendingModel = loadSceneModel(sceneId);
// make a note of our pending scene id
_pendingSceneId = sceneId;
// all systems go
return true;
}
/**
* Returns the model loaded in preparation for a scene
* transition. This is made available only for cooperating directors
* which may need to coopt the scene transition process. The pending
* model is only valid immediately following a call to {@link
* #prepareMoveTo}.
*/
public SceneModel getPendingModel ()
{
return _pendingModel;
}
/**
* Called in response to a successful moveTo request.
*/
public void handleMoveSucceeded (
int invid, int placeId, PlaceConfig config)
{
// our move request was successful, deal with subscribing to our
// new place object
_locdir.didMoveTo(placeId, config);
// 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
// keep track of our previous scene info
_previousSceneId = _sceneId;
// clear out the old info
clearScene();
// make the pending scene the active scene
_sceneId = _pendingSceneId;
_pendingSceneId = -1;
// load the new scene model
_model = loadSceneModel(_sceneId);
// complain if we didn't find a scene
if (_model == null) {
Log.warning("Aiya! Unable to load scene [sid=" + _sceneId +
", plid=" + placeId + "].");
}
// and finally create a display scene instance with the model and
// the place config
_scene = _dsfact.createScene(_model, config);
}
/**
* Called in response to a successful moveTo request when
* our cached scene was out of date and the server determined that we
* needed an updated copy.
*/
public void handleMoveSucceededPlusUpdate (
int invid, int placeId, PlaceConfig config, SceneModel model)
{
// update the model in the repository
try {
_screp.updateSceneModel(model);
} catch (IOException ioe) {
Log.warning("Danger Will Robinson! We were unable to update " +
"our scene cache with a new version of a scene " +
"provided by the server " +
"[newVersion=" + model.version + "].");
Log.logStackTrace(ioe);
}
// update our scene cache
_scache.put(model.sceneId, model);
// and pass through to the normal move succeeded handler
handleMoveSucceeded(invid, placeId, config);
}
/**
* Called in response to a failed moveTo 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
_locdir.failedToMoveTo(sceneId, reason);
}
/**
* Called when something breaks down in the process of performing a
* moveTo request.
*/
public void recoverFailedMove (int placeId)
{
// we'll need this momentarily
int sceneId = _sceneId;
// clear out our now bogus scene tracking info
clearScene();
// 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);
}
}
/**
* Clears out our current scene information and releases the scene
* model for the loaded scene back to the cache.
*/
protected void clearScene ()
{
// clear out our scene id info
_sceneId = -1;
// release the old scene model
releaseSceneModel(_model);
// clear out our references
_model = null;
_scene = null;
}
/**
* Loads a scene from the repository. If the scene is cached, it will
* be returned from the cache instead.
*/
protected SceneModel loadSceneModel (int sceneId)
{
// first look in the model cache
SceneModel model = (SceneModel)_scache.get(sceneId);
// load from the repository if it's not cached
if (model == null) {
try {
model = _screp.loadSceneModel(sceneId);
_scache.put(sceneId, model);
} 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 model;
}
/**
* Unloads a scene model that was previously loaded via {@link
* #loadSceneModel}. The model will probably continue to live in the
* cache for a while in case we quickly return to it.
*/
protected void releaseSceneModel (SceneModel model)
{
// we're cool if we're called with null
if (model == null) {
return;
}
}
/** Access to general client services. */
protected WhirledContext _ctx;
/** The client's active location director. */
protected LocationDirector _locdir;
/** The entity via which we load scene data. */
protected SceneRepository _screp;
/** The entity we use to create display scenes from scene models. */
protected DisplaySceneFactory _dsfact;
/** A cache of scene model information. */
protected HashIntMap _scache = new HashIntMap();
/** The display scene object for the scene we currently occupy. */
protected DisplayScene _scene;
/** The scene model for the scene we currently occupy. */
protected SceneModel _model;
/** The id of the scene we currently occupy. */
protected int _sceneId = -1;
/** Our most recent copy of the scene model for the scene we're about
* to enter. */
protected SceneModel _pendingModel;
/** 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;
}