More servicesWindows Live
HomeHotmailSpacesOneCare
 
MSN
Sign in
 
 
Spaces home  John Doe's SpacePhotosProfileFriendsMore Tools Explore the Spaces community

John Doe's Space

May 24

Camera = World x View x Projection

One day, I hope I will find the time to write a game. I have many - in my opinion - great ideas and I wish I could dedicate more time to it right now, but work has priority. Nevertheless, until I find the time, I still can get more familiar with the XNA framework and write some reusable components. Some of the stuff I will need and write is really basic, such as the camera I write about here, but who knows, maybe it helps someone. Well and I do not claim to be an expert in 3D programming, so if you find any big (or small) mistakes feel free to comment and I will correct them.

If you write a 3D application of any kind the most basic thing you need is usually some kind of camera class. The camera is not a GameComponent in my opinion as it does not do anything by itself, e. g. move, for that a camera controller of some sort is needed. Essentially such a class is just a wrapper for storing the position and orientation of the viewer as well as some aspects about the lens/perspective used and from that it spits out some matrices which can transform the 3D space into the 2D screen space - kind of like the real lenses of a real camera does when directing the light rays onto the film.

The first time I experimented with 3D programming there was a camera class of some kind I took from a sample and I just used it. Over time I experimented a little bit with the different properties of the camera and got a feel and understanding of it. It was not something I took time to understand from the very beginning, so while writing a clean reusable camera class, let me quickly wrap up - for you and myself - what the world, view and projection matrix do.

 

World

The world matrix transform the 3D coordinates into the coordinate system of your world. You might now this matrix from the BasicEffect, you can just use it to move, scale and rotate the objects to render. The coordinates of all my objects are usually in world coordinates or they have their own transformation in custom effects, e.g. via bones, so my general world matrix is mostly the identity matrix, i. e. no transformation necessary. This matrix might become useful if you want to flip the up vector or if you want to distort the whole world somehow, e.g. skew it, scale it, ...

 

View

The view matrix represents the camera position. Usually you would think, that by defining the camera position and rotation the camera would move in your 3D world and be somehow placed while the world is static. Actually the opposite is the case, the view matrix is the inverted position and rotation matrix and every object in the 3D world is transformed with it.

Matrix view = Matrix.Invert(rotation * translation);

This means, that the camera is static and all objects multiplied with the view matrix move in the opposite direction the camera would have moved into.

 

Projection

This matrix finally projects the 3D coordinates into 2D screen space. The projection matrix defines a view frustum. This frustum has a near plane which is essentially the screen space and a far plane which defines the maximum viewing distance. There are two different kind of projection matrices: perspective and orthogonal.

pcamera

The viewing frustum of the perspective matrix is defined by the near plane (1), the far plane (2) and the field of view (3). The field of view is the angle how far the frustum spans open. Using a very large angle is like attaching a wide angle or fisheye lens to the camera, while using a small angle effectively narrows the view and zooms objects closer. The near plane is essentially your screen space and the aspect ratio (width/height) of the projection matrix should match your screen aspect ratio or the displayed image will be distorted. If you imagine an object within the view frustum, and project a line from the origin of the frustum to the object, it will appear on the screen where this line intersects the near plane. I took some time and read up on how the matrix actually transforms the 3D coordinates, but I will not try to explain it here, others can do that better than me especially in English ;)

icamera

The orthographic or isometric projection matrix results in a box like view frustum. It is defined by the near plane (1), the far plane (2), the width (3) and the height (4). If you experiment with the isometric projection matrix and did not really think about how it works before, you will probably be a little bit confused when you move the camera. While in the perspective projection matrix object appear bigger when they are closer, all objects in the isometric projection stay the same size regardless of distance - parallel lines are parallel, they do not appear get closer in the distance. So when you move the camera towards an object nothing seems to happen until it passes the near plane and disappears. To make something appear bigger in the isometric view you would have to make the width and height of the frustum smaller.

The viewing frustum is very useful for determining if an object is visible. If it is not within the frustum it is outside the view of the camera and does not need to be rendered. The constructor of Microsoft.Xna.Framework.BoundingFrustum with the WorldViewProjection matrix as a parameter can be used to create the frustum.

 

Camera

The camera class I have written is nothing special and there are a lot of similar classes out there. It has a constructor which allows you to create a perspective or orthogonal projection matrix. There are methods to position the camera and translate and rotate it. Also there are two methods to set the initial direction either via rotation or a look at point. The most interesting method probably in the whole class is the SetLookAt/SetDirection method, because it calculates the rotation instead of just creating the projection matrix.

