Added Unity project files

This commit is contained in:
2026-06-07 16:58:24 +01:00
parent 3cc05d260b
commit 23bbcab156
3942 changed files with 453676 additions and 0 deletions

View File

@ -0,0 +1,36 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace UdonSharp.Serialization
{
/// <summary>
/// Handles class serialization where there are multiple unknown fields that need to be serialized.
/// Instead of how serializers key off the root storage type using type metadata, Formatters try to extract the type data from their target object to serialize
/// This handles inheritance which serializers cannot handle on their own
/// </summary>
public interface IFormatter
{
void Write(IValueStorage targetObject, object sourceObject);
void Read(ref object targetObject, IValueStorage sourceObject);
}
public abstract class Formatter<T> : IFormatter
{
public abstract void Read(ref T targetObject, IValueStorage sourceObject);
public abstract void Write(IValueStorage targetObject, T sourceObject);
void IFormatter.Read(ref object targetObject, IValueStorage sourceObject)
{
T targetT = (T)targetObject;
Read(ref targetT, sourceObject);
targetObject = targetT;
}
void IFormatter.Write(IValueStorage targetObject, object sourceObject)
{
Write(targetObject, (T)sourceObject);
}
}
}

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 0e15d45d631d675458e70de48cd83d82
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,452 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using UdonSharp.Compiler.Symbols;
using UdonSharpEditor;
using UnityEditor;
using UnityEngine;
using VRC.Udon;
using VRC.Udon.Serialization.OdinSerializer.Utilities;
namespace UdonSharp.Serialization
{
internal static class UdonSharpBehaviourFormatterEmitter
{
private const string RUNTIME_ASSEMBLY_NAME = "UdonSharp.Serialization.RuntimeEmittedFormatters";
private delegate void ReadDataMethodDelegate<T>(IValueStorage[] sourceObject, ref T targetObject, bool includeNonSerialized);
private delegate void WriteDataMethodDelegate<T>(IValueStorage[] targetObject, ref T sourceObject, bool includeNonSerialized);
// Force a ref equality lookup in case VRC implements an Equals or GetHashCode overrides in the future that don't act like we need them to
private class RefEqualityComparer<TRef> : EqualityComparer<TRef> where TRef : class
{
public override bool Equals(TRef x, TRef y) { return ReferenceEquals(x, y); }
public override int GetHashCode(TRef obj) { return RuntimeHelpers.GetHashCode(obj); }
}
private static IHeapStorage CreateHeapStorage(UdonBehaviour behaviour)
{
if (EditorApplication.isPlaying && !UsbSerializationContext.UseHeapSerialization)
{
UdonHeapStorageInterface heapStorageInterface = new UdonHeapStorageInterface(behaviour);
return heapStorageInterface.IsValid ? heapStorageInterface : null;
}
return new UdonVariableStorageInterface(behaviour);
}
private class UdonBehaviourHeapData
{
public IHeapStorage heapStorage;
public IValueStorage[] heapFieldValues; // Direct references to each field in order on the heap storage.
}
private class EmittedFormatter<T> : Formatter<T> where T : UdonSharpBehaviour
{
// Initialize field layout for T
public static void Init(FieldInfo[] publicFields, FieldInfo[] privateFields)
{
if (typeof(T) == typeof(UdonSharpBehaviour))
{
Debug.LogError("Attempted to initialize UdonSharpBehaviour emitted formatter for UdonSharpBehaviour exact type. This is not allowed.");
return;
}
string[] fieldLayout = new string[publicFields.Length + privateFields.Length];
for (int i = 0; i < publicFields.Length; ++i) fieldLayout[i] = publicFields[i].Name;
for (int i = publicFields.Length; i < publicFields.Length + privateFields.Length; ++i) fieldLayout[i] = privateFields[i - publicFields.Length].Name;
UdonSharpBehaviourFormatterManager.fieldLayout = fieldLayout;
}
private class UdonSharpBehaviourFormatterManager
{
// ReSharper disable once StaticMemberInGenericType
private static Dictionary<UdonBehaviour, UdonBehaviourHeapData> _heapDataLookup = new Dictionary<UdonBehaviour, UdonBehaviourHeapData>(new RefEqualityComparer<UdonBehaviour>());
// ReSharper disable once StaticMemberInGenericType
public static string[] fieldLayout;
public static UdonBehaviourHeapData GetHeapData(UdonBehaviour udonBehaviour)
{
if (!UsbSerializationContext.UseHeapSerialization && _heapDataLookup.TryGetValue(udonBehaviour, out var heapData))
return heapData;
if (fieldLayout == null)
throw new NullReferenceException($"Formatter manager {typeof(UdonSharpBehaviourFormatterManager).FullName} has not been initialized.");
IHeapStorage heapStorage = CreateHeapStorage(udonBehaviour);
if (heapStorage == null)
return null;
IValueStorage[] heapFieldValues = new IValueStorage[fieldLayout.Length];
for (int i = 0; i < heapFieldValues.Length; ++i)
heapFieldValues[i] = heapStorage.GetElementStorage(fieldLayout[i]);
heapData = new UdonBehaviourHeapData() { heapStorage = heapStorage, heapFieldValues = heapFieldValues };
if (!UsbSerializationContext.UseHeapSerialization)
_heapDataLookup.Add(udonBehaviour, heapData);
return heapData;
}
}
private ReadDataMethodDelegate<T> readDelegate;
private WriteDataMethodDelegate<T> writeDelegate;
public EmittedFormatter(ReadDataMethodDelegate<T> readDelegate, WriteDataMethodDelegate<T> writeDelegate)
{
this.readDelegate = readDelegate;
this.writeDelegate = writeDelegate;
}
public override void Read(ref T targetObject, IValueStorage sourceObject)
{
UdonBehaviourHeapData heapStorage = UdonSharpBehaviourFormatterManager.GetHeapData((UdonBehaviour)sourceObject.Value);
if (heapStorage != null)
readDelegate(heapStorage.heapFieldValues, ref targetObject, EditorApplication.isPlaying && !UsbSerializationContext.CurrentPolicy.IsPreBuildSerialize);
}
public override void Write(IValueStorage targetObject, T sourceObject)
{
UdonBehaviour backingBehaviour = UdonSharpEditorUtility.GetBackingUdonBehaviour(sourceObject);
UdonBehaviourHeapData heapStorage = UdonSharpBehaviourFormatterManager.GetHeapData(backingBehaviour);
if (heapStorage == null)
return;
writeDelegate(heapStorage.heapFieldValues, ref sourceObject, EditorApplication.isPlaying && !UsbSerializationContext.CurrentPolicy.IsPreBuildSerialize);
if (!UsbSerializationContext.CollectDependencies && !EditorApplication.isPlaying)
PrefabUtility.RecordPrefabInstancePropertyModifications(backingBehaviour);
}
}
private static Dictionary<Type, IFormatter> _formatters = new Dictionary<Type, IFormatter>();
private static readonly object _emitLock = new object();
private static AssemblyBuilder _runtimeEmittedAssembly;
private static ModuleBuilder _runtimeEmittedModule;
private static readonly MethodInfo _getMethodInfoGeneric = typeof(UdonSharpBehaviourFormatterEmitter)
.GetMethods(BindingFlags.Public | BindingFlags.Static)
.First(e => e.Name == nameof(GetFormatter) && e.IsGenericMethod);
private static Dictionary<Type, MethodInfo> _generatedMethods = new Dictionary<Type, MethodInfo>();
public static IFormatter GetFormatter(Type type)
{
lock (_emitLock)
{
if (!_generatedMethods.TryGetValue(type, out var formatterGet))
{
formatterGet = _getMethodInfoGeneric.MakeGenericMethod(type);
_generatedMethods.Add(type, formatterGet);
}
return (IFormatter)formatterGet.Invoke(null, Array.Empty<object>());
}
}
public static Formatter<T> GetFormatter<T>() where T : UdonSharpBehaviour
{
lock (_emitLock)
{
if (_formatters.TryGetValue(typeof(T), out IFormatter formatter))
{
return (Formatter<T>)formatter;
}
List<FieldInfo> serializedFieldList = new List<FieldInfo>();
List<FieldInfo> nonSerializedFieldList = new List<FieldInfo>();
Stack<Type> baseTypes = new Stack<Type>();
Type currentType = typeof(T);
while (currentType != null &&
currentType != typeof(UdonSharpBehaviour))
{
baseTypes.Push(currentType);
currentType = currentType.BaseType;
}
List<FieldInfo> allFields = new List<FieldInfo>();
while (baseTypes.Count > 0)
{
currentType = baseTypes.Pop();
allFields.AddRange(currentType.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance));
}
foreach (FieldInfo field in allFields)
{
// if (field.IsDefined(typeof(CompilerGeneratedAttribute), false))
// continue;
if (FieldSymbol.IsFieldSerialized(field))
{
serializedFieldList.Add(field);
}
else
{
nonSerializedFieldList.Add(field);
}
}
FieldInfo[] publicFields = serializedFieldList.ToArray();
FieldInfo[] privateFields = nonSerializedFieldList.ToArray();
EmittedFormatter<T>.Init(publicFields, privateFields);
InitializeRuntimeAssemblyBuilder();
BuildHelperType(typeof(T), publicFields, privateFields, out var serializerFields);
Type formatterType = typeof(EmittedFormatter<>).MakeGenericType(typeof(T));
Delegate readDel, writeDel;
// Read
{
Type readDelegateType = typeof(ReadDataMethodDelegate<>).MakeGenericType(typeof(T));
MethodInfo readDataMethod = formatterType.GetMethods(Flags.InstancePublic).First(e => e.Name == "Read" && e.GetParameters().Length == 2);
DynamicMethod readMethod = new DynamicMethod($"Dynamic_{typeof(T).GetCompilableNiceFullName()}_Read", null, new[] { typeof(IValueStorage[]), typeof(T).MakeByRefType(), typeof(bool) }, true);
foreach (ParameterInfo param in readDataMethod.GetParameters())
readMethod.DefineParameter(param.Position, param.Attributes, param.Name);
EmitReadMethod(readMethod.GetILGenerator(), publicFields, privateFields, serializerFields);
readDel = readMethod.CreateDelegate(readDelegateType);
}
// Write
{
Type writeDelegateType = typeof(WriteDataMethodDelegate<>).MakeGenericType(typeof(T));
MethodInfo writeDataMethod = formatterType.GetMethods(Flags.InstancePublic).First(e => e.Name == "Write" && e.GetParameters().Length == 2);
DynamicMethod writeMethod = new DynamicMethod($"Dynamic_{typeof(T).GetCompilableNiceFullName()}_Write", null, new[] { typeof(IValueStorage[]), typeof(T).MakeByRefType(), typeof(bool) }, true);
foreach (ParameterInfo param in writeDataMethod.GetParameters())
writeMethod.DefineParameter(param.Position, param.Attributes, param.Name);
EmitWriteMethod(writeMethod.GetILGenerator(), publicFields, privateFields, serializerFields);
writeDel = writeMethod.CreateDelegate(writeDelegateType);
}
formatter = (Formatter<T>)Activator.CreateInstance(typeof(EmittedFormatter<T>), readDel, writeDel);
_formatters.Add(typeof(T), formatter);
return (Formatter<T>)formatter;
}
}
private static void InitializeRuntimeAssemblyBuilder()
{
if (_runtimeEmittedAssembly == null)
{
AssemblyName assemblyName = new AssemblyName(RUNTIME_ASSEMBLY_NAME)
{
CultureInfo = System.Globalization.CultureInfo.InvariantCulture,
Flags = AssemblyNameFlags.None,
ProcessorArchitecture = ProcessorArchitecture.MSIL,
VersionCompatibility = System.Configuration.Assemblies.AssemblyVersionCompatibility.SameDomain
};
_runtimeEmittedAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
}
if (_runtimeEmittedModule == null)
{
_runtimeEmittedModule = _runtimeEmittedAssembly.DefineDynamicModule(RUNTIME_ASSEMBLY_NAME, true);
}
}
private static readonly MethodInfo _serializerCreateMethod = typeof(Serializer).GetMethod(nameof(Serializer.CreatePooled), BindingFlags.Public | BindingFlags.Static, null, new Type[] { }, null);
private static void BuildHelperType(Type formattedType,
FieldInfo[] publicFields,
FieldInfo[] privateFields,
out Dictionary<Type, FieldBuilder> serializerFields)
{
string generatedTypeName = $"{_runtimeEmittedModule.Name}.{formattedType.GetCompilableNiceFullName()}___{formattedType.Assembly.GetName()}___FormatterHelper___{Guid.NewGuid().ToString()}";
TypeBuilder typeBuilder = _runtimeEmittedModule.DefineType(generatedTypeName, TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class);
serializerFields = new Dictionary<Type, FieldBuilder>();
foreach (FieldInfo info in publicFields)
{
if (!serializerFields.ContainsKey(info.FieldType))
{
serializerFields.Add(info.FieldType, typeBuilder.DefineField($"{info.FieldType.GetCompilableNiceFullName()}___Serializer", typeof(Serializer<>).MakeGenericType(info.FieldType), FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly));
}
}
foreach (FieldInfo info in privateFields)
{
if (!serializerFields.ContainsKey(info.FieldType))
{
serializerFields.Add(info.FieldType, typeBuilder.DefineField($"{info.FieldType.GetCompilableNiceFullName()}___Serializer", typeof(Serializer<>).MakeGenericType(info.FieldType), FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.InitOnly));
}
}
//MethodInfo typeofMethod = typeof(System.Type).GetMethod("GetTypeFromHandle", Flags.StaticPublic, null, new[] { typeof(System.RuntimeTypeHandle) }, null);
ConstructorBuilder staticConstructor = typeBuilder.DefineTypeInitializer();
ILGenerator generator = staticConstructor.GetILGenerator();
foreach (var entry in serializerFields)
{
generator.Emit(OpCodes.Call, _serializerCreateMethod.MakeGenericMethod(entry.Key));
generator.Emit(OpCodes.Stsfld, entry.Value);
}
generator.Emit(OpCodes.Ret);
typeBuilder.CreateType();
}
// Is this even needed or will the generator do this anyways?
private static void EmitConstInt(ILGenerator gen, int value)
{
switch (value)
{
case 0:
gen.Emit(OpCodes.Ldc_I4_0);
break;
case 1:
gen.Emit(OpCodes.Ldc_I4_1);
break;
case 2:
gen.Emit(OpCodes.Ldc_I4_2);
break;
case 3:
gen.Emit(OpCodes.Ldc_I4_3);
break;
case 4:
gen.Emit(OpCodes.Ldc_I4_4);
break;
case 5:
gen.Emit(OpCodes.Ldc_I4_5);
break;
case 6:
gen.Emit(OpCodes.Ldc_I4_6);
break;
case 7:
gen.Emit(OpCodes.Ldc_I4_7);
break;
case 8:
gen.Emit(OpCodes.Ldc_I4_8);
break;
default:
gen.Emit(value < 128 ? OpCodes.Ldc_I4_S : OpCodes.Ldc_I4, value);
break;
}
}
private static void EmitReadMethod(ILGenerator generator,
FieldInfo[] publicFields,
FieldInfo[] privateFields,
Dictionary<Type, FieldBuilder> serializerFields)
{
for (int i = 0; i < publicFields.Length; ++i)
{
FieldInfo currentField = publicFields[i];
FieldBuilder serializerField = serializerFields[currentField.FieldType];
generator.Emit(OpCodes.Ldsfld, serializerField); // Load serializer field
generator.Emit(OpCodes.Ldarg_1); // Load the serialized type arg
generator.Emit(OpCodes.Ldind_Ref); // Read by ref type
generator.Emit(OpCodes.Ldflda, currentField); // Get field address to read to
generator.Emit(OpCodes.Ldarg_0); // Load the IValueStorage array
EmitConstInt(generator, i); // Emit the element index
generator.Emit(OpCodes.Ldelem_Ref); // Read element, push to stack
generator.Emit(OpCodes.Callvirt, serializerField.FieldType.GetMethod("Read")); // Call the serializer's Read method
}
Label skipNonSerializedLabel = generator.DefineLabel();
generator.Emit(OpCodes.Ldarg_2); // load includeNonSerialized bool arg
generator.Emit(OpCodes.Brfalse, skipNonSerializedLabel); // Jump to exit if includeNonSerialized is false
for (int i = 0; i < privateFields.Length; ++i)
{
FieldInfo currentField = privateFields[i];
FieldBuilder serializerField = serializerFields[currentField.FieldType];
generator.Emit(OpCodes.Ldsfld, serializerField); // Load serializer field
generator.Emit(OpCodes.Ldarg_1); // Load the serialized type arg
generator.Emit(OpCodes.Ldind_Ref); // Read by ref type
generator.Emit(OpCodes.Ldflda, currentField); // Get field address to read to
generator.Emit(OpCodes.Ldarg_0); // Load the IValueStorage array
EmitConstInt(generator, i + publicFields.Length); // Emit the element index
generator.Emit(OpCodes.Ldelem_Ref); // Read element, push to stack
generator.Emit(OpCodes.Callvirt, serializerField.FieldType.GetMethod("Read")); // Call the serializer's Read method
}
generator.MarkLabel(skipNonSerializedLabel);
generator.Emit(OpCodes.Ret);
}
private static void EmitWriteMethod(ILGenerator generator,
FieldInfo[] publicFields,
FieldInfo[] privateFields,
Dictionary<Type, FieldBuilder> serializerFields)
{
for (int i = 0; i < publicFields.Length; ++i)
{
FieldInfo currentField = publicFields[i];
FieldBuilder serializerField = serializerFields[currentField.FieldType];
generator.Emit(OpCodes.Ldsfld, serializerField); // Load serializer field
generator.Emit(OpCodes.Ldarg_0); // Load the IValueStorage array
EmitConstInt(generator, i); // Emit the element index
generator.Emit(OpCodes.Ldelem_Ref); // Read element, push to stack
generator.Emit(OpCodes.Ldarg_1); // Load the serialized type arg
generator.Emit(OpCodes.Ldind_Ref); // Read by ref type
generator.Emit(OpCodes.Ldflda, currentField); // Get field address to read from
generator.Emit(OpCodes.Callvirt, serializerField.FieldType.GetMethod("Write")); // Call the serializer's Write method
}
Label skipNonSerializedLabel = generator.DefineLabel();
generator.Emit(OpCodes.Ldarg_2); // load includeNonSerialized bool arg
generator.Emit(OpCodes.Brfalse, skipNonSerializedLabel); // Jump to exit if includeNonSerialized is false
for (int i = 0; i < privateFields.Length; ++i)
{
FieldInfo currentField = privateFields[i];
FieldBuilder serializerField = serializerFields[currentField.FieldType];
generator.Emit(OpCodes.Ldsfld, serializerField); // Load serializer field
generator.Emit(OpCodes.Ldarg_0); // Load the IValueStorage array
EmitConstInt(generator, i + publicFields.Length); // Emit the element index
generator.Emit(OpCodes.Ldelem_Ref); // Read element, push to stack
generator.Emit(OpCodes.Ldarg_1); // Load the serialized type arg
generator.Emit(OpCodes.Ldind_Ref); // Read by ref type
generator.Emit(OpCodes.Ldflda, currentField); // Get field address to read from
generator.Emit(OpCodes.Callvirt, serializerField.FieldType.GetMethod("Write")); // Call the serializer's Write method
}
generator.MarkLabel(skipNonSerializedLabel);
generator.Emit(OpCodes.Ret);
}
}
}

