From bdfcd156ab4384f974e9637f7c6157325ecb13a7 Mon Sep 17 00:00:00 2001 From: Robert Zubeck Date: Thu, 15 Feb 2007 21:55:49 +0000 Subject: [PATCH] For EZ property sets, added a setter that performs an "atomic" test-and-set: it's atomic in the sense that a single server event will test the variable, and only set it if the previous value was null (i.e. didn't exist). This allows for a level of elementary synchronization between the clients. The new function on EZGameControl is: _gameCtrl.testAndSet (propertyName, newValue[, index]) I hoped to generalize this to test against arbitrary values, but that's significantly harder, since properties accept numerous types as values, and those can have different representations on the client and the server. So it remains a check against null until we need to generalize it. :) git-svn-id: svn+ssh://src.earth.threerings.net/vilya/trunk@193 c613c5cb-e716-0410-b11b-feb51c14d237 --- src/as/com/threerings/ezgame/EZGameControl.as | 17 ++++- .../threerings/ezgame/client/EZGameService.as | 2 +- .../ezgame/client/GameControlBackend.as | 12 ++-- .../com/threerings/ezgame/client/GameData.as | 2 +- .../ezgame/data/EZGameMarshaller.as | 8 +-- .../threerings/ezgame/data/EZGameObject.as | 38 +++++----- .../ezgame/data/PropertySetEvent.as | 7 +- src/java/com/threerings/ezgame/EZGame.java | 10 +++ .../ezgame/client/EZGameService.java | 2 +- .../ezgame/client/GameObjectImpl.java | 18 ++++- .../ezgame/data/EZGameMarshaller.java | 8 +-- .../threerings/ezgame/data/EZGameObject.java | 70 ++++++++++--------- .../ezgame/data/PropertySetEvent.java | 8 ++- .../ezgame/server/DictionaryService.java | 9 --- .../ezgame/server/EZGameDispatcher.java | 2 +- .../ezgame/server/EZGameManager.java | 12 ++-- .../ezgame/server/EZGameProvider.java | 2 +- 17 files changed, 136 insertions(+), 91 deletions(-) diff --git a/src/as/com/threerings/ezgame/EZGameControl.as b/src/as/com/threerings/ezgame/EZGameControl.as index fb249ada..394905a9 100644 --- a/src/as/com/threerings/ezgame/EZGameControl.as +++ b/src/as/com/threerings/ezgame/EZGameControl.as @@ -153,11 +153,24 @@ 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_v1", propName, value, index); + callEZCode("setProperty_v2", propName, value, index, false); + } + + /** + * Set a property that will be distributed, but only if it was null before. + * + * 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 + { + callEZCode("setProperty_v2", propName, value, index, true); } /** diff --git a/src/as/com/threerings/ezgame/client/EZGameService.as b/src/as/com/threerings/ezgame/client/EZGameService.as index 329e572c..63455ace 100644 --- a/src/as/com/threerings/ezgame/client/EZGameService.as +++ b/src/as/com/threerings/ezgame/client/EZGameService.as @@ -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 :InvocationService_InvocationListener) :void; + function setProperty (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :Boolean, arg6 :InvocationService_InvocationListener) :void; // from Java interface EZGameService function setTicker (arg1 :Client, arg2 :String, arg3 :int, arg4 :InvocationService_InvocationListener) :void; diff --git a/src/as/com/threerings/ezgame/client/GameControlBackend.as b/src/as/com/threerings/ezgame/client/GameControlBackend.as index ef77b841..fe30b550 100644 --- a/src/as/com/threerings/ezgame/client/GameControlBackend.as +++ b/src/as/com/threerings/ezgame/client/GameControlBackend.as @@ -72,7 +72,7 @@ public class GameControlBackend { _ctx = ctx; _ezObj = ezObj; - _gameData = new GameData(setProperty_v1, _ezObj.getUserProps()); + _gameData = new GameData(setProperty_v2, _ezObj.getUserProps()); _ezObj.addListener(this); _ctx.getClient().getClientObject().addListener(_userListener); @@ -136,7 +136,7 @@ public class GameControlBackend o["gameData"] = _gameData; // functions - o["setProperty_v1"] = setProperty_v1; + o["setProperty_v2"] = setProperty_v2; o["mergeCollection_v1"] = mergeCollection_v1; o["setTicker_v1"] = setTicker_v1; o["sendChat_v1"] = sendChat_v1; @@ -163,18 +163,18 @@ public class GameControlBackend o["getPlayers_v1"] = getPlayers_v1; } - public function setProperty_v1 ( - propName :String, value :Object, index :int) :void + public function setProperty_v2 ( + propName :String, value :Object, index :int, testAndSet :Boolean) :void { validatePropertyChange(propName, value, index); var encoded :Object = EZObjectMarshaller.encode(value, (index == -1)); _ezObj.ezGameService.setProperty( - _ctx.getClient(), propName, encoded, index, + _ctx.getClient(), propName, encoded, index, testAndSet, createLoggingConfirmListener("setProperty")); // set it immediately in the game object - _ezObj.applyPropertySet(propName, value, index); + _ezObj.applyPropertySet(propName, value, index, testAndSet); } public function mergeCollection_v1 ( diff --git a/src/as/com/threerings/ezgame/client/GameData.as b/src/as/com/threerings/ezgame/client/GameData.as index d408aec1..c6bd82e8 100644 --- a/src/as/com/threerings/ezgame/client/GameData.as +++ b/src/as/com/threerings/ezgame/client/GameData.as @@ -67,7 +67,7 @@ public class GameData extends Proxy override flash_proxy function setProperty (propName :*, value :*) :void { - _propSetFn(String(propName), value, -1); + _propSetFn(String(propName), value, -1, false); } override flash_proxy function deleteProperty (propName :*) :Boolean diff --git a/src/as/com/threerings/ezgame/data/EZGameMarshaller.as b/src/as/com/threerings/ezgame/data/EZGameMarshaller.as index be50f3b1..306ef887 100644 --- a/src/as/com/threerings/ezgame/data/EZGameMarshaller.as +++ b/src/as/com/threerings/ezgame/data/EZGameMarshaller.as @@ -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 :InvocationService_InvocationListener) :void + public function setProperty (arg1 :Client, arg2 :String, arg3 :Object, arg4 :int, arg5 :Boolean, arg6 :InvocationService_InvocationListener) :void { - var listener5 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller(); - listener5.listener = arg5; + var listener6 :InvocationMarshaller_ListenerMarshaller = new InvocationMarshaller_ListenerMarshaller(); + listener6.listener = arg6; sendRequest(arg1, SET_PROPERTY, [ - arg2, arg3, Integer.valueOf(arg4), listener5 + arg2, arg3, Integer.valueOf(arg4), langBoolean.valueOf(arg5), listener6 ]); } diff --git a/src/as/com/threerings/ezgame/data/EZGameObject.as b/src/as/com/threerings/ezgame/data/EZGameObject.as index b5757b3a..8ebfc41f 100644 --- a/src/as/com/threerings/ezgame/data/EZGameObject.as +++ b/src/as/com/threerings/ezgame/data/EZGameObject.as @@ -98,26 +98,30 @@ public class EZGameObject extends GameObject * @return the old value */ public function applyPropertySet ( - propName :String, value :Object, index :int) :Object + propName :String, value :Object, index :int, testAndSet :Boolean) + :Object { var oldValue :Object = _props[propName]; - if (index >= 0) { - // set an array element - var arr :Array = (oldValue as Array); - if (arr == null) { - arr = []; - _props[propName] = arr; + 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]; } - 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; } diff --git a/src/as/com/threerings/ezgame/data/PropertySetEvent.as b/src/as/com/threerings/ezgame/data/PropertySetEvent.as index b9943c73..bd491716 100644 --- a/src/as/com/threerings/ezgame/data/PropertySetEvent.as +++ b/src/as/com/threerings/ezgame/data/PropertySetEvent.as @@ -35,7 +35,7 @@ public class PropertySetEvent extends NamedEvent override public function applyToObject (target :DObject) :Boolean { _oldValue = - EZGameObject(target).applyPropertySet(_name, _data, _index); + EZGameObject(target).applyPropertySet(_name, _data, _index, _testAndSet); return true; } @@ -69,6 +69,7 @@ public class PropertySetEvent extends NamedEvent super.readObject(ins); _index = ins.readInt(); _data = EZObjectMarshaller.decode(ins.readObject()); + _testAndSet = ins.readBoolean(); } // from interface Streamable @@ -77,6 +78,7 @@ public class PropertySetEvent extends NamedEvent super.writeObject(out); out.writeInt(_index); out.writeObject(_data); + out.writeBoolean(_testAndSet); } override protected function notifyListener (listener :Object) :void @@ -99,6 +101,9 @@ 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; } diff --git a/src/java/com/threerings/ezgame/EZGame.java b/src/java/com/threerings/ezgame/EZGame.java index af2cbc1d..fa2ad354 100644 --- a/src/java/com/threerings/ezgame/EZGame.java +++ b/src/java/com/threerings/ezgame/EZGame.java @@ -27,6 +27,16 @@ public interface EZGame */ public void set (String propName, Object value, int index); + /** + * Set a property that will be distributed. + */ + public void set (String propName, Object value, boolean testAndSet); + + /** + * Set a property that will be distributed. + */ + public void set (String propName, Object value, int index, boolean testAndSet); + /** * Register an object to receive whatever events it should receive, * based on which event listeners it implements. Note that it is not diff --git a/src/java/com/threerings/ezgame/client/EZGameService.java b/src/java/com/threerings/ezgame/client/EZGameService.java index c2de38d4..ea18d981 100644 --- a/src/java/com/threerings/ezgame/client/EZGameService.java +++ b/src/java/com/threerings/ezgame/client/EZGameService.java @@ -20,7 +20,7 @@ public interface EZGameService extends InvocationService */ public void setProperty ( Client client, String propName, Object value, int index, - InvocationListener listener); + boolean testAndSet, InvocationListener listener); /** * Request to end the turn, possibly futzing the next turn holder unless diff --git a/src/java/com/threerings/ezgame/client/GameObjectImpl.java b/src/java/com/threerings/ezgame/client/GameObjectImpl.java index ccdfdc39..60d2e8fb 100644 --- a/src/java/com/threerings/ezgame/client/GameObjectImpl.java +++ b/src/java/com/threerings/ezgame/client/GameObjectImpl.java @@ -61,22 +61,34 @@ public class GameObjectImpl // from EZGame public void set (String propName, Object value) { - set(propName, value, -1); + set(propName, value, -1, false); + } + + // from EZGame + public void set (String propName, Object value, boolean testAndSet) + { + set(propName, value, -1, testAndSet); } // from EZGame public void set (String propName, Object value, int index) + { + set(propName, value, index, false); + } + + // from EZGame + public void set (String propName, Object value, int index, boolean testAndSet) { validatePropertyChange(propName, value, -1); Object encoded = EZObjectMarshaller.encode(value); Object reconstituted = EZObjectMarshaller.decode(encoded); _ezObj.ezGameService.setProperty( - _ctx.getClient(), propName, encoded, index, + _ctx.getClient(), propName, encoded, index, testAndSet, createLoggingListener("setProperty")); // set it immediately in the game object - _ezObj.applyPropertySet(propName, reconstituted, index); + _ezObj.applyPropertySet(propName, reconstituted, index, testAndSet); } // from EZGame diff --git a/src/java/com/threerings/ezgame/data/EZGameMarshaller.java b/src/java/com/threerings/ezgame/data/EZGameMarshaller.java index 000f259b..20ac56d2 100644 --- a/src/java/com/threerings/ezgame/data/EZGameMarshaller.java +++ b/src/java/com/threerings/ezgame/data/EZGameMarshaller.java @@ -171,12 +171,12 @@ public class EZGameMarshaller extends InvocationMarshaller public static final int SET_PROPERTY = 11; // from interface EZGameService - public void setProperty (Client arg1, String arg2, Object arg3, int arg4, InvocationService.InvocationListener arg5) + public void setProperty (Client arg1, String arg2, Object arg3, int arg4, boolean arg5, InvocationService.InvocationListener arg6) { - ListenerMarshaller listener5 = new ListenerMarshaller(); - listener5.listener = arg5; + ListenerMarshaller listener6 = new ListenerMarshaller(); + listener6.listener = arg6; sendRequest(arg1, SET_PROPERTY, new Object[] { - arg2, arg3, Integer.valueOf(arg4), listener5 + arg2, arg3, Integer.valueOf(arg4), Boolean.valueOf(arg5), listener6 }); } diff --git a/src/java/com/threerings/ezgame/data/EZGameObject.java b/src/java/com/threerings/ezgame/data/EZGameObject.java index f379e82b..abd70686 100644 --- a/src/java/com/threerings/ezgame/data/EZGameObject.java +++ b/src/java/com/threerings/ezgame/data/EZGameObject.java @@ -85,46 +85,50 @@ public class EZGameObject extends GameObject /** * Called by PropertySetEvent to effect the property update. */ - public Object applyPropertySet (String propName, Object data, int index) + public Object applyPropertySet ( + String propName, Object data, int index, boolean testAndSet) { Object oldValue = _props.get(propName); - if (index >= 0) { - if (isOnServer()) { - byte[][] arr = (oldValue instanceof byte[][]) - ? (byte[][]) oldValue : null; - if (arr == null || arr.length <= index) { - // TODO: in case a user sets element 0 and element 90000, - // we might want to store elements in a hash - byte[][] newArr = new byte[index + 1][]; - if (arr != null) { - System.arraycopy(arr, 0, newArr, 0, arr.length); + if ((testAndSet && oldValue == null) || ! testAndSet) + { + if (index >= 0) { + if (isOnServer()) { + byte[][] arr = (oldValue instanceof byte[][]) + ? (byte[][]) oldValue : null; + if (arr == null || arr.length <= index) { + // TODO: in case a user sets element 0 and element 90000, + // we might want to store elements in a hash + byte[][] newArr = new byte[index + 1][]; + if (arr != null) { + System.arraycopy(arr, 0, newArr, 0, arr.length); + } + _props.put(propName, newArr); + arr = newArr; } - _props.put(propName, newArr); - arr = newArr; + oldValue = arr[index]; + arr[index] = (byte[]) data; + + } else { + Object[] arr = (oldValue instanceof Object[]) + ? (Object[]) oldValue : null; + if (arr == null || arr.length <= index) { + Object[] newArr = new Object[index + 1]; + if (arr != null) { + System.arraycopy(arr, 0, newArr, 0, arr.length); + } + _props.put(propName, newArr); + arr = newArr; + } + oldValue = arr[index]; + arr[index] = data; } - oldValue = arr[index]; - arr[index] = (byte[]) data; + + } else if (data != null) { + _props.put(propName, data); } else { - Object[] arr = (oldValue instanceof Object[]) - ? (Object[]) oldValue : null; - if (arr == null || arr.length <= index) { - Object[] newArr = new Object[index + 1]; - if (arr != null) { - System.arraycopy(arr, 0, newArr, 0, arr.length); - } - _props.put(propName, newArr); - arr = newArr; - } - oldValue = arr[index]; - arr[index] = data; + _props.remove(propName); } - - } else if (data != null) { - _props.put(propName, data); - - } else { - _props.remove(propName); } return oldValue; diff --git a/src/java/com/threerings/ezgame/data/PropertySetEvent.java b/src/java/com/threerings/ezgame/data/PropertySetEvent.java index 92adcafd..52d5ca37 100644 --- a/src/java/com/threerings/ezgame/data/PropertySetEvent.java +++ b/src/java/com/threerings/ezgame/data/PropertySetEvent.java @@ -30,11 +30,12 @@ public class PropertySetEvent extends NamedEvent * Create a PropertySetEvent. */ public PropertySetEvent ( - int targetOid, String propName, Object value, int index) + int targetOid, String propName, Object value, int index, boolean testAndSet) { super(targetOid, propName); _data = value; _index = index; + _testAndSet = testAndSet; } /** @@ -68,7 +69,7 @@ public class PropertySetEvent extends NamedEvent if (!ezObj.isOnServer()) { _data = EZObjectMarshaller.decode(_data); } - _oldValue = ezObj.applyPropertySet(_name, _data, _index); + _oldValue = ezObj.applyPropertySet(_name, _data, _index, _testAndSet); return true; } @@ -94,6 +95,9 @@ public class PropertySetEvent extends NamedEvent /** The client-side data that is assigned to this property. */ protected Object _data; + /** When true, the property will only be set if the old value was null. */ + protected boolean _testAndSet; + /** The old value. */ protected transient Object _oldValue; } diff --git a/src/java/com/threerings/ezgame/server/DictionaryService.java b/src/java/com/threerings/ezgame/server/DictionaryService.java index 3ff98eca..8349f08d 100644 --- a/src/java/com/threerings/ezgame/server/DictionaryService.java +++ b/src/java/com/threerings/ezgame/server/DictionaryService.java @@ -174,15 +174,6 @@ public class DictionaryService } - /** - * Obsolete init function. I'm keeping it around to prevent - * any automated build breakage between the upcoming Vilya checkin - * and the subsequent MSoy checkin. But this will go away real soon. - */ - public static void init () - { - } - /** * Creates the singleton instance of the dictionary service. */ diff --git a/src/java/com/threerings/ezgame/server/EZGameDispatcher.java b/src/java/com/threerings/ezgame/server/EZGameDispatcher.java index dc0b1ce3..00e6d2b1 100644 --- a/src/java/com/threerings/ezgame/server/EZGameDispatcher.java +++ b/src/java/com/threerings/ezgame/server/EZGameDispatcher.java @@ -129,7 +129,7 @@ public class EZGameDispatcher extends InvocationDispatcher case EZGameMarshaller.SET_PROPERTY: ((EZGameProvider)provider).setProperty( source, - (String)args[0], (Object)args[1], ((Integer)args[2]).intValue(), (InvocationService.InvocationListener)args[3] + (String)args[0], (Object)args[1], ((Integer)args[2]).intValue(), ((Boolean)args[3]).booleanValue(), (InvocationService.InvocationListener)args[4] ); return; diff --git a/src/java/com/threerings/ezgame/server/EZGameManager.java b/src/java/com/threerings/ezgame/server/EZGameManager.java index 8f455f19..28b12f61 100644 --- a/src/java/com/threerings/ezgame/server/EZGameManager.java +++ b/src/java/com/threerings/ezgame/server/EZGameManager.java @@ -127,11 +127,11 @@ public class EZGameManager extends GameManager // from EZGameProvider public void setProperty ( ClientObject caller, String propName, Object data, int index, - InvocationService.InvocationListener listener) + boolean testAndSet, InvocationService.InvocationListener listener) throws InvocationException { validateUser(caller); - setProperty(propName, data, index); + setProperty(propName, data, index, testAndSet); } // from EZGameProvider @@ -220,7 +220,7 @@ public class EZGameManager extends GameManager } if (playerId == 0) { - setProperty(msgOrPropName, result, -1); + setProperty(msgOrPropName, result, -1, false); } else { sendPrivateMessage(playerId, msgOrPropName, result); @@ -400,10 +400,12 @@ public class EZGameManager extends GameManager /** * Helper method to post a property set event. */ - protected void setProperty (String propName, Object value, int index) + protected void setProperty ( + String propName, Object value, int index, boolean testAndSet) { _gameObj.postEvent( - new PropertySetEvent(_gameObj.getOid(), propName, value, index)); + new PropertySetEvent( + _gameObj.getOid(), propName, value, index, testAndSet)); } /** diff --git a/src/java/com/threerings/ezgame/server/EZGameProvider.java b/src/java/com/threerings/ezgame/server/EZGameProvider.java index 06242815..38b22242 100644 --- a/src/java/com/threerings/ezgame/server/EZGameProvider.java +++ b/src/java/com/threerings/ezgame/server/EZGameProvider.java @@ -96,7 +96,7 @@ public interface EZGameProvider extends InvocationProvider /** * Handles a {@link EZGameService#setProperty} request. */ - public void setProperty (ClientObject caller, String arg1, Object arg2, int arg3, InvocationService.InvocationListener arg4) + public void setProperty (ClientObject caller, String arg1, Object arg2, int arg3, boolean arg4, InvocationService.InvocationListener arg5) throws InvocationException; /**