关于unity中 使用Protorbuf-net 序列化对象(iphone)

来源:互联网 发布:淘宝挖宝酱 假货 编辑:程序博客网 时间:2024/06/05 11:34

本文转载于http://www.frictionpointstudios.com/blog/2011/3/31/using-protobuf-net-serialization-in-unity-iphone.html


USING PROTOBUF-NET SERIALIZATION IN UNITY IPHONE

If performance is an issue for your serialization in a Unity iPhone project, this post shows you how to use the protobuf-net library.

SOME BACKGROUND

As part of a new game project I'm working on, I had to store level information in a file and read it in for each new level. I had created a simple level editor as a windows WPF C# project and then I would serialize my level model class onto the file system then include it in my Unity project to be loaded in during runtime.

I initially used simple XML serialization, but as the levels increased in complexity it was taking longer and longer (well, 100s of miliseconds, but every bit counts ;-) to deserialize. So, off to the web to find the fastest and easiest serialization. The best candidate was Marc Gravell's protobuf-net, which provides 'Fast, portable, binary serialization for .Net' using Google's protocol buffers technology. And he's not kidding on the 'Fast' part either, see the performance stats here.

So I grabbed the library, chuck it into Unity and start testing. It worked in Unity Windows, worked in Unity Mac but when I deployed it to my iPhone I hit this:

ExecutionEngineException: Attempting to JIT compile method

Now, I'm no expert on the inner working of .Net and it's relationship with iOS running Mono, but according to thisthread on the Unity forums, the culprit is the JIT (Just in Time) compilation of classes that have not been seen by the system before. Mono on iOS is an AOT (Ahead of Time) only system, so that's why it craps out. I'm sure smarter people than me could provide a better explanation, but that will do for now.