View File

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

View File

@ -0,0 +1,46 @@

using JetBrains.Annotations;
namespace UdonSharpEditor
{
public class ProxySerializationPolicy
{
public int MaxSerializationDepth { get; private set; } = int.MaxValue;
/// <summary>
/// If the policy should collect UnityEngine.Object dependencies. This makes any read/write operations a no-op on behaviours and instead just collects the referenced objects.
/// </summary>
public bool CollectDependencies { get; private set; }
/// <summary>
/// Forces use of the heap rather than the public variables.
/// Needed because on post process scene can happen while the editor is in play mode, but we need to setup behaviours with heap variables.
/// </summary>
public bool IsPreBuildSerialize { get; private set; }
internal static readonly ProxySerializationPolicy CollectRootDependencies = new ProxySerializationPolicy() { MaxSerializationDepth = 1, CollectDependencies = true };
internal static readonly ProxySerializationPolicy PreBuildSerialize = new ProxySerializationPolicy() { MaxSerializationDepth = 1, IsPreBuildSerialize = true };
[PublicAPI]
public static readonly ProxySerializationPolicy Default = new ProxySerializationPolicy() { MaxSerializationDepth = 1 };
[PublicAPI]
public static readonly ProxySerializationPolicy RootOnly = new ProxySerializationPolicy() { MaxSerializationDepth = 1 };
/// <summary>
/// Copies all properties on all behaviours directly and indirectly referenced by the target behaviour recursively.
/// example: Calling this on the root node of a tree where each node is an UdonSharpBehaviour would copy all node data for every node on the tree
/// </summary>
[PublicAPI]
public static readonly ProxySerializationPolicy All = new ProxySerializationPolicy() { MaxSerializationDepth = int.MaxValue };
/// <summary>
/// Does not run any copy operations, usually used if you want the GetUdonSharpComponent call to not copy any data
/// </summary>
[PublicAPI]
public static readonly ProxySerializationPolicy NoSerialization = new ProxySerializationPolicy() { MaxSerializationDepth = 0 };
private ProxySerializationPolicy()
{ }
}
}

