From 85d56cceecdc365a006cedae8072969263fc569e Mon Sep 17 00:00:00 2001 From: Ray Greenwell Date: Fri, 10 Feb 2006 03:44:20 +0000 Subject: [PATCH] Some more progress. I still need to work on the Streaming, but I've figured out how to do a bit of introspection which will be necessary when streaming an array. Started working on the client classes, did ClientObserver and SessionObserver stuff using flash's event notification system. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3846 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../com/threerings/io/ObjectOutputStream.as | 6 + src/as/com/threerings/io/StreamableArray.as | 20 ++ src/as/com/threerings/io/Streamer.as | 71 +++++- .../threerings/io/streamers/ArrayStreamer.as | 41 ++++ .../io/streamers/ByteArrayStreamer.as | 41 ++++ .../threerings/io/streamers/IntStreamer.as | 35 +++ .../threerings/io/streamers/NumberStreamer.as | 35 +++ .../threerings/io/streamers/StringStreamer.as | 35 +++ .../com/threerings/presents/client/Client.as | 210 ++++++++++++++++++ .../threerings/presents/client/ClientEvent.as | 40 ++++ .../presents/client/Communicator.as | 17 ++ src/as/com/threerings/util/long.as | 31 +++ 12 files changed, 580 insertions(+), 2 deletions(-) create mode 100644 src/as/com/threerings/io/StreamableArray.as create mode 100644 src/as/com/threerings/io/streamers/ArrayStreamer.as create mode 100644 src/as/com/threerings/io/streamers/ByteArrayStreamer.as create mode 100644 src/as/com/threerings/io/streamers/IntStreamer.as create mode 100644 src/as/com/threerings/io/streamers/NumberStreamer.as create mode 100644 src/as/com/threerings/io/streamers/StringStreamer.as create mode 100644 src/as/com/threerings/presents/client/Client.as create mode 100644 src/as/com/threerings/presents/client/ClientEvent.as create mode 100644 src/as/com/threerings/presents/client/Communicator.as create mode 100644 src/as/com/threerings/util/long.as diff --git a/src/as/com/threerings/io/ObjectOutputStream.as b/src/as/com/threerings/io/ObjectOutputStream.as index ebd41e202..94594fed2 100644 --- a/src/as/com/threerings/io/ObjectOutputStream.as +++ b/src/as/com/threerings/io/ObjectOutputStream.as @@ -33,6 +33,12 @@ public class ObjectOutputStream // 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; diff --git a/src/as/com/threerings/io/StreamableArray.as b/src/as/com/threerings/io/StreamableArray.as new file mode 100644 index 000000000..9d7114314 --- /dev/null +++ b/src/as/com/threerings/io/StreamableArray.as @@ -0,0 +1,20 @@ +package com.threerings.io { + +public dynamic class StreamableArray extends Array + implements Streamable +{ + public function StreamableArray (ctype :Class = undefined, length :int = 0) + { + super(length); + _ctype = ctype; + } + + public function StreamableArray (ctype :Class, ... values) + { + super(values); + _ctype = ctype; + } + + /** The type of all the elements in the array. */ + protected var _ctype :Class; +} diff --git a/src/as/com/threerings/io/Streamer.as b/src/as/com/threerings/io/Streamer.as index ecaf58a33..eab2504cc 100644 --- a/src/as/com/threerings/io/Streamer.as +++ b/src/as/com/threerings/io/Streamer.as @@ -1,9 +1,18 @@ package com.threerings.io { +import flash.util.ByteArray; + import com.threerings.util.SimpleMap; +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.StringStreamer; + public class Streamer { + /* public static function getStreamer (className :String) :Streamer //throws IOError { @@ -19,6 +28,58 @@ public class Streamer return streamer; } + */ + + public static function getStreamer (obj :*) :Streamer + { + if (_streamerMap == 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; + } + } + + 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; + } + } /** This should be a protected constructor. */ public function Streamer (targ :Class) @@ -86,12 +147,18 @@ public class Streamer protected static function createStreamers () :void { _streamerMap = new SimpleMap(); - - // TODO: fill in with the basic streamers } protected var _targ :Class; protected static var _streamerMap :SimpleMap; + + + 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(); } } diff --git a/src/as/com/threerings/io/streamers/ArrayStreamer.as b/src/as/com/threerings/io/streamers/ArrayStreamer.as new file mode 100644 index 000000000..b7bd4a44f --- /dev/null +++ b/src/as/com/threerings/io/streamers/ArrayStreamer.as @@ -0,0 +1,41 @@ +package com.threerings.io.streamers { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamer; + +/** + * A Streamer for Array objects. + */ +public class ArrayStreamer extends Streamer +{ + public function ArrayStreamer () + { + super(Array); + } + + public override function createObject (ins :ObjectInputStream) :* + { + return new Array(ins.readInt()); + } + + public override function writeObject (obj :*, out :ObjectOutputStream, + useWriter :Boolean) :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 :*, ins :ObjectInputStream, + useReader :Boolean) :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/io/streamers/ByteArrayStreamer.as b/src/as/com/threerings/io/streamers/ByteArrayStreamer.as new file mode 100644 index 000000000..43444725d --- /dev/null +++ b/src/as/com/threerings/io/streamers/ByteArrayStreamer.as @@ -0,0 +1,41 @@ +package com.threerings.io.streamers { + +import flash.util.ByteArray; + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamer; + +/** + * A Streamer for ByteArray objects. + */ +public class ByteArrayStreamer extends Streamer +{ + public function ByteArrayStreamer () + { + super(ByteArray); + } + + public override function createObject (ins :ObjectInputStream) :* + { + var bytes :ByteArray = new ByteArray(); + bytes.length = ins.readInt(); + return bytes; + } + + public override function writeObject (obj :*, out :ObjectOutputStream, + useWriter :Boolean) :void + { + var bytes :ByteArray = (obj as ByteArray); + out.writeInt(bytes.length); + out.writeBytes(bytes); + } + + public override function readObject (obj :*, ins :ObjectInputStream, + useReader :Boolean) :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 new file mode 100644 index 000000000..88688d3c3 --- /dev/null +++ b/src/as/com/threerings/io/streamers/IntStreamer.as @@ -0,0 +1,35 @@ +package com.threerings.io.streamers { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamer; + +/** + * A Streamer for int objects. + */ +public class IntStreamer extends Streamer +{ + public function IntStreamer () + { + super(int); + } + + public override function createObject (ins :ObjectInputStream) :* + { + return ins.readInt(); + } + + public override function writeObject (obj :*, out :ObjectOutputStream, + useWriter :Boolean) :void + { + var i :int = (obj as int); + out.writeInt(i); + } + + public override function readObject (obj :*, ins :ObjectInputStream, + useReader :Boolean) :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 new file mode 100644 index 000000000..78181c73b --- /dev/null +++ b/src/as/com/threerings/io/streamers/NumberStreamer.as @@ -0,0 +1,35 @@ +package com.threerings.io.streamers { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamer; + +/** + * A Streamer for Number objects. + */ +public class NumberStreamer extends Streamer +{ + public function NumberStreamer () + { + super(Number); + } + + public override function createObject (ins :ObjectInputStream) :* + { + return ins.readDouble(); + } + + public override function writeObject (obj :*, out :ObjectOutputStream, + useWriter :Boolean) :void + { + var n :Number = (obj as Number); + out.writeDouble(n); + } + + public override function readObject (obj :*, ins :ObjectInputStream, + useReader :Boolean) :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 new file mode 100644 index 000000000..a6be5a765 --- /dev/null +++ b/src/as/com/threerings/io/streamers/StringStreamer.as @@ -0,0 +1,35 @@ +package com.threerings.io.streamers { + +import com.threerings.io.ObjectInputStream; +import com.threerings.io.ObjectOutputStream; +import com.threerings.io.Streamer; + +/** + * A Streamer for String objects. + */ +public class StringStreamer extends Streamer +{ + public function StringStreamer () + { + super(String); + } + + public override function createObject (ins :ObjectInputStream) :* + { + return ins.readUTF(); + } + + public override function writeObject (obj :*, out :ObjectOutputStream, + useWriter :Boolean) :void + { + var s :String = (obj as String); + out.writeUTF(s); + } + + public override function readObject (obj :*, ins :ObjectInputStream, + useReader :Boolean) :void + { + // nothing here, the String is fully read in createObject() + } +} +} diff --git a/src/as/com/threerings/presents/client/Client.as b/src/as/com/threerings/presents/client/Client.as new file mode 100644 index 000000000..3e1a8462a --- /dev/null +++ b/src/as/com/threerings/presents/client/Client.as @@ -0,0 +1,210 @@ +package com.threerings.presents.client { + +import flash.events.EventDispatcher; +import flash.events.TimerEvent; +import flash.util.Timer; + +import com.threerings.presents.data.ClientObject; + +import com.threerings.presents.dobj.DObjectManager; + +import com.threerings.presents.net.AuthResponseData; +import com.threerings.presents.net.BootstrapData; +import com.threerings.presents.net.Credentials; +import com.threerings.presents.net.PingRequest; +import com.threerings.presents.net.PongResponse; + +public class Client extends EventDispatcher +{ + /** The default port on which the server listens for client connections. */ + public static const DEFAULT_SERVER_PORT :int = 47624; + + public function Client (creds :Credentials) + { + _creds = creds; + } + + public function setServer (hostname :String, port :int) :void + { + _hostname = hostname; + _port = port; + } + + public function setCredentials (creds :Credentials) :void + { + _creds = creds; + } + + public function getDObjectManager () :DObjectManager + { + return _omgr; + } + + public function getClientObject () :ClientObject + { + return _clobj; + } + + public function getInvocationDirector () :InvocationDirector + { + return _invdir; + } + + public function getBootstrapData () :BootstrapData + { + return _bstrap; + } + + public function isLoggedOn () :Boolean + { + return (_clobj != null); + } + + /** + * Requests that this client connect and logon to the server with + * which it was previously configured. + * + * @return false if we're already logged on. + */ + public function logon () :Boolean + { + // if we have a communicator, we're already logged on + if (_comm != null) { + return false; + } + + _comm = new Communicator(this); + _comm.logon(); + + _tickInterval = new Timer(5000); + _tickInterval.addEventListener(TimerEvent.TIMER, tick); + _tickInterval.start(); + return true; + } + + /** + * Requests that the client log off of the server to which it is + * connected. + * + * @param abortable if true, the client will call clientWillDisconnect + * on allthe client observers and abort the logoff process if any of them + * return false. If false, clientWillDisconnect will not be called. + * + * @return true if the logoff succeeded, false if it failed due to a + * disagreeable observer. + */ + public function logoff (abortable :Boolean) :Boolean + { + if (_comm == null) { + trace("Ignoring request to log off: not logged on."); + return true; + } + + // if the request is abortable, let's run it past the observers. + // if any of them call preventDefault() then the logoff will be + // cancelled + if (abortable && !notifyObservers(ClientEvent.CLIENT_WILL_LOGOFF)) { + return false; + } + + _tickInterval.stop(); + _tickInterval = null; + + _comm.logoff(); + return true; + } + + public function gotBootstrap (data :BootstrapData, omgr :DObjectManager) + :void + { + trace("Got bootstrap " + data + "."); + + _bstrap = data; + _omgr = omgr; + _cloid = data.clientOid; + + _invdir.init(omgr, _cloid, this); + } + + protected function gotClientObject (clobj :ClientObject) :void + { + _clobj = clobj; + notifyObservers(ClientEvent.CLIENT_DID_LOGON); + } + + protected function getClientObjectFailed (cause :Error) :void + { + notifyObservers(ClientEvent.CLIENT_FAILED_TO_LOGON, cause); + } + + protected function clientObjectDidChange (clobj :ClientObject) :void + { + _clobj = clobj; + _cloid = clobj.getOid(); + + notifyObservers(ClientEvent.CLIENT_OBJECT_CHANGED); + } + + /** + * Called every five seconds; ensures that we ping the server if we + * haven't communicated in a long while. + */ + protected function tick (event :TimerEvent) :void + { + if (_comm == null) { + return; + } + + var now :Number = new Date().getTime(); + if (now - _comm.getLastWrite() > PingRequest.PING_INTERVAL) { + _comm.postMessage(new PingRequest()); + } + } + + /** + * Convenience method to dispatch a client event to any listeners + * and return the result of dispatchEvent. + */ + protected function notifyObservers (evtCode :String, cause :Error = null) + :Boolean + { + return dispatchEvent(new ClientEvent(evtCode, this, cause)); + } + + /** The credentials we used to authenticate with the server. */ + protected var _creds :Credentials; + + /** The version string reported to the server at auth time. */ + protected var _version :String = ""; + + /** The distributed object manager we're using during this session. */ + protected var _omgr :DObjectManager; + + /** The data associated with our authentication response. */ + protected var _authData :AuthResponseData; + + /** Our client distributed object id. */ + protected var _cloid :int = -1; + + /** Our client distributed object. */ + protected var _clobj :ClientObject; + + /** The game server host. */ + protected var _hostname :String; + + /** The port on which we connect to the game server. */ + protected var _port :int; + + /** The entity that manages our network communications. */ + protected var _comm :Communicator; + + /** General startup information provided by the server. */ + protected var _bstrap :BootstrapData; + + /** Manages invocation services. */ + protected var _invdir :InvocationDirector; + + /** Ticks. */ + protected var _tickInterval :Timer; +} +} diff --git a/src/as/com/threerings/presents/client/ClientEvent.as b/src/as/com/threerings/presents/client/ClientEvent.as new file mode 100644 index 000000000..883e5d365 --- /dev/null +++ b/src/as/com/threerings/presents/client/ClientEvent.as @@ -0,0 +1,40 @@ +package com.threerings.presents.client { + +import flash.events.Event; + +public class ClientEvent extends Event +{ + public static const CLIENT_DID_LOGON :String = "clientDidLogon"; + public static const CLIENT_FAILED_TO_LOGON :String = "clientFailedLogon"; + public static const CLIENT_OBJECT_CHANGED :String = "clobjChanged"; + public static const CLIENT_CONNECTION_FAILED :String = "clientConnFailed"; + /** The logoff itself can be cancelled if a listener calls + * preventDefault() on this event. */ + public static const CLIENT_WILL_LOGOFF :String = "clientWillLogoff"; + public static const CLIENT_DID_LOGOFF :String = "clientDidLogoff"; + + public function ClientEvent (type :String, client :Client, + cause :Error = null) + { + super(type, false, (type === CLIENT_WILL_LOGOFF)); + _client = client; + _cause = cause; + } + + public function getClient () :Client + { + return _client; + } + + public function getCause () :Error + { + return _cause; + } + + /** The client that generated this client event. */ + protected var _client :Client; + + /** The error that caused this event, if applicable. */ + protected var _cause :Error; +} +} diff --git a/src/as/com/threerings/presents/client/Communicator.as b/src/as/com/threerings/presents/client/Communicator.as new file mode 100644 index 000000000..9479520ee --- /dev/null +++ b/src/as/com/threerings/presents/client/Communicator.as @@ -0,0 +1,17 @@ +package com.threerings.presents.client { + +public class Communicator +{ + public function Communicator (client :Client) + { + _client = client; + } + + public function logon () :void + { + } + + protected var _client :Client; + protected var _omgr :ClientDObjectManager; +} +} diff --git a/src/as/com/threerings/util/long.as b/src/as/com/threerings/util/long.as new file mode 100644 index 000000000..e7c7da2fd --- /dev/null +++ b/src/as/com/threerings/util/long.as @@ -0,0 +1,31 @@ +package com.threerings.util { + +public class long extends Object + implements Streamable +{ + public function long (lowbits :int, highbits :int = 0) + { + _lowbits = lowbits; + _highbits = highbits; + } + + // documentation inherited from interface Streamable + public function writeObject (out :ObjectOutputStream) :void + //throws IOError + { + out.writeInt(_lowbits); + out.writeInt(_highbits); + } + + // documentation inherited from interface Streamable + public function readObject (ins :ObjectInputStream) :void + //throws IOError + { + _lowbits = ins.readInt(); + _highbits = ins.readInt(); + } + + /** Yon bits. */ + protected var _lowbits :int, _highbits :int; +} +}