More crap, some new discoveries, backtracking, annoyance.

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3918 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2006-03-07 03:49:33 +00:00
parent e5c65b95b5
commit d8276bb4a9
12 changed files with 138 additions and 101 deletions
+22 -9
View File
@@ -16,9 +16,10 @@ Design decisions
- We need a realistic HashMap implementation. Using Object properties - We need a realistic HashMap implementation. Using Object properties
(a-la my SimpleMap) is not going to cut it because keys must always (a-la my SimpleMap) is not going to cut it because keys must always
be Strings and there's no way to *really* remove a value from an Object be Strings.<strike>and there's no way to *really* remove a value from
(you can set the property to null, but now the property is forever defined: an Object (you can set the property to null, but now the property is
the key is not cleared) forever defined: the key is not cleared)</strike>
***Update: the 'delete' operator removes properties.
mx.utils.UIDUtil.getUID() can be used to generate a (huge) unique String mx.utils.UIDUtil.getUID() can be used to generate a (huge) unique String
for any object for use as a key or something. for any object for use as a key or something.
@@ -81,10 +82,9 @@ Notes
and accessing only public properties of the main class from the helper. and accessing only public properties of the main class from the helper.
- Similarly, I'm unclear about sandboxes. If a user-created .swf is playing - Sandboxing classes is done with ApplicationDomains. When we load a sub-swf
inside ours, I don't know if it can interact with our classes, and if so, we'll want to put it into a different domain so that nothing malicious
what happens if it proceeds to define a class like can be done to our classes.
com.threerings.presents.client.Client?
- <strike>constructors do not defaultly call super()- be sure to do it explicitely. - <strike>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 Maybe we should get in the habit of doing it in Java for consistency and
@@ -169,6 +169,14 @@ ActionScript
Perhaps we'll want a util method that always generates an error if the Perhaps we'll want a util method that always generates an error if the
object's type is not identical or a subclass of the casted-to type. object's type is not identical or a subclass of the casted-to type.
***Update:
var o1 :String = null;
var o2 :String = String(o1); // ends up being "" or something
The 2nd kind of cast destroys null, at least for String. So fuck that,
I was trying to use it when pulling a value out of a hash, but if it
wasn't there it got booched.
- Pitfall! This is perfectly legal: - Pitfall! This is perfectly legal:
var b :int = 3; var b :int = 3;
@@ -198,5 +206,10 @@ ActionScript
me from correctly streaming arrays, as we need to know if the class me from correctly streaming arrays, as we need to know if the class
is final. I can't just pass an instance in because it may be a pain is final. I can't just pass an instance in because it may be a pain
to construct, it may even be unconstructable if the type of the array to construct, it may even be unconstructable if the type of the array
is an interface. The adobe forums are broken right now, but I'm going is an interface. Posted as a request for enhancement on the AS3.0 forums.
to post a request for enhancement so that we can get this information.
- Actionscript's property accessors are a cool feature, but beware hidden
performance issues: accessing a simple property of a variable
(like myArray.length) may actually be executing arbitrary code, possibly
creating many objects, each time.
+1 -18
View File
@@ -28,14 +28,6 @@ public class ObjectInputStream
_source = source; _source = source;
} }
/**
* Add a translation that should be used when writing objects.
*/
public function addTranslation (oldName :String, newName :String) :void
{
_translations[oldName] = newName;
}
public function readObject () :Object public function readObject () :Object
//throws IOError //throws IOError
{ {
@@ -57,13 +49,8 @@ public class ObjectInputStream
code *= -1; code *= -1;
// read in the class metadata // read in the class metadata
var cname :String = readUTF(); var cname :String = Translations.getFromServer(readUTF());
Log.debug("read cname: " + cname); Log.debug("read cname: " + cname);
// if we have a translation, use it
var tname :String = _translations[cname];
if (tname != null) {
cname = tname;
}
var streamer :Streamer = Streamer.getStreamerByJavaName(cname); var streamer :Streamer = Streamer.getStreamerByJavaName(cname);
cmap = new ClassMapping(code, cname, streamer); cmap = new ClassMapping(code, cname, streamer);
@@ -215,9 +202,5 @@ public class ObjectInputStream
/** A map of short class code to ClassMapping info. */ /** A map of short class code to ClassMapping info. */
protected var _classMap :Array = new Array(); protected var _classMap :Array = new Array();
/** An collection of class name translations to use when unserializing
* objects. */
protected var _translations :SimpleMap = new SimpleMap();
} }
} }
+4 -21
View File
@@ -15,15 +15,6 @@ public class ObjectOutputStream
_targ = targ; _targ = targ;
} }
/**
* Add a classname translation for rewriting the names of objects
* that we send to the server.
*/
public function addTranslation (oldName :String, newName :String) :void
{
_translations[oldName] = newName;
}
public function writeObject (obj :Object) :void public function writeObject (obj :Object) :void
//throws IOError //throws IOError
{ {
@@ -35,7 +26,7 @@ public class ObjectOutputStream
var cname :String = ClassUtil.getClassName(obj); var cname :String = ClassUtil.getClassName(obj);
// look up the class mapping record // look up the class mapping record
var cmap :ClassMapping = _classMap[cname]; var cmap :ClassMapping = (_classMap.get(cname) as ClassMapping);
// create a class mapping if we've not got one // create a class mapping if we've not got one
if (cmap == null) { if (cmap == null) {
@@ -48,18 +39,13 @@ public class ObjectOutputStream
} }
cmap = new ClassMapping(_nextCode++, cname, streamer); cmap = new ClassMapping(_nextCode++, cname, streamer);
_classMap[cname] = cmap; _classMap.put(cname, cmap);
// TODO: if _nextCode blows short, log an error // TODO: if _nextCode blows short, log an error
// see if there's a translation we should use
var tname :String = _translations[cname];
if (tname != null) {
cname = tname;
}
writeShort(-cmap.code); writeShort(-cmap.code);
writeUTF((streamer == null) ? cname : streamer.getJavaClassName()); writeUTF((streamer == null) ? Translations.getToServer(cname)
: streamer.getJavaClassName());
} else { } else {
writeShort(cmap.code); writeShort(cmap.code);
@@ -200,8 +186,5 @@ public class ObjectOutputStream
/** A map of classname to ClassMapping info. */ /** A map of classname to ClassMapping info. */
protected var _classMap :SimpleMap = new SimpleMap(); protected var _classMap :SimpleMap = new SimpleMap();
/** A map of classname translations. */
protected var _translations :SimpleMap = new SimpleMap();
} }
} }
+5 -1
View File
@@ -48,6 +48,10 @@ public class Streamer
{ {
initStreamers(); initStreamers();
if (jname.charAt(0) === "[") {
// Oh ghod
}
for each (var streamer :Streamer in _streamers) { for each (var streamer :Streamer in _streamers) {
if (streamer.getJavaClassName() === jname) { if (streamer.getJavaClassName() === jname) {
return streamer; return streamer;
@@ -86,7 +90,7 @@ public class Streamer
if (obj is Array) { if (obj is Array) {
trace("Arrays not yet done. Crap!"); trace("Arrays not yet done. Crap!");
/** /**
var arr :Array = (obj as Array); // not strictly necessary var arr :Array = Array(obj); // not strictly necessary
var length :int = arr.length; var length :int = arr.length;
out.writeInt(length); out.writeInt(length);
*/ */
@@ -208,14 +208,13 @@ public class ClientDObjectMgr
} else if (obj is UnsubscribeResponse) { } else if (obj is UnsubscribeResponse) {
var oid :int = obj.getOid(); var oid :int = obj.getOid();
if (_dead[oid] == null) { if (_dead.remove(oid) == null) {
Log.warning("Received unsub ACK from unknown object " + Log.warning("Received unsub ACK from unknown object " +
"[oid=" + oid + "]."); "[oid=" + oid + "].");
} }
_dead[oid] = undefined;
} else if (obj is FailureResponse) { } else if (obj is FailureResponse) {
var oid :int = obj.getOid(); var oid :int = (obj as FailureResponse).getOid();
notifyFailure(oid); notifyFailure(oid);
} else if (obj is PongResponse) { } else if (obj is PongResponse) {
@@ -250,9 +249,9 @@ public class ClientDObjectMgr
// look up the object on which we're dispatching this event // look up the object on which we're dispatching this event
var toid :int = event.getTargetOid(); var toid :int = event.getTargetOid();
var target :DObject = _ocache[toid]; var target :DObject = (_ocache.get(toid) as DEvent);
if (target == null) { if (target == null) {
if (_dead[toid] === undefined) { if (_dead.get(toid) == null) {
Log.warning("Unable to dispatch event on non-proxied " + Log.warning("Unable to dispatch event on non-proxied " +
"object [event=" + event + "]."); "object [event=" + event + "].");
} }
@@ -269,7 +268,7 @@ public class ClientDObjectMgr
// Log.info("Pitching destroyed object " + // Log.info("Pitching destroyed object " +
// "[oid=" + toid + ", class=" + // "[oid=" + toid + ", class=" +
// StringUtil.shortClassName(target) + "]."); // StringUtil.shortClassName(target) + "].");
_ocache[toid] = undefined; _ocache.remove(toid);
} }
// have the object pass this event on to its listeners // have the object pass this event on to its listeners
@@ -295,7 +294,7 @@ public class ClientDObjectMgr
var oid :int = obj.getOid(); var oid :int = obj.getOid();
// stick the object into the proxy object table // stick the object into the proxy object table
_ocache[oid] = obj; _ocache.put(oid, obj);
// let the penders know that the object is available // let the penders know that the object is available
var req :PendingRequest = (_penders.remove(oid) as PendingRequest); var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
@@ -321,8 +320,7 @@ public class ClientDObjectMgr
protected function notifyFailure (oid :int) :void protected function notifyFailure (oid :int) :void
{ {
// let the penders know that the object is not available // let the penders know that the object is not available
var req :PendingRequest = _penders[oid]; var req :PendingRequest = (_penders.remove(oid) as PendingRequest);
_penders[oid] = undefined;
if (req == null) { if (req == null) {
Log.warning("Failed to get object, but no one cares?! " + Log.warning("Failed to get object, but no one cares?! " +
"[oid=" + oid + "]."); "[oid=" + oid + "].");
@@ -345,10 +343,10 @@ public class ClientDObjectMgr
// Log.info("doSubscribe: " + oid + ": " + target); // Log.info("doSubscribe: " + oid + ": " + target);
// first see if we've already got the object in our table // first see if we've already got the object in our table
var obj :DObject = _ocache[oid]; var obj :DObject = (_ocache.get(oid) as DObject);
if (obj != null) { if (obj != null) {
// clear the object out of the flush table if it's in there // clear the object out of the flush table if it's in there
_flushes[oid] = undefined; _flushes.remove(oid);
// add the subscriber and call them back straight away // add the subscriber and call them back straight away
obj.addSubscriber(target); obj.addSubscriber(target);
target.objectAvailable(obj); target.objectAvailable(obj);
@@ -356,7 +354,7 @@ public class ClientDObjectMgr
} }
// see if we've already got an outstanding request for this object // see if we've already got an outstanding request for this object
var req :PendingRequest = _penders[oid]; var req :PendingRequest = (_penders.get(oid) as PendingRequest);
if (req != null) { if (req != null) {
// add this subscriber to the list of subscribers to be // add this subscriber to the list of subscribers to be
// notified when the request is satisfied // notified when the request is satisfied
@@ -367,7 +365,7 @@ public class ClientDObjectMgr
// otherwise we need to create a new request // otherwise we need to create a new request
req = new PendingRequest(oid); req = new PendingRequest(oid);
req.addTarget(target); req.addTarget(target);
_penders[oid] = req; _penders.put(oid, req);
// Log.info("Registering pending request [oid=" + oid + "]."); // Log.info("Registering pending request [oid=" + oid + "].");
// and issue a request to get things rolling // and issue a request to get things rolling
@@ -380,7 +378,7 @@ public class ClientDObjectMgr
*/ */
protected function doUnsubscribe (oid :int, target :Subscriber) :void protected function doUnsubscribe (oid :int, target :Subscriber) :void
{ {
var dobj :DObject = _ocache[oid]; var dobj :DObject = (_ocache.get(oid) as DObject);
if (dobj != null) { if (dobj != null) {
dobj.removeSubscriber(target); dobj.removeSubscriber(target);
@@ -401,8 +399,8 @@ public class ClientDObjectMgr
// have it around anymore; once our unsubscribe message is // have it around anymore; once our unsubscribe message is
// processed, it'll be 86ed // processed, it'll be 86ed
var ooid :int = obj.getOid(); var ooid :int = obj.getOid();
_ocache[ooid] = undefined; _ocache.remove(ooid);
_dead[ooid] = obj; _dead.put(ooid, obj);
// ship off an unsubscribe message to the server; we'll remove the // ship off an unsubscribe message to the server; we'll remove the
// object from our table when we get the unsub ack // object from our table when we get the unsub ack
@@ -416,10 +414,10 @@ public class ClientDObjectMgr
protected function flushObjects () :void protected function flushObjects () :void
{ {
var now :Number = new Date().getTime(); var now :Number = new Date().getTime();
for (var oid :String in _flushes) { for (var oid :String in _flushes.keys()) {
var rec :FlushRecord = _flushes[oid]; var rec :FlushRecord = (_flushes.get(oid) as FlushRecord);
if (rec.expire <= now) { if (rec.expire <= now) {
_flushes[oid] = undefined; _flushes.remove(oid);
flushObject(rec.obj); flushObject(rec.obj);
} }
} }
@@ -14,6 +14,7 @@ import com.threerings.io.FrameAvailableEvent;
import com.threerings.io.FrameReader; import com.threerings.io.FrameReader;
import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream; import com.threerings.io.ObjectOutputStream;
import com.threerings.io.Translations;
import com.threerings.presents.Log; import com.threerings.presents.Log;
@@ -57,9 +58,8 @@ public class Communicator
private function addClassTranslations () :void private function addClassTranslations () :void
{ {
_inStream.addTranslation("com.threerings.presents.dobj.DSet$Entry", Translations.addTranslation(
"com.threerings.presents.dobj.DSetEntry"); "com.threerings.presents.dobj.DSetEntry",
_outStream.addTranslation("com.threerings.presents.dobj.DSetEntry",
"com.threerings.presents.dobj.DSet$Entry"); "com.threerings.presents.dobj.DSet$Entry");
} }
@@ -102,7 +102,7 @@ public class Communicator
// write the message (ends up in _outBuffer) // write the message (ends up in _outBuffer)
_outStream.writeObject(msg); _outStream.writeObject(msg);
//Log.debug("outBuffer: " + Util.bytesToString(_outBuffer)); Log.debug("outBuffer: " + Util.bytesToString(_outBuffer));
// Frame it by writing the length, then the bytes. // Frame it by writing the length, then the bytes.
// We add 4 to the length, because the length is of the entire frame // We add 4 to the length, because the length is of the entire frame
@@ -103,7 +103,7 @@ public class InvocationDirector
var reg :InvocationRegistration = new InvocationRegistration( var reg :InvocationRegistration = new InvocationRegistration(
decoder.getReceiverCode(), nextReceiverId()); decoder.getReceiverCode(), nextReceiverId());
_clobj.addToReceivers(reg); _clobj.addToReceivers(reg);
_receivers[reg.receiverId] = decoder; _receivers.put(reg.receiverId, decoder);
} }
/** /**
@@ -139,7 +139,7 @@ public class InvocationDirector
// create a mapping for this marshaller so that we can // create a mapping for this marshaller so that we can
// properly dispatch responses sent to it // properly dispatch responses sent to it
_listeners[lm.requestId] = lm; _listeners.put(lm.requestId, lm);
} }
} }
@@ -187,7 +187,7 @@ public class InvocationDirector
(reqId :int, methodId :int, args :Array) :void (reqId :int, methodId :int, args :Array) :void
{ {
var listener :ListenerMarshaller = var listener :ListenerMarshaller =
(_listeners.remove(reqId) as ListenerMarshaller); (_listeners.remove(reqId) as ListenerMarshaller);
if (listener == null) { if (listener == null) {
Log.warning("Received invocation response for which we have " + Log.warning("Received invocation response for which we have " +
"no registered listener [reqId=" + reqId + "no registered listener [reqId=" + reqId +
@@ -229,7 +229,7 @@ public class InvocationDirector
{ {
// look up the decoder registered for this receiver // look up the decoder registered for this receiver
var decoder :InvocationDecoder = var decoder :InvocationDecoder =
(_receivers[receiverId] as InvocationDecoder); (_receivers.get(receiverId) as InvocationDecoder);
if (decoder == null) { if (decoder == null) {
Log.warning("Received notification for which we have no " + Log.warning("Received notification for which we have no " +
"registered receiver [recvId=" + receiverId + "registered receiver [recvId=" + receiverId +
@@ -268,11 +268,11 @@ public class InvocationDirector
protected function flushListeners (now :Number) :void protected function flushListeners (now :Number) :void
{ {
var then :Number = now - LISTENER_MAX_AGE; var then :Number = now - LISTENER_MAX_AGE;
for (var skey :String in _listeners) { for (var skey :String in _listeners.keys()) {
var lm :ListenerMarshaller = var lm :ListenerMarshaller =
(_listeners[skey] as ListenerMarshaller); (_listeners.get(skey) as ListenerMarshaller);
if (lm.mapStamp < then) { if (lm.mapStamp < then) {
_listeners.clear(skey); _listeners.remove(skey);
} }
} }
} }
@@ -15,13 +15,41 @@ public class TestClient extends Client
setServer("tasman.sea.earth.threerings.net", DEFAULT_SERVER_PORT); setServer("tasman.sea.earth.threerings.net", DEFAULT_SERVER_PORT);
logon(); logon();
var a :Object = new HelperClass(this); var g1 :String = null;
var g2 :String = String(g1);
Log.debug("foo: " + (g1 === g2));
Log.debug("instance: " + describeType(a).toXMLString()); var duckie :Duck = new Goose();
Log.debug("class : " + describeType(HelperClass).toXMLString()); duckie.screw();
var arr :Array = new Array();
arr[0] = "Florp";
arr[1] = "Blanger";
arr[2] = "Swissbrat";
for (var key:* in arr) {
Log.debug("a key=" + key + " -> " + arr[key]);
}
delete arr[1];
arr["1"] = "oinkenheimer";
for (var key:* in arr) {
Log.debug("a key=" + key + " -> " + arr[key]);
}
Log.debug("length: " + arr.length);
Log.debug("arr[0]: " + arr[0]);
Log.debug("arr[1]: " + arr[1]);
Log.debug("arr[2]: " + arr[2]);
/* /*
var b :Object = new PooperClass(); var a :Object = new OldClass();
var c :Class = OldClass;
Log.debug("instance: " + describeType(a).toXMLString());
Log.debug("class : " + describeType(Pork).toXMLString());
*/
/*
var b :Object = new OldClass();
var c :Object = 2.4; var c :Object = 2.4;
var d :Object = new HooperClass(this); var d :Object = new HooperClass(this);
@@ -74,6 +102,13 @@ public class TestClient extends Client
import com.threerings.presents.Log; import com.threerings.presents.Log;
import com.threerings.presents.client.TestClient; import com.threerings.presents.client.TestClient;
dynamic class OldClass
{
public function OldClass ()
{
}
}
class HelperClass class HelperClass
{ {
public function HelperClass (cli :TestClient) public function HelperClass (cli :TestClient)
@@ -113,10 +148,3 @@ final class HooperClass extends HelperClass
// nada // nada
} }
} }
class PooperClass
{
public function PooperClass ()
{
}
}
@@ -5,7 +5,6 @@ import flash.util.StringBuilder;
import com.threerings.io.ObjectInputStream; import com.threerings.io.ObjectInputStream;
import com.threerings.io.ObjectOutputStream; import com.threerings.io.ObjectOutputStream;
import com.threerings.util.Comparable;
import com.threerings.presents.Log; import com.threerings.presents.Log;
@@ -31,7 +30,7 @@ public class EntryRemovedEvent extends NamedEvent
* @param oldEntry the previous value of the entry. * @param oldEntry the previous value of the entry.
*/ */
public function EntryRemovedEvent ( public function EntryRemovedEvent (
targetOid :int, name :String, key :Comparable, oldEntry :DSetEntry) targetOid :int, name :String, key :Object, oldEntry :DSetEntry)
{ {
super(targetOid, name); super(targetOid, name);
_key = key; _key = key;
@@ -41,7 +40,7 @@ public class EntryRemovedEvent extends NamedEvent
/** /**
* Returns the key that identifies the entry that has been removed. * Returns the key that identifies the entry that has been removed.
*/ */
public function getKey () :Comparable public function getKey () :Object
{ {
return _key; return _key;
} }
@@ -99,10 +98,10 @@ public class EntryRemovedEvent extends NamedEvent
public override function readObject (ins :ObjectInputStream) :void public override function readObject (ins :ObjectInputStream) :void
{ {
super.readObject(ins); super.readObject(ins);
_key = (ins.readObject() as Comparable); _key = ins.readObject();
} }
protected var _key :Comparable; protected var _key :Object;
protected var _oldEntry :DSetEntry = UNSET_OLD_ENTRY; protected var _oldEntry :DSetEntry = UNSET_OLD_ENTRY;
} }
} }
+1
View File
@@ -26,6 +26,7 @@ public class ClassUtil
public static function getClassByName (cname :String) :Class public static function getClassByName (cname :String) :Class
{ {
// see also ApplicationDomain.currentDomain.getClass(cname)
return flash.util.getClassByName(cname); return flash.util.getClassByName(cname);
} }
+33 -4
View File
@@ -4,15 +4,44 @@ package com.threerings.util {
* Marker class that allows us to use ActionScript's built-in hashing * Marker class that allows us to use ActionScript's built-in hashing
* function without hassle. * function without hassle.
*/ */
public dynamic class SimpleMap extends Object public class SimpleMap extends Object
{ {
public function clear () :void
{
_data = new Object();
}
public function get (key :Object) :Object
{
var skey :String = key.toString();
return _data[skey];
}
public function keys () :Array
{
var arr :Array = new Array();
for (var skey :String in _data) {
arr.push(skey);
}
return arr;
}
public function put (key :Object, value :Object) :Object
{
var skey :String = key.toString();
var oldValue :Object = _data[skey];
_data[skey] = value;
return oldValue;
}
public function remove (key :Object) :Object public function remove (key :Object) :Object
{ {
// I'm not sure this really works
var skey :String = key.toString(); var skey :String = key.toString();
var value :Object = this[skey]; var value :Object = _data[skey];
this[skey] = undefined; delete _data[skey];
return value; return value;
} }
private var _data :Object = new Object();
} }
} }
+1 -2
View File
@@ -1,7 +1,5 @@
package com.threerings.util { package com.threerings.util {
import flash.util.describeType;
import mx.utils.ObjectUtil; import mx.utils.ObjectUtil;
import com.threerings.io.ArrayMask; import com.threerings.io.ArrayMask;
@@ -83,6 +81,7 @@ public dynamic class TypedArray extends Array
* except gives it a clue as to how to stream to our server. */ * except gives it a clue as to how to stream to our server. */
protected var _type :Class; protected var _type :Class;
/** Whether or not the type of the array represents a final class. */
protected var _final :Boolean; protected var _final :Boolean;
protected var _delegate :Streamer; protected var _delegate :Streamer;