AMF (de-)serializer

3 views
Skip to first unread message

Anjo Krank

unread,
Apr 8, 2008, 4:13:08 PM4/8/08
to milenia...@googlegroups.com
Hi Milan,

the current AMF handler version seems to be roughly the same as in 0.5
- which had quite a few problems:

- no support for nested structures (var x:Array = []; x[0] = x;
remoteCall(x) => crash)
- no object mapping support (var test:Test <-> Test test)
- strange errors (when byte values under-run)

I fixed the 0.5 version to support all this, but won't make the effort
to adapt it unless you ensure me that it will get included...

For the flash class <> java class mapping, it involves a "Resolver"
that is a property of the client. Code then looks like this:


client.setResolver(new AMFResolver() {

// gets called from the decoder when a var test:Test is
supplied as an argument
public Wrapper decodeTypedObject(String className,
Wrapper objectData) {
Test test = new Test();
test.x = objectData.hashValue.getString("x");
return new Wrapper("Test", test); // undefined
}

// gets called from the encoder when a "Test test" is
supplied as an argument
// wrapper was created with new Wrapper(test)
public Wrapper classNameForTypedObject(Wrapper wrapper) {
return new Wrapper(wrapper.className); // return
string wrapper
}

public Wrapper encodeTypedObject(Wrapper wrapper) {
Object realObject = wrapper.value;
WrapperMap<String, Object> data = new
WrapperMap<String, Object>();
data.put("x", "Hi");
Wrapper result = new Wrapper(data);
return result;
}
});

for a simple class Test {String x;}

Fr the rest, the AMFDecoder and AMFEncoder are pretty much rewritten
(no big deal, btw, it was just a bitch testing it). Also affected are
Wrapper, WrapperList and less so Client and ClientController. I could
also send you the 0.5 code and you can check it yourself, but there's
also a bunch of other stuff in there I'm not sure of yet...

Cheers, Anjo

Anjo Krank

unread,
Apr 8, 2008, 4:53:22 PM4/8/08
to milenia...@googlegroups.com
Ok, checking the new source a bit more: I don't think it's possible to
code cyclic references without keeping state in the Amf coding
classes. These static methods just don't cut it. And the "Encoder"
class should be named "AmfCoder", shouldn't it?

Cheers, Anjo

Milan Toth

unread,
Apr 8, 2008, 6:10:41 PM4/8/08
to milenia...@googlegroups.com
Hi Anjo,

> the current AMF handler version seems to be roughly the same as in 0.5
> - which had quite a few problems:
> - no support for nested structures (var x:Array = []; x[0] = x;
> remoteCall(x) => crash)

The new version handles mixed arrays and mixed objects also

> - no object mapping support (var test:Test <-> Test test)

True. I don't need this functionality in Milenia.

> - strange errors (when byte values under-run)

Hopefully all of them are gone, i heavily reworked the encoders.

> I fixed the 0.5 version to support all this, but won't make the effort
> to adapt it unless you ensure me that it will get included...

Well, the above all i can say. But i promise that the encoders won't
be changed any more ( too badly :) ), so you can finalize your own
encoders.

Regards

Milan

Milan Toth

unread,
Apr 8, 2008, 6:11:44 PM4/8/08
to milenia...@googlegroups.com
No, its only amf related function is to encode/decode invokes

Anjo Krank

unread,
Apr 8, 2008, 6:57:45 PM4/8/08
to milenia...@googlegroups.com
Hi Milan,

Am 09.04.2008 um 00:10 schrieb Milan Toth:
>> - no support for nested structures (var x:Array = []; x[0] = x;
>> remoteCall(x) => crash)
> The new version handles mixed arrays and mixed objects also

The problem here is circles in the data structures. You need to be
able to code and re-encode references. Also, the patch handles XML and
dates.

>> - no object mapping support (var test:Test <-> Test test)
> True. I don't need this functionality in Milenia.

