Fixes for property setting via atomic test-and-set:
- Moved property testing, so that it happens before any propertySet events are dispatched. In the previous version, testing happened while processing the set event on the server - but since by that time client events have already been sent, it introduced the possibility of short-lived inconsistencies between client and server data models. - Introduced a separate EZ API call for test and set - not only does it perform the test, but unlike regular set, it does not cache the new value ahead of time. Instead the new value will have to arrive from the server, at some future point. - Trimmed PropertySetEvent and other handlers back down - they don't need to carry any of the test info around, after it's already been performed. Also cut redundant testing on the clients. git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@195 c613c5cb-e716-0410-b11b-feb51c14d237
This commit is contained in:
@@ -153,24 +153,31 @@ public class EZGameControl extends EventDispatcher
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a property that will be distributed.
|
||||
* Set a property that will be distributed.
|
||||
*/
|
||||
public function set (propName :String, value :Object, index :int = -1) :void
|
||||
{
|
||||
callEZCode("setProperty_v2", propName, value, index, false);
|
||||
callEZCode("setProperty_v1", propName, value, index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a property that will be distributed, but only if it was null before.
|
||||
* Set a property that will be distributed, but only if it's equal
|
||||
* to the specified test value.
|
||||
*
|
||||
* Please note that, unlike in the standard set() function, the property
|
||||
* will not be updated right away, but will require a request to the server
|
||||
* and a response back. For this reason, there may be a considerable delay
|
||||
* between calling testAndSet, and seeing the result of the update.
|
||||
*
|
||||
* The operation is 'atomic', in the sense that testing and setting take place
|
||||
* during the same server event. In comparison, a separate 'get' followed by
|
||||
* a 'set' operation would involve two events with two network round-trips,
|
||||
* and no guarantee that the value won't change between the events.
|
||||
*/
|
||||
public function testAndSet (propName :String, value :Object, index :int = -1) :void
|
||||
public function testAndSet (
|
||||
propName :String, newValue :Object, testValue :Object, index :int = -1) :void
|
||||
{
|
||||
callEZCode("setProperty_v2", propName, value, index, true);
|
||||
callEZCode("testAndSetProperty_v1", propName, newValue, testValue, index);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -67,7 +67,7 @@ public interface EZGameService extends InvocationService
|
||||
function setCookie (arg1 :Client, arg2 :ByteArray, arg3 :InvocationService_InvocationListener) :void;
|
||||
|
||||
// from Java interface EZGameService
|
||||
function setProperty (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :Boolean, arg6 :InvocationService_InvocationListener) :void;
|
||||
function setProperty (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :Boolean, arg6 :Object, arg7 :InvocationService_InvocationListener) :void;
|
||||
|
||||
// from Java interface EZGameService
|
||||
function setTicker (arg1 :Client, arg2 :String, arg3 :int, arg4 :InvocationService_InvocationListener) :void;
|
||||
|
||||
@@ -72,7 +72,7 @@ public class GameControlBackend
|
||||
{
|
||||
_ctx = ctx;
|
||||
_ezObj = ezObj;
|
||||
_gameData = new GameData(setProperty_v2, _ezObj.getUserProps());
|
||||
_gameData = new GameData(setProperty_v1, _ezObj.getUserProps());
|
||||
|
||||
_ezObj.addListener(this);
|
||||
_ctx.getClient().getClientObject().addListener(_userListener);
|
||||
@@ -136,7 +136,8 @@ public class GameControlBackend
|
||||
o["gameData"] = _gameData;
|
||||
|
||||
// functions
|
||||
o["setProperty_v2"] = setProperty_v2;
|
||||
o["setProperty_v1"] = setProperty_v1;
|
||||
o["testAndSetProperty_v1"] = testAndSetProperty_v1;
|
||||
o["mergeCollection_v1"] = mergeCollection_v1;
|
||||
o["setTicker_v1"] = setTicker_v1;
|
||||
o["sendChat_v1"] = sendChat_v1;
|
||||
@@ -163,20 +164,33 @@ public class GameControlBackend
|
||||
o["getPlayers_v1"] = getPlayers_v1;
|
||||
}
|
||||
|
||||
public function setProperty_v2 (
|
||||
propName :String, value :Object, index :int, testAndSet :Boolean) :void
|
||||
public function setProperty_v1 (
|
||||
propName :String, value :Object, index :int) :void
|
||||
{
|
||||
validatePropertyChange(propName, value, index);
|
||||
|
||||
var encoded :Object = EZObjectMarshaller.encode(value, (index == -1));
|
||||
_ezObj.ezGameService.setProperty(
|
||||
_ctx.getClient(), propName, encoded, index, testAndSet,
|
||||
createLoggingConfirmListener("setProperty"));
|
||||
_ctx.getClient(), propName, encoded, index,
|
||||
false, null, createLoggingConfirmListener("setProperty"));
|
||||
|
||||
// set it immediately in the game object
|
||||
_ezObj.applyPropertySet(propName, value, index, testAndSet);
|
||||
_ezObj.applyPropertySet(propName, value, index);
|
||||
}
|
||||
|
||||
public function testAndSetProperty_v1 (
|
||||
propName :String, value :Object, testValue :Object, index :int) :void
|
||||
{
|
||||
validatePropertyChange(propName, value, index);
|
||||
|
||||
var encodedValue :Object = EZObjectMarshaller.encode(value, (index == -1));
|
||||
var encodedTestValue :Object = EZObjectMarshaller.encode(testValue, (index == -1));
|
||||
_ezObj.ezGameService.setProperty(
|
||||
_ctx.getClient(), propName, encodedValue, index,
|
||||
true, encodedTestValue, createLoggingConfirmListener("setProperty"));
|
||||
}
|
||||
|
||||
|
||||
public function mergeCollection_v1 (
|
||||
srcColl :String, intoColl :String) :void
|
||||
{
|
||||
|
||||
@@ -67,7 +67,7 @@ public class GameData extends Proxy
|
||||
|
||||
override flash_proxy function setProperty (propName :*, value :*) :void
|
||||
{
|
||||
_propSetFn(String(propName), value, -1, false);
|
||||
_propSetFn(String(propName), value, -1);
|
||||
}
|
||||
|
||||
override flash_proxy function deleteProperty (propName :*) :Boolean
|
||||
|
||||
@@ -178,12 +178,12 @@ public class EZGameMarshaller extends InvocationMarshaller
|
||||
public static const SET_PROPERTY :int = 11;
|
||||
|
||||
// from interface EZGameService
|
||||
public function setProperty (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :Boolean, arg6 :InvocationService_InvocationListener) :void
|
||||
public function setProperty (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :Boolean, arg6 :Object, arg7 :InvocationService_InvocationListener) :void
|
||||
{
|
||||
var listener6 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller();
|
||||
listener6.listener = arg6;
|
||||
var listener7 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller();
|
||||
listener7.listener = arg7;
|
||||
sendRequest(arg1, SET_PROPERTY, [
|
||||
arg2, arg3, Integer.valueOf(arg4), langBoolean.valueOf(arg5), listener6
|
||||
arg2, arg3, Integer.valueOf(arg4), langBoolean.valueOf(arg5), arg6, listener7
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -98,30 +98,26 @@ public class EZGameObject extends GameObject
|
||||
* @return the old value
|
||||
*/
|
||||
public function applyPropertySet (
|
||||
propName :String, value :Object, index :int, testAndSet :Boolean)
|
||||
:Object
|
||||
propName :String, value :Object, index :int) :Object
|
||||
{
|
||||
var oldValue :Object = _props[propName];
|
||||
if ((testAndSet && oldValue == null) || ! testAndSet)
|
||||
{
|
||||
if (index >= 0) {
|
||||
// set an array element
|
||||
var arr :Array = (oldValue as Array);
|
||||
if (arr == null) {
|
||||
arr = [];
|
||||
_props[propName] = arr;
|
||||
}
|
||||
oldValue = arr[index];
|
||||
arr[index] = value;
|
||||
|
||||
} else if (value != null) {
|
||||
// normal property set
|
||||
_props[propName] = value;
|
||||
|
||||
} else {
|
||||
// remove a property
|
||||
delete _props[propName];
|
||||
if (index >= 0) {
|
||||
// set an array element
|
||||
var arr :Array = (oldValue as Array);
|
||||
if (arr == null) {
|
||||
arr = [];
|
||||
_props[propName] = arr;
|
||||
}
|
||||
oldValue = arr[index];
|
||||
arr[index] = value;
|
||||
|
||||
} else if (value != null) {
|
||||
// normal property set
|
||||
_props[propName] = value;
|
||||
|
||||
} else {
|
||||
// remove a property
|
||||
delete _props[propName];
|
||||
}
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class PropertySetEvent extends NamedEvent
|
||||
override public function applyToObject (target :DObject) :Boolean
|
||||
{
|
||||
_oldValue =
|
||||
EZGameObject(target).applyPropertySet(_name, _data, _index, _testAndSet);
|
||||
EZGameObject(target).applyPropertySet(_name, _data, _index);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -69,7 +69,6 @@ public class PropertySetEvent extends NamedEvent
|
||||
super.readObject(ins);
|
||||
_index = ins.readInt();
|
||||
_data = EZObjectMarshaller.decode(ins.readObject());
|
||||
_testAndSet = ins.readBoolean();
|
||||
}
|
||||
|
||||
// from interface Streamable
|
||||
@@ -78,7 +77,6 @@ public class PropertySetEvent extends NamedEvent
|
||||
super.writeObject(out);
|
||||
out.writeInt(_index);
|
||||
out.writeObject(_data);
|
||||
out.writeBoolean(_testAndSet);
|
||||
}
|
||||
|
||||
override protected function notifyListener (listener :Object) :void
|
||||
@@ -101,9 +99,6 @@ public class PropertySetEvent extends NamedEvent
|
||||
/** The client-side data that is assigned to this property. */
|
||||
protected var _data :Object;
|
||||
|
||||
/** When true, the property will only be set if its previous value was null. */
|
||||
protected var _testAndSet :Boolean;
|
||||
|
||||
/** The old value. */
|
||||
protected var _oldValue :Object;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user