diff --git a/src/as/com/threerings/presents/dobj/DObject.as b/src/as/com/threerings/presents/dobj/DObject.as index c13c8e832..ef1318fd5 100644 --- a/src/as/com/threerings/presents/dobj/DObject.as +++ b/src/as/com/threerings/presents/dobj/DObject.as @@ -21,6 +21,8 @@ package com.threerings.presents.dobj { +import flash.errors.IllegalOperationError; + import flash.events.EventDispatcher; import com.threerings.util.ClassUtil; @@ -125,15 +127,24 @@ public class DObject // extends EventDispatcher } } + /** + * Posts a message event on this distrubuted object. + */ public function postMessage (name :String, args :Array) :void { postEvent(new MessageEvent(_oid, name, args)); } + /** + * Posts the specified event either to our dobject manager or to the compound event for which + * we are currently transacting. + */ public function postEvent (event :DEvent) :void { - // TODO: transactons? - if (_omgr != null) { + if (_tevent != null) { + _tevent.postEvent(event); + + } else if (_omgr != null) { _omgr.postEvent(event); } else { @@ -178,32 +189,122 @@ public class DObject // extends EventDispatcher buf.append("oid=", _oid); } + /** + * Begins a transaction on this distributed object. In some situations, it is desirable to + * cause multiple changes to distributed object fields in one unified operation. Starting a + * transaction causes all subsequent field modifications to be stored in a single compound + * event which can then be committed, dispatching and applying all included events in a single + * 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. + * + *
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 + * thread will begin and complete a transaction before giving up control to unknown code which + * might try to operate on the transacting distributed object. + * + *
Note also: if the object is already engaged in a transaction, a transaction participant
+ * count will be incremented to note that an additional call to {@link #commitTransaction} is
+ * required before the transaction should actually be committed. Thus every call to
+ * {@link #startTransaction} must be accompanied by a call to either {@link #commitTransaction}
+ * or {@link #cancelTransaction}. Additionally, if any transaction participant cancels the
+ * transaction, the entire transaction is cancelled for all participants, regardless of whether
+ * the other participants attempted to commit the transaction.
+ */
public function startTransaction () :void
{
- // TODO
+ if (_tevent != null) {
+ _tcount++;
+ } else {
+ _tevent = new CompoundEvent(this, _omgr);
+ }
}
+ /**
+ * Commits the transaction in which this distributed object is involved.
+ *
+ * @see CompoundEvent#commit
+ */
public function commitTransaction () :void
{
- // TODO
+ if (_tevent == null) {
+ 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
+ if (_tcount > 0) {
+ _tcount--;
+
+ } 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();
+ }
+ }
}
+ /**
+ * Returns true if this object is in the middle of a transaction or false if it is not.
+ */
public function inTransaction () :Boolean
{
- return false; // TODO
+ return (_tevent != null);
}
+ /**
+ * Cancels the transaction in which this distributed object is involved.
+ *
+ * @see CompoundEvent#cancel
+ */
public function cancelTransaction () :void
{
- // TODO
+ if (_tevent == null) {
+ 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
+ // parties commit and decrement the nest count
+ if (_tcount > 0) {
+ _tcancelled = true;
+ _tcount--;
+
+ } else {
+ _tevent.cancel();
+ }
}
+ /**
+ * Removes this object from participation in any transaction in which it might be taking part.
+ */
internal function clearTransaction () :void
{
- // TODO
+ // sanity check
+ if (_tcount != 0) {
+ log.warning("Transaction cleared with non-zero nesting count [dobj=" + this + "].");
+ _tcount = 0;
+ }
+
+ // clear our transaction state
+ _tevent = null;
+ _tcancelled = false;
}
- public function isActive () :Boolean
+ /**
+ * Returns true if this object is active and registered with the distributed object system. If
+ * an object is created via DObjectManager.createObject it will be active until
+ * such time as it is destroyed.
+ */
+ public final function isActive () :Boolean
{
return (_omgr != null);
}
@@ -310,6 +411,16 @@ public class DObject // extends EventDispatcher
protected var _subscribers :Array;
+ /** The compound event associated with our transaction, if we're currently in a transaction. */
+ protected var _tevent :CompoundEvent;
+
+ /** The nesting depth of our current transaction. */
+ protected var _tcount :int;
+
+ /** Whether or not our nested transaction has been cancelled. */
+ protected var _tcancelled :Boolean;
+
+ /** Indicates whether we want to be destroyed when our last subscriber is removed. */
protected var _deathWish :Boolean = false;
}
}