diff --git a/src/as/com/threerings/whirled/client/SceneController.as b/src/as/com/threerings/whirled/client/SceneController.as new file mode 100644 index 00000000..b337aaca --- /dev/null +++ b/src/as/com/threerings/whirled/client/SceneController.as @@ -0,0 +1,92 @@ +// +// $Id: SceneController.java 3099 2004-08-27 02:21:06Z mdb $ +// +// 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.client { + +import com.threerings.presents.dobj.MessageEvent; +import com.threerings.presents.dobj.MessageListener; + +import com.threerings.crowd.client.PlaceController; +import com.threerings.crowd.data.PlaceConfig; +import com.threerings.crowd.data.PlaceObject; +import com.threerings.crowd.util.CrowdContext; + +import com.threerings.whirled.data.SceneCodes; +import com.threerings.whirled.data.SceneUpdate; +import com.threerings.whirled.util.WhirledContext; + +/** + * The base scene controller class. It is expected that users of the + * Whirled services will extend this controller class when creating + * specialized controllers for their scenes. + */ +public /*abstract*/ class SceneController extends PlaceController +{ + // documentation inherited + override public function init (ctx :CrowdContext, config :PlaceConfig) :void + { + super.init(ctx, config); + _wctx = WhirledContext(ctx); + } + + // documentation inherited + override public function willEnterPlace (plobj :PlaceObject) :void + { + super.willEnterPlace(plobj); + plobj.addListener(_updateListener); + } + + // documentation inherited + override public function didLeavePlace (plobj :PlaceObject) :void + { + super.didLeavePlace(plobj); + plobj.removeListener(_updateListener); + } + + /** + * This method is called if a scene update is recorded while we + * currently occupy a scene. The default implementation will update + * our local scene and scene model, but derived classes will likely + * want to ensure that the update is properly displayed. + */ + protected function sceneUpdated (update :SceneUpdate) :void + { + // apply the update to the scene + _wctx.getSceneDirector().getScene().updateReceived(update); + + // we don't persistify these updates in this circumstance, but + // next time we come to this scene we'll redownload the update and + // apply it to the repository; as the updates are meant to be very + // small, this shouldn't be horribly less efficient + } + + /** Used to listen for scene updates. */ + protected var _updateListener :MessageListener = new MessageAdapter( + function (event :MessageEvent) :void { + if (event.getName() == SceneCodes.SCENE_UPDATE) { + sceneUpdated(event.getArgs()[0] as SceneUpdate); + } + } + ); + + protected var _wctx :WhirledContext; +} +} diff --git a/src/as/com/threerings/whirled/client/SceneDecoder.as b/src/as/com/threerings/whirled/client/SceneDecoder.as new file mode 100644 index 00000000..aad2769f --- /dev/null +++ b/src/as/com/threerings/whirled/client/SceneDecoder.as @@ -0,0 +1,68 @@ +// +// $Id: SceneDecoder.java 3914 2006-03-06 21:51:59Z mdb $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2006 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.client { + +import com.threerings.presents.client.InvocationDecoder; +import com.threerings.whirled.client.SceneReceiver; + +/** + * Dispatches calls to a {@link SceneReceiver} instance. + */ +public class SceneDecoder extends InvocationDecoder +{ + /** The generated hash code used to identify this receiver class. */ + public static const RECEIVER_CODE :String = + "c4d0cf66b81a6e83d119b2d607725651"; + + /** The method id used to dispatch {@link SceneReceiver#forcedMove} + * notifications. */ + public static const FORCED_MOVE :int = 1; + + /** + * Creates a decoder that may be registered to dispatch invocation + * service notifications to the specified receiver. + */ + public function SceneDecoder (receiver :SceneReceiver) + { + this.receiver = receiver; + } + + // documentation inherited + override public function getReceiverCode () :String + { + return RECEIVER_CODE; + } + + // documentation inherited + override public function dispatchNotification (methodId :int, args :Array) :void + { + switch (methodId) { + case FORCED_MOVE: + (receiver as SceneReceiver).forcedMove(args[0] as int); + return; + + default: + super.dispatchNotification(methodId, args); + } + } +} +} diff --git a/src/as/com/threerings/whirled/client/SceneDirector.as b/src/as/com/threerings/whirled/client/SceneDirector.as new file mode 100644 index 00000000..7343cf96 --- /dev/null +++ b/src/as/com/threerings/whirled/client/SceneDirector.as @@ -0,0 +1,527 @@ +// +// $Id: SceneDirector.java 4088 2006-05-04 00:39:46Z mjohnson $ +// +// 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.client { + +import flash.errors.IOError; +import flash.errors.IllegalOperationError; + +import com.threerings.util.HashMap; +import com.threerings.util.ResultListener; + +import com.threerings.io.TypedArray; + +import com.threerings.presents.client.BasicDirector; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.ClientEvent; +import com.threerings.presents.data.InvocationCodes; + +import com.threerings.crowd.client.LocationDirector; +import com.threerings.crowd.client.LocationDirector_FailureHandler; +import com.threerings.crowd.client.LocationObserver; +import com.threerings.crowd.data.PlaceConfig; + +import com.threerings.whirled.client.persist.SceneRepository; +import com.threerings.whirled.data.Scene; +import com.threerings.whirled.data.SceneCodes; +import com.threerings.whirled.data.SceneModel; +import com.threerings.whirled.util.NoSuchSceneError; +import com.threerings.whirled.util.SceneFactory; +import com.threerings.whirled.util.WhirledContext; +import com.threerings.whirled.data.SceneUpdate; + +/** + * 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 extends BasicDirector
+ implements LocationDirector_FailureHandler,
+ SceneReceiver, SceneService_SceneMoveListener
+{
+ private static const log :Log = Log.getLog(SceneDirector);
+
+ /**
+ * 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. This may be null
+ * when the SceneDirector is constructed, but it should be
+ * supplied via {@link #setSceneRepository} prior to really using
+ * this director.
+ * @param fact the factory that knows which derivation of {@link
+ * Scene} to create for the current system.
+ */
+ public function SceneDirector (
+ ctx :WhirledContext, locdir :LocationDirector,
+ screp :SceneRepository, fact :SceneFactory)
+ {
+ super(ctx);
+
+ // we'll need these for later
+ _wctx = ctx;
+ _locdir = locdir;
+ setSceneRepository(screp);
+ _fact = fact;
+
+ // set ourselves up as a failure handler with the location
+ // director because we need to do special processing
+ _locdir.setFailureHandler(this);
+
+ // register for scene notifications
+ _wctx.getClient().getInvocationDirector().registerReceiver(
+ new SceneDecoder(this));
+ }
+
+ /**
+ * Set the scene repository.
+ */
+ public function setSceneRepository (screp :SceneRepository) :void
+ {
+ _screp = screp;
+ _scache.clear();
+ }
+
+ /**
+ * Returns the display scene object associated with the scene we
+ * currently occupy or null if we currently occupy no scene.
+ */
+ public function getScene () :Scene
+ {
+ 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.
+ *
+ * @return true if the move to request was issued, false if it was
+ * rejected by a location observer or because we have another request
+ * outstanding.
+ */
+ public function moveTo (sceneId :int) :Boolean
+ {
+ // make sure the sceneId is valid
+ if (sceneId < 0) {
+ log.warning("Refusing moveTo(): invalid sceneId " + sceneId + ".");
+ return false;
+ }
+
+ // sanity-check the destination scene id
+ if (sceneId == _sceneId) {
+ log.warning("Refusing request to move to the same scene " +
+ "[sceneId=" + sceneId + "].");
+ return false;
+ }
+
+ // prepare to move to this scene (sets up pending data)
+ if (!prepareMoveTo(sceneId, null)) {
+ return false;
+ }
+
+ // 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
+ var sceneVers :int = 0;
+ if (_pendingModel != null) {
+ sceneVers = _pendingModel.version;
+ }
+
+ // issue a moveTo request
+ log.info("Issuing moveTo(" + sceneId + ", " + sceneVers + ").");
+ _sservice.moveTo(_wctx.getClient(), sceneId, sceneVers, this);
+ return true;
+ }
+
+ /**
+ * 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 function prepareMoveTo (sceneId :int, rl :ResultListener) :Boolean
+ {
+ // first check to see if our observers are happy with this move
+ // request
+ if (!_locdir.mayMoveTo(sceneId, rl)) {
+ return false;
+ }
+
+ // we need to call this both to mark that we're issuing a move
+ // request and to check to see if the last issued request should
+ // be considered stale
+ var refuse :Boolean = _locdir.checkRepeatMove();
+
+ // complain if we're over-writing a pending request
+ if (_pendingSceneId != -1) {
+ if (refuse) {
+ log.warning("Refusing moveTo; We have a request outstanding " +
+ "[psid=" + _pendingSceneId +
+ ", nsid=" + sceneId + "].");
+ return false;
+
+ } else {
+ log.warning("Overriding stale moveTo request " +
+ "[psid=" + _pendingSceneId +
+ ", nsid=" + sceneId + "].");
+ }
+ }
+
+ // 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 function getPendingModel () :SceneModel
+ {
+ return _pendingModel;
+ }
+
+ // documentation inherited from interface SceneService_SceneMoveListener
+ public function moveSucceeded (placeId :int, config :PlaceConfig) :void
+ {
+ // 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 + "].");
+ return;
+ }
+
+ // and finally create a display scene instance with the model and
+ // the place config
+ _scene = _fact.createScene(_model, config);
+ }
+
+ // documentation inherited from interface SceneService_SceneMoveListener
+ public function moveSucceededWithUpdates (
+ placeId :int, config :PlaceConfig, updates :TypedArray) :void
+ {
+ log.info("Got updates [placeId=" + placeId + ", config=" + config +
+ ", updates=" + updates + "].");
+
+ // apply the updates to our cached scene
+ var model :SceneModel = loadSceneModel(_pendingSceneId);
+ var failure :Boolean = false;
+ for each (var update :SceneUpdate in updates) {
+ try {
+ update.validate(model);
+ } catch (ise :IllegalOperationError) {
+ log.warning("Scene update failed validation [model=" + model +
+ ", update=" + update +
+ ", error=" + ise.getMessage() + "].");
+ failure = true;
+ break;
+ }
+
+ try {
+ update.apply(model);
+ } catch (e :Error) {
+ log.warning("Failure applying scene update [model=" + model +
+ ", update=" + update + "].");
+ log.logStackTrace(e);
+ failure = true;
+ break;
+ }
+ }
+
+ if (failure) {
+ // delete the now half-booched scene model from the repository
+ try {
+ _screp.deleteSceneModel(_pendingSceneId);
+ } catch (ioe :IOError) {
+ log.warning("Failure removing booched scene model " +
+ "[sceneId=" + _pendingSceneId + "].");
+ log.logStackTrace(ioe);
+ }
+
+ // act as if the scene move failed, though we'll be in a funny
+ // state because the server thinks we've changed scenes, but
+ // the client can try again without its booched scene model
+ requestFailed(InvocationCodes.INTERNAL_ERROR);
+ return;
+ }
+
+ // store the updated scene in the repository
+ try {
+ _screp.storeSceneModel(model);
+ } catch (ioe :IOError) {
+ log.warning("Failed to update repository with updated scene " +
+ "[sceneId=" + model.sceneId + "].");
+ log.logStackTrace(ioe);
+ }
+
+ // finally pass through to the normal success handler
+ moveSucceeded(placeId, config);
+ }
+
+ // documentation inherited from interface SceneService-SceneMoveListener
+ public function moveSucceededWithScene (
+ placeId :int, config :PlaceConfig, model :SceneModel) :void
+ {
+ log.info("Got updated scene model [placeId=" + placeId +
+ ", config=" + config + ", scene=" + model.sceneId + "/" +
+ model.name + "/" + model.version + "].");
+
+ // update the model in the repository
+ try {
+ _screp.storeSceneModel(model);
+ } catch (ioe :IOError) {
+ log.warning("Failed to update repository with new version " +
+ "[sceneId=" + model.sceneId +
+ ", nvers=" + model.version + "].");
+ log.logStackTrace(ioe);
+ }
+
+ // update our scene cache
+ _scache.put(model.sceneId, model);
+
+ // and pass through to the normal move succeeded handler
+ moveSucceeded(placeId, config);
+ }
+
+ // documentation inherited from interface
+ public function requestFailed (reason :String) :void
+ {
+ // clear out our pending request oid
+ var sceneId :int = _pendingSceneId;
+ _pendingSceneId = -1;
+
+ // let our observers know that something has gone horribly awry
+ _locdir.failedToMoveTo(sceneId, reason);
+ }
+
+ /**
+ * Called to clean up our place and scene state information when we
+ * leave a scene.
+ */
+ public function didLeaveScene () :void
+ {
+ // let the location director know what's up
+ _locdir.didLeavePlace();
+
+ // clear out our own scene state
+ clearScene();
+ }
+
+ // documentation inherited from interface
+ public function forcedMove (sceneId :int) :void
+ {
+ log.info("Moving at request of server [sceneId=" + sceneId + "].");
+
+ // clear out our old scene and place data
+ didLeaveScene();
+
+ // move to the new scene
+ moveTo(sceneId);
+ }
+
+ /**
+ * Sets the moveHandler for use in recoverFailedMove.
+ */
+ public function setMoveHandler (handler :SceneDirector_MoveHandler) :void
+ {
+ if (_moveHandler != null) {
+ log.warning("Requested to set move handler, but we've " +
+ "already got one. The conflicting entities will " +
+ "likely need to perform more sophisticated " +
+ "coordination to deal with failures. " +
+ "[old=" + _moveHandler + ", new=" + handler + "].");
+
+ } else {
+ _moveHandler = handler;
+ }
+ }
+
+ /**
+ * Called when something breaks down in the process of performing a
+ * moveTo request.
+ */
+ public function recoverFailedMove (placeId :int) :void
+ {
+ // we'll need this momentarily
+ var sceneId :int = _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) {
+ // if we have a move handler use that
+ if (_moveHandler != null) {
+ _moveHandler.recoverMoveTo(_previousSceneId);
+
+ } else {
+ moveTo(_previousSceneId);
+ }
+ }
+ }
+
+ /**
+ * Clears out our current scene information and releases the scene
+ * model for the loaded scene back to the cache.
+ */
+ protected function clearScene () :void
+ {
+ // clear out our scene id info
+ _sceneId = -1;
+
+ // 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 function loadSceneModel (sceneId :int) :SceneModel
+ {
+ // first look in the model cache
+ var model :SceneModel = (_scache.get(sceneId) as SceneModel);
+
+ // load from the repository if it's not cached
+ if (model == null) {
+ try {
+ model = _screp.loadSceneModel(sceneId);
+ _scache.put(sceneId, model);
+
+ } catch (nsse :NoSuchSceneError) {
+ // nothing special here, just fall through and return null
+
+ } catch (ioe :IOError) {
+ // complain first, then return null
+ log.warning("Error loading scene [scid=" + sceneId +
+ ", error=" + ioe + "].");
+ }
+ }
+
+ return model;
+ }
+
+ // documentation inherited
+ override public function clientDidLogoff (event :ClientEvent) :void
+ {
+ super.clientDidLogoff(event);
+
+ // clear out our business
+ clearScene();
+ _scache.clear();
+ _pendingSceneId = -1;
+ _pendingModel = null;
+ _previousSceneId = -1;
+ _sservice = null;
+ }
+
+ // documentation inherited
+ override protected function fetchServices (client :Client) :void
+ {
+ // get a handle on our scene service
+ _sservice = (client.requireService(SceneService) as SceneService);
+ }
+
+ /** Access to general client services. */
+ protected var _wctx :WhirledContext;
+
+ /** Access to our scene services. */
+ protected var _sservice :SceneService;
+
+ /** The client's active location director. */
+ protected var _locdir :LocationDirector;
+
+ /** The entity via which we load scene data. */
+ protected var _screp :SceneRepository;
+
+ /** The entity we use to create scenes from scene models. */
+ protected var _fact :SceneFactory;
+
+ /** A cache of scene model information. */
+ protected var _scache :HashMap = new HashMap(); // TODO: LRUHashMap(5)
+
+ /** The display scene object for the scene we currently occupy. */
+ protected var _scene :Scene;
+
+ /** The scene model for the scene we currently occupy. */
+ protected var _model :SceneModel;
+
+ /** The id of the scene we currently occupy. */
+ protected var _sceneId :int = -1;
+
+ /** Our most recent copy of the scene model for the scene we're about
+ * to enter. */
+ protected var _pendingModel :SceneModel;
+
+ /** The id of the scene for which we have an outstanding moveTo
+ * request, or -1 if we have no outstanding request. */
+ protected var _pendingSceneId :int = -1;
+
+ /** The id of the scene we previously occupied. */
+ protected var _previousSceneId :int = -1;
+
+ /** Reference to our move handler. */
+ protected var _moveHandler :SceneDirector_MoveHandler = null;
+}
+}
diff --git a/src/as/com/threerings/whirled/client/SceneDirector_MoveHandler.as b/src/as/com/threerings/whirled/client/SceneDirector_MoveHandler.as
new file mode 100644
index 00000000..35eb712e
--- /dev/null
+++ b/src/as/com/threerings/whirled/client/SceneDirector_MoveHandler.as
@@ -0,0 +1,14 @@
+package com.threerings.whirled.client {
+
+/**
+ * Used to recover from a problem after a completed moveTo.
+ */
+public interface SceneDirector_MoveHandler
+{
+ /**
+ * Should instruct the client to move the last known working
+ * location (as well as clean up after the failed moveTo request).
+ */
+ function recoverMoveTo (sceneId :int) :void;
+}
+}
diff --git a/src/as/com/threerings/whirled/client/SceneReceiver.as b/src/as/com/threerings/whirled/client/SceneReceiver.as
new file mode 100644
index 00000000..3941b91e
--- /dev/null
+++ b/src/as/com/threerings/whirled/client/SceneReceiver.as
@@ -0,0 +1,40 @@
+//
+// $Id: SceneReceiver.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.client {
+
+import com.threerings.presents.client.InvocationReceiver;
+
+/**
+ * Defines, for the scene services, a set of notifications delivered
+ * asynchronously by the server to the client.
+ */
+public interface SceneReceiver extends InvocationReceiver
+{
+ /**
+ * Used to communicate a required move notification to the client. The
+ * server will have removed the client from their existing scene
+ * and the client is then responsible for generating a {@link
+ * SceneService#moveTo} request to move to the new scene.
+ */
+ function forcedMove (sceneId :int) :void
+}
+}
diff --git a/src/as/com/threerings/whirled/client/SceneService.as b/src/as/com/threerings/whirled/client/SceneService.as
new file mode 100644
index 00000000..1e8294bc
--- /dev/null
+++ b/src/as/com/threerings/whirled/client/SceneService.as
@@ -0,0 +1,50 @@
+//
+// $Id: SceneService.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.client {
+
+import com.threerings.presents.client.Client;
+import com.threerings.presents.client.InvocationService;
+
+import com.threerings.crowd.data.PlaceConfig;
+
+import com.threerings.whirled.data.SceneModel;
+import com.threerings.whirled.data.SceneUpdate;
+
+/**
+ * The scene service class provides the client interface to the scene
+ * related invocation services (e.g. moving from scene to scene).
+ */
+public interface SceneService extends InvocationService
+{
+ /**
+ * Requests that that this client's body be moved to the specified
+ * scene.
+ *
+ * @param sceneId the scene id to which we want to move.
+ * @param version the version number of the scene object that we have
+ * in our local repository.
+ */
+ function moveTo (
+ client :Client, sceneId :int, version :int,
+ listener :SceneService_SceneMoveListener) :void;
+}
+}
diff --git a/src/as/com/threerings/whirled/client/SceneService_SceneMoveListener.as b/src/as/com/threerings/whirled/client/SceneService_SceneMoveListener.as
new file mode 100644
index 00000000..2809b5f5
--- /dev/null
+++ b/src/as/com/threerings/whirled/client/SceneService_SceneMoveListener.as
@@ -0,0 +1,50 @@
+package com.threerings.whirled.client {
+
+import com.threerings.presents.client.InvocationListener;
+
+import com.threerings.io.TypedArray;
+
+import com.threerings.crowd.data.PlaceConfig;
+
+import com.threerings.whirled.data.SceneModel;
+import com.threerings.whirled.data.SceneUpdate;
+
+/**
+ * Used to communicate the response to a {@link #moveTo} request.
+ */
+public interface SceneService_SceneMoveListener extends InvocationListener
+{
+ /**
+ * Indicates that a move succeeded.
+ *
+ * @param placeId the place object id of the newly occupied scene.
+ * @param config metadata related to the newly occupied scene.
+ */
+ function moveSucceeded (placeId :int, config :PlaceConfig) :void;
+
+ /**
+ * Indicates that a move succeeded and that the client's cached
+ * scene information should be updated with the supplied data.
+ *
+ * @param placeId the place object id of the newly occupied scene.
+ * @param config metadata related to the newly occupied scene.
+ * @param updates updates that must be applied to the client's
+ * copy of a scene model to bring it up to date.
+ */
+ function moveSucceededWithUpdates (
+ placeId :int, config :PlaceConfig,
+ updates :TypedArray /*of SceneUpdate*/) :void;
+
+ /**
+ * Indicates that a move succeeded and that the client's cached
+ * scene information should be updated with the supplied data.
+ *
+ * @param placeId the place object id of the newly occupied scene.
+ * @param config metadata related to the newly occupied scene.
+ * @param model a fresh copy of the most recent scene data for the
+ * newly occupied scene.
+ */
+ function moveSucceededWithScene (
+ placeId :int, config :PlaceConfig, model :SceneModel) :void;
+}
+}
diff --git a/src/as/com/threerings/whirled/client/persist/SceneRepository.as b/src/as/com/threerings/whirled/client/persist/SceneRepository.as
new file mode 100644
index 00000000..2d5aa949
--- /dev/null
+++ b/src/as/com/threerings/whirled/client/persist/SceneRepository.as
@@ -0,0 +1,66 @@
+//
+// $Id: SceneRepository.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.client.persist {
+
+import flash.errors.IOError;
+
+import com.threerings.whirled.data.SceneModel;
+import com.threerings.whirled.util.NoSuchSceneError;
+
+/**
+ * The scene repository provides access to a persistent repository of
+ * scene information.
+ *
+ * @see SceneModel
+ */
+public interface SceneRepository
+{
+ /**
+ * Fetches the model for the scene with the specified id.
+ *
+ * @exception IOException thrown if an error occurs attempting to load
+ * the scene data.
+ * @exception NoSuchSceneException thrown if no scene exists with the
+ * specified scene id.
+ */
+ function loadSceneModel (sceneId :int) :SceneModel;
+ //throws IOException, NoSuchSceneException;
+
+ /**
+ * Updates or inserts this scene model as appropriate.
+ *
+ * @exception IOException thrown if an error occurs attempting to
+ * access the repository.
+ */
+ function storeSceneModel (model :SceneModel) :void;
+ //throws IOException;
+
+ /**
+ * Deletes the specified scene model from the repository.
+ *
+ * @exception IOException thrown if an error occurs attempting to
+ * access the repository.
+ */
+ function deleteSceneModel (sceneId :int) :void;
+ //throws IOException;
+}
+}
diff --git a/src/as/com/threerings/whirled/data/AuxModel.as b/src/as/com/threerings/whirled/data/AuxModel.as
new file mode 100644
index 00000000..e7adf1f5
--- /dev/null
+++ b/src/as/com/threerings/whirled/data/AuxModel.as
@@ -0,0 +1,35 @@
+//
+// $Id: AuxModel.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.data {
+
+import com.threerings.util.Cloneable;
+
+import com.threerings.io.Streamable;
+
+/**
+ * An interface that must be implemented by auxiliary scene models.
+ */
+public interface AuxModel extends Streamable, Cloneable
+{
+ // no new methods
+}
+}
diff --git a/src/as/com/threerings/whirled/data/DefaultSceneConfig.as b/src/as/com/threerings/whirled/data/DefaultSceneConfig.as
new file mode 100644
index 00000000..89b07839
--- /dev/null
+++ b/src/as/com/threerings/whirled/data/DefaultSceneConfig.as
@@ -0,0 +1,45 @@
+//
+// $Id: DefaultSceneConfig.java 4026 2006-04-18 01:32:41Z mdb $
+//
+// 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.data {
+
+import com.threerings.crowd.data.PlaceConfig;
+import com.threerings.whirled.client.SceneController;
+
+/**
+ * The default scene config simply causes the default scene manager and
+ * controller to be created. A user of the Whirled services would most
+ * likely extend the default scene config.
+ *
+ *
Note that this place config won't even work on the client side + * because it instantiates a {@link SceneController} which is an abstract + * class. It is used only for testing the server side and as a placeholder + * in case standard scene configuration information is one day needed. + */ +public class DefaultSceneConfig extends PlaceConfig +{ + // documentation inherited + public function getManagerClassName () :String + { + return "com.threerings.whirled.server.SceneManager"; + } +} +} diff --git a/src/as/com/threerings/whirled/data/Scene.as b/src/as/com/threerings/whirled/data/Scene.as new file mode 100644 index 00000000..5301da69 --- /dev/null +++ b/src/as/com/threerings/whirled/data/Scene.as @@ -0,0 +1,84 @@ +// +// $Id: Scene.java 3099 2004-08-27 02:21:06Z mdb $ +// +// 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.data { + +import com.threerings.crowd.data.PlaceConfig; + +/** + * This interface makes available basic scene information. At this basic + * level, not much information is available, but extensions to this + * interface begin to create a more comprehensive picture of a scene in a + * system built from the Whirled services. + */ +public interface Scene +{ + /** + * Returns the unique identifier for this scene. + */ + function getId () :int; + + /** + * Returns the human readable name of this scene. + */ + function getName () :String; + + /** + * Returns the version number of this scene. + */ + function getVersion () :int; + + /** + * Returns the place config that can be used to determine which place + * controller instance should be used to display this scene as well as + * to obtain runtime configuration information. + */ + function getPlaceConfig () :PlaceConfig; + + /** + * Sets this scene's unique identifier. + */ + function setId (sceneId :int) :void; + + /** + * Sets the human readable name of this scene. + */ + function setName (name :String) :void; + + /** + * Sets this scene's version number. + */ + function setVersion (version :int) :void; + + /** + * Called to inform the scene that an update has been received while + * the scene was resolved and active. The update should be applied to + * the underlying scene model and any derivative data should be + * appropriately updated. + */ + function updateReceived (update :SceneUpdate) :void; + + /** + * Returns the scene model from which this scene was created. + */ + function getSceneModel () :SceneModel; +} +} diff --git a/src/as/com/threerings/whirled/data/SceneCodes.as b/src/as/com/threerings/whirled/data/SceneCodes.as new file mode 100644 index 00000000..b28ec338 --- /dev/null +++ b/src/as/com/threerings/whirled/data/SceneCodes.as @@ -0,0 +1,34 @@ +// +// $Id: SceneCodes.java 3099 2004-08-27 02:21:06Z mdb $ +// +// 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.data { + +import com.threerings.crowd.data.LocationCodes; + +/** + * Contains codes used by the scene invocation services. + */ +public class SceneCodes extends LocationCodes +{ + /** The message identifier for scene update messages. */ + public static const SCENE_UPDATE :String = "scene_update"; +} +} diff --git a/src/as/com/threerings/whirled/data/SceneImpl.as b/src/as/com/threerings/whirled/data/SceneImpl.as new file mode 100644 index 00000000..c2d1f402 --- /dev/null +++ b/src/as/com/threerings/whirled/data/SceneImpl.as @@ -0,0 +1,124 @@ +// +// $Id: SceneImpl.java 3099 2004-08-27 02:21:06Z mdb $ +// +// 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.data { + +import com.threerings.crowd.data.PlaceConfig; + +/** + * An implementation of the {@link Scene} interface. + */ +public class SceneImpl implements Scene +{ + /** + * Creates an instance that will obtain data from the supplied scene + * model and place config. + */ + public function SceneImpl ( + model :SceneModel = null, config :PlaceConfig = null) + { + if (model != null) { + _model = model; + _config = config; + + } else { + _model = SceneModel.blankSceneModel(); + } + } + + // documentation inherited + public function getId () :int + { + return _model.sceneId; + } + + // documentation inherited + public function getName () :String + { + return _model.name; + } + + // documentation inherited + public function getVersion () :int + { + return _model.version; + } + + // documentation inherited + public function getPlaceConfig () :PlaceConfig + { + return _config; + } + + // documentation inherited from interface + public function setId (sceneId :int) :void + { + _model.sceneId = sceneId; + } + + // documentation inherited from interface + public function setName (name :String) :void + { + _model.name = name; + } + + // documentation inherited from interface + public function setVersion (version :int) :void + { + _model.version = version; + } + + // documentation inherited from interface + public function updateReceived (update :SceneUpdate) :void + { + try { + // validate and apply the update + update.validate(_model); + update.apply(_model); + } catch (e :Error) { + var log :Log = Log.getLog(this); + log.warning("Error applying update [scene=" + this + + ", update=" + update + "]."); + log.logStackTrace(e); + } + } + + // documentation inherited from interface + public function getSceneModel () :SceneModel + { + return _model; + } + + /** + * Generates a string representation of this instance. + */ + public function toString () :String + { + return "[model=" + _model + ", config=" + _config + "]"; + } + + /** A reference to our scene model. */ + protected var _model :SceneModel; + + /** A reference to our place configuration. */ + protected var _config :PlaceConfig; +} +} diff --git a/src/as/com/threerings/whirled/data/SceneMarshaller.as b/src/as/com/threerings/whirled/data/SceneMarshaller.as new file mode 100644 index 00000000..f98098c6 --- /dev/null +++ b/src/as/com/threerings/whirled/data/SceneMarshaller.as @@ -0,0 +1,53 @@ +// +// $Id: SceneMarshaller.java 3793 2005-12-21 02:12:57Z ray $ +// +// Narya library - tools for developing networked games +// Copyright (C) 2002-2005 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.data { + +import com.threerings.util.Integer; + +import com.threerings.presents.client.Client; +import com.threerings.presents.data.InvocationMarshaller; +import com.threerings.whirled.client.SceneService; +import com.threerings.whirled.client.SceneService_SceneMoveListener; + +/** + * Provides the implementation of the {@link SceneService} interface + * that marshalls the arguments and delivers the request to the provider + * on the server. Also provides an implementation of the response listener + * interfaces that marshall the response arguments and deliver them back + * to the requesting client. + */ +public class SceneMarshaller extends InvocationMarshaller + implements SceneService +{ + /** The method id used to dispatch {@link #moveTo} requests. */ + public static const MOVE_TO :int = 1; + + // documentation inherited from interface + public function moveTo (arg1 :Client, arg2 :int, arg3 :int, arg4 :SceneService_SceneMoveListener) :void + { + var listener4 :SceneMarshaller_SceneMoveMarshaller = new SceneMarshaller_SceneMoveMarshaller(); + listener4.listener = arg4; + sendRequest(arg1, MOVE_TO, [ new Integer(arg2), new Integer(arg3), listener4 ]); + } + +} +} diff --git a/src/as/com/threerings/whirled/data/SceneMarshaller_SceneMoveMarshaller.as b/src/as/com/threerings/whirled/data/SceneMarshaller_SceneMoveMarshaller.as new file mode 100644 index 00000000..23e48546 --- /dev/null +++ b/src/as/com/threerings/whirled/data/SceneMarshaller_SceneMoveMarshaller.as @@ -0,0 +1,81 @@ +package com.threerings.whirled.data { + +import com.threerings.util.Integer; + +import com.threerings.io.TypedArray; + +import com.threerings.presents.data.InvocationMarshaller_ListenerMarshaller; + +import com.threerings.crowd.data.PlaceConfig; + +import com.threerings.presents.dobj.InvocationResponseEvent; + +import com.threerings.whirled.client.SceneService_SceneMoveListener; +import com.threerings.whirled.data.SceneModel; +import com.threerings.whirled.data.SceneUpdate; + +public class SceneMarshaller_SceneMoveMarshaller extends InvocationMarshaller_ListenerMarshaller + implements SceneService_SceneMoveListener +{ + /** The method id used to dispatch {@link #moveSucceeded} + * responses. */ + public static const MOVE_SUCCEEDED :int = 1; + + // documentation inherited from interface SceneService_SceneMoveListener + public function moveSucceeded (arg1 :int, arg2 :PlaceConfig) :void + { + omgr.postEvent(new InvocationResponseEvent( + callerOid, requestId, MOVE_SUCCEEDED, + [ new Integer(arg1), arg2 ])); + } + + /** The method id used to dispatch {@link #moveSucceededWithScene} + * responses. */ + public static const MOVE_SUCCEEDED_WITH_SCENE :int = 2; + + // documentation inherited from interface SceneService_SceneMoveListener + public function moveSucceededWithScene (arg1 :int, arg2 :PlaceConfig, arg3 :SceneModel) :void + { + omgr.postEvent(new InvocationResponseEvent( + callerOid, requestId, MOVE_SUCCEEDED_WITH_SCENE, + [ new Integer(arg1), arg2, arg3 ])); + } + + /** The method id used to dispatch {@link #moveSucceededWithUpdates} + * responses. */ + public static const MOVE_SUCCEEDED_WITH_UPDATES :int = 3; + + // documentation inherited from interface SceneService_SceneMoveListener + public function moveSucceededWithUpdates (arg1 :int, arg2 :PlaceConfig, arg3 :TypedArray /* of SceneUpdate */) :void + { + omgr.postEvent(new InvocationResponseEvent( + callerOid, requestId, MOVE_SUCCEEDED_WITH_UPDATES, + [ new Integer(arg1), arg2, arg3 ])); + } + + // documentation inherited + override public function dispatchResponse (methodId :int, args :Array) :void + { + switch (methodId) { + case MOVE_SUCCEEDED: + (listener as SceneService_SceneMoveListener).moveSucceeded( + (args[0] as Integer).value, args[1] as PlaceConfig); + return; + + case MOVE_SUCCEEDED_WITH_SCENE: + (listener as SceneService_SceneMoveListener).moveSucceededWithScene( + (args[0] as Integer).value, args[1] as PlaceConfig, args[2] as SceneModel); + return; + + case MOVE_SUCCEEDED_WITH_UPDATES: + (listener as SceneService_SceneMoveListener).moveSucceededWithUpdates( + (args[0] as Integer).value, args[1] as PlaceConfig, args[2] as TypedArray); + return; + + default: + super.dispatchResponse(methodId, args); + } + } +} + +} diff --git a/src/as/com/threerings/whirled/data/SceneModel.as b/src/as/com/threerings/whirled/data/SceneModel.as new file mode 100644 index 00000000..8a843dfc --- /dev/null +++ b/src/as/com/threerings/whirled/data/SceneModel.as @@ -0,0 +1,118 @@ +// +// $Id: SceneModel.java 3726 2005-10-11 19:17:43Z 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.data { + +import com.threerings.util.ClassUtil; +import com.threerings.util.Cloneable; + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamable; +import com.threerings.io.TypedArray; + +/** + * The scene model is the bare bones representation of the data for a + * scene in the Whirled system. From the scene model, one would create an + * instance of {@link Scene}. + * + *
The scene model is what is loaded from the scene repositories and
+ * what is transmitted over the wire when communicating scenes from the
+ * server to the client.
+ */
+public class SceneModel
+ implements Streamable, Cloneable
+{
+ /** This scene's unique identifier. */
+ public var sceneId :int;
+
+ /** The human readable name of this scene. */
+ public var name :String;
+
+ /** The version number of this scene. Versions are incremented
+ * whenever modifications are made to a scene so that clients can
+ * determine whether or not they have the latest version of a
+ * scene. */
+ public var version :int;
+
+ /** Auxiliary scene model information. */
+ public var auxModels :TypedArray =
+ new TypedArray("[Lcom.threerings.whirled.data.AuxModel;");
+
+ /**
+ * Adds the specified auxiliary model to this scene model.
+ */
+ public function addAuxModel (auxModel :AuxModel) :void
+ {
+ auxModels.push(auxModel);
+ }
+
+ // documentation inherited from interface Cloneable
+ public function clone () :Object
+ {
+ var clazz :Class = ClassUtil.getClass(this);
+ var model :SceneModel = new clazz();
+
+ for each (var aux :AuxModel in auxModels) {
+ model.addAuxModel(aux.clone() as AuxModel);
+ }
+ return model;
+ }
+
+ // documentation inherited from interface Streamable
+ public function writeObject (out :ObjectOutputStream) :void
+ {
+ out.writeInt(sceneId);
+ out.writeField(name);
+ out.writeInt(version);
+ out.writeObject(auxModels);
+ }
+
+ // documentation inherited from interface Streamable
+ public function readObject (ins :ObjectInputStream) :void
+ {
+ sceneId = ins.readInt();
+ name = (ins.readField(String) as String);
+ version = ins.readInt();
+ auxModels = (ins.readObject() as TypedArray);
+ }
+
+ /**
+ * Creates and returns a blank scene model.
+ */
+ public static function blankSceneModel () :SceneModel
+ {
+ var model :SceneModel = new SceneModel();
+ populateBlankSceneModel(model);
+ return model;
+ }
+
+ /**
+ * Populates a blank scene model with blank values.
+ */
+ protected static function populateBlankSceneModel (model :SceneModel) :void
+ {
+ model.sceneId = -1;
+ model.name = "super.apply.
+ */
+ public function apply (model :SceneModel) :void
+ {
+ // increment the version; disallowing integer overflow
+ model.version = Math.max(_targetVersion + 1, model.version);
+
+ // sanity check for the amazing two billion updates
+ if (model.version == _targetVersion) {
+ Log.getLog(this).warning("Egads! This scene has been updated two" +
+ " billion times [model=" + model + ", update=" + this + "].");
+ }
+ }
+
+ // documentation inherited from interface Streamable
+ public function writeObject (out :ObjectOutputStream) :void
+ //throws IOException
+ {
+ out.writeInt(_targetId);
+ out.writeInt(_targetVersion);
+ }
+
+ // documentation inherited from interface Streamable
+ public function readObject (ins :ObjectInputStream) :void
+ //throws IOException, ClassNotFoundException
+ {
+ _targetId = ins.readInt();
+ _targetVersion = ins.readInt();
+ }
+
+ /**
+ * Generates a string representation of this instance.
+ */
+ public function toString () :String
+ {
+ var buf :StringBuilder = new StringBuilder("[");
+ toStringBuf(buf);
+ return buf.append("]").toString();
+ }
+
+ /**
+ * An extensible mechanism for generating a string representation of
+ * this instance.
+ */
+ protected function toStringBuf (buf :StringBuilder) :void
+ {
+ buf.append("sceneId=").append(_targetId);
+ buf.append(", version=").append(_targetVersion);
+ }
+
+ /** The version number of the scene on which we operate. */
+ protected var _targetId :int;
+
+ /** The version number of the scene on which we operate. */
+ protected var _targetVersion :int;
+}
+}
diff --git a/src/as/com/threerings/whirled/data/ScenedBodyObject.as b/src/as/com/threerings/whirled/data/ScenedBodyObject.as
new file mode 100644
index 00000000..edd4747c
--- /dev/null
+++ b/src/as/com/threerings/whirled/data/ScenedBodyObject.as
@@ -0,0 +1,40 @@
+//
+// $Id: ScenedBodyObject.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.data {
+
+/**
+ * A system that uses the whirled services must provide a body object
+ * extension that implements this interface.
+ */
+public interface ScenedBodyObject
+{
+ /**
+ * Returns the scene id currently occupied by this body.
+ */
+ function getSceneId () :int;
+
+ /**
+ * Sets the scene id currently occupied by this body.
+ */
+ function setSceneId (sceneId :int) :void;
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/client/SpotSceneController.as b/src/as/com/threerings/whirled/spot/client/SpotSceneController.as
new file mode 100644
index 00000000..39c969e4
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/client/SpotSceneController.as
@@ -0,0 +1,37 @@
+//
+// $Id: SpotSceneController.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.spot.client {
+
+import com.threerings.whirled.client.SceneController;
+
+/**
+ * The base spot scene controller class. It is expected that users of the
+ * Whirled Spot services will extend this controller class when creating
+ * specialized controllers for their scenes. Presently there are no basic
+ * scene services provided by this controller, but its existence affords
+ * the addition of such services should they become necessary in the
+ * future.
+ */
+public /*abstract*/ class SpotSceneController extends SceneController
+{
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/client/SpotSceneDirector.as b/src/as/com/threerings/whirled/spot/client/SpotSceneDirector.as
new file mode 100644
index 00000000..4e6dff13
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/client/SpotSceneDirector.as
@@ -0,0 +1,472 @@
+//
+// $Id: SpotSceneDirector.java 3890 2006-02-24 19:51:11Z mthomas $
+//
+// 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.spot.client {
+
+import com.threerings.util.ResultListener;
+
+import com.threerings.presents.client.BasicDirector;
+import com.threerings.presents.client.Client;
+import com.threerings.presents.client.ClientEvent;
+import com.threerings.presents.client.ConfirmAdapter;
+import com.threerings.presents.client.ConfirmListener;
+import com.threerings.presents.data.ClientObject;
+import com.threerings.presents.dobj.AttributeChangeListener;
+import com.threerings.presents.dobj.AttributeChangedEvent;
+import com.threerings.presents.dobj.DObject;
+import com.threerings.presents.dobj.DObjectManager;
+import com.threerings.presents.dobj.ObjectAccessError;
+import com.threerings.presents.dobj.Subscriber;
+
+import com.threerings.crowd.chat.client.ChatDirector;
+import com.threerings.crowd.chat.data.ChatCodes;
+import com.threerings.crowd.client.LocationAdapter;
+import com.threerings.crowd.client.LocationDirector;
+import com.threerings.crowd.data.PlaceObject;
+
+import com.threerings.whirled.client.SceneDirector;
+import com.threerings.whirled.data.SceneModel;
+import com.threerings.whirled.data.ScenedBodyObject;
+import com.threerings.whirled.util.WhirledContext;
+
+import com.threerings.whirled.spot.data.ClusteredBodyObject;
+import com.threerings.whirled.spot.data.Location;
+import com.threerings.whirled.spot.data.Portal;
+import com.threerings.whirled.spot.data.SpotCodes;
+import com.threerings.whirled.spot.data.SpotScene;
+
+/**
+ * Extends the standard scene director with facilities to move between
+ * locations within a scene.
+ */
+public class SpotSceneDirector extends BasicDirector
+ implements Subscriber, AttributeChangeListener
+{
+ private static const log :Log = Log.getLog(SpotSceneDirector);
+
+ /**
+ * Creates a new spot scene director with the specified context and
+ * which will cooperate with the supplied scene director.
+ *
+ * @param ctx the active client context.
+ * @param locdir the location director with which we will be
+ * cooperating.
+ * @param scdir the scene director with which we will be cooperating.
+ */
+ public function SpotSceneDirector (
+ ctx :WhirledContext, locdir :LocationDirector, scdir :SceneDirector)
+ {
+ super(ctx);
+
+ _wctx = ctx;
+ _scdir = scdir;
+
+ // wire ourselves up to hear about leave place notifications
+ locdir.addLocationObserver(new LocationAdapter(null,
+ function (place :PlaceObject) :void {
+ handleDeparture();
+ }));
+ }
+
+ /**
+ * Configures this spot scene director with a chat director, with
+ * which it will coordinate to implement cluster chatting.
+ */
+ public function setChatDirector (chatdir :ChatDirector) :void
+ {
+ _chatdir = chatdir;
+ }
+
+ /**
+ * Returns our current location unless we have a location change
+ * pending, in which case our pending location is returned.
+ */
+ public function getIntendedLocation () :Location
+ {
+ return (_pendingLoc != null) ? _pendingLoc : _location;
+ }
+
+ /**
+ * Requests that this client move to the location specified by the
+ * supplied portal id. A request will be made and when the response is
+ * received, the location observers will be notified of success or
+ * failure.
+ *
+ * @return true if the request was issued, false if it was rejected by
+ * a location observer or because we have another request outstanding.
+ */
+ public function traversePortal (
+ portalId :int,
+ rl :com.threerings.util.ResultListener = null) :Boolean
+ {
+ // look up the destination scene and location
+ var scene :SpotScene = (_scdir.getScene() as SpotScene);
+ if (scene == null) {
+ log.warning("Requested to traverse portal when we have " +
+ "no scene [portalId=" + portalId + "].");
+ return false;
+ }
+
+ // sanity check the server's notion of what scene we're in with
+ // our notion of it
+ var sceneId :int = _scdir.getScene().getId();
+ var sbobj :ScenedBodyObject =
+ (_wctx.getClient().getClientObject() as ScenedBodyObject);
+ if (sceneId != sbobj.getSceneId()) {
+ log.warning("Client and server differ in opinion of what scene " +
+ "we're in [sSceneId=" + sbobj.getSceneId() +
+ ", cSceneId=" + sceneId + "].");
+ return false;
+ }
+
+ // find the portal they're talking about
+ var dest :Portal = scene.getPortal(portalId);
+ if (dest == null) {
+ log.warning("Requested to traverse non-existent portal " +
+ "[portalId=" + portalId + ", portals=" + scene.getPortals() +
+ "].");
+ return false;
+ }
+
+ // prepare to move to this scene (sets up pending data)
+ if (!_scdir.prepareMoveTo(dest.targetSceneId, rl)) {
+ log.info("Portal traversal vetoed by scene director " +
+ "[portalId=" + portalId + "].");
+ return false;
+ }
+
+ // 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
+ var sceneVer :int = 0;
+ var pendingModel :SceneModel = _scdir.getPendingModel();
+ if (pendingModel != null) {
+ sceneVer = pendingModel.version;
+ }
+
+ // issue a traversePortal request
+ log.info("Issuing traversePortal(" +
+ sceneId + ", " + dest + ", " + sceneVer + ").");
+ _sservice.traversePortal(
+ _wctx.getClient(), sceneId, portalId, sceneVer, _scdir);
+ return true;
+ }
+
+ /**
+ * Issues a request to change our location within the scene to the
+ * specified location.
+ *
+ * @param loc the new location to which to move.
+ * @param listener will be notified of success or failure. Most client
+ * entities find out about location changes via changes to the
+ * occupant info data, but the initiator of a location change request
+ * can be notified of its success or failure, primarily so that it can
+ * act in anticipation of a successful location change (like by
+ * starting a sprite moving toward the new location), but backtrack if
+ * it finds out that the location change failed.
+ */
+ public function changeLocation (
+ loc :Location, listener :com.threerings.util.ResultListener) :void
+ {
+ // refuse if there's a pending location change or if we're already
+ // at the specified location
+ if (loc.equivalent(_location)) {
+ log.info("Not going to " + loc + "; we're at " + _location +
+ " and we're headed to " + _pendingLoc + ".");
+ if (listener != null) {
+ // This isn't really a failure, it's just a no-op.
+ listener.requestCompleted(_location);
+ }
+ return;
+ }
+
+ if (_pendingLoc != null) {
+ log.info("Not going to " + loc + "; we're at " + _location +
+ " and we're headed to " + _pendingLoc + ".");
+ if (listener != null) {
+ // Already moving, best thing to do is ignore it.
+ listener.requestCompleted(_pendingLoc);
+ }
+ return;
+ }
+
+ var scene :SpotScene = (_scdir.getScene() as SpotScene);
+ if (scene == null) {
+ log.warning("Requested to change locations, but we're not " +
+ "currently in any scene [loc=" + loc + "].");
+ if (listener != null) {
+ listener.requestFailed(new Error("m.cant_get_there"));
+ }
+ return;
+ }
+
+ var sceneId :int = _scdir.getScene().getId();
+ log.info("Sending changeLocation request [scid=" + sceneId +
+ ", loc=" + loc + "].");
+
+ _pendingLoc = (loc.clone() as Location);
+ var clist :ConfirmAdapter = new ConfirmAdapter(
+ function () :void {
+ _location = _pendingLoc;
+ _pendingLoc = null;
+ if (listener != null) {
+ listener.requestCompleted(_location);
+ }
+ },
+ function (reason :String) :void {
+ _pendingLoc = null;
+ if (listener != null) {
+ listener.requestFailed(new Error(reason));
+ }
+ });
+ _sservice.changeLocation(_wctx.getClient(), sceneId, loc, clist);
+ }
+
+ /**
+ * Issues a request to join the cluster associated with the specified
+ * user (starting one if necessary).
+ *
+ * @param froid the bodyOid of another user; the calling user will
+ * be made to join the target user's cluster.
+ * @param listener will be notified of success or failure.
+ */
+ public function joinCluster (
+ froid :int, listener :com.threerings.util.ResultListener) :void
+ {
+ var scene :SpotScene = (_scdir.getScene() as SpotScene);
+ if (scene == null) {
+ log.warning("Requested to join cluster, but we're not " +
+ "currently in any scene [froid=" + froid + "].");
+ if (listener != null) {
+ listener.requestFailed(new Error("m.cant_get_there"));
+ }
+ return;
+ }
+
+ log.info("Joining cluster [friend=" + froid + "].");
+
+ _sservice.joinCluster(_wctx.getClient(), froid, new ConfirmAdapter(
+ function () :void {
+ if (listener != null) {
+ listener.requestCompleted(null);
+ }
+ },
+ function (reason :String) :void {
+ if (listener != null) {
+ listener.requestFailed(new Error(reason));
+ }
+ }));
+ }
+
+ /**
+ * Sends a chat message to the other users in the cluster to which the
+ * location that we currently occupy belongs.
+ *
+ * @return true if a cluster speak message was delivered, false if we
+ * are not in a valid cluster and refused to deliver the request.
+ */
+ public function requestClusterSpeak (
+ message :String, mode :int = ChatCodes.DEFAULT_MODE) :Boolean
+ {
+ // make sure we're currently in a scene
+ var scene :SpotScene = (_scdir.getScene() as SpotScene);
+ if (scene == null) {
+ log.warning("Requested to speak to cluster, but we're not " +
+ "currently in any scene [message=" + message + "].");
+ return false;
+ }
+
+ // make sure we're part of a cluster
+ if (_self.getClusterOid() <= 0) {
+ log.info("Ignoring cluster speak as we're not in a cluster " +
+ "[cloid=" + _self.getClusterOid() + "].");
+ return false;
+ }
+
+ message = _chatdir.filter(message, null, true);
+ if (message != null) {
+ _sservice.clusterSpeak(_wctx.getClient(), message, mode);
+ }
+ return true;
+ }
+
+ // documentation inherited from interface
+ public function objectAvailable (object :DObject) :void
+ {
+ clearCluster(false);
+ var oid :int = object.getOid();
+ if (oid != _self.getClusterOid()) {
+ // we got it too late, just unsubscribe
+ var omgr :DObjectManager = _wctx.getDObjectManager();
+ omgr.unsubscribeFromObject(oid, this);
+ } else {
+ // it's our new cluster!
+ _clobj = object;
+ if (_chatdir != null) {
+ _chatdir.addAuxiliarySource(object,
+ SpotCodes.CLUSTER_CHAT_TYPE);
+ }
+ }
+ }
+
+ // documentation inherited from interface
+ public function requestFailed (oid :int, cause :ObjectAccessError) :void
+ {
+ log.warning("Unable to subscribe to cluster chat object " +
+ "[oid=" + oid + ", cause=" + cause + "].");
+ }
+
+ // documentation inherited from interface
+ public function attributeChanged (event :AttributeChangedEvent) :void
+ {
+ if (event.getName() == _self.getClusterField() &&
+ event.getValue() != event.getOldValue()) {
+ maybeUpdateCluster();
+ }
+ }
+
+ // documentation inherited
+ override public function clientDidLogon (event :ClientEvent) :void
+ {
+ super.clientDidLogon(event);
+
+ var clientObj :ClientObject = event.getClient().getClientObject();
+ if (clientObj is ClusteredBodyObject) {
+ // listen to the client object
+ clientObj.addListener(this);
+ _self = (clientObj as ClusteredBodyObject);
+
+ // we may need to subscribe to a cluster due to session resumption
+ maybeUpdateCluster();
+ }
+ }
+
+ // documentation inherited
+ override public function clientObjectDidChange (event :ClientEvent) :void
+ {
+ super.clientObjectDidChange(event);
+
+ // listen to the client object
+ var clientObj :ClientObject = event.getClient().getClientObject();
+ clientObj.addListener(this);
+ _self = (clientObj as ClusteredBodyObject);
+ }
+
+ // documentation inherited
+ override public function clientDidLogoff (event :ClientEvent) :void
+ {
+ super.clientDidLogoff(event);
+
+ // clear out our business
+ _location = null;
+ _pendingLoc = null;
+ _sservice = null;
+ clearCluster(true);
+
+ // stop listening to the client object
+ event.getClient().getClientObject().removeListener(this);
+ _self = null;
+ }
+
+ // documentation inherited
+ override protected function fetchServices (client :Client) :void
+ {
+ _sservice = (client.requireService(SpotService) as SpotService);
+ }
+
+ /**
+ * Clean up after a few things when we depart from a scene.
+ */
+ protected function handleDeparture () :void
+ {
+ // clear out our last known location id
+ _location = null;
+ }
+
+ /**
+ * Checks to see if our cluster has changed and does the necessary
+ * subscription machinations if necessary.
+ */
+ protected function maybeUpdateCluster () :void
+ {
+ var cloid :int = _self.getClusterOid();
+ if ((_clobj == null && cloid <= 0) ||
+ (_clobj != null && cloid == _clobj.getOid())) {
+ // our cluster didn't change, we can stop now
+ return;
+ }
+
+ // clear out any old cluster object
+ clearCluster(false);
+
+ // if there's a new cluster object, subscribe to it
+ if (_chatdir != null && cloid > 0) {
+ var omgr :DObjectManager = _wctx.getDObjectManager();
+ // we'll wire up to the chat director when this completes
+ omgr.subscribeToObject(cloid, this);
+ }
+ }
+
+ /**
+ * Convenience routine to unwire chat for and unsubscribe from our
+ * current cluster, if any.
+ *
+ * @param force clear the cluster even if we're still apparently in it.
+ */
+ protected function clearCluster (force :Boolean) :void
+ {
+ if (_clobj != null &&
+ (force || (_clobj.getOid() != _self.getClusterOid()))) {
+ if (_chatdir != null) {
+ _chatdir.removeAuxiliarySource(_clobj);
+ }
+ var omgr :DObjectManager = _wctx.getDObjectManager();
+ omgr.unsubscribeFromObject(_clobj.getOid(), this);
+ _clobj = null;
+ }
+ }
+
+ /** The active client context. */
+ protected var _wctx :WhirledContext;
+
+ /** Access to spot scene services. */
+ protected var _sservice :SpotService;
+
+ /** The scene director with which we are cooperating. */
+ protected var _scdir :SceneDirector;
+
+ /** A casted reference to our clustered body object. */
+ protected var _self :ClusteredBodyObject;
+
+ /** A reference to the chat director with which we coordinate. */
+ protected var _chatdir :ChatDirector;
+
+ /** The location we currently occupy. */
+ protected var _location :Location;
+
+ /** The location to which we have an outstanding change location
+ * request. */
+ protected var _pendingLoc :Location;
+
+ /** The cluster chat object for the cluster we currently occupy. */
+ protected var _clobj :DObject;
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/client/SpotService.as b/src/as/com/threerings/whirled/spot/client/SpotService.as
new file mode 100644
index 00000000..7308f81a
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/client/SpotService.as
@@ -0,0 +1,85 @@
+//
+// $Id: SpotService.java 3363 2005-02-22 18:54:48Z mdb $
+//
+// 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.spot.client {
+
+import com.threerings.presents.client.Client;
+import com.threerings.presents.client.InvocationService;
+
+import com.threerings.whirled.client.SceneService_SceneMoveListener;
+import com.threerings.whirled.spot.data.Location;
+
+/**
+ * Defines the mechanism by which the client can request to move around
+ * within a scene and between scenes (taking exit and entry locations into
+ * account). These services should not be used directly, but instead
+ * should be accessed via the {@link SpotSceneDirector}.
+ */
+public interface SpotService extends InvocationService
+{
+ /**
+ * Requests to traverse the specified portal.
+ *
+ * @param sceneId the player's current scene which is used to sanity
+ * check things when the request actually arrives.
+ * @param portalId the portal to be traversed.
+ * @param destSceneVer the version of the destination scene data that
+ * the client has in its local repository.
+ */
+ function traversePortal (
+ client :Client, sceneId :int, portalId :int, destSceneVer :int,
+ listener :SceneService_SceneMoveListener) :void;
+
+ /**
+ * Requests that this client's body be made to move to the specified
+ * location. The user will be removed from any cluster from which they
+ * are an occupant.
+ *
+ * @param sceneId the id of the scene in which to change location.
+ * @param loc the location to which to move.
+ */
+ function changeLocation (
+ client :Client, sceneId :int, loc :Location,
+ listener :ConfirmListener) :void;
+
+ /**
+ * Requests that this client start or join the specified cluster. They
+ * will be relocated appropriately by the scene manager.
+ *
+ * @param friendOid the bodyOid of another user or the oid of an
+ * existing cluster; the calling user will be made to join the cluster
+ * or target user's cluster, or create a cluster with the target user
+ * if they are not already in one.
+ */
+ function joinCluster (
+ client :Client, friendOid :int, listener :ConfirmListener) :void;
+
+ /**
+ * Requests that the supplied message be delivered to listeners in the
+ * cluster to which the specified location belongs.
+ *
+ * @param message the text of the message to be spoken.
+ * @param mode an associated mode constant that can be used to
+ * identify different kinds of "speech" (emote, thought bubble, etc.).
+ */
+ function clusterSpeak (client :Client, message :String, mode :int) :void;
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/Cluster.as b/src/as/com/threerings/whirled/spot/data/Cluster.as
new file mode 100644
index 00000000..795476d1
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/Cluster.as
@@ -0,0 +1,76 @@
+//
+// $Id: Cluster.java 3310 2005-01-24 23:08:21Z mdb $
+//
+// 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.spot.data {
+
+import flash.geom.Rectangle;
+
+import com.threerings.io.Streamable;
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+
+import com.threerings.presents.dobj.DSet_Entry;
+
+/**
+ * Contains information on clusters.
+ */
+public class Cluster extends Rectangle
+ implements DSet_Entry, Streamable
+{
+ /** A unique identifier for this cluster (also the distributed object
+ * id of the cluster chat object). */
+ public var clusterOid :int;
+
+ // documentation inherited from interface DSet_Entry
+ public function getKey () :Object
+ {
+ return clusterOid;
+ }
+
+ // documentation inherited from interface Streamable
+ public function writeObject (out :ObjectOutputStream) :void
+ {
+ out.writeInt(x);
+ out.writeInt(y);
+ out.writeInt(width);
+ out.writeInt(height);
+ out.writeInt(clusterOid);
+ }
+
+ // documentation inherited from interface Streamable
+ public function readObject (ins :ObjectInputStream) :void
+ {
+ x = ins.readInt();
+ y = ins.readInt();
+ width = ins.readInt();
+ height = ins.readInt();
+ clusterOid = ins.readInt();
+ }
+
+ /**
+ * Generates a string representation of this instance.
+ */
+ public function toString () :String
+ {
+ return super.toString() + ", clusterOid=" + clusterOid;
+ }
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/ClusterObject.as b/src/as/com/threerings/whirled/spot/data/ClusterObject.as
new file mode 100644
index 00000000..b99b30fe
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/ClusterObject.as
@@ -0,0 +1,66 @@
+//
+// $Id: ClusterObject.java 3288 2004-12-28 03:51:29Z mdb $
+//
+// 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.spot.data {
+
+import com.threerings.presents.dobj.DObject;
+import com.threerings.presents.dobj.OidList;
+
+import com.threerings.crowd.chat.data.SpeakObject;
+
+/**
+ * Used to dispatch chat in clusters.
+ */
+public class ClusterObject extends DObject
+{
+ // AUTO-GENERATED: FIELDS START
+ /** The field name of the occupants field. */
+ public static const OCCUPANTS :String = "occupants";
+ // AUTO-GENERATED: FIELDS END
+
+ /**
+ * Tracks the oid of the body objects that occupy this cluster.
+ */
+ public var occupants :OidList = new OidList();
+
+ // AUTO-GENERATED: METHODS START
+ /**
+ * Requests that oid be added to the occupants
+ * oid list. The list will not change until the event is actually
+ * propagated through the system.
+ */
+ public function addToOccupants (oid :int) :void
+ {
+ requestOidAdd(OCCUPANTS, oid);
+ }
+
+ /**
+ * Requests that oid be removed from the
+ * occupants oid list. The list will not change until the
+ * event is actually propagated through the system.
+ */
+ public function removeFromOccupants (oid :int) :void
+ {
+ requestOidRemove(OCCUPANTS, oid);
+ }
+ // AUTO-GENERATED: METHODS END
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/ClusteredBodyObject.as b/src/as/com/threerings/whirled/spot/data/ClusteredBodyObject.as
new file mode 100644
index 00000000..2a1958c4
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/ClusteredBodyObject.as
@@ -0,0 +1,48 @@
+//
+// $Id: ClusteredBodyObject.java 3310 2005-01-24 23:08:21Z mdb $
+//
+// 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.spot.data {
+
+import com.threerings.whirled.data.ScenedBodyObject;
+
+/**
+ * Defines some required methods for a {@link ScenedBodyObject} that is to
+ * participate in the Whirled Spot system.
+ */
+public interface ClusteredBodyObject extends ScenedBodyObject
+{
+ /**
+ * Returns the field name of the cluster oid distributed object field.
+ */
+ function getClusterField () :String;
+
+ /**
+ * Returns the oid of the cluster to which this user currently
+ * belongs.
+ */
+ function getClusterOid () :int;
+
+ /**
+ * Sets the oid of the cluster to which this user currently belongs.
+ */
+ function setClusterOid (clusterOid :int) :void;
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/Location.as b/src/as/com/threerings/whirled/spot/data/Location.as
new file mode 100644
index 00000000..5a5c0dc9
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/Location.as
@@ -0,0 +1,52 @@
+//
+// $Id: Location.java 3726 2005-10-11 19:17:43Z 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.spot.data {
+
+import com.threerings.io.Streamable;
+
+import com.threerings.util.Cloneable;
+import com.threerings.util.Hashable;
+
+/**
+ * Contains information on a scene occupant's position and orientation.
+ */
+public interface Location extends Cloneable, Streamable, Hashable
+{
+ /**
+ * Get a new Location instance that is equals() to this one but that
+ * has an orientation facing the opposite direction.
+ */
+ function getOpposite () :Location;
+
+ /**
+ * Two locations are equivalent if they specify the same location
+ * and orientation.
+ */
+ function equivalent (other :Location) :Boolean;
+
+ /** Two locations are equals by coordinates only. */
+ //function equals (other :Object) :Boolean;
+
+ /** The hashcode should be based on coordinates only. */
+ //function hashCode () :int;
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/Portal.as b/src/as/com/threerings/whirled/spot/data/Portal.as
new file mode 100644
index 00000000..5805661f
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/Portal.as
@@ -0,0 +1,141 @@
+//
+// $Id: Portal.java 4072 2006-04-28 01:34:02Z 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.spot.data {
+
+import com.threerings.util.ClassUtil;
+import com.threerings.util.Cloneable;
+
+import com.threerings.io.Streamable;
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+
+import com.threerings.util.Hashable;
+
+/**
+ * Represents an exit to another scene. A body sprite would walk over to a
+ * portal's coordinates and then either proceed off of the edge of the
+ * display, or open a door and walk through it, or fizzle away in a Star
+ * Trekkian transporter style or whatever is appropriate for the game in
+ * question. It contains information on the scene to which the body exits
+ * when using this portal and the location at which the body sprite should
+ * appear in that target scene.
+ */
+public class Portal
+ implements Streamable, Hashable, Cloneable
+{
+ /** This portal's unique identifier. */
+ public var portalId :int;
+
+ /** The location of the portal. Typically this is a base Location (2d)
+ * class, but different games could use a different subclass of
+ * Location. */
+ public var loc :Location;
+
+ /** The scene identifier of the scene to which a body will exit when
+ * they "use" this portal. */
+ public var targetSceneId :int;
+
+ /** The portal identifier of the portal at which a body will enter
+ * the target scene when they "use" this portal. */
+ public var targetPortalId :int;
+
+ /**
+ * Returns a location instance configured with the location and
+ * orientation of this portal.
+ */
+ public function getLocation () :Location
+ {
+ return (loc.clone() as Location);
+ }
+
+ /**
+ * Returns a location instance configured with the location and
+ * opposite orientation of this portal. This is useful for when a body
+ * is entering a scene at a portal and we want them to face the
+ * opposite direction (as they are entering via the portal rather than
+ * leaving, which is the natural "orientation" of a portal).
+ */
+ public function getOppLocation () :Location
+ {
+ return loc.getOpposite();
+ }
+
+ /**
+ * Returns true if the portal has a potentially valid target scene and
+ * portal id (they are not guaranteed to exist, but they are at least
+ * potentially valid values rather than -1 or 0).
+ */
+ public function isValid () :Boolean
+ {
+ return (targetSceneId > 0) && (targetPortalId > 0);
+ }
+
+ // documentation inherited from interface Streamable
+ public function writeObject (out :ObjectOutputStream) :void
+ {
+ out.writeShort(portalId);
+ out.writeObject(loc);
+ out.writeInt(targetSceneId);
+ out.writeShort(targetPortalId);
+ }
+
+ // documentation inherited from interface Streamable
+ public function readObject (ins :ObjectInputStream) :void
+ {
+ portalId = ins.readShort();
+ loc = (ins.readObject() as Location);
+ targetSceneId = ins.readInt();
+ targetPortalId = ins.readShort();
+ }
+
+ // documentation inherited from interface Cloneable
+ public function clone () :Object
+ {
+ var clazz :Class = ClassUtil.getClass(this);
+ var p :Portal = new clazz();
+ p.portalId = portalId;
+ p.loc = loc;
+ p.targetSceneId = targetSceneId;
+ p.targetPortalId = targetPortalId;
+ return p;
+ }
+
+ // documentation inherited from interface Hashable
+ public function equals (other :Object) :Boolean
+ {
+ return (other is Portal) &&
+ ((other as Portal).portalId == portalId);
+ }
+
+ // documentation inherited from interface Hashable
+ public function hashCode () :int
+ {
+ return portalId;
+ }
+
+ public function toString () :String
+ {
+ return "Portal[id=" + portalId + ", destScene=" + targetSceneId +
+ ", loc=" + loc + "].";
+ }
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SceneLocation.as b/src/as/com/threerings/whirled/spot/data/SceneLocation.as
new file mode 100644
index 00000000..0469cb2e
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SceneLocation.as
@@ -0,0 +1,86 @@
+//
+// $Id: SceneLocation.java 3310 2005-01-24 23:08:21Z mdb $
+//
+// 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.spot.data {
+
+import com.threerings.util.Hashable;
+
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+
+import com.threerings.presents.dobj.DSet_Entry;
+
+/**
+ * Extends {@link Location} with the data and functionality needed to
+ * represent a particular user's location in a scene.
+ */
+public class SceneLocation
+ implements DSet_Entry, Hashable
+{
+ /** The oid of the body that occupies this location. */
+ public var bodyOid :int;
+
+ /** The actual location, which is interpreted by the display system. */
+ public var loc :Location;
+
+ /**
+ * Creates a scene location with the specified information.
+ */
+ public function SceneLocation (loc :Location = null, bodyOid :int = 0)
+ {
+ this.loc = loc;
+ this.bodyOid = bodyOid;
+ }
+
+ // documentation inherited from interface DSet_Entry
+ public function getKey () :Object
+ {
+ return bodyOid;
+ }
+
+ // documentation inherited from interface Hashable
+ public function equals (other :Object) :Boolean
+ {
+ return (other is SceneLocation) &&
+ this.loc.equals((other as SceneLocation).loc);
+ }
+
+ // documentation inherited from interface Hashable
+ public function hashCode () :int
+ {
+ return loc.hashCode();
+ }
+
+ // documentation inherited from superinterface Streamable
+ public function writeObject (out :ObjectOutputStream) :void
+ {
+ out.writeInt(bodyOid);
+ out.writeObject(loc);
+ }
+
+ // documentation inherited from superinterface Streamable
+ public function readObject (ins :ObjectInputStream) :void
+ {
+ bodyOid = ins.readInt();
+ loc = (ins.readObject() as Location);
+ }
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotCodes.as b/src/as/com/threerings/whirled/spot/data/SpotCodes.as
new file mode 100644
index 00000000..c5ad11f2
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotCodes.as
@@ -0,0 +1,58 @@
+//
+// $Id: SpotCodes.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.spot.data {
+
+import com.threerings.crowd.chat.data.ChatCodes;
+
+import com.threerings.whirled.data.SceneCodes;
+
+/**
+ * Contains codes used by the Spot invocation services.
+ */
+public class SpotCodes extends SceneCodes /*, ChatCodes */
+{
+ /** An error code indicating that the portal specified in a
+ * traversePortal request does not exist. */
+ public static const NO_SUCH_PORTAL :String = "m.no_such_portal";
+
+ /** An error code indicating that a location is occupied. Usually
+ * generated by a failed changeLoc request. */
+ public static const LOCATION_OCCUPIED :String = "m.location_occupied";
+
+ /** An error code indicating that a location is not valid. Usually
+ * generated by a failed changeLoc request. */
+ public static const INVALID_LOCATION :String = "m.invalid_location";
+
+ /** An error code indicating that a cluster is not valid. Usually
+ * generated by a failed joinCluster request. */
+ public static const NO_SUCH_CLUSTER :String = "m.no_such_cluster";
+
+ /** An error code indicating that a cluster is full. Usually generated
+ * by a failed joinCluster request. */
+ public static const CLUSTER_FULL :String = "m.cluster_full";
+
+ /** The chat type code with which we register our cluster auxiliary
+ * chat objects. Chat display implementations should interpret chat
+ * messages with this type accordingly. */
+ public static const CLUSTER_CHAT_TYPE :String = "clusterChat";
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotMarshaller.as b/src/as/com/threerings/whirled/spot/data/SpotMarshaller.as
new file mode 100644
index 00000000..2ae4762f
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotMarshaller.as
@@ -0,0 +1,100 @@
+//
+// $Id$
+//
+// Narya library - tools for developing networked games
+// Copyright (C) 2002-2005 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.spot.data {
+
+import com.threerings.util.Byte;
+import com.threerings.util.Integer;
+
+import com.threerings.presents.client.Client;
+import com.threerings.presents.client.InvocationService;
+import com.threerings.presents.client.ConfirmListener;
+import com.threerings.presents.data.InvocationMarshaller;
+import com.threerings.presents.data.InvocationMarshaller_ConfirmMarshaller;
+import com.threerings.presents.dobj.InvocationResponseEvent;
+import com.threerings.whirled.client.SceneService;
+import com.threerings.whirled.client.SceneService_SceneMoveListener;
+import com.threerings.whirled.data.SceneMarshaller_SceneMoveMarshaller;
+import com.threerings.whirled.spot.client.SpotService;
+import com.threerings.whirled.spot.data.Location;
+
+/**
+ * Provides the implementation of the {@link SpotService} interface
+ * that marshalls the arguments and delivers the request to the provider
+ * on the server. Also provides an implementation of the response listener
+ * interfaces that marshall the response arguments and deliver them back
+ * to the requesting client.
+ */
+public class SpotMarshaller extends InvocationMarshaller
+ implements SpotService
+{
+ /** The method id used to dispatch {@link #changeLocation} requests. */
+ public static const CHANGE_LOCATION :int = 1;
+
+ // documentation inherited from interface
+ public function changeLocation (arg1 :Client, arg2 :int, arg3 :Location, arg4 :ConfirmListener) :void
+ {
+ var listener4 :InvocationMarshaller_ConfirmMarshaller = new InvocationMarshaller_ConfirmMarshaller();
+ listener4.listener = arg4;
+ sendRequest(arg1, CHANGE_LOCATION, [
+ new Integer(arg2), arg3, listener4
+ ]);
+ }
+
+ /** The method id used to dispatch {@link #clusterSpeak} requests. */
+ public static const CLUSTER_SPEAK :int = 2;
+
+ // documentation inherited from interface
+ public function clusterSpeak (arg1 :Client, arg2 :String, arg3 :int) :void
+ {
+ sendRequest(arg1, CLUSTER_SPEAK, [
+ arg2, new Byte(arg3)
+ ]);
+ }
+
+ /** The method id used to dispatch {@link #joinCluster} requests. */
+ public static const JOIN_CLUSTER :int = 3;
+
+ // documentation inherited from interface
+ public function joinCluster (arg1 :Client, arg2 :int, arg3 :ConfirmListener) :void
+ {
+ var listener3 :InvocationMarshaller_ConfirmMarshaller = new InvocationMarshaller_ConfirmMarshaller();
+ listener3.listener = arg3;
+ sendRequest(arg1, JOIN_CLUSTER, [
+ new Integer(arg2), listener3
+ ]);
+ }
+
+ /** The method id used to dispatch {@link #traversePortal} requests. */
+ public static const TRAVERSE_PORTAL :int = 4;
+
+ // documentation inherited from interface
+ public function traversePortal (arg1 :Client, arg2 :int, arg3 :int, arg4 :int, arg5 :SceneService_SceneMoveListener) :void
+ {
+ var listener5 :SceneMarshaller_SceneMoveMarshaller = new SceneMarshaller_SceneMoveMarshaller();
+ listener5.listener = arg5;
+ sendRequest(arg1, TRAVERSE_PORTAL, [
+ new Integer(arg2), new Integer(arg3), new Integer(arg4), listener5
+ ]);
+ }
+
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotScene.as b/src/as/com/threerings/whirled/spot/data/SpotScene.as
new file mode 100644
index 00000000..33feb162
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotScene.as
@@ -0,0 +1,81 @@
+//
+// $Id: SpotScene.java 3451 2005-03-31 19:40:55Z mdb $
+//
+// 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.spot.data {
+
+import com.threerings.util.Iterator;
+
+/**
+ * Makes available the spot scene information that the server needs to do
+ * its business.
+ */
+public interface SpotScene
+{
+ /**
+ * Returns a {@link Portal} object for the portal with the specified
+ * id or null if no portal exists with that id.
+ */
+ function getPortal (portalId :int) :Portal;
+
+ /**
+ * Returns the number of portals in this scene.
+ */
+ function getPortalCount () :int;
+
+ /**
+ * Returns an iterator over the portals in this scene.
+ */
+ function getPortals () :Iterator;
+
+ /**
+ * Returns the portal id that should be assigned to the next portal
+ * added to this scene.
+ */
+ function getNextPortalId () :int;
+
+ /**
+ * Returns the portal that represents the default entrance to this
+ * scene. If a body enters the scene at logon time rather than
+ * entering from some other scene, this is the portal at which they
+ * would appear.
+ */
+ function getDefaultEntrance () :Portal;
+
+ /**
+ * Adds a portal to this scene, immediately making the requisite
+ * modifications to the underlying scene model. The portal id should
+ * have already been assigned using the value obtained from {@link
+ * #getNextPortalId}.
+ */
+ function addPortal (portal :Portal) :void;
+
+ /**
+ * Removes the specified portal from the scene.
+ */
+ function removePortal (portal :Portal) :void;
+
+ /**
+ * Sets the default entrance in this scene, immediately making the
+ * requisite modifications to the underlying scene model.
+ */
+ function setDefaultEntrance (portal :Portal) :void;
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotSceneImpl.as b/src/as/com/threerings/whirled/spot/data/SpotSceneImpl.as
new file mode 100644
index 00000000..1cb2e87b
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotSceneImpl.as
@@ -0,0 +1,152 @@
+//
+// $Id: SpotSceneImpl.java 3451 2005-03-31 19:40:55Z mdb $
+//
+// 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.spot.data {
+
+import com.threerings.util.ArrayIterator;
+import com.threerings.util.Iterator;
+import com.threerings.util.HashMap;
+
+/**
+ * An implementation of the {@link SpotScene} interface.
+ */
+public class SpotSceneImpl
+ implements SpotScene
+{
+ /**
+ * Creates an instance that will obtain data from the supplied spot
+ * scene model.
+ */
+ public function SpotSceneImpl (model :SpotSceneModel = null)
+ {
+ if (model != null) {
+ _smodel = model;
+ readPortals();
+
+ } else {
+ _smodel = new SpotSceneModel();
+ }
+ }
+
+ protected function readPortals () :void
+ {
+ _portals.clear();
+ for each (var port :Portal in _smodel.portals) {
+ _portals.put(port.portalId, port);
+ }
+ }
+
+ // documentation inherited from interface
+ public function getPortal (portalId :int) :Portal
+ {
+ return (_portals.get(portalId) as Portal);
+ }
+
+ // documentation inherited from interface
+ public function getPortalCount () :int
+ {
+ return _portals.size();
+ }
+
+ // documentation inherited from interface
+ public function getPortals () :Iterator
+ {
+ return new ArrayIterator(_portals.values());
+ }
+
+ // documentation inherited from interface
+ public function getNextPortalId () :int
+ {
+ // compute a new portal id for our friend the portal
+ for (var ii :int = 1; ii < MAX_PORTAL_ID; ii++) {
+ if (!_portals.containsKey(ii)) {
+ return ii;
+ }
+ }
+ return -1;
+ }
+
+ // documentation inherited from interface
+ public function getDefaultEntrance () :Portal
+ {
+ return getPortal(_smodel.defaultEntranceId);
+ }
+
+ // documentation inherited from interface
+ public function addPortal (portal :Portal) :void
+ {
+ if (portal.portalId <= 0) {
+ Log.getLog(this).warning("Refusing to add zero-id portal " +
+ "[scene=" + this + ", portal=" + portal + "].");
+ return;
+ }
+
+ // add it to our model
+ _smodel.addPortal(portal);
+
+ // and slap it into our table
+ _portals.put(portal.portalId, portal);
+ }
+
+ // documentation inherited from interface
+ public function removePortal (portal :Portal) :void
+ {
+ // remove the portal from our mapping
+ _portals.remove(portal.portalId);
+
+ // remove it from the model
+ _smodel.removePortal(portal);
+ }
+
+ /**
+ * Used when we're being parsed from an XML scene model.
+ */
+ public function setDefaultEntranceId (defaultEntranceId :int) :void
+ {
+ _smodel.defaultEntranceId = defaultEntranceId;
+ }
+
+ // documentation inherited from interface
+ public function setDefaultEntrance (portal :Portal) :void
+ {
+ _smodel.defaultEntranceId = (portal == null) ? -1 : portal.portalId;
+ }
+
+ /**
+ * This should be called if a scene update was received that caused
+ * our underlying scene model to change.
+ */
+ public function updateReceived () :void
+ {
+ readPortals();
+ }
+
+ /** A casted reference to our scene model. */
+ protected var _smodel :SpotSceneModel;
+
+ /** A mapping from portal id to portal. */
+ protected var _portals :HashMap = new HashMap();
+
+ /** We don't allow more than ~32k portals in a scene. Things would
+ * slow down *way* before we got there. */
+ protected static const MAX_PORTAL_ID :int = int(Math.pow(2, 15) - 1);
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotSceneModel.as b/src/as/com/threerings/whirled/spot/data/SpotSceneModel.as
new file mode 100644
index 00000000..c793e342
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotSceneModel.as
@@ -0,0 +1,115 @@
+//
+// $Id: SpotSceneModel.java 3726 2005-10-11 19:17:43Z 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.spot.data {
+
+import com.threerings.util.ClassUtil;
+
+import com.threerings.io.Streamable;
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+import com.threerings.io.TypedArray;
+
+import com.threerings.whirled.data.AuxModel;
+import com.threerings.whirled.data.SceneModel;
+
+/**
+ * The spot scene model extends the standard scene model with information
+ * on portals. Portals are referenced by an identifier, unique within the
+ * scene and unchanging, so that portals can stably reference the target
+ * portal in the scene to which they connect.
+ */
+public class SpotSceneModel
+ implements Streamable, AuxModel
+{
+ /** An array containing all portals in this scene. */
+ public var portals :TypedArray =
+ new TypedArray(TypedArray.getJavaType(Portal));
+
+ /** The portal id of the default entrance to this scene. If a body
+ * enters the scene without coming from another scene, this is the
+ * portal at which they would appear. */
+ public var defaultEntranceId :int = -1;
+
+ /**
+ * Adds a portal to this scene model.
+ */
+ public function addPortal (portal :Portal) :void
+ {
+ portals.push(portal);
+ }
+
+ /**
+ * Removes a portal from this model.
+ */
+ public function removePortal (portal :Portal) :void
+ {
+ for (var ii :int = 0; ii < portals.length; ii++) {
+ if (portal.equals(portals[ii])) {
+ portals.splice(ii, 1);
+ return;
+ }
+ }
+ }
+
+ // documentation inherited from superinterface Cloneable
+ public function clone () :Object
+ {
+ var clazz :Class = ClassUtil.getClass(this);
+ var model :SpotSceneModel = new clazz();
+
+ for each (var portal :Portal in portals) {
+ model.portals.push(portal.clone());
+ }
+ return model;
+ }
+
+ // documentation inherited from interface Streamable
+ public function writeObject (out :ObjectOutputStream) :void
+ {
+ out.writeObject(portals);
+ out.writeInt(defaultEntranceId);
+ }
+
+ // documentation inherited from interface Streamable
+ public function readObject (ins :ObjectInputStream) :void
+ {
+ portals = (ins.readObject() as TypedArray);
+ defaultEntranceId = ins.readInt();
+ }
+
+ /**
+ * Locates and returns the {@link SpotSceneModel} among the auxiliary
+ * scene models associated with the supplied scene
+ * model. null is returned if no spot scene model could
+ * be found.
+ */
+ public static function getSceneModel (model :SceneModel) :SpotSceneModel
+ {
+ for each (var aux :AuxModel in model.auxModels) {
+ if (aux is SpotSceneModel) {
+ return (aux as SpotSceneModel);
+ }
+ }
+ return null;
+ }
+}
+}
diff --git a/src/as/com/threerings/whirled/spot/data/SpotSceneObject.as b/src/as/com/threerings/whirled/spot/data/SpotSceneObject.as
new file mode 100644
index 00000000..25d30c09
--- /dev/null
+++ b/src/as/com/threerings/whirled/spot/data/SpotSceneObject.as
@@ -0,0 +1,162 @@
+//
+// $Id: SpotSceneObject.java 3300 2005-01-08 22:05:00Z 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.spot.data {
+
+import com.threerings.io.ObjectInputStream;
+import com.threerings.io.ObjectOutputStream;
+
+import com.threerings.presents.dobj.DSet;
+import com.threerings.whirled.data.SceneObject;
+
+/**
+ * Extends the {@link SceneObject} with information specific to spots.
+ */
+public class SpotSceneObject extends SceneObject
+{
+ // AUTO-GENERATED: FIELDS START
+ /** The field name of the occupantLocs field. */
+ public static const OCCUPANT_LOCS :String = "occupantLocs";
+
+ /** The field name of the clusters field. */
+ public static const CLUSTERS :String = "clusters";
+ // AUTO-GENERATED: FIELDS END
+
+ /** A distributed set containing {@link SceneLocation} records for all
+ * occupants of this scene. */
+ public var occupantLocs :DSet = new DSet();
+
+ /** Contains information on all {@link Cluster}s in this scene. */
+ public var clusters :DSet = new DSet();
+
+ // AUTO-GENERATED: METHODS START
+ /**
+ * Requests that the specified entry be added to the
+ * occupantLocs set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public function addToOccupantLocs (elem :DSet_Entry) :void
+ {
+ requestEntryAdd(OCCUPANT_LOCS, elem);
+ }
+
+ /**
+ * Requests that the entry matching the supplied key be removed from
+ * the occupantLocs set. The set will not change until the
+ * event is actually propagated through the system.
+ */
+ public function removeFromOccupantLocs (key :Object) :void
+ {
+ requestEntryRemove(OCCUPANT_LOCS, key);
+ }
+
+ /**
+ * Requests that the specified entry be updated in the
+ * occupantLocs set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public function updateOccupantLocs (elem :DSet_Entry) :void
+ {
+ requestEntryUpdate(OCCUPANT_LOCS, elem);
+ }
+
+ /**
+ * Requests that the occupantLocs field be set to the
+ * specified value. Generally one only adds, updates and removes
+ * entries of a distributed set, but certain situations call for a
+ * complete replacement of the set value. The local value will be
+ * updated immediately and an event will be propagated through the
+ * system to notify all listeners that the attribute did
+ * change. Proxied copies of this object (on clients) will apply the
+ * value change when they received the attribute changed notification.
+ */
+ public function setOccupantLocs (value :DSet) :void
+ {
+ requestAttributeChange(OCCUPANT_LOCS, value, this.occupantLocs);
+ this.occupantLocs = value;
+ }
+
+ /**
+ * Requests that the specified entry be added to the
+ * clusters set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public function addToClusters (elem :DSet_Entry) :void
+ {
+ requestEntryAdd(CLUSTERS, elem);
+ }
+
+ /**
+ * Requests that the entry matching the supplied key be removed from
+ * the clusters set. The set will not change until the
+ * event is actually propagated through the system.
+ */
+ public function removeFromClusters (key :Object) :void
+ {
+ requestEntryRemove(CLUSTERS, key);
+ }
+
+ /**
+ * Requests that the specified entry be updated in the
+ * clusters set. The set will not change until the event is
+ * actually propagated through the system.
+ */
+ public function updateClusters (elem :DSet_Entry) :void
+ {
+ requestEntryUpdate(CLUSTERS, elem);
+ }
+
+ /**
+ * Requests that the clusters field be set to the
+ * specified value. Generally one only adds, updates and removes
+ * entries of a distributed set, but certain situations call for a
+ * complete replacement of the set value. The local value will be
+ * updated immediately and an event will be propagated through the
+ * system to notify all listeners that the attribute did
+ * change. Proxied copies of this object (on clients) will apply the
+ * value change when they received the attribute changed notification.
+ */
+ public function setClusters (value :DSet) :void
+ {
+ requestAttributeChange(CLUSTERS, value, this.clusters);
+ this.clusters = value;
+ }
+ // AUTO-GENERATED: METHODS END
+
+ // documentation inherited
+ override public function writeObject (out :ObjectOutputStream) :void
+ {
+ super.writeObject(out);
+
+ out.writeObject(occupantLocs);
+ out.writeObject(clusters);
+ }
+
+ // documentation inherited
+ override public function readObject (ins :ObjectInputStream) :void
+ {
+ super.readObject(ins);
+
+ occupantLocs = (ins.readObject() as DSet);
+ clusters = (ins.readObject() as DSet);
+ }
+}
+}
diff --git a/src/as/com/threerings/whirled/util/NoSuchSceneError.as b/src/as/com/threerings/whirled/util/NoSuchSceneError.as
new file mode 100644
index 00000000..1ddb9fb9
--- /dev/null
+++ b/src/as/com/threerings/whirled/util/NoSuchSceneError.as
@@ -0,0 +1,43 @@
+//
+// $Id: NoSuchSceneError.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.util {
+
+/**
+ * Thrown when an attempt to load a non-existent scene is made on the
+ * repository.
+ */
+public class NoSuchSceneError extends Error
+{
+ public function NoSuchSceneError (sceneid :int)
+ {
+ super("No such scene [sceneid=" + sceneid + "]");
+ _sceneid = sceneid;
+ }
+
+ public function getSceneId () :int
+ {
+ return _sceneid;
+ }
+
+ protected var _sceneid :int;
+}
+}
diff --git a/src/as/com/threerings/whirled/util/SceneFactory.as b/src/as/com/threerings/whirled/util/SceneFactory.as
new file mode 100644
index 00000000..8c7da365
--- /dev/null
+++ b/src/as/com/threerings/whirled/util/SceneFactory.as
@@ -0,0 +1,40 @@
+//
+// $Id: SceneFactory.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.util {
+
+import com.threerings.crowd.data.PlaceConfig;
+import com.threerings.whirled.data.Scene;
+import com.threerings.whirled.data.SceneModel;
+
+/**
+ * This is used by the Whirled services to obtain a {@link Scene}
+ * implementation given a scene model and associated data.
+ */
+public interface SceneFactory
+{
+ /**
+ * Creates a {@link Scene} implementation given the supplied scene
+ * model and place config.
+ */
+ function createScene (model :SceneModel, config :PlaceConfig) :Scene;
+}
+}
diff --git a/src/as/com/threerings/whirled/util/UpdateList.as b/src/as/com/threerings/whirled/util/UpdateList.as
new file mode 100644
index 00000000..4b8ad5a6
--- /dev/null
+++ b/src/as/com/threerings/whirled/util/UpdateList.as
@@ -0,0 +1,99 @@
+//
+// $Id: UpdateList.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.util {
+
+import com.threerings.whirled.data.SceneUpdate;
+
+/**
+ * A list specialized for storing {@link SceneUpdate} objects.
+ */
+public class UpdateList
+{
+ public function UpdateList ()
+ {
+ _updates = new Array();
+ }
+
+ /**
+ * Adds an update to this list. The update must follow appropriately
+ * the chain of updates established by the updates already in the list
+ * (meaning it must operate on one version higher than the most recent
+ * update already in the list).
+ */
+ public function addUpdate (update :SceneUpdate) :void
+ {
+ if (_minVersion == -1) {
+ // this is our first update, so we initialize our min version
+ _minVersion = update.getSceneVersion();
+
+ } else {
+ var gotVersion :int = update.getSceneVersion();
+ var expVersion :int = _minVersion + _updates.length;
+ if (gotVersion > expVersion) {
+ Log.getLog(this).warning("Update continuity broken, " +
+ "flushing list [got=" + update + ", expect=" + expVersion +
+ ", ucount=" + _updates.length + "].");
+ // flush out our old updates and start anew from here
+ _updates.length = 0;
+ _minVersion = expVersion;
+
+ } else if (gotVersion < expVersion) {
+ // we somehow got an update that's older than updates we
+ // already have? wick wick wack
+ throw new IllegalArgumentError("Invalid update version " +
+ "[want=" + expVersion + ", got=" + update + "]");
+ }
+ }
+ _updates.push(update);
+ }
+
+ /**
+ * Returns all of the updates that should be applied to a scene with
+ * the specified version to bring it up to date. null is
+ * returned if the scene's version is older than the oldest update in
+ * our list, in which case it cannot be brought up to date by applying
+ * updates from this list.
+ */
+ public function getUpdates (fromVersion :int) :Array /*of SceneUpdate*/
+ {
+ if (_minVersion == -1 || fromVersion < _minVersion) {
+ return null;
+ }
+
+ var offset :int = fromVersion - _minVersion;
+ return _updates.slice(offset);
+ }
+
+ /**
+ * Returns true if the supplied actual scene version is in accordance
+ * with the updates contained in this list.
+ */
+ public function validate (sceneVersion :int) :Boolean
+ {
+ return ((_minVersion == -1) || // we have no updates
+ (_minVersion + _updates.length == sceneVersion));
+ }
+
+ protected var _updates :Array;
+ protected var _minVersion :int = -1;
+}
+}
diff --git a/src/as/com/threerings/whirled/util/WhirledContext.as b/src/as/com/threerings/whirled/util/WhirledContext.as
new file mode 100644
index 00000000..dc63de53
--- /dev/null
+++ b/src/as/com/threerings/whirled/util/WhirledContext.as
@@ -0,0 +1,38 @@
+//
+// $Id: WhirledContext.java 3099 2004-08-27 02:21:06Z mdb $
+//
+// 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.util {
+
+import com.threerings.crowd.util.CrowdContext;
+import com.threerings.whirled.client.SceneDirector;
+
+/**
+ * The whirled context provides access to the various managers, etc. that
+ * are needed by the whirled client code.
+ */
+public interface WhirledContext extends CrowdContext
+{
+ /**
+ * Returns a reference to the scene director.
+ */
+ function getSceneDirector () :SceneDirector;
+}
+}