diff --git a/src/java/com/threerings/presents/client/ClientDObjectMgr.java b/src/java/com/threerings/presents/client/ClientDObjectMgr.java index a241370e5..a34998f5e 100644 --- a/src/java/com/threerings/presents/client/ClientDObjectMgr.java +++ b/src/java/com/threerings/presents/client/ClientDObjectMgr.java @@ -1,9 +1,11 @@ // -// $Id: ClientDObjectMgr.java,v 1.11 2002/02/07 00:24:36 shaper Exp $ +// $Id: ClientDObjectMgr.java,v 1.12 2002/02/09 07:50:37 mdb Exp $ package com.threerings.presents.client; import java.util.ArrayList; +import java.util.List; + import com.samskivert.util.HashIntMap; import com.samskivert.util.Queue; import com.samskivert.util.StringUtil; @@ -153,6 +155,17 @@ public class ClientDObjectMgr { // Log.info("Dispatch event: " + event); + // if this is a compound event, we need to process its contained + // events in order + if (event instanceof CompoundEvent) { + List events = ((CompoundEvent)event).getEvents(); + int ecount = events.size(); + for (int i = 0; i < ecount; i++) { + dispatchEvent((DEvent)events.get(i)); + } + return; + } + // look up the object on which we're dispatching this event DObject target = (DObject)_ocache.get(event.getTargetOid()); if (target == null) { diff --git a/src/java/com/threerings/presents/dobj/CompoundEvent.java b/src/java/com/threerings/presents/dobj/CompoundEvent.java new file mode 100644 index 000000000..0e5ef5118 --- /dev/null +++ b/src/java/com/threerings/presents/dobj/CompoundEvent.java @@ -0,0 +1,168 @@ +// +// $Id: CompoundEvent.java,v 1.1 2002/02/09 07:50:37 mdb Exp $ + +package com.threerings.presents.dobj; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import java.util.ArrayList; +import java.util.List; + +import com.threerings.presents.io.TypedObjectFactory; + +/** + * Used to manage and submit groups of events on a collection of + * distributed objects in a single transaction. + * + * @see DObject#startTransaction + */ +public class CompoundEvent extends TypedEvent +{ + /** The typed object code for this event. */ + public static final short TYPE = TYPE_BASE + 50; + + /** + * Constructs a compound event and prepares it for operation. + */ + public CompoundEvent (DObjectManager omgr) + { + super(0); // we don't have a single target object oid + _omgr = omgr; + } + + /** + * Lets the event know that this dobject is participating in their + * transaction. The supplied dobject will have their transaction + * cleared when this event is committed or cancelled. + */ + public void addObject (DObject object) + { + if (_participants == null) { + _participants = new ArrayList(); + } + _participants.add(object); + } + + /** + * 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 void postEvent (TypedEvent event) + { + _events.add(event); + } + + /** + * Returns the list of events contained within this compound event. + */ + public List getEvents () + { + return _events; + } + + /** + * 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. + */ + public void commit () + { + // first clear our participants + clearParticipants(); + + // then post this event onto the queue + _omgr.postEvent(this); + } + + /** + * Cancels this transaction. All events posted to this transaction + * will be discarded. + */ + public void cancel () + { + // clear our participants + clearParticipants(); + // clear our event queue in case someone holds onto us + _events.clear(); + } + + /** + * Nothing to apply here. + */ + public boolean applyToObject (DObject target) + throws ObjectAccessException + { + return false; + } + + // documentation inherited + public short getType () + { + return TYPE; + } + + // documentation inherited + public void writeTo (DataOutputStream out) + throws IOException + { + super.writeTo(out); + int ecount = _events.size(); + for (int i = 0; i < ecount; i++) { + TypedEvent event = (TypedEvent)_events.get(i); + TypedObjectFactory.writeTo(out, event); + } + } + + // documentation inherited + public void readFrom (DataInputStream in) + throws IOException + { + super.readFrom(in); + int ecount = in.readInt(); + for (int i = 0; i < ecount; i++) { + _events.add(TypedObjectFactory.readFrom(in)); + } + } + + /** + * Calls out to all of the participating dobjects, clearing their + * transaction reference. + */ + protected void clearParticipants () + { + if (_participants != null) { + int psize = _participants.size(); + for (int i = 0; i < psize; i++) { + DObject obj = (DObject)_participants.get(i); + obj.clearTransaction(); + } + _participants = null; + } + } + + // documentation inherited + protected void toString (StringBuffer buf) + { + buf.append("COMPOUND:"); + super.toString(buf); + for (int i = 0; i < _events.size(); i++) { + buf.append(", ").append(_events.get(i)); + } + } + + /** The object manager that we'll post ourselves to when we're + * committed. */ + protected DObjectManager _omgr; + + /** A list of the events associated with this compound event. */ + protected ArrayList _events = new ArrayList(); + + /** A list of the dobject participants in this transaction. They will + * be notified when we are committed or cancelled so that they can + * stop posting their events to us. */ + protected ArrayList _participants; +} diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java index 0a1b37906..f89451277 100644 --- a/src/java/com/threerings/presents/dobj/DObject.java +++ b/src/java/com/threerings/presents/dobj/DObject.java @@ -1,5 +1,5 @@ // -// $Id: DObject.java,v 1.38 2002/02/06 22:47:28 mdb Exp $ +// $Id: DObject.java,v 1.39 2002/02/09 07:50:37 mdb Exp $ package com.threerings.presents.dobj; @@ -130,8 +130,8 @@ public class DObject // the last subscriber from our list; we also want to be sure // that we're still active otherwise there's no need to notify // our objmgr because we don't have one - if (--_scount == 0 && _mgr != null) { - _mgr.removedLastSubscriber(this); + if (--_scount == 0 && _omgr != null) { + _omgr.removedLastSubscriber(this); } } } @@ -226,8 +226,7 @@ public class DObject public void releaseLock (String name) { // queue up a release lock event - ReleaseLockEvent event = new ReleaseLockEvent(_oid, name); - _mgr.postEvent(event); + postEvent(new ReleaseLockEvent(_oid, name)); } /** @@ -257,7 +256,7 @@ public class DObject */ public void destroy () { - _mgr.postEvent(new ObjectDestroyedEvent(_oid)); + postEvent(new ObjectDestroyedEvent(_oid)); } /** @@ -410,7 +409,7 @@ public class DObject */ public boolean isActive () { - return _mgr != null; + return _omgr != null; } /** @@ -421,9 +420,9 @@ public class DObject * * @see DObjectManager#createObject */ - public void setManager (DObjectManager mgr) + public void setManager (DObjectManager omgr) { - _mgr = mgr; + _omgr = omgr; } /** @@ -448,16 +447,115 @@ public class DObject return buf.append("]").toString(); } + /** + * 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 all involved objects back to their 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 and release all + * involved objects. 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 (or objects).
+ *
+ * @return the compound event that encapsulates the transaction. This
+ * can be ignored if the transaction will be limited to this object,
+ * but if it is desired that other objects be involved in the
+ * transaction, the caller will need to pass the {@link CompoundEvent}
+ * returned by this method to a call to {@link #joinTransaction} on
+ * the other objects that are involved.
+ */
+ public CompoundEvent startTransaction ()
+ {
+ if (_tevent != null) {
+ String errmsg = "Cannot start transaction on dobject that " +
+ "is already transacting [dobj=" + this + "]";
+ throw new IllegalStateException(errmsg);
+ }
+ _tevent = new CompoundEvent(_omgr);
+ _tevent.addObject(this);
+ return _tevent;
+ }
+
+ /**
+ * Causes this object to join the supplied transaction. See {@link
+ * #startTransaction} for more information on transactions.
+ */
+ public void joinTransaction (CompoundEvent event)
+ {
+ if (_tevent != null) {
+ String errmsg = "Cannot join transaction while already " +
+ "transacting [dobj=" + this + "]";
+ throw new IllegalStateException(errmsg);
+ }
+ _tevent = event;
+ _tevent.addObject(this);
+ }
+
+ /**
+ * Commits the transaction in which this distributed object is
+ * involved.
+ *
+ * @see CompoundEvent#commit
+ */
+ public void commitTransaction ()
+ {
+ if (_tevent == null) {
+ String errmsg = "Cannot commit: not involved in a transaction " +
+ "[dobj=" + this + "]";
+ throw new IllegalStateException(errmsg);
+ }
+ _tevent.commit();
+ }
+
+ /**
+ * Cancels the transaction in which this distributed object is
+ * involved.
+ *
+ * @see CompoundEvent#cancel
+ */
+ public void cancelTransaction ()
+ {
+ if (_tevent == null) {
+ String errmsg = "Cannot cancel: not involved in a transaction " +
+ "[dobj=" + this + "]";
+ throw new IllegalStateException(errmsg);
+ }
+ _tevent.cancel();
+ }
+
+ /**
+ * Removes this object from participation in any transaction in which
+ * it might be taking part.
+ */
+ protected void clearTransaction ()
+ {
+ _tevent = null;
+ }
+
/**
* Called by derived instances when an attribute setter method was
* called.
*/
protected void requestAttributeChange (String name, Object value)
{
- // generate an attribute changed event
- DEvent event = new AttributeChangedEvent(_oid, name, value);
- // and dispatch it to our dobjmgr
- _mgr.postEvent(event);
+ // dispatch an attribute changed event
+ postEvent(new AttributeChangedEvent(_oid, name, value));
}
/**
@@ -465,10 +563,8 @@ public class DObject
*/
protected void requestOidAdd (String name, int oid)
{
- // generate an object added event
- DEvent event = new ObjectAddedEvent(_oid, name, oid);
- // and dispatch it to our dobjmgr
- _mgr.postEvent(event);
+ // dispatch an object added event
+ postEvent(new ObjectAddedEvent(_oid, name, oid));
}
/**
@@ -476,10 +572,8 @@ public class DObject
*/
protected void requestOidRemove (String name, int oid)
{
- // generate an object removed event
- DEvent event = new ObjectRemovedEvent(_oid, name, oid);
- // and dispatch it to our dobjmgr
- _mgr.postEvent(event);
+ // dispatch an object removed event
+ postEvent(new ObjectRemovedEvent(_oid, name, oid));
}
/**
@@ -489,11 +583,9 @@ public class DObject
{
try {
DSet set = (DSet)getAttribute(name);
- // generate an element added event
- DEvent event = new ElementAddedEvent(
- _oid, name, elem, !set.homogenous());
- // and dispatch it to our dobjmgr
- _mgr.postEvent(event);
+ // dispatch an element added event
+ postEvent(new ElementAddedEvent(
+ _oid, name, elem, !set.homogenous()));
} catch (ObjectAccessException oae) {
Log.warning("Unable to request elementAdd [name=" + name +
@@ -506,10 +598,8 @@ public class DObject
*/
protected void requestElementRemove (String name, Object key)
{
- // generate an element removed event
- DEvent event = new ElementRemovedEvent(_oid, name, key);
- // and dispatch it to our dobjmgr
- _mgr.postEvent(event);
+ // dispatch an element removed event
+ postEvent(new ElementRemovedEvent(_oid, name, key));
}
/**
@@ -519,11 +609,9 @@ public class DObject
{
try {
DSet set = (DSet)getAttribute(name);
- // generate an element updated event
- DEvent event = new ElementUpdatedEvent(
- _oid, name, elem, !set.homogenous());
- // and dispatch it to our dobjmgr
- _mgr.postEvent(event);
+ // dispatch an element updated event
+ postEvent(new ElementUpdatedEvent(
+ _oid, name, elem, !set.homogenous()));
} catch (ObjectAccessException oae) {
Log.warning("Unable to request elementUpdate [name=" + name +
@@ -531,11 +619,24 @@ public class DObject
}
}
+ /**
+ * Posts the specified event either to our dobject manager or to the
+ * compound event for which we are currently transacting.
+ */
+ protected void postEvent (TypedEvent event)
+ {
+ if (_tevent != null) {
+ _tevent.postEvent(event);
+ } else {
+ _omgr.postEvent(event);
+ }
+ }
+
/** Our object id. */
protected int _oid;
/** A reference to our object manager. */
- protected DObjectManager _mgr;
+ protected DObjectManager _omgr;
/** A list of outstanding locks. */
protected Object[] _locks;
@@ -548,4 +649,8 @@ public class DObject
/** Our subscriber count. */
protected int _scount;
+
+ /** The compound event associated with our transaction, if we're
+ * currently in a transaction. */
+ protected CompoundEvent _tevent;
}
diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
index 722f1c877..802e33f05 100644
--- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
+++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java
@@ -1,10 +1,11 @@
//
-// $Id: PresentsDObjectMgr.java,v 1.22 2002/02/02 09:53:00 mdb Exp $
+// $Id: PresentsDObjectMgr.java,v 1.23 2002/02/09 07:50:37 mdb Exp $
package com.threerings.presents.server;
import java.lang.reflect.*;
import java.util.HashMap;
+import java.util.List;
import com.samskivert.util.HashIntMap;
import com.samskivert.util.Queue;
@@ -132,62 +133,79 @@ public class PresentsDObjectMgr implements RootDObjectManager
Log.warning("Execution unit failed [unit=" + unit + "].");
Log.logStackTrace(e);
}
- continue;
- }
- // otherwise it's an event, so we do more complicated
- // processing
- DEvent event = (DEvent)unit;
-
- // look up the target object
- DObject target = (DObject)_objects.get(event.getTargetOid());
- if (target == null) {
- Log.warning("Event target no longer exists " +
- "[event=" + event + "].");
- continue;
- }
-
- // check the event's permissions
- if (!target.checkPermissions(event)) {
- Log.warning("Event failed permissions check " +
- "[event=" + event + ", target=" + target + "].");
- continue;
- }
-
- try {
- // do any internal management necessary based on this
- // event
- Method helper = (Method)_helpers.get(event.getClass());
- if (helper != null) {
- // invoke the helper method
- Object rv =
- helper.invoke(this, new Object[] { event, target });
- // if helper returns false, we abort event processing
- if (!((Boolean)rv).booleanValue()) {
- continue;
- }
+ } else if (unit instanceof CompoundEvent) {
+ // if this is a compound event, we need to apply each
+ // event indivdually
+ CompoundEvent event = (CompoundEvent)unit;
+ List events = event.getEvents();
+ int ecount = events.size();
+ for (int i = 0; i < ecount; i++) {
+ processEvent((DEvent)events.get(i));
}
- // everything's good so far, apply the event to the object
- boolean notify = event.applyToObject(target);
-
- // if the event returns false from applyToObject, this
- // means it's a silent event and we shouldn't notify the
- // listeners
- if (notify) {
- target.notifyListeners(event);
- }
-
- } catch (Exception e) {
- Log.warning("Failure processing event [event=" + event +
- ", target=" + target + "].");
- Log.logStackTrace(e);
+ } else {
+ // otherwise it's a regular event, so do the standard
+ // processing
+ processEvent((DEvent)unit);
}
}
Log.info("DOMGR exited.");
}
+ /**
+ * Performs the processing associated with an event, notifying
+ * subscribers and the like.
+ */
+ protected void processEvent (DEvent event)
+ {
+ // look up the target object
+ DObject target = (DObject)_objects.get(event.getTargetOid());
+ if (target == null) {
+ Log.warning("Event target no longer exists " +
+ "[event=" + event + "].");
+ return;
+ }
+
+ // check the event's permissions
+ if (!target.checkPermissions(event)) {
+ Log.warning("Event failed permissions check " +
+ "[event=" + event + ", target=" + target + "].");
+ return;
+ }
+
+ try {
+ // do any internal management necessary based on this
+ // event
+ Method helper = (Method)_helpers.get(event.getClass());
+ if (helper != null) {
+ // invoke the helper method
+ Object rv =
+ helper.invoke(this, new Object[] { event, target });
+ // if helper returns false, we abort event processing
+ if (!((Boolean)rv).booleanValue()) {
+ return;
+ }
+ }
+
+ // everything's good so far, apply the event to the object
+ boolean notify = event.applyToObject(target);
+
+ // if the event returns false from applyToObject, this
+ // means it's a silent event and we shouldn't notify the
+ // listeners
+ if (notify) {
+ target.notifyListeners(event);
+ }
+
+ } catch (Exception e) {
+ Log.warning("Failure processing event [event=" + event +
+ ", target=" + target + "].");
+ Log.logStackTrace(e);
+ }
+ }
+
/**
* Requests that the dobjmgr shut itself down. It will exit the event
* processing loop which cause run() to return.