Automatic XNB serialization in XNA Game Studio 3.1


One of the new features in Game Studio 3.1 is automatic serialization for .xnb files. This is a feature I have wanted ever since we first designed the Content Pipeline, so it makes me very happy that we finally found time to implement it!

 

Short Version

You know those ContentTypeWriter and ContentTypeReader thingamyhickies? You don’t need them any more! Delete them, and the Content Pipeline will automatically serialize your data using reflection.

Note: you can still write a ContentTypeWriter and ContentTypeReader by hand if you want. You just don’t have to any more.

 

Ever So Slightly Longer Version

If the Content Pipeline encounters a type that has no matching ContentTypeWriter, it will dynamically create one for you. This works in a similar way to the IntermediateSerializer, but it reads and writes binary .xnb files instead of XML.

  • By default it will serialize all the public fields and properties of your type (assuming they are not read-only).

  • If you want to skip a public member, mark it with a [ContentSerializerIgnore] attribute.

  • If you want to include a private, protected, or internal member, mark it with a [ContentSerializer] attribute.

  • If you want to serialize a data structure that contains cyclic references, mark these with [ContentSerializer(SharedResource = true)].

 

Example

The trick to successfully using the Content Pipeline is to understand which code runs at build time versus runtime. Even though the serialization is now automatic, you can still get in a muddle if you have things like cyclic references where your game tries to use a type that is defined inside itself while building itself!

Here’s an example of using the new serializer:

Create a new Windows Game project. Let’s call this MyGame.

Right-click on the Solution node, Add / New Project, and choose the Windows Game Library template (not Content Pipeline Extension Library, because we want to use this at runtime as well as build time). Call it MyDataTypes.

Add this class to the MyDataTypes project:

    public class CatData
    {
        public string Name;
        public float Weight;
        public int Lives;
    }

We’re going to use this type to build some custom content, so we need to reference it during the Content Pipeline build process. Right-click on the Content project that is nested inside MyGame, choose Add Reference, and select MyDataTypes from the Projects tab.

Now we can add this file (let’s call it cats.xml) to our Content project:

    <?xml version="1.0" encoding="utf-8" ?>
    <XnaContent>
      <Asset Type="MyDataTypes.CatData[]">
        <Item>
          <Name>Rhys</Name>
          <Weight>17</Weight>
          <Lives>9</Lives>
        </Item>
        <Item>
          <Name>Boo</Name>
          <Weight>11</Weight>
          <Lives>5</Lives>
        </Item>
      </Asset>
    </XnaContent>

Hit F5, and the content will build. Look in the bin directory, and you will see that it created a cats.xnb file. If you build in Release configuration, this file will be compressed. With the above example, my 326 byte XML file becomes 270 bytes in .xnb format, but the compression ratio will improve as your files get bigger.

Before we can load this data into our game, we must reference the MyDataTypes project directly from MyGame as well from its Content sub-project, in order to use our custom type at runtime as well as build time. Once we’ve done that, we can load the custom content:

    CatData[] cats = Content.Load<CatData[]>("cats");

If you create a copy of this project for Xbox 360, you will notice that although the Xbox version of MyGame references the Xbox version of MyDataTypes, its Content project still uses the Windows version of MyDataTypes, even though it is building content for Xbox. This is an important point to understand. Because our custom type is used both at build time (on Windows) and at runtime (on Xbox), we must provide both Windows and Xbox versions of this type.

 

Type Translations

Remember how some types are not the same at build time versus runtime? For instance a processor might output a Texture2DContent, but when you load this into your game it becomes a Texture2D.

The .xnb serializer understands such situations, as long as you give it a little help. For instance if I used this type in MyGame:

    public class Cat
    {
        public string Name;
        public Texture2D Texture;
    }

I could declare a corresponding build time type in a Content Pipeline extension project:

    [ContentSerializerRuntimeType("MyGame.Cat, MyGame")]
    public class CatContent
    {
        public string Name;
        public Texture2DContent Texture;
    }