View File

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

View File

@ -0,0 +1,172 @@

using System.Collections.Generic;
namespace UdonSharp.Serialization
{
public abstract class Serializer
{
protected readonly TypeSerializationMetadata typeMetadata;
protected Serializer(TypeSerializationMetadata typeMetadata)
{
this.typeMetadata = typeMetadata;
}
// Serializers that will be checked against the type, this list is ordered specifically based on priority, do not arbitrarily reorder it
private static readonly List<Serializer> _typeCheckSerializers = new List<Serializer>()
{
new JaggedArraySerializer<object>(null),
new ArraySerializer<object>(null),
// new UdonSharpBaseBehaviourSerializer(null),
new UdonSharpBehaviourSerializer(null),
new UnityObjectSerializer<UnityEngine.Object>(null),
new UserEnumSerializer<object>(null),
//new SystemObjectSerializer(null),
new DefaultSerializer<object>(null),
};
private static Dictionary<TypeSerializationMetadata, Serializer> _typeSerializerDictionary = new Dictionary<TypeSerializationMetadata, Serializer>();
protected static Serializer CreatePooled(TypeSerializationMetadata typeMetadata)
{
if (typeMetadata == null)
throw new System.ArgumentException("Type metadata cannot be null for serializer creation");
Serializer serializer;
lock (_pooledSerializerLock)
{
if (!_typeSerializerDictionary.TryGetValue(typeMetadata, out serializer))
{
serializer = Create(typeMetadata);
_typeSerializerDictionary.Add(typeMetadata, serializer);
}
}
return serializer;
}
public static Serializer<T> CreatePooled<T>()
{
Serializer val = CreatePooled(typeof(T));
return (Serializer<T>)val;
}
private static readonly object _pooledSerializerLock = new object();
private static TypeSerializationMetadata _lookupPooledTypeData = new TypeSerializationMetadata();
public static Serializer CreatePooled(System.Type type)
{
Serializer serializer;
lock (_pooledSerializerLock)
{
_lookupPooledTypeData.SetToType(type);
if (!_typeSerializerDictionary.TryGetValue(_lookupPooledTypeData, out serializer))
{
TypeSerializationMetadata typeMetadata = new TypeSerializationMetadata(type);
serializer = Create(typeMetadata);
_typeSerializerDictionary.Add(typeMetadata, serializer);
}
}
return serializer;
}
private static Serializer Create(TypeSerializationMetadata typeMetadata)
{
if (typeMetadata == null)
throw new System.ArgumentException("Type metadata cannot be null for serializer creation");
foreach (Serializer checkSerializer in _typeCheckSerializers)
{
if (checkSerializer.HandlesTypeSerialization(typeMetadata))
{
return checkSerializer.MakeSerializer(typeMetadata);
}
}
throw new System.Exception($"Failed to initialize a valid serializer for {typeMetadata}");
}
protected abstract Serializer MakeSerializer(TypeSerializationMetadata typeMetadata);
/// <summary>
/// Returns true if this serializer should be used for a given type, returns false otherwise.
/// </summary>
/// <param name="typeMetadata"></param>
/// <returns></returns>
protected abstract bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata);
/// <summary>
/// Serializes the source C# object directly into the target Udon object and attempt to avoid creating new objects when possible.
/// </summary>
/// <param name="targetObject"></param>
/// <param name="sourceObject"></param>
public abstract void WriteWeak(IValueStorage targetObject, object sourceObject);
/// <summary>
/// Serializes the source Udon object directly into the target C# object and attempt to avoid creating new objects when possible.
/// </summary>
/// <param name="targetObject"></param>
/// <param name="sourceObject"></param>
public abstract void ReadWeak(ref object targetObject, IValueStorage sourceObject);
/// <summary>
/// Verifies that this serializer is in the correct state to be using HandlesTypeSerialization()
/// </summary>
protected void VerifyTypeCheckSanity()
{
if (typeMetadata != null)
throw new System.Exception("Cannot call HandlesTypeSerialization() on object");
}
/// <summary>
/// Verifies that this serializer is in the correct state to be using the serialization methods
/// </summary>
protected void VerifySerializationSanity()
{
if (typeMetadata == null)
throw new System.Exception("Serializer is not in correct state to serialize data");
}
public abstract System.Type GetUdonStorageType();
}
public abstract class Serializer<T> : Serializer
{
protected Serializer(TypeSerializationMetadata typeMetadata)
:base(typeMetadata)
{
}
public abstract void Write(IValueStorage targetObject, in T sourceObject);
public override void WriteWeak(IValueStorage targetObject, object sourceObject)
{
T sourceObj = (T)sourceObject;
Write(targetObject, in sourceObj);
}
public abstract void Read(ref T targetObject, IValueStorage sourceObject);
public override void ReadWeak(ref object targetObject, IValueStorage sourceObject)
{
T outObj = default;
Read(ref outObj, sourceObject);
targetObject = outObj;
}
public virtual void Serialize(IValueStorage targetStorage, in T sourceObject)
{
Write(targetStorage, in sourceObject);
}
public virtual T Deserialize(IValueStorage sourceObject)
{
T output = default(T);
Read(ref output, sourceObject);
return output;
}
}
}

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 400fe840666d0ed4588b812d0d493b7f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,148 @@
using System;
using System.Collections.Concurrent;
using UnityEngine;
namespace UdonSharp.Serialization
{
internal class ArraySerializer<T> : Serializer<T[]>
{
private Serializer<T> elementSerializer;
private ConcurrentStack<IValueStorage> arrayStorages = new ConcurrentStack<IValueStorage>();
public ArraySerializer(TypeSerializationMetadata typeMetadata)
: base(typeMetadata)
{
if (typeMetadata == null)
return;
if (typeMetadata.arrayElementMetadata == null)
throw new ArgumentException("Array element metadata cannot be null on array type metadata");
elementSerializer = (Serializer<T>)CreatePooled(typeMetadata.arrayElementMetadata);
// If using the default serializer, we can just copy the array without iterating through each element.
if (elementSerializer is DefaultSerializer<T>)
{
elementSerializer = null;
}
}
private IValueStorage GetNextStorage()
{
if (arrayStorages.TryPop(out var storage))
return storage;
return ValueStorageUtil.CreateStorage(elementSerializer.GetUdonStorageType());
}
protected override bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return typeMetadata.cSharpType.IsArray && !typeMetadata.cSharpType.GetElementType().IsArray;
}
protected override Serializer MakeSerializer(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return (Serializer)Activator.CreateInstance(typeof(ArraySerializer<>).MakeGenericType(typeMetadata.cSharpType.GetElementType()), typeMetadata);
}
public override void Write(IValueStorage targetObject, in T[] sourceObject)
{
VerifySerializationSanity();
if (targetObject == null)
{
Debug.LogError($"Field of type '{typeof(T[]).Name}' does not exist any longer, compile U# scripts then allow Unity to compile assemblies to fix this");
return;
}
if (sourceObject == null)
{
targetObject.Value = null;
return;
}
Array targetArray = (Array)targetObject.Value;
if (targetArray == null || targetArray.Length != sourceObject.Length)
targetObject.Value = targetArray = (Array)Activator.CreateInstance(GetUdonStorageType(), sourceObject.Length);
if (elementSerializer == null)
{
if (!UsbSerializationContext.CollectDependencies)
Array.Copy(sourceObject, targetArray, targetArray.Length);
}
else
{
IValueStorage elementValueStorage = GetNextStorage();
for (int i = 0; i < sourceObject.Length; ++i)
{
elementValueStorage.Value = targetArray.GetValue(i);
elementSerializer.Write(elementValueStorage, in sourceObject[i]);
if (!UsbSerializationContext.CollectDependencies)
targetArray.SetValue(elementValueStorage.Value, i);
}
arrayStorages.Push(elementValueStorage);
}
}
public override void Read(ref T[] targetObject, IValueStorage sourceObject)
{
VerifySerializationSanity();
if (sourceObject == null)
{
Debug.LogError($"Field of type '{typeof(T[]).Name}' does not exist any longer, compile U# scripts then allow Unity to compile assemblies to fix this");
targetObject = null;
return;
}
if (sourceObject.Value == null)
{
targetObject = null;
return;
}
Array sourceArray = (Array)sourceObject.Value;
if (targetObject == null || targetObject.Length != sourceArray.Length)
{
targetObject = (T[])Activator.CreateInstance(typeMetadata.cSharpType, sourceArray.Length);
}
if (elementSerializer == null) // This type can just be serialized simply with a direct array copy. This prevents garbage from passing all the copies through an object.
{
if (!UsbSerializationContext.CollectDependencies)
Array.Copy(sourceArray, targetObject, sourceArray.Length);
}
else // The elements need special handling so use the element serializer
{
IValueStorage elementValueStorage = GetNextStorage();
for (int i = 0; i < sourceArray.Length; ++i)
{
T elementObj = targetObject[i];
elementValueStorage.Value = sourceArray.GetValue(i);
elementSerializer.Read(ref elementObj, elementValueStorage);
if (!UsbSerializationContext.CollectDependencies)
targetObject[i] = elementObj;
}
arrayStorages.Push(elementValueStorage);
}
}
public override Type GetUdonStorageType()
{
return UdonSharpUtils.UserTypeToUdonType(typeof(T[]));
}
}
}

