diff --git a/src/java/com/threerings/presents/dobj/DObject.java b/src/java/com/threerings/presents/dobj/DObject.java index 290943895..c160181ff 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.23 2001/08/15 18:38:54 mdb Exp $ +// $Id: DObject.java,v 1.24 2001/08/16 03:31:08 mdb Exp $ package com.threerings.cocktail.cher.dobj; @@ -412,6 +412,28 @@ public class DObject _mgr.postEvent(event); } + /** + * Calls by derived instances when a set adder method was called. + */ + protected void requestElementAdd (String name, DSet.Element elem) + { + // generate an element added event + DEvent event = new ElementAddedEvent(_oid, name, elem); + // and dispatch it to our dobjmgr + _mgr.postEvent(event); + } + + /** + * Calls by derived instances when a set remover method was called. + */ + protected void requestElementRemove (String name, Object key) + { + // generate an element removed event + DEvent event = new ElementRemovedEvent(_oid, name, key); + // and dispatch it to our dobjmgr + _mgr.postEvent(event); + } + /** Our object id. */ protected int _oid; diff --git a/src/java/com/threerings/presents/dobj/DSet.java b/src/java/com/threerings/presents/dobj/DSet.java new file mode 100644 index 000000000..1965587b8 --- /dev/null +++ b/src/java/com/threerings/presents/dobj/DSet.java @@ -0,0 +1,224 @@ +// +// $Id: DSet.java,v 1.1 2001/08/16 03:31:09 mdb Exp $ + +package com.threerings.cocktail.cher.dobj; + +import com.threerings.cocktail.cher.Log; +import com.threerings.cocktail.cher.io.Streamable; + +/** + * The distributed set class provides a means by which an unordered set of + * objects can be maintained as a distributed object field. Elements can + * be added to and removed from the set, requests for which will generate + * events much like other distributed object fields. + * + *
The sets must contain a homogenous set of objects (all of exactly + * the same type) and the master copy of the set (the one in the master + * copy of the distributed object, ie. the server copy), must be + * configured with the class object of the type of object that this set + * will contain. This allows the set to distributed updates without + * communicating the class of the object contained therein. That need only + * be communicated once when the entire containing distributed object is + * sent over the wire. + * + *
Classes that wish to act as set elements must implement the {@link
+ * com.threerings.cocktail.cher.dobj.DSet.Element} interface which extends
+ * {@link com.threerings.cocktail.cher.io.Streamable} and adds the
+ * requirement that the object provide a key which will be used to
+ * identify element equality. Thus an element is declared to be in a set
+ * of the object returned by that element's geyKey() method
+ * is equal (using equal()) to the element returned by the
+ * getKey() method of some other element in the set.
+ * Additionally, in the case of element removal, only the key for the
+ * element to be removed will be transmitted with the removal event to
+ * save network bandwidth. Lastly, the object returned by
+ * getKey() must be a valid distributed object type.
+ */
+public class DSet
+{
+ /**
+ * Elements of the set must implement this interface.
+ */
+ public static interface Element extends Streamable
+ {
+ /**
+ * Each element provide an associated key which is used to
+ * determine its uniqueness in the set. See the DSet
+ * class documentation for further information.
+ */
+ public Object getKey ();
+ }
+
+ /**
+ * Constructs a distributed set that will contain the specified
+ * element type.
+ */
+ public DSet (Class elementType)
+ {
+ setElementType(elementType);
+ }
+
+ /**
+ * Constructs a distributed set without specifying the element
+ * type. This is valid if the distributed set will soon be
+ * unserialized or if one plans to set the element type by hand before
+ * using the set.
+ */
+ public DSet ()
+ {
+ }
+
+ /**
+ * Indicates what type of elements will be stored in this set. This
+ * can be called multiple times before the set is used (in the event
+ * that one wishes to further specialize the contents of a set that
+ * has already been configured to use a particular element type), but
+ * once the set goes into use, it must not be changed. Also bear in
+ * mind that the class of elements added to the set are not checked at
+ * runtime, and adding elements of invalid class will simply result in
+ * the serialization mechanism failing when an event is dispatched to
+ * broadcast the addition of an element.
+ */
+ public void setElementType (Class elementType)
+ {
+ _elementType = elementType;
+ }
+
+ /**
+ * Instantiates a blank element of the type managed by this set. This
+ * is used during the process of unserializing elements from the
+ * network.
+ */
+ public DSet.Element newElement ()
+ {
+ try {
+ return (Element)_elementType.newInstance();
+ } catch (Exception e) {
+ Log.warning("Unable to instantiate element! We're hosed! " +
+ "[eclass=" + _elementType + "].");
+ return null;
+ }
+ }
+
+ /**
+ * Returns true if the set contains an element whose
+ * getKey() method returns a key that
+ * equals() the key returned by getKey() of
+ * the supplied element. Returns false otherwise.
+ */
+ public boolean contains (Element elem)
+ {
+ return containsKey(elem.getKey());
+ }
+
+ /**
+ * Returns true if an element in the set has a key that
+ * equals() the supplied key. Returns false otherwise.
+ */
+ public boolean containsKey (Object key)
+ {
+ return get(key) != null;
+ }
+
+ /**
+ * Returns the element that matches
+ * (getKey().equals(key)) the specified key or null if no
+ * element could be found that matches the key.
+ */
+ public Element get (Object key)
+ {
+ // scan the array looking for a matching element
+ int elength = _elements.length;
+ for (int i = 0; i < elength; i++) {
+ // the array may be sparse
+ if (_elements[i] != null) {
+ Element elem = _elements[i];
+ if (elem.getKey().equals(key)) {
+ return elem;
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds the specified element to the set. This should not be called
+ * directly, instead the associated addTo{Set}() method
+ * should be called on the distributed object that contains the set in
+ * question.
+ *
+ * @return true if the element was added, false if it was already in
+ * the set.
+ */
+ protected boolean add (Element elem)
+ {
+ Object key = elem.getKey();
+ int elength = _elements.length;
+ int index = elength;
+
+ // scan the array looking for a slot and/or the element already in
+ // the set
+ for (int i = 0; i < elength; i++) {
+ Element el = _elements[i];
+ // the array may be sparse
+ if (el == null) {
+ if (index == elength) {
+ index = i;
+ }
+ } else if (el.getKey().equals(key)) {
+ return false;
+ }
+ }
+
+ // expand the array if necessary
+ if (index >= elength) {
+ Element[] elems = new Element[elength*2];
+ System.arraycopy(_elements, 0, elems, 0, elength);
+ _elements = elems;
+ }
+
+ // insert the item
+ _elements[index] = elem;
+ return true;
+ }
+
+ /**
+ * Removes the specified element from the set. This should not be
+ * called directly, instead the associated
+ * removeFrom{Set}() method should be called on the
+ * distributed object that contains the set in question.
+ *
+ * @return true if the element was removed, false if it was not in the
+ * set.
+ */
+ protected boolean remove (Element elem)
+ {
+ return removeKey(elem.getKey());
+ }
+
+ /**
+ * Removes from the set the element whose key matches the supplied
+ * key. This should not be called directly, instead the associated
+ * removeFrom{Set}() method should be called on the
+ * distributed object that contains the set in question.
+ *
+ * @return true if a matching element was removed, false if no element
+ * in the set matched the key.
+ */
+ protected boolean removeKey (Object key)
+ {
+ // scan the array looking for a matching element
+ int elength = _elements.length;
+ for (int i = 0; i < elength; i++) {
+ Element el = _elements[i];
+ if (el != null && el.getKey().equals(key)) {
+ _elements[i] = null;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected Class _elementType;
+ protected Element[] _elements;
+}
diff --git a/src/java/com/threerings/presents/dobj/EntryAddedEvent.java b/src/java/com/threerings/presents/dobj/EntryAddedEvent.java
new file mode 100644
index 000000000..2e9b43572
--- /dev/null
+++ b/src/java/com/threerings/presents/dobj/EntryAddedEvent.java
@@ -0,0 +1,199 @@
+//
+// $Id: EntryAddedEvent.java,v 1.1 2001/08/16 03:31:09 mdb Exp $
+
+package com.threerings.cocktail.cher.dobj;
+
+import java.io.*;
+
+import com.threerings.cocktail.cher.Log;
+
+/**
+ * An element added event is dispatched when an element is added to a
+ * DSet attribute of a distributed element. It can also be
+ * constructed to request the addition of an element to a
+ * DSet attribute of an element and posted to the dobjmgr.
+ *
+ * @see DObjectManager#postEvent
+ */
+public class ElementAddedEvent extends TypedEvent
+{
+ /** The typed object code for this event. */
+ public static final short TYPE = TYPE_BASE + 8;
+
+ /**
+ * Constructs a new element added event on the specified target object
+ * with the supplied set attribute name and element to add.
+ *
+ * @param targetOid the object id of the object to whose set we will
+ * add an element.
+ * @param name the name of the attribute to which to add the specified
+ * element.
+ * @param elem the element to add to the set attribute.
+ */
+ public ElementAddedEvent (int targetOid, String name, DSet.Element elem)
+ {
+ super(targetOid);
+ _name = name;
+ _elem = elem;
+ }
+
+ /**
+ * Constructs a blank instance of this event in preparation for
+ * unserialization from the network.
+ */
+ public ElementAddedEvent ()
+ {
+ }
+
+ /**
+ * Returns the name of the set attribute to which an element has been
+ * added.
+ */
+ public String getName ()
+ {
+ return _name;
+ }
+
+ /**
+ * Returns the element that has been added.
+ */
+ public DSet.Element getElement ()
+ {
+ return _elem;
+ }
+
+ /**
+ * Applies this event to the object.
+ */
+ public boolean applyToObject (DObject target)
+ throws ObjectAccessException
+ {
+ DSet set = (DSet)target.getAttribute(_name);
+
+ // now that we have access to our target set, we can unflatten our
+ // element (if need be)
+ if (_elem == null) {
+ try {
+ _elem = unflatten(set, _bytes);
+ } catch (Exception e) {
+ Log.warning("Error unflattening element " + this + ".");
+ Log.logStackTrace(e);
+ return false;
+ }
+ }
+
+ set.add(_elem);
+ return true;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ out.writeUTF(_name);
+ flatten(out, _elem);
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ _name = in.readUTF();
+
+ // we read in the raw element data now and decode it later when we
+ // have access to the object and the DSet instance that knows what
+ // type of element we need to decode
+ int bcount = in.readInt();
+ _bytes = new byte[bcount];
+ in.readFully(_bytes, 0, bcount);
+ }
+
+ protected void toString (StringBuffer buf)
+ {
+ buf.append("ELADD:");
+ super.toString(buf);
+ buf.append(", name=").append(_name);
+ buf.append(", elem=").append(_elem);
+ }
+
+ protected String _name;
+ protected byte[] _bytes;
+ protected DSet.Element _elem;
+
+ /**
+ * Because we don't know the type of the element at the time that the
+ * event is read from the network (we only know it once the event is
+ * being applied to the object and we can ask the target
+ * DSet instance what type it manages), we flatten the
+ * element before writing it to the wire so that we can prepend it by
+ * a byte count. The receiver will read in the raw bytes and decode
+ * them only later when it has access to the DSet
+ * instance that knows what element type to use. This method should
+ * really only be called by the conmgr thread, but we synchronize just
+ * in case someone decides to write an event out in some other
+ * peculiar context. Uncontested syncs are pretty fast.
+ */
+ protected static synchronized void flatten (
+ DataOutputStream out, DSet.Element elem)
+ throws IOException
+ {
+ elem.writeTo(_dout);
+ _dout.flush();
+ out.writeInt(_bout.size());
+ _bout.writeTo(out);
+ }
+
+ /**
+ * Unflattens an element given the serialized element data. We know
+ * this will always be called on the dobjmgr thread, so we need not
+ * synchronize.
+ */
+ protected static DSet.Element unflatten (DSet set, byte[] data)
+ throws IOException
+ {
+ _bin.setBytes(data);
+ DSet.Element elem = set.newElement();
+ elem.readFrom(_din);
+ return elem;
+ }
+
+ /**
+ * We extend byte array input stream to avoid having to create a new
+ * input stream every time we unserialize an element. Our extensions
+ * allow us to repurpose this input stream to read from a new byte
+ * array each time we unserialize.
+ */
+ protected static class ReByteArrayInputStream extends ByteArrayInputStream
+ {
+ public ReByteArrayInputStream ()
+ {
+ super(new byte[0]);
+ }
+
+ public void setBytes (byte[] bytes)
+ {
+ buf = bytes;
+ pos = 0;
+ count = buf.length;
+ mark = 0;
+ }
+ }
+
+ /** Used when serializing elements. */
+ protected static ByteArrayOutputStream _bout = new ByteArrayOutputStream();
+
+ /** Used when serializing elements. */
+ protected static DataOutputStream _dout = new DataOutputStream(_bout);
+
+ /** Used when unserializing elements. */
+ protected static ReByteArrayInputStream _bin =
+ new ReByteArrayInputStream();
+
+ /** Used when unserializing elements. */
+ protected static DataInputStream _din = new DataInputStream(_bin);
+}
diff --git a/src/java/com/threerings/presents/dobj/EntryRemovedEvent.java b/src/java/com/threerings/presents/dobj/EntryRemovedEvent.java
new file mode 100644
index 000000000..31f61d6f9
--- /dev/null
+++ b/src/java/com/threerings/presents/dobj/EntryRemovedEvent.java
@@ -0,0 +1,111 @@
+//
+// $Id: EntryRemovedEvent.java,v 1.1 2001/08/16 03:31:09 mdb Exp $
+
+package com.threerings.cocktail.cher.dobj;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+import com.threerings.cocktail.cher.dobj.io.ValueMarshaller;
+
+/**
+ * An element removed event is dispatched when an element is removed from
+ * a DSet attribute of a distributed object. It can also be
+ * constructed to request the removal of an element from a
+ * DSet attribute of an object and posted to the dobjmgr.
+ *
+ * @see DObjectManager#postEvent
+ */
+public class ElementRemovedEvent extends TypedEvent
+{
+ /** The typed object code for this event. */
+ public static final short TYPE = TYPE_BASE + 9;
+
+ /**
+ * Constructs a new element removed event on the specified target
+ * object with the supplied set attribute name and element key to
+ * remove.
+ *
+ * @param targetOid the object id of the object from whose set we will
+ * remove an element.
+ * @param name the name of the attribute from which to remove the
+ * specified element.
+ * @param key the element key that identifies the element to remove.
+ */
+ public ElementRemovedEvent (int targetOid, String name, Object key)
+ {
+ super(targetOid);
+ _name = name;
+ _key = key;
+ }
+
+ /**
+ * Constructs a blank instance of this event in preparation for
+ * unserialization from the network.
+ */
+ public ElementRemovedEvent ()
+ {
+ }
+
+ /**
+ * Returns the name of the oid list attribute from which an oid has
+ * been removed.
+ */
+ public String getName ()
+ {
+ return _name;
+ }
+
+ /**
+ * Returns the key that identifies the element that has been removed.
+ */
+ public Object getKey ()
+ {
+ return _key;
+ }
+
+ /**
+ * Applies this event to the object.
+ */
+ public boolean applyToObject (DObject target)
+ throws ObjectAccessException
+ {
+ DSet set = (DSet)target.getAttribute(_name);
+ set.removeKey(_key);
+ return true;
+
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ out.writeUTF(_name);
+ ValueMarshaller.writeTo(out, _key);
+ }
+
+ public void readExternal (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ _name = in.readUTF();
+ _key = ValueMarshaller.readFrom(in);
+ }
+
+ protected void toString (StringBuffer buf)
+ {
+ buf.append("ELREM:");
+ super.toString(buf);
+ buf.append(", name=").append(_name);
+ buf.append(", key=").append(_key);
+ }
+
+ protected String _name;
+ protected Object _key;
+}
diff --git a/src/java/com/threerings/presents/io/TypedObjectRegistry.java b/src/java/com/threerings/presents/io/TypedObjectRegistry.java
index 4d1ce1a5f..083a718b5 100644
--- a/src/java/com/threerings/presents/io/TypedObjectRegistry.java
+++ b/src/java/com/threerings/presents/io/TypedObjectRegistry.java
@@ -1,5 +1,5 @@
//
-// $Id: TypedObjectRegistry.java,v 1.5 2001/08/07 20:38:58 mdb Exp $
+// $Id: TypedObjectRegistry.java,v 1.6 2001/08/16 03:31:09 mdb Exp $
package com.threerings.cocktail.cher.io;
@@ -66,5 +66,9 @@ public class TypedObjectRegistry
ReleaseLockEvent.class);
TypedObjectFactory.registerClass(ObjectDestroyedEvent.TYPE,
ObjectDestroyedEvent.class);
+ TypedObjectFactory.registerClass(ElementAddedEvent.TYPE,
+ ElementAddedEvent.class);
+ TypedObjectFactory.registerClass(ElementRemovedEvent.TYPE,
+ ElementRemovedEvent.class);
}
}