I did this because I noticed that in most cases you would want to initialize the camera with a position and look at point and then start moving it, which proofs difficult if you have no valid rotation values (this method will not work if the camera's roll angle is not 0).

Next time I will try to cover scene organization and how to do viewing frustum culling efficiently.

May 16

XNA Serializer - Dictionary support

I got some comments on my last post - which kind of proves to me, that someone must have read it ;) - and the question was asked, if the serializer supports dictionaries. In its current form it does not, but it is really easy to extend so that it does. The link to the modified project is at the bottom of the post, but let me quickly describe the steps necessary to add support.

First a new method WriteDictionaryValue in the serializer is needed, to write dictionaries to the content. Then we need to extend the huge if-else-statement in the method WriteValue to check for the IDictionary interface and call our new method.

namespace JohnDoe.XNA.Serialization.ContentPipeline
{
    public class XNASerializer
    {
...
        /// <summary>
        /// Writes a value to the output. 
        /// </summary>
        /// <param name="propertyInfo"></param>
        private void WriteValue(ContentWriter output, Type type, object value)
        {
            // types supported directly by the ContentWriter
            if (type == typeof(System.Boolean))
                output.Write((System.Boolean)value);
            else if (type == typeof(System.Byte))
    ...
            else if (type == typeof(Microsoft.Xna.Framework.Vector4))
                output.Write((Microsoft.Xna.Framework.Vector4)value);
            // nullable values
            else if (TypeHelper.IsNullableType(type))
                this.WriteNullableValue(output, type, value);
            // arrays or lists
            else if (type.GetInterface("IList") != null)
                this.WriteListValue(output, type, value);
            // dictionaries
            else if (type.GetInterface("IDictionary") != null)
                this.WriteDictionaryValue(output, type, value);
            // nested complex types
else
                this.WriteObjectValue(output, type, value);
        }
 
        /// <summary>
        /// Writes a dictionary to the output, just like the WriteListValue.
        /// The keys in the dictionary must be of one type, the values are checked
        /// if they are mixed.
        /// </summary>
        /// <param name="output"></param>
        /// <param name="type"></param>
        /// <param name="value"></param>
        private void WriteDictionaryValue(ContentWriter output, Type type, object value)
        {
            IDictionary dictionary = value as IDictionary;
 
            // write if the dictionary is null
            output.Write(dictionary == null);
 
            if (dictionary != null)
            {
                // write the number of elements in the dictionary
                output.Write(dictionary.Count);
 
                if (dictionary.Count > 0)
                {
                    // check for the key and value type and
                    // if all value types are the same
                    Type keyType = null;
                    Type valueType = null;
                    bool uniform = true;
                    IDictionaryEnumerator e = dictionary.GetEnumerator();
                    while (e.MoveNext())
                    {
                        if (keyType == null)
                            keyType = e.Key.GetType();
 
                        if (valueType == null)
                            valueType = e.Value.GetType();
                        else if (valueType != e.Value.GetType())
                        {
                            uniform = false;
                            break;
                        }
                    }
 
                    // write if the values are all of one type
                    // if not, we have to write the type info for each
                    // value we serialize
                    output.Write(uniform);
 
                    // write the key type
                    output.Write(keyType.AssemblyQualifiedName);
                    if (uniform)
                        output.Write(valueType.AssemblyQualifiedName);
 
                    e = dictionary.GetEnumerator();
                    while (e.MoveNext())
                    {
                        // write the key 
                        this.WriteValue(output, keyType, e.Key);
 
                        // if mixed value elements write the type for each one
                        if (!uniform)
                            output.Write(e.Value.GetType().AssemblyQualifiedName);
 
                        // write the value
                        this.WriteValue(output, e.Value.GetType(), e.Value);
                    }
                }
            }
        }
 
    ...
    
    }
}

That is all that is needed to serialize a dictionary. Now our deserializer need the counterpart method ReadDictionaryValue. Again this method is very similar to the one reading a list. In the method ReadValue we check for the IDictionary interface and call our new method.

namespace JohnDoe.XNA.Serialization
{
    public class XNADeserializer
    {
    ...
        /// <summary>
        /// Read a value from the input.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="type"></param>
        /// <param name="currentValue"></param>
        /// <returns></returns>
        private object ReadValue(ContentReader input, Type type, object currentValue)
        {
            object value = null;
 
            // types supported directly by the ContentReader
            if (type == typeof(System.Boolean))
                value = input.ReadBoolean();
    ...
            else if (type == typeof(Microsoft.Xna.Framework.Vector4))
                value = input.ReadVector4();
            // nullable types
            else if (TypeHelper.IsNullableType(type))
                value = this.ReadNullableValue(input, type);
            // lists
            else if (TypeHelper.HasInterface(type, typeof(IList)))
                value = this.ReadListValue(input, type, currentValue);
            // dictionaries
else if (TypeHelper.HasInterface(type, typeof(IDictionary)))
                value = this.ReadDictionaryValue(input, type, currentValue);
            else
                value = this.ReadObjectValue(input, type, currentValue);
 
