From e0aea87f626457d3fc1574869751aea1d8e71b8a Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Thu, 2 Oct 2008 00:47:06 +0000 Subject: [PATCH] Moved the Presents overview out of the javadocs. Added some simple top-level pages which give us something useful to link to when we want to point to all of the javadocs for a particular subtree of the Narya packages. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@5411 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- build.xml | 3 - .../presents/images/dobject.dia | Bin .../presents/images/dobject.png | Bin docs/presents/overview.html | 606 +++++++++++++++++ src/java/com/threerings/crowd/package.html | 50 ++ src/java/com/threerings/presents/package.html | 629 ++---------------- 6 files changed, 697 insertions(+), 591 deletions(-) rename {src/java/com/threerings => docs}/presents/images/dobject.dia (100%) rename {src/java/com/threerings => docs}/presents/images/dobject.png (100%) create mode 100644 docs/presents/overview.html create mode 100644 src/java/com/threerings/crowd/package.html diff --git a/build.xml b/build.xml index bf21bc3b3..da5c66cab 100644 --- a/build.xml +++ b/build.xml @@ -255,9 +255,6 @@ - - - diff --git a/src/java/com/threerings/presents/images/dobject.dia b/docs/presents/images/dobject.dia similarity index 100% rename from src/java/com/threerings/presents/images/dobject.dia rename to docs/presents/images/dobject.dia diff --git a/src/java/com/threerings/presents/images/dobject.png b/docs/presents/images/dobject.png similarity index 100% rename from src/java/com/threerings/presents/images/dobject.png rename to docs/presents/images/dobject.png diff --git a/docs/presents/overview.html b/docs/presents/overview.html new file mode 100644 index 000000000..d25e188a0 --- /dev/null +++ b/docs/presents/overview.html @@ -0,0 +1,606 @@ + + + + + + + + + +

Presents Distributed Object System

+ + The Presents Distributed Object System is a framework for distributing + information between multiple separate applications (over a network) and for + coordinating control flow between those applications in the form of remote + procedure call services. The normal configuration of the Presents system is + client/server; generally with many clients connecting to a single + server. All information transfer takes place through the server using the + distributed object system documented below. + + + +

A note to the reader: the Presents system is a complex + one and though a great deal of code is provided in explaining the + services it provides, it is not the intent that one should start from + only these examples and build a working system. A better approach is + to read through this documentation to come to an understanding of the + concepts and mechanisms that define the system and then take a look at + some working sample code which is provided in the tests + directory of this distribution. + +

Distributed Objects

+ + The Presents services allow applications to access and update shared + information through a mechanism known as distributed objects. + Distributed objects are maintainedon the server and clients + "subscribe" to the objects and are provided with proxy copies which + are updated by a stream of events sent by the server when any state + changes in the objects. + +

Clients cannot modify their proxy distributed objects directly, + instead they make use of setter methods which package up the requested + change into an event and send that event to the server for processing. + After performing access control checks, the server will apply the + event to the primary distributed object instance and then dispatch + that event to all subscribed clients. Those clients (including the + original change requesting client) then apply the event to their proxy + copy of the object and in this way all clients maintain an up to date + copy of the object's data. + +

+ +

Defining an object

+ + A distributed object is defined just like a regular Java object and is + then run through a post-processor which inserts methods and constants + into the object definition which are needed by the distributed object + system. Here is a distributed object as originally defined: + +
+    public class CageObject extends DObject
+    {
+        /** The number of monkeys in the cage. */
+        public int monkeys;
+
+        /** The name of the owner of this cage. */
+        public String owner;
+    }
+ + Note that all distributed fields, or attributes (fields in a + distributed object are frequently referred to as attributes in + this documentation and elsewhere in the system), are public fields in + our distributed object. Non-public fields will be ignored by the + system and not transmitted when a proxy object is delivered over the + network to a subscriber. Further, fields marked transient + will also be ignored by the system. + +

We then run our class definition through a post-processor which + turns it into the following: + +

+    public class CageObject extends DObject
+    {
+        // AUTO-GENERATED: FIELDS START
+        /** The field name of the monkeys field. */
+        public static final String MONKEYS = "monkeys";
+
+        /** The field name of the owner field. */
+        public static final String OWNER = "owner";
+        // AUTO-GENERATED: FIELDS END
+
+        /** The number of monkeys in the cage. */
+        public int monkeys;
+
+        /** The name of the owner of this cage. */
+        public String owner;
+
+        // AUTO-GENERATED: METHODS START
+        /**
+         * Requests that the monkeys field be set to the
+         * specified value. The local value will be updated immediately and an
+         * event will be propagated through the system to notify all listeners
+         * that the attribute did change. Proxied copies of this object (on
+         * clients) will apply the value change when they received the
+         * attribute changed notification.
+         */
+        public void setMonkeys (int value)
+        {
+            int ovalue = this.monkeys;
+            requestAttributeChange(
+                EVEN_BASE, new Integer(value), new Integer(ovalue));
+            this.monkeys = value;
+        }
+
+        /**
+         * Requests that the owner field be set to the
+         * specified value. The local value will be updated immediately and an
+         * event will be propagated through the system to notify all listeners
+         * that the attribute did change. Proxied copies of this object (on
+         * clients) will apply the value change when they received the
+         * attribute changed notification.
+         */
+        public void setOwner (String value)
+        {
+            String ovalue = this.owner;
+            requestAttributeChange(
+                ODD_BASE, value, ovalue);
+            this.owner = value;
+        }
+        // AUTO-GENERATED: METHODS END
+    }
+ + The contents of the methods are not too important, the main things to + note are that setter methods for the two attributes were generated and + constants were defined that will be used to identify which attribute + changed if we choose to inspect an event notifying us of such a + change. Note also that additional methods may be added to a + distributed object class as long as nothing is modified in the + AUTO-GENERATED section. As new fields are added and the + post-processing tool re-run, everything outside the auto-generated + section will be preserved. + +

