From 07485216137cef1a17e00ddc1371ed9315f9bcd3 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Tue, 4 Dec 2001 00:31:58 +0000 Subject: [PATCH] Zones! Scenes can be grouped into zones which are traversed via a higher-level mechanism. Zones provide zone summaries which can be used to generate maps of the zones on the client. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@727 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../whirled/zone/client/ZoneCodes.java | 20 ++ .../whirled/zone/client/ZoneDirector.java | 114 +++++++++++ .../whirled/zone/client/ZoneService.java | 36 ++++ .../whirled/zone/data/SceneSummary.java | 71 +++++++ .../whirled/zone/data/ZoneSummary.java | 54 ++++++ .../whirled/zone/data/ZonedBodyObject.java | 21 +++ .../whirled/zone/server/ZoneManager.java | 42 +++++ .../whirled/zone/server/ZoneProvider.java | 177 ++++++++++++++++++ .../whirled/zone/server/ZoneRegistry.java | 54 ++++++ 9 files changed, 589 insertions(+) create mode 100644 src/java/com/threerings/whirled/zone/client/ZoneCodes.java create mode 100644 src/java/com/threerings/whirled/zone/client/ZoneDirector.java create mode 100644 src/java/com/threerings/whirled/zone/client/ZoneService.java create mode 100644 src/java/com/threerings/whirled/zone/data/SceneSummary.java create mode 100644 src/java/com/threerings/whirled/zone/data/ZoneSummary.java create mode 100644 src/java/com/threerings/whirled/zone/data/ZonedBodyObject.java create mode 100644 src/java/com/threerings/whirled/zone/server/ZoneManager.java create mode 100644 src/java/com/threerings/whirled/zone/server/ZoneProvider.java create mode 100644 src/java/com/threerings/whirled/zone/server/ZoneRegistry.java diff --git a/src/java/com/threerings/whirled/zone/client/ZoneCodes.java b/src/java/com/threerings/whirled/zone/client/ZoneCodes.java new file mode 100644 index 000000000..718551118 --- /dev/null +++ b/src/java/com/threerings/whirled/zone/client/ZoneCodes.java @@ -0,0 +1,20 @@ +// +// $Id: ZoneCodes.java,v 1.1 2001/12/04 00:31:58 mdb Exp $ + +package com.threerings.whirled.zone.client; + +import com.threerings.whirled.client.SceneCodes; + +/** + * Contains codes used by the zone services. + */ +public interface ZoneCodes extends SceneCodes +{ + /** The module name for the zone services. */ + public static final String MODULE_NAME = "whirled!zone"; + + /** An error code indicating that a zone identified by a particular + * zone id does not exist. Usually generated by a failed moveTo + * request. */ + public static final String NO_SUCH_ZONE = "m.no_such_zone"; +} diff --git a/src/java/com/threerings/whirled/zone/client/ZoneDirector.java b/src/java/com/threerings/whirled/zone/client/ZoneDirector.java new file mode 100644 index 000000000..bd2080112 --- /dev/null +++ b/src/java/com/threerings/whirled/zone/client/ZoneDirector.java @@ -0,0 +1,114 @@ +// +// $Id: ZoneDirector.java,v 1.1 2001/12/04 00:31:58 mdb Exp $ + +package com.threerings.whirled.zone.client; + +import com.threerings.crowd.data.PlaceConfig; + +import com.threerings.whirled.client.DisplaySceneFactory; +import com.threerings.whirled.client.SceneDirector; +import com.threerings.whirled.client.persist.SceneRepository; + +import com.threerings.whirled.data.SceneModel; +import com.threerings.whirled.util.WhirledContext; + +import com.threerings.whirled.zone.data.ZoneSummary; + +/** + * The zone director extends the scene director with the notion of zones. + * Zones are self-contained, connected groups of scenes. The normal scene + * director services can be used to move from scene to scene, but moving + * to a new zone requires a special move request which can be accomplished + * via the zone director. The zone director also makes available the zone + * summary which provides information on the zone which can be used to + * generate an overview map or similar. + */ +public class ZoneDirector extends SceneDirector +{ + /** + * Constructs a zone director with the supplied context, repository + * and scene factory. A zone director is required on the client side + * for systems that wish to use the zone services. + */ + public ZoneDirector (WhirledContext ctx, SceneRepository screp, + DisplaySceneFactory dsfact) + { + super(ctx, screp, dsfact); + } + + /** + * Returns the summary for the zone currently occupied by the client + * or null if the client does not currently occupy a zone (not a + * normal situation). + */ + public ZoneSummary getZoneSummary () + { + return _summary; + } + + /** + * Requests that this client move the specified scene in the specified + * zone. A request will be made and when the response is received, the + * location observers will be notified of success or failure. + */ + public void moveTo (int zoneId, int sceneId) + { + // if the requested zone is the same as our current zone, we just + // want a regular old moveTo request + if (_summary != null && zoneId == _summary.zoneId) { + moveTo(sceneId); + + } else { // otherwise, we make a zoned moveTo request + // prepare to move to this scene (sets up pending data) + if (!prepareMoveTo(sceneId)) { + return; + } + + // check the version of our cached copy of the scene to which + // we're requesting to move; if we were unable to load it, assume + // a cached version of zero + int sceneVers = 0; + if (_pendingModel != null) { + sceneVers = _pendingModel.version; + } + + // issue a moveTo request + ZoneService.moveTo(_ctx.getClient(), zoneId, + sceneId, sceneVers, this); + } + } + + /** + * Called in response to a successful zoned moveTo + * request. + */ + public void handleMoveSucceeded ( + int invid, int placeId, PlaceConfig config, ZoneSummary summary) + { + // keep track of the summary + _summary = summary; + + // and pass the rest off to the standard scene transition code + handleMoveSucceeded(invid, placeId, config); + } + + /** + * Called in response to a successful zoned moveTo + * request when our cached scene was out of date and the server + * determined that we needed an updated copy. + */ + public void handleMoveSucceededPlusUpdate ( + int invid, int placeId, PlaceConfig config, ZoneSummary summary, + SceneModel model) + { + // keep track of the summary + _summary = summary; + + // and pass the rest off to the standard scene transition code + handleMoveSucceededPlusUpdate(invid, placeId, config, model); + } + + /** A reference to the zone summary for the currently occupied + * zone. */ + protected ZoneSummary _summary; +} diff --git a/src/java/com/threerings/whirled/zone/client/ZoneService.java b/src/java/com/threerings/whirled/zone/client/ZoneService.java new file mode 100644 index 000000000..13d376f82 --- /dev/null +++ b/src/java/com/threerings/whirled/zone/client/ZoneService.java @@ -0,0 +1,36 @@ +// +// $Id: ZoneService.java,v 1.1 2001/12/04 00:31:58 mdb Exp $ + +package com.threerings.whirled.zone.client; + +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationDirector; + +import com.threerings.whirled.Log; + +/** + * The zone service class provides the client interface to the zone + * related invocation services (e.g. moving between zones). + */ +public class ZoneService implements ZoneCodes +{ + /** + * Requests that that this client's body be moved to the specified + * scene in the specified zone. + * + * @param zoneId the zone id to which we want to move. + * @param sceneId the scene id to which we want to move. + * @param sceneVers the version number of the scene object that we + * have in our local repository. + */ + public static void moveTo (Client client, int zoneId, int sceneId, + int sceneVers, ZoneDirector rsptarget) + { + InvocationDirector invdir = client.getInvocationDirector(); + Object[] args = new Object[] { + new Integer(zoneId), new Integer(sceneId), new Integer(sceneVers) }; + invdir.invoke(MODULE_NAME, MOVE_TO_REQUEST, args, rsptarget); + Log.info("Sent moveTo request [zone=" + zoneId + + ", scene=" + sceneId + ", version=" + sceneVers + "]."); + } +} diff --git a/src/java/com/threerings/whirled/zone/data/SceneSummary.java b/src/java/com/threerings/whirled/zone/data/SceneSummary.java new file mode 100644 index 000000000..2b0cbcebe --- /dev/null +++ b/src/java/com/threerings/whirled/zone/data/SceneSummary.java @@ -0,0 +1,71 @@ +// +// $Id: SceneSummary.java,v 1.1 2001/12/04 00:31:58 mdb Exp $ + +package com.threerings.whirled.zone.data; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import com.threerings.presents.io.Streamable; +import com.threerings.presents.io.StreamableUtil; + +/** + * The scene summary class is used to provide info about the connected + * group of scenes that make up an island. The group of scenes that make + * up an island is a self-contained set of scenes, connected with one + * another (by portals) but not to any scenes outside the group. + */ +public class SceneSummary implements Streamable +{ + /** The id of this scene. */ + public int sceneId; + + /** The name of this scene. */ + public String name; + + /** The ids of the scenes to which this scene is connected via + * portals. */ + public int[] neighbors; + + /** + * Returns the population of this scene summary instance. This is + * synchronized because the population can be updated by a background + * thread. + */ + public synchronized int getPopulation () + { + return _population; + } + + /** + * Used to set the population of this scene summary instance. + */ + public synchronized void setPopulation (int population) + { + _population = population; + } + + // documentation inherited + public void writeTo (DataOutputStream out) + throws IOException + { + out.writeInt(sceneId); + out.writeUTF(name); + StreamableUtil.writeInts(out, neighbors); + out.writeInt(_population); + } + + // documentation inherited + public void readFrom (DataInputStream in) + throws IOException + { + sceneId = in.readInt(); + name = in.readUTF(); + neighbors = StreamableUtil.readInts(in); + _population = in.readInt(); + } + + /** The number of people currently occupying this scene. */ + protected int _population; +} diff --git a/src/java/com/threerings/whirled/zone/data/ZoneSummary.java b/src/java/com/threerings/whirled/zone/data/ZoneSummary.java new file mode 100644 index 000000000..a9a1f2eb4 --- /dev/null +++ b/src/java/com/threerings/whirled/zone/data/ZoneSummary.java @@ -0,0 +1,54 @@ +// +// $Id: ZoneSummary.java,v 1.1 2001/12/04 00:31:58 mdb Exp $ + +package com.threerings.whirled.zone.data; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import com.threerings.presents.io.Streamable; + +/** + * The zone summary contains information on a zone, including its name and + * summary info on all of the scenes in this zone (which can be used to + * generate a map of the zone on the client). + */ +public class ZoneSummary implements Streamable +{ + /** The zone's unique identifier. */ + public int zoneId; + + /** The name of the zone. */ + public String name; + + /** The summary information for all of the scenes in the zone. */ + public SceneSummary[] scenes; + + // documentation inherited + public void writeTo (DataOutputStream out) + throws IOException + { + out.writeInt(zoneId); + out.writeUTF(name); + int scount = scenes.length; + out.writeInt(scount); + for (int i = 0; i < scount; i++) { + scenes[i].writeTo(out); + } + } + + // documentation inherited + public void readFrom (DataInputStream in) + throws IOException + { + zoneId = in.readInt(); + name = in.readUTF(); + int scount = in.readInt(); + scenes = new SceneSummary[scount]; + for (int i = 0; i < scount; i++) { + scenes[i] = new SceneSummary(); + scenes[i].readFrom(in); + } + } +} diff --git a/src/java/com/threerings/whirled/zone/data/ZonedBodyObject.java b/src/java/com/threerings/whirled/zone/data/ZonedBodyObject.java new file mode 100644 index 000000000..9c0dbb605 --- /dev/null +++ b/src/java/com/threerings/whirled/zone/data/ZonedBodyObject.java @@ -0,0 +1,21 @@ +// +// $Id: ZonedBodyObject.java,v 1.1 2001/12/04 00:31:58 mdb Exp $ + +package com.threerings.whirled.zone.data; + +/** + * A system that uses the zone services must provide a body object + * extension that implements this interface. + */ +public interface ZonedBodyObject +{ + /** + * Returns the zone id currently occupied by this body. + */ + public int getZoneId (); + + /** + * Sets the zone id currently occupied by this body. + */ + public void setZoneId (int zoneId); +} diff --git a/src/java/com/threerings/whirled/zone/server/ZoneManager.java b/src/java/com/threerings/whirled/zone/server/ZoneManager.java new file mode 100644 index 000000000..738eb9217 --- /dev/null +++ b/src/java/com/threerings/whirled/zone/server/ZoneManager.java @@ -0,0 +1,42 @@ +// +// $Id: ZoneManager.java,v 1.1 2001/12/04 00:31:58 mdb Exp $ + +package com.threerings.whirled.zone.server; + +import com.threerings.whirled.zone.data.ZoneSummary; + +/** + * A zone is a collection of scenes organized into a connected group. A + * user can wander around within a zone, moving from scene to scene via + * the standard mechanisms. To move between zones, they must use a special + * mechanism (like at the dock, they can move from an island zone into + * their ship zone; or they can move from an island zone into their house + * zone). A zone provides scene summary information that can be used to + * display a map of the zone to the client. + */ +public interface ZoneManager +{ + /** + * Used to notify requesters when an asynchronous zone load has + * completed (successfully or not). + */ + public static interface ResolutionListener + { + /** + * Called when a zone was successfully resolved. + */ + public void zoneWasResolved (ZoneSummary summary); + + /** + * Called when a zone failed to resolve. + */ + public void zoneFailedToResolve (int zoneId, Exception reason); + } + + /** + * Resolves and delivers the scene summary information for the + * requested zone. Zone resolution is an asynchronous process, which + * necessitates this callback-style interface. + */ + public void resolveZone (int zoneId, ResolutionListener listener); +} diff --git a/src/java/com/threerings/whirled/zone/server/ZoneProvider.java b/src/java/com/threerings/whirled/zone/server/ZoneProvider.java new file mode 100644 index 000000000..e7eaa3a1e --- /dev/null +++ b/src/java/com/threerings/whirled/zone/server/ZoneProvider.java @@ -0,0 +1,177 @@ +// +// $Id: ZoneProvider.java,v 1.1 2001/12/04 00:31:58 mdb Exp $ + +package com.threerings.whirled.zone.server; + +import com.threerings.presents.server.InvocationProvider; +import com.threerings.presents.server.ServiceFailedException; + +import com.threerings.crowd.data.BodyObject; +import com.threerings.crowd.data.PlaceConfig; +import com.threerings.crowd.data.PlaceObject; +import com.threerings.crowd.server.LocationProvider; + +import com.threerings.whirled.Log; + +import com.threerings.whirled.data.SceneModel; +import com.threerings.whirled.server.SceneRegistry; +import com.threerings.whirled.server.SceneManager; + +import com.threerings.whirled.zone.client.ZoneCodes; +import com.threerings.whirled.zone.data.ZoneSummary; +import com.threerings.whirled.zone.data.ZonedBodyObject; + +/** + * Provides zone related services which are presently the ability to move + * from zone to zone. + */ +public class ZoneProvider + extends InvocationProvider implements ZoneCodes +{ + /** + * Constructs a zone provider that will interoperate with the supplied + * zone and scene registries. A system that wishes to use the zone + * services must instantiate a zone provider and register it with the + * invocation services. + */ + public ZoneProvider (ZoneRegistry zonereg, SceneRegistry screg) + { + _zonereg = zonereg; + _screg = screg; + } + + /** + * Processes a request from a client to move to a scene in a new zone. + * + * @param source the user requesting the move. + * @param invid the invocation id of the request. + * @param zoneId the identifier of the new zone. + * @param sceneId the identifier of the new scene. + * @param sceneVew the version of the scene model currently held by + * the client. + */ + public void handleMoveToRequest (BodyObject source, int invid, + int zoneId, int sceneId, int sceneVer) + { + // look up the zone manager for the zone + ZoneManager zmgr = _zonereg.getZoneManager(zoneId); + if (zmgr == null) { + Log.warning("Requested to enter a zone for which we have no " + + "manager [user=" + source + + ", zoneId=" + zoneId + "]."); + sendResponse(source, invid, MOVE_FAILED_RESPONSE, NO_SUCH_ZONE); + return; + } + + // make sure they're not already in that zone + if (((ZonedBodyObject)source).getZoneId() == zoneId) { + sendResponse(source, invid, MOVE_FAILED_RESPONSE, ALREADY_THERE); + return; + } + + // avoid cluttering up the method declaration with final keywords + final BodyObject fsource = source; + final int finvid = invid; + final int fsceneId = sceneId; + final int fsceneVer = sceneVer; + + // resolve the zone! + ZoneManager.ResolutionListener zl = new ZoneManager.ResolutionListener() + { + public void zoneWasResolved (ZoneSummary summary) { + continueMoveToRequest(fsource, finvid, summary, + fsceneId, fsceneVer); + } + + public void zoneFailedToResolve (int zoneId, Exception reason) { + Log.warning("Unable to resolve zone [zoneId=" + zoneId + + ", reason=" + reason + "]."); + sendResponse(fsource, finvid, + MOVE_FAILED_RESPONSE, NO_SUCH_ZONE); + } + }; + zmgr.resolveZone(zoneId, zl); + } + + /** + * This is called after we have resolved our zone. + */ + protected void continueMoveToRequest ( + BodyObject source, int invid, ZoneSummary summary, + int sceneId, int sceneVer) + { + // avoid cluttering up the method declaration with final keywords + final BodyObject fsource = source; + final int finvid = invid; + final ZoneSummary fsum = summary; + final int fsceneVer = sceneVer; + + // create a callback object that will handle the resolution or + // failed resolution of the scene + SceneRegistry.ResolutionListener rl = + new SceneRegistry.ResolutionListener() + { + public void sceneWasResolved (SceneManager scmgr) { + finishMoveToRequest(fsource, finvid, fsum, scmgr, fsceneVer); + } + + public void sceneFailedToResolve (int sceneId, Exception reason) { + Log.warning("Unable to resolve scene [sceneid=" + sceneId + + ", reason=" + reason + "]."); + // pretend like the scene doesn't exist to the client + sendResponse(fsource, finvid, + MOVE_FAILED_RESPONSE, NO_SUCH_PLACE); + } + }; + + // make sure the scene they are headed to is actually loaded into + // the server + _screg.resolveScene(sceneId, rl); + } + + /** + * This is called after the scene to which we are moving is guaranteed + * to have been loaded into the server. + */ + protected void finishMoveToRequest ( + BodyObject source, int invid, ZoneSummary summary, + SceneManager scmgr, int sceneVersion) + { + // move to the place object associated with this scene + PlaceObject plobj = scmgr.getPlaceObject(); + int ploid = plobj.getOid(); + + try { + // try doing the actual move + PlaceConfig config = LocationProvider.moveTo(source, ploid); + + // now that we've finally moved, we can update the user object + // with the new zone id + ((ZonedBodyObject)source).setZoneId(summary.zoneId); + + // check to see if they need a newer version of the scene data + SceneModel model = scmgr.getSceneModel(); + if (sceneVersion < model.version) { + // then send the moveTo response + sendResponse(source, invid, MOVE_SUCCEEDED_PLUS_UPDATE_RESPONSE, + new Object[] { new Integer(ploid), config, + summary, model }); + + } else { + // then send the moveTo response + sendResponse(source, invid, MOVE_SUCCEEDED_RESPONSE, + new Integer(ploid), config, summary); + } + + } catch (ServiceFailedException sfe) { + sendResponse(source, invid, + MOVE_FAILED_RESPONSE, sfe.getMessage()); + } + } + + /** The zone registry with which we communicate. */ + protected ZoneRegistry _zonereg; + + /** The scene registry with which we communicate. */ + protected SceneRegistry _screg; +} diff --git a/src/java/com/threerings/whirled/zone/server/ZoneRegistry.java b/src/java/com/threerings/whirled/zone/server/ZoneRegistry.java new file mode 100644 index 000000000..6a2a8a15b --- /dev/null +++ b/src/java/com/threerings/whirled/zone/server/ZoneRegistry.java @@ -0,0 +1,54 @@ +// +// $Id: ZoneRegistry.java,v 1.1 2001/12/04 00:31:58 mdb Exp $ + +package com.threerings.whirled.zone.server; + +import com.samskivert.util.Config; +import com.samskivert.util.HashIntMap; + +import com.threerings.whirled.Log; + +/** + * The zone registry takes care of mapping zone requests to the + * appropriate registered zone manager. + */ +public class ZoneRegistry +{ + /** + * Creates a zone manager with the supplied configuration. + */ + public ZoneRegistry (Config config) + { + } + + /** + * Registers the supplied zone manager as the manager for the + * specified zone type. Zone types are 7 bits and managers are + * responsible for making sure they don't use a zone type that + * collides with another manager (given that we have only three zone + * types at present, this doesn't seem unreasonable). + */ + public void registerZoneManager (byte zoneType, ZoneManager manager) + { + ZoneManager old = (ZoneManager)_managers.get(zoneType); + if (old != null) { + Log.warning("Zone manager already registered with requested " + + "type [type=" + zoneType + ", old=" + old + + ", new=" + manager + "]."); + } else { + _managers.put(zoneType, manager); + } + } + + /** + * Returns the zone manager that handles the specified zone id. + */ + public ZoneManager getZoneManager (int zoneId) + { + int zoneType = (0xFF000000 & zoneId) >> 24; + return (ZoneManager)_managers.get(zoneType); + } + + /** A table of zone managers. */ + protected HashIntMap _managers = new HashIntMap(); +}