Files
XCEngine/managed/XCEngine.ScriptCore/ScriptableObjectSerializedGraph.cs

1052 lines
32 KiB
C#

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<NodeRecord> nodes =
new List<NodeRecord>(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<object> nodes = new List<object>();
public readonly int rootId;
private readonly Dictionary<object, int> m_nodeIds =
new Dictionary<object, int>(
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<FieldRecord> 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<ValueRecord> elements;
public List<FieldRecord> fields;
}
private sealed class ReferenceEqualityComparer
: IEqualityComparer<object>
{
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<FieldRecord> ReadFieldSet(
BinaryReader reader)
{
int fieldCount = reader.ReadInt32();
List<FieldRecord> fields =
new List<FieldRecord>(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<ValueRecord>(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<FieldRecord> fields,
List<NodeRecord> 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<NodeRecord> 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<NodeRecord> 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<NodeRecord> 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<FieldInfo> fields = new List<FieldInfo>();
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);
}
}
}