One may also notice that attribute change requests result in the + new value of the attribute being immediately written to the local copy + of the object. This is a convention that was decided upon after + repeatedly running into trouble when users of the system would set a + value in an object and immediately assume it held the new value rather + than realizing that an event would have to propagate back from the + server before the value was in fact updated. By setting the value + immediately, these problems are avoided and the opposite assumption is + almost never made. This is further justified by the fact that, in + general, attribute changes never originate on a client but instead + originate on the server after processing a request from the client + (via the below documented invocation + services) to do something application-specific that results in one + or more attribute changes taking place. + +

See the section on Ant Tasks for + information on how to configure and run this post-processor. + +

Creating an object

+ + Generally, some entity on the server will choose to create a new + instance of a distributed object. Rather than simply instantiate the + object directly, one must create the object through the {@link + com.threerings.presents.dobj.DObjectManager}: + +
+    public class ServerEntity implements Subscriber {
+        public void init (DObjectManager omgr) {
+            omgr.createObject(CageObject.class, this);
+        }
+
+        // inherited from interface Subscriber
+        public void objectAvailable (DObject object) {
+            // yay! we created our object
+            _object = (CageObject)object;
+        }
+
+        // inherited from interface Subscriber
+        public void requestFailed (int oid, ObjectAccessException cause) {
+            // oh the humanity, we failed to create our object; in
+            // general this would only happen if we did something silly like
+            // passed in a DObject class that didn't extend DObject
+        }
+
+        protected CageObject _object;
+    }
+ + You'll notice that we provide an instance of a Subscriber + when creating our object. This subscriber instance is in fact + subscribed to the newly created object in the same manner as is + described below for all additional subscribers to the object. It is + possible to instruct an object to automatically destroy itself when + all subscribers have unsubscribed. (See the not very terse {@link + com.threerings.presents.dobj.DObject}.setDestroyOnLastSubscriberRemoved()). + +

Subscribing to an object

+ +

The client obtains a proxy of the object by a process called + subscription, which is accomplished via {@link + com.threerings.presents.dobj.DObjectManager}.subscribeToObject(): + +

+    public class ObjectUser implements Subscriber {
+        public void init (Client client, int objectId) {
+            client.getDObjectManager().subscribeToObject(objectId, this);
+        }
+
+        // inherited from interface Subscriber
+        public void objectAvailable (DObject object) {
+            // yay! we got our object
+            _object = (CageObject)object;
+        }
+
+        // inherited from interface Subscriber
+        public void requestFailed (int oid, ObjectAccessException cause) {
+            // oh the humanity, we failed to subscribe
+        }
+
+        protected CageObject _object;
+    }
+ +

Later a client would relinquish its subscription to the object + using a similar mechanism: + +

+    public class ObjectUser implements Subscriber {
+        // ...
+        public void shutdown (Client client) {
+            client.getDObjectManager().unsubscribeFromObject(
+                _object.getOid(), this);
+            _object = null;
+        }
+        // ...
+    }
+ + However, this is a fine time to point out the dangers of working in an + asynchronous distributed environment. There is no guarantee that your + object subscription request will be completed before the client + decides to call shutdown() on its ObjectUser. Thus, in + the previous code, we could get a null pointer exception, and even + worse, we could remain subscribed to the object even though we didn't + want to be. To avoid these sorts of problems, the {@link + com.threerings.presents.util.SafeSubscriber} class is provided: + +
+    public class ObjectUser implements Subscriber {
+        public void init (Client client, int objectId) {
+            _safesub = new SafeSubscriber(objectId, this);
+            _safesub.subcribe(client.getDObjectManager());
+        }
+
+        // inherited from interface Subscriber
+        public void objectAvailable (DObject object) {
+            // yay! we got our object
+            _object = (CageObject)object;
+        }
+
+        // inherited from interface Subscriber
+        public void requestFailed (int oid, ObjectAccessException cause) {
+            // oh the humanity, we failed to subscribe
+        }
+
+        public void shutdown (Client client) {
+            _safesub.unsubscribe(client.getDObjectManager());
+            _object = null;
+        }
+
+        protected SafeSubscriber _safesub;
+        protected CageObject _object;
+    }
+ + The safe subscriber will pass the object availability on to your + subscriber and when the time comes to unsubscribe, it will cope with + the case where the original subscription was not fully processed and + stick around long enough to ensure that once it is, the request to + unsubscribe is also dispatched. It will also cope with a request to + unsubscribe() even if the original subscription request + failed. + +