            return value;
        }
 
        /// <summary>
        /// Reads a dictionary.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="type"></param>
        /// <param name="currentValue"></param>
        /// <returns></returns>
        private object ReadDictionaryValue(ContentReader input, Type type, object currentValue)
        {
            IDictionary dictionary = null;
 
            // check if the object is supposed to be null
            bool isNull = input.ReadBoolean();
            if (!isNull)
            {
                // read the number of elements
                int count = input.ReadInt32();
 
                // an existing dictionary initialized by the parent object
                // might have been passed in, if not we create the dictionary
                dictionary = currentValue as IDictionary;
                if (dictionary == null)
                    dictionary = Activator.CreateInstance(type) as IDictionary;
 
                if (count > 0)
                {
                    // read if all value objects are of the same type
                    bool uniform = input.ReadBoolean();
 
                    // read key and value type (if uniform)
                    Type keyType = Type.GetType(input.ReadString());
                    Type valueType = null;
                    if (uniform)
                        valueType = Type.GetType(input.ReadString());
 
                    for (int i = 0; i < count; i++)
                    {
                        object key = this.ReadValue(input, keyType, null);
 
                        // read value type for each element if mixed
                        if (!uniform)
                            valueType = Type.GetType(input.ReadString());
 
                        object value = this.ReadValue(input, valueType, null);
 
                        // add the key value pair to the dictionary
                        dictionary.Add(key, value);
                    }
                }
            }
            return dictionary;
        }
    ...
    }
}

Finally I noticed one small bug as I tested this. I was too restrictive in filtering properties which are valid for XNA serialization in my TypeHelper.GetXNASerializationHandling method. First properties which can be serialized should be recognized, but the final deciding factor if and how they are serialized should always be the XNASerializableAttribute if it is attached to a property. The modified method investigation how to handle a specific property looks like this:

namespace JohnDoe.XNA.Serialization
{
    public static class TypeHelper
    {
    ...
        /// <summary>
        /// Gets the way the property should be handled by the XNA Serializer.
        /// </summary>
        /// <param name="propertyInfo"></param>
        /// <returns></returns>
        public static XNASerializationHandling GetXNASerializationHandling(System.Reflection.PropertyInfo propertyInfo)
        {
            XNASerializationHandling handling = XNASerializationHandling.Ignore;
 
            // by default we try to serialize properties which have these properties
            // the XNASerializationAttribute does not have to be defined
            if ((propertyInfo.CanRead && propertyInfo.CanWrite) ||
                (propertyInfo.CanRead && HasInterface(propertyInfo.PropertyType, typeof(System.Collections.IList))) ||
                (propertyInfo.CanRead && HasInterface(propertyInfo.PropertyType, typeof(System.Collections.IDictionary))))
                handling = XNASerializationHandling.Serialize;
 
            // the default serialization behavior can be overriden by 
            // an XmlSerializationAttribute.
            XNASerializationAttribute xmlSerialzationAttribute = GetXNASerializationAttribute(propertyInfo);
            if (xmlSerialzationAttribute != null)
                handling = xmlSerialzationAttribute.Handling;
 
            return handling;
        }
    ....
    }
}

Well, that's it, now the serializer supports dictionaries, too, and they are recognized by default as serializable properties.

And here is the modified project:

 

 

May 11

XNA Content Serialization

All of us who have programmed with the XNA framework and have created some custom content know that the serialization can be a little bit of a hassle. You have to keep three classes in sync: the actual content, the content writer and the content reader. If you are still in the middle of your project and make changes here and there, it can get annoying. So I was wondering why the XNA does not provide a more universal way of serializing content like the BinaryFormatter or the XmlSerializer. I assume the main reason is speed. The content is prepared for runtime as much as possible and speed is of importance for a game even during startup. As any universal serializer does have to work through some kind of reflection to inspect the objects this would be quite a hold up – reflection is slow. Nevertheless, during development time it would be quite convenient to have such a serializer – you can still replace it with specialized ContentReaders and ContentWriters before building the release version of the game.

So let’s just write such a serializer!
(link to the project source is at the bottom)

A serializer consists of two parts one for serialization of an object the other for deserialization. Usually you would just create one project with one class called XNASerializer for this, but as the serialization needs access to the ContentWriter, which is not available in the runtime environment, we need to split it into two projects.