Whooppie for you, but I need it. I don't see me pushing around
Wrapper's in the client code, you know :)

>> - strange errors (when byte values under-run)
> Hopefully all of them are gone, i heavily reworked the encoders.

Not from what I saw. The problem was byte values > 128 (or was is <
0 ?) which wrapped.

patch.txt.zip

Milan Toth

unread,
Apr 9, 2008, 2:16:45 AM4/9/08
to milenia...@googlegroups.com
Hello,

i mean simplification and performance tuning under reworking :) Well,
i have to think about reference handling, i've never ever used it, so
i don't think its necessary in a simple server.

Milan

> <patch.txt.zip>
>
>
> The patch still runs the Data Unit test, once you apply it, I'll add
> code to test the new functionality. The only thing I don't like about
> it is the toString() of the wrapper class and that the constants in
> Wrapper are not uppercased. But you can easily do that by refactoring.
>
> Cheers, Anjo

Anjo Krank

unread,
Apr 9, 2008, 2:31:25 AM4/9/08
to milenia...@googlegroups.com
Hi,

Am 09.04.2008 um 08:16 schrieb Milan Toth:
> i mean simplification and performance tuning under reworking :) Well,
> i have to think about reference handling, i've never ever used it, so
> i don't think its necessary in a simple server.

Is this a "no" on the patch?

Try this with your code:
{
...
var mixedArrayValue:Array = [];
var arrayValue:Array = [];
var objectValue:Object = {};

objectValue["key"] = "milgra" ;
objectValue["num"] = 234235;
//objectValue["array"] = arrayValue;
testXML = XML(objectValue);

mixedArrayValue[0] = "dasfdasg";
mixedArrayValue[1] = 234235;
mixedArrayValue[2] = 223235.2351235;
mixedArrayValue[3] = true;
mixedArrayValue[4] = objectValue;
mixedArrayValue[7] = false;
//mixedArrayValue[44] = objectValue;

for(var o:* in mixedArrayValue) {
arrayValue = arrayValue.concat(o);
}
arrayValue = arrayValue.concat("Test");

connection.call( "receiveArray" , null , arrayValue );
connection.call( "receiveMixedArray" , null , mixedArrayValue );
connection.call( "receiveObject" , null , objectValue );

...
}

public function receiveArray ( value:* ):void
{
var content:String = "";
for ( var a:* in value ) content += a + " : " + value[a] + ", ";
msg( "receiveArray: [" + content.replace(/,\s+$/, "") + "]");
}

public function receiveMixedArray ( value:* ):void
{
var content:String = "";
for ( var a:* in value ) content += a + " : " + value[a] + ", ";
msg( "receiveMixedArray: [" + content.replace(/,\s+$/, "") + "]");
}

public function receiveObject ( value:* ):void
{
var content:String = "";
for ( var a:* in value ) content += a + " : " + value[a] + ", ";
msg( "receiveObject: [" + content.replace(/,\s+$/, "") + "]");
}

from what I remember you get a crash in the server. Or incorrect
results. Or both.

Cheers, Anjo

Milan Toth

unread,
Apr 9, 2008, 3:36:41 AM4/9/08
to milenia...@googlegroups.com
Hi!

Yeah, you are right, it really fails after the second reference. I
have to deal with this, i will implement your patch, thanks.

Milan

Anjo Krank

unread,
Apr 9, 2008, 4:26:17 AM4/9/08
to milenia...@googlegroups.com
Mind you, it's not perfect. In particular the object de-serialization
could be better, as I don't know what happens when you have - say "var
test:Test; test.related = [test];" and try to give that as an
argument. That's also why all the objects that matter should have some
sort of unique identifier...

But is important to note that you must leave the resolving stuff to
the one that actually knows about it - be it client or app. That's why
there is a resolver class, which can look up the identifier and
actually return the correct instance instead of just creating a new
one. It's not possible to do that in a factory or other patterns.

