initial commit
This commit is contained in:
parent
6715289efe
commit
788c3389af
37645 changed files with 2526849 additions and 80 deletions
|
@ -0,0 +1,660 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Utilities.USerialize
|
||||
{
|
||||
/*
|
||||
* Main USerialize deserialzation class. Used to read instances of types from a stream
|
||||
*
|
||||
* To DeSerialize an object from a stream use code such as
|
||||
*
|
||||
* USerialize.DeSerializer deserializer = new USerialize.DeSerializer();
|
||||
* CachedInfo info = deserializer.DeSerialize<CachedInfo>(new MemoryStream(File.ReadAllBytes(filepath), false));
|
||||
*
|
||||
*/
|
||||
internal class DeSerializer
|
||||
{
|
||||
// Flag that can be set to true prior to deserialization to cause a detailed log of the data read to be emitted to the console. Will slow down deserialization considerably
|
||||
internal bool EmitVerboseLog = false;
|
||||
|
||||
// Cached data about a field in a type we've encountered
|
||||
internal class FieldData
|
||||
{
|
||||
internal FieldInfo m_FieldInfo;
|
||||
internal object m_Setter;
|
||||
internal object m_Getter;
|
||||
}
|
||||
|
||||
// Cached data about a type we've encountered
|
||||
internal class TypeData
|
||||
{
|
||||
// Data for each of the fields in this type, keyed by field name
|
||||
internal Dictionary<string, FieldData> m_Fields;
|
||||
|
||||
// LUT that maps field indices in the serialized data to fields in the object in memory. Reset every time a new object is deserialized in case they were created at different times and the data has changed
|
||||
internal FieldData[] m_FieldIndex;
|
||||
|
||||
// Type of this type
|
||||
internal Type m_Type;
|
||||
|
||||
// Custom delegate we've been given by the client code to create an instance of this type. It is much faster to have the client code create the instances directly than to use the generic Activator system
|
||||
internal ObjectFactory m_ObjectFactory;
|
||||
}
|
||||
|
||||
// Delegate type the client can supply to create instances of a specific type. It is much faster to have the client code create the instances directly than to use the generic Activator system
|
||||
internal delegate object ObjectFactory();
|
||||
|
||||
// Custom deserializers the client code has supplied to deserialize specific types. Usually used for types that the standard reflection based serialization cannot cope with. Keyed by Type
|
||||
Dictionary<Type, ICustomSerializer> m_CustomSerializers = new Dictionary<Type, ICustomSerializer>();
|
||||
|
||||
// Delegates supplied by the client to create instances of specific types, keyed by type name. It is much faster to have the client code create the instances directly than to use the generic Activator system.
|
||||
Dictionary<Type, ObjectFactory> m_ObjectFactories = new Dictionary<Type, ObjectFactory>();
|
||||
|
||||
// Cache of data about types we have encountered, provides better performance than querying the slow reflection API repeatedly
|
||||
Dictionary<string, TypeData> m_TypeDataCache = new Dictionary<string, TypeData>();
|
||||
|
||||
// Version of the object that was deserialized. This is the value supplied by the client code when Serializer.Serialize() was originally called
|
||||
int m_ObjectVersion;
|
||||
internal int ObjectVersion { get { return m_ObjectVersion; } }
|
||||
|
||||
// Reader we are using to read bytes from the stream
|
||||
BinaryReader m_Reader;
|
||||
|
||||
// Version of the serialization format itself read from the stream. Exists to provide data upgrade potential in the future
|
||||
byte m_SerializationVersion;
|
||||
|
||||
// The type/field name stringtable read from the stream
|
||||
string[] m_TypeStringTable;
|
||||
long m_TypeStringTableBytePos;
|
||||
|
||||
// The data value stringtable read from the stream
|
||||
string[] m_DataStringTable;
|
||||
long m_DataStringTableBytePos;
|
||||
|
||||
// LUT to get TypeData from a type name index for this object. Maps type name indices in the serialized data to TypeData entries in m_TypeDataCache (which persists between objects)
|
||||
TypeData[] m_TypeDataByTypeNameIndex;
|
||||
|
||||
// LUT to get a Type instance from a type name index for this object.
|
||||
Type[] m_TypeByTypeNameIndex;
|
||||
|
||||
internal DeSerializer()
|
||||
{
|
||||
}
|
||||
|
||||
internal DeSerializer(ICustomSerializer[] customSerializers, (Type, ObjectFactory)[] objectFactories)
|
||||
{
|
||||
if (customSerializers != null)
|
||||
Array.ForEach(customSerializers, (customSerializer) => AddCustomSerializer(customSerializer));
|
||||
|
||||
if (objectFactories != null)
|
||||
Array.ForEach(objectFactories, (item) => AddObjectFactory(item.Item1, item.Item2));
|
||||
}
|
||||
|
||||
string[] ReadStringTable(long streamPosition)
|
||||
{
|
||||
m_Reader.BaseStream.Position = streamPosition;
|
||||
|
||||
int numStrings = m_Reader.ReadInt32();
|
||||
string[] stringTable = new string[numStrings];
|
||||
for (int stringNum = 0; stringNum < numStrings; stringNum++)
|
||||
{
|
||||
stringTable[stringNum] = m_Reader.ReadString();
|
||||
}
|
||||
return stringTable;
|
||||
}
|
||||
|
||||
void ReadStringTables()
|
||||
{
|
||||
m_TypeStringTableBytePos = m_Reader.ReadInt64();
|
||||
m_DataStringTableBytePos = m_Reader.ReadInt64();
|
||||
|
||||
long dataStartPos = m_Reader.BaseStream.Position;
|
||||
|
||||
m_TypeStringTable = ReadStringTable(m_TypeStringTableBytePos);
|
||||
m_DataStringTable = ReadStringTable(m_DataStringTableBytePos);
|
||||
|
||||
m_Reader.BaseStream.Position = dataStartPos;
|
||||
}
|
||||
|
||||
// Clear data that we cache about types and object contents that can change between objects.
|
||||
void ClearPerObjectCachedData()
|
||||
{
|
||||
// Clear the field index -> field data LUT as this can change between objects
|
||||
foreach (KeyValuePair<string, TypeData> typeDataCacheEntry in m_TypeDataCache)
|
||||
{
|
||||
typeDataCacheEntry.Value.m_FieldIndex = null;
|
||||
}
|
||||
|
||||
// Clear the type name index -> type data LUT as this can change between objects
|
||||
m_TypeDataByTypeNameIndex = new TypeData[m_TypeStringTable.Length];
|
||||
m_TypeByTypeNameIndex = new Type[m_TypeStringTable.Length];
|
||||
}
|
||||
|
||||
// Main deserialize function. Creates and reads an instance of the specified type from the stream
|
||||
internal ClassType DeSerialize<ClassType>(Stream stream) where ClassType : new()
|
||||
{
|
||||
m_Reader = new BinaryReader(stream);
|
||||
|
||||
m_SerializationVersion = m_Reader.ReadByte();
|
||||
if (m_SerializationVersion != Serializer.SerializationVersion)
|
||||
throw new InvalidDataException($"Data stream is using an incompatible serialization format. Stream is version {m_SerializationVersion} but code requires version {Serializer.SerializationVersion}. The stream should be re-created with the current code");
|
||||
|
||||
m_ObjectVersion = m_Reader.ReadInt32();
|
||||
|
||||
ReadStringTables();
|
||||
|
||||
ClearPerObjectCachedData();
|
||||
|
||||
ClassType instance = (ClassType)ReadObject(0);
|
||||
|
||||
if (m_Reader.BaseStream.Position != m_TypeStringTableBytePos)
|
||||
throw new InvalidDataException($"Did not read entire stream in DeSerialize. Read to +{m_Reader.BaseStream.Position} but expected +{m_TypeStringTableBytePos}");
|
||||
|
||||
// NOTE: The reader is deliberately not disposed here as doing so would also close the stream but we rely on the outer code to manage the lifetime of the stream
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
// Call to start readingdirectly from a stream, used primarily for testing USerialize functions in isolation
|
||||
internal void StartReadingFromStream(Stream stream)
|
||||
{
|
||||
m_Reader = new BinaryReader(stream);
|
||||
}
|
||||
|
||||
// Return the object version from a serialized stream without doing anything else, this is the object version passed in the original Serializer.Serialize() call that created the stream.
|
||||
// Resets the stream read position back to where it was on entry before returning
|
||||
internal int GetObjectVersion(Stream stream)
|
||||
{
|
||||
long startPos = m_Reader.BaseStream.Position;
|
||||
|
||||
m_Reader.ReadByte(); // Serialization version
|
||||
int objectVersion = m_Reader.ReadInt32();
|
||||
|
||||
m_Reader.BaseStream.Position = startPos;
|
||||
|
||||
return objectVersion;
|
||||
}
|
||||
|
||||
// Add a custom deserializer function to handle a specific type
|
||||
internal void AddCustomSerializer(ICustomSerializer customSerializer)
|
||||
{
|
||||
m_CustomSerializers.Add(customSerializer.GetType(), customSerializer);
|
||||
}
|
||||
|
||||
// Add an object factory function that can create instances of a named type
|
||||
internal void AddObjectFactory(Type type, ObjectFactory objectFactory)
|
||||
{
|
||||
m_ObjectFactories.Add(type, objectFactory);
|
||||
}
|
||||
|
||||
// Get the type data for the specified type. If it exists in the cache it is returned directly, otherwise the type data is gathered and stored in the cache before being returned
|
||||
TypeData GetTypeData(string assemblyQualifiedTypeName)
|
||||
{
|
||||
if (!m_TypeDataCache.TryGetValue(assemblyQualifiedTypeName, out TypeData typeData))
|
||||
{
|
||||
typeData = new TypeData();
|
||||
typeData.m_Fields = new Dictionary<string, FieldData>();
|
||||
typeData.m_Type = Type.GetType(assemblyQualifiedTypeName);
|
||||
if (typeData.m_Type == null)
|
||||
throw new InvalidDataException($"Could not create type for '{assemblyQualifiedTypeName}'");
|
||||
|
||||
m_ObjectFactories.TryGetValue(typeData.m_Type, out typeData.m_ObjectFactory);
|
||||
|
||||
FieldInfo[] fieldArray = typeData.m_Type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
for (int fieldNum = 0; fieldNum < fieldArray.Length; fieldNum++)
|
||||
{
|
||||
FieldData fieldData = new FieldData();
|
||||
fieldData.m_FieldInfo = fieldArray[fieldNum];
|
||||
|
||||
if (fieldData.m_FieldInfo.FieldType == typeof(int))
|
||||
fieldData.m_Setter = CreateSetter<int>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(uint))
|
||||
fieldData.m_Setter = CreateSetter<uint>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(long))
|
||||
fieldData.m_Setter = CreateSetter<long>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(ulong))
|
||||
fieldData.m_Setter = CreateSetter<ulong>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(byte))
|
||||
fieldData.m_Setter = CreateSetter<byte>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(bool))
|
||||
fieldData.m_Setter = CreateSetter<bool>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(string))
|
||||
fieldData.m_Setter = CreateSetter<string>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(GUID))
|
||||
fieldData.m_Setter = CreateSetter<GUID>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(Hash128))
|
||||
fieldData.m_Setter = CreateSetter<Hash128>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(Type))
|
||||
fieldData.m_Setter = CreateSetter<Type>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(byte[]))
|
||||
{
|
||||
fieldData.m_Setter = CreateSetter<byte[]>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
fieldData.m_Getter = CreateObjectGetter(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
}
|
||||
// Per customer request
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(ulong[]))
|
||||
{
|
||||
fieldData.m_Setter = CreateSetter<ulong[]>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
fieldData.m_Getter = CreateObjectGetter(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
}
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(string[]))
|
||||
{
|
||||
fieldData.m_Setter = CreateSetter<string[]>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
fieldData.m_Getter = CreateObjectGetter(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
}
|
||||
else if (fieldData.m_FieldInfo.FieldType == typeof(Type[]))
|
||||
{
|
||||
fieldData.m_Setter = CreateSetter<Type[]>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
fieldData.m_Getter = CreateObjectGetter(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
}
|
||||
else if (fieldData.m_FieldInfo.FieldType.IsEnum)
|
||||
fieldData.m_Setter = CreateObjectSetter<int>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (fieldData.m_FieldInfo.FieldType.IsValueType && (!fieldData.m_FieldInfo.FieldType.IsPrimitive))
|
||||
fieldData.m_Setter = CreateObjectSetter<object>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
else if (typeof(Array).IsAssignableFrom(fieldData.m_FieldInfo.FieldType))
|
||||
{
|
||||
fieldData.m_Setter = CreateObjectSetter<object>(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
fieldData.m_Getter = CreateObjectGetter(typeData.m_Type, fieldData.m_FieldInfo);
|
||||
}
|
||||
|
||||
typeData.m_Fields.Add(fieldData.m_FieldInfo.Name, fieldData);
|
||||
}
|
||||
m_TypeDataCache.Add(assemblyQualifiedTypeName, typeData);
|
||||
}
|
||||
return typeData;
|
||||
}
|
||||
|
||||
// Create a function object to set the value of a field of type 'SetterType'. Calling this compiled function object is much faster than using the reflection API
|
||||
static Func<object, SetterType, SetterType> CreateSetter<SetterType>(Type type, FieldInfo field)
|
||||
{
|
||||
ParameterExpression valueExp = Expression.Parameter(field.FieldType, "value");
|
||||
ParameterExpression targetExp = Expression.Parameter(typeof(object), "target");
|
||||
MemberExpression fieldExp = (type.IsValueType && (!type.IsPrimitive)) ? Expression.Field(Expression.Unbox(targetExp, type), field) : Expression.Field(Expression.Convert(targetExp, type), field);
|
||||
return Expression.Lambda<Func<object, SetterType, SetterType>>(Expression.Assign(fieldExp, valueExp), targetExp, valueExp).Compile();
|
||||
}
|
||||
|
||||
// Create a function object to set the value of a field of generic object type that is stored in the stream with type 'StorageType'. Calling this compiled function object is much faster than using the reflection API
|
||||
static Func<object, StorageType, StorageType> CreateObjectSetter<StorageType>(Type type, FieldInfo field)
|
||||
{
|
||||
ParameterExpression valueExp = Expression.Parameter(typeof(StorageType), "value");
|
||||
ParameterExpression targetExp = Expression.Parameter(typeof(object), "target");
|
||||
MemberExpression fieldExp = (type.IsValueType && (!type.IsPrimitive)) ? Expression.Field(Expression.Unbox(targetExp, type), field) : Expression.Field(Expression.Convert(targetExp, type), field);
|
||||
BinaryExpression assignExp = Expression.Assign(fieldExp, Expression.Convert(valueExp, field.FieldType));
|
||||
return Expression.Lambda<Func<object, StorageType, StorageType>>(Expression.Convert(assignExp, typeof(StorageType)), targetExp, valueExp).Compile();
|
||||
}
|
||||
|
||||
// Create a function object to get the value from a field as a generic object. It is much faster to call this compiled function object than to use the reflection API
|
||||
static Func<object, object> CreateObjectGetter(Type type, FieldInfo field)
|
||||
{
|
||||
ParameterExpression valueExp = Expression.Parameter(typeof(object), "value");
|
||||
return Expression.Lambda<Func<object, object>>(Expression.Convert(Expression.Field(Expression.Convert(valueExp, type), field), typeof(object)), valueExp).Compile();
|
||||
}
|
||||
|
||||
// Return the TypeData for a type from the index of it's type name in the type/field stringtable
|
||||
TypeData GetTypeDataFromTypeNameIndex(int typeNameIndex)
|
||||
{
|
||||
// Populate the m_TypeDataByTypeNameIndex LUT with type data based on this index if not already set so we can get the type info from the index faster in the future
|
||||
if (m_TypeDataByTypeNameIndex[typeNameIndex] == null)
|
||||
{
|
||||
m_TypeDataByTypeNameIndex[typeNameIndex] = GetTypeData(m_TypeStringTable[typeNameIndex]);
|
||||
m_TypeByTypeNameIndex[typeNameIndex] = m_TypeDataByTypeNameIndex[typeNameIndex].m_Type;
|
||||
}
|
||||
return m_TypeDataByTypeNameIndex[typeNameIndex];
|
||||
}
|
||||
|
||||
// Read an object from the stream
|
||||
object ReadObject(int depth)
|
||||
{
|
||||
if (!ReadNullFlag())
|
||||
return null;
|
||||
|
||||
// Get the TypeData for the type of this object
|
||||
TypeData typeData = GetTypeDataFromTypeNameIndex(ReadStringIndex());
|
||||
|
||||
// Give custom object factories a chance to create the instance first as this is faster than the generic Activator call.
|
||||
// If no instance is created (either because no factory is registered for this type or the factory didn't produce an instance) then we call Activator as a fallback
|
||||
object objectRead = typeData.m_ObjectFactory?.Invoke();
|
||||
if (objectRead == null)
|
||||
objectRead = Activator.CreateInstance(typeData.m_Type);
|
||||
|
||||
if (EmitVerboseLog)
|
||||
Debug.Log($"{new String(' ', (depth * 2) - ((depth > 0) ? 1 : 0))}ReadObject({typeData.m_Type.Name}) +{m_Reader.BaseStream.Position}");
|
||||
|
||||
Dictionary<string, FieldData> fields = typeData.m_Fields;
|
||||
|
||||
int numFields = m_Reader.ReadUInt16();
|
||||
|
||||
// Initialise the index of field number -> field data for this type if it's the first time we've seen it
|
||||
if (typeData.m_FieldIndex == null)
|
||||
typeData.m_FieldIndex = new FieldData[numFields];
|
||||
|
||||
for (int fieldNum = 0; fieldNum < numFields; fieldNum++)
|
||||
{
|
||||
string fieldName = m_TypeStringTable[ReadStringIndex()];
|
||||
|
||||
FieldData field;
|
||||
if (typeData.m_FieldIndex[fieldNum] != null)
|
||||
field = typeData.m_FieldIndex[fieldNum];
|
||||
else
|
||||
{
|
||||
if (fields.TryGetValue(fieldName, out field))
|
||||
typeData.m_FieldIndex[fieldNum] = field;
|
||||
}
|
||||
|
||||
if (field != null)
|
||||
{
|
||||
DataType fieldDataType = (DataType)m_Reader.ReadByte();
|
||||
|
||||
if (EmitVerboseLog)
|
||||
Debug.Log($"{new String(' ', depth * 2)}Field {fieldName} -> {field?.m_FieldInfo.Name} ({fieldDataType}) +{m_Reader.BaseStream.Position}");
|
||||
|
||||
FieldInfo fieldInfo = field.m_FieldInfo;
|
||||
|
||||
switch (fieldDataType)
|
||||
{
|
||||
case DataType.Byte:
|
||||
((Func<object, byte, byte>)field.m_Setter)(objectRead, m_Reader.ReadByte());
|
||||
break;
|
||||
|
||||
case DataType.Bool:
|
||||
((Func<object, bool, bool>)field.m_Setter)(objectRead, m_Reader.ReadBoolean());
|
||||
break;
|
||||
|
||||
case DataType.Int:
|
||||
((Func<object, int, int>)field.m_Setter)(objectRead, m_Reader.ReadInt32());
|
||||
break;
|
||||
|
||||
case DataType.UInt:
|
||||
((Func<object, uint, uint>)field.m_Setter)(objectRead, m_Reader.ReadUInt32());
|
||||
break;
|
||||
|
||||
case DataType.Long:
|
||||
((Func<object, long, long>)field.m_Setter)(objectRead, m_Reader.ReadInt64());
|
||||
break;
|
||||
|
||||
case DataType.ULong:
|
||||
((Func<object, ulong, ulong>)field.m_Setter)(objectRead, m_Reader.ReadUInt64());
|
||||
break;
|
||||
|
||||
case DataType.Enum:
|
||||
((Func<object, int, int>)field.m_Setter)(objectRead, m_Reader.ReadInt32());
|
||||
break;
|
||||
|
||||
case DataType.String:
|
||||
((Func<object, string, string>)field.m_Setter)(objectRead, ReadString());
|
||||
break;
|
||||
|
||||
case DataType.Type:
|
||||
((Func<object, Type, Type>)field.m_Setter)(objectRead, GetTypeFromCache(ReadStringIndex()));
|
||||
break;
|
||||
|
||||
case DataType.Class:
|
||||
fieldInfo.SetValue(objectRead, ReadObject(depth + 1));
|
||||
break;
|
||||
|
||||
case DataType.Struct:
|
||||
{
|
||||
object structObject = ReadObject(depth + 1);
|
||||
((Func<object, object, object>)field.m_Setter)(objectRead, structObject);
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Array:
|
||||
if (ReadNullFlag())
|
||||
{
|
||||
Array fieldArray;
|
||||
if (field.m_Getter != null)
|
||||
fieldArray = (Array)((Func<object, object>)field.m_Getter)(objectRead);
|
||||
else
|
||||
fieldArray = (Array)fieldInfo.GetValue(objectRead);
|
||||
|
||||
int rank = m_Reader.ReadInt32();
|
||||
if (rank != 1)
|
||||
throw new InvalidDataException($"USerialize currently doesn't support arrays with ranks other than one - data for field {fieldInfo.Name} of type {fieldInfo.FieldType.Name} has rank {rank}");
|
||||
int length = m_Reader.ReadInt32();
|
||||
|
||||
DataType elementDataType = (DataType)m_Reader.ReadByte();
|
||||
|
||||
if (EmitVerboseLog)
|
||||
Debug.Log($"{new String(' ', (depth + 1) * 2)}Array {elementDataType} [{length}] +{m_Reader.BaseStream.Position}");
|
||||
|
||||
switch (elementDataType)
|
||||
{
|
||||
case DataType.Byte:
|
||||
{
|
||||
byte[] byteArray = (byte[])Array.CreateInstance(typeof(byte), length);
|
||||
((Func<object, byte[], byte[]>)field.m_Setter)(objectRead, byteArray);
|
||||
m_Reader.Read(byteArray, 0, length);
|
||||
break;
|
||||
}
|
||||
|
||||
// Per customer request
|
||||
case DataType.ULong:
|
||||
{
|
||||
ulong[] ulongArray = (ulong[])Array.CreateInstance(typeof(ulong), length);
|
||||
((Func<object, ulong[], ulong[]>)field.m_Setter)(objectRead, ulongArray);
|
||||
for (int elementNum = 0; elementNum < length; elementNum++)
|
||||
{
|
||||
ulongArray[elementNum] = m_Reader.ReadUInt64();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.String:
|
||||
{
|
||||
string[] stringArray = (string[])Array.CreateInstance(typeof(string), length);
|
||||
((Func<object, string[], string[]>)field.m_Setter)(objectRead, stringArray);
|
||||
for (int elementNum = 0; elementNum < length; elementNum++)
|
||||
{
|
||||
stringArray[elementNum] = ReadString();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Type:
|
||||
{
|
||||
Type[] typeArray = new Type[length];
|
||||
((Func<object, Type[], Type[]>)field.m_Setter)(objectRead, typeArray);
|
||||
for (int elementNum = 0; elementNum < length; elementNum++)
|
||||
{
|
||||
int elementTypeNameIndex = ReadStringIndex();
|
||||
if (elementTypeNameIndex != USerialize.InvalidStringIndex)
|
||||
{
|
||||
typeArray[elementNum] = GetTypeFromCache(elementTypeNameIndex);
|
||||
if (typeArray[elementNum] == null)
|
||||
throw new InvalidDataException($"Could not create Type for '{m_TypeStringTable[elementTypeNameIndex]}'");
|
||||
}
|
||||
|
||||
if (EmitVerboseLog)
|
||||
Debug.Log($"{new String(' ', (depth + 2) * 2)}Type[{elementNum}] = '{m_TypeStringTable[elementTypeNameIndex]}' +{m_Reader.BaseStream.Position}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Class:
|
||||
{
|
||||
Type arrayElementType = GetTypeFromCache(ReadStringIndex());
|
||||
Array classArray = Array.CreateInstance(arrayElementType, length);
|
||||
fieldInfo.SetValue(objectRead, classArray);
|
||||
for (int elementNum = 0; elementNum < length; elementNum++)
|
||||
{
|
||||
DataType objectDataType = (DataType)m_Reader.ReadByte();
|
||||
object elementObject = null;
|
||||
switch (objectDataType)
|
||||
{
|
||||
case DataType.Class:
|
||||
elementObject = ReadObject(depth + 2);
|
||||
break;
|
||||
|
||||
case DataType.String:
|
||||
elementObject = ReadString();
|
||||
break;
|
||||
|
||||
case DataType.Int:
|
||||
elementObject = m_Reader.ReadInt32();
|
||||
break;
|
||||
|
||||
case DataType.Custom:
|
||||
elementObject = ReadCustomObject();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"Found unsupported data type '{objectDataType}' in class array '{typeData.m_Type.Name}.{fieldName}'");
|
||||
}
|
||||
classArray.SetValue(elementObject, elementNum);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Struct:
|
||||
{
|
||||
Type elementType = GetTypeFromCache(ReadStringIndex());
|
||||
Array array = Array.CreateInstance(elementType, length);
|
||||
fieldInfo.SetValue(objectRead, array);
|
||||
for (int elementNum = 0; elementNum < length; elementNum++)
|
||||
{
|
||||
array.SetValue(ReadObject(depth + 2), elementNum);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"Unknown array element type {elementDataType} for field {fieldInfo.FieldType.Name}.{fieldInfo.Name}");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case DataType.List:
|
||||
if (ReadNullFlag())
|
||||
{
|
||||
int count = m_Reader.ReadInt32();
|
||||
Type listType = GetTypeFromCache(ReadStringIndex());
|
||||
|
||||
if (EmitVerboseLog)
|
||||
Debug.Log($"{new String(' ', (depth + 1) * 2)}List {listType.Name} [{count}] +{m_Reader.BaseStream.Position}");
|
||||
|
||||
System.Collections.IList list = (System.Collections.IList)Activator.CreateInstance(typeof(List<>).MakeGenericType(listType), count);
|
||||
for (int itemIndex = 0; itemIndex < count; itemIndex++)
|
||||
{
|
||||
object item = ReadObject(depth + 2);
|
||||
if (item != null)
|
||||
list.Add(item);
|
||||
}
|
||||
fieldInfo.SetValue(objectRead, list);
|
||||
}
|
||||
break;
|
||||
|
||||
case DataType.Custom:
|
||||
fieldInfo.SetValue(objectRead, ReadCustomObject());
|
||||
break;
|
||||
|
||||
case DataType.Guid:
|
||||
{
|
||||
GUID guid;
|
||||
unsafe
|
||||
{
|
||||
UInt64* guidPtr = (UInt64*)&guid;
|
||||
guidPtr[0] = m_Reader.ReadUInt64();
|
||||
guidPtr[1] = m_Reader.ReadUInt64();
|
||||
}
|
||||
((Func<object, GUID, GUID>)field.m_Setter)(objectRead, guid);
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Hash128:
|
||||
{
|
||||
Hash128 hash;
|
||||
unsafe
|
||||
{
|
||||
UInt64* hashPtr = (UInt64*)&hash;
|
||||
hashPtr[0] = m_Reader.ReadUInt64();
|
||||
hashPtr[1] = m_Reader.ReadUInt64();
|
||||
}
|
||||
((Func<object, Hash128, Hash128>)field.m_Setter)(objectRead, hash);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"USerialize found unknown field data type '{fieldDataType}' on field '{typeData.m_Type.Name}.{fieldName}' in stream at +{m_Reader.BaseStream.Position}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Didn't find a matching field in the object
|
||||
throw new InvalidDataException($"USerialize found unknown field '{fieldName}' for type '{typeData.m_Type.Name}' in stream at +{m_Reader.BaseStream.Position}");
|
||||
}
|
||||
}
|
||||
|
||||
return objectRead;
|
||||
}
|
||||
|
||||
// Read and deserialize data for an object that was serialized with a custom serializer
|
||||
object ReadCustomObject()
|
||||
{
|
||||
Type objectType = GetTypeFromCache(ReadStringIndex());
|
||||
if (m_CustomSerializers.TryGetValue(objectType, out ICustomSerializer customSerializer))
|
||||
{
|
||||
return customSerializer.UDeSerializer(this);
|
||||
}
|
||||
else
|
||||
throw new InvalidDataException($"Could not find custom deserializer for type {objectType.Name}, custom deserializers can be added prior to deserialization with AddCustomDeserializer()");
|
||||
}
|
||||
|
||||
// Read a byte from the stream and return true if it has value NotNull (1)
|
||||
internal bool ReadNullFlag()
|
||||
{
|
||||
return (m_Reader.ReadByte() == Serializer.NotNull);
|
||||
}
|
||||
|
||||
// Read a byte array from the stream. Will return null if the array was null when serialized
|
||||
internal byte[] ReadBytes()
|
||||
{
|
||||
byte[] bytes = null;
|
||||
if (ReadNullFlag())
|
||||
{
|
||||
bytes = new byte[m_Reader.ReadInt32()];
|
||||
m_Reader.Read(bytes, 0, bytes.Length);
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
// Read a potentially null value string from the stream
|
||||
internal string ReadString()
|
||||
{
|
||||
if (ReadNullFlag())
|
||||
return m_DataStringTable[ReadStringIndex()];
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return a Type instance for a type from the type/field stringtable index of it's name. Uses a cache of previously seen types to improve performance
|
||||
Type GetTypeFromCache(int typeNameIndex)
|
||||
{
|
||||
if (typeNameIndex >= 0)
|
||||
{
|
||||
if (m_TypeByTypeNameIndex[typeNameIndex] == null)
|
||||
m_TypeByTypeNameIndex[typeNameIndex] = Type.GetType(m_TypeStringTable[typeNameIndex]);
|
||||
|
||||
return m_TypeByTypeNameIndex[typeNameIndex];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Read a string table index. There are almost never more than 32,767 strings so we use 15 bits by default for compactness.
|
||||
// If a string has an index more than 32,767 (i.e. 0x8000+) we store 0x8000 as a flag to signify this combined with the bottom 15 bits of the index. Bits 15 to 30 are stored in the following 16 bits of data.
|
||||
internal int ReadStringIndex()
|
||||
{
|
||||
int stringIndex = m_Reader.ReadUInt16();
|
||||
if ((stringIndex & 0x8000) != 0)
|
||||
{
|
||||
stringIndex = (stringIndex & 0x7FFF) | (((int)m_Reader.ReadUInt16()) << 15);
|
||||
}
|
||||
return stringIndex;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: c8609290d91434541b2f561c09dc7b3b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,683 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Utilities.USerialize
|
||||
{
|
||||
/*
|
||||
* Main USerialize serialzation class. Used to write instances of types to a stream
|
||||
*
|
||||
* To Serialize an object to a stream use code such as
|
||||
*
|
||||
* MemoryStream stream = new MemoryStream();
|
||||
* USerialize.Serializer serializer = new USerialize.Serializer();
|
||||
* serializer.Serialize(stream, myClassInstance, 1); // '1' is the version of our object, we can read this value back at deserialization time if we want to determine the version of our data
|
||||
*/
|
||||
internal class Serializer
|
||||
{
|
||||
// Data for a single field of a single type we have serialized. We cache information obtained from the reflection API here as the reflection API can be slow to query
|
||||
internal class FieldData
|
||||
{
|
||||
internal FieldInfo m_FieldInfo;
|
||||
internal string m_Name;
|
||||
internal int m_NameIndex;
|
||||
internal Type m_ElementType;
|
||||
internal bool m_ElementTypeIsPrimitive;
|
||||
internal bool m_ElementTypeIsClass;
|
||||
internal bool m_ElementTypeIsValueType;
|
||||
internal DataType m_DataType = DataType.Invalid;
|
||||
internal object m_Getter;
|
||||
}
|
||||
|
||||
// Data for a type we have serialized. We cache information obtained from the reflection API here as the reflection API can be slow to query
|
||||
internal class TypeData
|
||||
{
|
||||
internal FieldData[] m_Fields;
|
||||
internal string m_AssemblyQualifiedName;
|
||||
internal int m_AssemblyQualifiedNameIndex;
|
||||
}
|
||||
|
||||
// Simple string table, our serialized data contains two of these, one for the names of the types and fields serialized and one for the values of any string fields or arrays/lists using strings
|
||||
internal class StringTable
|
||||
{
|
||||
List<string> m_Strings = new List<string>();
|
||||
Dictionary<string, int> m_Index = new Dictionary<string, int>();
|
||||
|
||||
/// <summary>
|
||||
/// Clear the data in this stringtable to make it empty
|
||||
/// </summary>
|
||||
internal void Clear()
|
||||
{
|
||||
m_Index.Clear();
|
||||
m_Strings.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write the strings from this stringtable to a binary writer one after another
|
||||
/// </summary>
|
||||
/// <param name="writer">Writer to write the strings to</param>
|
||||
/// <returns>the byte position in the stream being written to where the strings start, this is the current position in the stream when the function was called</returns>
|
||||
internal long Write(BinaryWriter writer)
|
||||
{
|
||||
long stringTableBytePosition = writer.Seek(0, SeekOrigin.Current);
|
||||
|
||||
writer.Write(m_Strings.Count);
|
||||
m_Strings.ForEach((item) => writer.Write(item));
|
||||
|
||||
return stringTableBytePosition;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return the index of a string in the stringtable if it exists, if it does not exist add it then return it's index
|
||||
/// </summary>
|
||||
/// <param name="stringToAddOrFind"></param>
|
||||
/// <returns></returns>
|
||||
internal int GetStringIndex(string stringToAddOrFind)
|
||||
{
|
||||
if (!m_Index.TryGetValue(stringToAddOrFind, out int stringIndex))
|
||||
{
|
||||
stringIndex = m_Strings.Count;
|
||||
m_Strings.Add(stringToAddOrFind);
|
||||
m_Index.Add(stringToAddOrFind, stringIndex);
|
||||
}
|
||||
return stringIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Byte values we store in the stream to signify whether a reference type is null (and thus absent from the stream) or not (and thus it's data comes next)
|
||||
internal const byte IsNull = 0;
|
||||
internal const byte NotNull = 1;
|
||||
|
||||
// Custom serializers can be provided by the client code to implement serialization for types that cannot be adequately handled by the generic reflection based code
|
||||
// Client code can pass an array of custom serializers and their associated types to the Serializer constructor or can call AddCustomSerializer() to add individual custom serializers at any time prior to serialization taking place
|
||||
Dictionary<Type, ICustomSerializer> m_CustomSerializers = new Dictionary<Type, ICustomSerializer>();
|
||||
|
||||
// Cache of TypeData instances for all the types we've been asked to serialize so far. Only type specific data is stored here not instance specific data and this cache *is not* cleared between calls to Serialize() so using the same
|
||||
// Serializer instance to write multiple instances of the same types achieves a significant performance benefit by being able to re-use type information without having to call slow reflection APIs again.
|
||||
Dictionary<Type, TypeData> m_TypeDataCache = new Dictionary<Type, TypeData>();
|
||||
|
||||
// Accessing Type.AssemblyQualifiedName can be slow so we keep this cache mapping types to the string table indices in the type/field string table of the assembly qualified name of types being serialized.
|
||||
// Each time Serialize() is called new stringtables are emitted so this is cleared before each call but still provides a measurable speed increase vs. accessing Type.AssemblyQualifiedName each time it's needed
|
||||
Dictionary<Type, int> m_TypeQualifiedNameIndices = new Dictionary<Type, int>();
|
||||
|
||||
// String table of strings from field values encountered
|
||||
StringTable m_DataStringTable = new StringTable();
|
||||
|
||||
// String table of type and field names encountered
|
||||
StringTable m_TypeStringTable = new StringTable();
|
||||
|
||||
// Writer we are writing out serialized data to
|
||||
BinaryWriter m_Writer;
|
||||
|
||||
// Serialization data format version number. Written to the stream to provide a means for upgrade should it be necessary in the future
|
||||
internal const byte SerializationVersion = 1;
|
||||
|
||||
internal Serializer()
|
||||
{
|
||||
}
|
||||
|
||||
internal Serializer(params ICustomSerializer[] customSerializers)
|
||||
{
|
||||
if (customSerializers != null)
|
||||
Array.ForEach(customSerializers, (customSerializer) => AddCustomSerializer(customSerializer));
|
||||
}
|
||||
|
||||
internal void AddCustomSerializer(ICustomSerializer customSerializer)
|
||||
{
|
||||
m_CustomSerializers.Add(customSerializer.GetType(), customSerializer);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear data that we cache about types and object contents that can change between objects.
|
||||
/// </summary>
|
||||
void ClearPerObjectCachedData()
|
||||
{
|
||||
// Reset the type/field name and data string tables to empty for each object.
|
||||
m_DataStringTable.Clear();
|
||||
m_TypeStringTable.Clear();
|
||||
|
||||
// Clear the type and field name indices from the cached type data as each object's type string table is distinct so the indices from any previously written objects will be invalid
|
||||
foreach (KeyValuePair<Type, TypeData> typeDataCacheEntry in m_TypeDataCache)
|
||||
{
|
||||
typeDataCacheEntry.Value.m_AssemblyQualifiedNameIndex = -1;
|
||||
|
||||
foreach (FieldData fieldData in typeDataCacheEntry.Value.m_Fields)
|
||||
{
|
||||
fieldData.m_NameIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Clear the assembly qualified type name cache as the indices of the assembly qualified type names in the type stringtable will likely be different for this object than the previous one serialized
|
||||
m_TypeQualifiedNameIndices.Clear();
|
||||
}
|
||||
|
||||
|
||||
// Main serialization function. Serializes 'objectToSerialize' to the given stream inserting the supplied object version number in the data. The object version number can be obtained by DeSerializer.ObjectVersion when deserializing the data
|
||||
internal void Serialize(Stream stream, object objectToSerialize, int objectVersion)
|
||||
{
|
||||
ClearPerObjectCachedData();
|
||||
|
||||
m_Writer = new BinaryWriter(stream);
|
||||
|
||||
// Version number for the serialization file format itself
|
||||
m_Writer.Write(SerializationVersion);
|
||||
|
||||
// Client code version number for their own use
|
||||
m_Writer.Write(objectVersion);
|
||||
|
||||
// Leave space for the offsets to the type and data stringtables data that we write after the serialization data proper
|
||||
long stringTableOffsetPosition = m_Writer.Seek(0, SeekOrigin.Current);
|
||||
m_Writer.Write(0uL); // Space for the offset to the type string table data
|
||||
m_Writer.Write(0uL); // Space for the offset to the data string table data
|
||||
|
||||
// Write serialization data for the object
|
||||
WriteObject(objectToSerialize);
|
||||
|
||||
// Write the type and data stringtables then fill in their position offsets at the start of the data we left space for earlier
|
||||
long typeStringTableBytePos = m_TypeStringTable.Write(m_Writer);
|
||||
long dataStringTableBytePos = m_DataStringTable.Write(m_Writer);
|
||||
|
||||
m_Writer.Seek((int)stringTableOffsetPosition, SeekOrigin.Begin);
|
||||
m_Writer.Write(typeStringTableBytePos);
|
||||
m_Writer.Write(dataStringTableBytePos);
|
||||
|
||||
m_Writer.Flush();
|
||||
}
|
||||
|
||||
// Call to start writing directly to a stream, used primarily for testing USerialize functions in isolation
|
||||
internal void StartWritingToStream(Stream stream)
|
||||
{
|
||||
m_Writer = new BinaryWriter(stream);
|
||||
}
|
||||
|
||||
// Call when we've finished writing to a stream, used primarily for testing USerialize functions in isolation
|
||||
internal void FinishWritingToStream()
|
||||
{
|
||||
m_Writer.Flush();
|
||||
}
|
||||
|
||||
// Return the cached type data for a given type. Will return it from the m_TypeDataCache cache if present otherwise will generate a new TypeData instance from the type and add it to the cache
|
||||
TypeData GetTypeData(Type type)
|
||||
{
|
||||
if (!m_TypeDataCache.TryGetValue(type, out TypeData typeData))
|
||||
{
|
||||
// Cache data about the fields that is slow to retrieve every time an instance of this type is processed
|
||||
|
||||
FieldInfo[] fieldInfos = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
typeData = new TypeData() { m_Fields = new FieldData[fieldInfos.Length] };
|
||||
typeData.m_AssemblyQualifiedName = type.AssemblyQualifiedName;
|
||||
typeData.m_AssemblyQualifiedNameIndex = m_TypeStringTable.GetStringIndex(typeData.m_AssemblyQualifiedName);
|
||||
|
||||
for (int fieldNum = 0; fieldNum < fieldInfos.Length; fieldNum++)
|
||||
{
|
||||
FieldInfo field = fieldInfos[fieldNum];
|
||||
FieldData fieldData = new FieldData();
|
||||
fieldData.m_FieldInfo = field;
|
||||
fieldData.m_Name = field.Name;
|
||||
fieldData.m_NameIndex = m_TypeStringTable.GetStringIndex(fieldData.m_Name);
|
||||
|
||||
if (typeof(Array).IsAssignableFrom(field.FieldType))
|
||||
{
|
||||
fieldData.m_DataType = DataType.Array;
|
||||
fieldData.m_ElementType = field.FieldType.GetElementType();
|
||||
fieldData.m_ElementTypeIsPrimitive = fieldData.m_ElementType.IsPrimitive;
|
||||
fieldData.m_ElementTypeIsClass = fieldData.m_ElementType.IsClass;
|
||||
fieldData.m_ElementTypeIsValueType = fieldData.m_ElementType.IsValueType;
|
||||
fieldData.m_Getter = CreateObjectGetter(type, field);
|
||||
}
|
||||
else if (field.FieldType.IsGenericType && (field.FieldType.GetGenericTypeDefinition() == typeof(List<>)))
|
||||
{
|
||||
fieldData.m_DataType = DataType.List;
|
||||
fieldData.m_ElementType = field.FieldType.GetGenericArguments()[0];
|
||||
}
|
||||
else if (field.FieldType == typeof(GUID))
|
||||
{
|
||||
fieldData.m_DataType = DataType.Guid;
|
||||
fieldData.m_Getter = CreateGetter<GUID>(type, field);
|
||||
}
|
||||
else if (field.FieldType == typeof(Hash128))
|
||||
{
|
||||
fieldData.m_DataType = DataType.Hash128;
|
||||
fieldData.m_Getter = CreateGetter<Hash128>(type, field);
|
||||
}
|
||||
else if (field.FieldType.IsEnum)
|
||||
{
|
||||
fieldData.m_DataType = DataType.Enum;
|
||||
fieldData.m_Getter = CreateObjectGetter(type, field);
|
||||
}
|
||||
else if (field.FieldType == typeof(String))
|
||||
{
|
||||
fieldData.m_DataType = DataType.String;
|
||||
fieldData.m_Getter = CreateGetter<string>(type, field);
|
||||
}
|
||||
else if (field.FieldType.IsClass)
|
||||
{
|
||||
fieldData.m_DataType = DataType.Class;
|
||||
fieldData.m_Getter = CreateObjectGetter(type, field);
|
||||
}
|
||||
else if (field.FieldType.IsValueType && (!field.FieldType.IsPrimitive))
|
||||
{
|
||||
fieldData.m_DataType = DataType.Struct;
|
||||
fieldData.m_Getter = CreateObjectGetter(type, field);
|
||||
}
|
||||
else if (field.FieldType == typeof(byte))
|
||||
{
|
||||
fieldData.m_DataType = DataType.Byte;
|
||||
fieldData.m_Getter = CreateGetter<byte>(type, field);
|
||||
}
|
||||
else if (field.FieldType == typeof(bool))
|
||||
{
|
||||
fieldData.m_DataType = DataType.Bool;
|
||||
fieldData.m_Getter = CreateGetter<bool>(type, field);
|
||||
}
|
||||
else if (field.FieldType == typeof(int))
|
||||
{
|
||||
fieldData.m_DataType = DataType.Int;
|
||||
fieldData.m_Getter = CreateGetter<int>(type, field);
|
||||
}
|
||||
else if (field.FieldType == typeof(uint))
|
||||
{
|
||||
fieldData.m_DataType = DataType.UInt;
|
||||
fieldData.m_Getter = CreateGetter<uint>(type, field);
|
||||
}
|
||||
else if (field.FieldType == typeof(long))
|
||||
{
|
||||
fieldData.m_DataType = DataType.Long;
|
||||
fieldData.m_Getter = CreateGetter<long>(type, field);
|
||||
}
|
||||
else if (field.FieldType == typeof(ulong))
|
||||
{
|
||||
fieldData.m_DataType = DataType.ULong;
|
||||
fieldData.m_Getter = CreateGetter<ulong>(type, field);
|
||||
}
|
||||
else if (field.FieldType == typeof(Type))
|
||||
{
|
||||
fieldData.m_DataType = DataType.Type;
|
||||
fieldData.m_Getter = CreateGetter<Type>(type, field);
|
||||
}
|
||||
|
||||
typeData.m_Fields[fieldNum] = fieldData;
|
||||
}
|
||||
m_TypeDataCache.Add(type, typeData);
|
||||
}
|
||||
else if (typeData.m_AssemblyQualifiedNameIndex == -1)
|
||||
{
|
||||
// This type is in our cache but it hasn't been used by the object being serialized yet. Find/add it's type and field names to the type string table so we have a valid index
|
||||
typeData.m_AssemblyQualifiedNameIndex = m_TypeStringTable.GetStringIndex(typeData.m_AssemblyQualifiedName);
|
||||
foreach (FieldData fieldData in typeData.m_Fields)
|
||||
{
|
||||
fieldData.m_NameIndex = m_TypeStringTable.GetStringIndex(fieldData.m_Name);
|
||||
}
|
||||
|
||||
}
|
||||
return typeData;
|
||||
}
|
||||
|
||||
// Create a function object to get the value from a field of type 'GetterType'. It is much faster to call this compiled function object than to use the reflection API
|
||||
static Func<object, GetterType> CreateGetter<GetterType>(Type type, FieldInfo field)
|
||||
{
|
||||
ParameterExpression valueExp = Expression.Parameter(typeof(object), "value");
|
||||
return Expression.Lambda<Func<object, GetterType>>(Expression.Field(Expression.Convert(valueExp, type), field), valueExp).Compile();
|
||||
}
|
||||
|
||||
// Create a function object to get the value from a field as a generic object. It is much faster to call this compiled function object than to use the reflection API
|
||||
static Func<object, object> CreateObjectGetter(Type type, FieldInfo field)
|
||||
{
|
||||
ParameterExpression valueExp = Expression.Parameter(typeof(object), "value");
|
||||
return Expression.Lambda<Func<object, object>>(Expression.Convert(Expression.Field(Expression.Convert(valueExp, type), field), typeof(object)), valueExp).Compile();
|
||||
}
|
||||
|
||||
// Write an object to the serialization stream
|
||||
void WriteObject(object objectToWrite)
|
||||
{
|
||||
if (!WriteNullFlag(objectToWrite))
|
||||
return;
|
||||
|
||||
// Get information about the objects type then write the type/field stringtable index of it's type name and how many fields it has
|
||||
Type objectType = objectToWrite.GetType();
|
||||
TypeData typeData = GetTypeData(objectType);
|
||||
|
||||
WriteStringIndex(typeData.m_AssemblyQualifiedNameIndex);
|
||||
|
||||
if (typeData.m_Fields.Length > ushort.MaxValue)
|
||||
throw new InvalidDataException($"USerialize cannot serialize objects with more than {ushort.MaxValue} fields");
|
||||
m_Writer.Write((ushort)typeData.m_Fields.Length);
|
||||
|
||||
// Process each field in turn
|
||||
foreach (FieldData field in typeData.m_Fields)
|
||||
{
|
||||
switch (field.m_DataType)
|
||||
{
|
||||
case DataType.Array:
|
||||
{
|
||||
WriteFieldInfo(field, DataType.Array);
|
||||
Array array = (Array)((Func<object, object>)field.m_Getter)(objectToWrite);
|
||||
if (WriteNullFlag(array))
|
||||
{
|
||||
// We only support rank 1 for now
|
||||
m_Writer.Write(array.Rank);
|
||||
m_Writer.Write(array.Length);
|
||||
|
||||
if (array.Rank != 1)
|
||||
throw new InvalidDataException($"USerialize currently doesn't support arrays with ranks other than one - field {field.m_Name} of type {field.m_FieldInfo.FieldType.Name} has rank {array.Rank}");
|
||||
|
||||
Type elementType = field.m_ElementType;
|
||||
if (field.m_ElementTypeIsPrimitive)
|
||||
{
|
||||
// A primitive array, write the bytes as optimally as possible for types we support
|
||||
if (elementType == typeof(byte))
|
||||
{
|
||||
// byte[]
|
||||
m_Writer.Write((byte)DataType.Byte);
|
||||
m_Writer.Write((byte[])array, 0, array.Length);
|
||||
}
|
||||
// Per customer request
|
||||
else if (elementType == typeof(ulong))
|
||||
{
|
||||
ulong[] ulongArray = (ulong[])array;
|
||||
m_Writer.Write((byte)DataType.ULong);
|
||||
for (int elementIndex = 0; elementIndex < array.Length; elementIndex++)
|
||||
{
|
||||
m_Writer.Write(ulongArray[elementIndex]);
|
||||
}
|
||||
}
|
||||
else
|
||||
throw new InvalidDataException($"USerialize currently doesn't support primitive arrays of type {elementType.Name} - field {field.m_Name} of type {field.m_FieldInfo.FieldType.Name}");
|
||||
}
|
||||
else if (elementType == typeof(string))
|
||||
{
|
||||
// String[]
|
||||
string[] stringArray = (string[])array;
|
||||
m_Writer.Write((byte)DataType.String);
|
||||
for (int elementIndex = 0; elementIndex < array.Length; elementIndex++)
|
||||
{
|
||||
WriteDataString(stringArray[elementIndex]);
|
||||
}
|
||||
}
|
||||
else if (elementType == typeof(Type))
|
||||
{
|
||||
// Type[]
|
||||
Type[] typeArray = (Type[])array;
|
||||
m_Writer.Write((byte)DataType.Type);
|
||||
for (int elementIndex = 0; elementIndex < array.Length; elementIndex++)
|
||||
{
|
||||
if (typeArray[elementIndex] != null)
|
||||
WriteStringIndex(GetTypeQualifiedNameIndex(typeArray[elementIndex]));
|
||||
else
|
||||
WriteStringIndex(USerialize.InvalidStringIndex);
|
||||
|
||||
}
|
||||
}
|
||||
else if (field.m_ElementTypeIsClass)
|
||||
{
|
||||
// An array of class instances
|
||||
m_Writer.Write((byte)DataType.Class);
|
||||
WriteStringIndex(GetTypeQualifiedNameIndex(elementType));
|
||||
for (int elementIndex = 0; elementIndex < array.Length; elementIndex++)
|
||||
{
|
||||
object elementToWrite = array.GetValue(elementIndex);
|
||||
|
||||
// If the element isn't null see if we have a custom serializer for the type of this instance.
|
||||
// The array type might be a base class for the actual instances which may not all be the same derived type so we use the runtime type of each instance individually to check for custom serializers rather than using the type of the array itself
|
||||
if (elementToWrite != null)
|
||||
{
|
||||
Type elementObjectType = elementToWrite.GetType();
|
||||
if (m_CustomSerializers.TryGetValue(elementObjectType, out ICustomSerializer customSerializer))
|
||||
{
|
||||
m_Writer.Write((byte)DataType.Custom);
|
||||
WriteStringIndex(GetTypeQualifiedNameIndex(elementObjectType));
|
||||
customSerializer.USerializer(this, elementToWrite);
|
||||
}
|
||||
else if (elementObjectType == typeof(string))
|
||||
{
|
||||
m_Writer.Write((byte)DataType.String);
|
||||
WriteDataString((string)elementToWrite);
|
||||
}
|
||||
else if (elementObjectType == typeof(Int32))
|
||||
{
|
||||
m_Writer.Write((byte)DataType.Int);
|
||||
m_Writer.Write((int)elementToWrite);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (elementObjectType.IsPrimitive)
|
||||
throw new InvalidDataException($"USerialize cannot handle type '{elementObjectType.Name}' in object[] array '{objectType.Name}.{field.m_Name}'");
|
||||
m_Writer.Write((byte)DataType.Class);
|
||||
WriteObject(elementToWrite);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Writer.Write((byte)DataType.Class);
|
||||
m_Writer.Write(IsNull);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (field.m_ElementTypeIsValueType)
|
||||
{
|
||||
// An array of struct instances
|
||||
m_Writer.Write((byte)DataType.Struct);
|
||||
WriteStringIndex(GetTypeQualifiedNameIndex(elementType));
|
||||
for (int elementIndex = 0; elementIndex < array.Length; elementIndex++)
|
||||
{
|
||||
WriteObject(array.GetValue(elementIndex));
|
||||
}
|
||||
}
|
||||
else
|
||||
throw new InvalidDataException($"USerialize doesn't support serializing array field {field.m_Name} of type {field.m_FieldInfo.FieldType.Name} which is of type {elementType.Name}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.List:
|
||||
{
|
||||
// A List<>
|
||||
WriteFieldInfo(field, DataType.List);
|
||||
System.Collections.IList list = field.m_FieldInfo.GetValue(objectToWrite) as System.Collections.IList;
|
||||
if (WriteNullFlag(list))
|
||||
{
|
||||
m_Writer.Write(list.Count);
|
||||
WriteStringIndex(GetTypeQualifiedNameIndex(field.m_ElementType));
|
||||
for (int elementIndex = 0; elementIndex < list.Count; elementIndex++)
|
||||
{
|
||||
WriteObject(list[elementIndex]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Guid:
|
||||
{
|
||||
// GUID instance
|
||||
WriteFieldInfo(field, DataType.Guid);
|
||||
GUID guid = ((Func<object, GUID>)field.m_Getter)(objectToWrite);
|
||||
unsafe
|
||||
{
|
||||
UInt64* guidPtr = (UInt64*)&guid;
|
||||
m_Writer.Write(guidPtr[0]);
|
||||
m_Writer.Write(guidPtr[1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Hash128:
|
||||
{
|
||||
// Hash128 instance
|
||||
WriteFieldInfo(field, DataType.Hash128);
|
||||
Hash128 hash = ((Func<object, Hash128>)field.m_Getter)(objectToWrite);
|
||||
unsafe
|
||||
{
|
||||
UInt64* hashPtr = (UInt64*)&hash;
|
||||
m_Writer.Write(hashPtr[0]);
|
||||
m_Writer.Write(hashPtr[1]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Enum:
|
||||
// An enum, we write it's value as an Int32
|
||||
WriteFieldInfo(field, DataType.Enum);
|
||||
m_Writer.Write((int)((Func<object, object>)field.m_Getter)(objectToWrite));
|
||||
break;
|
||||
|
||||
case DataType.String:
|
||||
{
|
||||
// String instance
|
||||
WriteFieldInfo(field, DataType.String);
|
||||
WriteDataString(((Func<object, string>)field.m_Getter)(objectToWrite));
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Class:
|
||||
{
|
||||
// Is a class instance. If the value isn't null check to see if we have been given a custom serializer for it's type.
|
||||
// If the value is null or there is no custom serializer registered for the value's type write it as normal
|
||||
// Note the type of the actual object is used to locate custom serializers rather than the type of the field in case the object is actually of a derived type
|
||||
object fieldValue = ((Func<object, object>)field.m_Getter)(objectToWrite);
|
||||
if ((fieldValue == null) || (!DoCustomSerialization(field, fieldValue)))
|
||||
{
|
||||
WriteFieldInfo(field, DataType.Class);
|
||||
WriteObject(fieldValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Struct:
|
||||
{
|
||||
// Is a struct instance. Check to see if we have been given a custom serializer for it's type, if not write it as normal
|
||||
object fieldValue = ((Func<object, object>)field.m_Getter)(objectToWrite);
|
||||
if (!DoCustomSerialization(field, fieldValue))
|
||||
{
|
||||
WriteFieldInfo(field, DataType.Struct);
|
||||
WriteObject(fieldValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case DataType.Byte:
|
||||
WriteFieldInfo(field, DataType.Byte);
|
||||
m_Writer.Write(((Func<object, byte>)field.m_Getter)(objectToWrite));
|
||||
break;
|
||||
|
||||
case DataType.Bool:
|
||||
WriteFieldInfo(field, DataType.Bool);
|
||||
m_Writer.Write(((Func<object, bool>)field.m_Getter)(objectToWrite));
|
||||
break;
|
||||
|
||||
case DataType.Int:
|
||||
WriteFieldInfo(field, DataType.Int);
|
||||
m_Writer.Write(((Func<object, int>)field.m_Getter)(objectToWrite));
|
||||
break;
|
||||
|
||||
case DataType.UInt:
|
||||
WriteFieldInfo(field, DataType.UInt);
|
||||
m_Writer.Write(((Func<object, uint>)field.m_Getter)(objectToWrite));
|
||||
break;
|
||||
|
||||
case DataType.Long:
|
||||
WriteFieldInfo(field, DataType.Long);
|
||||
m_Writer.Write(((Func<object, long>)field.m_Getter)(objectToWrite));
|
||||
break;
|
||||
|
||||
case DataType.ULong:
|
||||
WriteFieldInfo(field, DataType.ULong);
|
||||
m_Writer.Write(((Func<object, ulong>)field.m_Getter)(objectToWrite));
|
||||
break;
|
||||
|
||||
case DataType.Type:
|
||||
WriteFieldInfo(field, DataType.Type);
|
||||
WriteStringIndex(GetTypeQualifiedNameIndex(((Func<object, Type>)field.m_Getter)(objectToWrite)));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidDataException($"USerialize doesn't know how to serialize field {objectType.Name}.{field.m_Name} of type {field.m_FieldInfo.FieldType.Name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the index in the type/field stringtable of the AssemblyQualifiedName of the given type. Accessing Type.AssemblyQualifiedName can be slow so we use a cache
|
||||
// to store the string table indices of types we've encountered before
|
||||
int GetTypeQualifiedNameIndex(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
return -1;
|
||||
|
||||
if (!m_TypeQualifiedNameIndices.TryGetValue(type, out int qualifiedNameIndex))
|
||||
{
|
||||
qualifiedNameIndex = m_TypeStringTable.GetStringIndex(type.AssemblyQualifiedName);
|
||||
m_TypeQualifiedNameIndices.Add(type, qualifiedNameIndex);
|
||||
}
|
||||
return qualifiedNameIndex;
|
||||
}
|
||||
|
||||
// Check to see if a custom serializer has been registered for the type of a given object. If so call it and return true, otherwise return false
|
||||
bool DoCustomSerialization(FieldData field, object valueToSerialize)
|
||||
{
|
||||
Type valueType = valueToSerialize.GetType();
|
||||
if (m_CustomSerializers.TryGetValue(valueType, out ICustomSerializer customSerializer))
|
||||
{
|
||||
WriteFieldInfo(field, DataType.Custom);
|
||||
WriteStringIndex(GetTypeQualifiedNameIndex(valueType));
|
||||
customSerializer.USerializer(this, valueToSerialize);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write a byte array to the stream. The actual bytes are preceded by a flag byte indicating whether the array passed in was null or not
|
||||
internal void WriteBytes(byte[] bytes)
|
||||
{
|
||||
if (WriteNullFlag(bytes))
|
||||
{
|
||||
m_Writer.Write(bytes.Length);
|
||||
m_Writer.Write(bytes, 0, bytes.Length);
|
||||
}
|
||||
}
|
||||
|
||||
// If the supplied object *is not* null write a byte with value NotNull (1) to the stream and return true
|
||||
// if the supplied object *is* null write a byte with value IsNull (0) to the stream and return false
|
||||
internal bool WriteNullFlag(object value)
|
||||
{
|
||||
if (value != null)
|
||||
{
|
||||
m_Writer.Write(NotNull);
|
||||
return true;
|
||||
}
|
||||
|
||||
m_Writer.Write(IsNull);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write a string table index. There are almost never more than 32,767 strings so we use 15 bits by default for compactness.
|
||||
// If a string has an index more than 32,767 (i.e. 0x8000+) we store 0x8000 as a flag to signify this combined with the bottom 15 bits of the index. Bits 15 to 30 are stored in the following 16 bits of data.
|
||||
internal void WriteStringIndex(int stringIndex)
|
||||
{
|
||||
if (stringIndex < 0x8000)
|
||||
m_Writer.Write((ushort)stringIndex);
|
||||
else
|
||||
{
|
||||
m_Writer.Write((ushort)(0x8000 | (stringIndex & 0x7FFF)));
|
||||
m_Writer.Write((ushort)(stringIndex >> 15));
|
||||
}
|
||||
}
|
||||
|
||||
// Write meta-data for a field to the stream. The index of the field's name in the type/field stringtable is written followed by a byte indicating the type of the data in the stream
|
||||
void WriteFieldInfo(FieldData field, DataType dataType)
|
||||
{
|
||||
WriteStringIndex(field.m_NameIndex);
|
||||
m_Writer.Write((byte)dataType);
|
||||
}
|
||||
|
||||
// Write a field value string to the stream. A IsNull/NotNull byte is written then if the string is not null the index of the string in the data stringtable
|
||||
internal void WriteDataString(string stringToWrite)
|
||||
{
|
||||
if (WriteNullFlag(stringToWrite))
|
||||
WriteStringIndex(m_DataStringTable.GetStringIndex(stringToWrite));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 947200859c948604e8bbf513d0b1eaf1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
|
@ -0,0 +1,51 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Build.Pipeline.Utilities.USerialize
|
||||
{
|
||||
// Data types that USerialize can emit to the stream
|
||||
internal enum DataType
|
||||
{
|
||||
Byte,
|
||||
Bool,
|
||||
Int,
|
||||
UInt,
|
||||
Long,
|
||||
ULong,
|
||||
String,
|
||||
Class,
|
||||
Struct,
|
||||
Enum,
|
||||
Array,
|
||||
List,
|
||||
Custom,
|
||||
Type,
|
||||
Guid,
|
||||
Hash128,
|
||||
Invalid
|
||||
}
|
||||
|
||||
// Custom serializers can be provided by the client code to implement serialization for types that cannot be adequately handled by the generic reflection based code
|
||||
// A custom serializer is a class that implements this ICustomSerializer interface to provide functions to serialize data for a type to the stream and recreate an instance of the type from a serialized data stream
|
||||
// Client code can pass an array of custom serializers and their associated types to the Serializer/Deserializer constructor or can call AddCustomSerializer() to add individual custom serializers at any time prior to serialization taking place
|
||||
internal interface ICustomSerializer
|
||||
{
|
||||
// Return the type that this custom serializer deals with
|
||||
Type GetType();
|
||||
|
||||
// Serializer function to convert an instance of the type into a serialized stream.
|
||||
void USerializer(Serializer serializer, object value);
|
||||
|
||||
// Deserializer function to create an instance of the type from a previously serialized stream
|
||||
object UDeSerializer(DeSerializer deserializer);
|
||||
}
|
||||
|
||||
internal static class USerialize
|
||||
{
|
||||
// Reserved value for string indices representing an invalid string index, maximum value that can be written by Serializer.WriteStringIndex() or read by DeSerializer.ReadStringIndex()
|
||||
internal const int InvalidStringIndex = int.MaxValue;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: 4e3c19776468fc14296d45fbc07b24a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Loading…
Add table
Add a link
Reference in a new issue