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; /**