More progress.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3861 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2006-02-17 05:11:31 +00:00
parent cd7e127bc6
commit 74c35b6d65
22 changed files with 1632 additions and 19 deletions
+2
View File
@@ -11,6 +11,8 @@ Design decisions
- I am embracing flash's event distribution model because it saved me a bunch
of work.
- We could use the setter methods on DObject properties to generate dobj
events, but so far I haven't gone there.
Notes
@@ -63,7 +63,7 @@ public class ObjectInputStream
var target :*;
if (cmap.streamer === null) {
var clazz :Class = flash.util.getClassByName(cmap.cname);
var clazz :Class = flash.util.getClassByName(cmap.classname);
target = new clazz();
} else {
@@ -87,8 +87,7 @@ public class ObjectInputStream
{
// streamable objects
if (streamer == null) {
var sable :Streamable = (obj as Streamable);
sable.readObject(this);
obj.readObject(this); // obj is a Streamable.
return;
}
@@ -117,7 +116,7 @@ public class ObjectInputStream
public function defaultReadObject () :void
//throws IOError
{
_streamer.readObject(_current, this, false);
_streamer.readObject(_current, this);
}
public function readBoolean () :Boolean
@@ -29,6 +29,7 @@ public class ObjectOutputStream
// create a class mapping if we've not got one
if (cmap === undefined) {
var streamer :Streamer = Streamer.getStreamer(obj);
// streamer may be null to indicate a Streamable object
if (streamer === undefined) {
// TODO
trace("OMG, cannot stream ", cname);
@@ -60,8 +61,7 @@ public class ObjectOutputStream
{
// if it's Streamable, it goes straight through
if (streamer == null) {
var sable :Streamable = (obj as Streamable);
sable.writeObject(this);
obj.writeObject(this); // obj is a Streamable
return;
}
@@ -101,7 +101,7 @@ public class ObjectOutputStream
}
// write the instance data
_streamer.writeObject(_current, this, false)
_streamer.writeObject(_current, this);
}
public function writeBoolean (value :Boolean) :void
+4 -8
View File
@@ -40,11 +40,9 @@ public class Streamer
createStreamers();
}
var s :Streamer;
for (var ii :int = 0; ii < _streamers.lenght; ii++) {
s = (_streamers[ii] as Streamer);
if (s.isStreamerFor(obj)) {
return s;
if (_streamers[ii].isStreamerFor(obj)) {
return _streamers[ii];
}
}
@@ -57,11 +55,9 @@ public class Streamer
createStreamers();
}
var s :Streamer;
for (var ii :int = 0; ii < _streamers.length; ii++) {
s = (_streamers[ii] as Streamer);
if (s.getJavaClassName() === jname) {
return s;
if (_streamers[ii].getJavaClassName() === jname) {
return _streamers[ii];
}
}
@@ -109,8 +109,12 @@ public class Communicator
protected function inputFrameReceived (event :FrameAvailableEvent) :void
{
// convert the frame data into a message from the server
_inStream.setSource(event.getFrameData());
var frameData :ByteArray = event.getFrameData();
_inStream.setSource(frameData);
var msg :DownstreamMessage = _inStream.readObject();
if (frameData.bytesAvailable > 0) {
trace("OMG, we didn't fully read the frame. Surely there's a bug");
}
if (_omgr != null) {
// if we're logged on, then just do the normal thing
@@ -119,7 +123,7 @@ public class Communicator
}
// Otherwise, this would be the AuthResponse to our logon attempt.
var rsp :AuthResponse = (msg as AuthResponse); // TODO: as correct?
var rsp :AuthResponse = (msg as AuthResponse);
var data :AuthResponseData = rsp.getData();
if (data.code !== AuthResponseData.SUCCESS) {
shutdown(new Error(data.code));
@@ -0,0 +1,33 @@
package com.threerings.presents.client {
import com.threerings.presents.dobj.DObjectManager;
public class InvocationDirector
{
public function init (omgr :DObjectManager, cloid :int, client :Client) :void
{
if (_clobj != null) {
trace("Zoiks, client object around during invmgr init!");
cleanup();
}
_omgr = omgr;
_client = client;
// TODO : lots more
}
public function cleanup () :void
{
_clobj = null;
// TODO: lots more
}
protected var _omgr :DObjectManager;
protected var _client :Client;
protected var _clobj :ClientObject;
}
}
@@ -0,0 +1,89 @@
package com.threerings.presents.dobj {
import flash.util.StringBuilder;
/**
* An attribute changed event is dispatched when a single attribute of a
* distributed object has changed. It can also be constructed to request
* an attribute change on an object and posted to the dobjmgr.
*
* @see DObjectManager#postEvent
*/
public class AttributeChangedEvent extends NamedEvent
{
/**
* Returns the new value of the attribute.
*/
public function getValue () :*
{
return _value;
}
/**
* Returns the value of the attribute prior to the application of this
* event.
*/
public function getOldValue () :*
{
return _oldValue;
}
/**
* Applies this attribute change to the object.
*/
public override function applyToObject (target :DObject) :Boolean
//throws ObjectAccessException
{
// if we have no old value, that means we're not running on the
// master server and we have not already applied this attribute
// change to the object, so we must grab the previous value and
// actually apply the attribute change
if (_oldValue === UNSET_OLD_ENTRY) {
_oldValue = target[_name]; // fucking wack-ass language
}
target[_name] = _value;
return true;
}
/**
* Constructs a new attribute changed event on the specified target
* object with the supplied attribute name and value. <em>Do not
* construct these objects by hand.</em> Use {@link
* DObject#changeAttribute} instead.
*
* @param targetOid the object id of the object whose attribute has
* changed.
* @param name the name of the attribute (data member) that has
* changed.
* @param value the new value of the attribute (in the case of
* primitive types, the reflection-defined object-alternative is
* used).
*/
protected function AttributeChangedEvent (
targetOid :int, name :String, value :*, oldValue :*)
{
super(targetOid, name);
_value = value;
_oldValue = oldValue;
}
// documentation inherited
protected override function notifyListener (listener :*) :void
{
if (listener is AttributeChangeListener) {
listener.attributeChanged(this);
}
}
// documentation inherited
protected override function toString (buf :StringBuilder) :void
{
buf.append("CHANGE:");
super.toString(buf);
buf.append(", value=", _value);
}
protected var _value :*;
protected var _oldValue :* = UNSET_OLD_ENTRY;
}
}
@@ -0,0 +1,14 @@
package com.threerings.presents.dobj {
/**
* The various listener interfaces (e.g. {@link EventListener}, {@link
* AttributeChangeListener}, etc.) all extend this base interface so that
* the distributed object can check to make sure when an object is adding
* itself as a listener of some sort that it actually implements at least
* one of the listener interfaces. Thus, all listener interfaces must
* extend this one.
*/
public interface ChangeListener {
// nada
}
}
+87 -1
View File
@@ -1,6 +1,13 @@
package com.threerings.presents.dobj {
public class DEvent extends Object
import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
public class DEvent
implements Streamable
{
public function DEvent ()
{
@@ -11,12 +18,91 @@ public class DEvent extends Object
_toid = targetOid;
}
/**
* Returns the oid of the object that is the target of this event.
*/
public function getTargetOid () :int
{
return _toid;
}
/**
* Some events are used only internally on the server and need not be
* broadcast to subscribers, proxy or otherwise. Such events can
* return true here and short-circuit the normal proxy event dispatch
* mechanism.
*/
public function applyToObject (target :DObject) :void
{
trace("TODO: abstract methods?");
}
/**
* Events with associated listener interfaces should implement this
* function and notify the supplied listener if it implements their
* event listening interface. For example, the {@link
* AttributeChangedEvent} will notify listeners that implement {@link
* AttributeChangeListener}.
*/
protected function notifyListener (listener :*) :void
{
// the default is to do nothing
}
/**
* Constructs and returns a string representation of this event.
*/
public override function toString () :String
{
StringBuilder buf = new StringBuilder();
buf.append("[");
toString(buf);
buf.append("]");
return buf.toString();
}
// documentation inherited from interface Streamable
public function writeObject (out :ObjectOutputStream) :void
{
out.writeInt(_toid);
}
// documentation inherited from interface Streamable
public function readObject (ins :ObjectInputStream) :void
{
_toid = ins.readInt();
}
/**
* This should be overridden by derived classes (which should be sure
* to call <code>super.toString()</code>) to append the derived class
* specific event information to the string buffer.
*/
protected function toString (buf :StringBuilder) :void
{
buf.append("targetOid=", _toid);
}
/** The oid of the object that is the target of this event. */
protected var _toid :int;
protected static const UNSET_OLD_ENTRY :DSetEntry = new DummyEntry();
}
}
class DummyEntry implements DSetEntry
{
public function getKey () :Comparable
{
return null;
}
public function writeObject (out :ObjectOutputStream) :void
{
}
public function readObject (ins :ObjectInputStream) :void
{
}
}
+152 -1
View File
@@ -2,11 +2,13 @@ package com.threerings.presents.dobj {
import flash.events.EventDispatcher;
import mx.collections.ArrayCollection;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
public class DObject extends EventDispatcher
public class DObject // extends EventDispatcher
implements Streamable
{
public function getOid ():int
@@ -14,9 +16,152 @@ public class DObject extends EventDispatcher
return _oid;
}
public function getManager () :DObjectManager
{
return _omgr;
}
public function addListener (listener :ChangeListener) :void
{
if (_listeners == null) {
_listeners = new ArrayCollection();
} else if (_listeners.contains(listener)) {
trace("Refusing repeat listener registration");
return;
}
_listeners.addItem(listener);
}
public function removeListener (listener :ChangeListener) :void
{
if (_listeners != null) {
var dex :int = _listeners.getItemIndex(listener);
if (dex != -1) {
_listeners.removeItemAt(dex);
}
}
}
public function notifyListeners (event :DEvent) :void
{
if (_listeners == null) {
return;
}
for (var ii :int = 0; ii < _listeners.length; ii++) {
var listener :* = _listeners.getItemAt(ii);
try {
event.notifyListener(listener);
if (listener is EventListener) {
listener.eventReceived(event);
}
} catch (e :Error) {
trace("Listener choked during notification");
trace(e.getStackTrace());
}
}
}
public function postMessage (name :String, args :Array) :void
{
postEvent(new MessageEvent(_oid, name, args));
}
public function postEvent (DEvent event) :void
{
// TODO: transactons?
if (_omgr != null) {
_omgr.postEvent(event);
} else {
trace("Unable to post event, object has no omgr");
}
}
public function isActive () :Boolean
{
return (_omgr != null);
}
/**
* Don't call this function! It initializes this distributed object
* with the supplied distributed object manager. This is called by the
* distributed object manager when an object is created and registered
* with the system.
*
* @see DObjectManager#createObject
*/
public function setManager (omgr :DObjectManager) :void
{
_omgr = omgr;
}
/**
* Called by derived instances when an attribute setter method was
* called.
*/
protected function requestAttributeChange (
name :String, value :*, oldValue :*) :void
{
postEvent(new AttributeChangedEvent(_oid, name, value, oldValue));
}
/**
* Called by derived instances when an element updater method was
* called.
*/
protected function requestElementUpdate (
name :String, index :int, value :*, oldValue :*) :void
{
// dispatch an attribute changed event
postEvent(new ElementUpdatedEvent(_oid, name, value, oldValue, index));
}
/**
* Calls by derived instances when an oid adder method was called.
*/
protected function requestOidAdd (name :String, oid :int) :void
{
// dispatch an object added event
postEvent(new ObjectAddedEvent(_oid, name, oid));
}
/**
* Calls by derived instances when an oid remover method was called.
*/
protected function requestOidRemove (name :String, oid :int) :void
{
// dispatch an object removed event
postEvent(new ObjectRemovedEvent(_oid, name, oid));
}
/**
* Calls by derived instances when a set adder method was called.
*/
protected function requestEntryAdd (name :String, entry :DSet.Entry) :void
{
// dispatch an entry added event
postEvent(new EntryAddedEvent(_oid, name, entry, false));
}
/**
* Calls by derived instances when a set remover method was called.
*/
protected function requestEntryRemove (name :String, key :Comparable) :void
{
// dispatch an entry removed event
postEvent(new EntryRemovedEvent(_oid, name, key, null));
}
/**
* Calls by derived instances when a set updater method was called.
*/
protected function requestEntryUpdate (name :String, entry :DSet.Entry)
{
// dispatch an entry updated event
postEvent(new EntryUpdatedEvent(_oid, name, entry, null));
}
// documentation inherited from interface Streamable
@@ -32,5 +177,11 @@ public class DObject extends EventDispatcher
}
protected var _oid :int;
/** A reference to our object manager. */
protected var _omgr :DObjectManager;
/** Our event listeners. */
protected var _listeners :ArrayCollection;
}
}
@@ -0,0 +1,97 @@
package com.threerings.presents.dobj {
/**
* The distributed object manager is responsible for managing the creation
* and destruction of distributed objects and propagating dobj events to
* the appropriate subscribers. On the client, objects are managed as
* proxies to the real objects managed by the server, so attribute change
* requests are forwarded to the server and events coming down from the
* server are delivered to the local subscribers. On the server, the
* objects are managed directly.
*/
public interface DObjectManager
{
/**
* Returns true if this distributed object manager is the
* authoritative manager for the specified distributed object, or fals
* if we are only providing a proxy to the object.
*/
function isManager (obj :DObject) :Boolean;
/**
* Creates a distributed object instance of the supplied class and
* notifies the specified subscriber when it becomes available. This
* is the proper mechanism for constructing a new distributed object
* as it is the only way in which it can be properly registered with
* the dobj system.
*
* @param dclass The class object of the derived class of
* <code>DObject</code> (or <code>DObject.class</code> itself) that
* should be instantiated.
* @param target The subscriber to be notified when the object is
* created and available, or if there was a problem creating the
* object.
*/
function createObject (dclass :Class, target :Subscriber) :void;
/**
* Requests that the specified subscriber be subscribed to the object
* identified by the supplied object id. That subscriber will be
* notified when the object is available or if the subscription
* request failed.
*
* @param oid The object id of the distributed object to which
* subscription is desired.
* @param target The subscriber to be subscribed.
*
* @see Subscriber#objectAvailable
* @see Subscriber#requestFailed
*/
function subscribeToObject (oid :int, target :Subscriber) :void;
/**
* Requests that the specified subscriber be unsubscribed from the
* object identified by the supplied object id.
*
* @param oid The object id of the distributed object from which
* unsubscription is desired.
* @param target The subscriber to be unsubscribed.
*/
function unsubscribeFromObject (oid :int, target :Subscriber) :void;
/**
* Requests that the specified object be destroyed. Once destroyed an
* object is removed from the runtime system and may no longer have
* events dispatched on it.
*
* @param oid The object id of the distributed object to be destroyed.
*/
function destroyObject (oid :int) :void;
/**
* Posts a distributed object event into the system. Instead of
* requesting the modification of a distributed object attribute by
* calling the setter for that attribute on the object itself, an
* <code>AttributeChangedEvent</code> can be constructed and posted
* directly. This is true for all event types and is useful for
* situations where one doesn't have access to the object in question,
* but needs to affect some event.
*
* <p> This event will be forwarded to the ultimate manager of the
* object (on the client, this means it will be forwarded to the
* server) where it will be checked for validity and then applied to
* the object and dispatched to all its subscribers.
*
* @param event The event to be dispatched.
*/
function postEvent (event :DEvent) :void;
/**
* When a distributed object removes its last subscriber, it will call
* this function to let the object manager know. The manager might
* then choose to flush this object from the system or unregister from
* some upstream manager whose object it was proxying, for example.
*/
function removedLastSubscriber (obj :DObject, deathWish :Boolean) :void;
}
}
+275
View File
@@ -0,0 +1,275 @@
package com.threerings.presents.dobj {
import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Streamable;
/**
* The distributed set class provides a means by which an unordered set of
* objects can be maintained as a distributed object field. Entries can be
* added to and removed from the set, requests for which will generate
* events much like other distributed object fields.
*
* <p> Classes that wish to act as set entries must implement the {@link
* Entry} interface which extends {@link Streamable} and adds the
* requirement that the object provide a key which will be used to
* identify entry equality. Thus an entry is declared to be in a set of
* the object returned by that entry's {@link Entry#getKey} method is
* equal (using {@link Object#equals}) to the entry returned by the {@link
* Entry#getKey} method of some other entry in the set. Additionally, in
* the case of entry removal, only the key for the entry to be removed
* will be transmitted with the removal event to save network
* bandwidth. Lastly, the object returned by {@link Entry#getKey} must be
* a {@link Streamable} type.
*/
public class DSet
implements Streamable
{
/**
* Returns the number of entries in this set.
*/
public function size () :int
{
return _entries.length;
}
/**
* Returns true if the set contains an entry whose
* <code>getKey()</code> method returns a key that
* <code>equals()</code> the key returned by <code>getKey()</code> of
* the supplied entry. Returns false otherwise.
*/
public function contains (elem :DSetEntry) :Boolean
{
return containsKey(elem.getKey());
}
/**
* Returns true if an entry in the set has a key that
* <code>equals()</code> the supplied key. Returns false otherwise.
*/
public function containsKey (key :Comparable) :Boolean
{
return getByKey(key) != null;
}
/**
* Returns the entry that matches (<code>getKey().equals(key)</code>)
* the specified key or null if no entry could be found that matches
* the key.
*/
public function getByKey (key :Comparable) :DSetEntry
{
// o(n) for now
// TODO
for (var ii :int = 0; ii < _entries.length; ii++) {
var entry :DSetEntry = _entries[ii];
if (key.compareTo(entry.getKey()) == 0) {
return entry;
}
}
return null;
}
/**
* Returns an iterator over the entries of this set. It does not
* support modification (nor iteration while modifications are being
* made to the set). It should not be kept around as it can quickly
* become out of date.
*
* @deprecated
*/
// public Iterator entries ()
// {
// return iterator();
// }
/**
* Returns an iterator over the entries of this set. It does not
* support modification (nor iteration while modifications are being
* made to the set). It should not be kept around as it can quickly
* become out of date.
*/
// public Iterator iterator ()
// {
// // the crazy sanity checks
// if (_size < 0 ||_size > _entries.length ||
// (_size > 0 && _entries[_size-1] == null)) {
// Log.warning("DSet in a bad way [size=" + _size +
// ", entries=" + StringUtil.toString(_entries) + "].");
// Thread.dumpStack();
// }
//
// return new Iterator() {
// public boolean hasNext () {
// checkComodification();
// return (_index < _size);
// }
// public Object next () {
// checkComodification();
// return _entries[_index++];
// }
// public void remove () {
// throw new UnsupportedOperationException();
// }
// protected void checkComodification () {
// if (_modCount != _expectedModCount) {
// throw new ConcurrentModificationException();
// }
// if (_ssize != _size) {
// Log.warning("Size changed during iteration " +
// "[ssize=" + _ssize + ", nsize=" + _size +
// ", entsries=" + StringUtil.toString(_entries) +
// "].");
// Thread.dumpStack();
// }
// }
// protected int _index = 0;
// protected int _ssize = _size;
// protected int _expectedModCount = _modCount;
// };
// }
/**
* Copies the elements of this distributed set into the supplied
* array. If the array is not large enough to hold all of the
* elements, as many as fit into the array will be copied. If the
* <code>array</code> argument is null, an object array of sufficient
* size to contain all of the elements of this set will be created and
* returned.
*/
public function toArray () :Array
{
return _entries.concat(null); // makes a copy
}
/**
* Adds the specified entry to the set. This should not be called
* directly, instead the associated <code>addTo{Set}()</code> method
* should be called on the distributed object that contains the set in
* question.
*
* @return true if the entry was added, false if it was already in
* the set.
*/
protected function add (elem :DSetEntry) :Boolean
{
if (contains(elem)) {
trace("Refusing to add duplicate entry [set=" + this +
", entry=" + elem + "].");
return false;
}
_entries.push(elem);
return true;
}
/**
* Removes the specified entry from the set. This should not be called
* directly, instead the associated <code>removeFrom{Set}()</code>
* method should be called on the distributed object that contains the
* set in question.
*
* @return true if the entry was removed, false if it was not in the
* set.
*/
protected function remove (elem :DSetEntry) :Boolean
{
return (null != removeKey(elem.getKey()));
}
/**
* Removes from the set the entry whose key matches the supplied
* key. This should not be called directly, instead the associated
* <code>removeFrom{Set}()</code> method should be called on the
* distributed object that contains the set in question.
*
* @return the old matching entry if found and removed, null if not
* found.
*/
protected function removeKey (key :Comparable) :DSetEntry
{
// o(n) for now
// TODO
for (var ii :int = 0; ii < _entries.length; ii++) {
var entry :DSetEntry = _entries[ii];
if (key.compareTo(entry.getKey()) == 0) {
_entries.splice(ii, 1);
return entry;
}
}
return null;
}
/**
* Updates the specified entry by locating an entry whose key matches
* the key of the supplied entry and overwriting it. This should not
* be called directly, instead the associated
* <code>update{Set}()</code> method should be called on the
* distributed object that contains the set in question.
*
* @return the old entry that was replaced, or null if it was not
* found (in which case nothing is updated).
*/
protected function update (elem :DSetEntry) :DSetEntry
{
var key :Comparable = elem.getKey();
for (var ii :int = 0; ii < _entries.length; ii++) {
var entry :DSetEntry = _entries[ii];
if (key.compareTo(entry.getKey()) == 0) {
_entries[ii] = elem;
return entry;
}
}
return null;
}
/**
* Generates a string representation of this set instance.
*/
public override function toString () :String
{
StringBuilder buf = new StringBuilder("(");
buf.append(_entries.toString());
buf.append(")");
return buf.toString();
}
// documentation inherited from interface Streamable
public function writeObject (out :ObjectOutputStream) :void
{
trace("TODO");
}
// documentation inherited from interface Streamable
public function readObject (ins :ObjectInputStream) :void
{
_entries = ins.readField(Array);
_entries.length = ins.readInt(); // then read the length and limit it
}
/** The entries of the set (in a sparse array). */
protected var _entries :Array;
// /** Used to check for concurrent modification. */
// protected transient int _modCount;
//
// /** The default capacity of a set instance. */
// protected static final int INITIAL_CAPACITY = 2;
//
// /** Used for lookups and to keep the set contents sorted on
// * insertions. */
// protected static Comparator ENTRY_COMP = new Comparator() {
// public int compare (Object o1, Object o2) {
// Comparable c1 = (o1 instanceof Entry) ?
// ((Entry)o1).getKey() : (Comparable)o1;
// Comparable c2 = (o2 instanceof Entry) ?
// ((Entry)o2).getKey() : (Comparable)o2;
// return c1.compareTo(c2);
// }
// };
}
}
@@ -0,0 +1,9 @@
package com.threerings.presents.dobj {
import com.threerings.io.Streamable;
public interface DSetEntry extends Streamable
{
function getKey () :Comparable;
}
}
@@ -0,0 +1,117 @@
package com.threerings.presents.dobj {
import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* An element updated event is dispatched when an element of an array
* field in a distributed object is updated. It can also be constructed to
* request the update of an entry and posted to the dobjmgr.
*
* @see DObjectManager#postEvent
*/
public class ElementUpdatedEvent extends NamedEvent
{
/**
* Constructs a new element updated event on the specified target
* object with the supplied attribute name, element and index.
*
* @param targetOid the object id of the object whose attribute has
* changed.
* @param name the name of the attribute (data member) for which an
* element has changed.
* @param value the new value of the element (in the case of primitive
* types, the reflection-defined object-alternative is used).
* @param oldValue the previous value of the element (in the case of
* primitive types, the reflection-defined object-alternative is
* used).
* @param index the index in the array of the updated element.
*/
public ElementUpdatedEvent (
targetOid :int, name :String, value :*, oldValue :*, index :int)
{
super(targetOid, name);
_value = value;
_oldValue = oldValue;
_index = index;
}
/**
* Returns the new value of the element.
*/
public function getValue () :*
{
return _value;
}
/**
* Returns the value of the element prior to the application of this
* event.
*/
public function getOldValue () :*
{
return _oldValue;
}
/**
* Returns the index of the element.
*/
public function getIndex () :int
{
return _index;
}
/**
* Applies this element update to the object.
*/
public override function applyToObject (target :DObject) :Boolean
//throws ObjectAccessException
{
if (_oldValue === UNSET_OLD_ENTRY) {
_oldValue = target[_name][_index];
}
target[_name][_index] = _value;
return true;
}
// documentation inherited
public override function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeField(_value);
out.writeInt(_index);
}
// documentation inherited
public override function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
_value = ins.readField(Object);
_index = ins.readInt();
}
// documentation inherited
protected override function notifyListener (listener :*) :void
{
if (listener is ElementUpdateListener) {
listener.elementUpdated(this);
}
}
// documentation inherited
protected override function toString (buf :StringBuilder) :void
{
buf.append("UPDATE:");
super.toString(buf);
buf.append(", value=", _value);
buf.append(", index=", _index);
}
protected var _value :*;
protected var _index :int;
protected var _oldValue :* = UNSET_OLD_ENTRY;
}
}
@@ -0,0 +1,85 @@
package com.threerings.presents.dobj {
import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* An entry added event is dispatched when an entry is added to a {@link
* DSet} attribute of a distributed entry. It can also be constructed to
* request the addition of an entry to a set and posted to the dobjmgr.
*
* @see DObjectManager#postEvent
*/
public class EntryAddedEvent extends NamedEvent
{
/**
* Constructs a new entry added event on the specified target object
* with the supplied set attribute name and entry to add.
*
* @param targetOid the object id of the object to whose set we will
* add an entry.
* @param name the name of the attribute to which to add the specified
* entry.
* @param entry the entry to add to the set attribute.
*/
public function EntryAddedEvent (
targetOid :int, name :String, entry :DSetEntry)
{
super(targetOid, name);
_entry = entry;
}
/**
* Returns the entry that has been added.
*/
public function getEntry () :DSetEntry
{
return _entry;
}
/**
* Applies this event to the object.
*/
public override function applyToObject (target :DObject) :Boolean
//throws ObjectAccessException
{
var added :Boolean = target[_name].add(_entry);
if (!added) {
trace("Duplicate entry found [event=" + this + "].");
}
return true;
}
// documentation inherited
protected override function notifyListener (listener :*) :void
{
if (listener is SetListener) {
listener.entryAdded(this);
}
}
// documentation inherited
protected override function toString (StringBuilder buf)
{
buf.append("ELADD:");
super.toString(buf);
buf.append(", entry=", _entry);
}
public override function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeField(_entry);
}
public override function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
_entry = ins.readField(DSetEntry);
}
protected var _entry :DSetEntry;
}
}
@@ -0,0 +1,105 @@
package com.threerings.presents.dobj {
import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* An entry removed event is dispatched when an entry is removed from a
* {@link DSet} attribute of a distributed object. It can also be
* constructed to request the removal of an entry from a set and posted to
* the dobjmgr.
*
* @see DObjectManager#postEvent
*/
public class EntryRemovedEvent extends NamedEvent
{
/**
* Constructs a new entry removed event on the specified target object
* with the supplied set attribute name and entry key to remove.
*
* @param targetOid the object id of the object from whose set we will
* remove an entry.
* @param name the name of the attribute from which to remove the
* specified entry.
* @param key the entry key that identifies the entry to remove.
* @param oldEntry the previous value of the entry.
*/
public function EntryRemovedEvent (
targetOid :int, name :String, key :Comparable, oldEntry :DSetEntry)
{
super(targetOid, name);
_key = key;
_oldEntry = oldEntry;
}
/**
* Returns the key that identifies the entry that has been removed.
*/
public function getKey () :Comparable
{
return _key;
}
/**
* Returns the entry that was in the set prior to being updated.
*/
public function getOldEntry () :DSetEntry
{
return _oldEntry;
}
/**
* Applies this event to the object.
*/
public override function applyToObject (target :DObject) :Boolean
//throws ObjectAccessException
{
if (_oldEntry == UNSET_OLD_ENTRY) {
var dset :DSet = target[_name];
// remove, fetch the previous value for interested callers
_oldEntry = dset.removeKey(_key);
if (_oldEntry == null) {
// complain if there was actually nothing there
trace("No matching entry to remove [key=" + _key +
", set=" + set + "].");
return false;
}
}
return true;
}
// documentation inherited
protected override function notifyListener (listener :*) :void
{
if (listener is SetListener) {
listener.entryRemoved(this);
}
}
// documentation inherited
protected override function toString (buf :StringBuilder)
{
buf.append("ELREM:");
super.toString(buf);
buf.append(", key=", _key);
}
public override function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeField(_key);
}
public override function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
_key = ins.readField(Comparable);
}
protected var _key :Comparable;
protected var _oldEntry :DSetEntry = UNSET_OLD_ENTRY;
}
}
@@ -0,0 +1,104 @@
package com.threerings.presents.dobj {
import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* An entry updated event is dispatched when an entry of a {@link DSet} is
* updated. It can also be constructed to request the update of an entry
* and posted to the dobjmgr.
*
* @see DObjectManager#postEvent
*/
public class EntryUpdatedEvent extends NamedEvent
{
/**
* Constructs a new entry updated event on the specified target object
* for the specified set name and with the supplied updated entry.
*
* @param targetOid the object id of the object to whose set we will
* add an entry.
* @param name the name of the attribute in which to update the
* specified entry.
* @param entry the entry to update.
* @param oldEntry the previous value of the entry.
*/
public function EntryUpdatedEvent (
targetOid :int, name :String, entry :DSetEntry, oldEntry :DSetEntry)
{
super(targetOid, name);
_entry = entry;
_oldEntry = oldEntry;
}
/**
* Returns the entry that has been updated.
*/
public function getEntry () :DSetEntry
{
return _entry;
}
/**
* Returns the entry that was in the set prior to being updated.
*/
public function getOldEntry ():DSetEntry
{
return _oldEntry;
}
/**
* Applies this event to the object.
*/
public override function applyToObject (target :DObject) :Boolean
//throws ObjectAccessException
{
// only apply the change if we haven't already
if (_oldEntry == UNSET_OLD_ENTRY) {
var dset :DSet = target[_name];
// fetch the previous value for interested callers
_oldEntry = dset.update(_entry);
if (_oldEntry == null) {
// complain if we didn't update anything
trace("No matching entry to update [entry=" + this +
", set=" + set + "].");
return false;
}
}
return true;
}
// documentation inherited
protected override function notifyListener (listener :*) :void
{
if (listener is SetListener) {
listener.entryUpdated(this);
}
}
// documentation inherited
protected override function toString (buf :StringBuilder) :void
{
buf.append("ELUPD:");
super.toString(buf);
buf.append(", entry=", _entry);
}
public override function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeField(_entry);
}
public override function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
_entry = ins.readField(DSetEntry);
}
protected var _entry :DSetEntry;
protected var _oldEntry :DSetEntry = UNSET_OLD_ENTRY;
}
}
@@ -0,0 +1,81 @@
package com.threerings.presents.dobj {
import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* A message event is used to dispatch a message to all subscribers of a
* distributed object without actually changing any of the fields of the
* object. A message has a name, by which different subscribers of the
* same object can distinguish their different messages, and an array of
* arguments by which any contents of the message can be delivered.
*
* @see DObjectManager#postEvent
*/
public class MessageEvent extends NamedEvent
{
/**
* Constructs a new message event on the specified target object with
* the supplied name and arguments.
*
* @param targetOid the object id of the object whose attribute has
* changed.
* @param name the name of the message event.
* @param args the arguments for this message. This array should
* contain only values of valid distributed object types.
*/
public function MessageEvent (targetOid :int, name :String, args :Array)
{
super(targetOid, name);
_args = args;
}
/**
* Returns the arguments to this message.
*/
public function getArgs () :Array
{
return _args;
}
/**
* Replaces the arguments associated with this message event.
* <em>Note:</em> this should only be called on events that have not
* yet been dispatched into the distributed object system.
*/
public function setArgs (args :Array) :void
{
_args = args;
}
/**
* Applies this attribute change to the object.
*/
public override function applyToObject (target :DObject) :Boolean
//throws ObjectAccessException
{
// nothing to do here
return true;
}
// documentation inherited
protected override function notifyListener (listener :*) :void
{
if (listener is MessageListener) {
listener.messageReceived(this);
}
}
// documentation inherited
protected override function toString (buf :StringBuilder) :void
{
buf.append("MSG:");
super.toString(buf);
buf.append(", args=", _args);
}
protected var _args :Array;
}
}
@@ -0,0 +1,56 @@
package com.threerings.presents.dobj {
import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* A common parent class for all events that are associated with a name
* (in some cases a field name, in other cases just an identifying name).
*/
public class NamedEvent extends DEvent
{
/**
* Constructs a new named event for the specified target object with
* the supplied attribute name.
*
* @param targetOid the object id of the object in question.
* @param name the name associated with this event.
*/
public NamedEvent (targetOid :int, name :String)
{
super(targetOid);
_name = name;
}
/**
* Returns the name of the attribute to which this event pertains.
*/
public function getName () :String
{
return _name;
}
protected override function toString (buf :StringBuilder) :void
{
super.toString(buf);
buf.append(", name=", _name);
}
public override function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeField(_name);
}
public override function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
_name = ins.readField(String);
}
/** The name of the event. */
protected var _name :String;
}
}
@@ -0,0 +1,83 @@
package com.threerings.presents.dobj {
import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* An object added event is dispatched when an object is added to an
* <code>OidList</code> attribute of a distributed object. It can also be
* constructed to request the addition of an oid to an
* <code>OidList</code> attribute of an object and posted to the dobjmgr.
*
* @see DObjectManager#postEvent
*/
public class ObjectAddedEvent extends NamedEvent
{
/**
* Constructs a new object added event on the specified target object
* with the supplied oid list attribute name and object id to add.
*
* @param targetOid the object id of the object to whose oid list we
* will add an oid.
* @param name the name of the attribute (data member) to which to add
* the specified oid.
* @param oid the oid to add to the oid list attribute.
*/
public function ObjectAddedEvent (targetOid :int, name :String, oid :int)
{
super(targetOid, name);
_oid = oid;
}
/**
* Returns the oid that has been added.
*/
public function getOid () :int
{
return _oid;
}
/**
* Applies this event to the object.
*/
public override function applyToObject (target :DObject) :void
//throws ObjectAccessException
{
var list :OidList = target[_name];
list.add(_oid);
return true;
}
// documentation inherited
protected override function notifyListener (listener :*) :void
{
if (listener is OidListListener) {
listener.objectAdded(this);
}
}
public override function writeObject (out :ObjectOutputStream) :void
{
super.writeObject(out);
out.writeInt(_oid);
}
public override function readObject (ins :ObjectInputStream) :void
{
super.readObject(ins);
_oid = ins.readInt();
}
// documentation inherited
protected override function toString (buf :StringBuilder) :void
{
buf.append("OBJADD:");
super.toString(buf);
buf.append(", oid=", _oid);
}
protected var _oid :int;
}
}
@@ -0,0 +1,95 @@
//
// $Id: ObjectRemovedEvent.java 3099 2004-08-27 02:21:06Z mdb $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// This library is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation; either version 2.1 of the License, or
// (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
package com.threerings.presents.dobj;
/**
* An object removed event is dispatched when an object is removed from an
* <code>OidList</code> attribute of a distributed object. It can also be
* constructed to request the removal of an oid from an
* <code>OidList</code> attribute of an object and posted to the dobjmgr.
*
* @see DObjectManager#postEvent
*/
public class ObjectRemovedEvent extends NamedEvent
{
/**
* Constructs a new object removed event on the specified target
* object with the supplied oid list attribute name and object id to
* remove.
*
* @param targetOid the object id of the object from whose oid list we
* will remove an oid.
* @param name the name of the attribute (data member) from which to
* remove the specified oid.
* @param oid the oid to remove from the oid list attribute.
*/
public ObjectRemovedEvent (int targetOid, String name, int oid)
{
super(targetOid, name);
_oid = oid;
}
/**
* Constructs a blank instance of this event in preparation for
* unserialization from the network.
*/
public ObjectRemovedEvent ()
{
}
/**
* Returns the oid that has been removed.
*/
public int getOid ()
{
return _oid;
}
/**
* Applies this event to the object.
*/
public boolean applyToObject (DObject target)
throws ObjectAccessException
{
OidList list = (OidList)target.getAttribute(_name);
list.remove(_oid);
return true;
}
// documentation inherited
protected void notifyListener (Object listener)
{
if (listener is OidListListener) {
((OidListListener)listener).objectRemoved(this);
}
}
// documentation inherited
protected void toString (StringBuffer buf)
{
buf.append("OBJREM:");
super.toString(buf);
buf.append(", oid=").append(_oid);
}
protected int _oid;
}
@@ -0,0 +1,132 @@
package com.threerings.presents.dobj {
import com.threerings.io.Streamable;
import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream;
/**
* An oid list is used to store lists of object ids. The list will not
* allow duplicate ids. This class is not synchronized, with the
* expectation that all modifications of instances will take place on the
* dobjmgr thread.
*/
public class OidList
implements Streamable
{
/**
* Creates an empty oidlist.
*/
public function OidList ()
{
_oids = new Array();
}
/**
* Returns the number of object ids in the list.
*/
public function size () :int
{
return _size;
}
/**
* Adds the specified object id to the list if it is not already
* there.
*
* @return true if the object was added, false if it was already in
* the list.
*/
public function add (oid :int) :Boolean
{
// check for existence
for (int i = 0; i < _size; i++) {
if (_oids[i] == oid) {
return false;
}
}
// make room if necessary
if (_size+1 >= _oids.length) {
expand();
}
// add the oid
_oids[_size++] = oid;
return true;
}
/**
* Removes the specified oid from the list.
*
* @return true if the oid was in the list and was removed, false
* otherwise.
*/
public boolean remove (int oid)
{
// scan for the oid in question
for (int i = 0; i < _size; i++) {
if (_oids[i] == oid) {
// shift the rest of the list back one
System.arraycopy(_oids, i+1, _oids, i, --_size-i);
return true;
}
}
return false;
}
/**
* Returns true if the specified oid is in the list, false if not.
*/
public boolean contains (int oid)
{
for (int i = 0; i < _size; i++) {
if (_oids[i] == oid) {
return true;
}
}
return false;
}
/**
* Returns the object id at the specified index. This does no boundary
* checking.
*/
public function getAt (index :int) :int
{
return _oids[index];
}
// documentation inherited from interface Streamable
public function writeObject (out :ObjectOutputStream) :void
{
trace("Not implemented");
}
// documentation inherited from interface Streamable
public function readObject (ins :ObjectInputStream) :void
{
// TODO: this is some custom shit. We need to work out array streaming
_oids = new Array();
var ecount :int = ins.readInt();
for (var ii :int = 0; ii < ecount; ii++) {
_oids.push(ins.readInt());
}
_oids.length = ins.readInt(); // then, limit the length to the size
}
public override function toString () :String
{
StringBuilder buf = new StringBuilder();
buf.append("{");
buf.append(_oids.toString());
buf.append("}");
return buf.toString();
}
private var _oids :Array;
}
}