Event Listeners

+ + Once a client has subscribed to a distributed object, all events + pertaining to that object will be delivered to the client. Frequently, + it is useful to respond dynamically to changes in distributed object + values and this is accomplished using listeners. A client can register + any number of listeners on an object and when the object is finally + unsubscribed from and garbage collected, the listener registrations + all go away as well. + +

The basic listener is the {@link + com.threerings.presents.dobj.AttributeChangeListener} which is + informed of all simple attribute changes (setting a primitive field to + a new value is called an attribute change). We return to our trusty + example: + +

+    public class ObjectUser
+        implements Subscriber, AttributeChangeListener {
+        // ...
+        public void init (Client client, int objectId) {
+            _safesub = new SafeSubscriber(_subscriber, objectId);
+            _safesub.subcribe(client.getDObjectManager());
+        }
+
+        // inherited from interface Subscriber
+        public void objectAvailable (DObject object) {
+            // yay! we got our object
+            _object = (CageObject)object;
+            _object.addListener(this);
+        }
+
+        // inherited from interface Subscriber
+        public void requestFailed (int oid, ObjectAccessException cause) {
+            // oh the humanity, we failed to subscribe
+        }
+
+        // inherited from interface AttributeChangeListener
+        public void attributeChanged (AttributeChangedEvent event)
+        {
+            System.out.println("Wow! The " + event.getName() +
+                               " field changed to " + event.getValue() + ".");
+        }
+
+        public void shutdown (Client client) {
+            _safesub.unsubscribe(client.getDObjectManager());
+            if (_object != null) {
+                 // removing our listener not necessary as we are
+                 // unsubscribing, but it's a good habit to develop as
+                 // frequently listeners will come and go during the
+                 // lifetime of an object subscription
+                _object.removeListener(this);
+                _object = null;
+            }
+        }
+
+        protected SafeSubscriber _safesub;
+        protected CageObject _object;
+    }
+ + The attributeChanged() method of our registered listener + will be called whenever an event is received as a result of one of the + setter methods being called on the CageObject by + any participant in the distributed system. The setter creates + an event which is sent to the server, the server dispatches the event + to all subscribers of the object and the Presents system dispatches + the event notification to all registered listeners when the event is + received on the client. Note that listeners are also used on the + server as entities on the server also frequently need to respond to + attribute changes. They are notified immediately after the server has + dispatched the event (over the network) to all subscribed clients. + +

