From 0bf43bc2e8811efab1aba42b334154f677812406 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Tue, 29 Mar 2005 23:18:59 +0000 Subject: [PATCH] Step two in the great Stage refactor. I don't know how many steps will be needed all told, but I like to see the number keep going up. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3438 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../parlor/game/server/GameDispatcher.java | 2 +- .../com/threerings/stage/data/StageCodes.java | 54 ++ .../stage/data/StageOccupantInfo.java | 40 + .../com/threerings/stage/data/StageScene.java | 78 +- .../stage/data/StageSceneConfig.java | 26 + .../stage/data/StageSceneMarshaller.java | 54 ++ .../stage/data/StageSceneObject.java | 83 ++ .../stage/server/StageSceneDispatcher.java | 71 ++ .../stage/server/StageSceneManager.java | 817 ++++++++++++++++++ .../stage/server/StageSceneProvider.java | 25 + .../threerings/stage/server/StageServer.java | 67 ++ .../whirled/server/SceneManager.java | 35 +- .../server/persist/DummySceneRepository.java | 4 +- .../server/persist/SceneRepository.java | 12 +- 14 files changed, 1290 insertions(+), 78 deletions(-) create mode 100644 src/java/com/threerings/stage/data/StageCodes.java create mode 100644 src/java/com/threerings/stage/data/StageOccupantInfo.java create mode 100644 src/java/com/threerings/stage/data/StageSceneConfig.java create mode 100644 src/java/com/threerings/stage/data/StageSceneMarshaller.java create mode 100644 src/java/com/threerings/stage/data/StageSceneObject.java create mode 100644 src/java/com/threerings/stage/server/StageSceneDispatcher.java create mode 100644 src/java/com/threerings/stage/server/StageSceneManager.java create mode 100644 src/java/com/threerings/stage/server/StageSceneProvider.java create mode 100644 src/java/com/threerings/stage/server/StageServer.java diff --git a/src/java/com/threerings/parlor/game/server/GameDispatcher.java b/src/java/com/threerings/parlor/game/server/GameDispatcher.java index 48b590190..d635244ea 100644 --- a/src/java/com/threerings/parlor/game/server/GameDispatcher.java +++ b/src/java/com/threerings/parlor/game/server/GameDispatcher.java @@ -21,8 +21,8 @@ package com.threerings.parlor.game.server; -import com.threerings.parlor.game.data.GameMarshaller; import com.threerings.parlor.game.client.GameService; +import com.threerings.parlor.game.data.GameMarshaller; import com.threerings.presents.client.Client; import com.threerings.presents.data.ClientObject; import com.threerings.presents.data.InvocationMarshaller; diff --git a/src/java/com/threerings/stage/data/StageCodes.java b/src/java/com/threerings/stage/data/StageCodes.java new file mode 100644 index 000000000..065b87812 --- /dev/null +++ b/src/java/com/threerings/stage/data/StageCodes.java @@ -0,0 +1,54 @@ +// +// $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.stage.data; + +import com.threerings.util.MessageBundle; + +import com.threerings.crowd.data.BodyObject; + +/** + * Codes and constants relating to the Stage system. + */ +public interface StageCodes +{ + /** The i18n bundle identifier for the Stage system. */ + public static final String STAGE_MESSAGE_BUNDLE = "stage.general"; + + /** The resource set that contains our tileset bundles. */ + public static final String TILESET_RSRC_SET = "tilesets"; + + /** The access control identifier for scene modification privileges. + * See {@link BodyObject#checkAccess}. */ + public static final String MODIFY_SCENE_ACCESS = "stage.modify_scene"; + + /** The access control identifier for potentially damaging scene + * modification privileges. See {@link BodyObject#checkAccess}. */ + public static final String MUTILATE_SCENE_ACCESS = "stage.mutilate_scene"; + + /** An error delivered when adding objects to scenes. */ + public static final String ERR_NO_OVERLAP = MessageBundle.qualify( + STAGE_MESSAGE_BUNDLE, "m.addobj_no_overlap"); + + /** An error delivered when adding objects to scenes. */ + public static final String ERR_CANNOT_CLUSTER = MessageBundle.qualify( + STAGE_MESSAGE_BUNDLE, "m.cannot_cluster"); +} diff --git a/src/java/com/threerings/stage/data/StageOccupantInfo.java b/src/java/com/threerings/stage/data/StageOccupantInfo.java new file mode 100644 index 000000000..25be04f78 --- /dev/null +++ b/src/java/com/threerings/stage/data/StageOccupantInfo.java @@ -0,0 +1,40 @@ +// +// $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.stage.data; + +import com.threerings.crowd.data.OccupantInfo; + +/** + * Extends the standard occupant info with stage specific business. + */ +public class StageOccupantInfo extends OccupantInfo +{ + /** + * Should return true if the occupant in question is available to be + * clustered with, false if they are "busy". This means that a user + * can click on them and start a chat circle. + */ + public boolean isClusterable () + { + return true; + } +} diff --git a/src/java/com/threerings/stage/data/StageScene.java b/src/java/com/threerings/stage/data/StageScene.java index 38a81f328..899144977 100644 --- a/src/java/com/threerings/stage/data/StageScene.java +++ b/src/java/com/threerings/stage/data/StageScene.java @@ -40,27 +40,6 @@ public class StageScene extends SceneImpl readInterestingObjects(); } - protected void readInterestingObjects () - { - _objects.clear(); - StageMisoSceneModel mmodel = StageMisoSceneModel.getSceneModel(_model); - if (mmodel != null) { - mmodel.getInterestingObjects(_objects); - } - } - - // documentation inherited - public void updateReceived (SceneUpdate update) - { - super.updateReceived(update); - - // update our spot scene delegate - _sdelegate.updateReceived(); - - // re-read our interesting objects - readInterestingObjects(); - } - /** * Returns the scene type (e.g. "world", "port", "bank", etc.). */ @@ -103,6 +82,14 @@ public class StageScene extends SceneImpl _model.setDefaultColor(classId, colorId); } + /** + * Iterates over all of the interesting objects in this scene. + */ + public Iterator enumerateObjects () + { + return _objects.iterator(); + } + /** * Adds a new object to this scene. */ @@ -136,42 +123,16 @@ public class StageScene extends SceneImpl return removed; } - /** - * Iterates over all of the interesting objects in this scene. - */ - public Iterator enumerateObjects () + // documentation inherited + public void updateReceived (SceneUpdate update) { - return _objects.iterator(); - } + super.updateReceived(update); - /** - * Applies the supplied scene update to this scene. - */ - public void applyUpdate (SceneUpdate update) - { - if (update instanceof AddObjectUpdate) { - AddObjectUpdate aou = (AddObjectUpdate) update; + // update our spot scene delegate + _sdelegate.updateReceived(); - // remove any occluded objects - if (aou.casualties != null) { - for (int ii = 0; ii < aou.casualties.length; ii++) { - removeObject(aou.casualties[ii]); - } - } - - // add the object - addObject(aou.info); - - } else if (update instanceof DefaultColorUpdate) { - DefaultColorUpdate dcu = (DefaultColorUpdate) update; - setDefaultColor(dcu.classId, dcu.colorId); - - } else { - Log.warning("Unknown scene update applied to StageScene: " + update); - } - - // increment our scene version - setVersion(getVersion() + 1); + // re-read our interesting objects + readInterestingObjects(); } // documentation inherited @@ -224,6 +185,15 @@ public class StageScene extends SceneImpl _sdelegate.setDefaultEntrance(portal); } + protected void readInterestingObjects () + { + _objects.clear(); + StageMisoSceneModel mmodel = StageMisoSceneModel.getSceneModel(_model); + if (mmodel != null) { + mmodel.getInterestingObjects(_objects); + } + } + /** A reference to our scene model. */ protected StageSceneModel _model; diff --git a/src/java/com/threerings/stage/data/StageSceneConfig.java b/src/java/com/threerings/stage/data/StageSceneConfig.java new file mode 100644 index 000000000..ef7f4dff1 --- /dev/null +++ b/src/java/com/threerings/stage/data/StageSceneConfig.java @@ -0,0 +1,26 @@ +// +// $Id: StageSceneConfig.java 8478 2003-05-07 05:12:26Z mdb $ + +package com.threerings.stage.data; + +import com.threerings.crowd.data.PlaceConfig; + +import com.threerings.stage.client.StageSceneController; + +/** + * Place configuration for the main isometric scenes in Stage. + */ +public class StageSceneConfig extends PlaceConfig +{ + // documentation inherited + public Class getControllerClass () + { + return StageSceneController.class; + } + + // documentation inherited + public String getManagerClassName () + { + return "com.threerings.stage.server.StageSceneManager"; + } +} diff --git a/src/java/com/threerings/stage/data/StageSceneMarshaller.java b/src/java/com/threerings/stage/data/StageSceneMarshaller.java new file mode 100644 index 000000000..5db275fb2 --- /dev/null +++ b/src/java/com/threerings/stage/data/StageSceneMarshaller.java @@ -0,0 +1,54 @@ +// +// $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.stage.data; + +import com.threerings.miso.data.ObjectInfo; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationService; +import com.threerings.presents.data.InvocationMarshaller; +import com.threerings.presents.dobj.InvocationResponseEvent; +import com.threerings.stage.client.StageSceneService; + +/** + * Provides the implementation of the {@link StageSceneService} 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 StageSceneMarshaller extends InvocationMarshaller + implements StageSceneService +{ + /** The method id used to dispatch {@link #addObject} requests. */ + public static final int ADD_OBJECT = 1; + + // documentation inherited from interface + public void addObject (Client arg1, ObjectInfo arg2, InvocationService.ConfirmListener arg3) + { + InvocationMarshaller.ConfirmMarshaller listener3 = new InvocationMarshaller.ConfirmMarshaller(); + listener3.listener = arg3; + sendRequest(arg1, ADD_OBJECT, new Object[] { + arg2, listener3 + }); + } + +} diff --git a/src/java/com/threerings/stage/data/StageSceneObject.java b/src/java/com/threerings/stage/data/StageSceneObject.java new file mode 100644 index 000000000..511bcf3a9 --- /dev/null +++ b/src/java/com/threerings/stage/data/StageSceneObject.java @@ -0,0 +1,83 @@ +// +// $Id: StageSceneObject.java 18473 2004-12-28 03:52:57Z mdb $ + +package com.threerings.stage.data; + +import com.threerings.whirled.spot.data.SpotSceneObject; + +/** + * Extends the basic {@link SpotSceneObject} with data and services + * specific to isometric stage scenes. + */ +public class StageSceneObject extends SpotSceneObject +{ + // AUTO-GENERATED: FIELDS START + /** The field name of the stageSceneService field. */ + public static final String STAGE_SCENE_SERVICE = "stageSceneService"; + + /** The field name of the lightLevel field. */ + public static final String LIGHT_LEVEL = "lightLevel"; + + /** The field name of the lightShade field. */ + public static final String LIGHT_SHADE = "lightShade"; + // AUTO-GENERATED: FIELDS END + + /** Provides stage scene services. */ + public StageSceneMarshaller stageSceneService; + + /** The light level in this scene. 0f being fully on, 1f fully shaded. */ + public float lightLevel = 0f; + + /** The color of the light, if the light level is not 0f. */ + public int lightShade = 0; + + // AUTO-GENERATED: METHODS START + /** + * Requests that the stageSceneService field be set to the + * specified 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 void setStageSceneService (StageSceneMarshaller value) + { + StageSceneMarshaller ovalue = this.stageSceneService; + requestAttributeChange( + STAGE_SCENE_SERVICE, value, ovalue); + this.stageSceneService = value; + } + + /** + * Requests that the lightLevel field be set to the + * specified 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 void setLightLevel (float value) + { + float ovalue = this.lightLevel; + requestAttributeChange( + LIGHT_LEVEL, new Float(value), new Float(ovalue)); + this.lightLevel = value; + } + + /** + * Requests that the lightShade field be set to the + * specified 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 void setLightShade (int value) + { + int ovalue = this.lightShade; + requestAttributeChange( + LIGHT_SHADE, new Integer(value), new Integer(ovalue)); + this.lightShade = value; + } + // AUTO-GENERATED: METHODS END +} diff --git a/src/java/com/threerings/stage/server/StageSceneDispatcher.java b/src/java/com/threerings/stage/server/StageSceneDispatcher.java new file mode 100644 index 000000000..7efe7de38 --- /dev/null +++ b/src/java/com/threerings/stage/server/StageSceneDispatcher.java @@ -0,0 +1,71 @@ +// +// $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.stage.server; + +import com.threerings.miso.data.ObjectInfo; +import com.threerings.presents.client.Client; +import com.threerings.presents.client.InvocationService; +import com.threerings.presents.data.ClientObject; +import com.threerings.presents.data.InvocationMarshaller; +import com.threerings.presents.server.InvocationDispatcher; +import com.threerings.presents.server.InvocationException; +import com.threerings.stage.client.StageSceneService; +import com.threerings.stage.data.StageSceneMarshaller; + +/** + * Dispatches requests to the {@link StageSceneProvider}. + */ +public class StageSceneDispatcher extends InvocationDispatcher +{ + /** + * Creates a dispatcher that may be registered to dispatch invocation + * service requests for the specified provider. + */ + public StageSceneDispatcher (StageSceneProvider provider) + { + this.provider = provider; + } + + // documentation inherited + public InvocationMarshaller createMarshaller () + { + return new StageSceneMarshaller(); + } + + // documentation inherited + public void dispatchRequest ( + ClientObject source, int methodId, Object[] args) + throws InvocationException + { + switch (methodId) { + case StageSceneMarshaller.ADD_OBJECT: + ((StageSceneProvider)provider).addObject( + source, + (ObjectInfo)args[0], (InvocationService.ConfirmListener)args[1] + ); + return; + + default: + super.dispatchRequest(source, methodId, args); + } + } +} diff --git a/src/java/com/threerings/stage/server/StageSceneManager.java b/src/java/com/threerings/stage/server/StageSceneManager.java new file mode 100644 index 000000000..443ee7758 --- /dev/null +++ b/src/java/com/threerings/stage/server/StageSceneManager.java @@ -0,0 +1,817 @@ +// +// $Id: WorldSceneManager.java 19769 2005-03-17 07:38:31Z mdb $ + +package com.threerings.stage.server; + +import java.awt.Point; +import java.awt.Rectangle; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; + +import com.samskivert.io.PersistenceException; +import com.samskivert.util.HashIntMap; +import com.samskivert.util.Invoker; +import com.samskivert.util.StringUtil; + +import com.threerings.media.util.AStarPathUtil; +import com.threerings.media.util.MathUtil; +import com.threerings.miso.data.ObjectInfo; +import com.threerings.miso.util.MisoSceneMetrics; +import com.threerings.miso.util.MisoUtil; + +import com.threerings.crowd.data.BodyObject; +import com.threerings.presents.data.ClientObject; +import com.threerings.presents.server.InvocationException; + +import com.threerings.whirled.data.SceneUpdate; +import com.threerings.whirled.spot.data.Cluster; +import com.threerings.whirled.spot.data.ClusterObject; +import com.threerings.whirled.spot.data.Location; +import com.threerings.whirled.spot.data.Portal; +import com.threerings.whirled.spot.data.SceneLocation; +import com.threerings.whirled.spot.server.SpotSceneManager; + +import com.threerings.stage.Log; +import com.threerings.stage.client.StageSceneService; +import com.threerings.stage.data.AddObjectUpdate; +import com.threerings.stage.data.DefaultColorUpdate; +import com.threerings.stage.data.StageCodes; +import com.threerings.stage.data.StageMisoSceneModel; +import com.threerings.stage.data.StageOccupantInfo; +import com.threerings.stage.data.StageScene; +import com.threerings.stage.data.StageSceneMarshaller; +import com.threerings.stage.data.StageSceneModel; +import com.threerings.stage.data.StageSceneObject; +import com.threerings.stage.util.StageSceneUtil; + +/** + * Defines extensions to the basic Stage scene manager specific to + * displaying isometric "stage" scenes (these may be indoor, outdoor or + * aboard a vessel). + */ +public class StageSceneManager extends SpotSceneManager + implements StageSceneProvider +{ + /** + * Returns a traversal predicate for use with {@link + * StageSceneUtil#findStandingSpot} that validates whether a player can + * stand in the searched spots. + */ + public AStarPathUtil.TraversalPred getCanStandPred () + { + // this checks to see if we can stand at a particular tile coord + return new AStarPathUtil.TraversalPred() { + public boolean canTraverse (Object traverser, int x, int y) { + _tloc.x = MisoUtil.toFull(x, 2); + _tloc.y = MisoUtil.toFull(y, 2); + return mayStandAtLocation((BodyObject)traverser, _tloc); + } + protected Location _tloc = new Location(); + }; + } + + /** + * Adds the supplied object to this scene. A persistent update is + * generated and broadcast to all scene occupants. The update is + * stored in the repository for communication to future occupants of + * the scene and then entire complex process of changing our virtual + * game world is effected at the simple call of this single method. + * + * @param killOverlap if true, overlapping object will be removed, and + * the allowOverlap argument will be ignored. + * @param allowOverlap if true, overlapping objects will be allowed + * but one must be *very* careful to ensure that they know what they + * are doing (ie. the objects have render priorities that correctly + * handle the overlap). + * + * @return true if the object was added, false if the add was rejected + * because the object overlaps an existing scene object. + */ + public boolean addObject (ObjectInfo info, boolean killOverlap, + boolean allowOverlap) + { + // determine whether or not any object overlap our footprint + Rectangle foot = StageSceneUtil.getObjectFootprint( + StageServer.tilemgr, info.tileId, info.x, info.y); + if (foot == null) { + Log.warning("Aiya! Unable to compute object footprint! " + + "[where=" + where() + ", info=" + info + "]."); + return false; + } + + ObjectInfo[] lappers = StageSceneUtil.getIntersectedObjects( + StageServer.tilemgr, (StageSceneModel)_sscene.getSceneModel(), + foot); + if (!killOverlap && lappers.length > 0 && !allowOverlap) { + // no overlapping allowed + return false; + } + + // create our scene update which will be stored in the database + // and used to efficiently update clients + final AddObjectUpdate update = new AddObjectUpdate(); + update.init(_sscene.getId(), _sscene.getVersion(), info, + killOverlap ? lappers : null); + + Log.info("Adding object '" + update + "."); + recordUpdate(update, true); + + return true; + } + + /** + * Changes the default colorization for the specified color class. + * A persistent update is generated and broadcast to all scene + * occupants. The update is stored in the repository for communication + * to future occupants of the scene and then entire complex process + * of changing our virtual game world is effected at the simple call + * of this single method. + */ + public void setColor (int classId, int colorId) + { + DefaultColorUpdate update = new DefaultColorUpdate(); + update.init(_sscene.getId(), _sscene.getVersion(), classId, colorId); + recordUpdate(update, false); + } + + /** + * Returns true if the specified tile coordinate is passable (the base + * tile is passable and it is not in the footprint of an object). + */ + public boolean isPassable (int tx, int ty) + { + return (StageSceneUtil.isPassable( + StageServer.tilemgr, _mmodel.getBaseTileId(tx, ty)) && + !checkContains(_footprints.iterator(), tx, ty)); + } + + /** + * Called by NPPs to determine whether or not they can stand at the + * specified location. + */ + public boolean mayStandAtLocation (BodyObject source, Location loc) + { + return validateLocation(source, loc, false); + } + + // documentation inherited from interface + public void addObject (ClientObject caller, ObjectInfo info, + StageSceneService.ConfirmListener listener) + throws InvocationException + { + BodyObject user = (BodyObject)caller; + String err = user.checkAccess(StageCodes.MODIFY_SCENE_ACCESS, _sscene); + if (err != null) { + throw new InvocationException(err); + } + + boolean allowOverlap = + (user.checkAccess(StageCodes.MODIFY_SCENE_ACCESS, _sscene) == null); + if (addObject(info, false, allowOverlap)) { + listener.requestProcessed(); + } else { + listener.requestFailed(StageCodes.ERR_NO_OVERLAP); + } + } + + // documentation inherited + protected void gotSceneData () + { + super.gotSceneData(); + + // cast some scene related bits + _sscene = (StageScene)_scene; + _mmodel = StageMisoSceneModel.getSceneModel(_scene.getSceneModel()); + + // note the footprints of all objects and portals in this scene + computeFootprints(); + } + + /** + * Applies the supplied scene update to our runtime scene and then + * fires off an invocation unit to apply the update to the scene + * database. Finally the update is "recorded" which broadcasts the + * update to all occupants of the scene and stores the update so that + * it can be provided as a patch to future scene visitors. + */ + protected void recordUpdate (SceneUpdate update, boolean tilesModified) + { + // do the standard update processing + recordUpdate(update); + + // then recompute some internal structures if need be + if (tilesModified) { + sceneTilesModified(); + } + } + + /** + * Called when any change is made to a scene's base or object tiles + * (resulting in a change in the way the scene looks). If any sneaky + * business need be done to deal with said addition, it can be handled + * here. + */ + protected void sceneTilesModified () + { + // compute the object footprints of all objects in this scene + computeFootprints(); + } + + // documentation inherited + protected void didStartup () + { + super.didStartup(); + _ssobj = (StageSceneObject)_plobj; + + // register and fill in our stage scene service + StageSceneMarshaller service = (StageSceneMarshaller) + StageServer.invmgr.registerDispatcher( + new StageSceneDispatcher(this), false); + _ssobj.setStageSceneService(service); + } + + // documentation inherited + protected void didShutdown () + { + super.didShutdown(); + StageServer.invmgr.clearDispatcher(_ssobj.stageSceneService); + } + + // documentation inherited + protected Class getPlaceObjectClass () + { + return StageSceneObject.class; + } + + // documentation inherited + protected void bodyLeft (int bodyOid) + { + super.bodyLeft(bodyOid); + + // out ye go! + _loners.remove(bodyOid); + } + + // documentation inherited + protected void updateLocation (BodyObject source, Location loc) + { + super.updateLocation(source, loc); + + // keep a rectangle around for each un-clustered occupant + int tx = MisoUtil.fullToTile(loc.x), ty = MisoUtil.fullToTile(loc.y); + _loners.put(source.getOid(), new Rectangle(tx, ty, 1, 1)); + } + + /** + * Computes the footprints of all objects and portals in this scene. + * This is done when we are first resolved and following any + * modifications to the scene's tile data. + */ + protected void computeFootprints () + { + _footprints.clear(); + _mmodel.visitObjects(new StageMisoSceneModel.ObjectVisitor() { + public void visit (ObjectInfo info) { + Rectangle foot = StageSceneUtil.getObjectFootprint( + StageServer.tilemgr, info.tileId, info.x, info.y); + _footprints.add(foot); + } + }); + +// Log.info("Computed footprints [where=" + where () + +// ", rects=" + StringUtil.toString(_footprints) + "]."); + + // place the tile coordinates of our portals into a set for + // efficient comparison with location coordinates + _plocs.clear(); + for (Iterator iter = _sscene.getPortals(); iter.hasNext(); ) { + Portal port = (Portal)iter.next(); + _plocs.add(new Point(MisoUtil.fullToTile(port.x), + MisoUtil.fullToTile(port.y))); + } + } + + /** + * Helper function for {@link #mayStandAtLocation} and {@link + * #validateLocation(BodyObject,Location)}. + */ + protected boolean validateLocation (BodyObject source, Location loc, + boolean allowPortals) + { + int tx = MisoUtil.fullToTile(loc.x), ty = MisoUtil.fullToTile(loc.y); + + // make sure the tile at that location is passable + if (!StageSceneUtil.isPassable(StageServer.tilemgr, + _mmodel.getBaseTileId(tx, ty))) { +// Log.info("Rejecting non-passable loc [who=" + source.who() + +// ", loc=" + loc + "]."); + return false; + } + + // if they're moving to stand on a portal, let them do it + if (allowPortals && _plocs.contains(new Point(tx, ty))) { + return true; + } + + // if they are already standing on this tile, allow it + Location cloc = (Location) + _ssobj.occupantLocs.get(new Integer(source.getOid())); + if (cloc != null && + MisoUtil.fullToTile(cloc.x) == tx && + MisoUtil.fullToTile(cloc.y) == ty) { +// Log.info("Allowing adjust [who=" + source.who () + +// ", cloc=" + cloc + ", nloc=" + loc + "]."); + return true; + } + + // make sure they're not standing in a cluster footprint, an + // object footprint, or in the same tile as another scene occupant + if (checkContains(_ssobj.clusters.iterator(), tx, ty) || + checkContains(_footprints.iterator(), tx, ty) || + checkContains(_loners.values().iterator(), tx, ty)) { +// Log.info("Rejecting loc [who=" + source.who() + +// ", loc=" + loc + ", inCluster=" + +// checkContains(_ssobj.clusters.iterator(), tx, ty) + +// ", inFootprint=" + +// checkContains(_footprints.iterator(), tx, ty) + +// ", onLoner=" + +// checkContains(_loners.values().iterator(), tx, ty) + +// "]."); + return false; + } + + return true; + } + + /** Helper function for {@link #validateLocation}. */ + protected boolean checkContains (Iterator iter, int tx, int ty) + { + while (iter.hasNext()) { + Rectangle rect = (Rectangle)iter.next(); + if (rect.contains(tx, ty)) { +// Log.info(StringUtil.toString(rect) + " contains " + +// StringUtil.coordsToString(tx, ty) + "."); + return true; + } + } + return false; + } + + // documentation inherited + protected SceneLocation computeEnteringLocation ( + BodyObject body, Portal entry) + { + // sanity check + if (entry == null) { + Log.warning("Requested to compute entering location for " + + "non-existent portal [where=" + where() + + ", who=" + body.who() + "]."); + entry = _sscene.getDefaultEntrance(); + } + + MisoSceneMetrics metrics = StageSceneUtil.getMetrics(); + SceneLocation loc = new SceneLocation( + entry.getOppLocation(), body.getOid()); + int tx = MisoUtil.fullToTile(loc.x), + ty = MisoUtil.fullToTile(loc.y); + int oidx = loc.orient/2; + int lidx = (oidx+3)%4; // rotate to the left + int ridx = (oidx+1)%4; // rotate to the right + + // start in the row in front of the portal and search forward, + // checking the center, then the spot(s) to the left, then the + // spot(s) to the right (fanning out by one tile each time) + final int MAX_FAN = 4; + LOC_SEARCH: + for (int fan = 1; fan < MAX_FAN; fan++) { + tx += PORTAL_DX[oidx]; + ty += PORTAL_DY[oidx]; + + // look in the center column + if (checkEntry(metrics, body, tx, ty, loc)) { + break LOC_SEARCH; + } + + // look to the left + for (int lx = tx, ly = ty, ff = 0; ff < fan; ff++) { + lx += PORTAL_DX[lidx]; + ly += PORTAL_DY[lidx]; + if (checkEntry(metrics, body, lx, ly, loc)) { + break LOC_SEARCH; + } + } + + // look to the right + for (int rx = tx, ry = ty, ff = 0; ff < fan; ff++) { + rx += PORTAL_DX[ridx]; + ry += PORTAL_DY[ridx]; + if (checkEntry(metrics, body, rx, ry, loc)) { + break LOC_SEARCH; + } + } + + // if this is our last pass and we didn't find anything, + // revert back to the portal location + if (fan == MAX_FAN-1) { + loc = new SceneLocation( + entry.getOppLocation(), body.getOid()); + } + } + + tx = MisoUtil.fullToTile(loc.x); + ty = MisoUtil.fullToTile(loc.y); + _loners.put(body.getOid(), new Rectangle(tx, ty, 1, 1)); + return loc; + } + + /** Helper function for {@link #computeEnteringLocation}. */ + protected boolean checkEntry (MisoSceneMetrics metrics, BodyObject body, + int tx, int ty, SceneLocation sloc) + { + sloc.x = MisoUtil.toFull(tx, metrics.finegran/2); + sloc.y = MisoUtil.toFull(ty, metrics.finegran/2); + return validateLocation(body, sloc, false); + } + + // documentation inherited + protected boolean validateLocation (BodyObject source, Location loc) + { + // TODO: make sure the user isn't warping to hell and gone (and if + // they are, make sure they're an admin) + return validateLocation(source, loc, true); + } + + // documentation inherited + protected boolean canAddBody (ClusterRecord clrec, BodyObject body) + { + // make sure we have a setting for clusters of this size + if (clrec.size() >= TARGET_SIZE.length-2) { + return false; + } + + // if this is a brand new cluster, just make it 2x2 and be done + // with it + Cluster cl = clrec.getCluster(); + if (cl.width == 0) { + cl.width = 2; + cl.height = 2; +// Log.info("Created new cluster for " + body.who() + +// " (" + cl + ")."); + return true; + } + +// Log.info("Maybe adding " + body.who() + " to " + cl + "."); + + // if the cluster is already big enough, we're groovy + int target = TARGET_SIZE[clrec.size()+1]; + if (cl.width >= target) { + return true; + } + + // make an expanded footprint and try to fit it somewhere + int expand = target-cl.width; + Rectangle rect = null; + for (int ii = 0; ii < X_OFF.length; ii++) { + rect = new Rectangle(cl.x + expand*X_OFF[ii], + cl.y + expand*Y_OFF[ii], + cl.width+expand, cl.height+expand); + + // if this rect overlaps objects, other clusters, portals or + // impassable tiles, it's no good + if (checkIntersects(_ssobj.clusters.iterator(), rect, cl) || + checkIntersects(_footprints.iterator(), rect, cl) || + checkPortals(rect) || checkViolatesPassability(rect)) { + rect = null; + } else { + break; + } + } + + // if we couldn't expand in any direction, it's no go + if (rect == null) { + Log.info("Couldn't expand cluster " + cl + "."); + return false; + } + + // now look to see if we just expanded our cluster over top of any + // unsuspecting standers by and if so, attempt to subsume them + for (Iterator iter = _loners.keySet().iterator(); + iter.hasNext(); ) { + int bodyOid = ((Integer)iter.next()).intValue(); + // skip ourselves + if (bodyOid == body.getOid()) { + continue; + } + + // do the right thing with a person standing right on the + // cluster (they're the person we're clustering with) + Rectangle trect = (Rectangle)_loners.get(bodyOid); + if (trect.equals(cl)) { + continue; + } + + if (trect.intersects(rect)) { + // make sure this person isn't already in our cluster + ClusterObject clobj = clrec.getClusterObject(); + if (clobj != null && clobj.occupants.contains(bodyOid)) { + Log.warning("Ignoring stale occupant [where=" + where() + + ", cluster=" + cl + ", occ=" + bodyOid + "]."); + continue; + } + + // make sure the subsumee exists + final BodyObject bobj = (BodyObject) + StageServer.omgr.getObject(bodyOid); + if (bobj == null) { + Log.warning("Can't subsume disappeared body " + + "[where=" + where() + ", cluster=" + cl + + ", boid=" + bodyOid + "]."); + continue; + } + + // we subsume nearby pirates by queueing up their addition + // to our cluster to be processed after we finish adding + // ourselves to this cluster + final ClusterRecord fclrec = clrec; + StageServer.omgr.postRunnable(new Runnable() { + public void run () { + try { + Log.info("Subsuming " + bobj.who() + + " into " + fclrec.getCluster() + "."); + fclrec.addBody(bobj); + + } catch (InvocationException ie) { + Log.info("Unable to subsume neighbor " + + "[cluster=" + fclrec.getCluster() + + ", neighbor=" + bobj.who() + + ", cause=" + ie.getMessage() + "]."); + } + } + }); + } + } + +// Log.info("Expanding cluster [cluster=" + cl + +// ", rect=" + rect + "]."); + cl.setBounds(rect); + + return true; + } + + /** Helper function for {@link #canAddBody}. */ + protected boolean checkIntersects (Iterator iter, Rectangle rect, + Rectangle ignore) + { + while (iter.hasNext()) { + Rectangle trect = (Rectangle)iter.next(); + if (ignore != null && trect.equals(ignore)) { + continue; + } + if (trect.intersects(rect)) { + return true; + } + } + return false; + } + + /** Helper function for {@link #canAddBody}. */ + protected boolean checkPortals (Rectangle rect) + { + for (Iterator iter = _plocs.iterator(); iter.hasNext(); ) { + Point ppoint = (Point)iter.next(); + if (rect.contains(ppoint)) { + return true; + } + } + return false; + } + + /** Helper function for {@link #canAddBody}. */ + protected boolean checkViolatesPassability (Rectangle rect) + { + // we just check the whole damned thing, but it's not really that + // expensive, so we're not going to worry about it + // Check the bounds + 1 in each direction, so we can see if a fringer + // blocks us. + for (int xx = rect.x-1, ex = rect.x+rect.width+1; xx < ex; xx++) { + for (int yy = rect.y-1, ey = rect.y+rect.height+1; yy < ey; yy++) { + int btid = _mmodel.getBaseTileId(xx, yy); + if ((btid == 0) && + ((xx == rect.x-1) || (xx == rect.x + rect.width) || + (yy == rect.y-1) || (yy == rect.y + rect.height))) { + // it's ok to have an unspecified base tile in the outer + // border + continue; + + } else if (!StageSceneUtil.isPassable( + StageServer.tilemgr, btid)) { +// Log.info("Cluster impassable " + +// "[rect=" + StringUtil.toString(rect) + +// ", spot=" + StringUtil.coordsToString(xx, yy) + +// "]."); + return true; + } + } + } + return false; + } + + // documentation inherited + protected void bodyAdded (ClusterRecord clrec, BodyObject body) + { + super.bodyAdded(clrec, body); + +// Log.info(body.who() + " added to " + clrec + "."); + + // remove them from the loners map if they were in it + int bodyOid = body.getOid(); + _loners.remove(bodyOid); + + Cluster cl = clrec.getCluster(); + if (clrec.size() == 1) { + // if we're adding our first player, assign initial dimensions + // to the cluster + cl.width = 1; cl.height = 1; + Location loc = locationForBody(bodyOid); + if (loc == null) { + Log.warning("Foreign body added to cluster [clrec=" + clrec + + ", body=" + body.who() + "]."); + cl.x = 10; cl.y = 10; + } else { + cl.x = MisoUtil.fullToTile(loc.x); + cl.y = MisoUtil.fullToTile(loc.y); + } + // we'll do everything else when occupant two shows up + return; + } + + // generate a list of all valid locations for this cluster + ArrayList locs = StageSceneUtil.getClusterLocs(cl); + +// Log.info("Positioning " + clrec.size() + " bodies in " + +// StringUtil.toString(locs) + " for " + cl + "."); + + // make sure everyone is in their proper position + for (Iterator iter = clrec.keySet().iterator(); iter.hasNext(); ) { + int tbodyOid = ((Integer)iter.next()).intValue(); + // leave the newly added player to last + if (tbodyOid != bodyOid) { + positionBody(cl, tbodyOid, locs); + } + } + + // finally position the new guy + positionBody(cl, bodyOid, locs); + } + + /** Helper function for {@link #bodyAdded}. */ + protected void positionBody (Cluster cl, int bodyOid, ArrayList locs) + { + SceneLocation sloc = (SceneLocation) + _ssobj.occupantLocs.get(new Integer(bodyOid)); + if (sloc == null) { + BodyObject user = (BodyObject)StageServer.omgr.getObject(bodyOid); + String who = (user == null) ? ("" + bodyOid) : user.who(); + Log.warning("Can't position locationless user " + + "[where=" + where() + ", cluster=" + cl + + ", boid=" + who + "]."); + return; + } + + SceneLocation cloc = getClosestLoc(locs, sloc); +// Log.info("Maybe moving " + bodyOid + " from " + sloc + +// " to " + cloc + +// " (avail=" + StringUtil.toString(locs) + ")."); + if (cloc != null && !cloc.equivalent(sloc)) { + cloc.bodyOid = bodyOid; +// Log.info("Moving " + bodyOid + " to " + cloc + +// " for " + cl + "."); + _ssobj.updateOccupantLocs(cloc); + } + } + + // documentation inherited + protected void bodyRemoved (ClusterRecord clrec, BodyObject body) + { + super.bodyRemoved(clrec, body); + + // if we've been reduced to a zero person cluster, we can skip all + // this because the cluster will be destroyed when we return + if (clrec.size() < 1) { + return; + } + + // reduce the bounds of the cluster if necessary + int target = TARGET_SIZE[clrec.size()]; + Cluster cl = clrec.getCluster(); + // allow a cluster to remain one size larger than it needs to be + // as long as there's more than one person in it + if (cl.width <= (target + ((clrec.size() > 1) ? 1 : 0))) { + return; + } + + // otherwise shrink the cluster + cl.width = target; + cl.height = target; + + // generate a list of all valid locations for this cluster + ArrayList locs = StageSceneUtil.getClusterLocs(cl); + + // make sure everyone is in their proper position + for (Iterator iter = clrec.keySet().iterator(); iter.hasNext(); ) { + int bodyOid = ((Integer)iter.next()).intValue(); + // leave the newly added player to last + if (bodyOid != body.getOid()) { + positionBody(cl, bodyOid, locs); + } + } + } + + // documentation inherited + protected void checkCanCluster (BodyObject initiator, BodyObject target) + throws InvocationException + { + StageOccupantInfo info = (StageOccupantInfo)_ssobj.occupantInfo.get( + new Integer(target.getOid())); + if (info == null) { + Log.warning("Have no occinfo for cluster target " + + "[where=" + where() + ", init=" + initiator.who() + + ", target=" + target.who() + "]."); + throw new InvocationException(INTERNAL_ERROR); + } + + if (!info.isClusterable()) { + throw new InvocationException(StageCodes.ERR_CANNOT_CLUSTER); + } + } + + /** + * Locates and removes the location in the list closest to the + * supplied location. + */ + protected static SceneLocation getClosestLoc ( + ArrayList locs, SceneLocation loc) + { + SceneLocation cloc = null; + float cdist = Integer.MAX_VALUE; + int cidx = -1; + for (int ii = 0, ll = locs.size(); ii < ll; ii++) { + SceneLocation tloc = (SceneLocation)locs.get(ii); + float tdist = MathUtil.distance(loc.x, loc.y, tloc.x, tloc.y); + if (tdist < cdist) { + cloc = tloc; + cdist = tdist; + cidx = ii; + } + } + if (cidx != -1) { + locs.remove(cidx); + } + return cloc; + } + + /** A casted reference to our scene object. */ + protected StageSceneObject _ssobj; + + /** A casted reference to our scene data. */ + protected StageScene _sscene; + + /** Our miso scene data extracted for convenience and efficiency. */ + protected StageMisoSceneModel _mmodel; + + /** Rectangles describing the footprints (in tile coordinates) of all + * of our scene objects. */ + protected ArrayList _footprints = new ArrayList(); + + /** Rectangles containing a "footprint" for the users that aren't in + * any clusters. */ + protected HashIntMap _loners = new HashIntMap(); + + /** Contains the (tile) coordinates of all of our portals. */ + protected HashSet _plocs = new HashSet(); + + /** The dimensions of a cluster with the specified number of + * occupants. */ + protected static final int[] TARGET_SIZE = { + 1, 2, // one person cluster + 3, 3, 3, // two to four person cluster + 4, 4, 4, 4, // five to eight person cluster + 5, 5, 5, 5, // nine to twelve person cluster + 6, 6, 6, 6, // thirteen to sixteen person cluster + 7, 7, 7, 7, 7, 7, 7, 7, // seventeen to twenty four person cluster + 8 // needed though we'll never expand to this size + }; + + /** Used by {@link #canAddBody}. */ + protected static final int[] X_OFF = { 0, -1, 0, -1 }; + + /** Used by {@link #canAddBody}. */ + protected static final int[] Y_OFF = { 0, 0, -1, -1 }; + + /** Used by {@link #computeEnteringLocation}. */ + protected static final int[] PORTAL_DX = { 0, -1, 0, 1 }; // W N E S + + /** Used by {@link #computeEnteringLocation}. */ + protected static final int[] PORTAL_DY = { 1, 0, -1, 0 }; // W N E S +} diff --git a/src/java/com/threerings/stage/server/StageSceneProvider.java b/src/java/com/threerings/stage/server/StageSceneProvider.java new file mode 100644 index 000000000..962735fc8 --- /dev/null +++ b/src/java/com/threerings/stage/server/StageSceneProvider.java @@ -0,0 +1,25 @@ +// +// $Id: StageSceneProvider.java 14426 2004-03-12 12:12:32Z mdb $ + +package com.threerings.stage.server; + +import com.threerings.presents.data.ClientObject; +import com.threerings.presents.server.InvocationException; +import com.threerings.presents.server.InvocationProvider; + +import com.threerings.miso.data.ObjectInfo; + +import com.threerings.stage.client.StageSceneService; + +/** + * Defines the server side of the {@link StageSceneService}. + */ +public interface StageSceneProvider extends InvocationProvider +{ + /** + * Handles a {@link StageSceneService#addObject} request. + */ + public void addObject (ClientObject caller, ObjectInfo info, + StageSceneService.ConfirmListener listener) + throws InvocationException; +} diff --git a/src/java/com/threerings/stage/server/StageServer.java b/src/java/com/threerings/stage/server/StageServer.java new file mode 100644 index 000000000..f7596d0b4 --- /dev/null +++ b/src/java/com/threerings/stage/server/StageServer.java @@ -0,0 +1,67 @@ +// +// $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.stage.server; + +import com.threerings.resource.ResourceManager; + +import com.threerings.media.tile.TileManager; +import com.threerings.media.tile.bundle.BundledTileSetRepository; + +import com.threerings.whirled.server.WhirledServer; + +import com.threerings.stage.Log; +import com.threerings.stage.data.StageCodes; + +/** + * Extends the Whirled server to provide services needed by the Stage + * system. + */ +public abstract class StageServer extends WhirledServer +{ + /** A resource manager with which we can load resources in the same + * manner that the client does (for resources that are used on both + * the server and client). */ + public ResourceManager rsrcmgr; + + /** Provides access to our tile repository. */ + public static TileManager tilemgr; + + // documentation inherited + public void init () + throws Exception + { + // do the base server initialization + super.init(); + + // create the resource manager + rsrcmgr = new ResourceManager("rsrc"); + rsrcmgr.initBundles(null, "config/resource/manager.properties", null); + + // create our tile manager and repository + tilemgr = new TileManager(null); + tilemgr.setTileSetRepository( + new BundledTileSetRepository(rsrcmgr, null, + StageCodes.TILESET_RSRC_SET)); + + Log.info("Stage server initialized."); + } +} diff --git a/src/java/com/threerings/whirled/server/SceneManager.java b/src/java/com/threerings/whirled/server/SceneManager.java index 080bd4cd7..ed4b40087 100644 --- a/src/java/com/threerings/whirled/server/SceneManager.java +++ b/src/java/com/threerings/whirled/server/SceneManager.java @@ -127,32 +127,35 @@ public class SceneManager extends PlaceManager /** * When a modification is made to a scene, the scene manager should - * update its internal structures, update the {@link Scene} object, - * update the repository (either by storing the updated scene - * wholesale or more efficiently updating only what has changed), and - * then it should create a {@link SceneUpdate} record that can be - * delivered to clients to effect the update to the clients's cached - * copy of the scene model. This update will be stored persistently - * and provided (along with any other accumulated updates) to clients - * that later request to enter the scene with an old version of the - * scene data. Updates are not stored forever, but a sizable number of - * recent updates are stored so that moderately current clients can - * apply incremental patches to their scenes rather than redownloading - * the entire scenes when they change. + * create a SceneUpdate instance and pass it to this method which will + * update the in-memory scene, and apply and record the update in the + * scene repository. + * + *

