communicating arbitrary data via rtfmp (pt. 2)

sending custom classes via rtmfp? well, let's try :D

according to the example of the first part of this article we would now like to pass our receiverFunction() an instance of SomeObject instead of a String.


setup

here's the initial class SomeObject which we'll try to send from one peer to another:

Actionscript:
  1. package 
  2. {
  3.     public class SomeObject
  4.     {
  5.         private var i:int;
  6.        
  7.         public function SomeObject(value:int)
  8.         {
  9.             i = value;
  10.         }
  11.        
  12.         public function get value():int { return i; }
  13.     }
  14. }

of course we have to adjust our peers as well:

sending peer:

Actionscript:
  1. var netStream:NetStream = new NetStream(netConnection, NetStream.DIRECT_CONNECTIONS);
  2. var sendTimer:Timer = new Timer(1000);
  3. sendTimer.addEventListener(TimerEvent.TIMER, senderFunction);
  4. ...
  5. public function senderFunction(te:TimerEvent):void
  6. {
  7.     var someObject:SomeObject = new SomeObject(Math.random()*303);
  8.     netStream.send(”receiverFunction”, someObject);
  9. }

the timer-function sends an instance of SomeObject with a random value every second through the stream.

receiving peer:

Actionscript:
  1. var netStream:NetStream = new NetStream(nc, id_of_sending_peer);
  2. var clientObj:Object = new Object();
  3. clientObj.receiverFunction = function(arg1:SomeObject):void{ trace(arg1.value); }
  4. netStream.client = clientObj;

we want the receiver to accept the object and trace its value in the console.

unfortunately the application in its current state would throw an AsyncErrorEvent caused by the fact that the receiver is not able to read a SomeObject from the stream, it only sees a strange Object of unknown type:
Type Coercion failed: cannot convert Object@3019da9 to SomeObject.

maybe you know this error from experiments with ByteArray. in fact there is quite a sum of analogies between the ByteArray-methods writeObject() and readObject() and the exchange of data via NetStream.send() - that's because both are data streams.
so we have to make our SomeObject serializable, means: if we can write it to a ByteArray and read back again, we're halfway there.

i think i'll put a jump here, because this may be getting a longer article :)


serialize it

first of all our class has to implement the interface IExternalizable, the language reference tells us why:

The IExternalizable interface provides control over serialization of a class as it is encoded into a data stream. The writeExternal() and readExternal() methods of the IExternalizable interface are implemented by a class to allow customization of the contents and format of the data stream (but not the classname or type) for an object and its supertypes. Each individual class must serialize and reconstruct the state of its instances.

so we have to find a way to break our SomeObject down to its pieces. fortunately the load of our class is only one integer, so the readExternal() and writeExternal() implementations would look something like this:

Actionscript:
  1. public function readExternal(input:IDataInput):void
  2. {
  3.     i = input.readInt();
  4. }
  5.  
  6. public function writeExternal(output:IDataOutput):void
  7. {
  8.     output.writeInt(i);
  9. }

when an instance of SomeObject is written to a data stream, it "manually" writes the load, i, to the output-stream.
and when you try to read the instance from an input-stream, it "manually" reconstructs the object by creating a blank object and filling it with the load.
(those streams i'm talking about can be one of the following: ByteArray, LocalConnection, SharedObject, NetConnection and in our case NetStream!)

note! this blank object is an instance created with no parameters, like new SomeObject(), that is why we have to put a standard-constructor in place of the one expecting the value parameter:

Actionscript:
  1. public function SomeObject(value:int=0)
  2. {
  3.     i = value;
  4. }

so that's it? ha! of course not. i think adobe should really add this piece of information to the api of IExternalizable, because without it the readExternal() and writeExternal() methods are nice and all - but the serialization just won't work.
we need to register the class for AMF encoding (which i called 'serialization' until now and from here on again), the flash.net package supplies the function for that: registerClassAlias()!

a nifty way to do this for our class is with a static constant. with a static constant we can have the function call to registerCallAlias() only once for all our instances, even though it returns nothing:

Actionscript:
  1. private static const ALIAS:* = registerClassAlias( "SomeObject", SomeObject );

ok, now we're nearly set. our class is now ready to be written to and read from a data stream, e.g. ByteArray:

Actionscript:
  1. var someObject:SomeObject = new SomeObject(303);
  2.  
  3. var byteArray:ByteArray = new ByteArray();
  4. byteArray.writeObject(someObject);
  5. byteArray.position = 0;
  6.  
  7. var someClone:SomeObject = byteArray.readObject();
  8. trace(someClone.value); //outputs 303

this is a good and fast way to test if the serialization and reconstruction of your class works without taking the long route through the internet and back again via NetStream ;)


stupidity

the local serialization works. the remote however does NOT:
ArgumentError: Error #2173: Unable to read object in stream. The class SomeObject does not implement flash.utils.IExternalizable but is aliased to an externalizable class.