View File

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

View File

@ -0,0 +1,77 @@
using System;
using UnityEngine;
namespace UdonSharp.Serialization
{
internal class DefaultSerializer<T> : Serializer<T>
{
public DefaultSerializer(TypeSerializationMetadata typeMetadata)
: base(typeMetadata)
{
}
public override Type GetUdonStorageType()
{
return typeof(T);
}
protected override bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return true;
}
public override void Read(ref T targetObject, IValueStorage sourceObject)
{
VerifySerializationSanity();
if (sourceObject == null)
{
Debug.LogError($"Field for {typeof(T)} does not exist");
return;
}
ValueStorage<T> storage = sourceObject as ValueStorage<T>;
if (storage == null)
{
Debug.LogError($"Type {typeof(T)} not compatible with serializer {sourceObject}");
return;
}
if (UsbSerializationContext.CollectDependencies)
return;
targetObject = storage.Value;
}
public override void Write(IValueStorage targetObject, in T sourceObject)
{
VerifySerializationSanity();
if (targetObject == null)
{
Debug.LogError($"Field for {typeof(T)} does not exist");
return;
}
ValueStorage<T> storage = targetObject as ValueStorage<T>;
if (storage == null)
{
Debug.LogError($"Type {typeof(T)} not compatible with serializer {targetObject}");
return;
}
if (UsbSerializationContext.CollectDependencies)
return;
storage.Value = sourceObject;
}
protected override Serializer MakeSerializer(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return (Serializer)Activator.CreateInstance(typeof(DefaultSerializer<>).MakeGenericType(typeMetadata.cSharpType), typeMetadata);
}
}
}

View File

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

View File

@ -0,0 +1,182 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.Serialization;
namespace UdonSharp.Serialization
{
internal class JaggedArraySerializer<T> : Serializer<T>
{
private Serializer rootArraySerializer;
private ConcurrentStack<IValueStorage> innerValueStorages = new ConcurrentStack<IValueStorage>();
public JaggedArraySerializer(TypeSerializationMetadata typeMetadata)
: base(typeMetadata)
{
if (typeMetadata == null)
return;
if (!typeMetadata.cSharpType.GetElementType().IsArray)
throw new SerializationException($"Cannot convert {typeMetadata.udonStorageType} to {typeMetadata.cSharpType}");
if (typeMetadata.arrayElementMetadata == null)
throw new ArgumentException("Array element metadata cannot be null on array type metadata");
rootArraySerializer = CreatePooled(new TypeSerializationMetadata(typeMetadata.arrayElementMetadata.cSharpType.MakeArrayType()) { arrayElementMetadata = typeMetadata.arrayElementMetadata });
int arrayDepth = 0;
Type arrayType = typeMetadata.cSharpType;
while (arrayType.IsArray)
{
arrayDepth++;
arrayType = arrayType.GetElementType();
}
if (arrayDepth <= 1)
throw new SerializationException("Jagged array serializer must run on jagged arrays.");
}
private IValueStorage GetInnerValueStorage()
{
if (innerValueStorages.TryPop(out var storage))
return storage;
return ValueStorageUtil.CreateStorage(rootArraySerializer.GetUdonStorageType());
}
protected override bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return typeMetadata.cSharpType.IsArray && typeMetadata.cSharpType.GetElementType().IsArray;
}
private void ConvertToCSharpArrayElement(ref object targetElement, object elementValue, Type cSharpType)
{
if (elementValue == null)
{
targetElement = null;
return;
}
if (UdonSharpUtils.IsUserJaggedArray(cSharpType))
{
Array targetArray = (Array)targetElement;
Array sourceArray = (Array)elementValue;
if (!UsbSerializationContext.CollectDependencies)
{
if (targetArray == null || targetArray.Length != sourceArray.Length)
targetElement = targetArray = (Array)Activator.CreateInstance(cSharpType, sourceArray.Length);
}
for (int i = 0; i < sourceArray.Length; ++i)
{
object elementVal = targetArray.GetValue(i);
ConvertToCSharpArrayElement(ref elementVal, sourceArray.GetValue(i), cSharpType.GetElementType());
if (!UsbSerializationContext.CollectDependencies)
targetArray.SetValue(elementVal, i);
}
}
else if (cSharpType.IsArray)
{
IValueStorage innerArrayValueStorage = GetInnerValueStorage();
innerArrayValueStorage.Value = elementValue;
rootArraySerializer.ReadWeak(ref targetElement, innerArrayValueStorage);
innerValueStorages.Push(innerArrayValueStorage);
}
else
{
throw new Exception("Jagged array serializer requires a root array serializer");
}
}
private void ConvertToUdonArrayElement(ref object targetElement, object elementValue, Type cSharpType)
{
if (elementValue == null)
{
targetElement = null;
return;
}
if (UdonSharpUtils.IsUserJaggedArray(cSharpType))
{
Array targetArray = (Array)targetElement;
Array sourceArray = (Array)elementValue;
if (targetArray == null || targetArray.Length != sourceArray.Length)
targetElement = targetArray = (Array)Activator.CreateInstance(UdonSharpUtils.UserTypeToUdonType(cSharpType), sourceArray.Length);
for (int i = 0; i < sourceArray.Length; ++i)
{
object elementVal = targetArray.GetValue(i);
ConvertToUdonArrayElement(ref elementVal, sourceArray.GetValue(i), cSharpType.GetElementType());
if (!UsbSerializationContext.CollectDependencies)
targetArray.SetValue(elementVal, i);
}
}
else if (cSharpType.IsArray)
{
IValueStorage innerArrayValueStorage = GetInnerValueStorage();
innerArrayValueStorage.Value = targetElement;
rootArraySerializer.WriteWeak(innerArrayValueStorage, elementValue);
targetElement = innerArrayValueStorage.Value;
innerValueStorages.Push(innerArrayValueStorage);
}
else
{
throw new Exception("Jagged array serializer requires a root array serializer");
}
}
public override void ReadWeak(ref object targetObject, IValueStorage sourceObject)
{
VerifySerializationSanity();
//if (sourceObject != null && !UdonSharpUtils.IsUserJaggedArray(sourceObject.GetType()))
// throw new SerializationException($"Cannot convert {targetObject.GetType()} to {typeMetadata.cSharpType}");
ConvertToCSharpArrayElement(ref targetObject, sourceObject.Value, typeMetadata.cSharpType);
}
public override void WriteWeak(IValueStorage targetObject, object sourceObject)
{
VerifySerializationSanity();
if (sourceObject != null && !UdonSharpUtils.IsUserJaggedArray(sourceObject.GetType()))
throw new SerializationException($"Cannot convert {targetObject.GetType()} to {typeMetadata.cSharpType}");
object tarArray = targetObject.Value;
ConvertToUdonArrayElement(ref tarArray, sourceObject, typeMetadata.cSharpType);
targetObject.Value = tarArray;
}
public override void Write(IValueStorage targetObject, in T sourceObject)
{
WriteWeak(targetObject, sourceObject);
}
public override void Read(ref T targetObject, IValueStorage sourceObject)
{
object target = targetObject;
ReadWeak(ref target, sourceObject);
targetObject = (T)target;
}
public override Type GetUdonStorageType()
{
return typeMetadata.udonStorageType;
}
protected override Serializer MakeSerializer(TypeSerializationMetadata typeMetadata)
{
return (Serializer)Activator.CreateInstance(typeof(JaggedArraySerializer<>).MakeGenericType(typeMetadata.cSharpType), typeMetadata);
}
}
}