So start by creating a brand new XNA game project and add two more game library projects to the solution. I added “JohnDoe.XNA.Serialization” and “John.Doe.XNA.Serialization.Pipeline”. Do you spend a lot of time of the day with the search for meaningful variable and function names, too? Well however… I left the namespaces in my code snippets so that it is clear to which project they belong.

The sections of this post are

  • The basic serializer
  • Support for Arrays/Collections
  • ExternalReferences/SharedResources and existing ContentReaders/ContentWriters
  • Custom Serialization

OK, lets start...

 

The Basic Serializer


So here is a huge chunk of code that covers the basic serialization and deserialization.
If you just want to download the project scroll down to the bottom of this blog post and maybe just read the comments in between the code snippets.

(Code 1: An enumeration that defines how to handle serialization of a property)

namespace JohnDoe.XNA.Serialization
{
    /// <summary>
    /// Enumeration of how to handle a property during serialization.
    /// </summary>
    public enum XNASerializationHandling
    {
        // seralize normally
        Serialize,
        // skip
        Ignore,
    }
}

(Code 2: An attribute than can be attached to a property to describe how the serializer should handle it)

namespace JohnDoe.XNA.Serialization
{
    /// <summary>
    /// An attribute that defines how the XNASerializes handles a property.
    /// The default is that every property that supports get/set is serialized.
    /// </summary>
    [AttributeUsage(AttributeTargets.Property)]
    public class XNASerializationAttribute : Attribute
    {
        private XNASerializationHandling handling;
 
        public XNASerializationAttribute(XNASerializationHandling handling)
        {
            this.handling = handling;
        }
 
        /// <summary>
        /// Gets the serialization handling.
        /// </summary>
        public XNASerializationHandling Handling
        {
            get { return this.handling; }
        }
    }
}

(Code 3: The basic XNADeserializer.)

namespace JohnDoe.XNA.Serialization
{
    public class XNADeserializer
    {
        /// <summary>
        /// Deserializes an object from the input reader.
        /// </summary>
        public object Deserialize(ContentReader input, object existingInstance)
        {
            // get the object type
            Type type = Type.GetType(input.ReadString());
 
            // create an instance if no existing instance was passed in
            object obj = existingInstance;
            if (obj == null)
                obj = Activator.CreateInstance(type);
 
            // read the number of properties
            int propertyCount = input.ReadInt32();
 
            // read each property
            for (int i = 0; i < propertyCount; i++)
            {
                string propertyName = input.ReadString();
                System.Reflection.PropertyInfo propertyInfo = type.GetProperty(propertyName,
                    System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
 
                // read the property value
                object currentPropertyValue = propertyInfo.GetValue(obj, null);
                object propertyValue = this.ReadValue(input, propertyInfo.PropertyType, currentPropertyValue);
 
                // set the value if it can be set (does not need to be if it is a collection)
                if (propertyInfo.CanWrite)
                    propertyInfo.SetValue(obj, propertyValue, null);
            }
            return obj;
        }
 
        /// <summary>
        /// Read a value from the input.
        /// </summary>
        private object ReadValue(ContentReader input, Type type, object currentValue)
        {
            object value = null;
 
            // types supported directly by the ContentReader
            if (type == typeof(System.Boolean))
                value = input.ReadBoolean();
            else if (type == typeof(System.Byte))
                value = input.ReadByte();    
 
... all types directly supported by the ContentReader             
 
            else if (type == typeof(System.String))
            {
                // handle null strings
                if (input.ReadBoolean())
                    value = input.ReadString();
            }
            // nullable types
            else if (TypeHelper.IsNullableType(type))
                value = this.ReadNullableValue(input, type);
            else
                value = this.ReadObjectValue(input, type, currentValue);
 
            return value;
        }
 
        /// <summary>
        /// Reads a nullable value
        /// </summary>
        private object ReadNullableValue(ContentReader input, Type type)
        {
            object value = null;
 
            // check if the value is supposed to be null.
            bool hasValue = input.ReadBoolean();
 
            // if not read the valye by calling ReadValue recursively with the 
            // extracted non-generic value type
            if (hasValue)
                value = this.ReadValue(input, TypeHelper.GetGenericTypeParameter(type), null);
 
            return value;
        }
 
        /// <summary>
        /// Reads a complex object from the input.
        /// </summary>
        private object ReadObjectValue(ContentReader input, Type type, object currentValue)
        {
            object value = null;
 
            // check if the object is supposed to be null
            bool isNull = input.ReadBoolean();
 
