Added Unity project files
This commit is contained in:
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecf0282bac3152442b9615794f214b9b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e15d45d631d675458e70de48cd83d82
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a79c60ee254187c4eaf21fd07e61bf6f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3111ba2c4ee12ab479454b252ad55820
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5308ff490e40ab940aef65623364eb19
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 400fe840666d0ed4588b812d0d493b7f
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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[]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a796521624b70f241ba569b01c102f9b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dde4c981522b643468846305475c2f9b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9326fc6c7a8f380488b547ea3496748f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 054f784b43ca05142b91708b293e0184
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91a3fa68f7d4ce0499baecb33e16aff5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 28321cb3284870840ad001503d326c0f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bccc6c4ed9117f64aaee0b63ddb70b3d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b85e23f6345466469e6df711407ff92
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 146023a4560fa7447a05c6c9b94b351b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d132731e5b9d2a942857326b0edf5625
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0a668ec7f85a6c146ad98dd63d09aa3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a48cf19765aaec54187900ffa27b98ca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d16349e2d07545c42943d628dd7ed781
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user