Finally got around to making compound events actually stick together until

they arrive at the client. Mmm... network efficiency++.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@2396 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2003-04-10 17:48:42 +00:00
parent 2a554f1569
commit 36624d96ac
6 changed files with 177 additions and 144 deletions
@@ -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) {
@@ -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;
@@ -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.
*
* <p> 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.
* <p> 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. <em>Note:</em> on the server, object
* subscription is merely a formality as all objects remain live all the
* time, so <em>do not</em> 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.
*
* <p> 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.
*
* <p> 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.
*
* <p> Note that distributed object fields can only be of a limited set of
* supported types. These types are:
* <p> Note that distributed object fields can be any of the following set
* of primitive types:
*
* <code><pre>
* 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[]
* </pre></code>
*
* 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
*
* <p> 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.
*
* <p> 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.
*
* <p> 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.
*
* <p> 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
}
/**
@@ -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);
}
@@ -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
@@ -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.
*
* <p> 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;
}
/**