Note how both types have basically the same fields, and in the same order, but the runtime Cat class uses Texture2D where the build time version has Texture2DContent. Also note how the build time CatContent class is decorated with a ContentSerializerRuntimeType attribute. This allows me to use CatContent objects in my Content Pipeline code, but load the resulting .xnb file as type Cat when I call ContentManager.Load.

 

Performance

Every silver lining has a cloud, right?

The automatic .xnb serialization mechanism uses reflection. Reflection is slow, and causes a lot of boxing, which can result in a lot of garbage collections.

Fortunately, this is not as bad in practice as it sounds on paper. Many custom data types are small, or at least the custom part of them tends to contain just a few larger objects of types that have built-in ContentTypeWriter implementations (textures, vertex buffers, arrays or dictionaries of primitive types, XNA Framework math types, etc). In such cases the performance overhead will be low. When I converted a bunch of existing Content Pipeline samples to use the new serializer, it did not significantly affect their load times.

But if you have a collection holding many thousands of custom types, you may see poor load performance. In such cases, you can provide a ContentTypeWriter and ContentTypeReader for just the specific types that are slowing you down. The new system is easier to use, but it can be more efficient to do things the old manual way.


Comments (38)

  1. wmfwlr says:

    This will come in very handy, for sure. I could have used it a month ago. I guess now I’ll be one of those old guys who looks back and says "I remember when I had to write my own ContentTypeWriter. Kids nowadays have it easy".

  2. CatalinZima says:

    Ok, I was waiting for this post :) It’s good to be finally be rid of writing those writers/readers.

    Now, a question:

    For the last example, the one with the Texture2DContent: what is the corresponding input XML for this example? Can the Texture2DContent be simply written in the XML (how?), or does it have to be initialized inside a ConentProcessor (because this is where you have access to the context)?

  3. matthewdavis1980@hotmail.com says:

    Shawn, with this new feature is it possible to serialize to xnb during runtime?

  4. ShawnHargreaves says:

    barkers crest: no. Writing .xnb files is done as part of the Content Pipeline build process, same as before.

  5. ShawnHargreaves says:

    Catalin: in theory you could embed the texture data directly into XML (TextureContent can be serialized by IntermediateSerializer) but I’m not sure how useful that would be!

    More likely you would want to just store the texture filename in your XML, then go off and read that file in during the build process, which would require some processor code.

    Here’s an example from one of our unit tests of what a TextureContent looks like in IntermediateSerializer XML:

    <XnaContent xmlns:Graphics="Microsoft.Xna.Framework.Content.Pipeline.Graphics" xmlns:PackedVector="Microsoft.Xna.Framework.Graphics.PackedVector">

     <Asset Type="Graphics:Texture2DContent">

       <Mipmaps>

         <Mipmap Type="Graphics:PixelBitmapContent[PackedVector:Alpha8]">

           <Width>2</Width>

           <Height>2</Height>

           <Pixels>

             <Row>3B 6B</Row>

             <Row>AA FF</Row>

           </Pixels>

         </Mipmap>

         <Mipmap Type="Graphics:PixelBitmapContent[PackedVector:Alpha8]">

           <Width>1</Width>

           <Height>1</Height>

           <Pixels>

             <Row>1F</Row>

           </Pixels>

         </Mipmap>

       </Mipmaps>

     </Asset>

    </XnaContent>

  6. CatalinZima says:

    Ok, so I still need a Processor to solve this in an elegant (and resource-cheap) way, since textures will be shared between different assets (which means embedding them isn’t too good).

  7. ShawnHargreaves says:

    I would most likely do this in a processor, yes. The auto serialization doesn’t remove the need to write processor code if you want to process data or trigger other actions (like going off and building a texture) at runtime.

    There are a couple of other options, though:

    – you could just embed a string asset name in in the XML, then call ContentManager.Load on that name at runtime after you load the .xnb

    – or you could embed an ExternalReference instance, in which case the .xnb deserializer will automatically follow that reference and load the asset it points to in place of the original reference

    These options only work if the referenced asset is already being built through some other mechanism, though, so you know what name the reference should point to. They would be an option if you have a bunch of textures that are already added to your Content project, and can just put the name of these textures in this other XML asset, but unlike using a custom processor, they will not automatically go off and build that texture for you if the XML references a texture that is not otherwise part of your Content project.

  8. CatalinZima says:

    And unfortunately, I’d have no errors during build telling me that some textures are missing.

  9. DCCreation says:

    Just amazing !!!

    Thank you really !

    I work with XNA 3.0, and it couldn’t auto serialize my XML objects, and I’m so happy to see this post !

    It’s a very good new. I earn a lot of time.

  10. Kevin Smeltzer says:

    I see that runtime serialization to .xnb will not be availble which makes sense because that is part of the build process.

    Does that mean that this new fancy reflection based auto serialization will be useless for necessary things such as saving/loading game states, or can you use the framework to seriliaze to something other than .xnb at run time?

    Thanks!

  11. ShawnHargreaves says:

    Kevin: this new serializer is part of the Content Pipeline, so the writing part is only available at build time.

    For runtime serialization of things like save games, I would probably start with XmlSerializer, or if you need something more efficient, you could use BinaryWriter/BinaryReader to create a more compact binary format.

  12. Kevin Gadd says:

    So I’ve finally gotten around to trying out the content pipeline. Until now, I’ve been loading/saving all my game data using XML serialization, even on the 360, and it works fine – but I want to start deploying my content as packed XNB files for better load performance.

    As a basic implementation, I wrote a raw importer to read in my XML files as byte[] and then a processor to convert those to my runtime data structures. This seems to work, but for some reason, the XNB files generated by this automatic reflection-based serializer are around 10 times larger than my XML files.

    Is this behavior intended, or am I using the content pipeline incorrectly? Will the overhead remain fixed as my files grow larger? It’s hard to tell whether or not it’s fixed overhead from looking at the XNB files in a hex editor.

  13. ShawnHargreaves says:

    Kevin: this really depends on what your data looks like. XNB files are usually much smaller than XML (especially if you enable compression) but it’s certainly possible to construct objects where this would not be the case.

    I would recommend the forums on creators.xna.com if you want to follow up with more details, as they’re somewhat nicer than these comment boxes for having a discussion :-)

  14. Kevin Gadd says:

    Luckily it turns out it was a bit of a false alarm :) The size went down significantly once I fiddled with some of the serialization attributes – there’s still some overhead, but from testing it appears to be fixed – most of it comes from the fact that the ReflectiveWriter has to write out the fully qualified names of all the types it uses, which isn’t the case in my source XML.

    The actual binary data for the content seems to be quite compact, which is what I was hoping for.

  15. Phil says:

    I currently have several different (fairly large ~12KB) xml files that I copy to the output directory, and load when my game starts up.  They seem to take a long time to load on the Xbox.

    Would I expect to see a performance improvement by moving these files into content pipeline? (I would write a ContentImporter that deserializes from xml, and then uses this new automatic serialization functionality).

    There would be some improvement from the smaller file sizes… but is the deserialization functionality generally faster than what I have now (the standard Xml deserialization)?

  16. ShawnHargreaves says:

    Phil: no promises (the only way to be 100% sure is to try this both ways and time it with your particular data) but in general I would expect XNB serialization to be much faster than XML.

  17. mod says:

    something weird happens ! it tells me that it can’t find the XML file .. i didn’t try serializing before to tell u the truth.. but this really is bugging me..

  18. GrahamRanson says:

    Is it possible to use serialize Dictionary objects? And if so, how should they be defined the  XML file?

  19. Paul Solt says:

    What happens when you serialize a sub-class of another class? My game objects all have common attributes which are within the base class. Future classes sub-class and provide additional functionality.

    Does it automatically grab the super classes serializable attributes?

  20. ShawnHargreaves says:

    > Does it automatically grab the super classes serializable attributes?

    Of course!

    More technically, it calls into the ContentTypeWriter for the base class, which might be either manually implemented or automatically generated (the base class writer doesn’t have to be created the same way as that for the derived type).

  21. Paul Solt says:

    Another question related to XNB serialization for game objects in "level files."

    Is there any support for different "versions" of the class being serialized? My current solution is to mark public attributes as [ContentSerializer(Optional = true)]

    when I add a new attribute to the class.

    If I need version support would I have to keep track of a version value for the "game object" and then write my own reader/writer using that version number?

  22. ShawnHargreaves says:

    > Is there any support for different "versions" of the class being serialized

    The only built in support for .xnb versioning is the TypeVersion property on ContentTypeWriter and ContentTypeReader, which deliberately prevents loading if the version has changed.

    I guess you could build some more flexible mechanism of your own, but I wouldn’t really recommend this. The general thinking is that .xnb files are compiled data, so they don’t really need to be versioned on the fly. It’s usually easier to just recompile them from the source content (which obviously does need to have good versioning and backward compatibility) any time the version changes, which keeps the runtime loading code nice and simple.

  23. Paul Solt says:

    What does source content with good versioning and backward compatibility look like? Are there examples of this on the creators club website?

    My current routine is to convert the XML content for my game objects to the latest version using Find/Replace. I haven’t figured out a good system to make it automated.

  24. ShawnHargreaves says:

    The FBX file format is an example of something with good backward compatibility.

    In general, designing a format and writing a loader that supports multiple versions is a lot of work. The Content Pipeline does nothing to help with this, but also does nothing to get in your way: this is entirely down to what code you put in your importer, so any standard computer sciencey format versioning techniques can be used.

    Depending on the amount of data you are dealing with and how many people you have creating it, in many cases you may find it easier to just manually fix up any old files rather than doing the work to automate this.

  25. Chris says:

    If I have a hierarchy of types like this:

       public class Animal

       {

           public string Name;

       }

       public class Cat : Animal

       {

           public int Age;

       }

    How do I create a corresponding content type (or types) decorated with the ContentSerializerRuntimeType attribute, so that I can load the Cat type at runtime without writing a custom ContentTypeReader/Writer for it?

    I tried this:

       [ContentSerializerRuntimeType("Cat, MyAssembly")]

       public class CatContent

       {

           public string Name;

           public int Age;

       }

    Which works during the build phase.  However when I attempt to call Content.Load<Cat>("ACat") I receive a ContentLoadException.

    Microsoft.Xna.Framework.Content.ContentLoadException was unhandled

     Message="Error loading "ACat". Cannot find ContentTypeReader for Animal."

     Source="Microsoft.Xna.Framework"

     StackTrace:

          at Microsoft.Xna.Framework.Content.ContentTypeReaderManager.GetTypeReader(Type targetType, ContentReader contentReader)

          at Microsoft.Xna.Framework.Content.ContentTypeReaderManager.GetTypeReader(Type targetType)

          at Microsoft.Xna.Framework.Content.ReflectiveReader`1.Initialize(ContentTypeReaderManager manager)

          at Microsoft.Xna.Framework.Content.ContentTypeReaderManager.ReadTypeManifest(Int32 typeCount, ContentReader contentReader)

          at Microsoft.Xna.Framework.Content.ContentReader.ReadHeader()

          at Microsoft.Xna.Framework.Content.ContentReader.ReadAsset[T]()

          at Microsoft.Xna.Framework.Content.ContentManager.ReadAsset[T](String assetName, Action`1 recordDisposableObject)

          at Microsoft.Xna.Framework.Content.ContentManager.Load[T](String assetName)

          at Nexus.Client.Client.Initialize() in C:SourceNexusC#ClientClient.cs:line 97

          at Microsoft.Xna.Framework.Game.Run()

          at Nexus.Client.Program.Main(String[] args) in C:SourceNexusC#ClientProgram.cs:line 14

     InnerException:

  26. ShawnHargreaves says:

    chris:

    a) it’s generally better to use the creators.xna.com forums for this kind of support question – blog comments aren’t really the best place for posting code snippets!

    b) With automatic serialization, your design time inheritance hierarchy needs to match the runtime one, so you need an AnimalContent base class for use at design time. If your design time and runtime types are structurally different, you cannot use automatic serialization, and will have to write an old-style ContentTypeWriter/Reader pair for them.

  27. Thomas says:

    We have an article with some additional tipps to consider with XNB serialization on our blog http://blog.brightside-games.com/?p=65 so maybe that can answer some of your questions here. best Thomas

  28. Mike says:

    Hi Shawn,

    I hope you don’t mind me posting a) to an old topic and b) with such a (probably) stupid question… But I have been going around in circles for days trying to find the best way around this problem, and I am at wit’s end!

    Would it help if I described my problem as being pernicious? 😉

    I have implemented a content importer (without using a ContentTypeReader/Writer, since I am using XNA 3.1) which reads a XML file and creates a List of Sprites.

    (This Sprite object is in a (referenced) second project, as per the guidance on one of your previous posts).

    However, I now want to derive classes (Enemies, Scenery Tiles, Players etc) from this Sprite object, as I don’t want the various specialized methods within the base Sprite class.

    I naively expected to be able to downcast from a (say) Sprite object to a Player, but I am unable to do so, as the importer creates instances of the base class (Sprite).

    (I should at this point say that I thought I understood down-casting, but I have always used it from an object that had previously been up-cast)

    How should I approach this design issue?

    Anyway, please forgive me for asking such a newbie question!

    Cheers,

  29. ShawnHargreaves says:

    Mike: you can only cast a Sprite object to a Player if the object in question is in fact a Player instance. How do you originally create these objects? You will need to make sure you create them all using the correct types for each. The serialization mechanism will preserve whatever types you give it so my guess is you are not creating the right types in the first place.

    btw. I would recommend the forums on creators.xna.com if you have further questions – those tend to work better than blog comments for this kind of support discussion!

  30. Guffy says:

    Thank you for this post.It was realy helpful!

  31. Tim says:

    Dude I followed your instructions and this doesn't work.

    Error 1 Unsupported type. Cannot find a ContentTypeWriter implementation for MyDataTypes.CatData. C:UsersAdministratorDocumentsVisual Studio 2008ProjectsMyGameMyGameContentXMLFile1.xml MyGame

  32. ShawnHargreaves says:

    > Dude I followed your instructions and this doesn't work.

    I bet you're doing this in a Game Studio 3.0 (not 3.1) project.

  33. Tim says:

    When I create the project it is always 3.0 then I have to upgrade it.  When I formate it exactly like you I also get this error.

    Error 1 There was an error while deserializing intermediate XML. 'Element' is an invalid XmlNodeType. Line 4, position 8. C:UsersAdministratorDocumentsVisual Studio 2008ProjectsMyGameMyGameContentxmlfile.xml 4 8 MyGame

    That line and position is the <item> tag.

    I am using Visual Studio Express C#

    Thanks for any help.

  34. Tim says:

    ok so i got it working in windows but not xbox.

  35. Tim says:

    sorry for the double post….I got it.  In my XML file I have a lower case i for <Item> !!! OMG

  36. Michael Murphy says:

    I went through as suggested and removed all my ContentTypeReader classes (which were nested inside the original class to hold the data) and also the ContentTypeWriter classes (which were in a seperate assembly). I am following the tutorial for the RPG Game Kit on XNA.com and it seems that after i delete these, it will not dynamically generate the types. it errors and says can not find type Data.CharacterClass, which is still clearly defined, however there is no CharacterClassReader any more. I am trying to solve this huge enigma, the "Content Pipeline." Any help would be greatly appreciated.

  37. kame says:

    Hi Shawn, could you please post an example using a jagged array. Thanks.

  38. Regeta says:

    Thank you, this helped me even after I had already gone through the same tutorial on the Microsoft website.