#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