View File

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

View File

@ -0,0 +1,115 @@

using System;
using System.Collections.Concurrent;
using UnityEngine;
namespace UdonSharp.Serialization
{
internal class SystemObjectSerializer : Serializer<object>
{
private static ConcurrentDictionary<Type, ConcurrentStack<IValueStorage>> _objectValueStorageStack =
new ConcurrentDictionary<Type, ConcurrentStack<IValueStorage>>();
public SystemObjectSerializer(TypeSerializationMetadata typeMetadata)
: base(typeMetadata)
{
}
public override Type GetUdonStorageType()
{
return typeof(object);
}
protected override bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return typeMetadata.cSharpType == typeof(object);
}
public override void Read(ref object targetObject, IValueStorage sourceObject)
{
VerifySerializationSanity();
if (sourceObject == null)
{
Debug.LogError($"Field for {typeof(object)} does not exist");
return;
}
ValueStorage<object> storage = sourceObject as ValueStorage<object>;
if (storage == null)
{
Debug.LogError($"Type {typeof(object)} not compatible with serializer {sourceObject}");
return;
}
if (UsbSerializationContext.CollectDependencies)
return;
if (sourceObject.Value == null ||
(sourceObject.Value is UnityEngine.Object unityObject && unityObject == null))
{
targetObject = null;
return;
}
Serializer serializer = CreatePooled(sourceObject.Value.GetType());
Type valueStorageType = serializer.GetUdonStorageType();
ConcurrentStack<IValueStorage> varStorageStack = _objectValueStorageStack.GetOrAdd(valueStorageType,(type) => new ConcurrentStack<IValueStorage>());
if (!varStorageStack.TryPop(out var valueStorage))
valueStorage = (IValueStorage)Activator.CreateInstance(typeof(SimpleValueStorage<>).MakeGenericType(valueStorageType), sourceObject.Value);
serializer.ReadWeak(ref targetObject, valueStorage);
varStorageStack.Push(valueStorage);
}
public override void Write(IValueStorage targetObject, in object sourceObject)
{
VerifySerializationSanity();
if (targetObject == null)
{
Debug.LogError($"Field for {typeof(object)} does not exist");
return;
}
ValueStorage<object> storage = targetObject as ValueStorage<object>;
if (storage == null)
{
Debug.LogError($"Type {typeof(object)} not compatible with serializer {targetObject}");
return;
}
if (UsbSerializationContext.CollectDependencies)
return;
if (sourceObject == null ||
(sourceObject is UnityEngine.Object unityObject && unityObject == null))
{
targetObject.Value = null;
return;
}
Serializer serializer = CreatePooled(targetObject.Value.GetType());
Type valueStorageType = serializer.GetUdonStorageType();
ConcurrentStack<IValueStorage> varStorageStack = _objectValueStorageStack.GetOrAdd(valueStorageType,(type) => new ConcurrentStack<IValueStorage>());
if (!varStorageStack.TryPop(out var valueStorage))
valueStorage = (IValueStorage)Activator.CreateInstance(typeof(SimpleValueStorage<>).MakeGenericType(valueStorageType), targetObject.Value);
serializer.WriteWeak(valueStorage, sourceObject);
targetObject.Value = valueStorage.Value;
varStorageStack.Push(valueStorage);
}
protected override Serializer MakeSerializer(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return new SystemObjectSerializer(typeMetadata);
}
}
}

View File

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

View File

@ -0,0 +1,67 @@
using System;
using VRC.Udon;
namespace UdonSharp.Serialization
{
internal class UdonSharpBaseBehaviourSerializer : Serializer<UdonSharpBehaviour>
{
public UdonSharpBaseBehaviourSerializer(TypeSerializationMetadata typeMetadata)
: base(typeMetadata)
{
}
public override Type GetUdonStorageType()
{
return typeof(UdonBehaviour);
}
protected override bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return typeMetadata.cSharpType == typeof(UdonSharpBehaviour);
}
public override void Read(ref UdonSharpBehaviour targetObject, IValueStorage sourceObject)
{
VerifySerializationSanity();
UdonBehaviour sourceBehaviour = (UdonBehaviour)sourceObject.Value;
if (sourceBehaviour == null)
{
targetObject = null;
return;
}
Type behaviourType = UdonSharpProgramAsset.GetBehaviourClass(sourceBehaviour);
Serializer behaviourSerializer = CreatePooled(behaviourType);
object behaviourRef = targetObject;
behaviourSerializer.ReadWeak(ref behaviourRef, sourceObject);
targetObject = (UdonSharpBehaviour)behaviourRef;
}
public override void Write(IValueStorage targetObject, in UdonSharpBehaviour sourceObject)
{
VerifySerializationSanity();
if (sourceObject == null)
{
targetObject.Value = null;
return;
}
Serializer behaviourSerializer = CreatePooled(sourceObject.GetType());
behaviourSerializer.WriteWeak(targetObject, sourceObject);
}
protected override Serializer MakeSerializer(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return new UdonSharpBaseBehaviourSerializer(typeMetadata);
}
}
}

View File

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

View File

@ -0,0 +1,215 @@

