initial commit

This commit is contained in:
Jo 2025-01-07 02:06:59 +01:00
parent 6715289efe
commit 788c3389af
37645 changed files with 2526849 additions and 80 deletions

View file

@ -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;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8609290d91434541b2f561c09dc7b3b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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));
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 947200859c948604e8bbf513d0b1eaf1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -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;
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4e3c19776468fc14296d45fbc07b24a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: