From 1d36299e3c081dbbcddfc27672c2eb92d262a406 Mon Sep 17 00:00:00 2001 From: Ray Greenwell Date: Wed, 15 Feb 2006 01:41:14 +0000 Subject: [PATCH] Some work on streaming. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3855 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- src/as/com/threerings/README.txt | 7 + src/as/com/threerings/io/ArrayMask.as | 45 ++++++ src/as/com/threerings/io/ObjectInputStream.as | 62 +++++--- .../com/threerings/io/ObjectOutputStream.as | 79 +++++----- src/as/com/threerings/io/Streamer.as | 137 ++++++++---------- .../threerings/io/streamers/ArrayStreamer.as | 8 +- .../io/streamers/ByteArrayStreamer.as | 8 +- .../threerings/io/streamers/IntStreamer.as | 8 +- .../threerings/io/streamers/NumberStreamer.as | 8 +- .../threerings/io/streamers/StringStreamer.as | 8 +- .../presents/client/Communicator.as | 1 + src/as/com/threerings/util/ClassUtil.as | 16 +- src/as/com/threerings/util/Name.as | 4 +- 13 files changed, 216 insertions(+), 175 deletions(-) diff --git a/src/as/com/threerings/README.txt b/src/as/com/threerings/README.txt index 29dc50b1e..8ead7c4a1 100644 --- a/src/as/com/threerings/README.txt +++ b/src/as/com/threerings/README.txt @@ -51,3 +51,10 @@ Notes - constructors do not defaultly call super()- be sure to do it explicitely. Maybe we should get in the habit of doing it in Java for consistency and explicitness. + +- I'm a little shaky still about how I'm going to handle arrays. In + ActionScript all arrays (except ByteArray) are the same type: Array. + If class A is extended by B and C, the server could pass around an array of + A, filled with B and C elements. I would have no way on the client to + inspect an array like that and know to tell the server that it's an A[]. + Punting completely on Arrays for now. diff --git a/src/as/com/threerings/io/ArrayMask.as b/src/as/com/threerings/io/ArrayMask.as index 353fa04e6..25c77d44c 100644 --- a/src/as/com/threerings/io/ArrayMask.as +++ b/src/as/com/threerings/io/ArrayMask.as @@ -1,6 +1,51 @@ package com.threerings.io { +import flash.util.ByteArray; + public class ArrayMask + implements Streamable { + public function ArrayMask (length :int = 0) + { + var mlength :int = length / 8; + if (length % 8 != 0) { + mlength++; + } + _mask.length = mlength; + } + + /** + * Set the specified index as containing a non-null element in the + * array we're representing. + */ + public function setBit (index :int) :void + { + _mask[index/8] |= (1 << (index % 8)); + } + + /** + * Is the specified array element non-null? + */ + public function isSet (index :int) :Boolean + { + return (_mask[index/8] & (1 << (index % 8))) != 0; + } + + // documentation inherited from interface Streamable + public function writeObject (out :ObjectOutputStream) :void + { + out.writeShort(_mask.length); + out.writeBytes(_mask); + } + + // documentation inherited from interface Streamable + public function readObject (ins :ObjectInputStream) :void + { + _mask.length = ins.readShort(); + ins.readBytes(_mask, 0, _mask.length); + } + + /** The array mask. */ + protected var _mask :ByteArray = new ByteArray(); } } diff --git a/src/as/com/threerings/io/ObjectInputStream.as b/src/as/com/threerings/io/ObjectInputStream.as index 8a9f9e262..95de8f07d 100644 --- a/src/as/com/threerings/io/ObjectInputStream.as +++ b/src/as/com/threerings/io/ObjectInputStream.as @@ -29,8 +29,6 @@ public class ObjectInputStream public function readObject () :* //throws IOError { - var cmap :ClassMapping; - try { // read in the class code for this instance var code :int = readShort(); @@ -38,16 +36,19 @@ public class ObjectInputStream // a zero code indicates a null value if (code == 0) { return null; + } + + var cmap :ClassMapping; // if the code is negative, that means we've never seen it // before and class metadata follows - } else if (code < 0) { + if (code < 0) { // first swap the code into positive land code *= -1; // read in the class metadata var cname :String = readUTF(); - var streamer :Streamer = Streamer.getStreamer(cname); + var streamer :Streamer = Streamer.getStreamerByJavaName(cname); cmap = new ClassMapping(code, cname, streamer); _classMap[code] = cmap; @@ -60,34 +61,41 @@ public class ObjectInputStream } } - // create an instance of the appropriate object - _streamer = cmap.streamer; - var target :* = _streamer.createObject(this); - _current = target; + var target :*; + if (cmap.streamer === null) { + var clazz :Class = flash.util.getClassByName(cmap.cname); + target = new clazz(); - // now read the instance data - _streamer.readObject(target, this, true); - - // and return the newly read object + } else { + target = cmap.streamer.createObject(this); + } + readBareObjectImpl(target, cmap.streamer); return target; - } finally { - // clear out our current object references - _current = null; - _streamer = null; + } catch (MemoryError me) { + throw new IOError("out of memory" + me.message); } } public function readBareObject (obj :*) :void //throws IOError { - try { - // obtain the streamer for the objects of this class - _streamer = Streamer.getStreamer(ClassUtil.getClassName(obj)); - _current = obj; + readBareObjectImpl(obj, Streamer.getStreamer(obj)); + } - // now read the instance data - _streamer.readObject(obj, this, true); + protected function readBareObjectImpl (obj :*, streamer :Streamer) :void + { + // streamable objects + if (streamer == null) { + var sable :Streamable = (obj as Streamable); + sable.readObject(this); + return; + } + + _current = obj; + _streamer = steamer; + try { + _streamer.readObject(obj, this); } finally { // clear out our current object references @@ -125,9 +133,15 @@ public class ObjectInputStream } public function readBytes (bytes :ByteArray, offset :uint = 0, - length :uint = 0) :void + length :uint = undefined) :void //throws IOError { + // IDataInput reads all available bytes if a length is not passed + // in. Protect against an easy error to make by using the length of + // the array + if (length === undefined) { + length = bytes.length; + } _source.readBytes(bytes, offset, length); } @@ -180,6 +194,6 @@ public class ObjectInputStream protected var _streamer :Streamer; /** A map of short class code to ClassMapping info. */ - protected var _classMap :SimpleMap = new SimpleMap(); + protected var _classMap :Array = new Array(); } } diff --git a/src/as/com/threerings/io/ObjectOutputStream.as b/src/as/com/threerings/io/ObjectOutputStream.as index 94594fed2..2a030d78c 100644 --- a/src/as/com/threerings/io/ObjectOutputStream.as +++ b/src/as/com/threerings/io/ObjectOutputStream.as @@ -7,7 +7,6 @@ import com.threerings.util.ClassUtil; import com.threerings.util.SimpleMap; public class ObjectOutputStream -// implements IDataOutput { public function ObjectOutputStream (targ :IDataOutput) { @@ -23,57 +22,55 @@ public class ObjectOutputStream return; } - try { - var cname :String = ClassUtil.getClassName(obj); + var cname :String = ClassUtil.getClassName(obj); + // look up the class mapping record + var cmap :ClassMapping = _classMap[cname]; - // look up the class mapping record - var cmap :ClassMapping = _classMap[cname]; - _current = obj; - - // create a class mapping if we've not got one - if (cmap === undefined) { - var streamer :Streamer = Streamer.getStreamer(cname); - if (streamer === undefined) { - // TODO - trace("OMG, cannot stream ", cname); - return; - } - - cmap = new ClassMapping(_nextCode++, cname, streamer); - _classMap[cname] = cmap; - - // TODO: if _nextCode blows short, log an error - - writeShort(-cmap.code); - writeUTF(cname); - - } else { - writeShort(cmap.code); + // create a class mapping if we've not got one + if (cmap === undefined) { + var streamer :Streamer = Streamer.getStreamer(obj); + if (streamer === undefined) { + // TODO + trace("OMG, cannot stream ", cname); + return; } - // now write the instance data - _streamer = cmap.streamer; - _streamer.writeObject(obj, this, true); + cmap = new ClassMapping(_nextCode++, cname, streamer); + _classMap[cname] = cmap; - } finally { - _current = null; - _streamer = null; + // TODO: if _nextCode blows short, log an error + + writeShort(-cmap.code); + writeUTF((streamer == null) ? cname : streamer.getJavaClassName()); + + } else { + writeShort(cmap.code); } + + writeBareObjectImpl(obj, cmap.streamer); } public function writeBareObject (obj :*) :void //throws IOError { + writeBareObjectImpl(obj, Streamer.getStreamer(obj)); + } + + protected function writeBareObjectImpl (obj :*, streamer :Streamer) + { + // if it's Streamable, it goes straight through + if (streamer == null) { + var sable :Streamable = (obj as Streamable); + sable.writeObject(this); + return; + } + + // otherwise, stream it! + _current = obj; + _streamer = streamer; try { - // get the streamer for objects of this type - _streamer = Streamer.getStreamer(ClassUtil.getClassName(obj)); - _current = obj; - - // now write the instance data - _streamer.writeObject(obj, this, true); - + _streamer.writeObject(obj, this); } finally { - // clear out our current object references _current = null; _streamer = null; } @@ -147,7 +144,7 @@ public class ObjectOutputStream public function writeShort (value :int) :void //throws IOError { - _targ.writeInt(value); + _targ.writeShort(value); } public function writeUTF (value :String) :void diff --git a/src/as/com/threerings/io/Streamer.as b/src/as/com/threerings/io/Streamer.as index eab2504cc..5237f530c 100644 --- a/src/as/com/threerings/io/Streamer.as +++ b/src/as/com/threerings/io/Streamer.as @@ -32,87 +32,76 @@ public class Streamer public static function getStreamer (obj :*) :Streamer { - if (_streamerMap == null) { + if (obj is Streamable) { + return null; + } + + if (_streamers == null) { createStreamers(); } - if (obj is Streamable) { - return null; - - } else if (obj is String) { - return STRING_STREAMER; - - } else if (obj is int) { - return INT_STREAMER; - - } else if (obj is Number) { - return NUMBER_STREAMER; - - } else if (obj is Array) { - return ARRAY_STREAMER; - - } else if (obj is ByteArray) { - return BYTE_ARRAY_STREAMER; - - } else { - return undefined; + var s :Streamer; + for (var ii :int = 0; ii < _streamers.lenght; ii++) { + s = (_streamers[ii] as Streamer); + if (s.isStreamerFor(obj)) { + return s; + } } + + return undefined; } public static function getStreamerByJavaName (jname :String) :Streamer { - if (jname === "java.lang.String") { - return STRING_STREAMER; - - } else if (jname === "java.lang.Integer") { - return INT_STREAMER; - - } else if (jname === "java.lang.Double") { - return NUMBER_STREAMER; - - } else if (jname === "[Ljava.lang.Object") { - return ARRAY_STREAMER; - - } else if (jname === "[B") { - return BYTE_ARRAY_STREAMER; - - } else { - return null; + if (_streamers == null) { + createStreamers(); } + + var s :Streamer; + for (var ii :int = 0; ii < _streamers.length; ii++) { + s = (_streamers[ii] as Streamer); + if (s.getJavaClassName() === jname) { + return s; + } + } + + return null; } /** This should be a protected constructor. */ - public function Streamer (targ :Class) + public function Streamer (targ :Class, jname :String) //throws IOError { _targ = targ; + _jname = jname; + } + + public function isStreamerFor (obj :*) :Boolean + { + return (obj is _targ); // scripting langs are weird } /** * Return the String to use to identify the class that we're streaming. */ - // TODO - public function getClassName () :String + public function getJavaClassName () :String { - return "TODO: javaname"; + return _jname; } - public function writeObject (obj :*, out :ObjectOutputStream, - useWriter :Boolean) :void + public function writeObject (obj :*, out :ObjectOutputStream) :void //throws IOError { - // TODO: check use of isProtoTypeOf, it's unclear if it's - // going to do what we want - if (useWriter && obj.isPrototypeOf(Streamable)) { - obj.writeObject(out); - return; + trace("TODO"); + + if (obj is Array) { + trace("Arrays not yet done. Crap!"); + /** + var arr :Array = (obj as Array); // not strictly necessary + var length :int = arr.length; + out.writeInt(length); + */ } - - // TODO: cope with arrays - - // write out the fields... this is bogus because - // we just want to call the streamer method - obj.writeObject(out); } public function createObject (ins :ObjectInputStream) :* @@ -122,22 +111,10 @@ public class Streamer return new _targ(); } - public function readObject (obj :*, ins :ObjectInputStream, - useReader :Boolean) :void + public function readObject (obj :*, ins :ObjectInputStream) :void //throws IOError { - // TODO: check use of isProtoTypeOf, it's unclear if it's - // going to do what we want - if (useReader && obj.isPrototypeOf(Streamable)) { - obj.readObject(ins); - return; - } - - // TODO: cope with arrays - - // read in the fields... this is bogus because we just - // want to call the streamer method - obj.readObject(ins); + trace("TODO"); } /** @@ -146,19 +123,23 @@ public class Streamer */ protected static function createStreamers () :void { - _streamerMap = new SimpleMap(); + _streamers = new Array(); + + // add our default streamers + _streamers.push( + new StringStreamer(), + new IntStreamer(), + new NumberStreamer(), + new ArrayStreamer(), + new ByteArrayStreamer() + ); } protected var _targ :Class; - protected static var _streamerMap :SimpleMap; + protected var _jname :String; - - protected static const STRING_STREAMER :Streamer = new StringStreamer(); - protected static const BYTE_ARRAY_STREAMER :Streamer = - new ByteArrayStreamer(); - protected static const ARRAY_STREAMER :Streamer = new ArrayStreamer(); - protected static const INT_STREAMER :Streamer = new IntStreamer(); - protected static const NUMBER_STREAMER :Streamer = new NumberStreamer(); + /** Just a list of our standard streamers. */ + protected static var _streamers :Array; } } diff --git a/src/as/com/threerings/io/streamers/ArrayStreamer.as b/src/as/com/threerings/io/streamers/ArrayStreamer.as index b7bd4a44f..085dc42a2 100644 --- a/src/as/com/threerings/io/streamers/ArrayStreamer.as +++ b/src/as/com/threerings/io/streamers/ArrayStreamer.as @@ -11,7 +11,7 @@ public class ArrayStreamer extends Streamer { public function ArrayStreamer () { - super(Array); + super(Array, "[Ljava.lang.Object"); } public override function createObject (ins :ObjectInputStream) :* @@ -19,8 +19,7 @@ public class ArrayStreamer extends Streamer return new Array(ins.readInt()); } - public override function writeObject (obj :*, out :ObjectOutputStream, - useWriter :Boolean) :void + public override function writeObject (obj :*, out :ObjectOutputStream) :void { var arr :Array = (obj as Array); out.writeInt(arr.length); @@ -29,8 +28,7 @@ public class ArrayStreamer extends Streamer } } - public override function readObject (obj :*, ins :ObjectInputStream, - useReader :Boolean) :void + public override function readObject (obj :*, ins :ObjectInputStream) :void { var arr :Array = (obj as Array); for (var ii :int = 0; ii < arr.length; ii++) { diff --git a/src/as/com/threerings/io/streamers/ByteArrayStreamer.as b/src/as/com/threerings/io/streamers/ByteArrayStreamer.as index 43444725d..64a364395 100644 --- a/src/as/com/threerings/io/streamers/ByteArrayStreamer.as +++ b/src/as/com/threerings/io/streamers/ByteArrayStreamer.as @@ -13,7 +13,7 @@ public class ByteArrayStreamer extends Streamer { public function ByteArrayStreamer () { - super(ByteArray); + super(ByteArray, "[B"); // yes, that's the Java class for a byte[]. } public override function createObject (ins :ObjectInputStream) :* @@ -23,16 +23,14 @@ public class ByteArrayStreamer extends Streamer return bytes; } - public override function writeObject (obj :*, out :ObjectOutputStream, - useWriter :Boolean) :void + public override function writeObject (obj :*, out :ObjectOutputStream) :void { var bytes :ByteArray = (obj as ByteArray); out.writeInt(bytes.length); out.writeBytes(bytes); } - public override function readObject (obj :*, ins :ObjectInputStream, - useReader :Boolean) :void + public override function readObject (obj :*, ins :ObjectInputStream) :void { var bytes :ByteArray = (obj as ByteArray); ins.readBytes(bytes, 0, bytes.length); diff --git a/src/as/com/threerings/io/streamers/IntStreamer.as b/src/as/com/threerings/io/streamers/IntStreamer.as index 88688d3c3..eeae2fe71 100644 --- a/src/as/com/threerings/io/streamers/IntStreamer.as +++ b/src/as/com/threerings/io/streamers/IntStreamer.as @@ -11,7 +11,7 @@ public class IntStreamer extends Streamer { public function IntStreamer () { - super(int); + super(int, "java.lang.Integer"); } public override function createObject (ins :ObjectInputStream) :* @@ -19,15 +19,13 @@ public class IntStreamer extends Streamer return ins.readInt(); } - public override function writeObject (obj :*, out :ObjectOutputStream, - useWriter :Boolean) :void + public override function writeObject (obj :*, out :ObjectOutputStream) :void { var i :int = (obj as int); out.writeInt(i); } - public override function readObject (obj :*, ins :ObjectInputStream, - useReader :Boolean) :void + public override function readObject (obj :*, ins :ObjectInputStream) :void { // nothing here, the int is fully read in createObject() } diff --git a/src/as/com/threerings/io/streamers/NumberStreamer.as b/src/as/com/threerings/io/streamers/NumberStreamer.as index 78181c73b..ca224c83e 100644 --- a/src/as/com/threerings/io/streamers/NumberStreamer.as +++ b/src/as/com/threerings/io/streamers/NumberStreamer.as @@ -11,7 +11,7 @@ public class NumberStreamer extends Streamer { public function NumberStreamer () { - super(Number); + super(Number, "java.lang.Double"); } public override function createObject (ins :ObjectInputStream) :* @@ -19,15 +19,13 @@ public class NumberStreamer extends Streamer return ins.readDouble(); } - public override function writeObject (obj :*, out :ObjectOutputStream, - useWriter :Boolean) :void + public override function writeObject (obj :*, out :ObjectOutputStream) :void { var n :Number = (obj as Number); out.writeDouble(n); } - public override function readObject (obj :*, ins :ObjectInputStream, - useReader :Boolean) :void + public override function readObject (obj :*, ins :ObjectInputStream) :void { // nothing here, the Number is fully read in createObject() } diff --git a/src/as/com/threerings/io/streamers/StringStreamer.as b/src/as/com/threerings/io/streamers/StringStreamer.as index a6be5a765..83e38978d 100644 --- a/src/as/com/threerings/io/streamers/StringStreamer.as +++ b/src/as/com/threerings/io/streamers/StringStreamer.as @@ -11,7 +11,7 @@ public class StringStreamer extends Streamer { public function StringStreamer () { - super(String); + super(String, "java.lang.String"); } public override function createObject (ins :ObjectInputStream) :* @@ -19,15 +19,13 @@ public class StringStreamer extends Streamer return ins.readUTF(); } - public override function writeObject (obj :*, out :ObjectOutputStream, - useWriter :Boolean) :void + public override function writeObject (obj :*, out :ObjectOutputStream) :void { var s :String = (obj as String); out.writeUTF(s); } - public override function readObject (obj :*, ins :ObjectInputStream, - useReader :Boolean) :void + public override function readObject (obj :*, ins :ObjectInputStream) :void { // nothing here, the String is fully read in createObject() } diff --git a/src/as/com/threerings/presents/client/Communicator.as b/src/as/com/threerings/presents/client/Communicator.as index e217db525..6df5253e3 100644 --- a/src/as/com/threerings/presents/client/Communicator.as +++ b/src/as/com/threerings/presents/client/Communicator.as @@ -21,6 +21,7 @@ public class Communicator { // create the socket and set up listeners _socket = new Socket(); + _socket.endian = Endian.BIG_ENDIAN; _socket.addEventListener(Event.CONNECT, socketOpened); _socket.addEventListener(IOErrorEvent.IO_ERROR, socketError); _socket.addEventListener(Event.CLOSE, socketClosed); diff --git a/src/as/com/threerings/util/ClassUtil.as b/src/as/com/threerings/util/ClassUtil.as index 91077e191..58bf6e5a6 100644 --- a/src/as/com/threerings/util/ClassUtil.as +++ b/src/as/com/threerings/util/ClassUtil.as @@ -1,12 +1,20 @@ package com.threerings.util { -import flash.util.*; - -public class ClassUtil extends Object +public class ClassUtil { public static function getClassName (obj :*) :String { - return getQualifiedClassName(obj).replace("::", "."); + return flash.util.getQualifiedClassName(obj).replace("::", "."); + } + + public static function getClass (obj :*) :Class + { + return flash.util.getClassByName(getClassName(obj)); + } + + public static function getClassByName (cname :String) :Class + { + return flash.util.getClassByName(cname); } } } diff --git a/src/as/com/threerings/util/Name.as b/src/as/com/threerings/util/Name.as index c74f1cce3..97c27991f 100644 --- a/src/as/com/threerings/util/Name.as +++ b/src/as/com/threerings/util/Name.as @@ -30,7 +30,7 @@ public class Name extends Object return Name.isBlank(this); } - public /*override*/ function toString () :String + public override function toString () :String { return _name; } @@ -65,8 +65,6 @@ public class Name extends Object out.writeUTF(_name); } */ - - } // documentation inherited from interface Streamable