using System;
using System.Collections.Generic;
using UdonSharpEditor;
using UnityEngine;
using VRC.Udon;
using Object = UnityEngine.Object;
namespace UdonSharp.Serialization
{
/// <summary>
/// UdonSharpBehaviour Serialization Context, confusing abbreviation isn't it?
/// </summary>
internal static class UsbSerializationContext
{
public static readonly HashSet<UdonSharpBehaviour> SerializedBehaviourSet = new HashSet<UdonSharpBehaviour>();
public static ProxySerializationPolicy CurrentPolicy;
public static int CurrentDepth;
public static HashSet<UnityEngine.Object> Dependencies = new HashSet<Object>();
public static readonly object UsbLock = new object();
public static bool CollectDependencies => CurrentPolicy?.CollectDependencies ?? false;
public static bool UseHeapSerialization => CollectDependencies || (CurrentPolicy?.IsPreBuildSerialize ?? false);
}
internal class UdonSharpBehaviourSerializer : Serializer<UdonSharpBehaviour>
{
public UdonSharpBehaviourSerializer(TypeSerializationMetadata typeMetadata)
: base(typeMetadata)
{
}
public override Type GetUdonStorageType()
{
return typeof(UdonBehaviour);
}
protected override bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata)
{
return typeMetadata.cSharpType == typeof(UdonSharpBehaviour) || typeMetadata.cSharpType.IsSubclassOf(typeof(UdonSharpBehaviour));
}
public override void Read(ref UdonSharpBehaviour targetObject, IValueStorage sourceObject)
{
UdonBehaviour sourceBehaviour = (UdonBehaviour)sourceObject.Value;
if (sourceBehaviour == null)
{
if (!UsbSerializationContext.CollectDependencies)
targetObject = null;
return;
}
lock (UsbSerializationContext.UsbLock)
{
if (UsbSerializationContext.CurrentPolicy == null)
throw new NullReferenceException("Serialization policy cannot be null");
if (UsbSerializationContext.CollectDependencies)
UsbSerializationContext.Dependencies.Add(sourceBehaviour);
targetObject = UdonSharpEditorUtility.GetProxyBehaviour(sourceBehaviour);
if (UsbSerializationContext.CurrentDepth >= UsbSerializationContext.CurrentPolicy.MaxSerializationDepth)
return;
if (UsbSerializationContext.SerializedBehaviourSet.Contains(targetObject))
return;
UsbSerializationContext.SerializedBehaviourSet.Add(targetObject);
UsbSerializationContext.CurrentDepth++;
try
{
Type behaviourType = UdonSharpProgramAsset.GetBehaviourClass(sourceBehaviour);
IFormatter formatter = UdonSharpBehaviourFormatterEmitter.GetFormatter(behaviourType);
object targetSysObj = targetObject;
formatter.Read(ref targetSysObj, sourceObject);
if (!UsbSerializationContext.CollectDependencies)
targetObject = (UdonSharpBehaviour)targetSysObj;
}
finally
{
UsbSerializationContext.CurrentDepth--;
if (UsbSerializationContext.CurrentDepth <= 0)
{
Debug.Assert(UsbSerializationContext.CurrentDepth == 0,
"Serialization depth cannot be negative");
UsbSerializationContext.SerializedBehaviourSet.Clear();
}
}
}
}
public override void Write(IValueStorage targetObject, in UdonSharpBehaviour sourceObject)
{
if (sourceObject == null)
{
if (!UsbSerializationContext.CollectDependencies)
targetObject.Value = null;
return;
}
lock (UsbSerializationContext.UsbLock)
{
if (UsbSerializationContext.CurrentPolicy == null)
throw new NullReferenceException("Serialization policy cannot be null");
if (UsbSerializationContext.CollectDependencies)
UsbSerializationContext.Dependencies.Add(sourceObject);
UdonBehaviour backingBehaviour = UdonSharpEditorUtility.GetBackingUdonBehaviour(sourceObject);
if (UsbSerializationContext.CurrentDepth >= UsbSerializationContext.CurrentPolicy.MaxSerializationDepth)
{
if (!UsbSerializationContext.CollectDependencies)
targetObject.Value = backingBehaviour ? backingBehaviour : null;
return;
}
UsbSerializationContext.CurrentDepth++;
try
{
if (!UsbSerializationContext.CollectDependencies)
{
if (backingBehaviour)
{
targetObject.Value = backingBehaviour;
}
else
{
targetObject.Value = null;
}
}
if (UsbSerializationContext.SerializedBehaviourSet.Contains(sourceObject))
return;
UsbSerializationContext.SerializedBehaviourSet.Add(sourceObject);
IFormatter formatter = UdonSharpBehaviourFormatterEmitter.GetFormatter(sourceObject.GetType());
formatter.Write(targetObject, sourceObject);
}
finally
{
UsbSerializationContext.CurrentDepth--;
if (UsbSerializationContext.CurrentDepth <= 0)
{
Debug.Assert(UsbSerializationContext.CurrentDepth == 0,
"Serialization depth cannot be negative");
UsbSerializationContext.SerializedBehaviourSet.Clear();
}
}
}
}
protected override Serializer MakeSerializer(TypeSerializationMetadata typeMetadata)
{
Serializer innerSerializer = (Serializer)Activator.CreateInstance(typeof(UdonSharpBehaviourSerializer), typeMetadata);
return (Serializer)Activator.CreateInstance(typeof(UdonSharpBehaviourTypedWrapper<>).MakeGenericType(typeMetadata.cSharpType), typeMetadata, innerSerializer);
}
private class UdonSharpBehaviourTypedWrapper<T> : Serializer<T> where T : UdonSharpBehaviour
{
private readonly UdonSharpBehaviourSerializer _innerSerializer;
public UdonSharpBehaviourTypedWrapper(TypeSerializationMetadata typeMetadata, UdonSharpBehaviourSerializer innerSerializer)
:base(typeMetadata)
{
_innerSerializer = innerSerializer;
}
protected override Serializer MakeSerializer(TypeSerializationMetadata typeMetadata)
{
throw new NotImplementedException();
}
protected override bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata)
{
throw new NotImplementedException();
}
public override Type GetUdonStorageType()
{
return _innerSerializer.GetUdonStorageType();
}
public override void Write(IValueStorage targetObject, in T sourceObject)
{
_innerSerializer.Serialize(targetObject, sourceObject);
}
public override void Read(ref T targetObject, IValueStorage sourceObject)
{
UdonSharpBehaviour refObj = targetObject;
_innerSerializer.Read(ref refObj, sourceObject);
targetObject = (T)refObj;
}
}
}
}

View File

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

View File

@ -0,0 +1,127 @@

using System;
namespace UdonSharp.Serialization
{
internal class UnityObjectSerializer<T> : Serializer<T> where T : UnityEngine.Object
{
public UnityObjectSerializer(TypeSerializationMetadata typeMetadata)
: base(typeMetadata)
{
}
public override Type GetUdonStorageType()
{
return typeof(T);
}
protected override bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return typeMetadata.cSharpType == typeof(UnityEngine.Object) || typeMetadata.cSharpType.IsSubclassOf(typeof(UnityEngine.Object));
}
public override void Read(ref T targetObject, IValueStorage sourceObject)
{
VerifySerializationSanity();
if (sourceObject == null)
{
UdonSharpUtils.LogError($"Field for {typeof(T)} does not exist");
return;
}
if (UsbSerializationContext.CollectDependencies)
{
if (sourceObject.Value is UnityEngine.Object unityObject && unityObject != null)
UsbSerializationContext.Dependencies.Add(unityObject);
return;
}
IValueStorage storage = sourceObject as ValueStorage<T>;
if (storage == null)
{
Type storageType = sourceObject.GetType().GetGenericArguments()[0];
if (typeof(T).IsSubclassOf(storageType))
{
storage = sourceObject;
}
else if (targetObject != null && targetObject.GetType().IsAssignableFrom(storageType))
{
storage = sourceObject;
}
else if (targetObject == null && storageType.IsSubclassOf(typeof(T)))
{
storage = sourceObject;
}
else
{
UdonSharpUtils.LogError($"Type {typeof(T)} not compatible with serializer {sourceObject}");
return;
}
}
targetObject = (T)storage.Value;
}
public override void Write(IValueStorage targetObject, in T sourceObject)
{
VerifySerializationSanity();
if (targetObject == null)
{
UdonSharpUtils.LogError($"Field for {typeof(T)} does not exist");
return;
}
if (UsbSerializationContext.CollectDependencies)
{
if (sourceObject != null)
UsbSerializationContext.Dependencies.Add(sourceObject);
return;
}
IValueStorage storage = targetObject as ValueStorage<T>;
if (storage == null)
{
Type storageType = targetObject.GetType().GetGenericArguments()[0];
if (typeof(T).IsSubclassOf(storageType))
{
storage = targetObject;
}
else if (sourceObject != null && storageType.IsInstanceOfType(sourceObject))
{
storage = targetObject;
}
else if (sourceObject == null && storageType.IsSubclassOf(typeof(T)))
{
storage = targetObject;
}
else
{
UdonSharpUtils.LogError($"Type {typeof(T)} not compatible with serializer {targetObject}");
return;
}
}
// This is checking for UnityEngine.Object's special "null" which is not actually null
// If we allow it to assign the fake "null", Udon can run into issues when attempting to reference fake "null" values since they are intended to be referenced by the proxy object
// So if the null check passes, this value is either a real null or a fake null, and we assign a real null in either case
if (sourceObject == null)
storage.Value = null;
else
storage.Value = sourceObject;
}
protected override Serializer MakeSerializer(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return (Serializer)Activator.CreateInstance(typeof(UnityObjectSerializer<>).MakeGenericType(typeMetadata.cSharpType), typeMetadata);
}
}
}

View File

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

View File