This update will be stored persistently and provided (along + * with any other accumulated updates) to clients that later request + * to enter the scene with an old version of the scene data. Updates + * are not stored forever, but a sizable number of recent updates are + * stored so that moderately current clients can apply incremental + * patches to their scenes rather than redownloading entire scenes + * when they change. */ protected void recordUpdate (final SceneUpdate update) { + // instruct our in-memory copy of the scene to apply the update + _scene.updateReceived(update); + // add it to our in memory update list _updates.addUpdate(update); - // and store it in the repository + // and apply and store it in the repository WhirledServer.invoker.postUnit(new Invoker.Unit() { public boolean invoke () { try { - _screg.getSceneRepository().addUpdate(update); + _screg.getSceneRepository().applyAndRecordUpdate( + _scene.getSceneModel(), update); } catch (PersistenceException pe) { - Log.warning("Failed to store scene update " + - "[update=" + update + ", error=" + pe + "]."); + Log.warning("Failed to apply scene update " + update + "."); + Log.logStackTrace(pe); } return false; } diff --git a/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java b/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java index e30a68c21..a0142d0fb 100644 --- a/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java +++ b/src/java/com/threerings/whirled/server/persist/DummySceneRepository.java @@ -1,5 +1,5 @@ // -// $Id: DummySceneRepository.java,v 1.9 2004/08/27 02:20:43 mdb Exp $ +// $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved @@ -52,7 +52,7 @@ public class DummySceneRepository implements SceneRepository } // documentation inherited from interface - public void addUpdate (SceneUpdate update) + public void applyAndRecordUpdate (SceneModel model, SceneUpdate update) throws PersistenceException { // nothing doing diff --git a/src/java/com/threerings/whirled/server/persist/SceneRepository.java b/src/java/com/threerings/whirled/server/persist/SceneRepository.java index 13368fbd1..0695608fd 100644 --- a/src/java/com/threerings/whirled/server/persist/SceneRepository.java +++ b/src/java/com/threerings/whirled/server/persist/SceneRepository.java @@ -1,5 +1,5 @@ // -// $Id: SceneRepository.java,v 1.7 2004/08/27 02:20:43 mdb Exp $ +// $Id$ // // Narya library - tools for developing networked games // Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved @@ -60,12 +60,14 @@ public interface SceneRepository throws PersistenceException, NoSuchSceneException; /** - * Adds the supplied scene update to the list of updates for its - * associated scene. + * Applise the supplied scene update to persistent representation of + * its associated scene, then stores the update persistently for + * future invocations of the server to load. Note: the scene + * update will have already been applied to the supplied scene model. * * @exception PersistenceException thrown if an error occurs - * attempting to store the scene update. + * attempting to apply the scene update. */ - public void addUpdate (SceneUpdate update) + public void applyAndRecordUpdate (SceneModel model, SceneUpdate update) throws PersistenceException; }