            // if not use the Deserializer recursively to deserialize it
            if (!isNull)
                value = this.Deserialize(input, currentValue);
            return value;
        }
    }
}

(Code 4: The basic XNASerializer. Note, this is part of the Serialization.ContentPipeline project!)

namespace JohnDoe.XNA.Serialization.ContentPipeline
{
    public class XNASerializer
    {
        public void Serialize(ContentWriter output, object obj)
        {
            Type valueType = obj.GetType();
 
            // write the object type
            output.Write(valueType.AssemblyQualifiedName);
 
            // get the properties to serialize
            List<PropertyInfo> properties = this.GetXNASerializableProperties(valueType);
 
            // write the number of properties
            output.Write(properties.Count);
 
            // and each property
            for (int i = 0; i < properties.Count; i++)
            {
                PropertyInfo propertyInfo = properties[i];                
                string propertyName = propertyInfo.Name;
                Type propertyType = propertyInfo.PropertyType;
                object propertyValue = propertyInfo.GetValue(obj, null);
 
                // write property name and value
                output.Write(propertyInfo.Name);
 
                XNASerializationHandling handling = TypeHelper.GetXNASerializationHandling(propertyInfo);
                if (handling == XNASerializationHandling.Serialize)
                    this.WriteValue(output, propertyType, propertyValue);
            }        } 
 
        /// <summary>
        /// Writes a value to the output. 
        /// </summary>
        private void WriteValue(ContentWriter output, Type type, object value)
        {
            // types supported directly by the ContentWriter
            if (type == typeof(System.Boolean))
                output.Write((System.Boolean)value);
            else if (type == typeof(System.Byte))
                output.Write((System.Byte)value);     
 
... all types directly supported by the ContentWriter 
 
            else if (type == typeof(System.String))
            {
                // handle null strings
                output.Write(value != null);
                if (value != null)
                    output.Write((System.String)value);
            }
            // nullable values
            else if (TypeHelper.IsNullableType(type))
                this.WriteNullableValue(output, type, value);
            // nested complex types
            else
                this.WriteObjectValue(output, type, value);
        }
 
        /// <summary>
        /// Writes a nullable value.
        /// </summary>
        private void WriteNullableValue(ContentWriter output, Type type, object value)
        {
            // write a boolean if the nullable has a value
            bool hasValue = value != null;
            output.Write(hasValue);
 
            // if it has a value call again WriteValue recursively with the extracted non 
            // nullable value type.
            if (hasValue)
                this.WriteValue(output, TypeHelper.GetGenericTypeParameter(type), value);
        }
 
        /// <summary>
        /// Writes a complex object value.
        /// </summary>
        private void WriteObjectValue(ContentWriter output, Type type, object value)
        {
            // write if the object is null
            output.Write(value == null);
            
            // recursively call the Serialize for complex objects
            if (value != null)
                this.Serialize(output, value);
        }
 
        /// <summary>
        /// Gets the list of all properties which need to be serialized.
        /// </summary>
        private List<PropertyInfo> GetXNASerializableProperties(Type type)
        {
            List<PropertyInfo> properties = new List<PropertyInfo>();
            foreach (PropertyInfo propertyInfo in type.GetProperties(
                                                BindingFlags.Public | BindingFlags.Instance))
                if (TypeHelper.GetXNASerializationHandling(propertyInfo) != XNASerializationHandling.Ignore)
                        properties.Add(propertyInfo);
            return properties;
        }
    }
}

Well, I hope that was not too much. I tried to write readable code and I even put some comments in it ;)

What we have here covers the plain basics of the serialization. The serializer writes the assembly qualified name of the object to serialize, so that the deserializer can instantiate it, if necessary. Then it writes the number of properties it will serialize and then for each property the name and the value. Nullable values and strings have an additional boolean flag to indicate if they are null. Complex objects are handled by recursively calling Serialize again. Not too difficult, is it?
You might have noticed the TypeHelper class which eases access to the type properties a little bit. The .NET framework on the XBox lacks a couple of important functions which we need like Type.GetGenericTypeParameters or Type.GetInterface, the TypeHelper replaces them with well, not so optimal but hopefully working methods.

 

How to use it?

If we would want to use this serializer for some custom content it would look like this:

(Code 5: The content reader)

namespace SerializationSampleContent
{
    public class MyCustomDataReader : ContentTypeReader<MyCustomData>
    {
        protected override MyCustomData Read(ContentReader input, MyCustomData existingInstance)
        {
            XNADeserializer xnaDeserializer = new XNADeserializer();
            return xnaDeserializer.Deserialize(input, existingInstance) as MyCustomData;
        }
    }
}

(Code 6: The content writer)