@ -0,0 +1,65 @@
using System;
using UdonSharp.Compiler.Udon;
using UnityEngine;
namespace UdonSharp.Serialization
{
internal class UserEnumSerializer<T> : Serializer<T>
{
public UserEnumSerializer(TypeSerializationMetadata typeMetadata)
: base(typeMetadata)
{
}
public override Type GetUdonStorageType()
{
return typeof(T).GetEnumUnderlyingType();
}
protected override bool HandlesTypeSerialization(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return typeMetadata.cSharpType.IsEnum && !CompilerUdonInterface.IsExternType(typeMetadata.cSharpType);
}
public override void Read(ref T targetObject, IValueStorage sourceObject)
{
VerifySerializationSanity();
if (sourceObject == null)
{
Debug.LogError($"Field for {typeof(T)} does not exist");
return;
}
if (UsbSerializationContext.CollectDependencies)
return;
targetObject = (T)Enum.ToObject(typeof(T), sourceObject.Value);
}
public override void Write(IValueStorage targetObject, in T sourceObject)
{
VerifySerializationSanity();
if (targetObject == null)
{
Debug.LogError($"Field for {typeof(T)} does not exist");
return;
}
if (UsbSerializationContext.CollectDependencies)
return;
targetObject.Value = Convert.ChangeType(sourceObject, GetUdonStorageType());
}
protected override Serializer MakeSerializer(TypeSerializationMetadata typeMetadata)
{
VerifyTypeCheckSanity();
return (Serializer)Activator.CreateInstance(typeof(UserEnumSerializer<>).MakeGenericType(typeMetadata.cSharpType), typeMetadata);
}
}
}

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 146023a4560fa7447a05c6c9b94b351b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,97 @@

namespace UdonSharp.Serialization
{
public interface IHeapStorage
{
/// <summary>
/// Sets an element value in the storage interface, the storage interface must be a collection interface
/// </summary>
/// <param name="elementKey"></param>
/// <param name="value"></param>
void SetElementValueWeak(string elementKey, object value);
/// <summary>
/// Get an element value
/// </summary>
/// <param name="elementKey"></param>
/// <returns></returns>
object GetElementValueWeak(string elementKey);
T GetElementValue<T>(string elementKey);
void SetElementValue<T>(string elementKey, T value);
IValueStorage GetElementStorage(string elementKey);
}
public interface IValueStorage
{
object Value { get; set; }
}
public abstract class ValueStorage<T> : IValueStorage
{
public abstract T Value { get; set; }
object IValueStorage.Value
{
get => Value;
set
{
try
{
Value = (T)value;
}
catch (System.InvalidCastException)
{
Value = default;
UnityEngine.Debug.LogWarning($"Failed to assign element in storage, could not cast from '{value.GetType()}' to '{typeof(T)}'. Assigning default value.");
}
}
}
public System.Type ValueType => typeof(T);
}
/// <summary>
/// This variant of ValueStorage acts like StrongBox<T> more or less
/// More complex implementations of ValueStorage<T> can do things like reference an element in an IHeapStorageInterface
/// </summary>
public class SimpleValueStorage<T> : ValueStorage<T>
{
T _value;
public override T Value { get => _value; set => _value = value; }
public SimpleValueStorage()
{
_value = default;
}
public SimpleValueStorage(T value)
{
_value = value;
}
}
public static class ValueStorageUtil
{
public static IValueStorage CreateStorage(System.Type storageType)
{
return (IValueStorage)System.Activator.CreateInstance(typeof(SimpleValueStorage<>).MakeGenericType(storageType));
}
public static IValueStorage CreateStorage(System.Type storageType, object value)
{
return (IValueStorage)System.Activator.CreateInstance(typeof(SimpleValueStorage<>).MakeGenericType(storageType), value);
}
public static IValueStorage CreateStorage<T>(T value)
{
ValueStorage<T> valueStorage = (ValueStorage<T>)System.Activator.CreateInstance(typeof(SimpleValueStorage<T>));
valueStorage.Value = value;
return valueStorage;
}
}
}

View File

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

View File

@ -0,0 +1,140 @@

using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using VRC.Udon;
using VRC.Udon.Common.Interfaces;
namespace UdonSharp.Serialization
{
public class UdonHeapStorageInterface : IHeapStorage
{
private class UdonHeapValueStorage<T> : ValueStorage<T>
{
private IUdonHeap heap;
private uint symbolAddress;
public UdonHeapValueStorage(IUdonHeap heap, IUdonSymbolTable symbolTable, string symbolKey)
{
this.heap = heap;
bool isValid = symbolTable.TryGetAddressFromSymbol(UdonSharpUtils.UnmanglePropertyFieldName(symbolKey), out symbolAddress) &&
heap.GetHeapVariableType(symbolAddress) == typeof(T) &&
heap.TryGetHeapVariable<T>(symbolAddress, out _);
if (!isValid)
symbolAddress = 0xFFFFFFFF;
}
public override T Value
{
get => symbolAddress == 0xFFFFFFFF ? default : heap.GetHeapVariable<T>(symbolAddress);
set
{
if (symbolAddress == 0xFFFFFFFF)
return;
if (UsbSerializationContext.CollectDependencies)
return;
heap.SetHeapVariable<T>(symbolAddress, value);
}
}
}
private UdonBehaviour behaviour;
private IUdonHeap heap;
private IUdonSymbolTable symbolTable;
private List<IValueStorage> heapValueRefs = new List<IValueStorage>();
public bool IsValid { get; }
private static readonly FieldInfo _programField = typeof(UdonBehaviour).GetField("_program", BindingFlags.NonPublic | BindingFlags.Instance);
public UdonHeapStorageInterface(UdonBehaviour udonBehaviour)
{
behaviour = udonBehaviour;
IUdonProgram sourceProgram = (IUdonProgram)_programField.GetValue(udonBehaviour);
if (sourceProgram != null)
{
heap = sourceProgram.Heap;
symbolTable = sourceProgram.SymbolTable;
IsValid = true;
}
else
{
IsValid = false;
}
}
void IHeapStorage.SetElementValue<T>(string elementKey, T value)
{
if (symbolTable.TryGetAddressFromSymbol(elementKey, out uint symbolAddress))
{
var symbolType = heap.GetHeapVariableType(symbolAddress);
if (symbolType.IsAssignableFrom(typeof(T)))
{
heap.SetHeapVariable<T>(symbolAddress, value);
}
}
}
T IHeapStorage.GetElementValue<T>(string elementKey)
{
if (symbolTable.TryGetAddressFromSymbol(elementKey, out uint symbolAddress))
{
var symbolType = heap.GetHeapVariableType(symbolAddress);
if (symbolType.IsAssignableFrom(typeof(T)))
{
return heap.GetHeapVariable<T>(symbolAddress);
}
}
return default;
}
public void SetElementValueWeak(string elementKey, object value)
{
if (symbolTable.TryGetAddressFromSymbol(elementKey, out uint symbolAddress))
{
var symbolType = heap.GetHeapVariableType(symbolAddress);
if (symbolType.IsInstanceOfType(value))
{
heap.SetHeapVariable(symbolAddress, value, symbolType);
}
}
}
public object GetElementValueWeak(string elementKey)
{
if (symbolTable.TryGetAddressFromSymbol(elementKey, out uint symbolAddress))
{
return heap.GetHeapVariable(symbolAddress);
}
return null;
}
public IValueStorage GetElementStorage(string elementKey)
{
UdonSharpProgramAsset programAsset = (UdonSharpProgramAsset)behaviour.programSource;
if (!programAsset.fieldDefinitions.TryGetValue(elementKey, out Compiler.FieldDefinition fieldDefinition))
{
Debug.LogError($"Could not find definition for field {elementKey}");
return null;
}
IValueStorage udonHeapValue = (IValueStorage)System.Activator.CreateInstance(typeof(UdonHeapValueStorage<>).MakeGenericType(fieldDefinition.SystemType), heap, symbolTable, elementKey);
heapValueRefs.Add(udonHeapValue);
return udonHeapValue;
}
}
}

View File

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

View File

@ -0,0 +1,167 @@