why? i dont really know the answer (maybe you do?), but i know the solution:
we have to (trash-)instantiate the class once on the receiver's side. on the receiving NetStream's client-object to be exactly.

what? yeah right. here is the way how to do this for our clientObj we used above:

Actionscript:
  1. clientObj = new Object();
  2. clientObj.nonsense = new SomeObject();
  3. clientObj.nonsense = null;
  4. clientObj.receiverFunction = function(arg1:SomeObject):void { trace(arg1.value); }
  5. netStream.client = clientObj;

you could also write a new class and use it as the NetStream's client-object:

Actionscript:
  1. package 
  2. {
  3.     import SomeObject;
  4.    
  5.     public class ReceiverClient
  6.     {
  7.        
  8.         public function ReceiverClient()
  9.         {
  10.             new SomeObject();
  11.         }
  12.        
  13.         public function receiverFunction(so:SomeObject):void
  14.         {
  15.             trace(so.value);
  16.         }
  17.     }
  18. }

and finally, DONE! now the peers can exchange instances of SomeObject without any strange errors.

here's the now complete and working SomeObject-implementation:

Actionscript:
  1. package 
  2. {
  3.     import flash.net.registerClassAlias;
  4.     import flash.utils.IDataInput;
  5.     import flash.utils.IDataOutput;
  6.     import flash.utils.IExternalizable;
  7.    
  8.     public class SomeObject implements IExternalizable
  9.     {
  10.         private static const ALIAS:* = registerClassAlias( "SomeObject", SomeObject );
  11.        
  12.         private var i:int;
  13.        
  14.         public function SomeObject(value:int=0)
  15.         {
  16.             i = value;
  17.         }
  18.        
  19.         public function get value():int { return i; }
  20.        
  21.         public function readExternal(input:IDataInput):void
  22.         {
  23.             i = input.readInt();
  24.         }
  25.        
  26.         public function writeExternal(output:IDataOutput):void
  27.         {
  28.             output.writeInt(i);
  29.         }
  30.     }
  31. }


conclusion

i know it sounds like a lot to do, but essentially you just have to:

  • implement IExternalizable
  • register the class for serialization with registerClassAlias()
  • instantiate the class on the receiver's client once

just remember always to break everything down to serializable types and provide a way to put it back together again.


i hope you guys come up with some cool ways to use this! :)

This entry was posted in Actionscript, Networking. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

9 Comments

  1. Posted March 25, 2009 at 10:08 pm | Permalink

    Very interesting use of RTMFP. I’ve been playing around with Stratus as well. I’ve also been thinking of the power of RTMFP mixed with Alchemy and streaming Bytearrays.

    Cool stuff. Thanks.

  2. Jack
    Posted April 17, 2009 at 9:04 pm | Permalink

    Hi Kris. Great article. Been messing around with trying to get a “pen” class which mimics drawing with a pen to be streamed from one peer to another. But not sure if rtfmp is the best way to go…any suggestions would be great.

  3. kris
    Posted May 11, 2009 at 3:43 pm | Permalink

    hey jack, i’d say you just have to push the coordinates through the rtfmp-channel.. and some commands like PEN_UP and PEN_DOWN the clients knows to deal with (like a switch for lineTo() and moveTo()).

  4. Posted August 18, 2009 at 2:16 pm | Permalink

    hello,

    I have used you example as template for implementing my Own Chat functionality but i am getting exception wheile getting data from server.

    ArgumentError: Error #2173: Unable to read object in stream. The class ServerObjects.chatObject does not implement flash.utils.IExternalizable but is aliased to an externalizable class.

    can you please help me out here.

  5. Posted September 5, 2009 at 1:49 pm | Permalink

    About the #2173 Error:
    Although you would expect the static ‘ALIAS’ to be instantiated, it’s not!

    Try to do a registerclassAlias before (constructor) serialization and it will work.

  6. kris
    Posted September 21, 2009 at 12:45 pm | Permalink

    alexander, the way i see it there is no really smart way to do it. if you register in the constructors you have to tell the compiler to include the classes, don’t you? i’ll have to see for myself in the evening.

  7. Ricky
    Posted October 23, 2009 at 5:01 pm | Permalink

    Great!! Kris, very useful. I have found another article on Stratus and RTMFP if you guys want to have look on:

    http://www.msinghinteractive.com/blog/tag/p2p-application-in-as3-and-flash-playe-10/

    Cheers

  8. Posted December 10, 2009 at 1:24 pm | Permalink

    Iep, thanks a lot for the tuto, but just one thing that made me loose time is that it’s not clear you need to make publis, and play with the respective netStreams… after looking at your source code it’s more understandable, but I suggest you to add this point.

    Really good blog btw, continue !

  9. Posted January 29, 2011 at 6:04 am | Permalink

    “instantiate the class on the receiver’s client once”.

    That was missing in my case! Thanks a lot for very well written article.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*