So, I emailed Marc Gravell for help because he mentioned in this thread that version 2 of protobuf-net would have a 'pre-compile to dll' option, meaning that the serializer/deserializer classes can be pre-made in a dll instead of on the fly (and JIT'ed). I think that's how it works.

Anyway, he sent me an alpha version of a Unity iPhone friendly protobuf-net and that's the one that works. Woohoo.

I also have to say that this is all info that Marc sent me so all credit goes to him here. I wouldn't know a protocol buffer if it came up and bit me in the arse to be honest.

THE SOLUTION

First up, download the alpha version of the protobuf-net libraries. The link Marc sent me is this one but go check out the site to see if there is a later version.

This contains two libraries, the 'Light Framework' and 'Full Framework' versions of protobuf-net. The basic procedure is this:

  • Create a library dll (assembly) of the model classes you want to serialize/deserialize. That is, just create a new 'Class Library' Visual Studio project and have it contain only your model. (I don't use MonoDevelop but I'm sure it's a similar process). You will have to reference the 'Light Framework' dll in this project in order to use the  [ProtoContract]/[ProtoMember] attributes, described in the Getting Started guide. Build this project to produce your MyModel.dll.
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
namespaceProtoTest
{
    // Simple model classes, with some inheritence and generics thrown in.
    [ProtoContract]
    publicclass MyModel
    {
        [ProtoMember(1)]
        publicint int1 { get;set; }
        [ProtoMember(2)]
        publicTestEnum enum1 { get;set; }
 
        publicList<int> intList { get;set; }
        [ProtoMember(3)]
        publicList<float> floatList { get;set; }
        [ProtoMember(4)]
        publicList<string> stringList { get;set; }
 
        [ProtoMember(5)]
        publicList<anotherclass> anotherClassList { get;set; }
    }
 
    [ProtoContract, ProtoInclude(10, typeof(DerivedClass))]
    publicclass AnotherClass
    {
        [ProtoMember(1)]
        publicstring string1 { get;set; }
    }
 
    [ProtoContract]
    publicclass DerivedClass : AnotherClass
    {
        [ProtoMember(1)]
        publicfloat float1 { get;set; }
    }
 
    publicenum TestEnum
    {
        run,
        walk,
        skip
    }
}
</anotherclass></string></float></int>

    Note: Make sure your model project is set to .Net 2.0 in the project properties, otherwise Unity will throw up the following error:

    Unhandled Exception: System.TypeLoadException: Could not load type 'System.Runtime.Versioning.TargetFrameworkAttribute' from assembly 'MyModel'
  • Next you need to create the serilization/deserialization classes. So create a new 'Console Application' Visual Studio project. Now, for this one you need to reference the Full Framework protobuf-net library as well as obviously your newly created MyModel.dll assembly. 
  • Now you need the code to create the libraries.
?
1
2
3
4
5
6
7
var model = TypeModel.Create();
 
model.Add(typeof(AnotherClass),true);
// Note: you don't need to add DerivedClass here, in fact it craps out if you do.
model.Add(typeof(TestEnum),true);
model.Add(typeof(MyModel),true);
model.Compile("MySerializer","MySerializer.dll");
  • This will output 'MySerializer.dll' 
  • Now we have our serialization library that we can use in our Unity project. So now you have to add three assemblies to your Unity iPhone project:
    • MyModel.dll
    • MySerializer.dll
    • Protobuf-net.dll (Light Framework)

And we're good to go.

To serialize the files in my external application, I used the following code. I haven't played around with writing files to the iOS file system so I won't post that code, but I'm sure it's similar once you get the paths correct.

?
1
2
3
4
5
6
7
8
MyModel myNewModel = newMyModel();
 
MySerializer mySerializer = newMySerializer();
 
using(var file = File.Create("TestFile001.bytes"))
{
    mySerializer.Serialize(file, myNewModel);
}

In my case I was creating my game level files in an external application, so having the libraries external was actually more convenient. Once I had run my level editor app and created the binary serialized output files, I figured the easiest way to load them in Unity was via TextAsset class. TextAsset can be used to load files from the Resources folder just like any other resource, and despite the name, it is also fine for binary files. 

Note: From the Unity docs on TextAsset

If you're using the text asset to contain binary data, you should make sure the file has the .bytes extension. For any other of the extensions the TextImporter will try to strip nonascii characters if it is unable to parse the file as an utf8 string.

So inside our Unity project scripts, to read in the binary file we just use this.

?
1
2
3
4
5
6
7
8
9
10
TextAsset textFile = Resources.Load("TestFile001")asTextAsset;
 
MySerializer mySerializer = newMySerializer();
 
MyModel readInMyModel;
 
using(System.IO.Stream s = newSystem.IO.MemoryStream(textFile.bytes))
{
    readInMyModel = mySerializer.Deserialize(s, null,typeof(MyModel))asMyModel;
}

I had a look via Reflector and the second parameter to Deserialize() there is used in case your type variable is null, so I assume you can use either one.

And there you have it. A bit more work than just using a library directly, but if performance is an issue then it is well worth the effort. I haven't done proper metrics yet, but from a quick look it seems at least an order of magnitude faster than the XmlSerializer I was using before.

 

Edit: In response to the comment below about not being able to use Vector3.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[ProtoContract]
publicclass MyVector3
{
    [ProtoMember(1)]
    publicfloat x { get;set; }
 
    [ProtoMember(2)]
    publicfloat y { get;set; }
 
    [ProtoMember(3)]
    publicfloat z { get;set; }
 
    publicMyVector3()
    {
        this.x = 0.0f;
        this.y = 0.0f;
        this.z = 0.0f;
    }
 
    publicMyVector3(floatx, floaty, floatz)
    {
        this.x = x;
        this.y = y;
        this.z = z;
    }
 
    publicstatic implicit operator Vector3(MyVector3 v)
    {
        returnnew Vector3(v.x, v.y, v.z);
    }
 
    publicstatic implicit operator MyVector3(Vector3 v)
    {
        returnnew MyVector3(v.x, v.y, v.z);
    }
}
0 0