diff --git a/src/as/com/threerings/io/ObjectInputStream.as b/src/as/com/threerings/io/ObjectInputStream.as index dd4da198e..11bf8e341 100644 --- a/src/as/com/threerings/io/ObjectInputStream.as +++ b/src/as/com/threerings/io/ObjectInputStream.as @@ -57,7 +57,7 @@ public class ObjectInputStream _classMap[code] = cmap; } else { - cmap = _classMap[code]; + cmap = (_classMap[code] as ClassMapping); if (null == cmap) { throw new IOError("Read object for which we have no " + "registered class metadata."); @@ -107,13 +107,20 @@ public class ObjectInputStream } } + /** + * @param type either a String representing the java type, + * or a Class object representing the actionscript type. + */ // TODO: this is the equivalent of marshalling something for which // we have a basic streamer. Fill out with all the java types - public function readField (clazz :Class) :Object + public function readField (type :Object) :Object //throws IOError { if (readBoolean()) { - var streamer :Streamer = Streamer.getStreamerByClass(clazz); + var streamer :Streamer = (type is Class) + ? Streamer.getStreamerByClass(type as Class) + : Streamer.getStreamerByJavaName(type as String); + var obj :Object = streamer.createObject(this); streamer.readObject(obj, this); return obj; diff --git a/src/as/com/threerings/io/ObjectOutputStream.as b/src/as/com/threerings/io/ObjectOutputStream.as index bc5f7bbb6..36c7bf466 100644 --- a/src/as/com/threerings/io/ObjectOutputStream.as +++ b/src/as/com/threerings/io/ObjectOutputStream.as @@ -24,7 +24,13 @@ public class ObjectOutputStream return; } - var cname :String = ClassUtil.getClassName(obj); + var cname :String; + if (obj is TypedArray) { + cname = (obj as TypedArray).getJavaType(); + + } else { + cname = ClassUtil.getClassName(obj); + } // look up the class mapping record var cmap :ClassMapping = (_classMap.get(cname) as ClassMapping); @@ -166,7 +172,7 @@ public class ObjectOutputStream /** * Used by a Streamer that is writing an array of Streamable instances. */ - protected function setCurrent (streamer :Streamer, current :Object) + internal function setCurrent (streamer :Streamer, current :Object) { _streamer = streamer; _current = current; diff --git a/src/as/com/threerings/io/Streamer.as b/src/as/com/threerings/io/Streamer.as index 3e688afc5..94fb8b77c 100644 --- a/src/as/com/threerings/io/Streamer.as +++ b/src/as/com/threerings/io/Streamer.as @@ -10,6 +10,7 @@ import com.threerings.io.streamers.ArrayStreamer; import com.threerings.io.streamers.ByteArrayStreamer; import com.threerings.io.streamers.IntStreamer; import com.threerings.io.streamers.NumberStreamer; +import com.threerings.io.streamers.ObjectArrayStreamer; import com.threerings.io.streamers.StringStreamer; public class Streamer @@ -28,6 +29,13 @@ public class Streamer } } + if (obj is TypedArray) { + var streamer :Streamer = new ArrayStreamer( + (obj as TypedArray).getJavaType()); + _streamers.push(streamer); + return streamer; + } + return undefined; } @@ -35,6 +43,10 @@ public class Streamer { initStreamers(); + if (clazz === TypedArray) { + throw new Error("Broken, TODO"); + } + for each (var streamer :Streamer in _streamers) { if (streamer._target == clazz) { return streamer; @@ -48,16 +60,18 @@ public class Streamer { initStreamers(); - if (jname.charAt(0) === "[") { - // Oh ghod - } - for each (var streamer :Streamer in _streamers) { if (streamer.getJavaClassName() === jname) { return streamer; } } + if (jname.charAt(0) === "[") { + var streamer :Streamer = new ArrayStreamer(jname); + _streamers.push(streamer); + return streamer; + } + return null; } @@ -122,7 +136,7 @@ public class Streamer new StringStreamer(), new IntStreamer(), new NumberStreamer(), - new ArrayStreamer(), + new ObjectArrayStreamer(), new ByteArrayStreamer() ]; } @@ -132,10 +146,6 @@ public class Streamer protected var _jname :String; - /** If our target class is an array, this is a reference to a streamer - * that can stream our array elements, otherwise it is null. */ - protected var _delegate :Streamer; - /** Just a list of our standard streamers. */ protected static var _streamers :Array; } diff --git a/src/as/com/threerings/io/TypedArray.as b/src/as/com/threerings/io/TypedArray.as new file mode 100644 index 000000000..60761b657 --- /dev/null +++ b/src/as/com/threerings/io/TypedArray.as @@ -0,0 +1,19 @@ +package com.threerings.io { + +public dynamic class TypedArray extends Array +{ + public function TypedArray (jtype :String) + { + _jtype = jtype; + } + + public function getJavaType () :String + { + return _jtype; + } + + /** The 'type' of this array, which doesn't really mean anything + * except gives it a clue as to how to stream to our server. */ + protected var _jtype :String; +} +} diff --git a/src/as/com/threerings/io/streamers/ArrayStreamer.as b/src/as/com/threerings/io/streamers/ArrayStreamer.as index aaca73032..8e5023dfe 100644 --- a/src/as/com/threerings/io/streamers/ArrayStreamer.as +++ b/src/as/com/threerings/io/streamers/ArrayStreamer.as @@ -1,22 +1,55 @@ package com.threerings.io.streamers { +import com.threerings.util.ClassUtil; + import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectOutputStream; import com.threerings.io.Streamer; +import com.threerings.io.TypedArray; + +import com.threerings.presents.Log; /** * A Streamer for Array objects. */ public class ArrayStreamer extends Streamer { - public function ArrayStreamer () + public function ArrayStreamer (jname :String) { - super(Array, "[Ljava.lang.Object"); + super(TypedArray, jname); + + if (jname.charAt(1) === "[") { + // if we're a multi-dimensional array then we need a delegate + _delegate = Streamer.getStreamerByJavaName(jname.substring(1)); + _isFinal = true; // it just is + + } else { + if (jname.charAt(1) === "L") { + // form is "[L;" + var baseClass :String = jname.substring(2, jname.length - 1); + baseClass = Translations.getFromServer(baseClass); + _elementType = ClassUtil.getClassByName(baseClass); + _isFinal = ClassUtil.isFinal(_elementType); + + } else { + Log.warning("Other array types are currently not handled yet " + + "[jname=" + jname + "]."); + throw new Error("Unimplemented bit"); + } + } + } + + public override function isStreamerFor (obj :Object) :Boolean + { + return (obj is TypedArray) && + ((obj as TypedArray).getJavaType() === _jname); } public override function createObject (ins :ObjectInputStream) :Object { - return new Array(ins.readInt()); + var ta :TypedArray = new TypedArray(_jname); + ta.length = ins.readInt(); + return ta; } public override function writeObject (obj :Object, out :ObjectOutputStream) @@ -24,8 +57,26 @@ public class ArrayStreamer extends Streamer { var arr :Array = (obj as Array); out.writeInt(arr.length); - for (var ii :int = 0; ii < arr.length; ii++) { - out.writeObject(arr[ii]); + if (_isFinal) { + var mask :ArrayMask = new ArrayMask(arr.length); + for (var ii :int = 0; ii < arr.length; ii++) { + if (arr[ii] != null) { + mask.setBit(ii); + } + } + mask.writeTo(out); + // now write the populated elements + for (var ii :int = 0; ii < arr.length; ii++) { + var element :Object = arr[ii]; + if (element != null) { + out.writeBareObjectImpl(element, _delegate); + } + } + + } else { + for (var ii :int = 0; ii < arr.length; ii++) { + out.writeObject(arr[ii]); + } } } @@ -33,9 +84,37 @@ public class ArrayStreamer extends Streamer :void { var arr :Array = (obj as Array); - for (var ii :int = 0; ii < arr.length; ii++) { - arr[ii] = ins.readObject(); + if (_isFinal) { + var mask :ArrayMask = new ArrayMask(); + mask.readFrom(ins); + for (var ii :int = 0; ii < length; ii++) { + if (mask.isSet(ii)) { + var target :Object; + if (_delegate == null) { + target = new _elementType(); + } else { + target = _delegate.createObject(ins); + } + ins.readBareObjectImpl(target, _delegate); + this[ii] = target; + } + } + + } else { + for (var ii :int = 0; ii < arr.length; ii++) { + arr[ii] = ins.readObject(); + } } } + + /** A streamer for our elements. */ + protected var _delegate :Streamer; + + /** If this is the final dimension of the array, the element type. */ + protected var _elementType :Class; + + /** Whether we're final or not: true if we're not the final dimension + * or if the element type is final. */ + protected var _isFinal :Boolean; } } diff --git a/src/as/com/threerings/io/streamers/ObjectArrayStreamer.as b/src/as/com/threerings/io/streamers/ObjectArrayStreamer.as new file mode 100644 index 000000000..153df8d54 --- /dev/null +++ b/src/as/com/threerings/io/streamers/ObjectArrayStreamer.as @@ -0,0 +1,48 @@ +package com.threerings.io.streamers { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamer; +import com.threerings.io.TypedArray; + +/** + * A Streamer for Array objects. + */ +public class ObjectArrayStreamer extends Streamer +{ + public function ObjectArrayStreamer () + { + super(Array, "[Ljava.lang.Object;"); + } + + public override function isStreamerFor (obj :Object) :Boolean + { + // don't let TypedArrays be streamed with this streamer + return !(obj is TypedArray) && super.isStreamerFor(obj); + } + + public override function createObject (ins :ObjectInputStream) :Object + { + return new Array(ins.readInt()); + } + + public override function writeObject (obj :Object, out :ObjectOutputStream) + :void + { + var arr :Array = (obj as Array); + out.writeInt(arr.length); + for (var ii :int = 0; ii < arr.length; ii++) { + out.writeObject(arr[ii]); + } + } + + public override function readObject (obj :Object, ins :ObjectInputStream) + :void + { + var arr :Array = (obj as Array); + for (var ii :int = 0; ii < arr.length; ii++) { + arr[ii] = ins.readObject(); + } + } +} +} diff --git a/src/as/com/threerings/presents/dobj/DObject.as b/src/as/com/threerings/presents/dobj/DObject.as index bd3f96003..eaea8684b 100644 --- a/src/as/com/threerings/presents/dobj/DObject.as +++ b/src/as/com/threerings/presents/dobj/DObject.as @@ -29,6 +29,9 @@ public class DObject // extends EventDispatcher public function addSubscriber (sub :Subscriber) :void { + if (_subscribers == null) { + _subscribers = new ArrayCollection(); + } if (!_subscribers.contains(sub)) { _subscribers.addItem(sub); } @@ -36,6 +39,9 @@ public class DObject // extends EventDispatcher public function removeSubscriber (sub :Subscriber) :void { + if (_subscribers == null) { + return; + } var dex :int = _subscribers.getItemIndex(sub); if (dex != -1) { _subscribers.removeItemAt(dex); diff --git a/src/as/com/threerings/util/ClassUtil.as b/src/as/com/threerings/util/ClassUtil.as index f727ba695..8568055a2 100644 --- a/src/as/com/threerings/util/ClassUtil.as +++ b/src/as/com/threerings/util/ClassUtil.as @@ -32,11 +32,13 @@ public class ClassUtil public static function isFinal (type :Class) :Boolean { - var attrs :XMLList = describeType(type).elements("type"); + if (type === String) { + return true; + } - // FUCK!!! This information is lost in the Class introspection - - return true; //TODO + // TODO: there's currently no way to determine final from the class + //var attrs :XMLList = describeType(type).elements("type"); + return false; } } } diff --git a/src/as/com/threerings/util/TypedArray.as b/src/as/com/threerings/util/TypedArray.as deleted file mode 100644 index ab39ea13c..000000000 --- a/src/as/com/threerings/util/TypedArray.as +++ /dev/null @@ -1,89 +0,0 @@ -package com.threerings.util { - -import mx.utils.ObjectUtil; - -import com.threerings.io.ArrayMask; -import com.threerings.io.Streamer; - -import com.threerings.io.ObjectInputStream; -import com.threerings.io.ObjectOutputStream; -import com.threerings.io.Streamable; - -import com.threerings.presents.Log; - -public dynamic class TypedArray extends Array - implements Streamable -{ - // TODO: remove the isFinal parameter and determine it - // be introspecting on the class - public function TypedArray (type :Class, isFinal :Boolean, length :int = 0) - { - super(length); - _type = type; - _final = isFinal; - _delegate = Streamer.getStreamerByClass(type); - } - - // documentation inherited from interface Streamable - public function readObject (ins :ObjectInputStream) :void - { - if (_final) { - var mask = new ArrayMask(); - mask.readFrom(ins); - for (var ii :int = 0; ii < length; ii++) { - if (mask.isSet(ii)) { - var target :Object; - if (_delegate == null) { - target = new _type(); - } else { - target = _delegate.createObject(ins); - } - ins.readBareObjectImpl(target, _delegate); - this[ii] = target; - } - } - - } else { - for (var ii :int = 0; ii < length; ii++) { - this[ii] = ins.readObject(); - } - } - } - - // documentation inherited from interface Streamable - public function writeObject (out :ObjectOutputStream) :void - { - out.writeInt(length); - if (_final) { - var mask :ArrayMask = new ArrayMask(length); - for (var ii :int = 0; ii < length; ii++) { - if (this[ii] != null) { - mask.set(ii); - } - } - mask.writeTo(out); - // now write the populated elements - for (var ii :int = 0; ii < length; ii++) { - var element :Object = this[ii]; - if (element != null) { - out.writeBareObjectImpl(element, _delegate); - } - } - - } else { - for (var ii :int = 0; ii < length; ii++) { - out.writeObject(this[ii]); - } - } - } - - /** The 'type' of this array, which doesn't really mean anything - * except gives it a clue as to how to stream to our server. */ - protected var _type :Class; - - /** Whether or not the type of the array represents a final class. */ - protected var _final :Boolean; - - protected var _delegate :Streamer; -} -}