namespace SerializationSampleContentPipeline
{
    [ContentTypeWriter]
    public class MyCustomDataWriter : ContentTypeWriter<SerializationSampleContent.MyCustomData>
    {
        protected override void Write(ContentWriter output, SerializationSampleContent.MyCustomData value)
        {
            XNASerializer xnaSerializer = new XNASerializer();
            xnaSerializer.Serialize(output, value);
        }
 
        public override string GetRuntimeReader(Microsoft.Xna.Framework.TargetPlatform targetPlatform)
        {
            return typeof(SerializationSampleContent.MyCustomDataReader).AssemblyQualifiedName;
        }
    }
}

Support for Arrays/Collections

Did I say ALL properties? That is of course not true, so let's improve the serializer. So far it can handle almost all properties that have a getter and setter and are of a type directly supported by the ContentReader/ContentWriter, also it can handle complex object properties. The one really big thing that is not supported yet is the support for arrays and collections.

First we need to figure out if a property type is a list of some sort. The best way in my opinion is to check for the IList interface as I do really not know any collection that does not implement it. The TypeHelper has a method that can check for an interface on a type, so the huge list of if statements in the XNASerializer.WriteValue and XNADeserializer.ReadValue are expanded to check for the IList interface and if so call these methods:

(Code 7: XNASerializer.WriteListValue)

/// <summary>
/// Writes a list of values to the output.
/// The list can consist of mixed objects, although it will be 
/// much bigger then, because for each list element the type name
/// has to be serialized, too.
/// </summary>
private void WriteListValue(ContentWriter output, Type type, object value)
{
    // handle all lists and arrays
    IList list = value as IList;
 
    // write if the list is null
    output.Write(list == null);
 
    if (list != null)
    {
        // get a list element type and check if all elements in the list
        // are of this type, this might be very slow for large list, as the 
        // list is traversed twice.
        Type listElementType = null;
        bool uniform = true;
        for (int i = 0; i < list.Count; i++)
            if (listElementType == null)
            {
                // get the element type
                listElementType = list[i].GetType();
            }
            else if (listElementType != list[i].GetType())
            {
                uniform = false;
                listElementType = null;
                break;
            }
 
        // write list header information
        output.Write(list.Count);
        if (list.Count > 0)
        {
            // write if the list has only one element type
            output.Write(uniform);
 
            for (int i = 0; i < list.Count; i++)
            {
                // write the element type for the first element or for all 
                // if the list is not uniform
                if ((i == 0) || !uniform)
                {
                    listElementType = list[i].GetType();
                    output.Write(listElementType.AssemblyQualifiedName);
                }
                this.WriteValue(output, listElementType, list[i]);
            }
        }
    }
}

(Code 8: XNADeserializer.ReadListValue)

/// <summary>
/// Read a collection or array.
/// </summary>
private object ReadListValue(ContentReader input, Type type, object currentValue)
{
    IList values = null;
 
    // a list is an object type, check if it is supposed to be null
    bool isNull = input.ReadBoolean();
    if (!isNull)
    {
        // read the number of list elements
        int count = input.ReadInt32();
 
        // an existing collection might have been passed in as the current property value
        // we can fill that collection if it is not fixed in size, e.g. an array 
        values = currentValue as IList;
        if ((values == null) || (values.IsFixedSize))
        {
            // create a list that matches the property type
            if (type.IsArray)
            {
                ConstructorInfo constructorInfo = type.GetConstructor(new Type[] { typeof(int) });
                values = constructorInfo.Invoke(new object[] { count }) as IList;
            }
            else
            {
                values = Activator.CreateInstance(type) as IList;
            }
        }
 
        if (count > 0)
        {
            bool uniform = input.ReadBoolean();
            Type listElementType = null;
 
            for (int i = 0; i < count; i++)
            {
                // if the list is uniform, read the element type only once, otherwise always
                if ((i == 0) || !uniform)
                    listElementType = Type.GetType(input.ReadString());
 
                object elementValue = this.ReadValue(input, listElementType, null);
                if (values.IsFixedSize)
                    values[i] = elementValue;
                else
                    values.Add(elementValue);
            }
        }
    }
    return values;
}

Now the serializer and deserializer can handle arrays, collections and even with mixed elements. The one interesting thing here is the optional creation of the list object in ReadListValue. Some collections properties do only have a getter and are already created by the class, so they just need to be filled by the deserialization process. This is reason why the SetPropertyValue can be skipped sometimes (see Code 3).

Now our serializer supports value properties, collection properties and complex properties - not too bad, we could stop here.

 

ExternalReferences, SharedResources and existing ContentReaders/Writers


Our serializer does not support any of those yet. It would be nice, if a property has a type that has a already ContentReader/ContentWriter, it would be used, instead of our probably lousy attempt to serialize that object. So lets add support for this to the serializer. The implementation is a little bit more interesting because of the reflection involved to call the methods on the reader/writer, so continue reading ;)