Cheers, Anjo

Milan Toth

unread,
Apr 9, 2008, 4:27:42 AM4/9/08
to milenia...@googlegroups.com
> Hi, check out the AmfDecoder and Encoder classes, i've implemented
> the reference thingie.


Can you give me an example of the usage of the Typed object/class
object? Can i simply catch a typed object in flash under its type?

regards

Milan

Anjo Krank

unread,
Apr 9, 2008, 5:12:41 AM4/9/08
to milenia...@googlegroups.com
Totally copied together from my old version... you may need to fuzz
around a bit until it compiles and/or runs. But the idea is that you
can simply call:

var test:Test;
test = new Test();
conn.call("something", null, test)

and the java client gets a real java Test object when the invoke is
called. And vice-versa. In this example, the (de)serialization is done
manually, but you can include one of several bean serializers and plug
those in. Then you would be able to do that without code.

If I remember correctly, the AmfResolver

public class AmfResolver {

public Wrapper decodeTypedObject(String className, Wrapper
objectData) {

return new Wrapper("NoClassUnderTheSun", new Wrapper(new
WrapperMap())); // undefined
}

public Wrapper classNameForTypedObject(Wrapper wrapper) {
return new Wrapper("NoClassUnderTheSun"); // return string
wrapper
}

public Wrapper encodeTypedObject(Wrapper wrapper) {
return new Wrapper(new WrapperMap()); // undefined
}
}

is written this way only in case you forget a registerClassAlias() on
the client. Otherwise one might just make an interface from it.

Test.as:
package
{
public class Test {
public var x:String;

public function toString():String {
return "<Test x:" + x + ">";
}
}
}

Data.as:

{
...
var testValue:Test = new Test();

testValue.x = "Hi";
registerClassAlias("Test", Test);

connection.call( "receiveTest" , null , testValue );
msg( "testValue sent");
...
}

public function receiveTest ( value:* ):void
{
msg( "receiveTest: " + value + " x:" + (value != null ? value.x :
null));
}

Client.java:

static class Test {
String x;

@Override
public String toString() {
return "<Test x: " + x + ">";
}
}

