diff --git a/src/as/com/threerings/io/ArrayMask.as b/src/as/com/threerings/io/ArrayMask.as new file mode 100644 index 000000000..353fa04e6 --- /dev/null +++ b/src/as/com/threerings/io/ArrayMask.as @@ -0,0 +1,6 @@ +package com.threerings.io { + +public class ArrayMask +{ +} +} diff --git a/src/as/com/threerings/io/ClassMapping.as b/src/as/com/threerings/io/ClassMapping.as new file mode 100644 index 000000000..12522c05f --- /dev/null +++ b/src/as/com/threerings/io/ClassMapping.as @@ -0,0 +1,17 @@ +package com.threerings.io { + +public class ClassMapping +{ + public var code :int; + public var classname :String; + public var streamer :Streamer; + + public function ClassMapping (code :int, classname :String, + streamer :Streamer) + { + this.code = code; + this.classname = classname; + this.streamer = streamer; + } +} +} diff --git a/src/as/com/threerings/io/ObjectInputStream.as b/src/as/com/threerings/io/ObjectInputStream.as new file mode 100644 index 000000000..8910d6ed8 --- /dev/null +++ b/src/as/com/threerings/io/ObjectInputStream.as @@ -0,0 +1,174 @@ +package com.threerings.io { + +import flash.errors.IOError; + +import flash.util.ByteArray; +import flash.util.IDataInput; + +import com.threerings.util.ClassUtil; +import com.threerings.util.SimpleMap; + +public class ObjectInputStream +{ + public function ObjectInputStream (targ:IDataInput) + { + _targ = targ; + } + + public function readObject () :* + //throws IOError + { + var cmap :ClassMapping; + + try { + // read in the class code for this instance + var code :int = readShort(); + + // a zero code indicates a null value + if (code == 0) { + return null; + + // if the code is negative, that means we've never seen it + // before and class metadata follows + } else 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); + + cmap = new ClassMapping(code, cname, streamer); + _classMap[code] = cmap; + + } else { + cmap = _classMap[code]; + if (null == cmap) { + throw new IOError("Read object for which we have no " + + "registered class metadata."); + } + } + + // create an instance of the appropriate object + _streamer = cmap.streamer; + var target :* = _streamer.createObject(this); + _current = target; + + // now read the instance data + _streamer.readObject(target, this, true); + + // and return the newly read object + return target; + + } finally { + // clear out our current object references + _current = null; + _streamer = null; + } + } + + 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; + + // now read the instance data + _streamer.readObject(obj, this, true); + + } finally { + // clear out our current object references + _current = null; + _streamer = null; + } + } + + public function readField (clazz :Class) :* + //throws IOError + { + if (readBoolean()) { + var obj :* = new clazz(); + return readBareObject(obj); + } + return null; + } + + public function defaultReadObject () :void + //throws IOError + { + _streamer.readObject(_current, this, false); + } + + public function readBoolean () :Boolean + //throws IOError + { + return _targ.readBoolean(); + } + + public function readByte () :int + //throws IOError + { + return _targ.readByte(); + } + + public function readBytes (bytes :ByteArray, offset :uint = 0, + length :uint = 0) :void + //throws IOError + { + _targ.readBytes(bytes, offset, length); + } + + public function readDouble () :Number + //throws IOError + { + return _targ.readDouble(); + } + + public function readFloat () :Number + //throws IOError + { + return _targ.readFloat(); + } + + public function readInt () :int + //throws IOError + { + return _targ.readInt(); + } + + public function readShort () :int + //throws IOError + { + return _targ.readShort(); + } + + public function readUTF () :String + //throws IOError + { + return _targ.readUTF(); + } + + /** + * Used by a Streamer that is reading an array of Streamable instances. + */ + protected function setCurrent (streamer :Streamer, current :*) + { + _streamer = streamer; + _current = current; + } + + /** The target DataInput that we route input from. */ + protected var _targ :IDataInput; + + /** The object currently being read from the stream. */ + protected var _current :*; + + /** The stramer being used currently. */ + protected var _streamer :Streamer; + + /** A map of short class code to ClassMapping info. */ + protected var _classMap :SimpleMap = new SimpleMap(); +} +} diff --git a/src/as/com/threerings/io/ObjectOutputStream.as b/src/as/com/threerings/io/ObjectOutputStream.as new file mode 100644 index 000000000..ebd41e202 --- /dev/null +++ b/src/as/com/threerings/io/ObjectOutputStream.as @@ -0,0 +1,182 @@ +package com.threerings.io { + +import flash.util.ByteArray; +import flash.util.IDataOutput; + +import com.threerings.util.ClassUtil; +import com.threerings.util.SimpleMap; + +public class ObjectOutputStream +// implements IDataOutput +{ + public function ObjectOutputStream (targ :IDataOutput) + { + _targ = targ; + } + + public function writeObject (obj :*) :void + //throws IOError + { + // if the object to be written is null (or undefined) write a zero + if (obj == null) { + writeShort(0); + return; + } + + try { + var cname :String = ClassUtil.getClassName(obj); + + // 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); + 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); + } + + // now write the instance data + _streamer = cmap.streamer; + _streamer.writeObject(obj, this, true); + + } finally { + _current = null; + _streamer = null; + } + } + + public function writeBareObject (obj :*) :void + //throws IOError + { + 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); + + } finally { + // clear out our current object references + _current = null; + _streamer = null; + } + } + + public function writeField (val :*) :void + //throws IOError + { + var b :Boolean = (val != null); + writeBoolean(b); + if (b) { + writeBareObject(val); + } + } + + /** + * Uses the default streamable mechanism to write the contents of the + * object currently being streamed. This can only be called from + * within a writeObject implementation in a {@link + * Streamable} object. + */ + public function defaultWriteObject () :void + //throws IOError + { + // sanity check + if (_current == null) { + throw new Error("defaultWriteObject() called illegally."); + } + + // write the instance data + _streamer.writeObject(_current, this, false) + } + + public function writeBoolean (value :Boolean) :void + //throws IOError + { + _targ.writeBoolean(value); + } + + public function writeByte (value :int) :void + //throws IOError + { + _targ.writeByte(value); + } + + public function writeBytes (bytes :ByteArray, offset :uint=0, + length :uint = 0) :void + //throws IOError + { + _targ.writeBytes(bytes, offset, length); + } + + public function writeDouble (value :Number) :void + //throws IOError + { + _targ.writeDouble(value); + } + + public function writeFloat (value :Number) :void + //throws IOError + { + _targ.writeFloat(value); + } + + public function writeInt (value :int) :void + //throws IOError + { + _targ.writeInt(value); + } + + public function writeShort (value :int) :void + //throws IOError + { + _targ.writeInt(value); + } + + public function writeUTF (value :String) :void + //throws IOError + { + _targ.writeUTF(value); + } + + // these two are defined in IDataOutput, but have no java equivalent so + // we skip them. + //public function writeUnsignedInt (value :int) :void + //public function writeUTFBytes (value :int) :void + + /** + * Used by a Streamer that is writing an array of Streamable instances. + */ + protected function setCurrent (streamer :Streamer, current :*) + { + _streamer = streamer; + _current = current; + } + + /** The target DataOutput that we route things to. */ + protected var _targ :IDataOutput; + + /** A counter used to assign codes to streamed classes. */ + protected var _nextCode :int = 1; + + /** The object currently being written out. */ + protected var _current :*; + + /** The streamer being used currently. */ + protected var _streamer :Streamer; + + /** A map of classname to ClassMapping inffaro. */ + protected var _classMap :SimpleMap = new SimpleMap(); +} +} diff --git a/src/as/com/threerings/io/Streamable.as b/src/as/com/threerings/io/Streamable.as new file mode 100644 index 000000000..351b6c84b --- /dev/null +++ b/src/as/com/threerings/io/Streamable.as @@ -0,0 +1,11 @@ +package com.threerings.io { + +public interface Streamable +{ + function writeObject (out :ObjectOutputStream) :void; + //throws IOError; + + function readObject (ins :ObjectInputStream) :void; + //throws IOError; /** ClassCastException equivalent. */ +} +} diff --git a/src/as/com/threerings/io/Streamer.as b/src/as/com/threerings/io/Streamer.as new file mode 100644 index 000000000..ecaf58a33 --- /dev/null +++ b/src/as/com/threerings/io/Streamer.as @@ -0,0 +1,97 @@ +package com.threerings.io { + +import com.threerings.util.SimpleMap; + +public class Streamer +{ + public static function getStreamer (className :String) :Streamer + //throws IOError + { + if (_streamerMap == null) { + createStreamers(); + } + + var streamer :Streamer = _streamerMap[className]; + if (streamer == null) { + streamer = new Streamer(className); + _streamerMap[className] = streamer; + } + + return streamer; + } + + /** This should be a protected constructor. */ + public function Streamer (targ :Class) + //throws IOError + { + _targ = targ; + } + + /** + * Return the String to use to identify the class that we're streaming. + */ + // TODO + public function getClassName () :String + { + return "TODO: javaname"; + } + + public function writeObject (obj :*, out :ObjectOutputStream, + useWriter :Boolean) :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; + } + + // 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) :* + //throws IOError + { + // actionscript is so fucked up + return new _targ(); + } + + public function readObject (obj :*, ins :ObjectInputStream, + useReader :Boolean) :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); + } + + /** + * Creates our streamers table map and registers streamers for + * basic types. + */ + protected static function createStreamers () :void + { + _streamerMap = new SimpleMap(); + + // TODO: fill in with the basic streamers + } + + protected var _targ :Class; + + protected static var _streamerMap :SimpleMap; +} +} diff --git a/src/as/com/threerings/presents/dobj/DEvent.as b/src/as/com/threerings/presents/dobj/DEvent.as new file mode 100644 index 000000000..e5b10e304 --- /dev/null +++ b/src/as/com/threerings/presents/dobj/DEvent.as @@ -0,0 +1,22 @@ +package com.threerings.presents.dobj { + +public class DEvent extends Object +{ + public function DEvent () + { + } + + public function DEvent (int targetOid) + { + _toid = targetOid; + } + + public function getTargetOid () :int + { + return _toid; + } + + /** The oid of the object that is the target of this event. */ + protected var _toid :int; +} +} diff --git a/src/as/com/threerings/presents/dobj/DObject.as b/src/as/com/threerings/presents/dobj/DObject.as new file mode 100644 index 000000000..3df856b78 --- /dev/null +++ b/src/as/com/threerings/presents/dobj/DObject.as @@ -0,0 +1,19 @@ +package com.threerings.presents.dobj { + +import flash.events.EventDispatcher; + +public class DObject extends EventDispatcher +{ + public function getOid ():int + { + return _oid; + } + + public function postEvent (DEvent event) :void + { + + } + + protected var _oid :int; +} +} diff --git a/src/as/com/threerings/presents/net/Credentials.as b/src/as/com/threerings/presents/net/Credentials.as new file mode 100644 index 000000000..0b6964a73 --- /dev/null +++ b/src/as/com/threerings/presents/net/Credentials.as @@ -0,0 +1,29 @@ +package com.threerings.presents.net { + +import com.threerings.io.*; +import com.threerings.util.Name; + +public class Credentials + implements Streamable +{ + public function Credentials (username :Name) + { + _username = username; + } + + // documentation inherited from interface Streamable + public function writeObject (out :ObjectOutputStream) :void + throws IOError + { + } + + // documentation inherited from interface Streamable + public function readObject (in :ObjectInputStream) :void + throws IOError + { + } + + /** The username. */ + protected var _username :Name; +} +} diff --git a/src/as/com/threerings/util/ClassUtil.as b/src/as/com/threerings/util/ClassUtil.as new file mode 100644 index 000000000..91077e191 --- /dev/null +++ b/src/as/com/threerings/util/ClassUtil.as @@ -0,0 +1,12 @@ +package com.threerings.util { + +import flash.util.*; + +public class ClassUtil extends Object +{ + public static function getClassName (obj :*) :String + { + return getQualifiedClassName(obj).replace("::", "."); + } +} +} diff --git a/src/as/com/threerings/util/Name.as b/src/as/com/threerings/util/Name.as new file mode 100644 index 000000000..c74f1cce3 --- /dev/null +++ b/src/as/com/threerings/util/Name.as @@ -0,0 +1,101 @@ +package com.threerings.util { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamable; + +public class Name extends Object + implements Streamable +{ + public function Name (name :String = "") + { + _name = name; + } + + public function getNormal () :String + { + if (_normal == null) { + _normal = normalize(_name); + } + return _normal; + } + + public function isValid () :Boolean + { + return !isBlank(); + } + + public function isBlank () :Boolean + { + return Name.isBlank(this); + } + + public /*override*/ function toString () :String + { + return _name; + } + + // TODO: needed? I'd think so. Maybe we need to create OOObject + // that all our classes can extend that define a reasonable + // equals(). + public function equals (other :Name) :Boolean + { + return getNormal() === other.getNormal(); + } + + // TODO: comparable? + + // documentation inherited from interface Streamable + public function writeObject (out :ObjectOutputStream) :void + //throws IOError + { + /* + // TODO: oh shit, this is wrong. We need to write a boolean out + // indicating whether the object that follows is null or not + out.writeBareObject(_name); + */ + + out.writeField(_name); + + // we need a way of doing the following automatically: + /* + var b :Boolean = (_name != null); + out.writeBoolean(b); + if (b) { + out.writeUTF(_name); + } + */ + + + } + + // documentation inherited from interface Streamable + public function readObject (ins :ObjectInputStream) :void + //throws IOError + { + // TODO: oh shit, this is wrong + _name = ins.readField(String); + + /* + var b :Boolean = in.readBoolean(); + _name = b ? in.readUTF() : null; + */ + } + + protected function normalize (txt :String) :String + { + return txt.toLowerCase(); + } + + public static function isBlank (name :Name) :Boolean + { + return (name == null || "" === name.toString()); + } + + /** The raw name text. */ + protected var _name :String; + + /** The normalized name text. */ + protected var _normal :String; +} +} diff --git a/src/as/com/threerings/util/SimpleMap.as b/src/as/com/threerings/util/SimpleMap.as new file mode 100644 index 000000000..8ee795d5e --- /dev/null +++ b/src/as/com/threerings/util/SimpleMap.as @@ -0,0 +1,11 @@ +package com.threerings.util { + +/** + * Marker class that allows us to use ActionScript's built-in hashing + * function without hassle. + */ +public dynamic class SimpleMap extends Object +{ + // nada +} +}