To handle these serializations first we need more information how to serialize a property, so expand the XNASerializationHandling enumeration (Code 1) with the following enumeration values: ContentReaderWriterExternalReference, ContentReaderWriterObject, ContentReaderWriterRawObject and ContentReaderWriterSharedResource.
Now the XNASerializer.Serialize and XNADeserializer.Deserialize methods get a switch-case statement to handle all different scenarios calling the methods described in the follwing sections.

 

Existing ContentReader/Writer

First handling of the scenario where a type has a ContentReader/Writer. The property has the XNASerializationAttribute attached with the flag ContentReaderWriterObject set. This tells us, that we could just call output.WriteObject<TheObjectType>(TheObject) and input.ReadObject<TheObjectType>(ExistingObjectInstance). Easier said then done, as these are generic methods, luckily the .NET framework on the XBox does support Type.MakeGenericType, so that we are not stuck. The only problem is, that there is no good way of getting the MethodInfo for these methods as they are generic and overloaded, so GetMethodInfo will always fail. I wrote a method in the TypeHelper that will call the methods for us, but is not optimal as it just tries to match the method by name and number of parameters - it will work for the methods we need, though.

(Code 9: TypeHelper.InvokeGenericMethod)

/// <summary>
/// Invokes a method on the object.
/// This method is very very slow and could be improved with some caching. 
/// The issue is, that Type.GetMethod does not work if the method is generic and
/// is overloaded.
/// </summary>
public static object InvokeGenericMethod(object obj, string methodName, Type typeArgument, object[] parameters)
{
    System.Reflection.MethodInfo[] methodInfos = obj.GetType().GetMethods(
                          System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
    for (int i = 0; i < methodInfos.Length; i++)
        if ((methodInfos[i].Name == methodName) && 
            (methodInfos[i].GetParameters().Length == parameters.Length))
        {
            System.Reflection.MethodInfo methodInfo = methodInfos[i];
            methodInfo = methodInfo.MakeGenericMethod(typeArgument);
            return methodInfo.Invoke(obj, parameters);
        }
    throw new Exception("The method could not be found.");
}

This method makes calling the generic methods of the ContentReader/Writer much easier, that the WriteContentObject and ReadContentObject methods as well as the WriteContentRawObject and ReadContentRawObject of our classes simply call InvokeGenericMethod. 

 

SharedResource

Calling ContentWriter.WriteSharedResource works exactly as it does for WriteObject, but the reading is a little bit more challenging. The ReadSharedResource expects a generic delegate Action<T> as a parameter to 'fixup' the property value, i.e. usually assign the shared object to the property. This is an interesting problem because we cannot create the delegate via reflection, so we need a little helper class, that can return the delegate and do the work for us.

(Code 10: PropertyFixer)

namespace JohnDoe.XNA.Serialization
{
    /// <summary>
    /// Shared objects need an Action delegate to set the property 
    /// on the object. This helper class returns a delegate that 
    /// can be passed into the ContentReader to set the property.
    /// </summary>
    class PropertyFixer
    {
        private object obj;
        private System.Reflection.PropertyInfo propertyInfo;
        public PropertyFixer(object obj, System.Reflection.PropertyInfo propertyInfo)
        {
            this.obj = obj;
            this.propertyInfo = propertyInfo;
        }
        public Action<T> GetFixupDelegate<T>()
        {
            return new Action<T>(Fixer<T>);
        }
        public void Fixer<T>(T value)
        {
            this.propertyInfo.SetValue(this.obj, value, null);
        }
    }
}

In the XmlDeserializer.ReadSharedResource we call this method similar to the other ones. We first request the fixup delegate from the helper method preparing it to set the property value and then invoke ContentReader.ReadSharedResource.

(Code 11: XmlDeserializer.ReadSharedResource)

/// <summary>
/// Reads an object via ContentReader.ReadSharedResource.
/// </summary>
private void ReadContentSharedResource(ContentReader input, object obj, 
                                       PropertyInfo propertyInfo)
{
    PropertyFixer propertyFixer = new PropertyFixer(obj, propertyInfo);
    object fixupDelegate = TypeHelper.InvokeGenericMethod(propertyFixer, "GetFixupDelegate",
                             propertyInfo.PropertyType, new object[] { });
    TypeHelper.InvokeGenericMethod(input, "ReadSharedResource", 
                             propertyInfo.PropertyType, new object[] { fixupDelegate });
}

 

ExternalReference

I was a little bit doubtful, if I should include external references. I have not used them yet, so maybe I just don't see their value, but for the sake of completeness, lets support them. First there needs to be some file name where we load the reference from. There are two options, it could be defined as on another string property or it can be set in the XNASerializationAttribute. I extended the XNASerializationAttribute with two additional properties: ExternalReferenceFileNameProperty and ExternalReferenceFileName. If the first one is set, the serializer tries to get the file name from that property on the object if the second one is set, that is the file name. Again we face a little problem as we cannot create the generic type to instantiate the typed ExternalReference, so we again a helper class similar to the PropertyFixer.

(Code 12: ExternalReferenceCreator)

namespace JohnDoe.XNA.Serialization.ContentPipeline
{
    /// <summary>
    /// A helper class that creates an external reference. 
    /// The XBox .NET framework does not contain a method to get the generic definition
    /// type from a type, therefore this helper.
    /// </summary>
    class ExternalReferenceCreator
    {
        public ExternalReference<T> CreateExternalReference<T>(object obj, string filename)
        {
            return new ExternalReference<T>(filename);
        }
    }
}

The XNASerializer.WriteExternalReference looks like the following using the ExternalReferenceCreator:

(Code 13: XNASerializer.WriteContentExternalReference)

/// <summary>
/// Writes a property value using the ContentWriter.ExternalReference
/// </summary>
/// <param name="output"></param>
/// <param name="type"></param>
/// <param name="value"></param>
private void WriteContentExternalReference(ContentWriter output, object obj, PropertyInfo propertyInfo)
{
    XNASerializationAttribute xnaSerializationAttribute = 
                          TypeHelper.GetXNASerializationAttribute(propertyInfo);
    string filename = null;
 
    // get the filename either from a property or the attribute itself
    if (!String.IsNullOrEmpty(xnaSerializationAttribute.ExternalReferenceFileNameProperty))
    {
        PropertyInfo referencePropertyInfo = obj.GetType().GetProperty(
                                xnaSerializationAttribute.ExternalReferenceFileNameProperty);
        filename = (string)referencePropertyInfo.GetValue(obj, null);
    }
    else
    {
        filename = xnaSerializationAttribute.ExternalReferenceFileName;
    }
 
    // write the external reference
    ExternalReferenceCreator externalReferenceCreator = new ExternalReferenceCreator();
    object externalReference = TypeHelper.InvokeGenericMethod(
                    externalReferenceCreator, "CreateExternalReference", 
                    propertyInfo.PropertyType, new object[] { obj, filename });
    TypeHelper.InvokeGenericMethod(output, "WriteExternalReference", 
                    propertyInfo.PropertyType, new object[] { externalReference });
}

 

Finally - Custom Serialization

Almost there, but there is one final thing that is not covered yet - what if the serialization does not work on your class, e.g. because the properties do not have public setters?

In XML serialization we would just implement IXmlSerializable on the class and are done, so we just do the same and create a IXNASerializable interface. The only issue here is, that the content class itself which will implement the interface has to have some kind of way to use the ContentWriter without directly referencing the library. So we have to create an interface for the ContentWriter named IContentWriter as well as a wrapper for it which I named ContentWriterWrapper. Only three methods of the ContentWriter cannot be supported in the interface. These are WriteExternalReference and the two overloads of WriteObject and WriteRawObject which reference the ContentTypeWriter. These methods would mean a reference to libraries which are not supported in the runtime environment.

This allows us now to define an interface in the Serialization project that can be implemented by any content class.

(Code 14: IXNASerializable interface)

namespace JohnDoe.XNA.Serialization
{
    /// <summary>
    /// This interface allows to implement custom serialization
    /// in a class itself. If the XNASerializationHandling is set to
    /// Serialize and the interface is implemented by the class, it is
    /// used instead of default serialization.
    /// </summary>
    public interface IXNASerializable
    {
        void Deserialize(ContentReader input);
        void Serialize(IContentWriter output);
    }
}

The XNASerializer.Serialize and XNADeserializer.Deserialize method now check for this interface and if it does exist, it is used instead of the default serialization.

 

That's it!

What we have now is in a pretty flexible serializer. I suppose there are still lots of scenarios where it will not work, but it should at least cover most scenarios.  Also be awar, that I could not test this code on an actual XBox due to the lack of ... ah... an XBox ;). And remember, this should not be the way to serialize/deserialize content in a program you want to release, for speed you should write ContentReaders/ContentWriters for each class. If you find any bugs or have suggestions to improve the serializer feel free to leave some comments.

The full project including a little sample is in the download section of this blog.

The source is provided as is, no warranties, no guarantees, feel free to use it however you like.

Well, this was my first blog post ever (and I mean ever)!
PS: English is not my native language, so please excuse any mistakes in this post (and all which may follow).