public void onInvoke(Map<String, Wrapper> eventX) {

// getting client

long clientID = eventX.get("clientid").longValue;
Client client = clients.get(clientID);

client.resolver(new AmfResolver() {
// simple handler, just does the "Test" class, otherwise
we would need some checks here.


public Wrapper decodeTypedObject(String className,
Wrapper objectData) {
Test test = new Test();
test.x = objectData.hashValue.getString("x");
return new Wrapper("Test", test);
}

public Wrapper classNameForTypedObject(Wrapper wrapper) {
// string wrapper for class name
return new Wrapper(wrapper.className);
}

public Wrapper encodeTypedObject(Wrapper wrapper) {
Object realObject = wrapper.value;

WrapperMap data = new WrapperMap();
// actually, we should cast realObject to Test test
and call test.x ...


data.put("x", "Hi");
Wrapper result = new Wrapper(data);
return result;
}
});

Anjo Krank

unread,
Apr 9, 2008, 5:15:30 AM4/9/08
to milenia...@googlegroups.com
Again, from what I understand is that you *must* have a resolver on
the client or application. If you don't then you will always create
new intances of an object. If you have an resolver, then you could
make a one-to-one mapping from the client object to the server object,
as the resolver can - say - keep a map of the instances created and
return an existing one based on some client property.

Cheers, Anjo

Anjo Krank

unread,
Apr 9, 2008, 5:17:52 AM4/9/08
to milenia...@googlegroups.com
And you need to change the encoder, too... as the problem is just the
same when you want to send

WrapperList list = new WrapperList()

Wrapper arg = new Wrapper(list)
list.add(arg);

and try to send this from the server. (I just wrote that in mail, but
you should get the idea)

Cheers, Anjo

Milan Toth

unread,
Apr 9, 2008, 5:24:40 AM4/9/08
to milenia...@googlegroups.com
Hmm, so its simply an object with a class identifier. It can be handy,
but i have to rethink invoke distribution, since it only handles
Wrappers yet, and passing custom classes is tricky. Maybe if i handle
them as objects. But then in your destination function you have to
cast them into their real classes, and thats almost wrapping. But i
will think on it.

Is the referencing working for you?

Anjo Krank

unread,
Apr 9, 2008, 6:03:09 AM4/9/08
to milenia...@googlegroups.com
I can't test it here, but I really dont understand what your problem
is. My patch did all of this already, with no change to existing code.
The only change is that there is another Wrapper type with
className="SomeClassInFlash",object=someValue.

Cheers, Anjo

Milan Toth

unread,
Apr 9, 2008, 6:09:30 AM4/9/08
to milenia...@googlegroups.com
I have no problem, i'm really glad that you sent me the patches, but i
like doing things on my way, and i really have to consider every new
feature which could grow Milenia's size over 64 Kbyte :) So thats why
i'm always complaining, sorry for that.

cheers

Milan

Anjo Krank

unread,
Apr 10, 2008, 5:14:12 AM4/10/08
to milenia...@googlegroups.com
Milan,

I can understand that your goal is to keep the server under 64k.

However, I planned on actually using this software and I can't do that
if you won't integrate (IMO pretty minor) patches which I need in
order to get my job done.

For me, AMF support is important, correctness is important and being
able to launch my stuff in Eclipse and debug it is important as is
RTMPT support. I don't really have the time to argue with you and re-
apply my stuff every time I send you a patch and you write your own
code.

So unless you either apply my patches or come up with equivalent ones
I won't be able to use your code. I guess I will try my luck with Red5
and see if it is more useful :/

I'll check from time to time and see how you are doing.

Thanks and I wish you good luck, Anjo

Milan Toth

unread,
Apr 10, 2008, 7:23:10 AM4/10/08
to milenia...@googlegroups.com
Hi,

sorry about that :(

regards

Milan

Anjo Krank

unread,
Apr 11, 2008, 5:55:43 AM4/11/08
to milenia...@googlegroups.com
Hi Milan,

> I will try my luck with Red5 and see if it is more useful :/

Famous last words... So I downloaded Red5 (again), took a deep look
into all the spring config files and thought I'd rather give you
another try :)

I created a git repository and made my changes there. Then you can do
what you please and I can still continue working.

I think I found a few problems: when you close the flash debugger
window while running the test client and playing the recorded stream
(which btw sounds like crap, at least the one I put there), I get an
NPE. The problem is that StreamBuffer.destruct() null's the arrays,
but the buffer is still accessed later on. I don't understand enough
of your stream handling to see why this is happening, but you can
prevent the NPE when you change destruct() to clear the arrays instead.


/**
*StreamBufferdestructor
**/

public void destruct ( )
{

System.out.println( System.currentTimeMillis( ) + "
StreamBuffer.destruct " );

pool.clear();

positions.clear();
durations.clear();
bodysizes.clear();
timesizes.clear();

}

If you don't, the NPE will kill the ProcessThread, which will in turn
prevent other clients from playing files. So you might want to catch
runtime exceptions when processing;

--- a/MilGraServer/src/com/milgra/server/ProcessThread.java
+++ b/MilGraServer/src/com/milgra/server/ProcessThread.java
@@ -100,7 +100,14 @@ public class ProcessThread extends Thread

// step all processes

- for ( OProcess process : processes )
process.step( );
+ for ( OProcess process : processes ) {
+ try {
+ process.step( );
+ } catch(RuntimeException ex) {
+ System.err.println(ex);
+ ex.printStackTrace(System.err);
+ }
+ }

Probably also kill the client, but what do I know...

Cheers, Anjo

Reply all
Reply to author
Forward
0 new messages