using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; namespace XCEngine { internal static class ScriptableObjectSerializedGraph { private const string FormatTag = "XCEngine.ScriptableObjectGraph"; private const int FormatVersion = 1; private static readonly object MissingValue = new object(); private enum ValueKind : byte { Null = 0, Boolean = 1, Byte = 2, SByte = 3, Int16 = 4, UInt16 = 5, Int32 = 6, UInt32 = 7, Int64 = 8, UInt64 = 9, Single = 10, Double = 11, String = 12, Enum = 13, ObjectReference = 14, Array = 15, Struct = 16 } public static string Serialize( ScriptableObject root) { if (root == null) { return string.Empty; } SerializationContext context = new SerializationContext(root); using (MemoryStream stream = new MemoryStream()) using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write(FormatTag); writer.Write(FormatVersion); writer.Write(context.rootId); writer.Write(context.nodes.Count); for (int i = 0; i < context.nodes.Count; ++i) { context.WriteNode(writer, context.nodes[i]); } writer.Flush(); return Convert.ToBase64String(stream.ToArray()); } } public static ScriptableObject Deserialize( string serializedGraph) { if (string.IsNullOrEmpty(serializedGraph)) { return null; } try { byte[] bytes = Convert.FromBase64String(serializedGraph); using (MemoryStream stream = new MemoryStream(bytes)) using (BinaryReader reader = new BinaryReader(stream)) { string tag = reader.ReadString(); int version = reader.ReadInt32(); if (tag != FormatTag || version != FormatVersion) { return null; } int rootId = reader.ReadInt32(); int nodeCount = reader.ReadInt32(); if (rootId < 0 || rootId >= nodeCount || nodeCount < 0) { return null; } List nodes = new List(nodeCount); for (int i = 0; i < nodeCount; ++i) { nodes.Add(ReadNode(reader)); } for (int i = 0; i < nodes.Count; ++i) { Type type = ResolveType( nodes[i].assemblyName, nodes[i].typeName); nodes[i].instance = CreateObjectInstance(type); } if (nodes[rootId].instance == null) { return null; } for (int i = 0; i < nodes.Count; ++i) { ApplyFields( nodes[i].instance, nodes[i].fields, nodes); } return nodes[rootId].instance as ScriptableObject; } } catch { return null; } } private sealed class SerializationContext { public readonly List nodes = new List(); public readonly int rootId; private readonly Dictionary m_nodeIds = new Dictionary( ReferenceEqualityComparer.Instance); public SerializationContext( object root) { rootId = CollectObject(root); } public void WriteNode( BinaryWriter writer, object node) { WriteType(writer, node.GetType()); WriteFieldSet(writer, node, node.GetType()); } private int CollectObject( object value) { if (value == null) { return -1; } int existingId; if (m_nodeIds.TryGetValue(value, out existingId)) { return existingId; } int id = nodes.Count; m_nodeIds[value] = id; nodes.Add(value); Type type = value.GetType(); FieldInfo[] fields = GetSerializableFields(type); for (int i = 0; i < fields.Length; ++i) { FieldInfo field = fields[i]; object fieldValue = field.GetValue(value); EnsureSerializableFieldValue( field, fieldValue); CollectValue(fieldValue); } return id; } private void CollectValue( object value) { if (value == null) { return; } Type type = value.GetType(); if (IsScalarType(type) || type.IsEnum) { return; } if (type.IsArray) { Array array = value as Array; if (array == null) { return; } for (int i = 0; i < array.Length; ++i) { object elementValue = array.GetValue(i); EnsureSerializableArrayElementValue( type.GetElementType(), elementValue); CollectValue(elementValue); } return; } if (type.IsValueType) { FieldInfo[] fields = GetSerializableFields(type); for (int i = 0; i < fields.Length; ++i) { FieldInfo field = fields[i]; object fieldValue = field.GetValue(value); EnsureSerializableFieldValue( field, fieldValue); CollectValue(fieldValue); } return; } if (CanSerializeReferenceType(type)) { CollectObject(value); } } private void WriteFieldSet( BinaryWriter writer, object instance, Type type) { FieldInfo[] fields = GetSerializableFields(type); writer.Write(fields.Length); for (int i = 0; i < fields.Length; ++i) { FieldInfo field = fields[i]; object fieldValue = field.GetValue(instance); EnsureSerializableFieldValue( field, fieldValue); WriteType(writer, field.DeclaringType); writer.Write(field.Name); WriteValue( writer, fieldValue, field.FieldType); } } private void WriteValue( BinaryWriter writer, object value, Type declaredType) { if (value == null) { writer.Write((byte)ValueKind.Null); return; } Type type = value.GetType(); if (type == typeof(bool)) { writer.Write((byte)ValueKind.Boolean); writer.Write((bool)value); return; } if (type == typeof(byte)) { writer.Write((byte)ValueKind.Byte); writer.Write((byte)value); return; } if (type == typeof(sbyte)) { writer.Write((byte)ValueKind.SByte); writer.Write((sbyte)value); return; } if (type == typeof(short)) { writer.Write((byte)ValueKind.Int16); writer.Write((short)value); return; } if (type == typeof(ushort)) { writer.Write((byte)ValueKind.UInt16); writer.Write((ushort)value); return; } if (type == typeof(int)) { writer.Write((byte)ValueKind.Int32); writer.Write((int)value); return; } if (type == typeof(uint)) { writer.Write((byte)ValueKind.UInt32); writer.Write((uint)value); return; } if (type == typeof(long)) { writer.Write((byte)ValueKind.Int64); writer.Write((long)value); return; } if (type == typeof(ulong)) { writer.Write((byte)ValueKind.UInt64); writer.Write((ulong)value); return; } if (type == typeof(float)) { writer.Write((byte)ValueKind.Single); writer.Write((float)value); return; } if (type == typeof(double)) { writer.Write((byte)ValueKind.Double); writer.Write((double)value); return; } if (type == typeof(string)) { writer.Write((byte)ValueKind.String); writer.Write((string)value); return; } if (type.IsEnum) { writer.Write((byte)ValueKind.Enum); WriteType(writer, type); writer.Write(Convert.ToInt64( value, CultureInfo.InvariantCulture)); return; } if (type.IsArray) { Array array = value as Array; writer.Write((byte)ValueKind.Array); WriteType(writer, type.GetElementType()); writer.Write(array != null ? array.Length : 0); if (array == null) { return; } Type elementType = type.GetElementType(); for (int i = 0; i < array.Length; ++i) { object elementValue = array.GetValue(i); EnsureSerializableArrayElementValue( elementType, elementValue); WriteValue( writer, elementValue, elementType); } return; } if (type.IsValueType) { writer.Write((byte)ValueKind.Struct); WriteType(writer, type); WriteFieldSet(writer, value, type); return; } writer.Write((byte)ValueKind.ObjectReference); writer.Write(m_nodeIds[value]); } } private sealed class NodeRecord { public string assemblyName; public string typeName; public List fields; public object instance; } private sealed class FieldRecord { public string declaringAssemblyName; public string declaringTypeName; public string name; public ValueRecord value; } private sealed class ValueRecord { public ValueKind kind; public object scalarValue; public string assemblyName; public string typeName; public int objectId; public List elements; public List fields; } private sealed class ReferenceEqualityComparer : IEqualityComparer { public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); public new bool Equals( object x, object y) { return ReferenceEquals(x, y); } public int GetHashCode( object obj) { return RuntimeHelpers.GetHashCode(obj); } } private static NodeRecord ReadNode( BinaryReader reader) { NodeRecord node = new NodeRecord(); ReadType( reader, out node.assemblyName, out node.typeName); node.fields = ReadFieldSet(reader); return node; } private static List ReadFieldSet( BinaryReader reader) { int fieldCount = reader.ReadInt32(); List fields = new List(fieldCount); for (int i = 0; i < fieldCount; ++i) { FieldRecord field = new FieldRecord(); ReadType( reader, out field.declaringAssemblyName, out field.declaringTypeName); field.name = reader.ReadString(); field.value = ReadValue(reader); fields.Add(field); } return fields; } private static ValueRecord ReadValue( BinaryReader reader) { ValueRecord value = new ValueRecord(); value.kind = (ValueKind)reader.ReadByte(); switch (value.kind) { case ValueKind.Null: break; case ValueKind.Boolean: value.scalarValue = reader.ReadBoolean(); break; case ValueKind.Byte: value.scalarValue = reader.ReadByte(); break; case ValueKind.SByte: value.scalarValue = reader.ReadSByte(); break; case ValueKind.Int16: value.scalarValue = reader.ReadInt16(); break; case ValueKind.UInt16: value.scalarValue = reader.ReadUInt16(); break; case ValueKind.Int32: value.scalarValue = reader.ReadInt32(); break; case ValueKind.UInt32: value.scalarValue = reader.ReadUInt32(); break; case ValueKind.Int64: value.scalarValue = reader.ReadInt64(); break; case ValueKind.UInt64: value.scalarValue = reader.ReadUInt64(); break; case ValueKind.Single: value.scalarValue = reader.ReadSingle(); break; case ValueKind.Double: value.scalarValue = reader.ReadDouble(); break; case ValueKind.String: value.scalarValue = reader.ReadString(); break; case ValueKind.Enum: ReadType( reader, out value.assemblyName, out value.typeName); value.scalarValue = reader.ReadInt64(); break; case ValueKind.ObjectReference: value.objectId = reader.ReadInt32(); break; case ValueKind.Array: ReadType( reader, out value.assemblyName, out value.typeName); int elementCount = reader.ReadInt32(); value.elements = new List(elementCount); for (int i = 0; i < elementCount; ++i) { value.elements.Add(ReadValue(reader)); } break; case ValueKind.Struct: ReadType( reader, out value.assemblyName, out value.typeName); value.fields = ReadFieldSet(reader); break; } return value; } private static void ApplyFields( object target, List fields, List nodes) { if (target == null || fields == null) { return; } for (int i = 0; i < fields.Count; ++i) { FieldRecord fieldRecord = fields[i]; FieldInfo field = ResolveField(fieldRecord); if (field == null || !ShouldSerializeField(field)) { continue; } object value = ConvertValue( fieldRecord.value, field.FieldType, nodes); if (ReferenceEquals(value, MissingValue)) { continue; } try { field.SetValue(target, value); } catch { } } } private static object ConvertValue( ValueRecord value, Type targetType, List nodes) { if (value == null) { return MissingValue; } switch (value.kind) { case ValueKind.Null: return targetType != null && targetType.IsValueType ? Activator.CreateInstance(targetType) : null; case ValueKind.Boolean: case ValueKind.Byte: case ValueKind.SByte: case ValueKind.Int16: case ValueKind.UInt16: case ValueKind.Int32: case ValueKind.UInt32: case ValueKind.Int64: case ValueKind.UInt64: case ValueKind.Single: case ValueKind.Double: case ValueKind.String: return ConvertScalar( value.scalarValue, targetType); case ValueKind.Enum: Type enumType = ResolveType( value.assemblyName, value.typeName) ?? targetType; return enumType != null && enumType.IsEnum ? Enum.ToObject( enumType, value.scalarValue) : MissingValue; case ValueKind.ObjectReference: return value.objectId >= 0 && value.objectId < nodes.Count ? nodes[value.objectId].instance : MissingValue; case ValueKind.Array: return ConvertArray(value, targetType, nodes); case ValueKind.Struct: return ConvertStruct(value, targetType, nodes); default: return MissingValue; } } private static object ConvertScalar( object value, Type targetType) { if (targetType == null || value == null) { return value; } if (targetType.IsEnum) { return Enum.ToObject(targetType, value); } Type valueType = value.GetType(); if (targetType.IsAssignableFrom(valueType)) { return value; } try { return Convert.ChangeType( value, targetType, CultureInfo.InvariantCulture); } catch { return MissingValue; } } private static object ConvertArray( ValueRecord value, Type targetType, List nodes) { Type elementType = ResolveType( value.assemblyName, value.typeName); if (elementType == null && targetType != null && targetType.IsArray) { elementType = targetType.GetElementType(); } if (elementType == null || value.elements == null) { return MissingValue; } Array array = Array.CreateInstance( elementType, value.elements.Count); for (int i = 0; i < value.elements.Count; ++i) { object elementValue = ConvertValue( value.elements[i], elementType, nodes); if (!ReferenceEquals(elementValue, MissingValue)) { array.SetValue(elementValue, i); } } return array; } private static object ConvertStruct( ValueRecord value, Type targetType, List nodes) { Type type = ResolveType( value.assemblyName, value.typeName) ?? targetType; if (type == null || !type.IsValueType) { return MissingValue; } object instance = Activator.CreateInstance(type); ApplyFields(instance, value.fields, nodes); return instance; } private static object CreateObjectInstance( Type type) { if (type == null || type.IsAbstract) { return null; } try { return Activator.CreateInstance( type, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, binder: null, args: new object[0], culture: CultureInfo.InvariantCulture); } catch { return null; } } private static FieldInfo ResolveField( FieldRecord fieldRecord) { Type declaringType = ResolveType( fieldRecord.declaringAssemblyName, fieldRecord.declaringTypeName); return declaringType != null ? declaringType.GetField( fieldRecord.name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly) : null; } private static void WriteType( BinaryWriter writer, Type type) { writer.Write( type != null ? type.Assembly.GetName().Name : string.Empty); writer.Write( type != null ? type.FullName ?? string.Empty : string.Empty); } private static void ReadType( BinaryReader reader, out string assemblyName, out string typeName) { assemblyName = reader.ReadString(); typeName = reader.ReadString(); } private static Type ResolveType( string assemblyName, string typeName) { if (string.IsNullOrEmpty(typeName)) { return null; } Type type = null; if (!string.IsNullOrEmpty(assemblyName)) { type = Type.GetType( typeName + ", " + assemblyName, false); if (type != null) { return type; } } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); for (int i = 0; i < assemblies.Length; ++i) { Assembly assembly = assemblies[i]; if (!string.IsNullOrEmpty(assemblyName) && assembly.GetName().Name != assemblyName) { continue; } type = assembly.GetType(typeName, false); if (type != null) { return type; } } return null; } private static FieldInfo[] GetSerializableFields( Type type) { List fields = new List(); for (Type currentType = type; currentType != null && currentType != typeof(object); currentType = currentType.BaseType) { FieldInfo[] declaredFields = currentType.GetFields( BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); for (int i = 0; i < declaredFields.Length; ++i) { FieldInfo field = declaredFields[i]; if (ShouldSerializeField(field)) { fields.Add(field); } } } fields.Sort(CompareFields); return fields.ToArray(); } private static int CompareFields( FieldInfo lhs, FieldInfo rhs) { string lhsType = lhs.DeclaringType != null ? lhs.DeclaringType.FullName : string.Empty; string rhsType = rhs.DeclaringType != null ? rhs.DeclaringType.FullName : string.Empty; int result = string.CompareOrdinal(lhsType, rhsType); if (result != 0) { return result; } result = lhs.MetadataToken.CompareTo(rhs.MetadataToken); return result != 0 ? result : string.CompareOrdinal(lhs.Name, rhs.Name); } private static bool ShouldSerializeField( FieldInfo field) { if (field == null || field.IsStatic || field.IsLiteral || field.IsInitOnly || field.IsNotSerialized) { return false; } return field.IsPublic || Attribute.IsDefined( field, typeof(SerializeField), true); } private static bool CanSerializeValue( Type declaredType, object value) { Type type = value != null ? value.GetType() : declaredType; if (type == null || type.IsPointer || type.IsGenericTypeDefinition) { return false; } if (IsScalarType(type) || type.IsEnum) { return true; } if (type.IsArray) { Type elementType = type.GetElementType(); return elementType != null && CanSerializeValue(elementType, null); } if (type.IsValueType) { return true; } return CanSerializeReferenceType(type); } private static void EnsureSerializableFieldValue( FieldInfo field, object value) { if (field != null && !CanSerializeValue( field.FieldType, value)) { string ownerType = field.DeclaringType != null ? field.DeclaringType.FullName : string.Empty; throw new NotSupportedException( "Unsupported serialized field type: " + ownerType + "." + field.Name + " (" + field.FieldType.FullName + ")"); } } private static void EnsureSerializableArrayElementValue( Type elementType, object value) { if (!CanSerializeValue(elementType, value)) { throw new NotSupportedException( "Unsupported serialized array element type: " + (value != null ? value.GetType().FullName : elementType != null ? elementType.FullName : string.Empty)); } } private static bool CanSerializeReferenceType( Type type) { return type != null && type.IsClass && type != typeof(string) && type != typeof(Type) && !typeof(Delegate).IsAssignableFrom(type) && (type.Namespace == null || !type.Namespace.StartsWith( "System", StringComparison.Ordinal)); } private static bool IsScalarType( Type type) { return type == typeof(bool) || type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || type == typeof(ushort) || type == typeof(int) || type == typeof(uint) || type == typeof(long) || type == typeof(ulong) || type == typeof(float) || type == typeof(double) || type == typeof(string); } } }