Fix a bug where the client & server happen to attempt to change location nearly simultaneously.

Rather than ignoring all forced moves from the server, let's instead ignore those which are truly duplicates of our pending move, but merely postpone those which are a separate move until our pending move is complete.

(This works very similarly to the change to LocationDirector in narya)

Relatedly, if a portal traversal is ignored due to staleness (again, due to near-simultaneous client & server moves), tell the client about it so that it doesn't wait around forever thinking it's got a pending move in progress.



git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@970 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
Mike Thomas
2010-10-05 20:56:24 +00:00
parent 86a348222f
commit 9fe0c6f082
13 changed files with 309 additions and 29 deletions
@@ -265,6 +265,8 @@ public class SceneDirector extends BasicDirector
// and finally create a display scene instance with the model and the place config
_scene = _fact.createScene(_model, config);
handlePendingForcedMove();
}
// from interface SceneService_SceneMoveListener
@@ -361,6 +363,8 @@ public class SceneDirector extends BasicDirector
// let our observers know that something has gone horribly awry
_locdir.failedToMoveTo(sceneId, reason);
handlePendingForcedMove();
}
// from interface LocationObserver
@@ -421,8 +425,16 @@ public class SceneDirector extends BasicDirector
// just finish up what we're doing and assume that the repeated move request was the
// spurious one as it would be in the case of lag causing rapid-fire repeat requests
if (movePending()) {
if (_pendingData.sceneId == sceneId) {
log.info("Dropping forced move because we have a move pending",
"pendId", _pendingData.sceneId, "reqId", sceneId);
} else {
log.info("Delaying forced move because we have a move pending",
"pendId", _pendingData.sceneId, "reqId", sceneId);
addPendingForcedMove(new function() :void {
forcedMove(sceneId);
});
}
return;
}
@@ -488,6 +500,12 @@ public class SceneDirector extends BasicDirector
_sservice = null;
}
public function cancelMoveRequest () :void
{
_pendingData = null;
handlePendingForcedMove();
}
/**
* Issues the scene move request using information from the supplied pending data.
*/
@@ -530,6 +548,18 @@ public class SceneDirector extends BasicDirector
_scene = null;
}
public function addPendingForcedMove (move :Function) :void
{
_pendingForcedMoves.push(move);
}
protected function handlePendingForcedMove () :void
{
if (!_pendingForcedMoves.length == 0) {
_ctx.getClient().callLater(_pendingForcedMoves.pop());
}
}
/**
* Loads a scene from the repository. If the scene is cached, it will be returned from the
* cache instead.
@@ -619,5 +649,8 @@ public class SceneDirector extends BasicDirector
/** Reference to our move handler. */
protected var _moveHandler :SceneDirector_MoveHandler = null;
/** Forced move actions we should take once we complete the move we're in the middle of. */
protected var _pendingForcedMoves :Array = [];
}
}
@@ -159,7 +159,7 @@ public class SpotSceneDirector extends BasicDirector
// issue a traversePortal request
log.info("Issuing traversePortal(" + sceneId + ", " + dest + ", " + sceneVer + ").");
_sservice.traversePortal(sceneId, portalId, sceneVer, _scdir);
_sservice.traversePortal(sceneId, portalId, sceneVer, new SceneDirectorWrapper(_scdir));
return true;
}
@@ -461,3 +461,44 @@ public class SpotSceneDirector extends BasicDirector
protected var _clobj :DObject;
}
}
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.whirled.client.SceneDirector;
import com.threerings.whirled.data.SceneModel;
import com.threerings.whirled.spot.client.SpotService_SpotSceneMoveListener;
class SceneDirectorWrapper
implements SpotService_SpotSceneMoveListener
{
public function SceneDirectorWrapper (scdir :SceneDirector) {
_scdir = scdir;
}
public function requestFailed (cause :String) :void {
_scdir.requestFailed(cause);
}
public function moveSucceeded (placeId :int, config :PlaceConfig) :void {
_scdir.moveSucceeded(placeId, config);
}
public function moveSucceededWithUpdates (
placeId :int, config :PlaceConfig, updates :TypedArray) :void{
_scdir.moveSucceededWithUpdates(placeId, config, updates);
}
public function moveSucceededWithScene (placeId :int, config :PlaceConfig,
model :SceneModel) :void {
_scdir.moveSucceededWithScene(placeId, config, model);
}
public function moveRequiresServerSwitch (hostname :String, ports :TypedArray) :void {
_scdir.moveRequiresServerSwitch(hostname, ports);
}
public function requestCancelled () :void {
_scdir.cancelMoveRequest();
}
protected var _scdir :SceneDirector;
}
@@ -24,7 +24,6 @@ package com.threerings.whirled.spot.client {
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.presents.client.InvocationService_ConfirmListener;
import com.threerings.whirled.client.SceneService_SceneMoveListener;
import com.threerings.whirled.spot.data.Location;
/**
@@ -42,6 +41,6 @@ public interface SpotService extends InvocationService
function joinCluster (arg1 :int, arg2 :InvocationService_ConfirmListener) :void;
// from Java interface SpotService
function traversePortal (arg1 :int, arg2 :int, arg3 :int, arg4 :SceneService_SceneMoveListener) :void;
function traversePortal (arg1 :int, arg2 :int, arg3 :int, arg4 :SpotService_SpotSceneMoveListener) :void;
}
}
@@ -27,9 +27,8 @@ import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.presents.data.InvocationMarshaller_ConfirmMarshaller;
import com.threerings.util.Byte;
import com.threerings.util.Integer;
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.client.SpotService_SpotSceneMoveListener;
/**
* Provides the implementation of the <code>SpotService</code> interface
@@ -82,9 +81,9 @@ public class SpotMarshaller extends InvocationMarshaller
public static const TRAVERSE_PORTAL :int = 4;
// from interface SpotService
public function traversePortal (arg1 :int, arg2 :int, arg3 :int, arg4 :SceneService_SceneMoveListener) :void
public function traversePortal (arg1 :int, arg2 :int, arg3 :int, arg4 :SpotService_SpotSceneMoveListener) :void
{
var listener4 :SceneMarshaller_SceneMoveMarshaller = new SceneMarshaller_SceneMoveMarshaller();
var listener4 :SpotMarshaller_SpotSceneMoveMarshaller = new SpotMarshaller_SpotSceneMoveMarshaller();
listener4.listener = arg4;
sendRequest(TRAVERSE_PORTAL, [
Integer.valueOf(arg1), Integer.valueOf(arg2), Integer.valueOf(arg3), listener4
@@ -232,8 +232,16 @@ public class ZoneDirector extends BasicDirector
// just finish up what we're doing and assume that the repeated move request was the
// spurious one as it would be in the case of lag causing rapid-fire repeat requests
if (_scdir.movePending()) {
if (_scdir.getPendingSceneId() == sceneId) {
log.info("Dropping forced move because we have a move pending",
"pend", _scdir.getPendingModel(), "rzId", zoneId, "rsId", sceneId);
} else {
log.info("Delaying forced move because we have a move pending",
"pend", _scdir.getPendingModel(), "rzId", zoneId, "rsId", sceneId);
_scdir.addPendingForcedMove(new function() :void {
forcedMove(zoneId, sceneId);
});
}
return;
}
@@ -21,10 +21,13 @@
package com.threerings.whirled.client;
import java.util.ArrayList;
import java.util.Map;
import java.io.IOException;
import com.google.common.collect.Lists;
import com.samskivert.util.LRUHashMap;
import com.samskivert.util.ResultListener;
import com.threerings.presents.client.BasicDirector;
@@ -248,6 +251,8 @@ public class SceneDirector extends BasicDirector
// and finally create a display scene instance with the model and the place config
_scene = _fact.createScene(_model, config);
handlePendingForcedMove();
}
// from interface SceneService.SceneMoveListener
@@ -377,14 +382,24 @@ public class SceneDirector extends BasicDirector
}
// from interface SceneReceiver
public void forcedMove (int sceneId)
public void forcedMove (final int sceneId)
{
// if we're in the middle of a move, we can't abort it or we will screw everything up, so
// just finish up what we're doing and assume that the repeated move request was the
// spurious one as it would be in the case of lag causing rapid-fire repeat requests
if (movePending()) {
if (_pendingSceneId == sceneId) {
log.info("Dropping forced move because we have a move pending",
"pendId", _pendingSceneId, "reqId", sceneId);
} else {
log.info("Delaying forced move because we have a move pending",
"pendId", _pendingSceneId, "reqId", sceneId);
addPendingForcedMove(new Runnable() {
public void run () {
forcedMove(sceneId);
}
});
}
return;
}
@@ -420,6 +435,8 @@ public class SceneDirector extends BasicDirector
moveTo(_previousSceneId);
}
}
handlePendingForcedMove();
}
protected void sendMoveRequest ()
@@ -502,10 +519,18 @@ public class SceneDirector extends BasicDirector
_scache.clear();
_pendingSceneId = -1;
_pendingModel = null;
_pendingForcedMoves.clear();
_previousSceneId = -1;
_sservice = null;
}
public void cancelMoveRequest ()
{
_pendingSceneId = -1;
_pendingModel = null;
handlePendingForcedMove();
}
@Override
protected void registerServices (Client client)
{
@@ -519,6 +544,18 @@ public class SceneDirector extends BasicDirector
_sservice = client.requireService(SceneService.class);
}
public void addPendingForcedMove (Runnable move)
{
_pendingForcedMoves.add(move);
}
protected void handlePendingForcedMove ()
{
if (!_pendingForcedMoves.isEmpty()) {
_ctx.getClient().getRunQueue().postRunnable(_pendingForcedMoves.remove(0));
}
}
/** Access to general client services. */
protected WhirledContext _ctx;
@@ -558,4 +595,8 @@ public class SceneDirector extends BasicDirector
/** Reference to our move handler. */
protected MoveHandler _moveHandler = null;
/** Forced move actions we should take once we complete the move we're in the middle of. */
protected ArrayList<Runnable> _pendingForcedMoves = Lists.newArrayList();
}
@@ -43,6 +43,7 @@ import com.threerings.crowd.data.PlaceObject;
import com.threerings.whirled.client.SceneDirector;
import com.threerings.whirled.data.SceneModel;
import com.threerings.whirled.data.ScenePlace;
import com.threerings.whirled.data.SceneUpdate;
import com.threerings.whirled.spot.data.ClusteredBodyObject;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.Portal;
@@ -161,7 +162,34 @@ public class SpotSceneDirector extends BasicDirector
// issue a traversePortal request
log.info("Issuing traversePortal(" + sceneId + ", " + dest + ", " + sceneVer + ").");
_sservice.traversePortal(_ctx.getClient(), sceneId, portalId, sceneVer, _scdir);
_sservice.traversePortal(_ctx.getClient(), sceneId, portalId, sceneVer,
new SpotService.SpotSceneMoveListener() {
public void requestFailed (String cause) {
_scdir.requestFailed(cause);
}
public void moveSucceeded (int placeId, PlaceConfig config) {
_scdir.moveSucceeded(placeId, config);
}
public void moveSucceededWithUpdates (
int placeId, PlaceConfig config, SceneUpdate[] updates) {
_scdir.moveSucceededWithUpdates(placeId, config, updates);
}
public void moveSucceededWithScene (int placeId, PlaceConfig config,
SceneModel model) {
_scdir.moveSucceededWithScene(placeId, config, model);
}
public void moveRequiresServerSwitch (String hostname, int[] ports) {
_scdir.moveRequiresServerSwitch(hostname, ports);
}
public void requestCancelled () {
_scdir.cancelMoveRequest();
}
});
return true;
}
@@ -35,6 +35,17 @@ import com.threerings.whirled.spot.data.Location;
*/
public interface SpotService extends InvocationService
{
/**
* Used to communicate the response to a {@link SceneService#moveTo} request.
*/
public static interface SpotSceneMoveListener extends SceneMoveListener
{
/**
* The request is not going through because it was cancelled due to some other action.
*/
public void requestCancelled ();
}
/**
* Requests to traverse the specified portal.
*
@@ -46,7 +57,7 @@ public interface SpotService extends InvocationService
*/
public void traversePortal (
Client client, int sceneId, int portalId, int destSceneVer,
SceneMoveListener listener);
SpotSceneMoveListener listener);
/**
* Requests that this client's body be made to move to the specified
@@ -23,11 +23,13 @@ package com.threerings.whirled.spot.data;
import javax.annotation.Generated;
import com.threerings.crowd.data.PlaceConfig;
import com.threerings.presents.client.Client;
import com.threerings.presents.client.InvocationService;
import com.threerings.presents.data.InvocationMarshaller;
import com.threerings.whirled.client.SceneService;
import com.threerings.whirled.data.SceneMarshaller;
import com.threerings.presents.dobj.InvocationResponseEvent;
import com.threerings.whirled.data.SceneModel;
import com.threerings.whirled.data.SceneUpdate;
import com.threerings.whirled.spot.client.SpotService;
/**
@@ -42,6 +44,113 @@ import com.threerings.whirled.spot.client.SpotService;
public class SpotMarshaller extends InvocationMarshaller
implements SpotService
{
/**
* Marshalls results to implementations of {@link SpotService.SpotSceneMoveListener}.
*/
public static class SpotSceneMoveMarshaller extends ListenerMarshaller
implements SpotSceneMoveListener
{
/** The method id used to dispatch {@link #moveRequiresServerSwitch}
* responses. */
public static final int MOVE_REQUIRES_SERVER_SWITCH = 1;
// from interface SpotSceneMoveMarshaller
public void moveRequiresServerSwitch (String arg1, int[] arg2)
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, MOVE_REQUIRES_SERVER_SWITCH,
new Object[] { arg1, arg2 }, transport));
}
/** The method id used to dispatch {@link #moveSucceeded}
* responses. */
public static final int MOVE_SUCCEEDED = 2;
// from interface SpotSceneMoveMarshaller
public void moveSucceeded (int arg1, PlaceConfig arg2)
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, MOVE_SUCCEEDED,
new Object[] { Integer.valueOf(arg1), arg2 }, transport));
}
/** The method id used to dispatch {@link #moveSucceededWithScene}
* responses. */
public static final int MOVE_SUCCEEDED_WITH_SCENE = 3;
// from interface SpotSceneMoveMarshaller
public void moveSucceededWithScene (int arg1, PlaceConfig arg2, SceneModel arg3)
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, MOVE_SUCCEEDED_WITH_SCENE,
new Object[] { Integer.valueOf(arg1), arg2, arg3 }, transport));
}
/** The method id used to dispatch {@link #moveSucceededWithUpdates}
* responses. */
public static final int MOVE_SUCCEEDED_WITH_UPDATES = 4;
// from interface SpotSceneMoveMarshaller
public void moveSucceededWithUpdates (int arg1, PlaceConfig arg2, SceneUpdate[] arg3)
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, MOVE_SUCCEEDED_WITH_UPDATES,
new Object[] { Integer.valueOf(arg1), arg2, arg3 }, transport));
}
/** The method id used to dispatch {@link #requestCancelled}
* responses. */
public static final int REQUEST_CANCELLED = 5;
// from interface SpotSceneMoveMarshaller
public void requestCancelled ()
{
_invId = null;
omgr.postEvent(new InvocationResponseEvent(
callerOid, requestId, REQUEST_CANCELLED,
new Object[] { }, transport));
}
@Override // from InvocationMarshaller
public void dispatchResponse (int methodId, Object[] args)
{
switch (methodId) {
case MOVE_REQUIRES_SERVER_SWITCH:
((SpotSceneMoveListener)listener).moveRequiresServerSwitch(
(String)args[0], (int[])args[1]);
return;
case MOVE_SUCCEEDED:
((SpotSceneMoveListener)listener).moveSucceeded(
((Integer)args[0]).intValue(), (PlaceConfig)args[1]);
return;
case MOVE_SUCCEEDED_WITH_SCENE:
((SpotSceneMoveListener)listener).moveSucceededWithScene(
((Integer)args[0]).intValue(), (PlaceConfig)args[1], (SceneModel)args[2]);
return;
case MOVE_SUCCEEDED_WITH_UPDATES:
((SpotSceneMoveListener)listener).moveSucceededWithUpdates(
((Integer)args[0]).intValue(), (PlaceConfig)args[1], (SceneUpdate[])args[2]);
return;
case REQUEST_CANCELLED:
((SpotSceneMoveListener)listener).requestCancelled(
);
return;
default:
super.dispatchResponse(methodId, args);
return;
}
}
}
/** The method id used to dispatch {@link #changeLocation} requests. */
public static final int CHANGE_LOCATION = 1;
@@ -83,9 +192,9 @@ public class SpotMarshaller extends InvocationMarshaller
public static final int TRAVERSE_PORTAL = 4;
// from interface SpotService
public void traversePortal (Client arg1, int arg2, int arg3, int arg4, SceneService.SceneMoveListener arg5)
public void traversePortal (Client arg1, int arg2, int arg3, int arg4, SpotService.SpotSceneMoveListener arg5)
{
SceneMarshaller.SceneMoveMarshaller listener5 = new SceneMarshaller.SceneMoveMarshaller();
SpotMarshaller.SpotSceneMoveMarshaller listener5 = new SpotMarshaller.SpotSceneMoveMarshaller();
listener5.listener = arg5;
sendRequest(arg1, TRAVERSE_PORTAL, new Object[] {
Integer.valueOf(arg2), Integer.valueOf(arg3), Integer.valueOf(arg4), listener5
@@ -27,7 +27,7 @@ import com.threerings.presents.client.InvocationService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationDispatcher;
import com.threerings.presents.server.InvocationException;
import com.threerings.whirled.client.SceneService;
import com.threerings.whirled.spot.client.SpotService;
import com.threerings.whirled.spot.data.Location;
import com.threerings.whirled.spot.data.SpotMarshaller;
@@ -79,7 +79,7 @@ public class SpotDispatcher extends InvocationDispatcher<SpotMarshaller>
case SpotMarshaller.TRAVERSE_PORTAL:
((SpotProvider)provider).traversePortal(
source, ((Integer)args[0]).intValue(), ((Integer)args[1]).intValue(), ((Integer)args[2]).intValue(), (SceneService.SceneMoveListener)args[3]
source, ((Integer)args[0]).intValue(), ((Integer)args[1]).intValue(), ((Integer)args[2]).intValue(), (SpotService.SpotSceneMoveListener)args[3]
);
return;
@@ -27,7 +27,6 @@ import com.threerings.presents.client.InvocationService;
import com.threerings.presents.data.ClientObject;
import com.threerings.presents.server.InvocationException;
import com.threerings.presents.server.InvocationProvider;
import com.threerings.whirled.client.SceneService;
import com.threerings.whirled.spot.client.SpotService;
import com.threerings.whirled.spot.data.Location;
@@ -58,6 +57,6 @@ public interface SpotProvider extends InvocationProvider
/**
* Handles a {@link SpotService#traversePortal} request.
*/
void traversePortal (ClientObject caller, int arg1, int arg2, int arg3, SceneService.SceneMoveListener arg4)
void traversePortal (ClientObject caller, int arg1, int arg2, int arg3, SpotService.SpotSceneMoveListener arg4)
throws InvocationException;
}
@@ -115,7 +115,7 @@ public class SpotSceneRegistry extends SceneRegistry
// from interface SpotProvider
public void traversePortal (ClientObject caller, int sceneId, int portalId,
int destSceneVer, SceneService.SceneMoveListener listener)
int destSceneVer, SpotService.SpotSceneMoveListener listener)
throws InvocationException
{
// le sanity check
@@ -125,7 +125,7 @@ public class SpotSceneRegistry extends SceneRegistry
log.info("Ignoring stale traverse portal request [caller=" + caller.who() +
", oSceneId=" + sceneId + ", portalId=" + portalId +
", cSceneId=" + cSceneId + "].");
InvocationMarshaller.setNoResponse(listener);
listener.requestCancelled();
return;
}
@@ -253,14 +253,26 @@ public class ZoneDirector extends BasicDirector
}
// documentation inherited from interface
public void forcedMove (int zoneId, int sceneId)
public void forcedMove (final int zoneId, final int sceneId)
{
// if we're in the middle of a move, we can't abort it or we will screw everything up, so
// just finish up what we're doing and assume that the repeated move request was the
// spurious one as it would be in the case of lag causing rapid-fire repeat requests
if (_scdir.movePending()) {
if (_scdir.getPendingSceneId() == sceneId) {
log.info("Dropping forced move because we have a move pending",
"pend", _scdir.getPendingModel(), "rzId", zoneId, "rsId", sceneId);
} else {
log.info("Delaying forced move because we have a move pending",
"pend", _scdir.getPendingModel(), "rzId", zoneId, "rsId", sceneId);
// We have a move pending to a DIFFERENT scene - we'll do this as soon as that's
// done.
_scdir.addPendingForcedMove(new Runnable() {
public void run () {
forcedMove(zoneId, sceneId);
}
});
}
return;
}