diff --git a/src/as/com/threerings/presents/client/InvocationDirector.as b/src/as/com/threerings/presents/client/InvocationDirector.as index 22bd58afc..199d01cc8 100644 --- a/src/as/com/threerings/presents/client/InvocationDirector.as +++ b/src/as/com/threerings/presents/client/InvocationDirector.as @@ -21,6 +21,7 @@ package com.threerings.presents.client { +import flash.errors.IllegalOperationError; import flash.utils.getTimer; // function import import com.threerings.util.HashMap; @@ -28,6 +29,7 @@ import com.threerings.util.Wrapped; import com.threerings.presents.data.InvocationMarshaller_ListenerMarshaller; +import com.threerings.presents.dobj.CompoundEvent; import com.threerings.presents.dobj.DEvent; import com.threerings.presents.dobj.DObject; import com.threerings.presents.dobj.DObjectManager; @@ -48,8 +50,7 @@ public class InvocationDirector { private static const log :Log = Log.getLog(InvocationDirector); - public function init (omgr :DObjectManager, cloid :int, client :Client) - :void + public function init (omgr :DObjectManager, cloid :int, client :Client) :void { if (_clobj != null) { log.warning("Zoiks, client object around during invmgr init!"); @@ -74,8 +75,7 @@ public class InvocationDirector } /** - * Registers an invocation notification receiver by way of its - * notification event decoder. + * Registers an invocation notification receiver by way of its notification event decoder. */ public function registerReceiver (decoder :InvocationDecoder) :void { @@ -143,6 +143,36 @@ public class InvocationDirector } } + /** + * Starts a transaction that allows multiple invocation service requests to be batched into a + * single message and sent to the server all at once. + * + *
When the transaction is complete, the caller must call {@link #commitTransaction} to + * cause the requests to be sent. Failure to do so will render the entire invocation services + * non-functional. + */ + public function startTransaction () :void + { + // just increment our transaction nesting count, sendRequest will handle everything else + _tcount++; + } + + /** + * Commits a transaction started with {@link #startTransaction}. + */ + public function commitTransaction () :void + { + if (_tcount <= 0) { + throw new IllegalOperationError("Cannot commit: not involved in a transaction"); + } + if (--_tcount == 0) { + for each (var event :CompoundEvent in _tevents) { + event.commit(_omgr); + } + _tevents = []; + } + } + /** * Requests that the specified invocation request be packaged up and sent to the supplied * invocation oid. @@ -165,7 +195,17 @@ public class InvocationDirector } // create an invocation request event and dispatch it - _omgr.postEvent(new InvocationRequestEvent(invOid, invCode, methodId, args)); + var req :InvocationRequestEvent = + new InvocationRequestEvent(invOid, invCode, methodId, args); + if (_tcount > 0) { + var event :CompoundEvent = _tevents[invOid]; + if (event == null) { + _tevents[invOid] = (event = new CompoundEvent(invOid)); + } + event.postEvent(req); + } else { + _omgr.postEvent(req); + } } // documentation inherited from interface EventListener @@ -359,6 +399,12 @@ public class InvocationDirector /** Used to keep track of invocation notification receivers. */ protected var _receivers :HashMap = new HashMap(); + /** A count of how deeply nested we are in transaction land. */ + protected var _tcount :int; + + /** An event used to accumulate service request events when in a transaction. */ + protected var _tevents :Array = []; + /** All registered receivers are maintained in a list so that we can assign receiver ids to * them when we go online. */ internal var _reclist :Array = []; diff --git a/src/as/com/threerings/presents/dobj/CompoundEvent.as b/src/as/com/threerings/presents/dobj/CompoundEvent.as index 0a95d010c..b09ab11c9 100644 --- a/src/as/com/threerings/presents/dobj/CompoundEvent.as +++ b/src/as/com/threerings/presents/dobj/CompoundEvent.as @@ -28,8 +28,8 @@ import com.threerings.util.StreamableArrayList; import com.threerings.util.StringBuilder; /** - * Used to manage and submit groups of events on a collection of - * distributed objects in a single transaction. + * Used to manage and submit groups of events on a collection of distributed objects in a single + * transaction. * * @see DObject#startTransaction */ @@ -38,28 +38,18 @@ public class CompoundEvent extends DEvent /** * Constructs a compound event and prepares it for operation. */ - public function CompoundEvent ( - target :DObject = null, omgr :DObjectManager = null) + public function CompoundEvent (targetOid :int = 0) { - super((target == null) ? 0 : target.getOid()); + super(targetOid); - if (target != null) { - // sanity check - if (omgr == null) { - throw new ArgumentError( - "Must receive non-null object manager reference"); - } - - _omgr = omgr; - _target = target; + if (targetOid != 0) { _events = new StreamableArrayList(); } } /** - * Posts an event to this transaction. The event will be delivered as - * part of the entire transaction if it is committed or discarded if - * the transaction is cancelled. + * Posts an event to this transaction. The event will be delivered as part of the entire + * transaction if it is committed or discarded if the transaction is cancelled. */ public function postEvent (event :DEvent) :void { @@ -67,8 +57,7 @@ public class CompoundEvent extends DEvent } /** - * Returns the list of events contained within this compound event. - * Don't mess with it. + * Returns the list of events contained within this compound event. Don't mess with it. */ public function getEvents () :Array { @@ -76,63 +65,31 @@ public class CompoundEvent extends DEvent } /** - * Commits this transaction by posting this event to the distributed - * object event queue. All participating dobjects will have their - * transaction references cleared and will go back to normal - * operation. + * Commits this transaction by posting this event to the distributed object event queue. */ - public function commit () :void + public function commit (omgr :DObjectManager) :void { - // first clear our target - clearTarget(); - - // then post this event onto the queue (but only if we actually - // accumulated some events) + // post this event onto the queue (but only if we actually accumulated some events) switch (_events.size()) { case 0: // nothing doing break; case 1: // no point in being compound - _omgr.postEvent(_events.get(0) as DEvent); + omgr.postEvent(_events.get(0) as DEvent); break; default: // now we're talking - _omgr.postEvent(this); + omgr.postEvent(this); break; } } - /** - * Cancels this transaction. All events posted to this transaction - * will be discarded. - */ - public function cancel () :void - { - // clear our target - clearTarget(); - // clear our event queue in case someone holds onto us - _events.clear(); - } - - /** - * Nothing to apply here. - */ + // from DObject override public function applyToObject (target :DObject) :Boolean - //throws ObjectAccessException + // throws ObjectAccessException { return false; } - /** - * Calls out to our target object, clearing its transaction reference. - */ - protected function clearTarget () :void - { - if (_target != null) { - _target.clearTransaction(); - _target = null; - } - } - - // documentation inherited + // from DObject override protected function toStringBuf (buf :StringBuilder) :void { buf.append("COMPOUND:"); @@ -144,25 +101,20 @@ public class CompoundEvent extends DEvent } } + // from DObject override public function writeObject (out :ObjectOutputStream) :void { super.writeObject(out); out.writeObject(_events); } + // from DObject override public function readObject (ins :ObjectInputStream) :void { super.readObject(ins); _events = (ins.readObject() as StreamableArrayList); } - /** The object manager that we'll post ourselves to when we're - * committed. */ - protected var _omgr :DObjectManager; - - /** The object for which we're managing a transaction. */ - protected var _target :DObject; - /** A list of the events associated with this compound event. */ protected var _events :StreamableArrayList; } diff --git a/src/as/com/threerings/presents/dobj/DObject.as b/src/as/com/threerings/presents/dobj/DObject.as index ef1318fd5..c906eeeab 100644 --- a/src/as/com/threerings/presents/dobj/DObject.as +++ b/src/as/com/threerings/presents/dobj/DObject.as @@ -197,11 +197,11 @@ public class DObject // extends EventDispatcher * group. Additionally, the events are dispatched over the network in a single unit which can * significantly enhance network efficiency. * - *
When the transaction is complete, the caller must call {@link #commitTransaction} or - * {@link CompoundEvent#commit} to commit the transaction and release the object back to its - * normal non-transacting state. If the caller decides not to commit their transaction, they - * must call {@link #cancelTransaction} or {@link CompoundEvent#cancel} to cancel the - * transaction. Failure to do so will cause the pooch to be totally screwed. + *
When the transaction is complete, the caller must call {@link #commitTransaction} to + * commit the transaction and release the object back to its normal non-transacting state. If + * the caller decides not to commit their transaction, they must call {@link + * #cancelTransaction} to cancel the transaction. Failure to do so will cause the pooch to be + * totally screwed. * *
Note: like all other distributed object operations, transactions are not thread safe. It * is expected that a single thread will handle all distributed object operations and that @@ -221,20 +221,18 @@ public class DObject // extends EventDispatcher if (_tevent != null) { _tcount++; } else { - _tevent = new CompoundEvent(this, _omgr); + _tevent = new CompoundEvent(getOid()); } } /** * Commits the transaction in which this distributed object is involved. - * - * @see CompoundEvent#commit */ public function commitTransaction () :void { if (_tevent == null) { - throw new IllegalOperationError("Cannot commit: not involved in a transaction " + - "[dobj=" + this + "]"); + throw new IllegalOperationError( + "Cannot commit: not involved in a transaction [dobj=" + this + "]"); } // if we are nested, we decrement our nesting count rather than committing the transaction @@ -244,11 +242,10 @@ public class DObject // extends EventDispatcher } else { // we may actually be doing our final commit after someone already cancelled this // transaction, so we need to perform the appropriate action at this point - if (_tcancelled) { - _tevent.cancel(); - } else { - _tevent.commit(); + if (!_tcancelled) { + _tevent.commit(_omgr); } + clearTransaction(); } } @@ -262,14 +259,12 @@ public class DObject // extends EventDispatcher /** * Cancels the transaction in which this distributed object is involved. - * - * @see CompoundEvent#cancel */ public function cancelTransaction () :void { if (_tevent == null) { - throw new IllegalOperationError("Cannot cancel: not involved in a transaction " + - "[dobj=" + this + "]"); + throw new IllegalOperationError( + "Cannot cancel: not involved in a transaction [dobj=" + this + "]"); } // if we're in a nested transaction, make a note that it is to be cancelled when all @@ -279,7 +274,7 @@ public class DObject // extends EventDispatcher _tcount--; } else { - _tevent.cancel(); + clearTransaction(); } } @@ -310,10 +305,9 @@ public class DObject // extends EventDispatcher } /** - * Don't call this function! It initializes this distributed object - * with the supplied distributed object manager. This is called by the - * distributed object manager when an object is created and registered - * with the system. + * Don't call this function! It initializes this distributed object with the supplied + * distributed object manager. This is called by the distributed object manager when an object + * is created and registered with the system. * * @see DObjectManager#createObject */ @@ -323,21 +317,18 @@ public class DObject // extends EventDispatcher } /** - * Called by derived instances when an attribute setter method was - * called. + * Called by derived instances when an attribute setter method was called. */ - protected function requestAttributeChange ( - name :String, value :Object, oldValue :Object) :void + protected function requestAttributeChange (name :String, value :Object, oldValue :Object) :void { postEvent(new AttributeChangedEvent(_oid, name, value, oldValue)); } /** - * Called by derived instances when an element updater method was - * called. + * Called by derived instances when an element updater method was called. */ protected function requestElementUpdate ( - name :String, index :int, value :Object, oldValue :Object) :void + name :String, index :int, value :Object, oldValue :Object) :void { // dispatch an attribute changed event postEvent(new ElementUpdatedEvent(_oid, name, value, oldValue, index)); @@ -401,6 +392,7 @@ public class DObject // extends EventDispatcher _oid = ins.readInt(); } + /** Our unique identifier. */ protected var _oid :int; /** A reference to our object manager. */ @@ -409,6 +401,7 @@ public class DObject // extends EventDispatcher /** Our event listeners. */ protected var _listeners :Array; + /** A list of all subscribers to this object. */ protected var _subscribers :Array; /** The compound event associated with our transaction, if we're currently in a transaction. */