It is useful to note that listeners are notified of a changed + attribute after the change has been applied to the object. The + previous value of the attribute is available through the {@link + com.threerings.presents.dobj.AttributeChangedEvent#getOldValue} + method, though in spite of many years of experience using this system + in a variety of circumstances, we have rarely found that we cared to + know the previous value. + +

Distributed collections

+ + One soon discovers that primitive object fields do not make for a very + useful information distribution mechanism and that more complex data + structures are necessary. Two collection types, sets and arrays, are + supported, and a mechanism is provided for allowing whole objects to + be passed around in toto as if they were a primitive field. + +

Distributed Arrays
+ + Arrays of primitive types can be used in a distributed object and the + system will detect their use and provide a mechanism for updating the + entire array and an additional mechanism for updating a single element + at a time: + +

+    public class ChessObject extends DObject
+    {
+        // AUTO-GENERATED: FIELDS START
+        /** The field name of the state field. */
+        public static final String STATE = "state";
+        // AUTO-GENERATED: FIELDS END
+
+        /** Used to track our board state. */
+        public int[] state;
+
+        // AUTO-GENERATED: METHODS START
+        /**
+         * Requests that the state field be set to the
+         * specified value. The local value will be updated immediately and an
+         * event will be propagated through the system to notify all listeners
+         * that the attribute did change. Proxied copies of this object (on
+         * clients) will apply the value change when they received the
+         * attribute changed notification.
+         */
+        public void setState (int[] value)
+        {
+            int[] ovalue = this.state;
+            requestAttributeChange(
+                STATE, value, ovalue);
+            this.state = (value == null) ? null : (int[])value.clone();
+        }
+
+        /**
+         * Requests that the indexth element of
+         * state field be set to the specified value.
+         * The local value will be updated immediately and an event will be
+         * propagated through the system to notify all listeners that the
+         * attribute did change. Proxied copies of this object (on clients)
+         * will apply the value change when they received the attribute
+         * changed notification.
+         */
+        public void setStateAt (int value, int index)
+        {
+            int ovalue = this.state[index];
+            requestElementUpdate(
+                STATE, index, new Integer(value), new Integer(ovalue));
+            this.state[index] = value;
+        }
+        // AUTO-GENERATED: METHODS END
+    }
+ + To correspond with what is called an "element update" (the + modification of a single element in an array), there is the {@link + com.threerings.presents.dobj.ElementUpdateListener}. When an element + is updated, listeners implementing that interface will be notified. + Remember that if the whole array is changed using + setState(), the normal {@link + com.threerings.presents.dobj.AttributeChangeListener} is the interface + one uses to hear about it. + +

Note that distributed arrays are not + automatically resized. If a request is made to update the element at + index 9 of an array, the array must be of at least size 10 or an array + index out of bounds exception will be thrown (as should be evident + from inspecting the code above). For more dynamic collections of + objects, see the documentation below about distributed sets. + +

This mechanism is not actually limited to arrays of primitive + types. It also works for arrays of objects that implement the {@link + com.threerings.io.Streamable} interface which is documented next. + +

Streamable and its good friend SimpleStreamableObject
+ + The {@link com.threerings.io.Streamable} interface is used to mark + objects that can be sent over the network by using them in distributed + object fields by using arrays of such objects as a field. This + interface functions in much the same way that {@link + java.io.Serializable} does in that it simply marks the class and an + underlying mechanism uses reflection to actually marshall and + unmarshall the object on the network. In fact, all + non-transient fields of a streamable object are included + during the marhsalling process. Here's an example: + +

+    public class Player implements Streamable
+    {
+        /** This player's name. */
+        public String name;
+
+        /** This player's rating. */
+        public int rating;
+    }
+
+    public class ChessObject extends DObject
+    {
+        /** A record for each player in the game. */
+        public Player[] players;
+    }
+ + The generated methods are ommitted for the sake of brevity, but as you + would expect, both a setPlayers(Player[] value) and a + setPlayersAt(Player value, int index) method will be + generated and do just what you expect. + +

It should be pointed out that streamable objects sent over the + network are sent in their entirety. No mechanism is provided for + updating just a single field in a streamable instance both because + that would increase the complexity of the system tremendously and + because it is generally not very useful. If conservation of bandwidth + is of extreme importance, special {@link + com.threerings.presents.dobj.DEvent} derived classes can be created to + transmit precisely what is desired and nothing more. Doing so is + beyond the scope of this introduction, but will hopefully be covered + in an additional tutorial. + +

The {@link com.threerings.io.SimpleStreamableObject} class is a + convenient way to create a simple record like the Player + record above that implements Streamable and provides a + default implementation of toString() that uses reflection + to print out the actual values of the fields in the object (a boon + when logging and debugging). + +

Distributed Sets
+ + In developing a distributed system, one frequently encounters + situations where one wants distributed collection of objects where + order is generally not important but the ability to fluidly add and + remove elements is. For such occasions we provide the distributed set + or {@link com.threerings.presents.dobj.DSet}. + +

A DSet contains entries (called entries rather than + elements to avoid confusion with array "element updating") which must + implement the {@link com.threerings.presents.dobj.DSet.Entry} + interface. This automatically makes them {@link + com.threerings.io.Streamable} and requires that they provide a {@link + java.lang.Comparable} key which is used to distinguish them from other + entries in the set (and look them up via an efficient binary search). + +

When using a DSet one is provided with three new + operations: addToFoo(), updateFoo() and + removeFromFoo(). Once again an example is in order: + +

+    public class Monkey implements DSet.Entry
+    {
+        /** The monkey's name. */
+        public String name;
+
+        /** The monkey's age. */
+        public int age;
+
+        // documentation inherited from interface DSet.Entry
+        public Comparable getKey ()
+        {
+            return name;
+        }
+    }
+
+    public class CageObject extends DObject
+    {
+        // AUTO-GENERATED: FIELDS START
+        /** The field name of the monkeys field. */
+        public static final String MONKEYS = "monkeys";
+        // AUTO-GENERATED: FIELDS END
+
+        /** A collection of monkeys. */
+        public DSet monkeys;
+
+        // AUTO-GENERATED: METHODS START
+        /**
+         * Requests that the specified entry be added to the
+         * monkeys set.
+         */
+        public void addToMonkeys (DSet.Entry elem)
+        {
+            requestEntryAdd(MONKEYS, monkeys, elem);
+        }
+
+        /**
+         * Requests that the entry matching the supplied key be removed from
+         * the monkeys set.
+         */
+        public void removeFromMonkeys (Comparable key)
+        {
+            requestEntryRemove(MONKEYS, monkeys, key);
+        }
+
+        /**
+         * Requests that the specified entry be updated in the
+         * monkeys set.
+         */
+        public void updateMonkeys (DSet.Entry elem)
+        {
+            requestEntryUpdate(MONKEYS, monkeys, elem);
+        }
+
+        /**
+         * Requests that the monkeys field be set to the
+         * specified value.
+         */
+        public void setMonkeys (DSet value)
+        {
+            requestAttributeChange(MONKEYS, value, this.monkeys);
+            this.monkeys = (value == null) ? null : (DSet)value.clone();
+        }
+        // AUTO-GENERATED: METHODS END
+    }
+ + It is possible to set the entire set (which is necessary to establish + its original value even if one decides to set it to the empty set), + but more commonly one will simply add entries to the set, update those + entries and remove them using the provided methods. + +

In conjunction with the DSet there exists the {@link + com.threerings.presents.dobj.SetListener} which is notified when + changes are made to a distributed set. This functions in the same was + as the previously documented listeners, so I will refrain from boring + you with yet more sample code. + +

Invocation Services

+ + TBD + +

Ant Tasks

+ + TBD + + + diff --git a/src/java/com/threerings/crowd/package.html b/src/java/com/threerings/crowd/package.html new file mode 100644 index 000000000..f47007851 --- /dev/null +++ b/src/java/com/threerings/crowd/package.html @@ -0,0 +1,50 @@ + + + + + + + + + Builds on the Presents framework to provide services uesful for chatting + and moving from place to place in a distributed space. + +

The main Crowd concepts are that of Body and Place and the notion of a + PlaceManager on the server and a pairing of PlaceController and PlaceView + on the client.

+ + + +

Crowd also provides a mechanism for Chat in Places and between + individuals that are logged on.

+ + + +

Crowd also integrates with the Presents peer system to make its Chat + services work in a peer environment.

+ + + + + diff --git a/src/java/com/threerings/presents/package.html b/src/java/com/threerings/presents/package.html index 0aa08dbe1..dfcb3dced 100644 --- a/src/java/com/threerings/presents/package.html +++ b/src/java/com/threerings/presents/package.html @@ -6,598 +6,51 @@ - Defines a framework for distributing information between multiple - separate applications (over a network) and for coordinating control - flow between those applications in the form of remote procedure call - services. The normal configuration of the Presents system is - client/server; generally with many clients connecting to a single - server. All information transfer takes place through the server using - the distributed object system documented below. + A framework for sharing data between network-connected applications and a + remote procedure call mechanism. + + The normal configuration of the Presents system is client/server; generally + with many clients connecting to a single server though a system exists to + allow a network of peer servers to communicate with one another . All + information transfer takes place through the server using the distributed + object system documented below. + +

For libraries that are build on the distributed object services as well + as most code in a system using the distributed object services, everything + you need are in these packages:

-

A note to the reader: the Presents system is a complex - one and though a great deal of code is provided in explaining the - services it provides, it is not the intent that one should start from - only these examples and build a working system. A better approach is - to read through this documentation to come to an understanding of the - concepts and mechanisms that define the system and then take a look at - some working sample code which is provided in the tests - directory of this distribution. - -

Distributed Objects

- - The Presents services allow applications to access and update shared - information through a mechanism known as distributed objects. - Distributed objects are maintainedon the server and clients - "subscribe" to the objects and are provided with proxy copies which - are updated by a stream of events sent by the server when any state - changes in the objects. - -

Clients cannot modify their proxy distributed objects directly, - instead they make use of setter methods which package up the requested - change into an event and send that event to the server for processing. - After performing access control checks, the server will apply the - event to the primary distributed object instance and then dispatch - that event to all subscribed clients. Those clients (including the - original change requesting client) then apply the event to their proxy - copy of the object and in this way all clients maintain an up to date - copy of the object's data. - -

- -

Defining an object

- - A distributed object is defined just like a regular Java object and is - then run through a post-processor which inserts methods and constants - into the object definition which are needed by the distributed object - system. Here is a distributed object as originally defined: - -
-    public class CageObject extends DObject
-    {
-        /** The number of monkeys in the cage. */
-        public int monkeys;
-
-        /** The name of the owner of this cage. */
-        public String owner;
-    }
- - Note that all distributed fields, or attributes (fields in a - distributed object are frequently referred to as attributes in - this documentation and elsewhere in the system), are public fields in - our distributed object. Non-public fields will be ignored by the - system and not transmitted when a proxy object is delivered over the - network to a subscriber. Further, fields marked transient - will also be ignored by the system. - -

We then run our class definition through a post-processor which - turns it into the following: - -

-    public class CageObject extends DObject
-    {
-        // AUTO-GENERATED: FIELDS START
-        /** The field name of the monkeys field. */
-        public static final String MONKEYS = "monkeys";
-
-        /** The field name of the owner field. */
-        public static final String OWNER = "owner";
-        // AUTO-GENERATED: FIELDS END
-
-        /** The number of monkeys in the cage. */
-        public int monkeys;
-
-        /** The name of the owner of this cage. */
-        public String owner;
-
-        // AUTO-GENERATED: METHODS START
-        /**
-         * Requests that the monkeys field be set to the
-         * specified value. The local value will be updated immediately and an
-         * event will be propagated through the system to notify all listeners
-         * that the attribute did change. Proxied copies of this object (on
-         * clients) will apply the value change when they received the
-         * attribute changed notification.
-         */
-        public void setMonkeys (int value)
-        {
-            int ovalue = this.monkeys;
-            requestAttributeChange(
-                EVEN_BASE, new Integer(value), new Integer(ovalue));
-            this.monkeys = value;
-        }
-
-        /**
-         * Requests that the owner field be set to the
-         * specified value. The local value will be updated immediately and an
-         * event will be propagated through the system to notify all listeners
-         * that the attribute did change. Proxied copies of this object (on
-         * clients) will apply the value change when they received the
-         * attribute changed notification.
-         */
-        public void setOwner (String value)
-        {
-            String ovalue = this.owner;
-            requestAttributeChange(
-                ODD_BASE, value, ovalue);
-            this.owner = value;
-        }
-        // AUTO-GENERATED: METHODS END
-    }
- - The contents of the methods are not too important, the main things to - note are that setter methods for the two attributes were generated and - constants were defined that will be used to identify which attribute - changed if we choose to inspect an event notifying us of such a - change. Note also that additional methods may be added to a - distributed object class as long as nothing is modified in the - AUTO-GENERATED section. As new fields are added and the - post-processing tool re-run, everything outside the auto-generated - section will be preserved. - -

One may also notice that attribute change requests result in the - new value of the attribute being immediately written to the local copy - of the object. This is a convention that was decided upon after - repeatedly running into trouble when users of the system would set a - value in an object and immediately assume it held the new value rather - than realizing that an event would have to propagate back from the - server before the value was in fact updated. By setting the value - immediately, these problems are avoided and the opposite assumption is - almost never made. This is further justified by the fact that, in - general, attribute changes never originate on a client but instead - originate on the server after processing a request from the client - (via the below documented invocation - services) to do something application-specific that results in one - or more attribute changes taking place. - -

See the section on Ant Tasks for - information on how to configure and run this post-processor. - -

Creating an object

- - Generally, some entity on the server will choose to create a new - instance of a distributed object. Rather than simply instantiate the - object directly, one must create the object through the {@link - com.threerings.presents.dobj.DObjectManager}: - -
-    public class ServerEntity implements Subscriber {
-        public void init (DObjectManager omgr) {
-            omgr.createObject(CageObject.class, this);
-        }
-
-        // inherited from interface Subscriber
-        public void objectAvailable (DObject object) {
-            // yay! we created our object
-            _object = (CageObject)object;
-        }
-
-        // inherited from interface Subscriber
-        public void requestFailed (int oid, ObjectAccessException cause) {
-            // oh the humanity, we failed to create our object; in
-            // general this would only happen if we did something silly like
-            // passed in a DObject class that didn't extend DObject
-        }
-
-        protected CageObject _object;
-    }
- - You'll notice that we provide an instance of a Subscriber - when creating our object. This subscriber instance is in fact - subscribed to the newly created object in the same manner as is - described below for all additional subscribers to the object. It is - possible to instruct an object to automatically destroy itself when - all subscribers have unsubscribed. (See the not very terse {@link - com.threerings.presents.dobj.DObject}.setDestroyOnLastSubscriberRemoved()). - -

Subscribing to an object

- -

The client obtains a proxy of the object by a process called - subscription, which is accomplished via {@link - com.threerings.presents.dobj.DObjectManager}.subscribeToObject(): - -

-    public class ObjectUser implements Subscriber {
-        public void init (Client client, int objectId) {
-            client.getDObjectManager().subscribeToObject(objectId, this);
-        }
-
-        // inherited from interface Subscriber
-        public void objectAvailable (DObject object) {
-            // yay! we got our object
-            _object = (CageObject)object;
-        }
-
-        // inherited from interface Subscriber
-        public void requestFailed (int oid, ObjectAccessException cause) {
-            // oh the humanity, we failed to subscribe
-        }
-
-        protected CageObject _object;
-    }
- -

Later a client would relinquish its subscription to the object - using a similar mechanism: - -

-    public class ObjectUser implements Subscriber {
-        // ...
-        public void shutdown (Client client) {
-            client.getDObjectManager().unsubscribeFromObject(
-                _object.getOid(), this);
-            _object = null;
-        }
-        // ...
-    }
- - However, this is a fine time to point out the dangers of working in an - asynchronous distributed environment. There is no guarantee that your - object subscription request will be completed before the client - decides to call shutdown() on its ObjectUser. Thus, in - the previous code, we could get a null pointer exception, and even - worse, we could remain subscribed to the object even though we didn't - want to be. To avoid these sorts of problems, the {@link - com.threerings.presents.util.SafeSubscriber} class is provided: - -
-    public class ObjectUser implements Subscriber {
-        public void init (Client client, int objectId) {
-            _safesub = new SafeSubscriber(objectId, this);
-            _safesub.subcribe(client.getDObjectManager());
-        }
-
-        // inherited from interface Subscriber
-        public void objectAvailable (DObject object) {
-            // yay! we got our object
-            _object = (CageObject)object;
-        }
-
-        // inherited from interface Subscriber
-        public void requestFailed (int oid, ObjectAccessException cause) {
-            // oh the humanity, we failed to subscribe
-        }
-
-        public void shutdown (Client client) {
-            _safesub.unsubscribe(client.getDObjectManager());
-            _object = null;
-        }
-
-        protected SafeSubscriber _safesub;
-        protected CageObject _object;
-    }
- - The safe subscriber will pass the object availability on to your - subscriber and when the time comes to unsubscribe, it will cope with - the case where the original subscription was not fully processed and - stick around long enough to ensure that once it is, the request to - unsubscribe is also dispatched. It will also cope with a request to - unsubscribe() even if the original subscription request - failed. - -

Event Listeners

- - Once a client has subscribed to a distributed object, all events - pertaining to that object will be delivered to the client. Frequently, - it is useful to respond dynamically to changes in distributed object - values and this is accomplished using listeners. A client can register - any number of listeners on an object and when the object is finally - unsubscribed from and garbage collected, the listener registrations - all go away as well. - -

The basic listener is the {@link - com.threerings.presents.dobj.AttributeChangeListener} which is - informed of all simple attribute changes (setting a primitive field to - a new value is called an attribute change). We return to our trusty - example: - -

-    public class ObjectUser
-        implements Subscriber, AttributeChangeListener {
-        // ...
-        public void init (Client client, int objectId) {
-            _safesub = new SafeSubscriber(_subscriber, objectId);
-            _safesub.subcribe(client.getDObjectManager());
-        }
-
-        // inherited from interface Subscriber
-        public void objectAvailable (DObject object) {
-            // yay! we got our object
-            _object = (CageObject)object;
-            _object.addListener(this);
-        }
-
-        // inherited from interface Subscriber
-        public void requestFailed (int oid, ObjectAccessException cause) {
-            // oh the humanity, we failed to subscribe
-        }
-
-        // inherited from interface AttributeChangeListener
-        public void attributeChanged (AttributeChangedEvent event)
-        {
-            System.out.println("Wow! The " + event.getName() +
-                               " field changed to " + event.getValue() + ".");
-        }
-
-        public void shutdown (Client client) {
-            _safesub.unsubscribe(client.getDObjectManager());
-            if (_object != null) {
-                 // removing our listener not necessary as we are
-                 // unsubscribing, but it's a good habit to develop as
-                 // frequently listeners will come and go during the
-                 // lifetime of an object subscription
-                _object.removeListener(this);
-                _object = null;
-            }
-        }
-
-        protected SafeSubscriber _safesub;
-        protected CageObject _object;
-    }
- - The attributeChanged() method of our registered listener - will be called whenever an event is received as a result of one of the - setter methods being called on the CageObject by - any participant in the distributed system. The setter creates - an event which is sent to the server, the server dispatches the event - to all subscribers of the object and the Presents system dispatches - the event notification to all registered listeners when the event is - received on the client. Note that listeners are also used on the - server as entities on the server also frequently need to respond to - attribute changes. They are notified immediately after the server has - dispatched the event (over the network) to all subscribed clients. - -

It is useful to note that listeners are notified of a changed - attribute after the change has been applied to the object. The - previous value of the attribute is available through the {@link - com.threerings.presents.dobj.AttributeChangedEvent#getOldValue} - method, though in spite of many years of experience using this system - in a variety of circumstances, we have rarely found that we cared to - know the previous value. - -

Distributed collections

- - One soon discovers that primitive object fields do not make for a very - useful information distribution mechanism and that more complex data - structures are necessary. Two collection types, sets and arrays, are - supported, and a mechanism is provided for allowing whole objects to - be passed around in toto as if they were a primitive field. - -

Distributed Arrays
- - Arrays of primitive types can be used in a distributed object and the - system will detect their use and provide a mechanism for updating the - entire array and an additional mechanism for updating a single element - at a time: - -

-    public class ChessObject extends DObject
-    {
-        // AUTO-GENERATED: FIELDS START
-        /** The field name of the state field. */
-        public static final String STATE = "state";
-        // AUTO-GENERATED: FIELDS END
-
-        /** Used to track our board state. */
-        public int[] state;
-
-        // AUTO-GENERATED: METHODS START
-        /**
-         * Requests that the state field be set to the
-         * specified value. The local value will be updated immediately and an
-         * event will be propagated through the system to notify all listeners
-         * that the attribute did change. Proxied copies of this object (on
-         * clients) will apply the value change when they received the
-         * attribute changed notification.
-         */
-        public void setState (int[] value)
-        {
-            int[] ovalue = this.state;
-            requestAttributeChange(
-                STATE, value, ovalue);
-            this.state = (value == null) ? null : (int[])value.clone();
-        }
-
-        /**
-         * Requests that the indexth element of
-         * state field be set to the specified value.
-         * The local value will be updated immediately and an event will be
-         * propagated through the system to notify all listeners that the
-         * attribute did change. Proxied copies of this object (on clients)
-         * will apply the value change when they received the attribute
-         * changed notification.
-         */
-        public void setStateAt (int value, int index)
-        {
-            int ovalue = this.state[index];
-            requestElementUpdate(
-                STATE, index, new Integer(value), new Integer(ovalue));
-            this.state[index] = value;
-        }
-        // AUTO-GENERATED: METHODS END
-    }
- - To correspond with what is called an "element update" (the - modification of a single element in an array), there is the {@link - com.threerings.presents.dobj.ElementUpdateListener}. When an element - is updated, listeners implementing that interface will be notified. - Remember that if the whole array is changed using - setState(), the normal {@link - com.threerings.presents.dobj.AttributeChangeListener} is the interface - one uses to hear about it. - -

Note that distributed arrays are not - automatically resized. If a request is made to update the element at - index 9 of an array, the array must be of at least size 10 or an array - index out of bounds exception will be thrown (as should be evident - from inspecting the code above). For more dynamic collections of - objects, see the documentation below about distributed sets. - -

This mechanism is not actually limited to arrays of primitive - types. It also works for arrays of objects that implement the {@link - com.threerings.io.Streamable} interface which is documented next. - -

Streamable and its good friend SimpleStreamableObject
- - The {@link com.threerings.io.Streamable} interface is used to mark - objects that can be sent over the network by using them in distributed - object fields by using arrays of such objects as a field. This - interface functions in much the same way that {@link - java.io.Serializable} does in that it simply marks the class and an - underlying mechanism uses reflection to actually marshall and - unmarshall the object on the network. In fact, all - non-transient fields of a streamable object are included - during the marhsalling process. Here's an example: - -

-    public class Player implements Streamable
-    {
-        /** This player's name. */
-        public String name;
-
-        /** This player's rating. */
-        public int rating;
-    }
-
-    public class ChessObject extends DObject
-    {
-        /** A record for each player in the game. */
-        public Player[] players;
-    }
- - The generated methods are ommitted for the sake of brevity, but as you - would expect, both a setPlayers(Player[] value) and a - setPlayersAt(Player value, int index) method will be - generated and do just what you expect. - -

It should be pointed out that streamable objects sent over the - network are sent in their entirety. No mechanism is provided for - updating just a single field in a streamable instance both because - that would increase the complexity of the system tremendously and - because it is generally not very useful. If conservation of bandwidth - is of extreme importance, special {@link - com.threerings.presents.dobj.DEvent} derived classes can be created to - transmit precisely what is desired and nothing more. Doing so is - beyond the scope of this introduction, but will hopefully be covered - in an additional tutorial. - -

The {@link com.threerings.io.SimpleStreamableObject} class is a - convenient way to create a simple record like the Player - record above that implements Streamable and provides a - default implementation of toString() that uses reflection - to print out the actual values of the fields in the object (a boon - when logging and debugging). - -

Distributed Sets
- - In developing a distributed system, one frequently encounters - situations where one wants distributed collection of objects where - order is generally not important but the ability to fluidly add and - remove elements is. For such occasions we provide the distributed set - or {@link com.threerings.presents.dobj.DSet}. - -

A DSet contains entries (called entries rather than - elements to avoid confusion with array "element updating") which must - implement the {@link com.threerings.presents.dobj.DSet.Entry} - interface. This automatically makes them {@link - com.threerings.io.Streamable} and requires that they provide a {@link - java.lang.Comparable} key which is used to distinguish them from other - entries in the set (and look them up via an efficient binary search). - -

When using a DSet one is provided with three new - operations: addToFoo(), updateFoo() and - removeFromFoo(). Once again an example is in order: - -

-    public class Monkey implements DSet.Entry
-    {
-        /** The monkey's name. */
-        public String name;
-
-        /** The monkey's age. */
-        public int age;
-
-        // documentation inherited from interface DSet.Entry
-        public Comparable getKey ()
-        {
-            return name;
-        }
-    }
-
-    public class CageObject extends DObject
-    {
-        // AUTO-GENERATED: FIELDS START
-        /** The field name of the monkeys field. */
-        public static final String MONKEYS = "monkeys";
-        // AUTO-GENERATED: FIELDS END
-
-        /** A collection of monkeys. */
-        public DSet monkeys;
-
-        // AUTO-GENERATED: METHODS START
-        /**
-         * Requests that the specified entry be added to the
-         * monkeys set.
-         */
-        public void addToMonkeys (DSet.Entry elem)
-        {
-            requestEntryAdd(MONKEYS, monkeys, elem);
-        }
-
-        /**
-         * Requests that the entry matching the supplied key be removed from
-         * the monkeys set.
-         */
-        public void removeFromMonkeys (Comparable key)
-        {
-            requestEntryRemove(MONKEYS, monkeys, key);
-        }
-
-        /**
-         * Requests that the specified entry be updated in the
-         * monkeys set.
-         */
-        public void updateMonkeys (DSet.Entry elem)
-        {
-            requestEntryUpdate(MONKEYS, monkeys, elem);
-        }
-
-        /**
-         * Requests that the monkeys field be set to the
-         * specified value.
-         */
-        public void setMonkeys (DSet value)
-        {
-            requestAttributeChange(MONKEYS, value, this.monkeys);
-            this.monkeys = (value == null) ? null : (DSet)value.clone();
-        }
-        // AUTO-GENERATED: METHODS END
-    }
- - It is possible to set the entire set (which is necessary to establish - its original value even if one decides to set it to the empty set), - but more commonly one will simply add entries to the set, update those - entries and remove them using the provided methods. - -

In conjunction with the DSet there exists the {@link - com.threerings.presents.dobj.SetListener} which is notified when - changes are made to a distributed set. This functions in the same was - as the previously documented listeners, so I will refrain from boring - you with yet more sample code. - -

Invocation Services

- - TBD - -

Ant Tasks

- - TBD +

Systems making use of the distributed object services will need to set + up a client and server. The following packages contain those services and + their implementation:

+ + + +

Systems that need to scale beyond a single server will need to make use + of services in the peer package which provides a mechanism by which + multiple servers can operate in concert.