From ae5105185c75fb94841995867120fbb46e9775db Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Thu, 27 Jan 2005 04:16:45 +0000 Subject: [PATCH] More progress on our glorious Presents documentation. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3314 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- docs/stylesheet.css | 4 + src/java/com/threerings/presents/package.html | 319 ++++++++++++++++-- 2 files changed, 304 insertions(+), 19 deletions(-) diff --git a/docs/stylesheet.css b/docs/stylesheet.css index 47ae720bd..3f0592b81 100644 --- a/docs/stylesheet.css +++ b/docs/stylesheet.css @@ -10,6 +10,10 @@ pre { font-family: Courier New, monospace } +code { + font-family: Courier New, monospace +} + /* Headings */ h1 { font-size: 145% } diff --git a/src/java/com/threerings/presents/package.html b/src/java/com/threerings/presents/package.html index a80c62d4b..2518df377 100644 --- a/src/java/com/threerings/presents/package.html +++ b/src/java/com/threerings/presents/package.html @@ -14,6 +14,14 @@ 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 @@ -23,7 +31,7 @@ some working sample code which is provided in the tests directory of this distribution. -

Distributed Objects

+

Distributed Objects

The Presents services allow applications to access and update shared information through a mechanism known as distributed objects. @@ -61,8 +69,16 @@ public String owner; } - This class definition is then run through a post-processor which turns - it into the following: + 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
@@ -117,13 +133,29 @@
     }
The contents of the methods are not too important, the main things to - note are that setter methods for the two attributes (fields in a - distributed object are referred to as attributes in this - documentation and elsewhere in the system) were generated and + 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. +

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 @@ -158,7 +190,7 @@ 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 {@link + all subscribers have unsubscribed. (See the not very terse {@link com.threerings.presents.dobj.DObject}.setDestroyOnLastSubscriberRemoved()).

Subscribing to an object

@@ -205,8 +237,8 @@ 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 would get a null pointer exception, and even - worse, we would remain subscribed to the object even though we didn't + 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: @@ -220,7 +252,7 @@ // inherited from interface Subscriber public void objectAvailable (DObject object) { // yay! we got our object - _object = object; + _object = (CageObject)object; } // inherited from interface Subscriber @@ -234,7 +266,7 @@ } protected SafeSubscriber _safesub; - protected DObject _object; + protected CageObject _object; } The safe subscriber will pass the object availability on to your @@ -245,7 +277,7 @@ unsubscribe() even if the original subscription request failed. -

Listeners

+

Event Listeners

Once a client has subscribed to a distributed object, all events pertaining to that object will be delivered to the client. Frequently, @@ -258,8 +290,8 @@

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 in this distributed object - system). We return to our trusty example: + a new value is called an attribute change). We return to our trusty + example:

     public class ObjectUser
@@ -273,7 +305,7 @@
         // inherited from interface Subscriber
         public void objectAvailable (DObject object) {
             // yay! we got our object
-            _object = object;
+            _object = (CageObject)object;
             _object.addListener(this);
         }
 
@@ -302,10 +334,22 @@
         }
 
         protected SafeSubscriber _safesub;
-        protected DObject _object;
+        protected CageObject _object;
     }
- It is useful to note that listeners are notified of a changed + 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} @@ -313,9 +357,246 @@ in a variety of circumstances, we have rarely found that we cared to know the previous value. -

Distributed collections

+

Distributed collections

-

Invocation Services

+ 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: addEntry(), updateEntry() and + removeEntry(). 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. The set will not change until the event is
+         * actually propagated through the system.
+         */
+        public void addToMonkeys (DSet.Entry elem)
+        {
+            requestEntryAdd(MONKEYS, monkeys, elem);
+        }
+
+        /**
+         * Requests that the entry matching the supplied key be removed from
+         * the monkeys set. The set will not change until the
+         * event is actually propagated through the system.
+         */
+        public void removeFromMonkeys (Comparable key)
+        {
+            requestEntryRemove(MONKEYS, monkeys, key);
+        }
+
+        /**
+         * Requests that the specified entry be updated in the
+         * monkeys set. The set will not change until the event is
+         * actually propagated through the system.
+         */
+        public void updateMonkeys (DSet.Entry elem)
+        {
+            requestEntryUpdate(MONKEYS, monkeys, elem);
+        }
+
+        /**
+         * Requests that the monkeys field be set to the
+         * specified value. Generally one only adds, updates and removes
+         * entries of a distributed set, but certain situations call for a
+         * complete replacement of the set 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 (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. + +

Invocation Services

+ + TBD + +

Ant Tasks

+ + TBD