using System;
using System.Collections.Generic;
using UdonSharp.Compiler;
using UnityEngine;
using VRC.Udon;
using VRC.Udon.Common;
using VRC.Udon.Common.Interfaces;
using Object = UnityEngine.Object;
namespace UdonSharp.Serialization
{
public class UdonVariableStorageInterface : IHeapStorage
{
private class VariableValueStorage<T> : ValueStorage<T>
{
private string elementKey;
private UdonBehaviour behaviour;
public VariableValueStorage(string elementKey, UdonBehaviour behaviour)
{
this.elementKey = elementKey;
this.behaviour = behaviour;
}
public override T Value
{
get => GetVariable<T>(behaviour, elementKey);
set => SetVariable<T>(behaviour, elementKey, value);
}
}
private static void SetVarInternal<T>(UdonBehaviour behaviour, string variableKey, T value)
{
variableKey = UdonSharpUtils.UnmanglePropertyFieldName(variableKey);
if (behaviour.publicVariables.TrySetVariableValue<T>(variableKey, value))
return;
UdonVariable<T> varVal = new UdonVariable<T>(variableKey, value);
if (!behaviour.publicVariables.TryAddVariable(varVal))
{
if (!behaviour.publicVariables.RemoveVariable(variableKey) || !behaviour.publicVariables.TryAddVariable(varVal)) // Fallback in case the value already exists for some reason
Debug.LogError($"Could not write variable '{variableKey}' to public variables on UdonBehaviour");
else
Debug.LogWarning($"Storage for variable '{variableKey}' of type '{typeof(T)}' did not match, updated storage type");
}
}
private static void SetVariable<T>(UdonBehaviour behaviour, string variableKey, T value)
{
if (UsbSerializationContext.CollectDependencies)
return;
Type type = typeof(T);
bool isNull = (value is Object unityEngineObject && unityEngineObject == null) || value == null;
if (isNull)
{
bool isRemoveType = (type == typeof(GameObject) ||
type == typeof(Transform) ||
type == typeof(UdonBehaviour));
if (isRemoveType)
behaviour.publicVariables.RemoveVariable(variableKey);
else
SetVarInternal<T>(behaviour, variableKey, value);
}
else
{
SetVarInternal<T>(behaviour, variableKey, value);
}
}
private static T GetVariable<T>(UdonBehaviour behaviour, string variableKey)
{
if (behaviour.publicVariables.TryGetVariableValue<T>(variableKey, out T output))
return output;
// The type no longer matches exactly, but is trivially convertible
// This will usually flow into a reassignment of the public variable type in SetVarInternal() when the value gets copied back to Udon
if (behaviour.publicVariables.TryGetVariableValue(variableKey, out object outputObj) && !outputObj.IsUnityObjectNull() && outputObj is T obj)
return obj;
// Try to get the default value if there's no custom value specified
// We don't care about default values when we're doing dependency checks because the default heap currently does not contain any UnityEngine.Object references.
// This may change in the future though, so we may want to look at caching only the 'serialized' heap values if that ever becomes the case.
if (behaviour.programSource != null && behaviour.programSource is UdonSharpProgramAsset udonSharpProgramAsset && !UsbSerializationContext.CollectDependencies && !UsbSerializationContext.UseHeapSerialization)
{
udonSharpProgramAsset.UpdateProgram();
IUdonProgram program = udonSharpProgramAsset.GetRealProgram();
if (program.SymbolTable.TryGetAddressFromSymbol(variableKey, out uint varAddress))
{
if (program.Heap.TryGetHeapVariable<T>(varAddress, out output))
return output;
}
}
return default;
}
private UdonBehaviour udonBehaviour;
private static Dictionary<UdonSharpProgramAsset, Dictionary<string, Type>> _variableTypeLookup = new Dictionary<UdonSharpProgramAsset, Dictionary<string, Type>>();
private Type GetElementType(string elementKey)
{
UdonSharpProgramAsset programAsset = (UdonSharpProgramAsset)udonBehaviour.programSource;
if (!_variableTypeLookup.TryGetValue(programAsset, out var programTypeLookup))
{
programTypeLookup = new Dictionary<string, Type>();
foreach (FieldDefinition def in programAsset.fieldDefinitions.Values)
{
// if (def.fieldSymbol.declarationType.HasFlag(SymbolDeclTypeFlags.Public) || def.fieldSymbol.declarationType.HasFlag(SymbolDeclTypeFlags.Private))
programTypeLookup.Add(def.Name, def.SystemType);
}
_variableTypeLookup.Add(programAsset, programTypeLookup);
}
if (!programTypeLookup.TryGetValue(elementKey, out var fieldType))
return null;
return fieldType;
}
public UdonVariableStorageInterface(UdonBehaviour udonBehaviour)
{
this.udonBehaviour = udonBehaviour;
}
public IValueStorage GetElementStorage(string elementKey)
{
Type elementType = GetElementType(elementKey);
if (elementType == null)
return null;
return (IValueStorage)Activator.CreateInstance(typeof(VariableValueStorage<>).MakeGenericType(elementType), elementKey, udonBehaviour);
}
public object GetElementValueWeak(string elementKey)
{
udonBehaviour.publicVariables.TryGetVariableValue(elementKey, out object valueOut);
return valueOut;
}
public T GetElementValue<T>(string elementKey)
{
if (udonBehaviour.publicVariables.TryGetVariableValue<T>(elementKey, out T variableVal))
return variableVal;
return default;
}
public void SetElementValueWeak(string elementKey, object value)
{
udonBehaviour.publicVariables.TrySetVariableValue(elementKey, value);
}
public void SetElementValue<T>(string elementKey, T value)
{
udonBehaviour.publicVariables.TrySetVariableValue<T>(elementKey, value);
}
}
}

View File

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

View File

@ -0,0 +1,145 @@

using System;
using System.Collections.Generic;
namespace UdonSharp.Serialization
{
/// <summary>
/// Version for type serialization.
/// ONLY add versions where the comment below dictates, if you add them anywhere else, you will break serialization.
/// </summary>
[Serializable]
public enum TypeSerializationVersion
{
Default,
V1Serialization,
// Add new versions before this line
// DO NOT touch these and don't add anything after them
// ReSharper disable once InconsistentNaming
__MostRecentVerIntnl,
LatestVer = __MostRecentVerIntnl - 1,
}
[Serializable]
public class FieldSerializationMetadata
{
/// <summary>
/// The name of the field, used for mapping fields between changes in serialized data
/// </summary>
public string fieldName;
/// <summary>
/// The index that the field is currently stored in inside the parent type
/// If the index is negative, it means that this field is stored directly in a UdonBehaviour and does not need to know its corresponding index since it can be looked up by name
/// </summary>
public int fieldStorageIdx = -1;
/// <summary>
/// The type data for this field
/// </summary>
public TypeSerializationMetadata fieldTypeMetadata;
public override bool Equals(object obj)
{
return obj is FieldSerializationMetadata metadata &&
fieldStorageIdx == metadata.fieldStorageIdx &&
fieldName == metadata.fieldName &&
EqualityComparer<TypeSerializationMetadata>.Default.Equals(fieldTypeMetadata, metadata.fieldTypeMetadata);
}
// todo: this hash code should be built on construction
public override int GetHashCode()
{
var hashCode = -1973968215;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(fieldName);
hashCode = hashCode * -1521134295 + fieldStorageIdx.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<TypeSerializationMetadata>.Default.GetHashCode(fieldTypeMetadata);
return hashCode;
}
}
/// <summary>
/// Contains type information for a serialized Udon type.
/// This will be necessary for keeping backwards compatibility when types get fields moved around, deleted, or have their types changed.
/// Assumes use of Odin serializer to serialize things that Unity can't serialize by default.
/// There may be multiple metadatas defined for the same type, since objects may have been saved and serialized in different formats due to changes in the underlying script.
/// </summary>
[Serializable]
public class TypeSerializationMetadata
{
/// <summary>
/// The version of the serialized data, allows backwards compatibility with changes in type serialization.
/// </summary>
public TypeSerializationVersion version = TypeSerializationVersion.LatestVer;
/// <summary>
/// The underlying C# type which will often differ from the Udon storage type. This is what is used for the type's C# interface.
/// </summary>
public Type cSharpType;
/// <summary>
/// The type that this type is stored as in Udon programs. For custom user-defined classes that aren't behaviours this will always be `object[]`
/// For fields in behaviours this will usually be the most exact type that something can be stored as in Udon
/// </summary>
public Type udonStorageType;
/// <summary>
/// Used to describe the format of array element data. Will be null if this isn't an array
/// </summary>
public TypeSerializationMetadata arrayElementMetadata;
/// <summary>
/// All serialized fields stored by this type instance
/// </summary>
public FieldSerializationMetadata[] fieldData;
public TypeSerializationMetadata()
{ }
public TypeSerializationMetadata(System.Type cSharpType)
{
SetToType(cSharpType);
}
public override bool Equals(object obj)
{
return obj is TypeSerializationMetadata metadata &&
version == metadata.version &&
EqualityComparer<Type>.Default.Equals(cSharpType, metadata.cSharpType) &&
EqualityComparer<Type>.Default.Equals(udonStorageType, metadata.udonStorageType) &&
EqualityComparer<FieldSerializationMetadata[]>.Default.Equals(fieldData, metadata.fieldData);
}
// todo: this hash code should be built on construction
public override int GetHashCode()
{
var hashCode = -2022933226;
hashCode = hashCode * -1521134295 + version.GetHashCode();
hashCode = hashCode * -1521134295 + EqualityComparer<Type>.Default.GetHashCode(cSharpType);
hashCode = hashCode * -1521134295 + EqualityComparer<Type>.Default.GetHashCode(udonStorageType);
hashCode = hashCode * -1521134295 + EqualityComparer<FieldSerializationMetadata[]>.Default.GetHashCode(fieldData);
return hashCode;
}
public override string ToString()
{
return $"Serialization metadata - C# T:{cSharpType}, U# T: {udonStorageType}";
}
public void SetToType(Type type)
{
cSharpType = type;
if (cSharpType != null && cSharpType.IsArray)
{
Type elementType = cSharpType;
while (elementType.IsArray)
elementType = elementType.GetElementType();
arrayElementMetadata = new TypeSerializationMetadata(elementType);
}
udonStorageType = UdonSharpUtils.UserTypeToUdonType(cSharpType);
}
}
}

View File

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