#region CPL License /* Nuclex Framework Copyright (C) 2002-2011 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #endregion using System; using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework; using Nuclex.Support; namespace Nuclex.Game.Serialization { /// Contains utility methods for serializating objects into binary data public static class BinarySerializer { /// Initializes the static members of the class static BinarySerializer() { simpleNameToTypeDictionary = new Dictionary(); typeToSimpleNameDictionary = new Dictionary(); registerSimpleNamesForSerializableTypes(); registerSimpleNamesForAssetTypes(); } /// Registers a simple name for the specified type /// Type a simple name will be registered for /// Simple name of the specified type /// /// Using simple names can reduce the size of the serialized data (useful if /// data is being sent over network connections) and is a means to avoid /// versioning trouble if your assembly versions change, affecting the assembly /// qualified names of any types you serialize. /// public static void RegisterSimpleName(string simpleName) { lock(typeToSimpleNameDictionary) { Type type = typeof(T); if(checkExistingRegistration(type, simpleName)) { return; } typeToSimpleNameDictionary.Add(type, simpleName); simpleNameToTypeDictionary.Add(simpleName, type); } } #region Any /// Loads an object from its serialization representation /// Type of value that will be loaded /// Reader to use for reading the object /// Receives the deserialized object public static void Load(BinaryReader reader, out T value) where T : IBinarySerializable { Type type; Load(reader, out type); // Get type value = (T)Activator.CreateInstance(type); value.Load(reader); // Deserialize contents } /// Serializes an object into a binary data stream /// Type of value that will be saved /// BinaryWriter to serialize the object into /// Object that will be serialized public static void Save(BinaryWriter writer, T value) where T : IBinarySerializable { Type valueType = value.GetType(); if(!valueType.HasDefaultConstructor()) { throw new InvalidOperationException( "Attempted to serialize type '" + valueType.Name + "' without default constructor" ); } Save(writer, valueType); // Save type value.Save(writer); // Serialize contents } #endregion // Any #region System.Type /// Loads a type specification from its serialization representation /// Reader to use for reading the type specification /// Receives the deserialized type specification public static void Load(BinaryReader reader, out Type type) { string typeName = reader.ReadString(); if(simpleNameToTypeDictionary.TryGetValue(typeName, out type)) { return; } type = Type.GetType(typeName); } /// Serializes a type specification into a binary data stream /// BinaryWriter to serialize the type specification into /// Type specification that will be serialized public static void Save(BinaryWriter writer, Type type) { string typeName; if(!typeToSimpleNameDictionary.TryGetValue(type, out typeName)) { typeName = type.AssemblyQualifiedName; } writer.Write(typeName); } #endregion // System.Type #region System.Collections.Generic.ICollection /// Loads a collection from its serialized representation /// Reader to use for reading the collection /// Collection that will receive the deserialized items /// /// This method loads right into the collection and is not transactional. /// If an error occurs during loading, the collection is left in /// an intermediate state and no assumptions should be made as to its /// contents. If you need transactional safety, create a temporary collection, /// load into the temporary collection and then replace your actual /// collection with it. /// public static void LoadCollection(BinaryReader reader, ICollection collection) where T : IBinarySerializable { collection.Clear(); int count = reader.ReadInt32(); for(int index = 0; index < count; ++index) { T item; Load(reader, out item); collection.Add(item); } } /// Serializes a collection of binary serializable objects /// BinaryWriter to serialize the collection into /// Collection that will be serialized public static void SaveCollection(BinaryWriter writer, ICollection collection) where T : IBinarySerializable { writer.Write((int)collection.Count); foreach(T item in collection) { Save(writer, item); } } #endregion // System.Collections.Generic.ICollection #region Microsoft.Xna.Framework.Matrix /// Loads a matrix from its serialized representation /// Reader to use for reading the matrix /// Received the deserialized matrix public static void Load(BinaryReader reader, out Matrix matrix) { matrix = new Matrix( reader.ReadSingle(), // m11 reader.ReadSingle(), // m12 reader.ReadSingle(), // m13 reader.ReadSingle(), // m14 reader.ReadSingle(), // m21 reader.ReadSingle(), // m22 reader.ReadSingle(), // m23 reader.ReadSingle(), // m24 reader.ReadSingle(), // m31 reader.ReadSingle(), // m32 reader.ReadSingle(), // m33 reader.ReadSingle(), // m34 reader.ReadSingle(), // m41 reader.ReadSingle(), // m42 reader.ReadSingle(), // m43 reader.ReadSingle() // m44 ); } /// Serializes a matrix into a binary data stream /// BinaryWriter to serialize the matrix into /// Matrix that will be serialized public static void Save(BinaryWriter writer, ref Matrix matrix) { writer.Write(matrix.M11); writer.Write(matrix.M12); writer.Write(matrix.M13); writer.Write(matrix.M14); writer.Write(matrix.M21); writer.Write(matrix.M22); writer.Write(matrix.M23); writer.Write(matrix.M24); writer.Write(matrix.M31); writer.Write(matrix.M32); writer.Write(matrix.M33); writer.Write(matrix.M34); writer.Write(matrix.M41); writer.Write(matrix.M42); writer.Write(matrix.M43); writer.Write(matrix.M44); } #endregion // Microsoft.Xna.Framework.Matrix #region Microsoft.Xna.Framework.Vector2 /// Loads a vector from its serialized representation /// Reader to use for reading the vector /// Receives the deserialized vector public static void Load(BinaryReader reader, out Vector2 vector) { vector = new Vector2( reader.ReadSingle(), reader.ReadSingle() ); } /// Serializes a vector into a binary data stream /// BinaryWriter to serialize the vector into /// Vector that will be serialized public static void Save(BinaryWriter writer, ref Vector2 vector) { writer.Write(vector.X); writer.Write(vector.Y); } #endregion // Microsoft.Xna.Framework.Vector2 #region Microsoft.Xna.Framework.Vector3 /// Loads a vector from its serialized representation /// Reader to use for reading the vector /// Received the deserialized vector public static void Load(BinaryReader reader, out Vector3 vector) { vector = new Vector3( reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle() ); } /// Serializes a vector into a binary data stream /// BinaryWriter to serialize the vector into /// Vector that will be serialized public static void Save(BinaryWriter writer, ref Vector3 vector) { writer.Write(vector.X); writer.Write(vector.Y); writer.Write(vector.Z); } #endregion // Microsoft.Xna.Framework.Vector3 #region Microsoft.Xna.Framework.Vector4 /// Loads a vector from its serialized representation /// Reader to use for reading the vector /// Receives the deserialized vector public static void Load(BinaryReader reader, out Vector4 vector) { // This is valid in C# (but order of evaluation would be undefined in C++) vector = new Vector4( reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle() ); } /// Serializes a vector into a binary data stream /// BinaryWriter to serialize the vector into /// Vector that will be serialized public static void Save(BinaryWriter writer, ref Vector4 vector) { writer.Write(vector.X); writer.Write(vector.Y); writer.Write(vector.Z); writer.Write(vector.W); } #endregion // Microsoft.Xna.Framework.Vector4 #region Microsoft.Xna.Framework.Quaternion /// Loads a quaternion from its serialized representation /// Reader to use for reading the quaternion /// Receives the deserialized quaternion public static void Load(BinaryReader reader, out Quaternion quaternion) { // This is valid in C# (but order of evaluation would be undefined in C++) quaternion = new Quaternion( reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle() ); } /// Serializes a quaternion into a binary data stream /// BinaryWriter to serialize the quaternion into /// Quaternion that will be serialized public static void Save(BinaryWriter writer, ref Quaternion quaternion) { writer.Write(quaternion.X); writer.Write(quaternion.Y); writer.Write(quaternion.Z); writer.Write(quaternion.W); } #endregion // Microsoft.Xna.Framework.Quaternion #region Microsoft.Xna.Framework.Curve /// Loads a curve from its serialized representation /// Reader to use for reading the curve /// Curve to be deserialized /// /// This method loads right into the curve and is not transactional. /// If an error occurs during loading, the curve is left in /// an intermediate state and no assumptions should be made as to its /// contents. If you need transactional safety, create a temporary curve, /// load into the temporary curve and then replace your actual /// curve with it. /// public static void Load(BinaryReader reader, Curve curve) { curve.Keys.Clear(); // Load the curve's loop settings curve.PreLoop = (CurveLoopType)reader.ReadByte(); curve.PostLoop = (CurveLoopType)reader.ReadByte(); // Load the key frames defined for the curve int keyCount = reader.ReadInt32(); for(int keyIndex = 0; keyIndex < keyCount; ++keyIndex) { float position = reader.ReadSingle(); float value = reader.ReadSingle(); float tangentIn = reader.ReadSingle(); float tangentOut = reader.ReadSingle(); CurveContinuity continuity = (CurveContinuity)reader.ReadByte(); curve.Keys.Add(new CurveKey(position, value, tangentIn, tangentOut, continuity)); } // for } /// Serializes a curve into a binary data stream /// BinaryWriter to serialize the curve into /// Curve to be serialized public static void Save(BinaryWriter writer, Curve curve) { // Save the curve's loop settings writer.Write((byte)curve.PreLoop); writer.Write((byte)curve.PostLoop); // Save the key frames contained in the curve writer.Write(curve.Keys.Count); for(int keyIndex = 0; keyIndex < curve.Keys.Count; ++keyIndex) { CurveKey key = curve.Keys[keyIndex]; writer.Write(key.Position); writer.Write(key.Value); writer.Write(key.TangentIn); writer.Write(key.TangentOut); writer.Write((byte)key.Continuity); } // for } #endregion // Microsoft.Xna.Framework.Curve /// Checks whether the registration is valid /// Type that will be checked /// Simple name the type would be registered under /// True if the type is already registered, false otherwise /// /// If the type has already been registered under another name of the specified /// simple name has already been used for a different type. /// private static bool checkExistingRegistration(Type type, string simpleName) { string existingSimpleName; if(typeToSimpleNameDictionary.TryGetValue(type, out existingSimpleName)) { if(simpleName == existingSimpleName) { return true; // This registration already exists } throw new InvalidOperationException( string.Format( "Type '{0}' has already been given the simple name '{1}'", type.Name, existingSimpleName ) ); } Type existingType; if(simpleNameToTypeDictionary.TryGetValue(simpleName, out existingType)) { throw new InvalidOperationException( string.Format( "Simple name '{0}' has already been used for type '{1}'", simpleName, existingType.Name ) ); } return false; } /// Registers simple names for types the user might persist private static void registerSimpleNamesForSerializableTypes() { RegisterSimpleName("Xna.Vector2"); RegisterSimpleName("Xna.Vector3"); RegisterSimpleName("Xna.Vector4"); RegisterSimpleName("Xna.Matrix"); RegisterSimpleName("Xna.Quaternion"); RegisterSimpleName("Xna.Curve"); } /// /// Registers simple names for types the user might load from a content manager /// private static void registerSimpleNamesForAssetTypes() { RegisterSimpleName("Xna.SpriteFont"); RegisterSimpleName("Xna.Texture2D"); RegisterSimpleName("Xna.Texture3D"); RegisterSimpleName("Xna.Model"); RegisterSimpleName("Xna.SoundEffect"); RegisterSimpleName("Xna.Song"); #if !NO_VIDEO RegisterSimpleName("Xna.Video"); #endif } /// Dictionary that converts simple names into types private static IDictionary simpleNameToTypeDictionary; /// Dictionary that converts types into simple names private static IDictionary typeToSimpleNameDictionary; } // class BinarySerializer } // namespace Nuclex.Game.Serialization