diff --git a/src/java/com/threerings/presents/client/ClientDObjectMgr.java b/src/java/com/threerings/presents/client/ClientDObjectMgr.java index 56f535838..ea10b5b8c 100644 --- a/src/java/com/threerings/presents/client/ClientDObjectMgr.java +++ b/src/java/com/threerings/presents/client/ClientDObjectMgr.java @@ -1,5 +1,5 @@ // -// $Id: ClientDObjectMgr.java,v 1.22 2003/03/11 04:43:14 mdb Exp $ +// $Id: ClientDObjectMgr.java,v 1.23 2003/04/10 17:48:42 mdb Exp $ package com.threerings.presents.client; @@ -166,6 +166,7 @@ public class ClientDObjectMgr } else if (obj instanceof EventNotification) { DEvent evt = ((EventNotification)obj).getEvent(); +// Log.info("Dispatch event: " + evt); dispatchEvent(evt); } else if (obj instanceof ObjectResponse) { @@ -202,8 +203,6 @@ public class ClientDObjectMgr */ protected void dispatchEvent (DEvent event) { - // Log.info("Dispatch event: " + event); - // if this is a compound event, we need to process its contained // events in order if (event instanceof CompoundEvent) { diff --git a/src/java/com/threerings/presents/dobj/CompoundEvent.java b/src/java/com/threerings/presents/dobj/CompoundEvent.java index 0a39a3ac6..1c0044e51 100644 --- a/src/java/com/threerings/presents/dobj/CompoundEvent.java +++ b/src/java/com/threerings/presents/dobj/CompoundEvent.java @@ -1,5 +1,5 @@ // -// $Id: CompoundEvent.java,v 1.8 2002/12/20 23:41:26 mdb Exp $ +// $Id: CompoundEvent.java,v 1.9 2003/04/10 17:48:42 mdb Exp $ package com.threerings.presents.dobj; @@ -27,9 +27,9 @@ public class CompoundEvent extends DEvent /** * Constructs a compound event and prepares it for operation. */ - public CompoundEvent (DObjectManager omgr) + public CompoundEvent (DObject target, DObjectManager omgr) { - super(0); // we don't have a single target object oid + super(target.getOid()); // sanity check if (omgr == null) { @@ -38,22 +38,10 @@ public class CompoundEvent extends DEvent } _omgr = omgr; + _target = target; _events = new StreamableArrayList(); } - /** - * 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 @@ -66,6 +54,7 @@ public class CompoundEvent extends DEvent /** * Returns the list of events contained within this compound event. + * Don't mess with it. */ public List getEvents () { @@ -80,13 +69,20 @@ public class CompoundEvent extends DEvent */ public void commit () { - // first clear our participants - clearParticipants(); + // first clear our target + clearTarget(); // then post this event onto the queue (but only if we actually // accumulated some events) - if (_events.size() > 0) { + switch (_events.size()) { + case 0: // nothing doing + break; + case 1: // no point in being compound + _omgr.postEvent((DEvent)_events.get(0)); + break; + default: // now we're talking _omgr.postEvent(this); + break; } } @@ -96,8 +92,8 @@ public class CompoundEvent extends DEvent */ public void cancel () { - // clear our participants - clearParticipants(); + // clear our target + clearTarget(); // clear our event queue in case someone holds onto us _events.clear(); } @@ -125,18 +121,13 @@ public class CompoundEvent extends DEvent } /** - * Calls out to all of the participating dobjects, clearing their - * transaction reference. + * Calls out to our target object, clearing its transaction reference. */ - protected void clearParticipants () + protected void clearTarget () { - if (_participants != null) { - int psize = _participants.size(); - for (int i = 0; i < psize; i++) { - DObject obj = (DObject)_participants.get(i); - obj.clearTransaction(); - } - _participants = null; + if (_target != null) { + _target.clearTransaction(); + _target = null; } } @@ -154,10 +145,8 @@ public class CompoundEvent extends DEvent * committed. */ protected transient DObjectManager _omgr; - /** 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 transient ArrayList _participants; + /** The object for which we're managing a transaction. */ + protected transient DObject _target; /** A list of the events associated with this compound event. */ protected StreamableArrayList _events; diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java index d7725c5b3..cebebbf9d 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.61 2003/03/30 19:38:56 mdb Exp $ +// $Id: DObject.java,v 1.62 2003/04/10 17:48:42 mdb Exp $ package com.threerings.presents.dobj; @@ -16,18 +16,26 @@ import com.threerings.presents.Log; /** * The distributed object forms the foundation of the Presents system. All * information shared among users of the system is done via distributed - * objects. A distributed object has a set of subscribers. These - * subscribers have access to the object or a proxy of the object and - * therefore have access to the data stored in the object's members at all - * times. + * objects. A distributed object has a set of listeners. These listeners + * have access to the object or a proxy of the object and therefore have + * access to the data stored in the object's members at all times. * - *
When there is any change to that data, initiated by one of the - * subscribers, an event is generated which is dispatched to all - * subscribers of the object, notifying them of that change and affecting - * that change to the copy of the object maintained at each client. In - * this way, both a respository of shared information and a mechanism for - * asynchronous notification are made available as a fundamental - * application building blocks. + *
Additionally, an object as a set of subscribers. Subscribers manage + * the lifespan of the object; while a subscriber is subscribed, the + * listeners registered with an object will be notified of events. When + * the subscriber unsubscribes, the object becomes non-live and the + * listeners are no longer notified. Note: on the server, object + * subscription is merely a formality as all objects remain live all the + * time, so do not depend on event notifications ceasing when a + * subscriber has relinquished its subscription. Always unregister all + * listeners when they no longer need to hear from an object. + * + *
When there is any change to the the object's fields data, an event + * is generated which is dispatched to all listeners of the object, + * notifying them of that change and effecting that change to the copy of + * the object maintained at each client. In this way, both a respository + * of shared information and a mechanism for asynchronous notification are + * made available as a fundamental application building blocks. * *
To define what information is shared, an application creates a * distributed object declaration which is much like a class declaration @@ -78,14 +86,16 @@ import com.threerings.presents.Log; * These method calls on the actual distributed object will result in the * proper attribute change events being generated and dispatched. * - *
Note that distributed object fields can only be of a limited set of - * supported types. These types are: + *
Note that distributed object fields can be any of the following set
+ * of primitive types:
*
*
+ *
+ * Fields of type {@link Streamable} can also be used.
*/
public class DObject implements Streamable
{
@@ -173,11 +183,9 @@ public class DObject implements Streamable
Object[] els = ListUtil.testAndAdd(_listeners, listener);
if (els != null) {
_listeners = els;
-
} else {
- // complain if an object requests to add itself more than once
- Log.warning("Refusing listener that's already in the list " +
- "[dobj=" + which() + ", listener=" + listener + "]");
+ Log.warning("Refusing repeat listener registration " +
+ "[dobj=" + which() + ", list=" + listener + "]");
Thread.dumpStack();
}
}
@@ -341,24 +349,19 @@ public class DObject implements Streamable
/**
* Called by the distributed object manager after it has applied an
* event to this object. This dispatches an event notification to all
- * of the subscribers of this object.
+ * of the listeners registered with this object.
*
* @param event the event that was just applied.
*/
public void notifyListeners (DEvent event)
{
- // if we have no listeners, we're home free
if (_listeners == null) {
return;
}
- // iterate over the listener list, performing the necessary
- // notifications
int llength = _listeners.length;
for (int i = 0; i < llength; i++) {
Object listener = _listeners[i];
-
- // skip empty slots
if (listener == null) {
continue;
}
@@ -374,8 +377,34 @@ public class DObject implements Streamable
} catch (Exception e) {
Log.warning("Listener choked during notification " +
- "[listener=" + listener +
- ", event=" + event + "].");
+ "[list=" + listener + ", event=" + event + "].");
+ Log.logStackTrace(e);
+ }
+ }
+ }
+
+ /**
+ * Called by the distributed object manager after it has applied an
+ * event to this object. This dispatches an event notification to all
+ * of the proxy listeners registered with this object.
+ *
+ * @param event the event that was just applied.
+ */
+ public void notifyProxies (DEvent event)
+ {
+ if (_subs == null) {
+ return;
+ }
+
+ for (int ii = 0, ll = _subs.length; ii < ll; ii++) {
+ Object sub = _subs[ii];
+ try {
+ if (sub != null && sub instanceof ProxySubscriber) {
+ ((ProxySubscriber)sub).eventReceived(event);
+ }
+ } catch (Exception e) {
+ Log.warning("Proxy choked during notification " +
+ "[sub=" + sub + ", event=" + event + "].");
Log.logStackTrace(e);
}
}
@@ -565,19 +594,18 @@ public class DObject implements Streamable
*
*
- * byte, short, int, long, float, double
- * Byte, Short, Integer, Long, Float, Double, String
- * byte[], short[], int[], long[], float[], double[], String[]
+ * boolean, byte, short, int, long, float, double
+ * Boolean, Byte, Short, Integer, Long, Float, Double, String
+ * boolean[], byte[], short[], int[], long[], float[], double[], String[]
*
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 + * 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 and release all - * involved objects. Failure to do so will cause the pooch to be - * totally screwed. + * 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 (or objects). + * 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 @@ -589,19 +617,8 @@ public class DObject implements Streamable * cancels the transaction, the entire transaction is cancelled for * all participants, regardless of whether the other participants * attempted to commit the transaction. - * - *
Note also: nested transactions are not allowed when an object - * is joined to another object's transaction via {@link - * #joinTransaction}. - * - * @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 () + public void startTransaction () { // sanity check if (!isActive()) { @@ -611,40 +628,10 @@ public class DObject implements Streamable } if (_tevent != null) { - // a transaction count of -1 indicates that we're joined to - // another objects transaction that should not allow - // transaction nesting - if (_tcount == -1) { - String errmsg = "Object involved in shared transaction, " + - "nesting not allowed [dobj=" + this + "]"; - throw new IllegalStateException(errmsg); - } else { - _tcount++; - } - + _tcount++; } else { - _tevent = new CompoundEvent(_omgr); - _tevent.addObject(this); + _tevent = new CompoundEvent(this, _omgr); } - return _tevent; - } - - /** - * Causes this object to join the supplied transaction. See {@link - * #startTransaction} for more information on transactions. The - * transaction can be committed by a call to {@link - * #commitTransaction} on any participanting object. - */ - 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); - _tcount = -1; // make a note that nesting is not allowed } /** diff --git a/src/java/com/threerings/presents/dobj/ProxySubscriber.java b/src/java/com/threerings/presents/dobj/ProxySubscriber.java new file mode 100644 index 000000000..89e02904b --- /dev/null +++ b/src/java/com/threerings/presents/dobj/ProxySubscriber.java @@ -0,0 +1,22 @@ +// +// $Id: ProxySubscriber.java,v 1.1 2003/04/10 17:48:42 mdb Exp $ + +package com.threerings.presents.dobj; + +/** + * Defines a special kind of subscriber that proxies events for a + * subordinate distributed object manager. All events dispatched on + * objects with which this subscriber is registered are passed along to + * the subscriber for delivery to its subordinate manager. + * + * @see DObject#addListener + */ +public interface ProxySubscriber extends Subscriber +{ + /** + * Called when any event has been dispatched on an object. + * + * @param event The event that was dispatched on the object. + */ + public void eventReceived (DEvent event); +} diff --git a/src/java/com/threerings/presents/server/PresentsClient.java b/src/java/com/threerings/presents/server/PresentsClient.java index 0c02d3951..05f9db327 100644 --- a/src/java/com/threerings/presents/server/PresentsClient.java +++ b/src/java/com/threerings/presents/server/PresentsClient.java @@ -1,5 +1,5 @@ // -// $Id: PresentsClient.java,v 1.53 2003/03/30 21:04:18 mdb Exp $ +// $Id: PresentsClient.java,v 1.54 2003/04/10 17:48:42 mdb Exp $ package com.threerings.presents.server; @@ -15,9 +15,8 @@ import com.threerings.presents.data.ClientObject; import com.threerings.presents.dobj.DEvent; import com.threerings.presents.dobj.DObject; -import com.threerings.presents.dobj.EventListener; import com.threerings.presents.dobj.ObjectAccessException; -import com.threerings.presents.dobj.Subscriber; +import com.threerings.presents.dobj.ProxySubscriber; import com.threerings.presents.net.BootstrapData; import com.threerings.presents.net.BootstrapNotification; @@ -54,8 +53,7 @@ import com.threerings.presents.server.net.MessageHandler; * conmgr thread and therefore also need not be synchronized. */ public class PresentsClient - implements Subscriber, EventListener, MessageHandler, - ClientResolutionListener + implements ProxySubscriber, MessageHandler, ClientResolutionListener { /** Used by {@link #setUsername} to report success or failure. */ public static interface UserChangeListener @@ -416,7 +414,6 @@ public class PresentsClient { DObject object = (DObject)_subscrips.remove(oid); if (object != null) { - object.removeListener(this); object.removeSubscriber(this); } else { Log.warning("Requested to unmap non-existent subscription " + @@ -435,7 +432,6 @@ public class PresentsClient DObject object = (DObject)enum.next(); // Log.info("Clearing subscription [client=" + this + // ", obj=" + object.getOid() + "]."); - object.removeListener(this); object.removeSubscriber(this); } } @@ -649,8 +645,6 @@ public class PresentsClient // queue up an object response Connection conn = getConnection(); if (conn != null) { - // add ourselves as an event listener - object.addListener(this); // pass the successful subscrip on to the client conn.postMessage(new ObjectResponse(object)); // make a note of this new subscription diff --git a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java index a261a63de..cd98b2946 100644 --- a/src/java/com/threerings/presents/server/PresentsDObjectMgr.java +++ b/src/java/com/threerings/presents/server/PresentsDObjectMgr.java @@ -1,5 +1,5 @@ // -// $Id: PresentsDObjectMgr.java,v 1.30 2003/03/31 04:11:24 mdb Exp $ +// $Id: PresentsDObjectMgr.java,v 1.31 2003/04/10 17:48:42 mdb Exp $ package com.threerings.presents.server; @@ -23,7 +23,7 @@ import com.threerings.presents.dobj.*; * server. By virtue of running on the server, it manages its objects * directly rather than managing proxies of objects which is what is done * on the client. Thus it simply queues up events and dispatches them to - * subscribers. + * listeners. * *
The server object manager is meant to run on the main thread of the * server application and thus provides a method to be invoked by the @@ -180,18 +180,9 @@ public class PresentsDObjectMgr } } 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)); - } + processCompoundEvent((CompoundEvent)unit); } else { - // otherwise it's a regular event, so do the standard - // processing processEvent((DEvent)unit); } @@ -217,9 +208,45 @@ public class PresentsDObjectMgr Log.info("DOMGR exited."); } + /** + * Performs the processing associated with a compound event, notifying + * listeners and the like. + */ + protected void processCompoundEvent (CompoundEvent event) + { + List events = event.getEvents(); + int ecount = events.size(); + + // look up the target object + DObject target = (DObject)_objects.get(event.getTargetOid()); + if (target == null) { + Log.debug("Compound event target no longer exists " + + "[event=" + event + "]."); + return; + } + + // check the permissions on all of the events + for (int ii = 0; ii < ecount; ii++) { + DEvent sevent = (DEvent)events.get(ii); + if (!target.checkPermissions(sevent)) { + Log.warning("Event failed permissions check " + + "[event=" + sevent + ", target=" + target + "]."); + return; + } + } + + // dispatch the events + for (int ii = 0; ii < ecount; ii++) { + dispatchEvent((DEvent)events.get(ii), target); + } + + // always notify proxies of compound events + target.notifyProxies(event); + } + /** * Performs the processing associated with an event, notifying - * subscribers and the like. + * listeners and the like. */ protected void processEvent (DEvent event) { @@ -244,22 +271,36 @@ public class PresentsDObjectMgr return; } + if (dispatchEvent(event, target)) { + // unless requested not to, notify any proxies + target.notifyProxies(event); + } + } + + /** + * Dispatches an event after the target object has been resolved and + * the permissions have been checked. This is used by {@link + * #processEvent} and {@link #processCompoundEvent}. + * + * @return the value returned by {@link DEvent#applyToObject}. + */ + protected boolean dispatchEvent (DEvent event, DObject target) + { + boolean notify = true; // assume always notify try { - // do any internal management necessary based on this - // event + // 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 }); + Object rv = helper.invoke(this, new Object[] { event, target }); // if helper returns false, we abort event processing if (!((Boolean)rv).booleanValue()) { - return; + return false; } } // everything's good so far, apply the event to the object - boolean notify = event.applyToObject(target); + notify = event.applyToObject(target); // if the event returns false from applyToObject, this // means it's a silent event and we shouldn't notify the @@ -276,6 +317,7 @@ public class PresentsDObjectMgr // track the number of events dispatched ++_eventCount; + return true; } /**