testss
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class Cloner<T> : ICloner
|
||||
{
|
||||
protected Cloner() { }
|
||||
|
||||
public abstract bool Handles(Type type);
|
||||
|
||||
void ICloner.BeforeClone(Type type, object original)
|
||||
{
|
||||
BeforeClone(type, (T)original);
|
||||
}
|
||||
|
||||
public virtual void BeforeClone(Type type, T original) { }
|
||||
|
||||
object ICloner.ConstructClone(Type type, object original)
|
||||
{
|
||||
return ConstructClone(type, (T)original);
|
||||
}
|
||||
|
||||
public virtual T ConstructClone(Type type, T original)
|
||||
{
|
||||
return (T)Activator.CreateInstance(type, true);
|
||||
}
|
||||
|
||||
void ICloner.FillClone(Type type, ref object clone, object original, CloningContext context)
|
||||
{
|
||||
var _instance = (T)clone;
|
||||
FillClone(type, ref _instance, (T)original, context);
|
||||
clone = _instance;
|
||||
}
|
||||
|
||||
public virtual void FillClone(Type type, ref T clone, T original, CloningContext context) { }
|
||||
|
||||
void ICloner.AfterClone(Type type, object clone)
|
||||
{
|
||||
AfterClone(type, (T)clone);
|
||||
}
|
||||
|
||||
public virtual void AfterClone(Type type, T clone) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class AnimationCurveCloner : Cloner<AnimationCurve>
|
||||
{
|
||||
public override bool Handles(Type type)
|
||||
{
|
||||
return type == typeof(AnimationCurve);
|
||||
}
|
||||
|
||||
public override AnimationCurve ConstructClone(Type type, AnimationCurve original)
|
||||
{
|
||||
return new AnimationCurve();
|
||||
}
|
||||
|
||||
public override void FillClone(Type type, ref AnimationCurve clone, AnimationCurve original, CloningContext context)
|
||||
{
|
||||
for (int i = 0; i < clone.length; i++)
|
||||
{
|
||||
clone.RemoveKey(i);
|
||||
}
|
||||
|
||||
foreach (var key in original.keys)
|
||||
{
|
||||
clone.AddKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class ArrayCloner : Cloner<Array>
|
||||
{
|
||||
public override bool Handles(Type type)
|
||||
{
|
||||
return type.IsArray;
|
||||
}
|
||||
|
||||
public override Array ConstructClone(Type type, Array original)
|
||||
{
|
||||
return Array.CreateInstance(type.GetElementType(), 0);
|
||||
}
|
||||
|
||||
public override void FillClone(Type type, ref Array clone, Array original, CloningContext context)
|
||||
{
|
||||
var length = original.GetLength(0);
|
||||
|
||||
clone = Array.CreateInstance(type.GetElementType(), length);
|
||||
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
clone.SetValue(Cloning.Clone(context, original.GetValue(i)), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class DictionaryCloner : Cloner<IDictionary>
|
||||
{
|
||||
public override bool Handles(Type type)
|
||||
{
|
||||
return typeof(IDictionary).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
public override void FillClone(Type type, ref IDictionary clone, IDictionary original, CloningContext context)
|
||||
{
|
||||
// No support for instance preservation here, but none in FS either, so it shouldn't matter
|
||||
|
||||
var originalEnumerator = original.GetEnumerator();
|
||||
|
||||
while (originalEnumerator.MoveNext())
|
||||
{
|
||||
var originalKey = originalEnumerator.Key;
|
||||
var originalValue = originalEnumerator.Value;
|
||||
|
||||
var cloneKey = Cloning.Clone(context, originalKey);
|
||||
var cloneValue = Cloning.Clone(context, originalValue);
|
||||
|
||||
clone.Add(cloneKey, cloneValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.VisualScripting.FullSerializer;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class EnumerableCloner : Cloner<IEnumerable>
|
||||
{
|
||||
public override bool Handles(Type type)
|
||||
{
|
||||
return typeof(IEnumerable).IsAssignableFrom(type) && !typeof(IList).IsAssignableFrom(type) && GetAddMethod(type) != null;
|
||||
}
|
||||
|
||||
public override void FillClone(Type type, ref IEnumerable clone, IEnumerable original, CloningContext context)
|
||||
{
|
||||
var addMethod = GetAddMethod(type);
|
||||
|
||||
if (addMethod == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot instantiate enumerable type '{type}' because it does not provide an add method.");
|
||||
}
|
||||
|
||||
// No way to preserve instances here
|
||||
|
||||
foreach (var item in original)
|
||||
{
|
||||
addMethod.Invoke(item, Cloning.Clone(context, item));
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<Type, IOptimizedInvoker> addMethods = new Dictionary<Type, IOptimizedInvoker>();
|
||||
|
||||
private IOptimizedInvoker GetAddMethod(Type type)
|
||||
{
|
||||
if (!addMethods.ContainsKey(type))
|
||||
{
|
||||
var addMethod = fsReflectionUtility.GetInterface(type, typeof(ICollection<>))?.GetDeclaredMethod("Add") ??
|
||||
type.GetFlattenedMethod("Add") ??
|
||||
type.GetFlattenedMethod("Push") ??
|
||||
type.GetFlattenedMethod("Enqueue");
|
||||
|
||||
addMethods.Add(type, addMethod?.Prewarm());
|
||||
}
|
||||
|
||||
return addMethods[type];
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Unity.VisualScripting.FullSerializer;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class FakeSerializationCloner : ReflectedCloner
|
||||
{
|
||||
public fsConfig config { get; set; } = new fsConfig();
|
||||
|
||||
public override void BeforeClone(Type type, object original)
|
||||
{
|
||||
(original as ISerializationCallbackReceiver)?.OnBeforeSerialize();
|
||||
}
|
||||
|
||||
public override void AfterClone(Type type, object clone)
|
||||
{
|
||||
(clone as ISerializationCallbackReceiver)?.OnAfterDeserialize();
|
||||
}
|
||||
|
||||
protected override IEnumerable<MemberInfo> GetMembers(Type type)
|
||||
{
|
||||
return fsMetaType.Get(config, type).Properties.Select(p => p._memberInfo);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,17 @@
|
||||
using System.Reflection;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class FieldsCloner : ReflectedCloner
|
||||
{
|
||||
protected override bool IncludeField(FieldInfo field)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override bool IncludeProperty(PropertyInfo property)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class ListCloner : Cloner<IList>
|
||||
{
|
||||
public override bool Handles(Type type)
|
||||
{
|
||||
return typeof(IList).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
public override void FillClone(Type type, ref IList clone, IList original, CloningContext context)
|
||||
{
|
||||
if (context.tryPreserveInstances)
|
||||
{
|
||||
for (int i = 0; i < original.Count; i++)
|
||||
{
|
||||
var originalItem = original[i];
|
||||
|
||||
if (i < clone.Count)
|
||||
{
|
||||
// The clone has at least as many items, we can try to preserve its instances.
|
||||
var cloneItem = clone[i];
|
||||
Cloning.CloneInto(context, ref cloneItem, originalItem);
|
||||
clone[i] = cloneItem;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The clone has less items than the original, we have to add a new item.
|
||||
clone.Add(Cloning.Clone(context, originalItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Avoiding foreach to avoid enumerator allocation
|
||||
|
||||
for (int i = 0; i < original.Count; i++)
|
||||
{
|
||||
var originalItem = original[i];
|
||||
|
||||
clone.Add(Cloning.Clone(context, originalItem));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,130 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class ReflectedCloner : Cloner<object>
|
||||
{
|
||||
public override bool Handles(Type type)
|
||||
{
|
||||
return false; // Should only be used as a fallback cloner
|
||||
}
|
||||
|
||||
public override void FillClone(Type type, ref object clone, object original, CloningContext context)
|
||||
{
|
||||
if (PlatformUtility.supportsJit)
|
||||
{
|
||||
foreach (var accessor in GetOptimizedAccessors(type))
|
||||
{
|
||||
if (context.tryPreserveInstances)
|
||||
{
|
||||
var cloneProperty = accessor.GetValue(clone);
|
||||
Cloning.CloneInto(context, ref cloneProperty, accessor.GetValue(original));
|
||||
accessor.SetValue(clone, cloneProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
accessor.SetValue(clone, Cloning.Clone(context, accessor.GetValue(original)));
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var accessor in GetAccessors(type))
|
||||
{
|
||||
if (accessor is FieldInfo)
|
||||
{
|
||||
var field = (FieldInfo)accessor;
|
||||
|
||||
if (context.tryPreserveInstances)
|
||||
{
|
||||
var cloneProperty = field.GetValue(clone);
|
||||
Cloning.CloneInto(context, ref cloneProperty, field.GetValue(original));
|
||||
field.SetValue(clone, cloneProperty);
|
||||
}
|
||||
else
|
||||
{
|
||||
field.SetValue(clone, Cloning.Clone(context, field.GetValue(original)));
|
||||
}
|
||||
}
|
||||
else if (accessor is PropertyInfo)
|
||||
{
|
||||
var property = (PropertyInfo)accessor;
|
||||
|
||||
if (context.tryPreserveInstances)
|
||||
{
|
||||
var cloneProperty = property.GetValue(clone, null);
|
||||
Cloning.CloneInto(context, ref cloneProperty, property.GetValue(original, null));
|
||||
property.SetValue(clone, cloneProperty, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
property.SetValue(clone, Cloning.Clone(context, property.GetValue(original, null)), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Dictionary<Type, MemberInfo[]> accessors = new Dictionary<Type, MemberInfo[]>();
|
||||
|
||||
private MemberInfo[] GetAccessors(Type type)
|
||||
{
|
||||
if (!accessors.ContainsKey(type))
|
||||
{
|
||||
accessors.Add(type, GetMembers(type).ToArray());
|
||||
}
|
||||
|
||||
return accessors[type];
|
||||
}
|
||||
|
||||
private readonly Dictionary<Type, IOptimizedAccessor[]> optimizedAccessors = new Dictionary<Type, IOptimizedAccessor[]>();
|
||||
|
||||
private IOptimizedAccessor[] GetOptimizedAccessors(Type type)
|
||||
{
|
||||
if (!optimizedAccessors.ContainsKey(type))
|
||||
{
|
||||
var list = new List<IOptimizedAccessor>();
|
||||
|
||||
foreach (var member in GetMembers(type))
|
||||
{
|
||||
if (member is FieldInfo)
|
||||
{
|
||||
list.Add(((FieldInfo)member).Prewarm());
|
||||
}
|
||||
else if (member is PropertyInfo)
|
||||
{
|
||||
list.Add(((PropertyInfo)member).Prewarm());
|
||||
}
|
||||
}
|
||||
|
||||
optimizedAccessors.Add(type, list.ToArray());
|
||||
}
|
||||
|
||||
return optimizedAccessors[type];
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<MemberInfo> GetMembers(Type type)
|
||||
{
|
||||
var bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
|
||||
|
||||
return LinqUtility.Concat<MemberInfo>
|
||||
(
|
||||
type.GetFields(bindingFlags).Where(IncludeField),
|
||||
type.GetProperties(bindingFlags).Where(IncludeProperty)
|
||||
);
|
||||
}
|
||||
|
||||
protected virtual bool IncludeField(FieldInfo field)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected virtual bool IncludeProperty(PropertyInfo property)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class Cloning
|
||||
{
|
||||
static Cloning()
|
||||
{
|
||||
cloners.Add(arrayCloner);
|
||||
cloners.Add(dictionaryCloner);
|
||||
cloners.Add(enumerableCloner);
|
||||
cloners.Add(listCloner);
|
||||
cloners.Add(animationCurveCloner);
|
||||
}
|
||||
|
||||
// Cloning has to be really fast, and skippable takes a while.
|
||||
private static readonly Dictionary<Type, bool> skippable = new Dictionary<Type, bool>();
|
||||
|
||||
public static HashSet<ICloner> cloners { get; } = new HashSet<ICloner>();
|
||||
|
||||
public static ArrayCloner arrayCloner { get; } = new ArrayCloner();
|
||||
public static DictionaryCloner dictionaryCloner { get; } = new DictionaryCloner();
|
||||
public static EnumerableCloner enumerableCloner { get; } = new EnumerableCloner();
|
||||
public static ListCloner listCloner { get; } = new ListCloner();
|
||||
public static AnimationCurveCloner animationCurveCloner { get; } = new AnimationCurveCloner();
|
||||
|
||||
public static FieldsCloner fieldsCloner { get; } = new FieldsCloner();
|
||||
public static FakeSerializationCloner fakeSerializationCloner { get; } = new FakeSerializationCloner();
|
||||
|
||||
public static object Clone(this object original, ICloner fallbackCloner, bool tryPreserveInstances)
|
||||
{
|
||||
using (var context = CloningContext.New(fallbackCloner, tryPreserveInstances))
|
||||
{
|
||||
return Clone(context, original);
|
||||
}
|
||||
}
|
||||
|
||||
public static T Clone<T>(this T original, ICloner fallbackCloner, bool tryPreserveInstances)
|
||||
{
|
||||
return (T)Clone((object)original, fallbackCloner, tryPreserveInstances);
|
||||
}
|
||||
|
||||
public static object CloneViaFakeSerialization(this object original)
|
||||
{
|
||||
return original.Clone(fakeSerializationCloner, true);
|
||||
}
|
||||
|
||||
public static T CloneViaFakeSerialization<T>(this T original)
|
||||
{
|
||||
return (T)CloneViaFakeSerialization((object)original);
|
||||
}
|
||||
|
||||
internal static object Clone(CloningContext context, object original)
|
||||
{
|
||||
object clone = null;
|
||||
CloneInto(context, ref clone, original);
|
||||
return clone;
|
||||
}
|
||||
|
||||
internal static void CloneInto(CloningContext context, ref object clone, object original)
|
||||
{
|
||||
if (original == null)
|
||||
{
|
||||
clone = null;
|
||||
return;
|
||||
}
|
||||
|
||||
var type = original.GetType();
|
||||
|
||||
if (Skippable(type))
|
||||
{
|
||||
clone = original;
|
||||
return;
|
||||
}
|
||||
|
||||
if (context.clonings.ContainsKey(original))
|
||||
{
|
||||
clone = context.clonings[original];
|
||||
return;
|
||||
}
|
||||
|
||||
var cloner = GetCloner(original, type, context.fallbackCloner);
|
||||
|
||||
if (clone == null)
|
||||
{
|
||||
clone = cloner.ConstructClone(type, original);
|
||||
}
|
||||
|
||||
context.clonings.Add(original, clone);
|
||||
cloner.BeforeClone(type, original);
|
||||
cloner.FillClone(type, ref clone, original, context);
|
||||
cloner.AfterClone(type, clone);
|
||||
context.clonings[original] = clone; // In case the reference changed, for example in arrays
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
public static ICloner GetCloner(object original, Type type)
|
||||
{
|
||||
if (original is ISpecifiesCloner cloneableVia)
|
||||
{
|
||||
return cloneableVia.cloner;
|
||||
}
|
||||
|
||||
return cloners.FirstOrDefault(cloner => cloner.Handles(type));
|
||||
}
|
||||
|
||||
private static ICloner GetCloner(object original, Type type, ICloner fallbackCloner)
|
||||
{
|
||||
var cloner = GetCloner(original, type);
|
||||
|
||||
if (cloner != null)
|
||||
return cloner;
|
||||
|
||||
Ensure.That(nameof(fallbackCloner)).IsNotNull(fallbackCloner);
|
||||
|
||||
return fallbackCloner;
|
||||
}
|
||||
|
||||
private static bool Skippable(Type type)
|
||||
{
|
||||
bool result;
|
||||
|
||||
if (!skippable.TryGetValue(type, out result))
|
||||
{
|
||||
result = type.IsValueType || // Value types are copied on assignment, so no cloning is necessary
|
||||
type == typeof(string) || // Strings have copy on write semantics as well, but aren't value types
|
||||
typeof(Type).IsAssignableFrom(type) || // Types are guaranteed to be singletons. Using inheritance because MonoType/RuntimeType extend Type
|
||||
typeof(UnityObject).IsAssignableFrom(type); // Unity objects act as pure references
|
||||
|
||||
skippable.Add(type, result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class CloningContext : IPoolable, IDisposable
|
||||
{
|
||||
public Dictionary<object, object> clonings { get; } = new Dictionary<object, object>(ReferenceEqualityComparer.Instance);
|
||||
|
||||
public ICloner fallbackCloner { get; private set; }
|
||||
|
||||
public bool tryPreserveInstances { get; private set; }
|
||||
|
||||
private bool disposed;
|
||||
|
||||
void IPoolable.New()
|
||||
{
|
||||
disposed = false;
|
||||
}
|
||||
|
||||
void IPoolable.Free()
|
||||
{
|
||||
disposed = true;
|
||||
clonings.Clear();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(ToString());
|
||||
}
|
||||
|
||||
GenericPool<CloningContext>.Free(this);
|
||||
}
|
||||
|
||||
public static CloningContext New(ICloner fallbackCloner, bool tryPreserveInstances)
|
||||
{
|
||||
var context = GenericPool<CloningContext>.New(() => new CloningContext());
|
||||
context.fallbackCloner = fallbackCloner;
|
||||
context.tryPreserveInstances = tryPreserveInstances;
|
||||
return context;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface ICloner
|
||||
{
|
||||
bool Handles(Type type);
|
||||
object ConstructClone(Type type, object original);
|
||||
void BeforeClone(Type type, object original);
|
||||
void FillClone(Type type, ref object clone, object original, CloningContext context);
|
||||
void AfterClone(Type type, object clone);
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface ISpecifiesCloner
|
||||
{
|
||||
ICloner cloner { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
// Making this inherit OrderedDictionary for now because metadata
|
||||
// doesn't work well for unordered dictionaries. The ideal solution
|
||||
// would be to rework the MetadataDictionaryAdaptor to not require
|
||||
// the index at all, then make this inherit Dictionary<object, object>
|
||||
|
||||
public sealed class AotDictionary : OrderedDictionary
|
||||
{
|
||||
public AotDictionary() : base() { }
|
||||
public AotDictionary(IEqualityComparer comparer) : base(comparer) { }
|
||||
public AotDictionary(int capacity) : base(capacity) { }
|
||||
public AotDictionary(int capacity, IEqualityComparer comparer) : base(capacity, comparer) { }
|
||||
|
||||
[Preserve]
|
||||
public static void AotStubs()
|
||||
{
|
||||
var dictionary = new AotDictionary();
|
||||
|
||||
dictionary.Add(default(object), default(object));
|
||||
dictionary.Remove(default(object));
|
||||
var item = dictionary[default(object)];
|
||||
dictionary[default(object)] = default(object);
|
||||
dictionary.Contains(default(object));
|
||||
dictionary.Clear();
|
||||
var count = dictionary.Count;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System.Collections;
|
||||
using UnityEngine.Scripting;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public sealed class AotList : ArrayList
|
||||
{
|
||||
public AotList() : base() { }
|
||||
public AotList(int capacity) : base(capacity) { }
|
||||
public AotList(ICollection c) : base(c) { }
|
||||
|
||||
[Preserve]
|
||||
public static void AotStubs()
|
||||
{
|
||||
var list = new AotList();
|
||||
|
||||
list.Add(default(object));
|
||||
list.Remove(default(object));
|
||||
var item = list[default(int)];
|
||||
list[default(int)] = default(object);
|
||||
list.Contains(default(object));
|
||||
list.Clear();
|
||||
var count = list.Count;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class DebugDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
|
||||
{
|
||||
private readonly Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>();
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return dictionary[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
Debug($"Set: {key} => {value}");
|
||||
dictionary[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
object IDictionary.this[object key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return this[(TKey)key];
|
||||
}
|
||||
set
|
||||
{
|
||||
this[(TKey)key] = (TValue)value;
|
||||
}
|
||||
}
|
||||
|
||||
public string label { get; set; } = "Dictionary";
|
||||
|
||||
public bool debug { get; set; } = false;
|
||||
|
||||
public int Count => dictionary.Count;
|
||||
|
||||
object ICollection.SyncRoot => ((ICollection)dictionary).SyncRoot;
|
||||
|
||||
bool ICollection.IsSynchronized => ((ICollection)dictionary).IsSynchronized;
|
||||
|
||||
ICollection IDictionary.Values => ((IDictionary)dictionary).Values;
|
||||
|
||||
bool IDictionary.IsReadOnly => ((IDictionary)dictionary).IsReadOnly;
|
||||
|
||||
bool IDictionary.IsFixedSize => ((IDictionary)dictionary).IsFixedSize;
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.IsReadOnly => ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).IsReadOnly;
|
||||
|
||||
public ICollection<TKey> Keys => dictionary.Keys;
|
||||
|
||||
ICollection IDictionary.Keys => ((IDictionary)dictionary).Keys;
|
||||
|
||||
public ICollection<TValue> Values => dictionary.Values;
|
||||
|
||||
void ICollection.CopyTo(Array array, int index)
|
||||
{
|
||||
((ICollection)dictionary).CopyTo(array, index);
|
||||
}
|
||||
|
||||
private void Debug(string message)
|
||||
{
|
||||
if (!debug)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(label))
|
||||
{
|
||||
message = $"[{label}] {message}";
|
||||
}
|
||||
|
||||
UnityEngine.Debug.Log(message + "\n");
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)dictionary).GetEnumerator();
|
||||
}
|
||||
|
||||
void IDictionary.Remove(object key)
|
||||
{
|
||||
Remove((TKey)key);
|
||||
}
|
||||
|
||||
bool IDictionary.Contains(object key)
|
||||
{
|
||||
return ContainsKey((TKey)key);
|
||||
}
|
||||
|
||||
void IDictionary.Add(object key, object value)
|
||||
{
|
||||
Add((TKey)key, (TValue)value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
Debug("Clear");
|
||||
dictionary.Clear();
|
||||
}
|
||||
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||||
{
|
||||
return ((IDictionary)dictionary).GetEnumerator();
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return dictionary.Contains(item);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Add(item);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)dictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Remove(item);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
Debug($"Add: {key} => {value}");
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
Debug($"Remove: {key}");
|
||||
return dictionary.Remove(key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class FlexibleDictionary<TKey, TValue> : Dictionary<TKey, TValue>
|
||||
{
|
||||
public new TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return base[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (ContainsKey(key))
|
||||
{
|
||||
base[key] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GuidCollection<T> : KeyedCollection<Guid, T>, IKeyedCollection<Guid, T> where T : IIdentifiable
|
||||
{
|
||||
protected override Guid GetKeyForItem(T item)
|
||||
{
|
||||
return item.guid;
|
||||
}
|
||||
|
||||
protected override void InsertItem(int index, T item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
base.InsertItem(index, item);
|
||||
}
|
||||
|
||||
protected override void SetItem(int index, T item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
base.SetItem(index, item);
|
||||
}
|
||||
|
||||
public bool TryGetValue(Guid key, out T value)
|
||||
{
|
||||
if (Dictionary == null)
|
||||
{
|
||||
value = default(T);
|
||||
return false;
|
||||
}
|
||||
|
||||
return Dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IKeyedCollection<TKey, TItem> : ICollection<TItem>
|
||||
{
|
||||
TItem this[TKey key] { get; }
|
||||
TItem this[int index] { get; } // For allocation free enumerators
|
||||
bool TryGetValue(TKey key, out TItem value);
|
||||
bool Contains(TKey key);
|
||||
bool Remove(TKey key);
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IMergedCollection<T> : ICollection<T>
|
||||
{
|
||||
bool Includes<TI>() where TI : T;
|
||||
bool Includes(Type elementType);
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface INotifiedCollectionItem
|
||||
{
|
||||
void BeforeAdd();
|
||||
|
||||
void AfterAdd();
|
||||
|
||||
void BeforeRemove();
|
||||
|
||||
void AfterRemove();
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface INotifyCollectionChanged<T>
|
||||
{
|
||||
event Action<T> ItemAdded;
|
||||
|
||||
event Action<T> ItemRemoved;
|
||||
|
||||
event Action CollectionChanged;
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IProxyableNotifyCollectionChanged<T>
|
||||
{
|
||||
bool ProxyCollectionChange { get; set; }
|
||||
|
||||
void BeforeAdd(T item);
|
||||
|
||||
void AfterAdd(T item);
|
||||
|
||||
void BeforeRemove(T item);
|
||||
|
||||
void AfterRemove(T item);
|
||||
}
|
||||
}
|
@@ -0,0 +1,62 @@
|
||||
// ==++==
|
||||
//
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
//
|
||||
// ==--==
|
||||
/*============================================================
|
||||
**
|
||||
** Interface: ISet
|
||||
**
|
||||
** <OWNER>kimhamil</OWNER>
|
||||
**
|
||||
**
|
||||
** Purpose: Base interface for all generic sets.
|
||||
**
|
||||
**
|
||||
===========================================================*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic collection that guarantees the uniqueness of its elements, as defined
|
||||
/// by some comparer. It also supports basic set operations such as Union, Intersection,
|
||||
/// Complement and Exclusive Complement.
|
||||
/// </summary>
|
||||
public interface ISet<T> : ICollection<T>
|
||||
{
|
||||
//Add ITEM to the set, return true if added, false if duplicate
|
||||
new bool Add(T item);
|
||||
|
||||
//Transform this set into its union with the IEnumerable<T> other
|
||||
void UnionWith(IEnumerable<T> other);
|
||||
|
||||
//Transform this set into its intersection with the IEnumberable<T> other
|
||||
void IntersectWith(IEnumerable<T> other);
|
||||
|
||||
//Transform this set so it contains no elements that are also in other
|
||||
void ExceptWith(IEnumerable<T> other);
|
||||
|
||||
//Transform this set so it contains elements initially in this or in other, but not both
|
||||
void SymmetricExceptWith(IEnumerable<T> other);
|
||||
|
||||
//Check if this set is a subset of other
|
||||
bool IsSubsetOf(IEnumerable<T> other);
|
||||
|
||||
//Check if this set is a superset of other
|
||||
bool IsSupersetOf(IEnumerable<T> other);
|
||||
|
||||
//Check if this set is a subset of other, but not the same as it
|
||||
bool IsProperSupersetOf(IEnumerable<T> other);
|
||||
|
||||
//Check if this set is a superset of other, but not the same as it
|
||||
bool IsProperSubsetOf(IEnumerable<T> other);
|
||||
|
||||
//Check if this set has any elements in common with other
|
||||
bool Overlaps(IEnumerable<T> other);
|
||||
|
||||
//Check if this set contains the same and only the same elements as other
|
||||
bool SetEquals(IEnumerable<T> other);
|
||||
}
|
||||
}
|
@@ -0,0 +1,150 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class MergedCollection<T> : IMergedCollection<T>
|
||||
{
|
||||
public MergedCollection()
|
||||
{
|
||||
collections = new Dictionary<Type, ICollection<T>>();
|
||||
}
|
||||
|
||||
private readonly Dictionary<Type, ICollection<T>> collections;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (var collection in collections.Values)
|
||||
{
|
||||
count += collection.Count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public void Include<TI>(ICollection<TI> collection) where TI : T
|
||||
{
|
||||
collections.Add(typeof(TI), new VariantCollection<T, TI>(collection));
|
||||
}
|
||||
|
||||
public bool Includes<TI>() where TI : T
|
||||
{
|
||||
return Includes(typeof(TI));
|
||||
}
|
||||
|
||||
public bool Includes(Type implementationType)
|
||||
{
|
||||
return GetCollectionForType(implementationType, false) != null;
|
||||
}
|
||||
|
||||
public ICollection<TI> ForType<TI>() where TI : T
|
||||
{
|
||||
return ((VariantCollection<T, TI>)GetCollectionForType(typeof(TI))).implementation;
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
foreach (var collection in collections.Values)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ICollection<T> GetCollectionForItem(T item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
return GetCollectionForType(item.GetType());
|
||||
}
|
||||
|
||||
private ICollection<T> GetCollectionForType(Type type, bool throwOnFail = true)
|
||||
{
|
||||
if (collections.ContainsKey(type))
|
||||
{
|
||||
return collections[type];
|
||||
}
|
||||
|
||||
foreach (var collectionByType in collections)
|
||||
{
|
||||
if (collectionByType.Key.IsAssignableFrom(type))
|
||||
{
|
||||
return collectionByType.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (throwOnFail)
|
||||
{
|
||||
throw new InvalidOperationException($"No sub-collection available for type '{type}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return GetCollectionForItem(item).Contains(item);
|
||||
}
|
||||
|
||||
public virtual void Add(T item)
|
||||
{
|
||||
GetCollectionForItem(item).Add(item);
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
foreach (var collection in collections.Values)
|
||||
{
|
||||
collection.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Remove(T item)
|
||||
{
|
||||
return GetCollectionForItem(item).Remove(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var collection in collections.Values)
|
||||
{
|
||||
collection.CopyTo(array, arrayIndex + i);
|
||||
i += collection.Count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,320 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class MergedKeyedCollection<TKey, TItem> : IMergedCollection<TItem>
|
||||
{
|
||||
public MergedKeyedCollection() : base()
|
||||
{
|
||||
collections = new Dictionary<Type, IKeyedCollection<TKey, TItem>>();
|
||||
collectionsLookup = new Dictionary<Type, IKeyedCollection<TKey, TItem>>();
|
||||
}
|
||||
|
||||
protected readonly Dictionary<Type, IKeyedCollection<TKey, TItem>> collections;
|
||||
|
||||
// Used for performance optimization when finding the right collection for a type
|
||||
protected readonly Dictionary<Type, IKeyedCollection<TKey, TItem>> collectionsLookup;
|
||||
|
||||
public TItem this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (key == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(key));
|
||||
}
|
||||
|
||||
foreach (var collectionByType in collections)
|
||||
{
|
||||
if (collectionByType.Value.Contains(key))
|
||||
{
|
||||
return collectionByType.Value[key];
|
||||
}
|
||||
}
|
||||
|
||||
throw new KeyNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (var collectionByType in collections)
|
||||
{
|
||||
count += collectionByType.Value.Count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public bool Includes<TSubItem>() where TSubItem : TItem
|
||||
{
|
||||
return Includes(typeof(TSubItem));
|
||||
}
|
||||
|
||||
public bool Includes(Type elementType)
|
||||
{
|
||||
return GetCollectionForType(elementType, false) != null;
|
||||
}
|
||||
|
||||
public IKeyedCollection<TKey, TSubItem> ForType<TSubItem>() where TSubItem : TItem
|
||||
{
|
||||
return ((VariantKeyedCollection<TItem, TSubItem, TKey>)GetCollectionForType(typeof(TSubItem))).implementation;
|
||||
}
|
||||
|
||||
public virtual void Include<TSubItem>(IKeyedCollection<TKey, TSubItem> collection) where TSubItem : TItem
|
||||
{
|
||||
var type = typeof(TSubItem);
|
||||
var variantCollection = new VariantKeyedCollection<TItem, TSubItem, TKey>(collection);
|
||||
collections.Add(type, variantCollection);
|
||||
collectionsLookup.Add(type, variantCollection);
|
||||
}
|
||||
|
||||
protected IKeyedCollection<TKey, TItem> GetCollectionForItem(TItem item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
return GetCollectionForType(item.GetType());
|
||||
}
|
||||
|
||||
protected IKeyedCollection<TKey, TItem> GetCollectionForType(Type type, bool throwOnFail = true)
|
||||
{
|
||||
Ensure.That(nameof(type)).IsNotNull(type);
|
||||
|
||||
if (collectionsLookup.TryGetValue(type, out var collection))
|
||||
{
|
||||
return collection;
|
||||
}
|
||||
|
||||
foreach (var collectionByType in collections)
|
||||
{
|
||||
if (collectionByType.Key.IsAssignableFrom(type))
|
||||
{
|
||||
collection = collectionByType.Value;
|
||||
collectionsLookup.Add(type, collection);
|
||||
return collection;
|
||||
}
|
||||
}
|
||||
|
||||
if (throwOnFail)
|
||||
{
|
||||
throw new InvalidOperationException($"No sub-collection available for type '{type}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected IKeyedCollection<TKey, TItem> GetCollectionForKey(TKey key, bool throwOnFail = true)
|
||||
{
|
||||
// Optim: avoid boxing here.
|
||||
// Ensure.That(nameof(key)).IsNotNull(key);
|
||||
|
||||
foreach (var collectionsByType in collections)
|
||||
{
|
||||
if (collectionsByType.Value.Contains(key))
|
||||
{
|
||||
return collectionsByType.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (throwOnFail)
|
||||
{
|
||||
throw new InvalidOperationException($"No sub-collection available for key '{key}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TItem value)
|
||||
{
|
||||
var collection = GetCollectionForKey(key, false);
|
||||
|
||||
value = default(TItem);
|
||||
|
||||
return collection != null && collection.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public virtual void Add(TItem item)
|
||||
{
|
||||
GetCollectionForItem(item).Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var collection in collections.Values)
|
||||
{
|
||||
collection.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(TItem item)
|
||||
{
|
||||
return GetCollectionForItem(item).Contains(item);
|
||||
}
|
||||
|
||||
public bool Remove(TItem item)
|
||||
{
|
||||
return GetCollectionForItem(item).Remove(item);
|
||||
}
|
||||
|
||||
public void CopyTo(TItem[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var collection in collections.Values)
|
||||
{
|
||||
collection.CopyTo(array, arrayIndex + i);
|
||||
i += collection.Count;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(TKey key)
|
||||
{
|
||||
return GetCollectionForKey(key, false) != null;
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
return GetCollectionForKey(key).Remove(key);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<TItem> IEnumerable<TItem>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<TItem>
|
||||
{
|
||||
private Dictionary<Type, IKeyedCollection<TKey, TItem>>.Enumerator collectionsEnumerator;
|
||||
private TItem currentItem;
|
||||
private IKeyedCollection<TKey, TItem> currentCollection;
|
||||
private int indexInCurrentCollection;
|
||||
private bool exceeded;
|
||||
|
||||
public Enumerator(MergedKeyedCollection<TKey, TItem> merged) : this()
|
||||
{
|
||||
collectionsEnumerator = merged.collections.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
// We just started, so we're not in a collection yet
|
||||
if (currentCollection == null)
|
||||
{
|
||||
// Try to find the first collection
|
||||
if (collectionsEnumerator.MoveNext())
|
||||
{
|
||||
// There is at least a collection, start with this one
|
||||
currentCollection = collectionsEnumerator.Current.Value;
|
||||
|
||||
if (currentCollection == null)
|
||||
{
|
||||
throw new InvalidOperationException("Merged sub collection is null.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// There is no collection at all, stop
|
||||
currentItem = default(TItem);
|
||||
exceeded = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're within the current collection
|
||||
if (indexInCurrentCollection < currentCollection.Count)
|
||||
{
|
||||
// We are, return this element and move to the next
|
||||
currentItem = currentCollection[indexInCurrentCollection];
|
||||
indexInCurrentCollection++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We're beyond the current collection, but there may be more,
|
||||
// and because there may be many empty collections, we need to check
|
||||
// them all until we find an element, not just the next one
|
||||
while (collectionsEnumerator.MoveNext())
|
||||
{
|
||||
currentCollection = collectionsEnumerator.Current.Value;
|
||||
indexInCurrentCollection = 0;
|
||||
|
||||
if (currentCollection == null)
|
||||
{
|
||||
throw new InvalidOperationException("Merged sub collection is null.");
|
||||
}
|
||||
|
||||
if (indexInCurrentCollection < currentCollection.Count)
|
||||
{
|
||||
currentItem = currentCollection[indexInCurrentCollection];
|
||||
indexInCurrentCollection++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We're beyond all collections, stop
|
||||
currentItem = default(TItem);
|
||||
exceeded = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public TItem Current => currentItem;
|
||||
|
||||
Object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (exceeded)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,252 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
// The advantage of this class is not to provide list accessors
|
||||
// for merged lists (which would be confusing), but rather that unlike
|
||||
// merged collection, it can provide a zero-allocation enumerator.
|
||||
|
||||
// OPTIM note: Dictionary<,>.Values allocated memory the first time, so avoid it if possible
|
||||
public class MergedList<T> : IMergedCollection<T>
|
||||
{
|
||||
public MergedList()
|
||||
{
|
||||
lists = new Dictionary<Type, IList<T>>();
|
||||
}
|
||||
|
||||
protected readonly Dictionary<Type, IList<T>> lists;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
foreach (var listByType in lists)
|
||||
{
|
||||
count += listByType.Value.Count;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public virtual void Include<TI>(IList<TI> list) where TI : T
|
||||
{
|
||||
lists.Add(typeof(TI), new VariantList<T, TI>(list));
|
||||
}
|
||||
|
||||
public bool Includes<TI>() where TI : T
|
||||
{
|
||||
return Includes(typeof(TI));
|
||||
}
|
||||
|
||||
public bool Includes(Type elementType)
|
||||
{
|
||||
return GetListForType(elementType, false) != null;
|
||||
}
|
||||
|
||||
public IList<TI> ForType<TI>() where TI : T
|
||||
{
|
||||
return ((VariantList<T, TI>)GetListForType(typeof(TI))).implementation;
|
||||
}
|
||||
|
||||
protected IList<T> GetListForItem(T item)
|
||||
{
|
||||
Ensure.That(nameof(item)).IsNotNull(item);
|
||||
|
||||
return GetListForType(item.GetType());
|
||||
}
|
||||
|
||||
protected IList<T> GetListForType(Type type, bool throwOnFail = true)
|
||||
{
|
||||
if (lists.ContainsKey(type))
|
||||
{
|
||||
return lists[type];
|
||||
}
|
||||
|
||||
foreach (var listByType in lists)
|
||||
{
|
||||
if (listByType.Key.IsAssignableFrom(type))
|
||||
{
|
||||
return listByType.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (throwOnFail)
|
||||
{
|
||||
throw new InvalidOperationException($"No sub-collection available for type '{type}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
return GetListForItem(item).Contains(item);
|
||||
}
|
||||
|
||||
public virtual void Add(T item)
|
||||
{
|
||||
GetListForItem(item).Add(item);
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
foreach (var listByType in lists)
|
||||
{
|
||||
listByType.Value.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool Remove(T item)
|
||||
{
|
||||
return GetListForItem(item).Remove(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var i = 0;
|
||||
|
||||
foreach (var listByType in lists)
|
||||
{
|
||||
var list = listByType.Value;
|
||||
list.CopyTo(array, arrayIndex + i);
|
||||
i += list.Count;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<T> IEnumerable<T>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public Enumerator GetEnumerator()
|
||||
{
|
||||
return new Enumerator(this);
|
||||
}
|
||||
|
||||
public struct Enumerator : IEnumerator<T>
|
||||
{
|
||||
private Dictionary<Type, IList<T>>.Enumerator listsEnumerator;
|
||||
private T currentItem;
|
||||
private IList<T> currentList;
|
||||
private int indexInCurrentList;
|
||||
private bool exceeded;
|
||||
|
||||
public Enumerator(MergedList<T> merged) : this()
|
||||
{
|
||||
listsEnumerator = merged.lists.GetEnumerator();
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
// We just started, so we're not in a list yet
|
||||
if (currentList == null)
|
||||
{
|
||||
// Try to find the first list
|
||||
if (listsEnumerator.MoveNext())
|
||||
{
|
||||
// There is at least a list, start with this one
|
||||
currentList = listsEnumerator.Current.Value;
|
||||
|
||||
if (currentList == null)
|
||||
{
|
||||
throw new InvalidOperationException("Merged sub list is null.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// There is no list at all, stop
|
||||
currentItem = default(T);
|
||||
exceeded = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we're within the current list
|
||||
if (indexInCurrentList < currentList.Count)
|
||||
{
|
||||
// We are, return this element and move to the next
|
||||
currentItem = currentList[indexInCurrentList];
|
||||
indexInCurrentList++;
|
||||
return true;
|
||||
}
|
||||
|
||||
// We're beyond the current list, but there may be more,
|
||||
// and because there may be many empty lists, we need to check
|
||||
// them all until we find an element, not just the next one
|
||||
while (listsEnumerator.MoveNext())
|
||||
{
|
||||
currentList = listsEnumerator.Current.Value;
|
||||
indexInCurrentList = 0;
|
||||
|
||||
if (currentList == null)
|
||||
{
|
||||
throw new InvalidOperationException("Merged sub list is null.");
|
||||
}
|
||||
|
||||
if (indexInCurrentList < currentList.Count)
|
||||
{
|
||||
currentItem = currentList[indexInCurrentList];
|
||||
indexInCurrentList++;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// We're beyond all lists, stop
|
||||
currentItem = default(T);
|
||||
exceeded = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
public T Current => currentItem;
|
||||
|
||||
Object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (exceeded)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public struct NoAllocEnumerator<T> : IEnumerator<T>
|
||||
{
|
||||
private readonly IList<T> list;
|
||||
private int index;
|
||||
private T current;
|
||||
private bool exceeded;
|
||||
|
||||
public NoAllocEnumerator(IList<T> list) : this()
|
||||
{
|
||||
this.list = list;
|
||||
}
|
||||
|
||||
public void Dispose() { }
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (index < list.Count)
|
||||
{
|
||||
current = list[index];
|
||||
index++;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
index = list.Count + 1;
|
||||
current = default(T);
|
||||
exceeded = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public T Current => current;
|
||||
|
||||
Object IEnumerator.Current
|
||||
{
|
||||
get
|
||||
{
|
||||
if (exceeded)
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return Current;
|
||||
}
|
||||
}
|
||||
|
||||
void IEnumerator.Reset()
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public abstract class NonNullableCollection<T> : Collection<T>
|
||||
{
|
||||
protected override void InsertItem(int index, T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
base.InsertItem(index, item);
|
||||
}
|
||||
|
||||
protected override void SetItem(int index, T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
base.SetItem(index, item);
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> collection)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,169 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class NonNullableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IDictionary
|
||||
{
|
||||
public NonNullableDictionary()
|
||||
{
|
||||
dictionary = new Dictionary<TKey, TValue>();
|
||||
}
|
||||
|
||||
public NonNullableDictionary(int capacity)
|
||||
{
|
||||
dictionary = new Dictionary<TKey, TValue>(capacity);
|
||||
}
|
||||
|
||||
public NonNullableDictionary(IEqualityComparer<TKey> comparer)
|
||||
{
|
||||
dictionary = new Dictionary<TKey, TValue>(comparer);
|
||||
}
|
||||
|
||||
public NonNullableDictionary(IDictionary<TKey, TValue> dictionary)
|
||||
{
|
||||
this.dictionary = new Dictionary<TKey, TValue>(dictionary);
|
||||
}
|
||||
|
||||
public NonNullableDictionary(int capacity, IEqualityComparer<TKey> comparer)
|
||||
{
|
||||
dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
|
||||
}
|
||||
|
||||
public NonNullableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
|
||||
{
|
||||
this.dictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
|
||||
}
|
||||
|
||||
private readonly Dictionary<TKey, TValue> dictionary;
|
||||
|
||||
public TValue this[TKey key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return dictionary[key];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
dictionary[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
object IDictionary.this[object key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((IDictionary)dictionary)[key];
|
||||
}
|
||||
set
|
||||
{
|
||||
((IDictionary)dictionary)[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => dictionary.Count;
|
||||
|
||||
public bool IsSynchronized => ((ICollection)dictionary).IsSynchronized;
|
||||
|
||||
public object SyncRoot => ((ICollection)dictionary).SyncRoot;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public ICollection<TKey> Keys => dictionary.Keys;
|
||||
|
||||
ICollection IDictionary.Values => ((IDictionary)dictionary).Values;
|
||||
|
||||
ICollection IDictionary.Keys => ((IDictionary)dictionary).Keys;
|
||||
|
||||
public ICollection<TValue> Values => dictionary.Values;
|
||||
|
||||
public bool IsFixedSize => ((IDictionary)dictionary).IsFixedSize;
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
((ICollection)dictionary).CopyTo(array, index);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.Add(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Add(item);
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value)
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
dictionary.Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(object key, object value)
|
||||
{
|
||||
((IDictionary)dictionary).Add(key, value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
dictionary.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(object key)
|
||||
{
|
||||
return ((IDictionary)dictionary).Contains(key);
|
||||
}
|
||||
|
||||
IDictionaryEnumerator IDictionary.GetEnumerator()
|
||||
{
|
||||
return ((IDictionary)dictionary).GetEnumerator();
|
||||
}
|
||||
|
||||
public void Remove(object key)
|
||||
{
|
||||
((IDictionary)dictionary).Remove(key);
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Contains(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Contains(item);
|
||||
}
|
||||
|
||||
public bool ContainsKey(TKey key)
|
||||
{
|
||||
return dictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
void ICollection<KeyValuePair<TKey, TValue>>.CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
|
||||
{
|
||||
((ICollection<KeyValuePair<TKey, TValue>>)dictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
|
||||
{
|
||||
return dictionary.GetEnumerator();
|
||||
}
|
||||
|
||||
bool ICollection<KeyValuePair<TKey, TValue>>.Remove(KeyValuePair<TKey, TValue> item)
|
||||
{
|
||||
return ((ICollection<KeyValuePair<TKey, TValue>>)dictionary).Remove(item);
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
return dictionary.Remove(key);
|
||||
}
|
||||
|
||||
public bool TryGetValue(TKey key, out TValue value)
|
||||
{
|
||||
return dictionary.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return dictionary.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class NonNullableHashSet<T> : ISet<T>
|
||||
{
|
||||
public NonNullableHashSet()
|
||||
{
|
||||
set = new HashSet<T>();
|
||||
}
|
||||
|
||||
public NonNullableHashSet(IEqualityComparer<T> comparer)
|
||||
{
|
||||
set = new HashSet<T>(comparer);
|
||||
}
|
||||
|
||||
public NonNullableHashSet(IEnumerable<T> collection)
|
||||
{
|
||||
set = new HashSet<T>(collection);
|
||||
}
|
||||
|
||||
public NonNullableHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer)
|
||||
{
|
||||
set = new HashSet<T>(collection, comparer);
|
||||
}
|
||||
|
||||
private readonly HashSet<T> set;
|
||||
|
||||
public int Count => set.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public bool Add(T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
return set.Add(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
set.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
return set.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
set.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public void ExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
set.ExceptWith(other);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return set.GetEnumerator();
|
||||
}
|
||||
|
||||
public void IntersectWith(IEnumerable<T> other)
|
||||
{
|
||||
set.IntersectWith(other);
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
return set.IsProperSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
return set.IsProperSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other)
|
||||
{
|
||||
return set.IsSubsetOf(other);
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other)
|
||||
{
|
||||
return set.IsSupersetOf(other);
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other)
|
||||
{
|
||||
return set.Overlaps(other);
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
return set.Remove(item);
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other)
|
||||
{
|
||||
return set.SetEquals(other);
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<T> other)
|
||||
{
|
||||
set.SymmetricExceptWith(other);
|
||||
}
|
||||
|
||||
public void UnionWith(IEnumerable<T> other)
|
||||
{
|
||||
set.UnionWith(other);
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
((ICollection<T>)set).Add(item);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return ((IEnumerable)set).GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class NonNullableList<T> : IList<T>, IList
|
||||
{
|
||||
public NonNullableList()
|
||||
{
|
||||
list = new List<T>();
|
||||
}
|
||||
|
||||
public NonNullableList(int capacity)
|
||||
{
|
||||
list = new List<T>(capacity);
|
||||
}
|
||||
|
||||
public NonNullableList(IEnumerable<T> collection)
|
||||
{
|
||||
list = new List<T>(collection);
|
||||
}
|
||||
|
||||
private readonly List<T> list;
|
||||
|
||||
public T this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return list[index];
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(value));
|
||||
}
|
||||
|
||||
list[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
object IList.this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return ((IList)list)[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
((IList)list)[index] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public int Count => list.Count;
|
||||
|
||||
public bool IsSynchronized => ((ICollection)list).IsSynchronized;
|
||||
|
||||
public object SyncRoot => ((ICollection)list).SyncRoot;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public bool IsFixedSize => ((IList)list).IsFixedSize;
|
||||
|
||||
public void CopyTo(Array array, int index)
|
||||
{
|
||||
((ICollection)list).CopyTo(array, index);
|
||||
}
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
list.Add(item);
|
||||
}
|
||||
|
||||
public int Add(object value)
|
||||
{
|
||||
return ((IList)list).Add(value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
list.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(object value)
|
||||
{
|
||||
return ((IList)list).Contains(value);
|
||||
}
|
||||
|
||||
public int IndexOf(object value)
|
||||
{
|
||||
return ((IList)list).IndexOf(value);
|
||||
}
|
||||
|
||||
public void Insert(int index, object value)
|
||||
{
|
||||
((IList)list).Insert(index, value);
|
||||
}
|
||||
|
||||
public void Remove(object value)
|
||||
{
|
||||
((IList)list).Remove(value);
|
||||
}
|
||||
|
||||
public bool Contains(T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
return list.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex)
|
||||
{
|
||||
list.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
public int IndexOf(T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
return list.IndexOf(item);
|
||||
}
|
||||
|
||||
public void Insert(int index, T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
list.Insert(index, item);
|
||||
}
|
||||
|
||||
public bool Remove(T item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
return list.Remove(item);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
list.RemoveAt(index);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> collection)
|
||||
{
|
||||
foreach (var item in collection)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class VariantCollection<TBase, TImplementation> : ICollection<TBase> where TImplementation : TBase
|
||||
{
|
||||
public VariantCollection(ICollection<TImplementation> implementation)
|
||||
{
|
||||
if (implementation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(implementation));
|
||||
}
|
||||
|
||||
this.implementation = implementation;
|
||||
}
|
||||
|
||||
public ICollection<TImplementation> implementation { get; private set; }
|
||||
|
||||
public int Count => implementation.Count;
|
||||
|
||||
public bool IsReadOnly => implementation.IsReadOnly;
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerator<TBase> GetEnumerator()
|
||||
{
|
||||
foreach (var i in implementation)
|
||||
{
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TBase item)
|
||||
{
|
||||
if (!(item is TImplementation))
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
implementation.Add((TImplementation)item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
implementation.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(TBase item)
|
||||
{
|
||||
if (!(item is TImplementation))
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
return implementation.Contains((TImplementation)item);
|
||||
}
|
||||
|
||||
public bool Remove(TBase item)
|
||||
{
|
||||
if (!(item is TImplementation))
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
return implementation.Remove((TImplementation)item);
|
||||
}
|
||||
|
||||
public void CopyTo(TBase[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var implementationArray = new TImplementation[Count];
|
||||
implementation.CopyTo(implementationArray, 0);
|
||||
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
array[i + arrayIndex] = implementationArray[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,37 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class VariantKeyedCollection<TBase, TImplementation, TKey> :
|
||||
VariantCollection<TBase, TImplementation>,
|
||||
IKeyedCollection<TKey, TBase>
|
||||
where TImplementation : TBase
|
||||
{
|
||||
public VariantKeyedCollection(IKeyedCollection<TKey, TImplementation> implementation) : base(implementation)
|
||||
{
|
||||
this.implementation = implementation;
|
||||
}
|
||||
|
||||
public TBase this[TKey key] => implementation[key];
|
||||
|
||||
public new IKeyedCollection<TKey, TImplementation> implementation { get; private set; }
|
||||
|
||||
public bool TryGetValue(TKey key, out TBase value)
|
||||
{
|
||||
TImplementation implementationValue;
|
||||
var result = implementation.TryGetValue(key, out implementationValue);
|
||||
value = implementationValue;
|
||||
return result;
|
||||
}
|
||||
|
||||
public bool Contains(TKey key)
|
||||
{
|
||||
return implementation.Contains(key);
|
||||
}
|
||||
|
||||
public bool Remove(TKey key)
|
||||
{
|
||||
return implementation.Remove(key);
|
||||
}
|
||||
|
||||
TBase IKeyedCollection<TKey, TBase>.this[int index] => implementation[index];
|
||||
}
|
||||
}
|
@@ -0,0 +1,143 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class VariantList<TBase, TImplementation> : IList<TBase> where TImplementation : TBase
|
||||
{
|
||||
public VariantList(IList<TImplementation> implementation)
|
||||
{
|
||||
if (implementation == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(implementation));
|
||||
}
|
||||
|
||||
this.implementation = implementation;
|
||||
}
|
||||
|
||||
public TBase this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return implementation[index];
|
||||
}
|
||||
set
|
||||
{
|
||||
if (!(value is TImplementation))
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
implementation[index] = (TImplementation)value;
|
||||
}
|
||||
}
|
||||
|
||||
public IList<TImplementation> implementation { get; private set; }
|
||||
|
||||
public int Count => implementation.Count;
|
||||
|
||||
public bool IsReadOnly => implementation.IsReadOnly;
|
||||
|
||||
public void Add(TBase item)
|
||||
{
|
||||
if (!(item is TImplementation))
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
implementation.Add((TImplementation)item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
implementation.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(TBase item)
|
||||
{
|
||||
if (!(item is TImplementation))
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
return implementation.Contains((TImplementation)item);
|
||||
}
|
||||
|
||||
public bool Remove(TBase item)
|
||||
{
|
||||
if (!(item is TImplementation))
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
return implementation.Remove((TImplementation)item);
|
||||
}
|
||||
|
||||
public void CopyTo(TBase[] array, int arrayIndex)
|
||||
{
|
||||
if (array == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(array));
|
||||
}
|
||||
|
||||
if (arrayIndex < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayIndex));
|
||||
}
|
||||
|
||||
if (array.Length - arrayIndex < Count)
|
||||
{
|
||||
throw new ArgumentException();
|
||||
}
|
||||
|
||||
var implementationArray = new TImplementation[Count];
|
||||
implementation.CopyTo(implementationArray, 0);
|
||||
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
array[i + arrayIndex] = implementationArray[i];
|
||||
}
|
||||
}
|
||||
|
||||
public int IndexOf(TBase item)
|
||||
{
|
||||
if (!(item is TImplementation))
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
return implementation.IndexOf((TImplementation)item);
|
||||
}
|
||||
|
||||
public void Insert(int index, TBase item)
|
||||
{
|
||||
if (!(item is TImplementation))
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
implementation.Insert(index, (TImplementation)item);
|
||||
}
|
||||
|
||||
public void RemoveAt(int index)
|
||||
{
|
||||
implementation.RemoveAt(index);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator<TBase> IEnumerable<TBase>.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public NoAllocEnumerator<TBase> GetEnumerator()
|
||||
{
|
||||
return new NoAllocEnumerator<TBase>(this);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class WatchedList<T> : Collection<T>, INotifyCollectionChanged<T>
|
||||
{
|
||||
public event Action<T> ItemAdded;
|
||||
|
||||
public event Action<T> ItemRemoved;
|
||||
|
||||
public event Action CollectionChanged;
|
||||
|
||||
protected override void InsertItem(int index, T item)
|
||||
{
|
||||
base.InsertItem(index, item);
|
||||
ItemAdded?.Invoke(item);
|
||||
CollectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
protected override void RemoveItem(int index)
|
||||
{
|
||||
if (index < Count)
|
||||
{
|
||||
var item = this[index];
|
||||
base.RemoveItem(index);
|
||||
ItemRemoved?.Invoke(item);
|
||||
CollectionChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ClearItems()
|
||||
{
|
||||
while (Count > 0)
|
||||
{
|
||||
RemoveItem(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class ConnectionCollection<TConnection, TSource, TDestination> : ConnectionCollectionBase<TConnection, TSource, TDestination, List<TConnection>>
|
||||
where TConnection : IConnection<TSource, TDestination>
|
||||
{
|
||||
public ConnectionCollection() : base(new List<TConnection>()) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,234 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class ConnectionCollectionBase<TConnection, TSource, TDestination, TCollection> : IConnectionCollection<TConnection, TSource, TDestination>
|
||||
where TConnection : IConnection<TSource, TDestination>
|
||||
where TCollection : ICollection<TConnection>
|
||||
{
|
||||
public ConnectionCollectionBase(TCollection collection)
|
||||
{
|
||||
this.collection = collection;
|
||||
bySource = new Dictionary<TSource, List<TConnection>>();
|
||||
byDestination = new Dictionary<TDestination, List<TConnection>>();
|
||||
}
|
||||
|
||||
// Using lists instead of HashSet to allow access by index
|
||||
// instead of creating an enumeration and allocating memory
|
||||
// specifically for the "With*NoAlloc" methods, used
|
||||
// very often in flow graphs.
|
||||
private readonly Dictionary<TDestination, List<TConnection>> byDestination;
|
||||
private readonly Dictionary<TSource, List<TConnection>> bySource;
|
||||
protected readonly TCollection collection;
|
||||
|
||||
public IEnumerable<TConnection> this[TSource source] => WithSource(source);
|
||||
|
||||
public IEnumerable<TConnection> this[TDestination destination] => WithDestination(destination);
|
||||
|
||||
public int Count => collection.Count;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
public IEnumerator<TConnection> GetEnumerator()
|
||||
{
|
||||
return collection.GetEnumerator();
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
{
|
||||
return GetEnumerator();
|
||||
}
|
||||
|
||||
public IEnumerable<TConnection> WithSource(TSource source)
|
||||
{
|
||||
return WithSourceNoAlloc(source);
|
||||
}
|
||||
|
||||
public List<TConnection> WithSourceNoAlloc(TSource source)
|
||||
{
|
||||
Ensure.That(nameof(source)).IsNotNull(source);
|
||||
|
||||
if (bySource.TryGetValue(source, out var withSource))
|
||||
{
|
||||
return withSource;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Empty<TConnection>.list;
|
||||
}
|
||||
}
|
||||
|
||||
public TConnection SingleOrDefaultWithSource(TSource source)
|
||||
{
|
||||
Ensure.That(nameof(source)).IsNotNull(source);
|
||||
|
||||
if (bySource.TryGetValue(source, out var withSource))
|
||||
{
|
||||
if (withSource.Count == 1)
|
||||
{
|
||||
return withSource[0];
|
||||
}
|
||||
else if (withSource.Count == 0)
|
||||
{
|
||||
return default(TConnection);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return default(TConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<TConnection> WithDestination(TDestination destination)
|
||||
{
|
||||
return WithDestinationNoAlloc(destination);
|
||||
}
|
||||
|
||||
public List<TConnection> WithDestinationNoAlloc(TDestination destination)
|
||||
{
|
||||
Ensure.That(nameof(destination)).IsNotNull(destination);
|
||||
|
||||
if (byDestination.TryGetValue(destination, out var withDestination))
|
||||
{
|
||||
return withDestination;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Empty<TConnection>.list;
|
||||
}
|
||||
}
|
||||
|
||||
public TConnection SingleOrDefaultWithDestination(TDestination destination)
|
||||
{
|
||||
Ensure.That(nameof(destination)).IsNotNull(destination);
|
||||
|
||||
if (byDestination.TryGetValue(destination, out var withDestination))
|
||||
{
|
||||
if (withDestination.Count == 1)
|
||||
{
|
||||
return withDestination[0];
|
||||
}
|
||||
else if (withDestination.Count == 0)
|
||||
{
|
||||
return default(TConnection);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return default(TConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(TConnection item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
if (item.source == null)
|
||||
{
|
||||
throw new ArgumentNullException("item.source");
|
||||
}
|
||||
|
||||
if (item.destination == null)
|
||||
{
|
||||
throw new ArgumentNullException("item.destination");
|
||||
}
|
||||
|
||||
BeforeAdd(item);
|
||||
collection.Add(item);
|
||||
AddToDictionaries(item);
|
||||
AfterAdd(item);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
collection.Clear();
|
||||
bySource.Clear();
|
||||
byDestination.Clear();
|
||||
}
|
||||
|
||||
public bool Contains(TConnection item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
return collection.Contains(item);
|
||||
}
|
||||
|
||||
public void CopyTo(TConnection[] array, int arrayIndex)
|
||||
{
|
||||
collection.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public bool Remove(TConnection item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(item));
|
||||
}
|
||||
|
||||
if (item.source == null)
|
||||
{
|
||||
throw new ArgumentNullException("item.source");
|
||||
}
|
||||
|
||||
if (item.destination == null)
|
||||
{
|
||||
throw new ArgumentNullException("item.destination");
|
||||
}
|
||||
|
||||
if (!collection.Contains(item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
BeforeRemove(item);
|
||||
collection.Remove(item);
|
||||
RemoveFromDictionaries(item);
|
||||
AfterRemove(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual void BeforeAdd(TConnection item) { }
|
||||
protected virtual void AfterAdd(TConnection item) { }
|
||||
protected virtual void BeforeRemove(TConnection item) { }
|
||||
protected virtual void AfterRemove(TConnection item) { }
|
||||
|
||||
private void AddToDictionaries(TConnection item)
|
||||
{
|
||||
if (!bySource.ContainsKey(item.source))
|
||||
{
|
||||
bySource.Add(item.source, new List<TConnection>());
|
||||
}
|
||||
|
||||
bySource[item.source].Add(item);
|
||||
|
||||
if (!byDestination.ContainsKey(item.destination))
|
||||
{
|
||||
byDestination.Add(item.destination, new List<TConnection>());
|
||||
}
|
||||
|
||||
byDestination[item.destination].Add(item);
|
||||
}
|
||||
|
||||
private void RemoveFromDictionaries(TConnection item)
|
||||
{
|
||||
bySource[item.source].Remove(item);
|
||||
byDestination[item.destination].Remove(item);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class GraphConnectionCollection<TConnection, TSource, TDestination> :
|
||||
ConnectionCollectionBase<TConnection, TSource, TDestination, GraphElementCollection<TConnection>>,
|
||||
IGraphElementCollection<TConnection>
|
||||
where TConnection : IConnection<TSource, TDestination>, IGraphElement
|
||||
{
|
||||
public GraphConnectionCollection(IGraph graph) : base(new GraphElementCollection<TConnection>(graph))
|
||||
{
|
||||
// The issue of reusing GEC as the internal collection a CCB is that
|
||||
// the add / remove events will NOT be in sync with the CCB's dictionaries
|
||||
// if we just watched the collection's insertion.
|
||||
// Therefore, we must provide a way to let the CCB proxy its own events
|
||||
// and to disable our the GEC's events by default.
|
||||
collection.ProxyCollectionChange = true;
|
||||
}
|
||||
|
||||
TConnection IKeyedCollection<Guid, TConnection>.this[Guid key] => collection[key];
|
||||
|
||||
TConnection IKeyedCollection<Guid, TConnection>.this[int index] => collection[index];
|
||||
|
||||
public bool TryGetValue(Guid key, out TConnection value)
|
||||
{
|
||||
return collection.TryGetValue(key, out value);
|
||||
}
|
||||
|
||||
public bool Contains(Guid key)
|
||||
{
|
||||
return collection.Contains(key);
|
||||
}
|
||||
|
||||
public bool Remove(Guid key)
|
||||
{
|
||||
if (Contains(key))
|
||||
{
|
||||
// Call base remove to remove from dictionaries as well
|
||||
return Remove(collection[key]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public event Action<TConnection> ItemAdded
|
||||
{
|
||||
add { collection.ItemAdded += value; }
|
||||
remove { collection.ItemAdded -= value; }
|
||||
}
|
||||
|
||||
public event Action<TConnection> ItemRemoved
|
||||
{
|
||||
add { collection.ItemRemoved += value; }
|
||||
remove { collection.ItemRemoved -= value; }
|
||||
}
|
||||
|
||||
public event Action CollectionChanged
|
||||
{
|
||||
add { collection.CollectionChanged += value; }
|
||||
remove { collection.CollectionChanged -= value; }
|
||||
}
|
||||
|
||||
protected override void BeforeAdd(TConnection item)
|
||||
{
|
||||
collection.BeforeAdd(item);
|
||||
}
|
||||
|
||||
protected override void AfterAdd(TConnection item)
|
||||
{
|
||||
collection.AfterAdd(item);
|
||||
}
|
||||
|
||||
protected override void BeforeRemove(TConnection item)
|
||||
{
|
||||
collection.BeforeRemove(item);
|
||||
}
|
||||
|
||||
protected override void AfterRemove(TConnection item)
|
||||
{
|
||||
collection.AfterRemove(item);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IConnection<out TSource, out TDestination>
|
||||
{
|
||||
TSource source { get; }
|
||||
TDestination destination { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IConnectionCollection<TConnection, TSource, TDestination> : ICollection<TConnection>
|
||||
where TConnection : IConnection<TSource, TDestination>
|
||||
{
|
||||
IEnumerable<TConnection> this[TSource source] { get; }
|
||||
IEnumerable<TConnection> this[TDestination destination] { get; }
|
||||
IEnumerable<TConnection> WithSource(TSource source);
|
||||
IEnumerable<TConnection> WithDestination(TDestination destination);
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public class InvalidConnectionException : Exception
|
||||
{
|
||||
public InvalidConnectionException() : base("") { }
|
||||
public InvalidConnectionException(string message) : base(message) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public interface IDecoratorAttribute
|
||||
{
|
||||
Type type { get; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,213 @@
|
||||
// Copyright Christophe Bertrand.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Unity.VisualScripting.AssemblyQualifiedNameParser
|
||||
{
|
||||
public class ParsedAssemblyQualifiedName
|
||||
{
|
||||
public string AssemblyDescriptionString { get; }
|
||||
|
||||
public string TypeName { get; private set; }
|
||||
|
||||
public string ShortAssemblyName { get; }
|
||||
|
||||
public string Version { get; }
|
||||
|
||||
public string Culture { get; }
|
||||
|
||||
public string PublicKeyToken { get; }
|
||||
|
||||
public List<ParsedAssemblyQualifiedName> GenericParameters { get; } = new List<ParsedAssemblyQualifiedName>();
|
||||
|
||||
public int GenericParameterCount { get; }
|
||||
|
||||
public ParsedAssemblyQualifiedName(string AssemblyQualifiedName)
|
||||
{
|
||||
var typeNameLength = AssemblyQualifiedName.Length;
|
||||
var hasAssemblyDescription = false;
|
||||
|
||||
var rootBlock = new Block();
|
||||
{
|
||||
var depth = 0;
|
||||
var currentBlock = rootBlock;
|
||||
|
||||
for (var index = 0; index < AssemblyQualifiedName.Length; ++index)
|
||||
{
|
||||
var c = AssemblyQualifiedName[index];
|
||||
|
||||
if (c == '[')
|
||||
{
|
||||
if (AssemblyQualifiedName[index + 1] == ']') // Array type // TODO (LAZLO): This won't detect multidimensional array, but FS can't handle them anyway
|
||||
{
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (depth == 0)
|
||||
{
|
||||
typeNameLength = index;
|
||||
}
|
||||
|
||||
++depth;
|
||||
|
||||
var innerBlock = new Block
|
||||
{
|
||||
startIndex = index + 1,
|
||||
level = depth,
|
||||
parentBlock = currentBlock
|
||||
};
|
||||
|
||||
currentBlock.innerBlocks.Add(innerBlock);
|
||||
|
||||
currentBlock = innerBlock;
|
||||
}
|
||||
}
|
||||
else if (c == ']')
|
||||
{
|
||||
currentBlock.endIndex = index - 1;
|
||||
|
||||
if (AssemblyQualifiedName[currentBlock.startIndex] != '[')
|
||||
{
|
||||
currentBlock.parsedAssemblyQualifiedName = new ParsedAssemblyQualifiedName(AssemblyQualifiedName.Substring(currentBlock.startIndex, index - currentBlock.startIndex));
|
||||
|
||||
if (depth == 2)
|
||||
{
|
||||
GenericParameters.Add(currentBlock.parsedAssemblyQualifiedName);
|
||||
}
|
||||
}
|
||||
|
||||
currentBlock = currentBlock.parentBlock;
|
||||
--depth;
|
||||
}
|
||||
else if (depth == 0 && c == ',')
|
||||
{
|
||||
typeNameLength = index;
|
||||
hasAssemblyDescription = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypeName = AssemblyQualifiedName.Substring(0, typeNameLength);
|
||||
|
||||
var tickIndex = TypeName.IndexOf('`');
|
||||
|
||||
if (tickIndex >= 0)
|
||||
{
|
||||
GenericParameterCount = int.Parse(TypeName.Substring(tickIndex + 1));
|
||||
TypeName = TypeName.Substring(0, tickIndex);
|
||||
}
|
||||
|
||||
if (hasAssemblyDescription)
|
||||
{
|
||||
AssemblyDescriptionString = AssemblyQualifiedName.Substring(typeNameLength + 2);
|
||||
|
||||
var parts = AssemblyDescriptionString.Split(',')
|
||||
.Select(x => x.Trim())
|
||||
.ToList();
|
||||
|
||||
Version = LookForPairThenRemove(parts, "Version");
|
||||
Culture = LookForPairThenRemove(parts, "Culture");
|
||||
PublicKeyToken = LookForPairThenRemove(parts, "PublicKeyToken");
|
||||
|
||||
if (parts.Count > 0)
|
||||
{
|
||||
ShortAssemblyName = parts[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Block
|
||||
{
|
||||
internal int startIndex;
|
||||
|
||||
internal int endIndex;
|
||||
|
||||
internal int level;
|
||||
|
||||
internal Block parentBlock;
|
||||
|
||||
internal readonly List<Block> innerBlocks = new List<Block>();
|
||||
|
||||
internal ParsedAssemblyQualifiedName parsedAssemblyQualifiedName;
|
||||
}
|
||||
|
||||
private static string LookForPairThenRemove(List<string> strings, string Name)
|
||||
{
|
||||
for (var istr = 0; istr < strings.Count; istr++)
|
||||
{
|
||||
var s = strings[istr];
|
||||
var i = s.IndexOf(Name);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
var i2 = s.IndexOf('=');
|
||||
|
||||
if (i2 > 0)
|
||||
{
|
||||
var ret = s.Substring(i2 + 1);
|
||||
strings.RemoveAt(istr);
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public void Replace(string oldTypeName, string newTypeName)
|
||||
{
|
||||
if (TypeName == oldTypeName)
|
||||
{
|
||||
TypeName = newTypeName;
|
||||
}
|
||||
|
||||
foreach (var genericParameter in GenericParameters)
|
||||
{
|
||||
genericParameter.Replace(oldTypeName, newTypeName);
|
||||
}
|
||||
}
|
||||
|
||||
private string ToString(bool includeAssemblyDescription)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
sb.Append(TypeName);
|
||||
|
||||
if (GenericParameters.Count > 0)
|
||||
{
|
||||
sb.Append("`");
|
||||
|
||||
sb.Append(GenericParameterCount);
|
||||
|
||||
sb.Append("[[");
|
||||
|
||||
foreach (var genericParameter in GenericParameters)
|
||||
{
|
||||
sb.Append(genericParameter.ToString(true));
|
||||
}
|
||||
|
||||
sb.Append("]]");
|
||||
}
|
||||
|
||||
if (includeAssemblyDescription)
|
||||
{
|
||||
sb.Append(", ");
|
||||
|
||||
sb.Append(AssemblyDescriptionString);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return ToString(false);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
#if !NO_UNITY
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
public static AnimationCurve_DirectConverter Register_AnimationCurve_DirectConverter;
|
||||
}
|
||||
|
||||
public class AnimationCurve_DirectConverter : fsDirectConverter<AnimationCurve>
|
||||
{
|
||||
protected override fsResult DoSerialize(AnimationCurve model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "keys", model.keys);
|
||||
result += SerializeMember(serialized, null, "preWrapMode", model.preWrapMode);
|
||||
result += SerializeMember(serialized, null, "postWrapMode", model.postWrapMode);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref AnimationCurve model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
var t0 = model.keys;
|
||||
result += DeserializeMember(data, null, "keys", out t0);
|
||||
model.keys = t0;
|
||||
|
||||
var t1 = model.preWrapMode;
|
||||
result += DeserializeMember(data, null, "preWrapMode", out t1);
|
||||
model.preWrapMode = t1;
|
||||
|
||||
var t2 = model.postWrapMode;
|
||||
result += DeserializeMember(data, null, "postWrapMode", out t2);
|
||||
model.postWrapMode = t2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new AnimationCurve();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,47 @@
|
||||
#if !NO_UNITY
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
public static Bounds_DirectConverter Register_Bounds_DirectConverter;
|
||||
}
|
||||
|
||||
public class Bounds_DirectConverter : fsDirectConverter<Bounds>
|
||||
{
|
||||
protected override fsResult DoSerialize(Bounds model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "center", model.center);
|
||||
result += SerializeMember(serialized, null, "size", model.size);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref Bounds model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
var t0 = model.center;
|
||||
result += DeserializeMember(data, null, "center", out t0);
|
||||
model.center = t0;
|
||||
|
||||
var t1 = model.size;
|
||||
result += DeserializeMember(data, null, "size", out t1);
|
||||
model.size = t1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new Bounds();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@@ -0,0 +1,46 @@
|
||||
#if !NO_UNITY
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
public static GUIStyleState_DirectConverter Register_GUIStyleState_DirectConverter;
|
||||
}
|
||||
|
||||
public class GUIStyleState_DirectConverter : fsDirectConverter<GUIStyleState>
|
||||
{
|
||||
protected override fsResult DoSerialize(GUIStyleState model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "background", model.background);
|
||||
result += SerializeMember(serialized, null, "textColor", model.textColor);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref GUIStyleState model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
var t0 = model.background;
|
||||
result += DeserializeMember(data, null, "background", out t0);
|
||||
model.background = t0;
|
||||
|
||||
var t2 = model.textColor;
|
||||
result += DeserializeMember(data, null, "textColor", out t2);
|
||||
model.textColor = t2;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new GUIStyleState();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,166 @@
|
||||
#if !NO_UNITY
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
public static GUIStyle_DirectConverter Register_GUIStyle_DirectConverter;
|
||||
}
|
||||
|
||||
public class GUIStyle_DirectConverter : fsDirectConverter<GUIStyle>
|
||||
{
|
||||
protected override fsResult DoSerialize(GUIStyle model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "active", model.active);
|
||||
result += SerializeMember(serialized, null, "alignment", model.alignment);
|
||||
result += SerializeMember(serialized, null, "border", model.border);
|
||||
result += SerializeMember(serialized, null, "clipping", model.clipping);
|
||||
result += SerializeMember(serialized, null, "contentOffset", model.contentOffset);
|
||||
result += SerializeMember(serialized, null, "fixedHeight", model.fixedHeight);
|
||||
result += SerializeMember(serialized, null, "fixedWidth", model.fixedWidth);
|
||||
result += SerializeMember(serialized, null, "focused", model.focused);
|
||||
result += SerializeMember(serialized, null, "font", model.font);
|
||||
result += SerializeMember(serialized, null, "fontSize", model.fontSize);
|
||||
result += SerializeMember(serialized, null, "fontStyle", model.fontStyle);
|
||||
result += SerializeMember(serialized, null, "hover", model.hover);
|
||||
result += SerializeMember(serialized, null, "imagePosition", model.imagePosition);
|
||||
result += SerializeMember(serialized, null, "margin", model.margin);
|
||||
result += SerializeMember(serialized, null, "name", model.name);
|
||||
result += SerializeMember(serialized, null, "normal", model.normal);
|
||||
result += SerializeMember(serialized, null, "onActive", model.onActive);
|
||||
result += SerializeMember(serialized, null, "onFocused", model.onFocused);
|
||||
result += SerializeMember(serialized, null, "onHover", model.onHover);
|
||||
result += SerializeMember(serialized, null, "onNormal", model.onNormal);
|
||||
result += SerializeMember(serialized, null, "overflow", model.overflow);
|
||||
result += SerializeMember(serialized, null, "padding", model.padding);
|
||||
result += SerializeMember(serialized, null, "richText", model.richText);
|
||||
result += SerializeMember(serialized, null, "stretchHeight", model.stretchHeight);
|
||||
result += SerializeMember(serialized, null, "stretchWidth", model.stretchWidth);
|
||||
result += SerializeMember(serialized, null, "wordWrap", model.wordWrap);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref GUIStyle model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
var t0 = model.active;
|
||||
result += DeserializeMember(data, null, "active", out t0);
|
||||
model.active = t0;
|
||||
|
||||
var t2 = model.alignment;
|
||||
result += DeserializeMember(data, null, "alignment", out t2);
|
||||
model.alignment = t2;
|
||||
|
||||
var t3 = model.border;
|
||||
result += DeserializeMember(data, null, "border", out t3);
|
||||
model.border = t3;
|
||||
|
||||
var t4 = model.clipping;
|
||||
result += DeserializeMember(data, null, "clipping", out t4);
|
||||
model.clipping = t4;
|
||||
|
||||
var t5 = model.contentOffset;
|
||||
result += DeserializeMember(data, null, "contentOffset", out t5);
|
||||
model.contentOffset = t5;
|
||||
|
||||
var t6 = model.fixedHeight;
|
||||
result += DeserializeMember(data, null, "fixedHeight", out t6);
|
||||
model.fixedHeight = t6;
|
||||
|
||||
var t7 = model.fixedWidth;
|
||||
result += DeserializeMember(data, null, "fixedWidth", out t7);
|
||||
model.fixedWidth = t7;
|
||||
|
||||
var t8 = model.focused;
|
||||
result += DeserializeMember(data, null, "focused", out t8);
|
||||
model.focused = t8;
|
||||
|
||||
var t9 = model.font;
|
||||
result += DeserializeMember(data, null, "font", out t9);
|
||||
model.font = t9;
|
||||
|
||||
var t10 = model.fontSize;
|
||||
result += DeserializeMember(data, null, "fontSize", out t10);
|
||||
model.fontSize = t10;
|
||||
|
||||
var t11 = model.fontStyle;
|
||||
result += DeserializeMember(data, null, "fontStyle", out t11);
|
||||
model.fontStyle = t11;
|
||||
|
||||
var t12 = model.hover;
|
||||
result += DeserializeMember(data, null, "hover", out t12);
|
||||
model.hover = t12;
|
||||
|
||||
var t13 = model.imagePosition;
|
||||
result += DeserializeMember(data, null, "imagePosition", out t13);
|
||||
model.imagePosition = t13;
|
||||
|
||||
var t16 = model.margin;
|
||||
result += DeserializeMember(data, null, "margin", out t16);
|
||||
model.margin = t16;
|
||||
|
||||
var t17 = model.name;
|
||||
result += DeserializeMember(data, null, "name", out t17);
|
||||
model.name = t17;
|
||||
|
||||
var t18 = model.normal;
|
||||
result += DeserializeMember(data, null, "normal", out t18);
|
||||
model.normal = t18;
|
||||
|
||||
var t19 = model.onActive;
|
||||
result += DeserializeMember(data, null, "onActive", out t19);
|
||||
model.onActive = t19;
|
||||
|
||||
var t20 = model.onFocused;
|
||||
result += DeserializeMember(data, null, "onFocused", out t20);
|
||||
model.onFocused = t20;
|
||||
|
||||
var t21 = model.onHover;
|
||||
result += DeserializeMember(data, null, "onHover", out t21);
|
||||
model.onHover = t21;
|
||||
|
||||
var t22 = model.onNormal;
|
||||
result += DeserializeMember(data, null, "onNormal", out t22);
|
||||
model.onNormal = t22;
|
||||
|
||||
var t23 = model.overflow;
|
||||
result += DeserializeMember(data, null, "overflow", out t23);
|
||||
model.overflow = t23;
|
||||
|
||||
var t24 = model.padding;
|
||||
result += DeserializeMember(data, null, "padding", out t24);
|
||||
model.padding = t24;
|
||||
|
||||
var t25 = model.richText;
|
||||
result += DeserializeMember(data, null, "richText", out t25);
|
||||
model.richText = t25;
|
||||
|
||||
var t26 = model.stretchHeight;
|
||||
result += DeserializeMember(data, null, "stretchHeight", out t26);
|
||||
model.stretchHeight = t26;
|
||||
|
||||
var t27 = model.stretchWidth;
|
||||
result += DeserializeMember(data, null, "stretchWidth", out t27);
|
||||
model.stretchWidth = t27;
|
||||
|
||||
var t28 = model.wordWrap;
|
||||
result += DeserializeMember(data, null, "wordWrap", out t28);
|
||||
model.wordWrap = t28;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new GUIStyle();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,46 @@
|
||||
#if !NO_UNITY
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
public static Gradient_DirectConverter Register_Gradient_DirectConverter;
|
||||
}
|
||||
|
||||
public class Gradient_DirectConverter : fsDirectConverter<Gradient>
|
||||
{
|
||||
protected override fsResult DoSerialize(Gradient model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "alphaKeys", model.alphaKeys);
|
||||
result += SerializeMember(serialized, null, "colorKeys", model.colorKeys);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref Gradient model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
var t0 = model.alphaKeys;
|
||||
result += DeserializeMember(data, null, "alphaKeys", out t0);
|
||||
model.alphaKeys = t0;
|
||||
|
||||
var t1 = model.colorKeys;
|
||||
result += DeserializeMember(data, null, "colorKeys", out t1);
|
||||
model.colorKeys = t1;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new Gradient();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,69 @@
|
||||
#if !NO_UNITY
|
||||
#if PACKAGE_INPUT_SYSTEM_EXISTS
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEngine.InputSystem;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public static InputAction_DirectConverter Register_InputAction_DirectConverter;
|
||||
}
|
||||
|
||||
[UsedImplicitly]
|
||||
public class InputAction_DirectConverter : fsDirectConverter<InputAction>
|
||||
{
|
||||
protected override fsResult DoSerialize(InputAction model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "id", model.id.ToString());
|
||||
result += SerializeMember(serialized, null, "name", model.name.ToString());
|
||||
result += SerializeMember(serialized, null, "expectedControlType", model.expectedControlType);
|
||||
result += SerializeMember(serialized, null, "type", model.type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref InputAction model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += DeserializeMember(data, null, "id", out string actionId);
|
||||
result += DeserializeMember(data, null, "name", out string actionName);
|
||||
result += DeserializeMember(data, null, "expectedControlType", out string expectedControlType);
|
||||
result += DeserializeMember(data, null, "type", out InputActionType type);
|
||||
|
||||
model = MakeInputActionWithId(actionId, actionName, expectedControlType, type);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fake InputAction. Ports with an editor MUST serialize a value of the port's type, even if a GUID
|
||||
/// would suffice in that case
|
||||
/// </summary>
|
||||
public static InputAction MakeInputActionWithId(string actionId, string actionName, string expectedControlType, InputActionType type)
|
||||
{
|
||||
var model = new InputAction();
|
||||
typeof(InputAction).GetField("m_Id", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(model, actionId);
|
||||
typeof(InputAction).GetField("m_Name", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(model, actionName);
|
||||
typeof(InputAction).GetField("m_Type", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(model, type);
|
||||
model.expectedControlType = expectedControlType;
|
||||
return model;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new InputAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
@@ -0,0 +1,62 @@
|
||||
#if !NO_UNITY
|
||||
#pragma warning disable 618
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
public static Keyframe_DirectConverter Register_Keyframe_DirectConverter;
|
||||
}
|
||||
|
||||
public class Keyframe_DirectConverter : fsDirectConverter<Keyframe>
|
||||
{
|
||||
protected override fsResult DoSerialize(Keyframe model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "time", model.time);
|
||||
result += SerializeMember(serialized, null, "value", model.value);
|
||||
result += SerializeMember(serialized, null, "tangentMode", model.tangentMode);
|
||||
result += SerializeMember(serialized, null, "inTangent", model.inTangent);
|
||||
result += SerializeMember(serialized, null, "outTangent", model.outTangent);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref Keyframe model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
var t0 = model.time;
|
||||
result += DeserializeMember(data, null, "time", out t0);
|
||||
model.time = t0;
|
||||
|
||||
var t1 = model.value;
|
||||
result += DeserializeMember(data, null, "value", out t1);
|
||||
model.value = t1;
|
||||
|
||||
var t2 = model.tangentMode;
|
||||
result += DeserializeMember(data, null, "tangentMode", out t2);
|
||||
model.tangentMode = t2;
|
||||
|
||||
var t3 = model.inTangent;
|
||||
result += DeserializeMember(data, null, "inTangent", out t3);
|
||||
model.inTangent = t3;
|
||||
|
||||
var t4 = model.outTangent;
|
||||
result += DeserializeMember(data, null, "outTangent", out t4);
|
||||
model.outTangent = t4;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new Keyframe();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,41 @@
|
||||
#if !NO_UNITY
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
public static LayerMask_DirectConverter Register_LayerMask_DirectConverter;
|
||||
}
|
||||
|
||||
public class LayerMask_DirectConverter : fsDirectConverter<LayerMask>
|
||||
{
|
||||
protected override fsResult DoSerialize(LayerMask model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "value", model.value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref LayerMask model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
var t0 = model.value;
|
||||
result += DeserializeMember(data, null, "value", out t0);
|
||||
model.value = t0;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new LayerMask();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,56 @@
|
||||
#if !NO_UNITY
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
public static RectOffset_DirectConverter Register_RectOffset_DirectConverter;
|
||||
}
|
||||
|
||||
public class RectOffset_DirectConverter : fsDirectConverter<RectOffset>
|
||||
{
|
||||
protected override fsResult DoSerialize(RectOffset model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "bottom", model.bottom);
|
||||
result += SerializeMember(serialized, null, "left", model.left);
|
||||
result += SerializeMember(serialized, null, "right", model.right);
|
||||
result += SerializeMember(serialized, null, "top", model.top);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref RectOffset model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
var t0 = model.bottom;
|
||||
result += DeserializeMember(data, null, "bottom", out t0);
|
||||
model.bottom = t0;
|
||||
|
||||
var t2 = model.left;
|
||||
result += DeserializeMember(data, null, "left", out t2);
|
||||
model.left = t2;
|
||||
|
||||
var t3 = model.right;
|
||||
result += DeserializeMember(data, null, "right", out t3);
|
||||
model.right = t3;
|
||||
|
||||
var t4 = model.top;
|
||||
result += DeserializeMember(data, null, "top", out t4);
|
||||
model.top = t4;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new RectOffset();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,56 @@
|
||||
#if !NO_UNITY
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
public static Rect_DirectConverter Register_Rect_DirectConverter;
|
||||
}
|
||||
|
||||
public class Rect_DirectConverter : fsDirectConverter<Rect>
|
||||
{
|
||||
protected override fsResult DoSerialize(Rect model, Dictionary<string, fsData> serialized)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
result += SerializeMember(serialized, null, "xMin", model.xMin);
|
||||
result += SerializeMember(serialized, null, "yMin", model.yMin);
|
||||
result += SerializeMember(serialized, null, "xMax", model.xMax);
|
||||
result += SerializeMember(serialized, null, "yMax", model.yMax);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref Rect model)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
var t0 = model.xMin;
|
||||
result += DeserializeMember(data, null, "xMin", out t0);
|
||||
model.xMin = t0;
|
||||
|
||||
var t1 = model.yMin;
|
||||
result += DeserializeMember(data, null, "yMin", out t1);
|
||||
model.yMin = t1;
|
||||
|
||||
var t2 = model.xMax;
|
||||
result += DeserializeMember(data, null, "xMax", out t2);
|
||||
model.xMax = t2;
|
||||
|
||||
var t3 = model.yMax;
|
||||
result += DeserializeMember(data, null, "yMax", out t3);
|
||||
model.yMax = t3;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new Rect();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,56 @@
|
||||
#if !NO_UNITY && UNITY_5_3_OR_NEWER
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
partial class fsConverterRegistrar
|
||||
{
|
||||
// Disable the converter for the time being. Unity's JsonUtility API
|
||||
// cannot be called from within a C# ISerializationCallbackReceiver
|
||||
// callback.
|
||||
|
||||
// public static Internal.Converters.UnityEvent_Converter
|
||||
// Register_UnityEvent_Converter;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer.Internal.Converters
|
||||
{
|
||||
// The standard FS reflection converter has started causing Unity to crash
|
||||
// when processing UnityEvent. We can send the serialization through
|
||||
// JsonUtility which appears to work correctly instead.
|
||||
//
|
||||
// We have to support legacy serialization formats so importing works as
|
||||
// expected.
|
||||
public class UnityEvent_Converter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return typeof(UnityEvent).Resolve().IsAssignableFrom(type.Resolve()) && type.Resolve().IsGenericType == false;
|
||||
}
|
||||
|
||||
public override bool RequestCycleSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
Type objectType = (Type)instance;
|
||||
|
||||
fsResult result = fsResult.Success;
|
||||
instance = JsonUtility.FromJson(fsJsonPrinter.CompressedJson(data), objectType);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
fsResult result = fsResult.Success;
|
||||
serialized = fsJsonParser.Parse(JsonUtility.ToJson(instance));
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
public class fsArrayConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return type.IsArray;
|
||||
}
|
||||
|
||||
public override bool RequestCycleSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool RequestInheritanceSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
// note: IList[index] is **significantly** faster than Array.Get, so
|
||||
// make sure we use that instead.
|
||||
|
||||
IList arr = (Array)instance;
|
||||
var elementType = storageType.GetElementType();
|
||||
|
||||
var result = fsResult.Success;
|
||||
|
||||
serialized = fsData.CreateList(arr.Count);
|
||||
var serializedList = serialized.AsList;
|
||||
|
||||
for (var i = 0; i < arr.Count; ++i)
|
||||
{
|
||||
var item = arr[i];
|
||||
|
||||
fsData serializedItem;
|
||||
|
||||
var itemResult = Serializer.TrySerialize(elementType, item, out serializedItem);
|
||||
result.AddMessages(itemResult);
|
||||
if (itemResult.Failed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
serializedList.Add(serializedItem);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
// Verify that we actually have an List
|
||||
if ((result += CheckType(data, fsDataType.Array)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var elementType = storageType.GetElementType();
|
||||
|
||||
var serializedList = data.AsList;
|
||||
var list = new ArrayList(serializedList.Count);
|
||||
var existingCount = list.Count;
|
||||
|
||||
for (var i = 0; i < serializedList.Count; ++i)
|
||||
{
|
||||
var serializedItem = serializedList[i];
|
||||
object deserialized = null;
|
||||
if (i < existingCount)
|
||||
{
|
||||
deserialized = list[i];
|
||||
}
|
||||
|
||||
var itemResult = Serializer.TryDeserialize(serializedItem, elementType, ref deserialized);
|
||||
result.AddMessages(itemResult);
|
||||
if (itemResult.Failed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (i < existingCount)
|
||||
{
|
||||
list[i] = deserialized;
|
||||
}
|
||||
else
|
||||
{
|
||||
list.Add(deserialized);
|
||||
}
|
||||
}
|
||||
|
||||
instance = list.ToArray(elementType);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return fsMetaType.Get(Serializer.Config, storageType).CreateInstance();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Supports serialization for DateTime, DateTimeOffset, and TimeSpan.
|
||||
/// </summary>
|
||||
public class fsDateConverter : fsConverter
|
||||
{
|
||||
private string DateTimeFormatString => Serializer.Config.CustomDateTimeFormatString ?? DefaultDateTimeFormatString;
|
||||
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return
|
||||
type == typeof(DateTime) ||
|
||||
type == typeof(DateTimeOffset) ||
|
||||
type == typeof(TimeSpan);
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
if (instance is DateTime)
|
||||
{
|
||||
var dateTime = (DateTime)instance;
|
||||
serialized = new fsData(dateTime.ToString(DateTimeFormatString));
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
if (instance is DateTimeOffset)
|
||||
{
|
||||
var dateTimeOffset = (DateTimeOffset)instance;
|
||||
serialized = new fsData(dateTimeOffset.ToString(DateTimeOffsetFormatString));
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
if (instance is TimeSpan)
|
||||
{
|
||||
var timeSpan = (TimeSpan)instance;
|
||||
serialized = new fsData(timeSpan.ToString());
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("FullSerializer Internal Error -- Unexpected serialization type");
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
if (data.IsString == false)
|
||||
{
|
||||
return fsResult.Fail("Date deserialization requires a string, not " + data.Type);
|
||||
}
|
||||
|
||||
if (storageType == typeof(DateTime))
|
||||
{
|
||||
DateTime result;
|
||||
if (DateTime.TryParse(data.AsString, null, DateTimeStyles.RoundtripKind, out result))
|
||||
{
|
||||
instance = result;
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
// DateTime.TryParse can fail for some valid DateTime instances.
|
||||
// Try to use Convert.ToDateTime.
|
||||
if (fsGlobalConfig.AllowInternalExceptions)
|
||||
{
|
||||
try
|
||||
{
|
||||
instance = Convert.ToDateTime(data.AsString);
|
||||
return fsResult.Success;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return fsResult.Fail("Unable to parse " + data.AsString + " into a DateTime; got exception " + e);
|
||||
}
|
||||
}
|
||||
|
||||
return fsResult.Fail("Unable to parse " + data.AsString + " into a DateTime");
|
||||
}
|
||||
|
||||
if (storageType == typeof(DateTimeOffset))
|
||||
{
|
||||
DateTimeOffset result;
|
||||
if (DateTimeOffset.TryParse(data.AsString, null, DateTimeStyles.RoundtripKind, out result))
|
||||
{
|
||||
instance = result;
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
return fsResult.Fail("Unable to parse " + data.AsString + " into a DateTimeOffset");
|
||||
}
|
||||
|
||||
if (storageType == typeof(TimeSpan))
|
||||
{
|
||||
TimeSpan result;
|
||||
if (TimeSpan.TryParse(data.AsString, out result))
|
||||
{
|
||||
instance = result;
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
return fsResult.Fail("Unable to parse " + data.AsString + " into a TimeSpan");
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("FullSerializer Internal Error -- Unexpected deserialization type");
|
||||
}
|
||||
|
||||
// The format strings that we use when serializing DateTime and
|
||||
// DateTimeOffset types.
|
||||
private const string DefaultDateTimeFormatString = @"o";
|
||||
private const string DateTimeOffsetFormatString = @"o";
|
||||
}
|
||||
}
|
@@ -0,0 +1,232 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
// While the generic IEnumerable converter can handle dictionaries, we
|
||||
// process them separately here because we support a few more advanced
|
||||
// use-cases with dictionaries, such as inline strings. Further, dictionary
|
||||
// processing in general is a bit more advanced because a few of the
|
||||
// collection implementations are buggy.
|
||||
public class fsDictionaryConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return typeof(IDictionary).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return fsMetaType.Get(Serializer.Config, storageType).CreateInstance();
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance_, Type storageType)
|
||||
{
|
||||
var instance = (IDictionary)instance_;
|
||||
var result = fsResult.Success;
|
||||
|
||||
Type keyStorageType, valueStorageType;
|
||||
GetKeyValueTypes(instance.GetType(), out keyStorageType, out valueStorageType);
|
||||
|
||||
if (data.IsList)
|
||||
{
|
||||
var list = data.AsList;
|
||||
for (var i = 0; i < list.Count; ++i)
|
||||
{
|
||||
var item = list[i];
|
||||
|
||||
fsData keyData, valueData;
|
||||
if ((result += CheckType(item, fsDataType.Object)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if ((result += CheckKey(item, "Key", out keyData)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if ((result += CheckKey(item, "Value", out valueData)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
object keyInstance = null, valueInstance = null;
|
||||
if ((result += Serializer.TryDeserialize(keyData, keyStorageType, ref keyInstance)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if ((result += Serializer.TryDeserialize(valueData, valueStorageType, ref valueInstance)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
AddItemToDictionary(instance, keyInstance, valueInstance);
|
||||
}
|
||||
}
|
||||
else if (data.IsDictionary)
|
||||
{
|
||||
foreach (var entry in data.AsDictionary)
|
||||
{
|
||||
if (fsSerializer.IsReservedKeyword(entry.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
fsData keyData = new fsData(entry.Key), valueData = entry.Value;
|
||||
object keyInstance = null, valueInstance = null;
|
||||
|
||||
if ((result += Serializer.TryDeserialize(keyData, keyStorageType, ref keyInstance)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if ((result += Serializer.TryDeserialize(valueData, valueStorageType, ref valueInstance)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
AddItemToDictionary(instance, keyInstance, valueInstance);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return FailExpectedType(data, fsDataType.Array, fsDataType.Object);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance_, out fsData serialized, Type storageType)
|
||||
{
|
||||
serialized = fsData.Null;
|
||||
|
||||
var result = fsResult.Success;
|
||||
|
||||
var instance = (IDictionary)instance_;
|
||||
|
||||
Type keyStorageType, valueStorageType;
|
||||
GetKeyValueTypes(instance.GetType(), out keyStorageType, out valueStorageType);
|
||||
|
||||
// No other way to iterate dictionaries and still have access to the
|
||||
// key/value info
|
||||
var enumerator = instance.GetEnumerator();
|
||||
|
||||
var allStringKeys = true;
|
||||
var serializedKeys = new List<fsData>(instance.Count);
|
||||
var serializedValues = new List<fsData>(instance.Count);
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
fsData keyData, valueData;
|
||||
if ((result += Serializer.TrySerialize(keyStorageType, enumerator.Key, out keyData)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if ((result += Serializer.TrySerialize(valueStorageType, enumerator.Value, out valueData)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
serializedKeys.Add(keyData);
|
||||
serializedValues.Add(valueData);
|
||||
|
||||
allStringKeys &= keyData.IsString;
|
||||
}
|
||||
|
||||
if (allStringKeys)
|
||||
{
|
||||
serialized = fsData.CreateDictionary();
|
||||
var serializedDictionary = serialized.AsDictionary;
|
||||
|
||||
for (var i = 0; i < serializedKeys.Count; ++i)
|
||||
{
|
||||
var key = serializedKeys[i];
|
||||
var value = serializedValues[i];
|
||||
serializedDictionary[key.AsString] = value;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
serialized = fsData.CreateList(serializedKeys.Count);
|
||||
var serializedList = serialized.AsList;
|
||||
|
||||
for (var i = 0; i < serializedKeys.Count; ++i)
|
||||
{
|
||||
var key = serializedKeys[i];
|
||||
var value = serializedValues[i];
|
||||
|
||||
var container = new Dictionary<string, fsData>();
|
||||
container["Key"] = key;
|
||||
container["Value"] = value;
|
||||
serializedList.Add(new fsData(container));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private fsResult AddItemToDictionary(IDictionary dictionary, object key, object value)
|
||||
{
|
||||
// Because we're operating through the IDictionary interface by
|
||||
// default (and not the generic one), we normally send items through
|
||||
// IDictionary.Add(object, object). This works fine in the general
|
||||
// case, except that the add method verifies that it's parameter
|
||||
// types are proper types. However, mono is buggy and these type
|
||||
// checks do not consider null a subtype of the parameter types, and
|
||||
// exceptions get thrown. So, we have to special case adding null
|
||||
// items via the generic functions (which do not do the null check),
|
||||
// which is slow and messy.
|
||||
//
|
||||
// An example of a collection that fails deserialization without this
|
||||
// method is `new SortedList<int, string> { { 0, null } }`.
|
||||
// (SortedDictionary is fine because it properly handles null
|
||||
// values).
|
||||
if (key == null || value == null)
|
||||
{
|
||||
// Life would be much easier if we had MakeGenericType available,
|
||||
// but we don't. So we're going to find the correct generic
|
||||
// KeyValuePair type via a bit of trickery. All dictionaries
|
||||
// extend ICollection<KeyValuePair<TKey, TValue>>, so we just
|
||||
// fetch the ICollection<> type with the proper generic
|
||||
// arguments, and then we take the KeyValuePair<> generic
|
||||
// argument, and whola! we have our proper generic type.
|
||||
|
||||
var collectionType = fsReflectionUtility.GetInterface(dictionary.GetType(), typeof(ICollection<>));
|
||||
if (collectionType == null)
|
||||
{
|
||||
return fsResult.Warn(dictionary.GetType() + " does not extend ICollection");
|
||||
}
|
||||
|
||||
var keyValuePairType = collectionType.GetGenericArguments()[0];
|
||||
var keyValueInstance = Activator.CreateInstance(keyValuePairType, key, value);
|
||||
var add = collectionType.GetFlattenedMethod("Add");
|
||||
add.Invoke(dictionary, new[] { keyValueInstance });
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
// We use the inline set methods instead of dictionary.Add;
|
||||
// dictionary.Add will throw an exception if the key already exists.
|
||||
dictionary[key] = value;
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
private static void GetKeyValueTypes(Type dictionaryType, out Type keyStorageType, out Type valueStorageType)
|
||||
{
|
||||
// All dictionaries extend IDictionary<TKey, TValue>, so we just
|
||||
// fetch the generic arguments from it
|
||||
var interfaceType = fsReflectionUtility.GetInterface(dictionaryType, typeof(IDictionary<,>));
|
||||
if (interfaceType != null)
|
||||
{
|
||||
var genericArgs = interfaceType.GetGenericArguments();
|
||||
keyStorageType = genericArgs[0];
|
||||
valueStorageType = genericArgs[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fetching IDictionary<,> failed... we have to encode full type
|
||||
// information :(
|
||||
keyStorageType = typeof(object);
|
||||
valueStorageType = typeof(object);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes and deserializes enums by their current name.
|
||||
/// </summary>
|
||||
public class fsEnumConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return type.Resolve().IsEnum;
|
||||
}
|
||||
|
||||
public override bool RequestCycleSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool RequestInheritanceSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
// In .NET compact, Enum.ToObject(Type, Object) is defined but the
|
||||
// overloads like Enum.ToObject(Type, int) are not -- so we get
|
||||
// around this by boxing the value.
|
||||
return Enum.ToObject(storageType, (object)0);
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
if (Serializer.Config.SerializeEnumsAsInteger)
|
||||
{
|
||||
serialized = new fsData(Convert.ToInt64(instance));
|
||||
}
|
||||
else if (fsPortableReflection.GetAttribute<FlagsAttribute>(storageType) != null)
|
||||
{
|
||||
var lValue = Convert.ToInt64(instance);
|
||||
var result = new StringBuilder();
|
||||
|
||||
var first = true;
|
||||
foreach (var flag in Enum.GetValues(storageType))
|
||||
{
|
||||
var lFlag = Convert.ToInt64(flag);
|
||||
|
||||
if (lFlag == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var isSet = (lValue & lFlag) == lFlag;
|
||||
|
||||
if (isSet)
|
||||
{
|
||||
if (first == false)
|
||||
{
|
||||
result.Append(",");
|
||||
}
|
||||
first = false;
|
||||
result.Append(flag.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
serialized = new fsData(result.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
serialized = new fsData(Enum.GetName(storageType, instance));
|
||||
}
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
if (data.IsString)
|
||||
{
|
||||
var enumValues = data.AsString.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
for (var i = 0; i < enumValues.Length; ++i)
|
||||
{
|
||||
var enumValue = enumValues[i];
|
||||
|
||||
// Verify that the enum name exists; Enum.TryParse is only
|
||||
// available in .NET 4.0 and above :(.
|
||||
if (ArrayContains(Enum.GetNames(storageType), enumValue) == false)
|
||||
{
|
||||
return fsResult.Fail("Cannot find enum name " + enumValue + " on type " + storageType);
|
||||
}
|
||||
}
|
||||
|
||||
var underlyingType = Enum.GetUnderlyingType(storageType);
|
||||
|
||||
if (underlyingType == typeof(ulong))
|
||||
{
|
||||
ulong instanceValue = 0;
|
||||
|
||||
for (var i = 0; i < enumValues.Length; ++i)
|
||||
{
|
||||
var enumValue = enumValues[i];
|
||||
var flagValue = (ulong)Convert.ChangeType(Enum.Parse(storageType, enumValue), typeof(ulong));
|
||||
instanceValue |= flagValue;
|
||||
}
|
||||
|
||||
instance = Enum.ToObject(storageType, (object)instanceValue);
|
||||
}
|
||||
else
|
||||
{
|
||||
long instanceValue = 0;
|
||||
|
||||
for (var i = 0; i < enumValues.Length; ++i)
|
||||
{
|
||||
var enumValue = enumValues[i];
|
||||
var flagValue = (long)Convert.ChangeType(Enum.Parse(storageType, enumValue), typeof(long));
|
||||
instanceValue |= flagValue;
|
||||
}
|
||||
|
||||
instance = Enum.ToObject(storageType, (object)instanceValue);
|
||||
}
|
||||
|
||||
return fsResult.Success;
|
||||
}
|
||||
else if (data.IsInt64)
|
||||
{
|
||||
var enumValue = (int)data.AsInt64;
|
||||
|
||||
// In .NET compact, Enum.ToObject(Type, Object) is defined but
|
||||
// the overloads like Enum.ToObject(Type, int) are not -- so we
|
||||
// get around this by boxing the value.
|
||||
instance = Enum.ToObject(storageType, (object)enumValue);
|
||||
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
return fsResult.Fail($"EnumConverter encountered an unknown JSON data type for {storageType}: {data.Type}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given value is contained within the specified
|
||||
/// array.
|
||||
/// </summary>
|
||||
private static bool ArrayContains<T>(T[] values, T value)
|
||||
{
|
||||
// note: We don't use LINQ because this function will *not* allocate
|
||||
for (var i = 0; i < values.Length; ++i)
|
||||
{
|
||||
if (EqualityComparer<T>.Default.Equals(values[i], value))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// This allows you to forward serialization of an object to one of its
|
||||
/// members. For example,
|
||||
/// [fsForward("Values")]
|
||||
/// struct Wrapper {
|
||||
/// public int[] Values;
|
||||
/// }
|
||||
/// Then `Wrapper` will be serialized into a JSON array of integers. It will
|
||||
/// be as if `Wrapper` doesn't exist.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct)]
|
||||
public sealed class fsForwardAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Forward object serialization to an instance member. See class
|
||||
/// comment.
|
||||
/// </summary>
|
||||
/// <param name="memberName">
|
||||
/// The name of the member that we should serialize this object as.
|
||||
/// </param>
|
||||
public fsForwardAttribute(string memberName)
|
||||
{
|
||||
MemberName = memberName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of the member we should serialize as.
|
||||
/// </summary>
|
||||
public string MemberName;
|
||||
}
|
||||
|
||||
public class fsForwardConverter : fsConverter
|
||||
{
|
||||
public fsForwardConverter(fsForwardAttribute attribute)
|
||||
{
|
||||
_memberName = attribute.MemberName;
|
||||
}
|
||||
|
||||
private string _memberName;
|
||||
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
throw new NotSupportedException("Please use the [fsForward(...)] attribute.");
|
||||
}
|
||||
|
||||
private fsResult GetProperty(object instance, out fsMetaProperty property)
|
||||
{
|
||||
var properties = fsMetaType.Get(Serializer.Config, instance.GetType()).Properties;
|
||||
for (var i = 0; i < properties.Length; ++i)
|
||||
{
|
||||
if (properties[i].MemberName == _memberName)
|
||||
{
|
||||
property = properties[i];
|
||||
return fsResult.Success;
|
||||
}
|
||||
}
|
||||
|
||||
property = default(fsMetaProperty);
|
||||
return fsResult.Fail("No property named \"" + _memberName + "\" on " + fsTypeExtensions.CSharpName(instance.GetType()));
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
serialized = fsData.Null;
|
||||
var result = fsResult.Success;
|
||||
|
||||
fsMetaProperty property;
|
||||
if ((result += GetProperty(instance, out property)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var actualInstance = property.Read(instance);
|
||||
return Serializer.TrySerialize(property.StorageType, actualInstance, out serialized);
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
fsMetaProperty property;
|
||||
if ((result += GetProperty(instance, out property)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
object actualInstance = null;
|
||||
if ((result += Serializer.TryDeserialize(data, property.StorageType, ref actualInstance)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
property.Write(instance, actualInstance);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return fsMetaType.Get(Serializer.Config, storageType).CreateInstance();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes and deserializes guids.
|
||||
/// </summary>
|
||||
public class fsGuidConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return type == typeof(Guid);
|
||||
}
|
||||
|
||||
public override bool RequestCycleSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool RequestInheritanceSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
var guid = (Guid)instance;
|
||||
serialized = new fsData(guid.ToString());
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
if (data.IsString)
|
||||
{
|
||||
instance = new Guid(data.AsString);
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
return fsResult.Fail("fsGuidConverter encountered an unknown JSON data type");
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new Guid();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides serialization support for anything which extends from
|
||||
/// `IEnumerable` and has an `Add` method.
|
||||
/// </summary>
|
||||
public class fsIEnumerableConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
if (typeof(IEnumerable).IsAssignableFrom(type) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return GetAddMethod(type) != null;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return fsMetaType.Get(Serializer.Config, storageType).CreateInstance();
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance_, out fsData serialized, Type storageType)
|
||||
{
|
||||
var instance = (IEnumerable)instance_;
|
||||
var result = fsResult.Success;
|
||||
|
||||
var elementType = GetElementType(storageType);
|
||||
|
||||
serialized = fsData.CreateList(HintSize(instance));
|
||||
var serializedList = serialized.AsList;
|
||||
|
||||
foreach (var item in instance)
|
||||
{
|
||||
fsData itemData;
|
||||
|
||||
// note: We don't fail the entire deserialization even if the
|
||||
// item failed
|
||||
var itemResult = Serializer.TrySerialize(elementType, item, out itemData);
|
||||
result.AddMessages(itemResult);
|
||||
if (itemResult.Failed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
serializedList.Add(itemData);
|
||||
}
|
||||
|
||||
// Stacks iterate from back to front, which means when we deserialize
|
||||
// we will deserialize the items in the wrong order, so the stack
|
||||
// will get reversed.
|
||||
if (IsStack(instance.GetType()))
|
||||
{
|
||||
serializedList.Reverse();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private bool IsStack(Type type)
|
||||
{
|
||||
return type.Resolve().IsGenericType &&
|
||||
type.Resolve().GetGenericTypeDefinition() == typeof(Stack<>);
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance_, Type storageType)
|
||||
{
|
||||
var instance = (IEnumerable)instance_;
|
||||
var result = fsResult.Success;
|
||||
|
||||
if ((result += CheckType(data, fsDataType.Array)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// LAZLO/LUDIQ: Changes to default behaviour.
|
||||
// - Do not try to serialize into existing element; always clear and add
|
||||
// (more reliable, compatible with custom indexers)
|
||||
// - If the type is a list, add a default element on failure to prevent
|
||||
// messing up the order.
|
||||
// - Commented out that last change.
|
||||
|
||||
var elementStorageType = GetElementType(storageType);
|
||||
var addMethod = GetAddMethod(storageType);
|
||||
TryClear(storageType, instance);
|
||||
|
||||
var serializedList = data.AsList;
|
||||
|
||||
for (var i = 0; i < serializedList.Count; ++i)
|
||||
{
|
||||
var itemData = serializedList[i];
|
||||
object itemInstance = null;
|
||||
|
||||
var itemResult = Serializer.TryDeserialize(itemData, elementStorageType, ref itemInstance);
|
||||
|
||||
result.AddMessages(itemResult);
|
||||
|
||||
if (itemResult.Succeeded)
|
||||
{
|
||||
addMethod.Invoke(instance, new[] { itemInstance });
|
||||
}
|
||||
// else
|
||||
// {
|
||||
// if (typeof(IList).IsAssignableFrom(storageType))
|
||||
// {
|
||||
// addMethod.Invoke(instance, new[] { elementStorageType.Default() });
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static int HintSize(IEnumerable collection)
|
||||
{
|
||||
if (collection is ICollection)
|
||||
{
|
||||
return ((ICollection)collection).Count;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the element type for objects inside of the collection.
|
||||
/// </summary>
|
||||
private static Type GetElementType(Type objectType)
|
||||
{
|
||||
if (objectType.HasElementType)
|
||||
{
|
||||
return objectType.GetElementType();
|
||||
}
|
||||
|
||||
var enumerableList = fsReflectionUtility.GetInterface(objectType, typeof(IEnumerable<>));
|
||||
if (enumerableList != null)
|
||||
{
|
||||
return enumerableList.GetGenericArguments()[0];
|
||||
}
|
||||
|
||||
return typeof(object);
|
||||
}
|
||||
|
||||
private static void TryClear(Type type, object instance)
|
||||
{
|
||||
var clear = type.GetFlattenedMethod("Clear");
|
||||
if (clear != null)
|
||||
{
|
||||
clear.Invoke(instance, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static int TryGetExistingSize(Type type, object instance)
|
||||
{
|
||||
var count = type.GetFlattenedProperty("Count");
|
||||
if (count != null)
|
||||
{
|
||||
return (int)count.GetGetMethod().Invoke(instance, null);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static MethodInfo GetAddMethod(Type type)
|
||||
{
|
||||
// There is a really good chance the type will extend ICollection{},
|
||||
// which will contain the add method we want. Just doing
|
||||
// type.GetFlattenedMethod() may return the incorrect one -- for
|
||||
// example, with dictionaries, it'll return Add(TKey, TValue), and we
|
||||
// want Add(KeyValuePair<TKey, TValue>).
|
||||
var collectionInterface = fsReflectionUtility.GetInterface(type, typeof(ICollection<>));
|
||||
if (collectionInterface != null)
|
||||
{
|
||||
var add = collectionInterface.GetDeclaredMethod("Add");
|
||||
if (add != null)
|
||||
{
|
||||
return add;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise try and look up a general Add method.
|
||||
return
|
||||
type.GetFlattenedMethod("Add") ??
|
||||
type.GetFlattenedMethod("Push") ??
|
||||
type.GetFlattenedMethod("Enqueue");
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
public class fsKeyValuePairConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return
|
||||
type.Resolve().IsGenericType &&
|
||||
type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>);
|
||||
}
|
||||
|
||||
public override bool RequestCycleSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool RequestInheritanceSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
fsData keyData, valueData;
|
||||
if ((result += CheckKey(data, "Key", out keyData)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if ((result += CheckKey(data, "Value", out valueData)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var genericArguments = storageType.GetGenericArguments();
|
||||
Type keyType = genericArguments[0], valueType = genericArguments[1];
|
||||
|
||||
object keyObject = null, valueObject = null;
|
||||
result.AddMessages(Serializer.TryDeserialize(keyData, keyType, ref keyObject));
|
||||
result.AddMessages(Serializer.TryDeserialize(valueData, valueType, ref valueObject));
|
||||
|
||||
instance = Activator.CreateInstance(storageType, keyObject, valueObject);
|
||||
return result;
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
var keyProperty = storageType.GetDeclaredProperty("Key");
|
||||
var valueProperty = storageType.GetDeclaredProperty("Value");
|
||||
|
||||
var keyObject = keyProperty.GetValue(instance, null);
|
||||
var valueObject = valueProperty.GetValue(instance, null);
|
||||
|
||||
var genericArguments = storageType.GetGenericArguments();
|
||||
Type keyType = genericArguments[0], valueType = genericArguments[1];
|
||||
|
||||
var result = fsResult.Success;
|
||||
|
||||
fsData keyData, valueData;
|
||||
result.AddMessages(Serializer.TrySerialize(keyType, keyObject, out keyData));
|
||||
result.AddMessages(Serializer.TrySerialize(valueType, valueObject, out valueData));
|
||||
|
||||
serialized = fsData.CreateDictionary();
|
||||
if (keyData != null)
|
||||
{
|
||||
serialized.AsDictionary["Key"] = keyData;
|
||||
}
|
||||
if (valueData != null)
|
||||
{
|
||||
serialized.AsDictionary["Value"] = valueData;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// The reflected converter will properly serialize nullable types. However,
|
||||
/// we do it here instead as we can emit less serialization data.
|
||||
/// </summary>
|
||||
public class fsNullableConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return
|
||||
type.Resolve().IsGenericType &&
|
||||
type.GetGenericTypeDefinition() == typeof(Nullable<>);
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
// null is automatically serialized
|
||||
return Serializer.TrySerialize(Nullable.GetUnderlyingType(storageType), instance, out serialized);
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
// null is automatically deserialized
|
||||
return Serializer.TryDeserialize(data, Nullable.GetUnderlyingType(storageType), ref instance);
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return storageType;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
public class fsPrimitiveConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return
|
||||
type.Resolve().IsPrimitive ||
|
||||
type == typeof(string) ||
|
||||
type == typeof(decimal);
|
||||
}
|
||||
|
||||
public override bool RequestCycleSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool RequestInheritanceSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
var instanceType = instance.GetType();
|
||||
|
||||
if (Serializer.Config.Serialize64BitIntegerAsString && (instanceType == typeof(Int64) || instanceType == typeof(UInt64)))
|
||||
{
|
||||
serialized = new fsData((string)Convert.ChangeType(instance, typeof(string)));
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
if (UseBool(instanceType))
|
||||
{
|
||||
serialized = new fsData((bool)instance);
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
if (UseInt64(instanceType))
|
||||
{
|
||||
serialized = new fsData((Int64)Convert.ChangeType(instance, typeof(Int64)));
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
if (UseDouble(instanceType))
|
||||
{
|
||||
// Casting from float to double introduces floating point jitter,
|
||||
// ie, 0.1 becomes 0.100000001490116. Casting to decimal as an
|
||||
// intermediate step removes the jitter. Not sure why.
|
||||
if (instance.GetType() == typeof(float) &&
|
||||
// Decimal can't store
|
||||
// float.MinValue/float.MaxValue/float.PositiveInfinity/float.NegativeInfinity/float.NaN
|
||||
// - an exception gets thrown in that scenario.
|
||||
(float)instance != float.MinValue &&
|
||||
(float)instance != float.MaxValue &&
|
||||
!float.IsInfinity((float)instance) &&
|
||||
!float.IsNaN((float)instance)
|
||||
)
|
||||
{
|
||||
serialized = new fsData((double)(decimal)(float)instance);
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
serialized = new fsData((double)Convert.ChangeType(instance, typeof(double)));
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
if (UseString(instanceType))
|
||||
{
|
||||
serialized = new fsData((string)Convert.ChangeType(instance, typeof(string)));
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
serialized = null;
|
||||
return fsResult.Fail("Unhandled primitive type " + instance.GetType());
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData storage, ref object instance, Type storageType)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
if (UseBool(storageType))
|
||||
{
|
||||
if ((result += CheckType(storage, fsDataType.Boolean)).Succeeded)
|
||||
{
|
||||
instance = storage.AsBool;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (UseDouble(storageType) || UseInt64(storageType))
|
||||
{
|
||||
if (storage.IsDouble)
|
||||
{
|
||||
instance = Convert.ChangeType(storage.AsDouble, storageType);
|
||||
}
|
||||
else if (storage.IsInt64)
|
||||
{
|
||||
instance = Convert.ChangeType(storage.AsInt64, storageType);
|
||||
}
|
||||
else if (Serializer.Config.Serialize64BitIntegerAsString && storage.IsString &&
|
||||
(storageType == typeof(Int64) || storageType == typeof(UInt64)))
|
||||
{
|
||||
instance = Convert.ChangeType(storage.AsString, storageType);
|
||||
}
|
||||
else
|
||||
{
|
||||
return fsResult.Fail(GetType().Name + " expected number but got " + storage.Type + " in " + storage);
|
||||
}
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
if (UseString(storageType))
|
||||
{
|
||||
if ((result += CheckType(storage, fsDataType.String)).Succeeded)
|
||||
{
|
||||
var str = storage.AsString;
|
||||
|
||||
if (storageType == typeof(char))
|
||||
{
|
||||
if (storageType == typeof(char))
|
||||
{
|
||||
if (str.Length == 1)
|
||||
{
|
||||
instance = str[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = default(char);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
instance = str;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return fsResult.Fail(GetType().Name + ": Bad data; expected bool, number, string, but got " + storage);
|
||||
}
|
||||
|
||||
private static bool UseBool(Type type)
|
||||
{
|
||||
return type == typeof(bool);
|
||||
}
|
||||
|
||||
private static bool UseInt64(Type type)
|
||||
{
|
||||
return type == typeof(sbyte) || type == typeof(byte) ||
|
||||
type == typeof(Int16) || type == typeof(UInt16) ||
|
||||
type == typeof(Int32) || type == typeof(UInt32) ||
|
||||
type == typeof(Int64) || type == typeof(UInt64);
|
||||
}
|
||||
|
||||
private static bool UseDouble(Type type)
|
||||
{
|
||||
return type == typeof(float) ||
|
||||
type == typeof(double) ||
|
||||
type == typeof(decimal);
|
||||
}
|
||||
|
||||
private static bool UseString(Type type)
|
||||
{
|
||||
return type == typeof(string) ||
|
||||
type == typeof(char);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_WSA
|
||||
// For System.Reflection.TypeExtensions
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
public class fsReflectedConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
if (type.Resolve().IsArray ||
|
||||
typeof(ICollection).IsAssignableFrom(type))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
serialized = fsData.CreateDictionary();
|
||||
var result = fsResult.Success;
|
||||
|
||||
var metaType = fsMetaType.Get(Serializer.Config, instance.GetType());
|
||||
metaType.EmitAotData();
|
||||
|
||||
for (var i = 0; i < metaType.Properties.Length; ++i)
|
||||
{
|
||||
var property = metaType.Properties[i];
|
||||
if (property.CanRead == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
fsData serializedData;
|
||||
|
||||
var itemResult = Serializer.TrySerialize(property.StorageType, property.OverrideConverterType,
|
||||
property.Read(instance), out serializedData);
|
||||
result.AddMessages(itemResult);
|
||||
if (itemResult.Failed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
serialized.AsDictionary[property.JsonName] = serializedData;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
// Verify that we actually have an Object
|
||||
if ((result += CheckType(data, fsDataType.Object)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var metaType = fsMetaType.Get(Serializer.Config, storageType);
|
||||
metaType.EmitAotData();
|
||||
|
||||
for (var i = 0; i < metaType.Properties.Length; ++i)
|
||||
{
|
||||
var property = metaType.Properties[i];
|
||||
if (property.CanWrite == false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
fsData propertyData;
|
||||
if (data.AsDictionary.TryGetValue(property.JsonName, out propertyData))
|
||||
{
|
||||
object deserializedValue = null;
|
||||
|
||||
// We have to read in the existing value, since we need to
|
||||
// support partial deserialization. However, this is bad for
|
||||
// perf.
|
||||
// TODO: Find a way to avoid this call when we are not doing
|
||||
// a partial deserialization Maybe through a new
|
||||
// property, ie, Serializer.IsPartialSerialization,
|
||||
// which just gets set when starting a new
|
||||
// serialization? We cannot pipe the information
|
||||
// through CreateInstance unfortunately.
|
||||
if (property.CanRead)
|
||||
{
|
||||
deserializedValue = property.Read(instance);
|
||||
}
|
||||
|
||||
var itemResult = Serializer.TryDeserialize(propertyData, property.StorageType,
|
||||
property.OverrideConverterType, ref deserializedValue);
|
||||
result.AddMessages(itemResult);
|
||||
if (itemResult.Failed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
property.Write(instance, deserializedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
var metaType = fsMetaType.Get(Serializer.Config, storageType);
|
||||
return metaType.CreateInstance();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_WSA
|
||||
// For System.Reflection.TypeExtensions
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
public class fsTypeConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return typeof(Type).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
public override bool RequestCycleSupport(Type type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool RequestInheritanceSupport(Type type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
var type = (Type)instance;
|
||||
serialized = new fsData(RuntimeCodebase.SerializeType(type));
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
if (data.IsString == false)
|
||||
{
|
||||
return fsResult.Fail("Type converter requires a string");
|
||||
}
|
||||
|
||||
if (RuntimeCodebase.TryDeserializeType(data.AsString, out var type))
|
||||
{
|
||||
instance = type;
|
||||
}
|
||||
else
|
||||
{
|
||||
return fsResult.Fail($"Unable to find type: '{data.AsString ?? "(null)"}'.");
|
||||
}
|
||||
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return storageType;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes and deserializes WeakReferences.
|
||||
/// </summary>
|
||||
public class fsWeakReferenceConverter : fsConverter
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return type == typeof(WeakReference);
|
||||
}
|
||||
|
||||
public override bool RequestCycleSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override bool RequestInheritanceSupport(Type storageType)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
var weakRef = (WeakReference)instance;
|
||||
|
||||
var result = fsResult.Success;
|
||||
serialized = fsData.CreateDictionary();
|
||||
|
||||
if (weakRef.IsAlive)
|
||||
{
|
||||
fsData data;
|
||||
if ((result += Serializer.TrySerialize(weakRef.Target, out data)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
serialized.AsDictionary["Target"] = data;
|
||||
serialized.AsDictionary["TrackResurrection"] = new fsData(weakRef.TrackResurrection);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
|
||||
if ((result += CheckType(data, fsDataType.Object)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
if (data.AsDictionary.ContainsKey("Target"))
|
||||
{
|
||||
var targetData = data.AsDictionary["Target"];
|
||||
object targetInstance = null;
|
||||
|
||||
if ((result += Serializer.TryDeserialize(targetData, typeof(object), ref targetInstance)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var trackResurrection = false;
|
||||
if (data.AsDictionary.ContainsKey("TrackResurrection") && data.AsDictionary["TrackResurrection"].IsBool)
|
||||
{
|
||||
trackResurrection = data.AsDictionary["TrackResurrection"].AsBool;
|
||||
}
|
||||
|
||||
instance = new WeakReference(targetInstance, trackResurrection);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public override object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
return new WeakReference(null);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer.Internal
|
||||
{
|
||||
public class fsCyclicReferenceManager
|
||||
{
|
||||
private Dictionary<object, int> _objectIds = new Dictionary<object, int>(ObjectReferenceEqualityComparator.Instance);
|
||||
private int _nextId;
|
||||
|
||||
private Dictionary<int, object> _marked = new Dictionary<int, object>();
|
||||
private int _depth;
|
||||
|
||||
public void Enter()
|
||||
{
|
||||
_depth++;
|
||||
}
|
||||
|
||||
public bool Exit()
|
||||
{
|
||||
_depth--;
|
||||
|
||||
if (_depth == 0)
|
||||
{
|
||||
_objectIds = new Dictionary<object, int>(ObjectReferenceEqualityComparator.Instance);
|
||||
_nextId = 0;
|
||||
_marked = new Dictionary<int, object>();
|
||||
}
|
||||
|
||||
if (_depth < 0)
|
||||
{
|
||||
_depth = 0;
|
||||
throw new InvalidOperationException("Internal Error - Mismatched Enter/Exit. Please report a bug at https://github.com/jacobdufault/fullserializer/issues with the serialization data.");
|
||||
}
|
||||
|
||||
return _depth == 0;
|
||||
}
|
||||
|
||||
public object GetReferenceObject(int id)
|
||||
{
|
||||
if (_marked.ContainsKey(id) == false)
|
||||
{
|
||||
throw new InvalidOperationException("Internal Deserialization Error - Object " +
|
||||
"definition has not been encountered for object with id=" + id +
|
||||
"; have you reordered or modified the serialized data? If this is an issue " +
|
||||
"with an unmodified Full Serializer implementation and unmodified serialization " +
|
||||
"data, please report an issue with an included test case.");
|
||||
}
|
||||
|
||||
return _marked[id];
|
||||
}
|
||||
|
||||
public void AddReferenceWithId(int id, object reference)
|
||||
{
|
||||
_marked[id] = reference;
|
||||
}
|
||||
|
||||
public int GetReferenceId(object item)
|
||||
{
|
||||
int id;
|
||||
if (_objectIds.TryGetValue(item, out id) == false)
|
||||
{
|
||||
id = _nextId++;
|
||||
_objectIds[item] = id;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public bool IsReference(object item)
|
||||
{
|
||||
return _marked.ContainsKey(GetReferenceId(item));
|
||||
}
|
||||
|
||||
public void MarkSerialized(object item)
|
||||
{
|
||||
var referenceId = GetReferenceId(item);
|
||||
|
||||
if (_marked.ContainsKey(referenceId))
|
||||
{
|
||||
throw new InvalidOperationException("Internal Error - " + item +
|
||||
" has already been marked as serialized");
|
||||
}
|
||||
|
||||
_marked[referenceId] = item;
|
||||
}
|
||||
|
||||
// We use the default ReferenceEquals when comparing two objects because
|
||||
// custom objects may override equals methods. These overriden equals may
|
||||
// treat equals differently; we want to serialize/deserialize the object
|
||||
// graph *identically* to how it currently exists.
|
||||
private class ObjectReferenceEqualityComparator : IEqualityComparer<object>
|
||||
{
|
||||
bool IEqualityComparer<object>.Equals(object x, object y)
|
||||
{
|
||||
return ReferenceEquals(x, y);
|
||||
}
|
||||
|
||||
int IEqualityComparer<object>.GetHashCode(object obj)
|
||||
{
|
||||
return RuntimeHelpers.GetHashCode(obj);
|
||||
}
|
||||
|
||||
public static readonly IEqualityComparer<object> Instance = new ObjectReferenceEqualityComparator();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple option type. This is akin to nullable types.
|
||||
/// </summary>
|
||||
public struct fsOption<T>
|
||||
{
|
||||
private bool _hasValue;
|
||||
private T _value;
|
||||
|
||||
public bool HasValue => _hasValue;
|
||||
|
||||
public bool IsEmpty => _hasValue == false;
|
||||
|
||||
public T Value
|
||||
{
|
||||
get
|
||||
{
|
||||
if (IsEmpty)
|
||||
{
|
||||
throw new InvalidOperationException("fsOption is empty");
|
||||
}
|
||||
return _value;
|
||||
}
|
||||
}
|
||||
|
||||
public fsOption(T value)
|
||||
{
|
||||
_hasValue = true;
|
||||
_value = value;
|
||||
}
|
||||
|
||||
public static fsOption<T> Empty;
|
||||
}
|
||||
|
||||
public static class fsOption
|
||||
{
|
||||
public static fsOption<T> Just<T>(T value)
|
||||
{
|
||||
return new fsOption<T>(value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,507 @@
|
||||
#if !UNITY_EDITOR && UNITY_METRO && !ENABLE_IL2CPP
|
||||
#define USE_TYPEINFO
|
||||
#if !UNITY_WINRT_10_0
|
||||
#define USE_TYPEINFO_EXTENSIONS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
#if USE_TYPEINFO
|
||||
namespace System
|
||||
{
|
||||
public static class AssemblyExtensions
|
||||
{
|
||||
#if USE_TYPEINFO_EXTENSIONS
|
||||
public static Type[] GetTypes(this Assembly assembly)
|
||||
{
|
||||
TypeInfo[] infos = assembly.DefinedTypes.ToArray();
|
||||
Type[] types = new Type[infos.Length];
|
||||
for (int i = 0; i < infos.Length; ++i)
|
||||
{
|
||||
types[i] = infos[i].AsType();
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
public static Type GetType(this Assembly assembly, string name, bool throwOnError)
|
||||
{
|
||||
var types = assembly.GetTypes();
|
||||
for (int i = 0; i < types.Length; ++i)
|
||||
{
|
||||
if (types[i].Name == name)
|
||||
{
|
||||
return types[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (throwOnError) throw new Exception("Type " + name + " was not found");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// This wraps reflection types so that it is portable across different Unity
|
||||
/// runtimes.
|
||||
/// </summary>
|
||||
public static class fsPortableReflection
|
||||
{
|
||||
public static Type[] EmptyTypes = { };
|
||||
|
||||
#region Attribute Queries
|
||||
|
||||
#if USE_TYPEINFO
|
||||
public static TAttribute GetAttribute<TAttribute>(Type type)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return GetAttribute<TAttribute>(type.GetTypeInfo());
|
||||
}
|
||||
|
||||
public static Attribute GetAttribute(Type type, Type attributeType)
|
||||
{
|
||||
return GetAttribute(type.GetTypeInfo(), attributeType, /*shouldCache:*/ false);
|
||||
}
|
||||
|
||||
public static bool HasAttribute(Type type, Type attributeType)
|
||||
{
|
||||
return GetAttribute(type, attributeType) != null;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given attribute is defined on the given element.
|
||||
/// </summary>
|
||||
public static bool HasAttribute<TAttribute>(MemberInfo element)
|
||||
{
|
||||
return HasAttribute(element, typeof(TAttribute));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given attribute is defined on the given element.
|
||||
/// </summary>
|
||||
public static bool HasAttribute<TAttribute>(MemberInfo element, bool shouldCache)
|
||||
{
|
||||
return HasAttribute(element, typeof(TAttribute), shouldCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given attribute is defined on the given element.
|
||||
/// </summary>
|
||||
public static bool HasAttribute(MemberInfo element, Type attributeType)
|
||||
{
|
||||
return HasAttribute(element, attributeType, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the given attribute is defined on the given element.
|
||||
/// </summary>
|
||||
public static bool HasAttribute(MemberInfo element, Type attributeType, bool shouldCache)
|
||||
{
|
||||
// LAZLO / LUDIQ FIX
|
||||
// Inheritance on property overrides. MemberInfo.IsDefined ignores the inherited parameter.
|
||||
// https://stackoverflow.com/questions/2520035
|
||||
return Attribute.IsDefined(element, attributeType, true);
|
||||
//return element.IsDefined(attributeType, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the given attribute from the given MemberInfo. This method
|
||||
/// applies caching and is allocation free (after caching has been
|
||||
/// performed).
|
||||
/// </summary>
|
||||
/// <param name="element">
|
||||
/// The MemberInfo the get the attribute from.
|
||||
/// </param>
|
||||
/// <param name="attributeType">The type of attribute to fetch.</param>
|
||||
/// <returns>The attribute or null.</returns>
|
||||
public static Attribute GetAttribute(MemberInfo element, Type attributeType, bool shouldCache)
|
||||
{
|
||||
var query = new AttributeQuery
|
||||
{
|
||||
MemberInfo = element,
|
||||
AttributeType = attributeType
|
||||
};
|
||||
|
||||
Attribute attribute;
|
||||
if (_cachedAttributeQueries.TryGetValue(query, out attribute) == false)
|
||||
{
|
||||
// LAZLO / LUDIQ FIX
|
||||
// Inheritance on property overrides. MemberInfo.IsDefined ignores the inherited parameter
|
||||
//var attributes = element.GetCustomAttributes(attributeType, /*inherit:*/ true).ToArray();
|
||||
var attributes = Attribute.GetCustomAttributes(element, attributeType, true).ToArray();
|
||||
|
||||
if (attributes.Length > 0)
|
||||
{
|
||||
attribute = (Attribute)attributes[0];
|
||||
}
|
||||
if (shouldCache)
|
||||
{
|
||||
_cachedAttributeQueries[query] = attribute;
|
||||
}
|
||||
}
|
||||
|
||||
return attribute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the given attribute from the given MemberInfo.
|
||||
/// </summary>
|
||||
/// <typeparam name="TAttribute">
|
||||
/// The type of attribute to fetch.
|
||||
/// </typeparam>
|
||||
/// <param name="element">
|
||||
/// The MemberInfo to get the attribute from.
|
||||
/// </param>
|
||||
/// <param name="shouldCache">
|
||||
/// Should this computation be cached? If this is the only time it will
|
||||
/// ever be done, don't bother caching.
|
||||
/// </param>
|
||||
/// <returns>The attribute or null.</returns>
|
||||
public static TAttribute GetAttribute<TAttribute>(MemberInfo element, bool shouldCache)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return (TAttribute)GetAttribute(element, typeof(TAttribute), shouldCache);
|
||||
}
|
||||
|
||||
public static TAttribute GetAttribute<TAttribute>(MemberInfo element)
|
||||
where TAttribute : Attribute
|
||||
{
|
||||
return GetAttribute<TAttribute>(element, /*shouldCache:*/ true);
|
||||
}
|
||||
|
||||
private struct AttributeQuery
|
||||
{
|
||||
public MemberInfo MemberInfo;
|
||||
public Type AttributeType;
|
||||
}
|
||||
|
||||
private static IDictionary<AttributeQuery, Attribute> _cachedAttributeQueries =
|
||||
new Dictionary<AttributeQuery, Attribute>(new AttributeQueryComparator());
|
||||
|
||||
private class AttributeQueryComparator : IEqualityComparer<AttributeQuery>
|
||||
{
|
||||
public bool Equals(AttributeQuery x, AttributeQuery y)
|
||||
{
|
||||
return
|
||||
x.MemberInfo == y.MemberInfo &&
|
||||
x.AttributeType == y.AttributeType;
|
||||
}
|
||||
|
||||
public int GetHashCode(AttributeQuery obj)
|
||||
{
|
||||
return
|
||||
obj.MemberInfo.GetHashCode() +
|
||||
(17 * obj.AttributeType.GetHashCode());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Attribute Queries
|
||||
|
||||
#if !USE_TYPEINFO
|
||||
private static BindingFlags DeclaredFlags =
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.Static |
|
||||
BindingFlags.DeclaredOnly;
|
||||
#endif
|
||||
|
||||
public static PropertyInfo GetDeclaredProperty(this Type type, string propertyName)
|
||||
{
|
||||
var props = GetDeclaredProperties(type);
|
||||
|
||||
for (var i = 0; i < props.Length; ++i)
|
||||
{
|
||||
if (props[i].Name == propertyName)
|
||||
{
|
||||
return props[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodInfo GetDeclaredMethod(this Type type, string methodName)
|
||||
{
|
||||
var methods = GetDeclaredMethods(type);
|
||||
|
||||
for (var i = 0; i < methods.Length; ++i)
|
||||
{
|
||||
if (methods[i].Name == methodName)
|
||||
{
|
||||
return methods[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ConstructorInfo GetDeclaredConstructor(this Type type, Type[] parameters)
|
||||
{
|
||||
var ctors = GetDeclaredConstructors(type);
|
||||
|
||||
for (var i = 0; i < ctors.Length; ++i)
|
||||
{
|
||||
var ctor = ctors[i];
|
||||
var ctorParams = ctor.GetParameters();
|
||||
|
||||
if (parameters.Length != ctorParams.Length)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var j = 0; j < ctorParams.Length; ++j)
|
||||
{
|
||||
// require an exact match
|
||||
if (ctorParams[j].ParameterType != parameters[j])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return ctor;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ConstructorInfo[] GetDeclaredConstructors(this Type type)
|
||||
{
|
||||
#if USE_TYPEINFO
|
||||
return type.GetTypeInfo().DeclaredConstructors.ToArray();
|
||||
#else
|
||||
return type.GetConstructors(DeclaredFlags & ~BindingFlags.Static); // LUDIQ: Exclude static constructors
|
||||
#endif
|
||||
}
|
||||
|
||||
public static MemberInfo[] GetFlattenedMember(this Type type, string memberName)
|
||||
{
|
||||
var result = new List<MemberInfo>();
|
||||
|
||||
while (type != null)
|
||||
{
|
||||
var members = GetDeclaredMembers(type);
|
||||
|
||||
for (var i = 0; i < members.Length; ++i)
|
||||
{
|
||||
if (members[i].Name == memberName)
|
||||
{
|
||||
result.Add(members[i]);
|
||||
}
|
||||
}
|
||||
|
||||
type = type.Resolve().BaseType;
|
||||
}
|
||||
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
public static MethodInfo GetFlattenedMethod(this Type type, string methodName)
|
||||
{
|
||||
while (type != null)
|
||||
{
|
||||
var methods = GetDeclaredMethods(type);
|
||||
|
||||
for (var i = 0; i < methods.Length; ++i)
|
||||
{
|
||||
if (methods[i].Name == methodName)
|
||||
{
|
||||
return methods[i];
|
||||
}
|
||||
}
|
||||
|
||||
type = type.Resolve().BaseType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static IEnumerable<MethodInfo> GetFlattenedMethods(this Type type, string methodName)
|
||||
{
|
||||
while (type != null)
|
||||
{
|
||||
var methods = GetDeclaredMethods(type);
|
||||
|
||||
for (var i = 0; i < methods.Length; ++i)
|
||||
{
|
||||
if (methods[i].Name == methodName)
|
||||
{
|
||||
yield return methods[i];
|
||||
}
|
||||
}
|
||||
|
||||
type = type.Resolve().BaseType;
|
||||
}
|
||||
}
|
||||
|
||||
public static PropertyInfo GetFlattenedProperty(this Type type, string propertyName)
|
||||
{
|
||||
while (type != null)
|
||||
{
|
||||
var properties = GetDeclaredProperties(type);
|
||||
|
||||
for (var i = 0; i < properties.Length; ++i)
|
||||
{
|
||||
if (properties[i].Name == propertyName)
|
||||
{
|
||||
return properties[i];
|
||||
}
|
||||
}
|
||||
|
||||
type = type.Resolve().BaseType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MemberInfo GetDeclaredMember(this Type type, string memberName)
|
||||
{
|
||||
var members = GetDeclaredMembers(type);
|
||||
|
||||
for (var i = 0; i < members.Length; ++i)
|
||||
{
|
||||
if (members[i].Name == memberName)
|
||||
{
|
||||
return members[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static MethodInfo[] GetDeclaredMethods(this Type type)
|
||||
{
|
||||
#if USE_TYPEINFO
|
||||
return type.GetTypeInfo().DeclaredMethods.ToArray();
|
||||
#else
|
||||
return type.GetMethods(DeclaredFlags);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static PropertyInfo[] GetDeclaredProperties(this Type type)
|
||||
{
|
||||
#if USE_TYPEINFO
|
||||
return type.GetTypeInfo().DeclaredProperties.ToArray();
|
||||
#else
|
||||
return type.GetProperties(DeclaredFlags);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static FieldInfo[] GetDeclaredFields(this Type type)
|
||||
{
|
||||
#if USE_TYPEINFO
|
||||
return type.GetTypeInfo().DeclaredFields.ToArray();
|
||||
#else
|
||||
return type.GetFields(DeclaredFlags);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static MemberInfo[] GetDeclaredMembers(this Type type)
|
||||
{
|
||||
#if USE_TYPEINFO
|
||||
return type.GetTypeInfo().DeclaredMembers.ToArray();
|
||||
#else
|
||||
return type.GetMembers(DeclaredFlags);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static MemberInfo AsMemberInfo(Type type)
|
||||
{
|
||||
#if USE_TYPEINFO
|
||||
return type.GetTypeInfo();
|
||||
#else
|
||||
return type;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static bool IsType(MemberInfo member)
|
||||
{
|
||||
#if USE_TYPEINFO
|
||||
return member is TypeInfo;
|
||||
#else
|
||||
return member is Type;
|
||||
#endif
|
||||
}
|
||||
|
||||
public static Type AsType(MemberInfo member)
|
||||
{
|
||||
#if USE_TYPEINFO
|
||||
return ((TypeInfo)member).AsType();
|
||||
#else
|
||||
return (Type)member;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if USE_TYPEINFO
|
||||
public static TypeInfo Resolve(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo();
|
||||
}
|
||||
|
||||
#else
|
||||
public static Type Resolve(this Type type)
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#region Extensions
|
||||
|
||||
#if USE_TYPEINFO_EXTENSIONS
|
||||
public static bool IsAssignableFrom(this Type parent, Type child)
|
||||
{
|
||||
return parent.GetTypeInfo().IsAssignableFrom(child.GetTypeInfo());
|
||||
}
|
||||
|
||||
public static Type GetElementType(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().GetElementType();
|
||||
}
|
||||
|
||||
public static MethodInfo GetSetMethod(this PropertyInfo member, bool nonPublic = false)
|
||||
{
|
||||
// only public requested but the set method is not public
|
||||
if (nonPublic == false && member.SetMethod != null && member.SetMethod.IsPublic == false) return null;
|
||||
|
||||
return member.SetMethod;
|
||||
}
|
||||
|
||||
public static MethodInfo GetGetMethod(this PropertyInfo member, bool nonPublic = false)
|
||||
{
|
||||
// only public requested but the set method is not public
|
||||
if (nonPublic == false && member.GetMethod != null && member.GetMethod.IsPublic == false) return null;
|
||||
|
||||
return member.GetMethod;
|
||||
}
|
||||
|
||||
public static MethodInfo GetBaseDefinition(this MethodInfo method)
|
||||
{
|
||||
return method.GetRuntimeBaseDefinition();
|
||||
}
|
||||
|
||||
public static Type[] GetInterfaces(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().ImplementedInterfaces.ToArray();
|
||||
}
|
||||
|
||||
public static Type[] GetGenericArguments(this Type type)
|
||||
{
|
||||
return type.GetTypeInfo().GenericTypeArguments.ToArray();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endregion Extensions
|
||||
}
|
||||
}
|
@@ -0,0 +1,118 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_WSA
|
||||
// For System.Reflection.TypeExtensions
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer.Internal
|
||||
{
|
||||
public static class fsTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns a pretty name for the type in the style of one that you'd see
|
||||
/// in C# without the namespace.
|
||||
/// </summary>
|
||||
public static string CSharpName(this Type type)
|
||||
{
|
||||
return CSharpName(type, /*includeNamespace:*/ false);
|
||||
}
|
||||
|
||||
public static string CSharpName(this Type type, bool includeNamespace, bool ensureSafeDeclarationName)
|
||||
{
|
||||
var name = CSharpName(type, includeNamespace);
|
||||
if (ensureSafeDeclarationName)
|
||||
{
|
||||
name = name.Replace('>', '_').Replace('<', '_').Replace('.', '_');
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a pretty name for the type in the style of one that you'd see
|
||||
/// in C#.
|
||||
/// </summary>
|
||||
/// <parparam name="includeNamespace">
|
||||
/// Should the name include namespaces?
|
||||
/// </parparam>
|
||||
public static string CSharpName(this Type type, bool includeNamespace)
|
||||
{
|
||||
// we special case some of the common type names
|
||||
if (type == typeof(void))
|
||||
{
|
||||
return "void";
|
||||
}
|
||||
if (type == typeof(int))
|
||||
{
|
||||
return "int";
|
||||
}
|
||||
if (type == typeof(float))
|
||||
{
|
||||
return "float";
|
||||
}
|
||||
if (type == typeof(bool))
|
||||
{
|
||||
return "bool";
|
||||
}
|
||||
if (type == typeof(double))
|
||||
{
|
||||
return "double";
|
||||
}
|
||||
if (type == typeof(string))
|
||||
{
|
||||
return "string";
|
||||
}
|
||||
|
||||
// Generic parameter, ie, T in Okay<T> We special-case this logic
|
||||
// otherwise we will recurse on the T
|
||||
if (type.IsGenericParameter)
|
||||
{
|
||||
return type.ToString();
|
||||
}
|
||||
|
||||
var name = "";
|
||||
|
||||
var genericArguments = (IEnumerable<Type>)type.GetGenericArguments();
|
||||
if (type.IsNested)
|
||||
{
|
||||
name += type.DeclaringType.CSharpName() + ".";
|
||||
|
||||
// The declaring type generic parameters are considered part of
|
||||
// the nested types generic parameters so we need to remove them,
|
||||
// otherwise it will get included again.
|
||||
//
|
||||
// Say we have type `class Parent<T> { class Child {} }` If we
|
||||
// did not do the removal, then we would output
|
||||
// Parent<T>.Child<T>, but we really want to output
|
||||
// Parent<T>.Child
|
||||
if (type.DeclaringType.GetGenericArguments().Length > 0)
|
||||
{
|
||||
genericArguments = genericArguments.Skip(type.DeclaringType.GetGenericArguments().Length);
|
||||
}
|
||||
}
|
||||
|
||||
if (genericArguments.Any() == false)
|
||||
{
|
||||
name += type.Name;
|
||||
}
|
||||
else
|
||||
{
|
||||
var genericsTic = type.Name.IndexOf('`');
|
||||
if (genericsTic > 0)
|
||||
{
|
||||
name += type.Name.Substring(0, genericsTic);
|
||||
}
|
||||
name += "<" + String.Join(",", genericArguments.Select(t => CSharpName(t, includeNamespace)).ToArray()) + ">";
|
||||
}
|
||||
|
||||
if (includeNamespace && type.Namespace != null)
|
||||
{
|
||||
name = type.Namespace + "." + name;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,157 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer.Internal
|
||||
{
|
||||
public static class fsVersionManager
|
||||
{
|
||||
private static readonly Dictionary<Type, fsOption<fsVersionedType>> _cache = new Dictionary<Type, fsOption<fsVersionedType>>();
|
||||
|
||||
public static fsResult GetVersionImportPath(string currentVersion, fsVersionedType targetVersion, out List<fsVersionedType> path)
|
||||
{
|
||||
path = new List<fsVersionedType>();
|
||||
|
||||
if (GetVersionImportPathRecursive(path, currentVersion, targetVersion) == false)
|
||||
{
|
||||
return fsResult.Fail("There is no migration path from \"" + currentVersion + "\" to \"" + targetVersion.VersionString + "\"");
|
||||
}
|
||||
|
||||
path.Add(targetVersion);
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
private static bool GetVersionImportPathRecursive(List<fsVersionedType> path, string currentVersion, fsVersionedType current)
|
||||
{
|
||||
for (var i = 0; i < current.Ancestors.Length; ++i)
|
||||
{
|
||||
var ancestor = current.Ancestors[i];
|
||||
|
||||
if (ancestor.VersionString == currentVersion ||
|
||||
GetVersionImportPathRecursive(path, currentVersion, ancestor))
|
||||
{
|
||||
path.Add(ancestor);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static fsOption<fsVersionedType> GetVersionedType(Type type)
|
||||
{
|
||||
fsOption<fsVersionedType> optionalVersionedType;
|
||||
|
||||
if (_cache.TryGetValue(type, out optionalVersionedType) == false)
|
||||
{
|
||||
var attr = fsPortableReflection.GetAttribute<fsObjectAttribute>(type);
|
||||
|
||||
if (attr != null)
|
||||
{
|
||||
if (string.IsNullOrEmpty(attr.VersionString) == false || attr.PreviousModels != null)
|
||||
{
|
||||
// Version string must be provided
|
||||
if (attr.PreviousModels != null && string.IsNullOrEmpty(attr.VersionString))
|
||||
{
|
||||
throw new Exception("fsObject attribute on " + type + " contains a PreviousModels specifier - it must also include a VersionString modifier");
|
||||
}
|
||||
|
||||
// Map the ancestor types into versioned types
|
||||
var ancestors = new fsVersionedType[attr.PreviousModels != null ? attr.PreviousModels.Length : 0];
|
||||
for (var i = 0; i < ancestors.Length; ++i)
|
||||
{
|
||||
var ancestorType = GetVersionedType(attr.PreviousModels[i]);
|
||||
if (ancestorType.IsEmpty)
|
||||
{
|
||||
throw new Exception("Unable to create versioned type for ancestor " + ancestorType + "; please add an [fsObject(VersionString=\"...\")] attribute");
|
||||
}
|
||||
ancestors[i] = ancestorType.Value;
|
||||
}
|
||||
|
||||
// construct the actual versioned type instance
|
||||
var versionedType = new fsVersionedType
|
||||
{
|
||||
Ancestors = ancestors,
|
||||
VersionString = attr.VersionString,
|
||||
ModelType = type
|
||||
};
|
||||
|
||||
// finally, verify that the versioned type passes some
|
||||
// sanity checks
|
||||
VerifyUniqueVersionStrings(versionedType);
|
||||
VerifyConstructors(versionedType);
|
||||
|
||||
optionalVersionedType = fsOption.Just(versionedType);
|
||||
}
|
||||
}
|
||||
|
||||
_cache[type] = optionalVersionedType;
|
||||
}
|
||||
|
||||
return optionalVersionedType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the given type has constructors to migrate from all
|
||||
/// ancestor types.
|
||||
/// </summary>
|
||||
private static void VerifyConstructors(fsVersionedType type)
|
||||
{
|
||||
var publicConstructors = type.ModelType.GetDeclaredConstructors();
|
||||
|
||||
for (var i = 0; i < type.Ancestors.Length; ++i)
|
||||
{
|
||||
var requiredConstructorType = type.Ancestors[i].ModelType;
|
||||
|
||||
var found = false;
|
||||
for (var j = 0; j < publicConstructors.Length; ++j)
|
||||
{
|
||||
var parameters = publicConstructors[j].GetParameters();
|
||||
if (parameters.Length == 1 && parameters[0].ParameterType == requiredConstructorType)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found == false)
|
||||
{
|
||||
throw new fsMissingVersionConstructorException(type.ModelType, requiredConstructorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the given version graph contains only unique versions.
|
||||
/// </summary>
|
||||
private static void VerifyUniqueVersionStrings(fsVersionedType type)
|
||||
{
|
||||
// simple tree traversal
|
||||
|
||||
var found = new Dictionary<string, Type>();
|
||||
|
||||
var remaining = new Queue<fsVersionedType>();
|
||||
remaining.Enqueue(type);
|
||||
|
||||
while (remaining.Count > 0)
|
||||
{
|
||||
var item = remaining.Dequeue();
|
||||
|
||||
// Verify we do not already have the version string. Take into
|
||||
// account that we're not just comparing the same model twice,
|
||||
// since we can have a valid import graph that has the same model
|
||||
// multiple times.
|
||||
if (found.ContainsKey(item.VersionString) && found[item.VersionString] != item.ModelType)
|
||||
{
|
||||
throw new fsDuplicateVersionNameException(found[item.VersionString], item.ModelType, item.VersionString);
|
||||
}
|
||||
found[item.VersionString] = item.ModelType;
|
||||
|
||||
// scan the ancestors as well
|
||||
foreach (var ancestor in item.Ancestors)
|
||||
{
|
||||
remaining.Enqueue(ancestor);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer.Internal
|
||||
{
|
||||
public struct fsVersionedType
|
||||
{
|
||||
/// <summary>
|
||||
/// The direct ancestors that this type can import.
|
||||
/// </summary>
|
||||
public fsVersionedType[] Ancestors;
|
||||
|
||||
/// <summary>
|
||||
/// The identifying string that is unique among all ancestors.
|
||||
/// </summary>
|
||||
public string VersionString;
|
||||
|
||||
/// <summary>
|
||||
/// The modeling type that this versioned type maps back to.
|
||||
/// </summary>
|
||||
public Type ModelType;
|
||||
|
||||
/// <summary>
|
||||
/// Migrate from an instance of an ancestor.
|
||||
/// </summary>
|
||||
public object Migrate(object ancestorInstance)
|
||||
{
|
||||
return Activator.CreateInstance(ModelType, ancestorInstance);
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "fsVersionedType [ModelType=" + ModelType + ", VersionString=" + VersionString + ", Ancestors.Length=" + Ancestors.Length + "]";
|
||||
}
|
||||
|
||||
public static bool operator ==(fsVersionedType a, fsVersionedType b)
|
||||
{
|
||||
return a.ModelType == b.ModelType;
|
||||
}
|
||||
|
||||
public static bool operator !=(fsVersionedType a, fsVersionedType b)
|
||||
{
|
||||
return a.ModelType != b.ModelType;
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return
|
||||
obj is fsVersionedType &&
|
||||
ModelType == ((fsVersionedType)obj).ModelType;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return ModelType.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// A property or field on a MetaType. This unifies the FieldInfo and
|
||||
/// PropertyInfo classes.
|
||||
/// </summary>
|
||||
public class fsMetaProperty
|
||||
{
|
||||
internal fsMetaProperty(fsConfig config, FieldInfo field)
|
||||
{
|
||||
_memberInfo = field;
|
||||
StorageType = field.FieldType;
|
||||
MemberName = field.Name;
|
||||
IsPublic = field.IsPublic;
|
||||
IsReadOnly = field.IsInitOnly;
|
||||
CanRead = true;
|
||||
CanWrite = true;
|
||||
|
||||
CommonInitialize(config);
|
||||
}
|
||||
|
||||
internal fsMetaProperty(fsConfig config, PropertyInfo property)
|
||||
{
|
||||
_memberInfo = property;
|
||||
StorageType = property.PropertyType;
|
||||
MemberName = property.Name;
|
||||
IsPublic = property.GetGetMethod() != null && property.GetGetMethod().IsPublic && property.GetSetMethod() != null && property.GetSetMethod().IsPublic;
|
||||
IsReadOnly = false;
|
||||
CanRead = property.CanRead;
|
||||
CanWrite = property.CanWrite;
|
||||
|
||||
CommonInitialize(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Internal handle to the reflected member.
|
||||
/// </summary>
|
||||
internal MemberInfo _memberInfo;
|
||||
|
||||
/// <summary>
|
||||
/// The type of value that is stored inside of the property. For example,
|
||||
/// for an int field, StorageType will be typeof(int).
|
||||
/// </summary>
|
||||
public Type StorageType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// A custom fsBaseConverter instance to use for this field/property, if
|
||||
/// requested. This will be null if the default converter selection
|
||||
/// algorithm should be used. This is specified using the [fsObject]
|
||||
/// annotation with the Converter field.
|
||||
/// </summary>
|
||||
public Type OverrideConverterType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Can this property be read?
|
||||
/// </summary>
|
||||
public bool CanRead { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Can this property be written to?
|
||||
/// </summary>
|
||||
public bool CanWrite { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The serialized name of the property, as it should appear in JSON.
|
||||
/// </summary>
|
||||
public string JsonName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the actual member.
|
||||
/// </summary>
|
||||
public string MemberName { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this member public?
|
||||
/// </summary>
|
||||
public bool IsPublic { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Is this type readonly? We can modify readonly properties using
|
||||
/// reflection, but not using generated C#.
|
||||
/// </summary>
|
||||
public bool IsReadOnly { get; private set; }
|
||||
|
||||
private void CommonInitialize(fsConfig config)
|
||||
{
|
||||
var attr = fsPortableReflection.GetAttribute<fsPropertyAttribute>(_memberInfo);
|
||||
if (attr != null)
|
||||
{
|
||||
JsonName = attr.Name;
|
||||
OverrideConverterType = attr.Converter;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(JsonName))
|
||||
{
|
||||
JsonName = config.GetJsonNameFromMemberName(MemberName, _memberInfo);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes a value to the property that this MetaProperty represents,
|
||||
/// using given object instance as the context.
|
||||
/// </summary>
|
||||
public void Write(object context, object value)
|
||||
{
|
||||
var field = _memberInfo as FieldInfo;
|
||||
var property = _memberInfo as PropertyInfo;
|
||||
|
||||
if (field != null)
|
||||
{
|
||||
if (PlatformUtility.supportsJit)
|
||||
{
|
||||
field.SetValueOptimized(context, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
field.SetValue(context, value);
|
||||
}
|
||||
}
|
||||
else if (property != null)
|
||||
{
|
||||
if (PlatformUtility.supportsJit)
|
||||
{
|
||||
if (property.CanWrite)
|
||||
{
|
||||
property.SetValueOptimized(context, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var setMethod = property.GetSetMethod(/*nonPublic:*/ true);
|
||||
if (setMethod != null)
|
||||
{
|
||||
setMethod.Invoke(context, new[] { value });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a value from the property that this MetaProperty represents,
|
||||
/// using the given object instance as the context.
|
||||
/// </summary>
|
||||
public object Read(object context)
|
||||
{
|
||||
if (_memberInfo is PropertyInfo)
|
||||
{
|
||||
#if LUDIQ_OPTIMIZE
|
||||
return ((PropertyInfo)_memberInfo).GetValueOptimized(context);
|
||||
#else
|
||||
return ((PropertyInfo)_memberInfo).GetValue(context, null); // Attempt fix-1588
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
#if LUDIQ_OPTIMIZE
|
||||
return ((FieldInfo)_memberInfo).GetValueOptimized(context);
|
||||
#else
|
||||
return ((FieldInfo)_memberInfo).GetValue(context);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,414 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// MetaType contains metadata about a type. This is used by the reflection
|
||||
/// serializer.
|
||||
/// </summary>
|
||||
public class fsMetaType
|
||||
{
|
||||
private fsMetaType(fsConfig config, Type reflectedType)
|
||||
{
|
||||
ReflectedType = reflectedType;
|
||||
|
||||
var properties = new List<fsMetaProperty>();
|
||||
CollectProperties(config, properties, reflectedType);
|
||||
Properties = properties.ToArray();
|
||||
}
|
||||
|
||||
public Type ReflectedType;
|
||||
private bool _hasEmittedAotData;
|
||||
private bool? _hasDefaultConstructorCache;
|
||||
private bool _isDefaultConstructorPublic;
|
||||
|
||||
public fsMetaProperty[] Properties { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the type represented by this metadata contains a
|
||||
/// default constructor.
|
||||
/// </summary>
|
||||
public bool HasDefaultConstructor
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_hasDefaultConstructorCache.HasValue == false)
|
||||
{
|
||||
// arrays are considered to have a default constructor
|
||||
if (ReflectedType.Resolve().IsArray)
|
||||
{
|
||||
_hasDefaultConstructorCache = true;
|
||||
_isDefaultConstructorPublic = true;
|
||||
}
|
||||
// value types (ie, structs) always have a default
|
||||
// constructor
|
||||
else if (ReflectedType.Resolve().IsValueType)
|
||||
{
|
||||
_hasDefaultConstructorCache = true;
|
||||
_isDefaultConstructorPublic = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// consider private constructors as well
|
||||
var ctor = ReflectedType.GetDeclaredConstructor(fsPortableReflection.EmptyTypes);
|
||||
_hasDefaultConstructorCache = ctor != null;
|
||||
if (ctor != null)
|
||||
{
|
||||
_isDefaultConstructorPublic = ctor.IsPublic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return _hasDefaultConstructorCache.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to emit an AOT compiled direct converter for this type.
|
||||
/// </summary>
|
||||
/// <returns>True if AOT data was emitted, false otherwise.</returns>
|
||||
public bool EmitAotData()
|
||||
{
|
||||
if (_hasEmittedAotData == false)
|
||||
{
|
||||
_hasEmittedAotData = true;
|
||||
|
||||
// NOTE: Even if the type has derived types, we can still
|
||||
// generate a direct converter for it. Direct converters are not
|
||||
// used for inherited types, so the reflected converter or
|
||||
// something similar will be used for the derived type instead of
|
||||
// our AOT compiled one.
|
||||
|
||||
for (var i = 0; i < Properties.Length; ++i)
|
||||
{
|
||||
// Cannot AOT compile since we need to public member access.
|
||||
if (Properties[i].IsPublic == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Cannot AOT compile since readonly members can only be
|
||||
// modified using reflection.
|
||||
if (Properties[i].IsReadOnly)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Cannot AOT compile since we need a default ctor.
|
||||
if (HasDefaultConstructor == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
fsAotCompilationManager.AddAotCompilation(ReflectedType, Properties, _isDefaultConstructorPublic);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the type that this metadata points back to.
|
||||
/// If this type has a default constructor, then Activator.CreateInstance
|
||||
/// will be used to construct the type (or Array.CreateInstance if it an
|
||||
/// array). Otherwise, an uninitialized object created via
|
||||
/// FormatterServices.GetSafeUninitializedObject is used to construct the
|
||||
/// instance.
|
||||
/// </summary>
|
||||
public object CreateInstance()
|
||||
{
|
||||
if (ReflectedType.Resolve().IsInterface || ReflectedType.Resolve().IsAbstract)
|
||||
{
|
||||
throw new Exception("Cannot create an instance of an interface or abstract type for " + ReflectedType);
|
||||
}
|
||||
|
||||
#if !NO_UNITY
|
||||
// Unity requires special construction logic for types that derive
|
||||
// from ScriptableObject.
|
||||
if (typeof(UnityEngine.ScriptableObject).IsAssignableFrom(ReflectedType))
|
||||
{
|
||||
return UnityEngine.ScriptableObject.CreateInstance(ReflectedType);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Strings don't have default constructors but also fail when run
|
||||
// through FormatterSerivces.GetSafeUninitializedObject
|
||||
if (typeof(string) == ReflectedType)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (HasDefaultConstructor == false)
|
||||
{
|
||||
#if !UNITY_EDITOR && (UNITY_WEBPLAYER || UNITY_WP8 || UNITY_METRO)
|
||||
throw new InvalidOperationException("The selected Unity platform requires " +
|
||||
ReflectedType.FullName + " to have a default constructor. Please add one.");
|
||||
#else
|
||||
return System.Runtime.Serialization.FormatterServices.GetSafeUninitializedObject(ReflectedType);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (ReflectedType.Resolve().IsArray)
|
||||
{
|
||||
// we have to start with a size zero array otherwise it will have
|
||||
// invalid data inside of it
|
||||
return Array.CreateInstance(ReflectedType.GetElementType(), 0);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
#if (!UNITY_EDITOR && (UNITY_METRO))
|
||||
// In WinRT/WinStore builds, Activator.CreateInstance(..., true)
|
||||
// is broken
|
||||
return Activator.CreateInstance(ReflectedType);
|
||||
#else
|
||||
return Activator.CreateInstance(ReflectedType, /*nonPublic:*/ true);
|
||||
#endif
|
||||
}
|
||||
#if (!UNITY_EDITOR && (UNITY_METRO)) == false
|
||||
catch (MissingMethodException e)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to create instance of " + ReflectedType + "; there is no default constructor", e);
|
||||
}
|
||||
#endif
|
||||
catch (TargetInvocationException e)
|
||||
{
|
||||
throw new InvalidOperationException("Constructor of " + ReflectedType + " threw an exception when creating an instance", e);
|
||||
}
|
||||
catch (MemberAccessException e)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to access constructor of " + ReflectedType, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<fsConfig, Dictionary<Type, fsMetaType>> _configMetaTypes =
|
||||
new Dictionary<fsConfig, Dictionary<Type, fsMetaType>>();
|
||||
|
||||
public static fsMetaType Get(fsConfig config, Type type)
|
||||
{
|
||||
Dictionary<Type, fsMetaType> metaTypes;
|
||||
lock (typeof(fsMetaType))
|
||||
{
|
||||
if (_configMetaTypes.TryGetValue(config, out metaTypes) == false)
|
||||
{
|
||||
metaTypes = _configMetaTypes[config] = new Dictionary<Type, fsMetaType>();
|
||||
}
|
||||
}
|
||||
|
||||
fsMetaType metaType;
|
||||
if (metaTypes.TryGetValue(type, out metaType) == false)
|
||||
{
|
||||
metaType = new fsMetaType(config, type);
|
||||
metaTypes[type] = metaType;
|
||||
}
|
||||
|
||||
return metaType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears out the cached type results. Useful if some prior assumptions
|
||||
/// become invalid, ie, the default member serialization mode.
|
||||
/// </summary>
|
||||
public static void ClearCache()
|
||||
{
|
||||
lock (typeof(fsMetaType))
|
||||
{
|
||||
_configMetaTypes = new Dictionary<fsConfig, Dictionary<Type, fsMetaType>>();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CollectProperties(fsConfig config, List<fsMetaProperty> properties, Type reflectedType)
|
||||
{
|
||||
// do we require a [SerializeField] or [fsProperty] attribute?
|
||||
var requireOptIn = config.DefaultMemberSerialization == fsMemberSerialization.OptIn;
|
||||
var requireOptOut = config.DefaultMemberSerialization == fsMemberSerialization.OptOut;
|
||||
|
||||
var attr = fsPortableReflection.GetAttribute<fsObjectAttribute>(reflectedType);
|
||||
if (attr != null)
|
||||
{
|
||||
requireOptIn = attr.MemberSerialization == fsMemberSerialization.OptIn;
|
||||
requireOptOut = attr.MemberSerialization == fsMemberSerialization.OptOut;
|
||||
}
|
||||
|
||||
var members = reflectedType.GetDeclaredMembers();
|
||||
foreach (var member in members)
|
||||
{
|
||||
// We don't serialize members annotated with any of the ignore
|
||||
// serialize attributes
|
||||
if (config.IgnoreSerializeAttributes.Any(t => fsPortableReflection.HasAttribute(member, t)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var property = member as PropertyInfo;
|
||||
var field = member as FieldInfo;
|
||||
|
||||
// Early out if it's neither a field or a property, since we
|
||||
// don't serialize anything else.
|
||||
if (property == null && field == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip properties if we don't want them, to avoid the cost of
|
||||
// checking attributes.
|
||||
if (property != null && !config.EnablePropertySerialization)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If an opt-in annotation is required, then skip the property if
|
||||
// it doesn't have one of the serialize attributes
|
||||
if (requireOptIn &&
|
||||
!config.SerializeAttributes.Any(t => fsPortableReflection.HasAttribute(member, t)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// If an opt-out annotation is required, then skip the property
|
||||
// *only if* it has one of the not serialize attributes
|
||||
if (requireOptOut &&
|
||||
config.IgnoreSerializeAttributes.Any(t => fsPortableReflection.HasAttribute(member, t)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property != null)
|
||||
{
|
||||
if (CanSerializeProperty(config, property, members, requireOptOut))
|
||||
{
|
||||
properties.Add(new fsMetaProperty(config, property));
|
||||
}
|
||||
}
|
||||
else if (field != null)
|
||||
{
|
||||
if (CanSerializeField(config, field, requireOptOut))
|
||||
{
|
||||
properties.Add(new fsMetaProperty(config, field));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (reflectedType.Resolve().BaseType != null)
|
||||
{
|
||||
CollectProperties(config, properties, reflectedType.Resolve().BaseType);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsAutoProperty(PropertyInfo property, MemberInfo[] members)
|
||||
{
|
||||
return
|
||||
property.CanWrite && property.CanRead &&
|
||||
fsPortableReflection.HasAttribute(
|
||||
property.GetGetMethod(), typeof(CompilerGeneratedAttribute), /*shouldCache:*/ false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if the given property should be serialized.
|
||||
/// </summary>
|
||||
/// <param name="annotationFreeValue">
|
||||
/// Should a property without any annotations be serialized?
|
||||
/// </param>
|
||||
private static bool CanSerializeProperty(fsConfig config, PropertyInfo property, MemberInfo[] members, bool annotationFreeValue)
|
||||
{
|
||||
// We don't serialize delegates
|
||||
if (typeof(Delegate).IsAssignableFrom(property.PropertyType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var publicGetMethod = property.GetGetMethod(/*nonPublic:*/ false);
|
||||
var publicSetMethod = property.GetSetMethod(/*nonPublic:*/ false);
|
||||
|
||||
// We do not bother to serialize static fields.
|
||||
if ((publicGetMethod != null && publicGetMethod.IsStatic) ||
|
||||
(publicSetMethod != null && publicSetMethod.IsStatic))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Never serialize indexers. I can't think of a sane way to
|
||||
// serialize/deserialize them, and they're normally wrappers around
|
||||
// other fields anyway...
|
||||
if (property.GetIndexParameters().Length > 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// If a property is annotated with one of the serializable
|
||||
// attributes, then it should it should definitely be serialized.
|
||||
//
|
||||
// NOTE: We place this override check *after* the static check,
|
||||
// because we *never* allow statics to be serialized.
|
||||
if (config.SerializeAttributes.Any(t => fsPortableReflection.HasAttribute(property, t)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If the property cannot be both read and written to, we are not
|
||||
// going to serialize it regardless of the default serialization mode
|
||||
if (property.CanRead == false || property.CanWrite == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Depending on the configuration options, check whether the property
|
||||
// is automatic and if it has a public setter to determine whether it
|
||||
// should be serialized
|
||||
if ((publicGetMethod != null && (config.SerializeNonPublicSetProperties || publicSetMethod != null)) &&
|
||||
(config.SerializeNonAutoProperties || IsAutoProperty(property, members)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Otherwise, we don't bother with serialization
|
||||
return annotationFreeValue;
|
||||
}
|
||||
|
||||
private static bool CanSerializeField(fsConfig config, FieldInfo field, bool annotationFreeValue)
|
||||
{
|
||||
// We don't serialize delegates
|
||||
if (typeof(Delegate).IsAssignableFrom(field.FieldType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't serialize compiler generated fields.
|
||||
// LAZLO / LUDIQ FIX: Attributes inheritance
|
||||
if (Attribute.IsDefined(field, typeof(CompilerGeneratedAttribute), false))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We don't serialize static fields
|
||||
if (field.IsStatic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// We want to serialize any fields annotated with one of the
|
||||
// serialize attributes.
|
||||
//
|
||||
// NOTE: This occurs *after* the static check, because we *never*
|
||||
// want to serialize static fields.
|
||||
if (config.SerializeAttributes.Any(t => fsPortableReflection.HasAttribute(field, t)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// We use !IsPublic because that also checks for internal, protected,
|
||||
// and private.
|
||||
if (!annotationFreeValue && !field.IsPublic)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
#if !UNITY_EDITOR && UNITY_WSA
|
||||
// For System.Reflection.TypeExtensions
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
public static class fsReflectionUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Searches for a particular implementation of the given interface type
|
||||
/// inside of the type. This is particularly useful if the interface type
|
||||
/// is an open type, ie, typeof(IFace{}), because this method will then
|
||||
/// return IFace{} but with appropriate type parameters inserted.
|
||||
/// </summary>
|
||||
/// <param name="type">The base type to search for interface</param>
|
||||
/// <param name="interfaceType">
|
||||
/// The interface type to search for. Can be an open generic type.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// The actual interface type that the type contains, or null if there is
|
||||
/// no implementation of the given interfaceType on type.
|
||||
/// </returns>
|
||||
public static Type GetInterface(Type type, Type interfaceType)
|
||||
{
|
||||
if (interfaceType.Resolve().IsGenericType &&
|
||||
interfaceType.Resolve().IsGenericTypeDefinition == false)
|
||||
{
|
||||
throw new ArgumentException("GetInterface requires that if the interface " +
|
||||
"type is generic, then it must be the generic type definition, not a " +
|
||||
"specific generic type instantiation");
|
||||
}
|
||||
;
|
||||
|
||||
while (type != null)
|
||||
{
|
||||
foreach (var iface in type.GetInterfaces())
|
||||
{
|
||||
if (iface.Resolve().IsGenericType)
|
||||
{
|
||||
if (interfaceType == iface.GetGenericTypeDefinition())
|
||||
{
|
||||
return iface;
|
||||
}
|
||||
}
|
||||
else if (interfaceType == iface)
|
||||
{
|
||||
return iface;
|
||||
}
|
||||
}
|
||||
|
||||
type = type.Resolve().BaseType;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Caches type name to type lookups. Type lookups occur in all loaded
|
||||
/// assemblies.
|
||||
/// </summary>
|
||||
public static class fsTypeCache
|
||||
{
|
||||
// Moved / converted to RuntimeCodebase
|
||||
}
|
||||
}
|
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// The AOT compilation manager
|
||||
/// </summary>
|
||||
public class fsAotCompilationManager
|
||||
{
|
||||
private static Dictionary<Type, string> _computedAotCompilations = new Dictionary<Type, string>();
|
||||
private static List<AotCompilation> _uncomputedAotCompilations = new List<AotCompilation>();
|
||||
|
||||
/// <summary>
|
||||
/// Ahead of time compilations that are available. The type maps to the
|
||||
/// object type the generated converter will serialize/deserialize, and
|
||||
/// the string is the text content for a converter that will do the
|
||||
/// serialization.
|
||||
/// <para />
|
||||
/// The generated serializer is completely independent and you don't need
|
||||
/// to do anything. Simply add the file to your project and it'll get
|
||||
/// used instead of the reflection based one.
|
||||
/// </summary>
|
||||
public static Dictionary<Type, string> AvailableAotCompilations
|
||||
{
|
||||
get
|
||||
{
|
||||
for (var i = 0; i < _uncomputedAotCompilations.Count; ++i)
|
||||
{
|
||||
var item = _uncomputedAotCompilations[i];
|
||||
_computedAotCompilations[item.Type] = GenerateDirectConverterForTypeInCSharp(item.Type, item.Members, item.IsConstructorPublic);
|
||||
}
|
||||
_uncomputedAotCompilations.Clear();
|
||||
|
||||
return _computedAotCompilations;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a helper method that makes it simple to run an AOT
|
||||
/// compilation on the given type.
|
||||
/// </summary>
|
||||
/// <param name="config">
|
||||
/// The configuration to use when running AOT compilation.
|
||||
/// </param>
|
||||
/// <param name="type">
|
||||
/// The type to perform the AOT compilation on.
|
||||
/// </param>
|
||||
/// <param name="aotCompiledClassInCSharp">
|
||||
/// The AOT class. Add this C# code to your project.
|
||||
/// </param>
|
||||
/// <returns>True if AOT compilation was successful.</returns>
|
||||
public static bool TryToPerformAotCompilation(fsConfig config, Type type, out string aotCompiledClassInCSharp)
|
||||
{
|
||||
if (fsMetaType.Get(config, type).EmitAotData())
|
||||
{
|
||||
aotCompiledClassInCSharp = AvailableAotCompilations[type];
|
||||
return true;
|
||||
}
|
||||
|
||||
aotCompiledClassInCSharp = default(string);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new AOT compilation unit.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of object we are AOT compiling.</param>
|
||||
/// <param name="members">
|
||||
/// The members on the object which will be serialized/deserialized.
|
||||
/// </param>
|
||||
public static void AddAotCompilation(Type type, fsMetaProperty[] members, bool isConstructorPublic)
|
||||
{
|
||||
_uncomputedAotCompilations.Add(new AotCompilation
|
||||
{
|
||||
Type = type,
|
||||
Members = members,
|
||||
IsConstructorPublic = isConstructorPublic
|
||||
});
|
||||
}
|
||||
|
||||
private static string GetConverterString(fsMetaProperty member)
|
||||
{
|
||||
if (member.OverrideConverterType == null)
|
||||
{
|
||||
return "null";
|
||||
}
|
||||
|
||||
return $"typeof({fsTypeExtensions.CSharpName( /*includeNamespace:*/member.OverrideConverterType, true)})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AOT compiles the object (in C#).
|
||||
/// </summary>
|
||||
private static string GenerateDirectConverterForTypeInCSharp(Type type, fsMetaProperty[] members, bool isConstructorPublic)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var typeName = fsTypeExtensions.CSharpName( /*includeNamespace:*/
|
||||
type, true);
|
||||
var typeNameSafeDecl = type.CSharpName(true, true);
|
||||
|
||||
sb.AppendLine("using System;");
|
||||
sb.AppendLine("using System.Collections.Generic;");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("namespace Unity.VisualScripting.Dependencies.FullSerializer {");
|
||||
sb.AppendLine(" partial class fsConverterRegistrar {");
|
||||
sb.AppendLine(" public static Speedup." + typeNameSafeDecl + "_DirectConverter " + "Register_" + typeNameSafeDecl + ";");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine("}");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine("namespace Unity.VisualScripting.Dependencies.FullSerializer.Speedup {");
|
||||
sb.AppendLine(" public class " + typeNameSafeDecl + "_DirectConverter : fsDirectConverter<" + typeName + "> {");
|
||||
sb.AppendLine(" protected override fsResult DoSerialize(" + typeName + " model, Dictionary<string, fsData> serialized) {");
|
||||
sb.AppendLine(" var result = fsResult.Success;");
|
||||
sb.AppendLine();
|
||||
foreach (var member in members)
|
||||
{
|
||||
sb.AppendLine(" result += SerializeMember(serialized, " + GetConverterString(member) + ", \"" + member.JsonName + "\", model." + member.MemberName + ");");
|
||||
}
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" return result;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" protected override fsResult DoDeserialize(Dictionary<string, fsData> data, ref " + typeName + " model) {");
|
||||
sb.AppendLine(" var result = fsResult.Success;");
|
||||
sb.AppendLine();
|
||||
for (var i = 0; i < members.Length; ++i)
|
||||
{
|
||||
var member = members[i];
|
||||
sb.AppendLine(" var t" + i + " = model." + member.MemberName + ";");
|
||||
sb.AppendLine(" result += DeserializeMember(data, " + GetConverterString(member) + ", \"" + member.JsonName + "\", out t" + i + ");");
|
||||
sb.AppendLine(" model." + member.MemberName + " = t" + i + ";");
|
||||
sb.AppendLine();
|
||||
}
|
||||
sb.AppendLine(" return result;");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine();
|
||||
sb.AppendLine(" public override object CreateInstance(fsData data, Type storageType) {");
|
||||
if (isConstructorPublic)
|
||||
{
|
||||
sb.AppendLine(" return new " + typeName + "();");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine(" return Activator.CreateInstance(typeof(" + typeName + "), /*nonPublic:*/true);");
|
||||
}
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine(" }");
|
||||
sb.AppendLine("}");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private struct AotCompilation
|
||||
{
|
||||
public Type Type;
|
||||
public fsMetaProperty[] Members;
|
||||
public bool IsConstructorPublic;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// The serialization converter allows for customization of the serialization
|
||||
/// process.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// You do not want to derive from this class - there is no way to actually
|
||||
/// use it within the serializer.. Instead, derive from either fsConverter or
|
||||
/// fsDirectConverter
|
||||
/// </remarks>
|
||||
public abstract class fsBaseConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// The serializer that was owns this converter.
|
||||
/// </summary>
|
||||
public fsSerializer Serializer;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an object instance that will be passed to TryDeserialize.
|
||||
/// This should **not** deserialize the object.
|
||||
/// </summary>
|
||||
/// <param name="data">The data the object was serialized with.</param>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <returns>An object instance</returns>
|
||||
public virtual object CreateInstance(fsData data, Type storageType)
|
||||
{
|
||||
if (RequestCycleSupport(storageType))
|
||||
{
|
||||
throw new InvalidOperationException("Please override CreateInstance for " +
|
||||
GetType() + "; the object graph for " + storageType +
|
||||
" can contain potentially contain cycles, so separated instance creation " +
|
||||
"is needed");
|
||||
}
|
||||
|
||||
return storageType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, then the serializer will support cyclic references with the
|
||||
/// given converted type.
|
||||
/// </summary>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is currently storing the object that is
|
||||
/// being serialized.
|
||||
/// </param>
|
||||
public virtual bool RequestCycleSupport(Type storageType)
|
||||
{
|
||||
if (storageType == typeof(string))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return storageType.Resolve().IsClass || storageType.Resolve().IsInterface;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If true, then the serializer will include inheritance data for the
|
||||
/// given converter.
|
||||
/// </summary>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is currently storing the object that is
|
||||
/// being serialized.
|
||||
/// </param>
|
||||
public virtual bool RequestInheritanceSupport(Type storageType)
|
||||
{
|
||||
return storageType.Resolve().IsSealed == false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serialize the actual object into the given data storage.
|
||||
/// </summary>
|
||||
/// <param name="instance">
|
||||
/// The object instance to serialize. This will never be null.
|
||||
/// </param>
|
||||
/// <param name="serialized">The serialized state.</param>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing this instance.
|
||||
/// </param>
|
||||
/// <returns>If serialization was successful.</returns>
|
||||
public abstract fsResult TrySerialize(object instance, out fsData serialized, Type storageType);
|
||||
|
||||
/// <summary>
|
||||
/// Deserialize data into the object instance.
|
||||
/// </summary>
|
||||
/// <param name="data">Serialization data to deserialize from.</param>
|
||||
/// <param name="instance">
|
||||
/// The object instance to deserialize into.
|
||||
/// </param>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// True if serialization was successful, false otherwise.
|
||||
/// </returns>
|
||||
public abstract fsResult TryDeserialize(fsData data, ref object instance, Type storageType);
|
||||
|
||||
protected fsResult FailExpectedType(fsData data, params fsDataType[] types)
|
||||
{
|
||||
return fsResult.Fail(GetType().Name + " expected one of " +
|
||||
string.Join(", ", types.Select(t => t.ToString()).ToArray()) +
|
||||
" but got " + data.Type + " in " + data);
|
||||
}
|
||||
|
||||
protected fsResult CheckType(fsData data, fsDataType type)
|
||||
{
|
||||
if (data.Type != type)
|
||||
{
|
||||
return fsResult.Fail(GetType().Name + " expected " + type + " but got " + data.Type + " in " + data);
|
||||
}
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
protected fsResult CheckKey(fsData data, string key, out fsData subitem)
|
||||
{
|
||||
return CheckKey(data.AsDictionary, key, out subitem);
|
||||
}
|
||||
|
||||
protected fsResult CheckKey(Dictionary<string, fsData> data, string key, out fsData subitem)
|
||||
{
|
||||
if (data.TryGetValue(key, out subitem) == false)
|
||||
{
|
||||
return fsResult.Fail(GetType().Name + " requires a <" + key + "> key in the data " + data);
|
||||
}
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
protected fsResult SerializeMember<T>(Dictionary<string, fsData> data, Type overrideConverterType, string name, T value)
|
||||
{
|
||||
fsData memberData;
|
||||
var result = Serializer.TrySerialize(typeof(T), overrideConverterType, value, out memberData);
|
||||
if (result.Succeeded)
|
||||
{
|
||||
data[name] = memberData;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected fsResult DeserializeMember<T>(Dictionary<string, fsData> data, Type overrideConverterType, string name, out T value)
|
||||
{
|
||||
fsData memberData;
|
||||
if (data.TryGetValue(name, out memberData) == false)
|
||||
{
|
||||
value = default(T);
|
||||
return fsResult.Fail("Unable to find member \"" + name + "\"");
|
||||
}
|
||||
|
||||
object storage = null;
|
||||
var result = Serializer.TryDeserialize(memberData, typeof(T), overrideConverterType, ref storage);
|
||||
value = (T)storage;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
// Global configuration options.
|
||||
public static class fsGlobalConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// Should deserialization be case sensitive? If this is false and the
|
||||
/// JSON has multiple members with the same keys only separated by case,
|
||||
/// then this results in undefined behavior.
|
||||
/// </summary>
|
||||
public static bool IsCaseSensitive = true;
|
||||
|
||||
/// <summary>
|
||||
/// If exceptions are allowed internally, then additional date formats
|
||||
/// can be deserialized. Note that the Full Serializer public API will
|
||||
/// *not* throw exceptions with this enabled; errors will still be
|
||||
/// returned in a fsResult instance.
|
||||
/// </summary>
|
||||
public static bool AllowInternalExceptions = true;
|
||||
|
||||
/// <summary>
|
||||
/// This string will be used to prefix fields used internally by
|
||||
/// FullSerializer.
|
||||
/// </summary>
|
||||
public static string InternalFieldPrefix = "$";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Enables some top-level customization of Full Serializer.
|
||||
/// </summary>
|
||||
public class fsConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// The attributes that will force a field or property to be serialized.
|
||||
/// </summary>
|
||||
public Type[] SerializeAttributes =
|
||||
{
|
||||
#if !NO_UNITY
|
||||
typeof(UnityEngine.SerializeField),
|
||||
#endif
|
||||
typeof(fsPropertyAttribute),
|
||||
typeof(SerializeAttribute),
|
||||
typeof(SerializeAsAttribute)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The attributes that will force a field or property to *not* be
|
||||
/// serialized.
|
||||
/// </summary>
|
||||
public Type[] IgnoreSerializeAttributes =
|
||||
{
|
||||
typeof(NonSerializedAttribute),
|
||||
typeof(fsIgnoreAttribute),
|
||||
typeof(DoNotSerializeAttribute)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The default member serialization.
|
||||
/// </summary>
|
||||
public fsMemberSerialization DefaultMemberSerialization = fsMemberSerialization.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Convert a C# field/property name into the key used for the JSON
|
||||
/// object. For example, you could force all JSON names to lowercase
|
||||
/// with:
|
||||
/// fsConfig.GetJsonNameFromMemberName = (name, info) =>
|
||||
/// name.ToLower();
|
||||
/// This will only be used when the name is not explicitly specified with
|
||||
/// fsProperty.
|
||||
/// </summary>
|
||||
public Func<string, MemberInfo, string> GetJsonNameFromMemberName = (name, info) => name;
|
||||
|
||||
/// <summary>
|
||||
/// If false, then *all* property serialization support will be disabled
|
||||
/// - even properties explicitly annotated with fsProperty or any other
|
||||
/// opt-in annotation.
|
||||
/// Setting this to false means that SerializeNonAutoProperties and
|
||||
/// SerializeNonPublicSetProperties will be completely ignored.
|
||||
/// </summary>
|
||||
public bool EnablePropertySerialization = true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the default serialization behaviour include non-auto
|
||||
/// properties?
|
||||
/// </summary>
|
||||
public bool SerializeNonAutoProperties = false;
|
||||
|
||||
/// <summary>
|
||||
/// Should the default serialization behaviour include properties with
|
||||
/// non-public setters?
|
||||
/// </summary>
|
||||
public bool SerializeNonPublicSetProperties = true;
|
||||
|
||||
/// <summary>
|
||||
/// If not null, this string format will be used for DateTime instead of
|
||||
/// the default one.
|
||||
/// </summary>
|
||||
public string CustomDateTimeFormatString = null;
|
||||
|
||||
/// <summary>
|
||||
/// Int64 and UInt64 will be serialized and deserialized as string for
|
||||
/// compatibility
|
||||
/// </summary>
|
||||
public bool Serialize64BitIntegerAsString = false;
|
||||
|
||||
/// <summary>
|
||||
/// Enums are serialized using their names by default. Setting this to
|
||||
/// true will serialize them as integers instead.
|
||||
/// </summary>
|
||||
public bool SerializeEnumsAsInteger = false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// fsContext stores global metadata that can be used to customize how
|
||||
/// fsConverters operate during serialization.
|
||||
/// </summary>
|
||||
public sealed class fsContext
|
||||
{
|
||||
/// <summary>
|
||||
/// All of the context objects.
|
||||
/// </summary>
|
||||
private readonly Dictionary<Type, object> _contextObjects = new Dictionary<Type, object>();
|
||||
|
||||
/// <summary>
|
||||
/// Removes all context objects from the context.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
_contextObjects.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the context object for the given type with the given value.
|
||||
/// </summary>
|
||||
public void Set<T>(T obj)
|
||||
{
|
||||
_contextObjects[typeof(T)] = obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if there is a context object for the given type.
|
||||
/// </summary>
|
||||
public bool Has<T>()
|
||||
{
|
||||
return _contextObjects.ContainsKey(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the context object for the given type.
|
||||
/// </summary>
|
||||
public T Get<T>()
|
||||
{
|
||||
object val;
|
||||
if (_contextObjects.TryGetValue(typeof(T), out val))
|
||||
{
|
||||
return (T)val;
|
||||
}
|
||||
throw new InvalidOperationException("There is no context object of type " + typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// The serialization converter allows for customization of the serialization
|
||||
/// process.
|
||||
/// </summary>
|
||||
public abstract class fsConverter : fsBaseConverter
|
||||
{
|
||||
/// <summary>
|
||||
/// Can this converter serialize and deserialize the given object type?
|
||||
/// </summary>
|
||||
/// <param name="type">The given object type.</param>
|
||||
/// <returns>
|
||||
/// True if the converter can serialize it, false otherwise.
|
||||
/// </returns>
|
||||
public abstract bool CanProcess(Type type);
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Unity.VisualScripting.FullSerializer.Internal;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// This class allows arbitrary code to easily register global converters. To
|
||||
/// add a converter, simply declare a new field called "Register_*" that
|
||||
/// stores the type of converter you would like to add. Alternatively, you
|
||||
/// can do the same with a method called "Register_*"; just add the converter
|
||||
/// type to the `Converters` list.
|
||||
/// </summary>
|
||||
public partial class fsConverterRegistrar
|
||||
{
|
||||
static fsConverterRegistrar()
|
||||
{
|
||||
Converters = new List<Type>();
|
||||
|
||||
foreach (var field in typeof(fsConverterRegistrar).GetDeclaredFields())
|
||||
{
|
||||
if (field.Name.StartsWith("Register_"))
|
||||
{
|
||||
Converters.Add(field.FieldType);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var method in typeof(fsConverterRegistrar).GetDeclaredMethods())
|
||||
{
|
||||
if (method.Name.StartsWith("Register_"))
|
||||
{
|
||||
method.Invoke(null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static List<Type> Converters;
|
||||
//public static AnimationCurve_DirectConverter Register_AnimationCurve_DirectConverter;
|
||||
|
||||
// Example field registration:
|
||||
|
||||
// Example method registration:
|
||||
//public static void Register_AnimationCurve_DirectConverter() {
|
||||
// Converters.Add(typeof(AnimationCurve_DirectConverter));
|
||||
//}
|
||||
}
|
||||
}
|
@@ -0,0 +1,425 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// The actual type that a JsonData instance can store.
|
||||
/// </summary>
|
||||
public enum fsDataType
|
||||
{
|
||||
Array,
|
||||
Object,
|
||||
Double,
|
||||
Int64,
|
||||
Boolean,
|
||||
String,
|
||||
Null
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A union type that stores a serialized value. The stored type can be one
|
||||
/// of six different
|
||||
/// types: null, boolean, double, Int64, string, Dictionary, or List.
|
||||
/// </summary>
|
||||
public sealed class fsData
|
||||
{
|
||||
/// <summary>
|
||||
/// The raw value that this serialized data stores. It can be one of six
|
||||
/// different types; a boolean, a double, Int64, a string, a Dictionary,
|
||||
/// or a List.
|
||||
/// </summary>
|
||||
private object _value;
|
||||
|
||||
#region ToString Implementation
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return fsJsonPrinter.CompressedJson(this);
|
||||
}
|
||||
|
||||
#endregion ToString Implementation
|
||||
|
||||
#region Constructors
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fsData instance that holds null.
|
||||
/// </summary>
|
||||
public fsData()
|
||||
{
|
||||
_value = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fsData instance that holds a boolean.
|
||||
/// </summary>
|
||||
public fsData(bool boolean)
|
||||
{
|
||||
_value = boolean;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fsData instance that holds a double.
|
||||
/// </summary>
|
||||
public fsData(double f)
|
||||
{
|
||||
_value = f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new fsData instance that holds an integer.
|
||||
/// </summary>
|
||||
public fsData(Int64 i)
|
||||
{
|
||||
_value = i;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fsData instance that holds a string.
|
||||
/// </summary>
|
||||
public fsData(string str)
|
||||
{
|
||||
_value = str;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fsData instance that holds a dictionary of values.
|
||||
/// </summary>
|
||||
public fsData(Dictionary<string, fsData> dict)
|
||||
{
|
||||
_value = dict;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a fsData instance that holds a list of values.
|
||||
/// </summary>
|
||||
public fsData(List<fsData> list)
|
||||
{
|
||||
_value = list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a fsData instance that holds a dictionary.
|
||||
/// </summary>
|
||||
public static fsData CreateDictionary()
|
||||
{
|
||||
return new fsData(new Dictionary<string, fsData>(
|
||||
fsGlobalConfig.IsCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a fsData instance that holds a list.
|
||||
/// </summary>
|
||||
public static fsData CreateList()
|
||||
{
|
||||
return new fsData(new List<fsData>());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method to create a fsData instance that holds a list with the
|
||||
/// initial capacity.
|
||||
/// </summary>
|
||||
public static fsData CreateList(int capacity)
|
||||
{
|
||||
return new fsData(new List<fsData>(capacity));
|
||||
}
|
||||
|
||||
public readonly static fsData True = new fsData(true);
|
||||
public readonly static fsData False = new fsData(false);
|
||||
public readonly static fsData Null = new fsData();
|
||||
|
||||
#endregion Constructors
|
||||
|
||||
#region Internal Helper Methods
|
||||
|
||||
/// <summary>
|
||||
/// Transforms the internal fsData instance into a dictionary.
|
||||
/// </summary>
|
||||
internal void BecomeDictionary()
|
||||
{
|
||||
_value = new Dictionary<string, fsData>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a shallow clone of this data instance.
|
||||
/// </summary>
|
||||
internal fsData Clone()
|
||||
{
|
||||
var clone = new fsData();
|
||||
clone._value = _value;
|
||||
return clone;
|
||||
}
|
||||
|
||||
#endregion Internal Helper Methods
|
||||
|
||||
#region Casting Predicates
|
||||
|
||||
public fsDataType Type
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_value == null)
|
||||
{
|
||||
return fsDataType.Null;
|
||||
}
|
||||
if (_value is double)
|
||||
{
|
||||
return fsDataType.Double;
|
||||
}
|
||||
if (_value is Int64)
|
||||
{
|
||||
return fsDataType.Int64;
|
||||
}
|
||||
if (_value is bool)
|
||||
{
|
||||
return fsDataType.Boolean;
|
||||
}
|
||||
if (_value is string)
|
||||
{
|
||||
return fsDataType.String;
|
||||
}
|
||||
if (_value is Dictionary<string, fsData>)
|
||||
{
|
||||
return fsDataType.Object;
|
||||
}
|
||||
if (_value is List<fsData>)
|
||||
{
|
||||
return fsDataType.Array;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("unknown JSON data type");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this fsData instance maps back to null.
|
||||
/// </summary>
|
||||
public bool IsNull => _value == null;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this fsData instance maps back to a double.
|
||||
/// </summary>
|
||||
public bool IsDouble => _value is double;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this fsData instance maps back to an Int64.
|
||||
/// </summary>
|
||||
public bool IsInt64 => _value is Int64;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this fsData instance maps back to a boolean.
|
||||
/// </summary>
|
||||
public bool IsBool => _value is bool;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this fsData instance maps back to a string.
|
||||
/// </summary>
|
||||
public bool IsString => _value is string;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this fsData instance maps back to a Dictionary.
|
||||
/// </summary>
|
||||
public bool IsDictionary => _value is Dictionary<string, fsData>;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this fsData instance maps back to a List.
|
||||
/// </summary>
|
||||
public bool IsList => _value is List<fsData>;
|
||||
|
||||
#endregion Casting Predicates
|
||||
|
||||
#region Casts
|
||||
|
||||
/// <summary>
|
||||
/// Casts this fsData to a double. Throws an exception if it is not a
|
||||
/// double.
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
public double AsDouble => Cast<double>();
|
||||
|
||||
/// <summary>
|
||||
/// Casts this fsData to an Int64. Throws an exception if it is not an
|
||||
/// Int64.
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
public Int64 AsInt64 => Cast<Int64>();
|
||||
|
||||
/// <summary>
|
||||
/// Casts this fsData to a boolean. Throws an exception if it is not a
|
||||
/// boolean.
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
public bool AsBool => Cast<bool>();
|
||||
|
||||
/// <summary>
|
||||
/// Casts this fsData to a string. Throws an exception if it is not a
|
||||
/// string.
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
public string AsString => Cast<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Casts this fsData to a Dictionary. Throws an exception if it is not a
|
||||
/// Dictionary.
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
public Dictionary<string, fsData> AsDictionary => Cast<Dictionary<string, fsData>>();
|
||||
|
||||
/// <summary>
|
||||
/// Casts this fsData to a List. Throws an exception if it is not a List.
|
||||
/// </summary>
|
||||
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
|
||||
public List<fsData> AsList => Cast<List<fsData>>();
|
||||
|
||||
/// <summary>
|
||||
/// Internal helper method to cast the underlying storage to the given
|
||||
/// type or throw a pretty printed exception on failure.
|
||||
/// </summary>
|
||||
private T Cast<T>()
|
||||
{
|
||||
if (_value is T)
|
||||
{
|
||||
return (T)_value;
|
||||
}
|
||||
|
||||
throw new InvalidCastException("Unable to cast <" + this + "> (with type = " +
|
||||
_value.GetType() + ") to type " + typeof(T));
|
||||
}
|
||||
|
||||
#endregion Casts
|
||||
|
||||
#region Equality Comparisons
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current
|
||||
/// object.
|
||||
/// </summary>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as fsData);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current
|
||||
/// object.
|
||||
/// </summary>
|
||||
public bool Equals(fsData other)
|
||||
{
|
||||
if (other == null || Type != other.Type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
case fsDataType.Null:
|
||||
return true;
|
||||
|
||||
case fsDataType.Double:
|
||||
return AsDouble == other.AsDouble || Math.Abs(AsDouble - other.AsDouble) < double.Epsilon;
|
||||
|
||||
case fsDataType.Int64:
|
||||
return AsInt64 == other.AsInt64;
|
||||
|
||||
case fsDataType.Boolean:
|
||||
return AsBool == other.AsBool;
|
||||
|
||||
case fsDataType.String:
|
||||
return AsString == other.AsString;
|
||||
|
||||
case fsDataType.Array:
|
||||
var thisList = AsList;
|
||||
var otherList = other.AsList;
|
||||
|
||||
if (thisList.Count != otherList.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (var i = 0; i < thisList.Count; ++i)
|
||||
{
|
||||
if (thisList[i].Equals(otherList[i]) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
case fsDataType.Object:
|
||||
var thisDict = AsDictionary;
|
||||
var otherDict = other.AsDictionary;
|
||||
|
||||
if (thisDict.Count != otherDict.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var key in thisDict.Keys)
|
||||
{
|
||||
if (otherDict.ContainsKey(key) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (thisDict[key].Equals(otherDict[key]) == false)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new Exception("Unknown data type");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true iff a == b.
|
||||
/// </summary>
|
||||
public static bool operator ==(fsData a, fsData b)
|
||||
{
|
||||
// If both are null, or both are same instance, return true.
|
||||
if (ReferenceEquals(a, b))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// If one is null, but not both, return false.
|
||||
if (((object)a == null) || ((object)b == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (a.IsDouble && b.IsDouble)
|
||||
{
|
||||
return Math.Abs(a.AsDouble - b.AsDouble) < double.Epsilon;
|
||||
}
|
||||
|
||||
return a.Equals(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true iff a != b.
|
||||
/// </summary>
|
||||
public static bool operator !=(fsData a, fsData b)
|
||||
{
|
||||
return !(a == b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a hash code for this instance.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for this instance, suitable for use in hashing algorithms
|
||||
/// and data structures like a hash table.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _value.GetHashCode();
|
||||
}
|
||||
|
||||
#endregion Equality Comparisons
|
||||
}
|
||||
}
|
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// The direct converter is similar to a regular converter, except that it
|
||||
/// targets specifically only one type. This means that it can be used
|
||||
/// without performance impact when discovering converters. It is strongly
|
||||
/// recommended that you derive from fsDirectConverter{TModel}.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Due to the way that direct converters operate, inheritance is *not*
|
||||
/// supported. Direct converters will only be used with the exact ModelType
|
||||
/// object.
|
||||
/// </remarks>
|
||||
public abstract class fsDirectConverter : fsBaseConverter
|
||||
{
|
||||
public abstract Type ModelType { get; }
|
||||
}
|
||||
|
||||
public abstract class fsDirectConverter<TModel> : fsDirectConverter
|
||||
{
|
||||
public override Type ModelType => typeof(TModel);
|
||||
|
||||
public sealed override fsResult TrySerialize(object instance, out fsData serialized, Type storageType)
|
||||
{
|
||||
var serializedDictionary = new Dictionary<string, fsData>();
|
||||
var result = DoSerialize((TModel)instance, serializedDictionary);
|
||||
serialized = new fsData(serializedDictionary);
|
||||
return result;
|
||||
}
|
||||
|
||||
public sealed override fsResult TryDeserialize(fsData data, ref object instance, Type storageType)
|
||||
{
|
||||
var result = fsResult.Success;
|
||||
if ((result += CheckType(data, fsDataType.Object)).Failed)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var obj = (TModel)instance;
|
||||
result += DoDeserialize(data.AsDictionary, ref obj);
|
||||
instance = obj;
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract fsResult DoSerialize(TModel model, Dictionary<string, fsData> serialized);
|
||||
protected abstract fsResult DoDeserialize(Dictionary<string, fsData> data, ref TModel model);
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
// note: This file contains exceptions used by FullSerializer. Exceptions are
|
||||
// never used at runtime in FullSerializer; they are only used when
|
||||
// validating annotations and code-based models.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
public sealed class fsMissingVersionConstructorException : Exception
|
||||
{
|
||||
public fsMissingVersionConstructorException(Type versionedType, Type constructorType) :
|
||||
base(versionedType + " is missing a constructor for previous model type " + constructorType)
|
||||
{ }
|
||||
}
|
||||
|
||||
public sealed class fsDuplicateVersionNameException : Exception
|
||||
{
|
||||
public fsDuplicateVersionNameException(Type typeA, Type typeB, string version) :
|
||||
base(typeA + " and " + typeB + " have the same version string (" + version + "); please change one of them.")
|
||||
{ }
|
||||
}
|
||||
}
|
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
#if !UNITY_EDITOR && UNITY_WSA
|
||||
// For System.Reflection.TypeExtensions
|
||||
using System.Reflection;
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Extend this interface on your type to receive notifications about
|
||||
/// serialization/deserialization events. If you don't have access to the
|
||||
/// type itself, then you can write an fsObjectProcessor instead.
|
||||
/// </summary>
|
||||
public interface fsISerializationCallbacks
|
||||
{
|
||||
/// <summary>
|
||||
/// Called before serialization.
|
||||
/// </summary>
|
||||
void OnBeforeSerialize(Type storageType);
|
||||
|
||||
/// <summary>
|
||||
/// Called after serialization.
|
||||
/// </summary>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <param name="data">The data that was serialized.</param>
|
||||
void OnAfterSerialize(Type storageType, ref fsData data);
|
||||
|
||||
/// <summary>
|
||||
/// Called before deserialization.
|
||||
/// </summary>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <param name="data">
|
||||
/// The data that will be used for deserialization.
|
||||
/// </param>
|
||||
void OnBeforeDeserialize(Type storageType, ref fsData data);
|
||||
|
||||
/// <summary>
|
||||
/// Called after deserialization.
|
||||
/// </summary>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <param name="instance">The type of the instance.</param>
|
||||
void OnAfterDeserialize(Type storageType);
|
||||
}
|
||||
|
||||
public class fsSerializationCallbackProcessor : fsObjectProcessor
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return typeof(fsISerializationCallbacks).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
public override void OnBeforeSerialize(Type storageType, object instance)
|
||||
{
|
||||
// Don't call the callback on null instances.
|
||||
if (instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((fsISerializationCallbacks)instance).OnBeforeSerialize(storageType);
|
||||
}
|
||||
|
||||
public override void OnAfterSerialize(Type storageType, object instance, ref fsData data)
|
||||
{
|
||||
// Don't call the callback on null instances.
|
||||
if (instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((fsISerializationCallbacks)instance).OnAfterSerialize(storageType, ref data);
|
||||
}
|
||||
|
||||
public override void OnBeforeDeserializeAfterInstanceCreation(Type storageType, object instance, ref fsData data)
|
||||
{
|
||||
if (instance is fsISerializationCallbacks == false)
|
||||
{
|
||||
throw new InvalidCastException("Please ensure the converter for " + storageType + " actually returns an instance of it, not an instance of " + instance.GetType());
|
||||
}
|
||||
|
||||
((fsISerializationCallbacks)instance).OnBeforeDeserialize(storageType, ref data);
|
||||
}
|
||||
|
||||
public override void OnAfterDeserialize(Type storageType, object instance)
|
||||
{
|
||||
// Don't call the callback on null instances.
|
||||
if (instance == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((fsISerializationCallbacks)instance).OnAfterDeserialize(storageType);
|
||||
}
|
||||
}
|
||||
|
||||
#if !NO_UNITY
|
||||
public class fsSerializationCallbackReceiverProcessor : fsObjectProcessor
|
||||
{
|
||||
public override bool CanProcess(Type type)
|
||||
{
|
||||
return typeof(ISerializationCallbackReceiver).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
public override void OnBeforeSerialize(Type storageType, object instance)
|
||||
{
|
||||
// Don't call the callback on null instances.
|
||||
if (instance == null || instance is UnityObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((ISerializationCallbackReceiver)instance).OnBeforeSerialize();
|
||||
}
|
||||
|
||||
public override void OnAfterDeserialize(Type storageType, object instance)
|
||||
{
|
||||
// Don't call the callback on null instances.
|
||||
if (instance == null || instance is UnityObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
((ISerializationCallbackReceiver)instance).OnAfterDeserialize();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// The given property or field annotated with [JsonIgnore] will not be
|
||||
/// serialized.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public sealed class fsIgnoreAttribute : Attribute { }
|
||||
}
|
@@ -0,0 +1,628 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
// TODO: properly propagate warnings/etc for fsResult states
|
||||
|
||||
/// <summary>
|
||||
/// A simple recursive descent parser for JSON.
|
||||
/// </summary>
|
||||
public class fsJsonParser
|
||||
{
|
||||
private fsJsonParser(string input)
|
||||
{
|
||||
_input = input;
|
||||
_start = 0;
|
||||
}
|
||||
|
||||
private readonly StringBuilder _cachedStringBuilder = new StringBuilder(256);
|
||||
private int _start;
|
||||
private string _input;
|
||||
|
||||
private fsResult MakeFailure(string message)
|
||||
{
|
||||
var start = Math.Max(0, _start - 20);
|
||||
var length = Math.Min(50, _input.Length - start);
|
||||
|
||||
var error = "Error while parsing: " + message + "; context = <" +
|
||||
_input.Substring(start, length) + ">";
|
||||
return fsResult.Fail(error);
|
||||
}
|
||||
|
||||
private bool TryMoveNext()
|
||||
{
|
||||
if (_start < _input.Length)
|
||||
{
|
||||
++_start;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool HasValue()
|
||||
{
|
||||
return HasValue(0);
|
||||
}
|
||||
|
||||
private bool HasValue(int offset)
|
||||
{
|
||||
return (_start + offset) >= 0 && (_start + offset) < _input.Length;
|
||||
}
|
||||
|
||||
private char Character()
|
||||
{
|
||||
return Character(0);
|
||||
}
|
||||
|
||||
private char Character(int offset)
|
||||
{
|
||||
return _input[_start + offset];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Skips input such that Character() will return a non-whitespace
|
||||
/// character
|
||||
/// </summary>
|
||||
private void SkipSpace()
|
||||
{
|
||||
while (HasValue())
|
||||
{
|
||||
var c = Character();
|
||||
|
||||
// whitespace; fine to skip
|
||||
if (char.IsWhiteSpace(c))
|
||||
{
|
||||
TryMoveNext();
|
||||
continue;
|
||||
}
|
||||
|
||||
// comment?
|
||||
if (HasValue(1) && Character(0) == '/')
|
||||
{
|
||||
if (Character(1) == '/')
|
||||
{
|
||||
// skip the rest of the line
|
||||
while (HasValue() && Environment.NewLine.Contains("" + Character()) == false)
|
||||
{
|
||||
TryMoveNext();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (Character(1) == '*')
|
||||
{
|
||||
// skip to comment close
|
||||
TryMoveNext();
|
||||
TryMoveNext();
|
||||
while (HasValue(1))
|
||||
{
|
||||
if (Character(0) == '*' && Character(1) == '/')
|
||||
{
|
||||
TryMoveNext();
|
||||
TryMoveNext();
|
||||
TryMoveNext();
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
TryMoveNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
// let other checks to check fail
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private fsResult TryParseExact(string content)
|
||||
{
|
||||
for (var i = 0; i < content.Length; ++i)
|
||||
{
|
||||
if (Character() != content[i])
|
||||
{
|
||||
return MakeFailure("Expected " + content[i]);
|
||||
}
|
||||
|
||||
if (TryMoveNext() == false)
|
||||
{
|
||||
return MakeFailure("Unexpected end of content when parsing " + content);
|
||||
}
|
||||
}
|
||||
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
private fsResult TryParseTrue(out fsData data)
|
||||
{
|
||||
var fail = TryParseExact("true");
|
||||
|
||||
if (fail.Succeeded)
|
||||
{
|
||||
data = new fsData(true);
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
data = null;
|
||||
return fail;
|
||||
}
|
||||
|
||||
private fsResult TryParseFalse(out fsData data)
|
||||
{
|
||||
var fail = TryParseExact("false");
|
||||
|
||||
if (fail.Succeeded)
|
||||
{
|
||||
data = new fsData(false);
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
data = null;
|
||||
return fail;
|
||||
}
|
||||
|
||||
private fsResult TryParseNull(out fsData data)
|
||||
{
|
||||
var fail = TryParseExact("null");
|
||||
|
||||
if (fail.Succeeded)
|
||||
{
|
||||
data = new fsData();
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
data = null;
|
||||
return fail;
|
||||
}
|
||||
|
||||
private bool IsSeparator(char c)
|
||||
{
|
||||
return char.IsWhiteSpace(c) || c == ',' || c == '}' || c == ']';
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses numbers that follow the regular expression [-+](\d+|\d*\.\d*)
|
||||
/// </summary>
|
||||
private fsResult TryParseNumber(out fsData data)
|
||||
{
|
||||
var start = _start;
|
||||
|
||||
// read until we get to a separator
|
||||
while (
|
||||
TryMoveNext() &&
|
||||
(HasValue() && IsSeparator(Character()) == false)) { }
|
||||
|
||||
// try to parse the value
|
||||
var numberString = _input.Substring(start, _start - start);
|
||||
|
||||
// double -- includes a .
|
||||
if (numberString.Contains(".") || numberString.Contains("e") || numberString.Contains("E") ||
|
||||
numberString == "Infinity" || numberString == "-Infinity" || numberString == "NaN")
|
||||
{
|
||||
double doubleValue;
|
||||
if (double.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out doubleValue) == false)
|
||||
{
|
||||
data = null;
|
||||
return MakeFailure("Bad double format with " + numberString);
|
||||
}
|
||||
|
||||
data = new fsData(doubleValue);
|
||||
return fsResult.Success;
|
||||
}
|
||||
else
|
||||
{
|
||||
Int64 intValue;
|
||||
if (Int64.TryParse(numberString, NumberStyles.Any, CultureInfo.InvariantCulture, out intValue) == false)
|
||||
{
|
||||
data = null;
|
||||
return MakeFailure("Bad Int64 format with " + numberString);
|
||||
}
|
||||
|
||||
data = new fsData(intValue);
|
||||
return fsResult.Success;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses a string
|
||||
/// </summary>
|
||||
private fsResult TryParseString(out string str)
|
||||
{
|
||||
_cachedStringBuilder.Length = 0;
|
||||
|
||||
// skip the first "
|
||||
if (Character() != '"' || TryMoveNext() == false)
|
||||
{
|
||||
str = string.Empty;
|
||||
return MakeFailure("Expected initial \" when parsing a string");
|
||||
}
|
||||
|
||||
// read until the next "
|
||||
while (HasValue() && Character() != '\"')
|
||||
{
|
||||
var c = Character();
|
||||
|
||||
// escape if necessary
|
||||
if (c == '\\')
|
||||
{
|
||||
char unescaped;
|
||||
var fail = TryUnescapeChar(out unescaped);
|
||||
if (fail.Failed)
|
||||
{
|
||||
str = string.Empty;
|
||||
return fail;
|
||||
}
|
||||
|
||||
_cachedStringBuilder.Append(unescaped);
|
||||
}
|
||||
// no escaping necessary
|
||||
else
|
||||
{
|
||||
_cachedStringBuilder.Append(c);
|
||||
|
||||
// get the next character
|
||||
if (TryMoveNext() == false)
|
||||
{
|
||||
str = string.Empty;
|
||||
return MakeFailure("Unexpected end of input when reading a string");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// skip the first "
|
||||
if (HasValue() == false || Character() != '"' || TryMoveNext() == false)
|
||||
{
|
||||
str = string.Empty;
|
||||
return MakeFailure("No closing \" when parsing a string");
|
||||
}
|
||||
|
||||
str = _cachedStringBuilder.ToString();
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses an array
|
||||
/// </summary>
|
||||
private fsResult TryParseArray(out fsData arr)
|
||||
{
|
||||
if (Character() != '[')
|
||||
{
|
||||
arr = null;
|
||||
return MakeFailure("Expected initial [ when parsing an array");
|
||||
}
|
||||
|
||||
// skip '['
|
||||
if (TryMoveNext() == false)
|
||||
{
|
||||
arr = null;
|
||||
return MakeFailure("Unexpected end of input when parsing an array");
|
||||
}
|
||||
SkipSpace();
|
||||
|
||||
var result = new List<fsData>();
|
||||
|
||||
while (HasValue() && Character() != ']')
|
||||
{
|
||||
// parse the element
|
||||
fsData element;
|
||||
var fail = RunParse(out element);
|
||||
if (fail.Failed)
|
||||
{
|
||||
arr = null;
|
||||
return fail;
|
||||
}
|
||||
|
||||
result.Add(element);
|
||||
|
||||
// parse the comma
|
||||
SkipSpace();
|
||||
if (HasValue() && Character() == ',')
|
||||
{
|
||||
if (TryMoveNext() == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
SkipSpace();
|
||||
}
|
||||
}
|
||||
|
||||
// skip the final ]
|
||||
if (HasValue() == false || Character() != ']' || TryMoveNext() == false)
|
||||
{
|
||||
arr = null;
|
||||
return MakeFailure("No closing ] for array");
|
||||
}
|
||||
|
||||
arr = new fsData(result);
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
private fsResult TryParseObject(out fsData obj)
|
||||
{
|
||||
if (Character() != '{')
|
||||
{
|
||||
obj = null;
|
||||
return MakeFailure("Expected initial { when parsing an object");
|
||||
}
|
||||
|
||||
// skip '{'
|
||||
if (TryMoveNext() == false)
|
||||
{
|
||||
obj = null;
|
||||
return MakeFailure("Unexpected end of input when parsing an object");
|
||||
}
|
||||
SkipSpace();
|
||||
|
||||
var result = new Dictionary<string, fsData>(
|
||||
fsGlobalConfig.IsCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
while (HasValue() && Character() != '}')
|
||||
{
|
||||
fsResult failure;
|
||||
|
||||
// parse the key
|
||||
SkipSpace();
|
||||
string key;
|
||||
failure = TryParseString(out key);
|
||||
if (failure.Failed)
|
||||
{
|
||||
obj = null;
|
||||
return failure;
|
||||
}
|
||||
SkipSpace();
|
||||
|
||||
// parse the ':' after the key
|
||||
if (HasValue() == false || Character() != ':' || TryMoveNext() == false)
|
||||
{
|
||||
obj = null;
|
||||
return MakeFailure("Expected : after key \"" + key + "\"");
|
||||
}
|
||||
SkipSpace();
|
||||
|
||||
// parse the value
|
||||
fsData value;
|
||||
failure = RunParse(out value);
|
||||
if (failure.Failed)
|
||||
{
|
||||
obj = null;
|
||||
return failure;
|
||||
}
|
||||
|
||||
result.Add(key, value);
|
||||
|
||||
// parse the comma
|
||||
SkipSpace();
|
||||
if (HasValue() && Character() == ',')
|
||||
{
|
||||
if (TryMoveNext() == false)
|
||||
{
|
||||
break;
|
||||
}
|
||||
SkipSpace();
|
||||
}
|
||||
}
|
||||
|
||||
// skip the final }
|
||||
if (HasValue() == false || Character() != '}' || TryMoveNext() == false)
|
||||
{
|
||||
obj = null;
|
||||
return MakeFailure("No closing } for object");
|
||||
}
|
||||
|
||||
obj = new fsData(result);
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
private fsResult RunParse(out fsData data)
|
||||
{
|
||||
SkipSpace();
|
||||
|
||||
if (HasValue() == false)
|
||||
{
|
||||
data = default(fsData);
|
||||
return MakeFailure("Unexpected end of input");
|
||||
}
|
||||
|
||||
switch (Character())
|
||||
{
|
||||
case 'I': // Infinity
|
||||
case 'N': // NaN
|
||||
case '.':
|
||||
case '+':
|
||||
case '-':
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return TryParseNumber(out data);
|
||||
case '"':
|
||||
{
|
||||
string str;
|
||||
var fail = TryParseString(out str);
|
||||
if (fail.Failed)
|
||||
{
|
||||
data = null;
|
||||
return fail;
|
||||
}
|
||||
data = new fsData(str);
|
||||
return fsResult.Success;
|
||||
}
|
||||
case '[':
|
||||
return TryParseArray(out data);
|
||||
case '{':
|
||||
return TryParseObject(out data);
|
||||
case 't':
|
||||
return TryParseTrue(out data);
|
||||
case 'f':
|
||||
return TryParseFalse(out data);
|
||||
case 'n':
|
||||
return TryParseNull(out data);
|
||||
default:
|
||||
data = null;
|
||||
return MakeFailure("unable to parse; invalid token \"" + Character() + "\"");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the specified input. Returns a failure state if parsing
|
||||
/// failed.
|
||||
/// </summary>
|
||||
/// <param name="input">The input to parse.</param>
|
||||
/// <param name="data">
|
||||
/// The parsed data. This is undefined if parsing fails.
|
||||
/// </param>
|
||||
/// <returns>The parsed input.</returns>
|
||||
public static fsResult Parse(string input, out fsData data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
{
|
||||
data = default(fsData);
|
||||
return fsResult.Fail("No input");
|
||||
}
|
||||
|
||||
var context = new fsJsonParser(input);
|
||||
return context.RunParse(out data);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper method for Parse that does not allow the error information to
|
||||
/// be recovered.
|
||||
/// </summary>
|
||||
public static fsData Parse(string input)
|
||||
{
|
||||
fsData data;
|
||||
Parse(input, out data).AssertSuccess();
|
||||
return data;
|
||||
}
|
||||
|
||||
#region Escaping
|
||||
|
||||
private bool IsHex(char c)
|
||||
{
|
||||
return ((c >= '0' && c <= '9') ||
|
||||
(c >= 'a' && c <= 'f') ||
|
||||
(c >= 'A' && c <= 'F'));
|
||||
}
|
||||
|
||||
private uint ParseSingleChar(char c1, uint multipliyer)
|
||||
{
|
||||
uint p1 = 0;
|
||||
if (c1 >= '0' && c1 <= '9')
|
||||
{
|
||||
p1 = (uint)(c1 - '0') * multipliyer;
|
||||
}
|
||||
else if (c1 >= 'A' && c1 <= 'F')
|
||||
{
|
||||
p1 = (uint)((c1 - 'A') + 10) * multipliyer;
|
||||
}
|
||||
else if (c1 >= 'a' && c1 <= 'f')
|
||||
{
|
||||
p1 = (uint)((c1 - 'a') + 10) * multipliyer;
|
||||
}
|
||||
return p1;
|
||||
}
|
||||
|
||||
private uint ParseUnicode(char c1, char c2, char c3, char c4)
|
||||
{
|
||||
var p1 = ParseSingleChar(c1, 0x1000);
|
||||
var p2 = ParseSingleChar(c2, 0x100);
|
||||
var p3 = ParseSingleChar(c3, 0x10);
|
||||
var p4 = ParseSingleChar(c4, 0x1);
|
||||
|
||||
return p1 + p2 + p3 + p4;
|
||||
}
|
||||
|
||||
private fsResult TryUnescapeChar(out char escaped)
|
||||
{
|
||||
// skip leading backslash '\'
|
||||
TryMoveNext();
|
||||
if (HasValue() == false)
|
||||
{
|
||||
escaped = ' ';
|
||||
return MakeFailure("Unexpected end of input after \\");
|
||||
}
|
||||
|
||||
switch (Character())
|
||||
{
|
||||
case '\\':
|
||||
TryMoveNext();
|
||||
escaped = '\\';
|
||||
return fsResult.Success;
|
||||
case '/':
|
||||
TryMoveNext();
|
||||
escaped = '/';
|
||||
return fsResult.Success;
|
||||
case '"':
|
||||
TryMoveNext();
|
||||
escaped = '\"';
|
||||
return fsResult.Success;
|
||||
case 'a':
|
||||
TryMoveNext();
|
||||
escaped = '\a';
|
||||
return fsResult.Success;
|
||||
case 'b':
|
||||
TryMoveNext();
|
||||
escaped = '\b';
|
||||
return fsResult.Success;
|
||||
case 'f':
|
||||
TryMoveNext();
|
||||
escaped = '\f';
|
||||
return fsResult.Success;
|
||||
case 'n':
|
||||
TryMoveNext();
|
||||
escaped = '\n';
|
||||
return fsResult.Success;
|
||||
case 'r':
|
||||
TryMoveNext();
|
||||
escaped = '\r';
|
||||
return fsResult.Success;
|
||||
case 't':
|
||||
TryMoveNext();
|
||||
escaped = '\t';
|
||||
return fsResult.Success;
|
||||
case '0':
|
||||
TryMoveNext();
|
||||
escaped = '\0';
|
||||
return fsResult.Success;
|
||||
case 'u':
|
||||
TryMoveNext();
|
||||
if (IsHex(Character(0))
|
||||
&& IsHex(Character(1))
|
||||
&& IsHex(Character(2))
|
||||
&& IsHex(Character(3)))
|
||||
{
|
||||
var codePoint = ParseUnicode(Character(0), Character(1), Character(2), Character(3));
|
||||
|
||||
TryMoveNext();
|
||||
TryMoveNext();
|
||||
TryMoveNext();
|
||||
TryMoveNext();
|
||||
|
||||
escaped = (char)codePoint;
|
||||
return fsResult.Success;
|
||||
}
|
||||
|
||||
// invalid escape sequence
|
||||
escaped = (char)0;
|
||||
return MakeFailure(
|
||||
$"invalid escape sequence '\\u{Character(0)}{Character(1)}{Character(2)}{Character(3)}'\n");
|
||||
default:
|
||||
escaped = (char)0;
|
||||
return MakeFailure($"Invalid escape sequence \\{Character()}");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion Escaping
|
||||
}
|
||||
}
|
@@ -0,0 +1,359 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
public static class fsJsonPrinter
|
||||
{
|
||||
/// <summary>
|
||||
/// Inserts the given number of indents into the builder.
|
||||
/// </summary>
|
||||
private static void InsertSpacing(TextWriter stream, int count)
|
||||
{
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
stream.Write(" ");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Escapes a string.
|
||||
/// </summary>
|
||||
private static string EscapeString(string str)
|
||||
{
|
||||
// Escaping a string is pretty allocation heavy, so we try hard to
|
||||
// not do it.
|
||||
|
||||
var needsEscape = false;
|
||||
for (var i = 0; i < str.Length; ++i)
|
||||
{
|
||||
var c = str[i];
|
||||
|
||||
// unicode code point
|
||||
var intChar = Convert.ToInt32(c);
|
||||
if (intChar < 0 || intChar > 127)
|
||||
{
|
||||
needsEscape = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// standard escape character
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
case '\\':
|
||||
case '\a':
|
||||
case '\b':
|
||||
case '\f':
|
||||
case '\n':
|
||||
case '\r':
|
||||
case '\t':
|
||||
case '\0':
|
||||
needsEscape = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (needsEscape)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsEscape == false)
|
||||
{
|
||||
return str;
|
||||
}
|
||||
|
||||
var result = new StringBuilder();
|
||||
|
||||
for (var i = 0; i < str.Length; ++i)
|
||||
{
|
||||
var c = str[i];
|
||||
|
||||
// unicode code point
|
||||
var intChar = Convert.ToInt32(c);
|
||||
if (intChar < 0 || intChar > 127)
|
||||
{
|
||||
result.Append($"\\u{intChar:x4} ".Trim());
|
||||
continue;
|
||||
}
|
||||
|
||||
// standard escape character
|
||||
switch (c)
|
||||
{
|
||||
case '"':
|
||||
result.Append("\\\"");
|
||||
continue;
|
||||
case '\\':
|
||||
result.Append(@"\\");
|
||||
continue;
|
||||
case '\a':
|
||||
result.Append(@"\a");
|
||||
continue;
|
||||
case '\b':
|
||||
result.Append(@"\b");
|
||||
continue;
|
||||
case '\f':
|
||||
result.Append(@"\f");
|
||||
continue;
|
||||
case '\n':
|
||||
result.Append(@"\n");
|
||||
continue;
|
||||
case '\r':
|
||||
result.Append(@"\r");
|
||||
continue;
|
||||
case '\t':
|
||||
result.Append(@"\t");
|
||||
continue;
|
||||
case '\0':
|
||||
result.Append(@"\0");
|
||||
continue;
|
||||
}
|
||||
|
||||
// no escaping needed
|
||||
result.Append(c);
|
||||
}
|
||||
return result.ToString();
|
||||
}
|
||||
|
||||
private static void BuildCompressedString(fsData data, TextWriter stream)
|
||||
{
|
||||
switch (data.Type)
|
||||
{
|
||||
case fsDataType.Null:
|
||||
stream.Write("null");
|
||||
break;
|
||||
|
||||
case fsDataType.Boolean:
|
||||
if (data.AsBool)
|
||||
{
|
||||
stream.Write("true");
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Write("false");
|
||||
}
|
||||
break;
|
||||
|
||||
case fsDataType.Double:
|
||||
// doubles must *always* include a decimal
|
||||
stream.Write(ConvertDoubleToString(data.AsDouble));
|
||||
break;
|
||||
|
||||
case fsDataType.Int64:
|
||||
stream.Write(data.AsInt64);
|
||||
break;
|
||||
|
||||
case fsDataType.String:
|
||||
stream.Write('"');
|
||||
stream.Write(EscapeString(data.AsString));
|
||||
stream.Write('"');
|
||||
break;
|
||||
|
||||
case fsDataType.Object:
|
||||
{
|
||||
stream.Write('{');
|
||||
var comma = false;
|
||||
foreach (var entry in data.AsDictionary)
|
||||
{
|
||||
if (comma)
|
||||
{
|
||||
stream.Write(',');
|
||||
}
|
||||
comma = true;
|
||||
stream.Write('"');
|
||||
stream.Write(entry.Key);
|
||||
stream.Write('"');
|
||||
stream.Write(":");
|
||||
BuildCompressedString(entry.Value, stream);
|
||||
}
|
||||
stream.Write('}');
|
||||
break;
|
||||
}
|
||||
|
||||
case fsDataType.Array:
|
||||
{
|
||||
stream.Write('[');
|
||||
var comma = false;
|
||||
foreach (var entry in data.AsList)
|
||||
{
|
||||
if (comma)
|
||||
{
|
||||
stream.Write(',');
|
||||
}
|
||||
comma = true;
|
||||
BuildCompressedString(entry, stream);
|
||||
}
|
||||
stream.Write(']');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats this data into the given builder.
|
||||
/// </summary>
|
||||
private static void BuildPrettyString(fsData data, TextWriter stream, int depth)
|
||||
{
|
||||
switch (data.Type)
|
||||
{
|
||||
case fsDataType.Null:
|
||||
stream.Write("null");
|
||||
break;
|
||||
|
||||
case fsDataType.Boolean:
|
||||
if (data.AsBool)
|
||||
{
|
||||
stream.Write("true");
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.Write("false");
|
||||
}
|
||||
break;
|
||||
|
||||
case fsDataType.Double:
|
||||
stream.Write(ConvertDoubleToString(data.AsDouble));
|
||||
break;
|
||||
|
||||
case fsDataType.Int64:
|
||||
stream.Write(data.AsInt64);
|
||||
break;
|
||||
|
||||
case fsDataType.String:
|
||||
stream.Write('"');
|
||||
stream.Write(EscapeString(data.AsString));
|
||||
stream.Write('"');
|
||||
break;
|
||||
|
||||
case fsDataType.Object:
|
||||
{
|
||||
stream.Write('{');
|
||||
stream.WriteLine();
|
||||
var comma = false;
|
||||
foreach (var entry in data.AsDictionary)
|
||||
{
|
||||
if (comma)
|
||||
{
|
||||
stream.Write(',');
|
||||
stream.WriteLine();
|
||||
}
|
||||
comma = true;
|
||||
InsertSpacing(stream, depth + 1);
|
||||
stream.Write('"');
|
||||
stream.Write(entry.Key);
|
||||
stream.Write('"');
|
||||
stream.Write(": ");
|
||||
BuildPrettyString(entry.Value, stream, depth + 1);
|
||||
}
|
||||
stream.WriteLine();
|
||||
InsertSpacing(stream, depth);
|
||||
stream.Write('}');
|
||||
break;
|
||||
}
|
||||
|
||||
case fsDataType.Array:
|
||||
// special case for empty lists; we don't put an empty line
|
||||
// between the brackets
|
||||
if (data.AsList.Count == 0)
|
||||
{
|
||||
stream.Write("[]");
|
||||
}
|
||||
else
|
||||
{
|
||||
var comma = false;
|
||||
|
||||
stream.Write('[');
|
||||
stream.WriteLine();
|
||||
foreach (var entry in data.AsList)
|
||||
{
|
||||
if (comma)
|
||||
{
|
||||
stream.Write(',');
|
||||
stream.WriteLine();
|
||||
}
|
||||
comma = true;
|
||||
InsertSpacing(stream, depth + 1);
|
||||
BuildPrettyString(entry, stream, depth + 1);
|
||||
}
|
||||
stream.WriteLine();
|
||||
InsertSpacing(stream, depth);
|
||||
stream.Write(']');
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the pretty JSON output data to the given stream.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to print.</param>
|
||||
/// <param name="outputStream">Where to write the printed data.</param>
|
||||
public static void PrettyJson(fsData data, TextWriter outputStream)
|
||||
{
|
||||
BuildPrettyString(data, outputStream, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the data in a pretty printed JSON format.
|
||||
/// </summary>
|
||||
public static string PrettyJson(fsData data)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
using (var writer = new StringWriter(sb))
|
||||
{
|
||||
BuildPrettyString(data, writer, 0);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the compressed JSON output data to the given stream.
|
||||
/// </summary>
|
||||
/// <param name="data">The data to print.</param>
|
||||
/// <param name="outputStream">Where to write the printed data.</param>
|
||||
public static void CompressedJson(fsData data, StreamWriter outputStream)
|
||||
{
|
||||
BuildCompressedString(data, outputStream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the data in a relatively compressed JSON format.
|
||||
/// </summary>
|
||||
public static string CompressedJson(fsData data)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
using (var writer = new StringWriter(sb))
|
||||
{
|
||||
BuildCompressedString(data, writer);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility method that converts a double to a string.
|
||||
/// </summary>
|
||||
private static string ConvertDoubleToString(double d)
|
||||
{
|
||||
if (Double.IsInfinity(d) || Double.IsNaN(d))
|
||||
{
|
||||
return d.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
var doubledString = d.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
// NOTE/HACK: If we don't serialize with a period or an exponent,
|
||||
// then the number will be deserialized as an Int64, not a double.
|
||||
if (doubledString.Contains(".") == false &&
|
||||
doubledString.Contains("e") == false &&
|
||||
doubledString.Contains("E") == false)
|
||||
{
|
||||
doubledString += ".0";
|
||||
}
|
||||
|
||||
return doubledString;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Controls how the reflected converter handles member serialization.
|
||||
/// </summary>
|
||||
public enum fsMemberSerialization
|
||||
{
|
||||
/// <summary>
|
||||
/// Only members with [SerializeField] or [fsProperty] attributes are
|
||||
/// serialized.
|
||||
/// </summary>
|
||||
OptIn,
|
||||
|
||||
/// <summary>
|
||||
/// Only members with [NotSerialized] or [fsIgnore] will not be
|
||||
/// serialized.
|
||||
/// </summary>
|
||||
OptOut,
|
||||
|
||||
/// <summary>
|
||||
/// The default member serialization behavior is applied.
|
||||
/// </summary>
|
||||
Default
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute controls some serialization behavior for a type. See the
|
||||
/// comments on each of the fields for more information.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
|
||||
public class fsObjectAttribute : Attribute
|
||||
{
|
||||
public fsObjectAttribute() { }
|
||||
|
||||
public fsObjectAttribute(string versionString, params Type[] previousModels)
|
||||
{
|
||||
VersionString = versionString;
|
||||
PreviousModels = previousModels;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The previous model that should be used if an old version of this
|
||||
/// object is encountered. Using this attribute also requires that the
|
||||
/// type have a public constructor that takes only one parameter, an
|
||||
/// object instance of the given type. Use of this parameter *requires*
|
||||
/// that the VersionString parameter is also set.
|
||||
/// </summary>
|
||||
public Type[] PreviousModels;
|
||||
|
||||
/// <summary>
|
||||
/// The version string to use for this model. This should be unique among
|
||||
/// all prior versions of this model that is supported for importation.
|
||||
/// If PreviousModel is set, then this attribute must also be set. A good
|
||||
/// valid example for this is "v1", "v2", "v3", ...
|
||||
/// </summary>
|
||||
public string VersionString;
|
||||
|
||||
/// <summary>
|
||||
/// This controls the behavior for member serialization. The default
|
||||
/// behavior is fsMemberSerialization.Default.
|
||||
/// </summary>
|
||||
public fsMemberSerialization MemberSerialization = fsMemberSerialization.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Specify a custom converter to use for serialization. The converter
|
||||
/// type needs to derive from fsBaseConverter. This defaults to null.
|
||||
/// </summary>
|
||||
public Type Converter;
|
||||
|
||||
/// <summary>
|
||||
/// Specify a custom processor to use during serialization. The processor
|
||||
/// type needs to derive from fsObjectProcessor and the call to
|
||||
/// CanProcess is not invoked. This defaults to null.
|
||||
/// </summary>
|
||||
public Type Processor;
|
||||
}
|
||||
}
|
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// <para>
|
||||
/// Enables injecting code before/after an object has been serialized. This
|
||||
/// is most useful if you want to run the default serialization process but
|
||||
/// apply a pre/post processing step.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Multiple object processors can be active at the same time. When running
|
||||
/// they are called in a "nested" fashion - if we have processor1 and
|
||||
/// process2 added to the serializer in that order (p1 then p2), then the
|
||||
/// execution order will be p1#Before p2#Before /serialization/ p2#After
|
||||
/// p1#After.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public abstract class fsObjectProcessor
|
||||
{
|
||||
/// <summary>
|
||||
/// Is the processor interested in objects of the given type?
|
||||
/// </summary>
|
||||
/// <param name="type">The given type.</param>
|
||||
/// <returns>
|
||||
/// True if the processor should be applied, false otherwise.
|
||||
/// </returns>
|
||||
public virtual bool CanProcess(Type type)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called before serialization.
|
||||
/// </summary>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <param name="instance">The type of the instance.</param>
|
||||
public virtual void OnBeforeSerialize(Type storageType, object instance) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called after serialization.
|
||||
/// </summary>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <param name="instance">The type of the instance.</param>
|
||||
/// <param name="data">The data that was serialized.</param>
|
||||
public virtual void OnAfterSerialize(Type storageType, object instance, ref fsData data) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called before deserialization.
|
||||
/// </summary>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <param name="data">
|
||||
/// The data that will be used for deserialization.
|
||||
/// </param>
|
||||
public virtual void OnBeforeDeserialize(Type storageType, ref fsData data) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called before deserialization has begun but *after* the object
|
||||
/// instance has been created. This will get invoked even if the user
|
||||
/// passed in an existing instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// **IMPORTANT**: The actual instance that gets passed here is *not*
|
||||
/// guaranteed to be an a subtype of storageType, since the value for
|
||||
/// instance is whatever the active converter returned for
|
||||
/// CreateInstance() - ie, some converters will return dummy types in
|
||||
/// CreateInstance() if instance creation cannot be separated from
|
||||
/// deserialization (ie, KeyValuePair).
|
||||
/// </remarks>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <param name="instance">
|
||||
/// The created object instance. No deserialization has been applied to
|
||||
/// it.
|
||||
/// </param>
|
||||
/// <param name="data">
|
||||
/// The data that will be used for deserialization.
|
||||
/// </param>
|
||||
public virtual void OnBeforeDeserializeAfterInstanceCreation(Type storageType, object instance, ref fsData data) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called after deserialization.
|
||||
/// </summary>
|
||||
/// <param name="storageType">
|
||||
/// The field/property type that is storing the instance.
|
||||
/// </param>
|
||||
/// <param name="instance">The type of the instance.</param>
|
||||
public virtual void OnAfterDeserialize(Type storageType, object instance) { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Explicitly mark a property to be serialized. This can also be used to
|
||||
/// give the name that the property should use during serialization.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
public class fsPropertyAttribute : Attribute
|
||||
{
|
||||
public fsPropertyAttribute()
|
||||
: this(string.Empty) { }
|
||||
|
||||
public fsPropertyAttribute(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The name of that the property will use in JSON serialization.
|
||||
/// </summary>
|
||||
public string Name;
|
||||
|
||||
/// <summary>
|
||||
/// Use a custom converter for the given type. Specify the converter to
|
||||
/// use using typeof.
|
||||
/// </summary>
|
||||
public Type Converter;
|
||||
}
|
||||
}
|
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Unity.VisualScripting.FullSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// The result of some sort of operation. A result is either successful or
|
||||
/// not, but if it is successful then there may be a set of warnings/messages
|
||||
/// associated with it. These warnings describe the performed error recovery
|
||||
/// operations.
|
||||
/// </summary>
|
||||
public struct fsResult
|
||||
{
|
||||
// We cache the empty string array so we can unify some collections
|
||||
// processing code.
|
||||
private static readonly string[] EmptyStringArray = { };
|
||||
|
||||
/// <summary>
|
||||
/// Is this result successful?
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is intentionally a `success` state so that when the object is
|
||||
/// default constructed it defaults to a failure state.
|
||||
/// </remarks>
|
||||
private bool _success;
|
||||
|
||||
/// <summary>
|
||||
/// The warning or error messages associated with the result. This may be
|
||||
/// null if there are no messages.
|
||||
/// </summary>
|
||||
private List<string> _messages;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new message to this result.
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
public void AddMessage(string message)
|
||||
{
|
||||
if (_messages == null)
|
||||
{
|
||||
_messages = new List<string>();
|
||||
}
|
||||
|
||||
_messages.Add(message);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds only the messages from the other result into this result,
|
||||
/// ignoring the success/failure status of the other result.
|
||||
/// </summary>
|
||||
public void AddMessages(fsResult result)
|
||||
{
|
||||
if (result._messages == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_messages == null)
|
||||
{
|
||||
_messages = new List<string>();
|
||||
}
|
||||
|
||||
_messages.AddRange(result._messages);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges the other result into this one. If the other result failed,
|
||||
/// then this one too will have failed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that you can use += instead of this method so that you don't
|
||||
/// bury the actual method call that is generating the other fsResult.
|
||||
/// </remarks>
|
||||
public fsResult Merge(fsResult other)
|
||||
{
|
||||
// Copy success over
|
||||
_success = _success && other._success;
|
||||
|
||||
// Copy messages over
|
||||
if (other._messages != null)
|
||||
{
|
||||
if (_messages == null)
|
||||
{
|
||||
_messages = new List<string>(other._messages);
|
||||
}
|
||||
else
|
||||
{
|
||||
_messages.AddRange(other._messages);
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A successful result.
|
||||
/// </summary>
|
||||
public static fsResult Success = new fsResult
|
||||
{
|
||||
_success = true
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create a result that is successful but contains the given warning
|
||||
/// message.
|
||||
/// </summary>
|
||||
public static fsResult Warn(string warning)
|
||||
{
|
||||
return new fsResult
|
||||
{
|
||||
_success = true,
|
||||
_messages = new List<string> { warning }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a result that failed.
|
||||
/// </summary>
|
||||
public static fsResult Fail(string warning)
|
||||
{
|
||||
return new fsResult
|
||||
{
|
||||
_success = false,
|
||||
_messages = new List<string> { warning }
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: how to make sure this is only used as +=?
|
||||
|
||||
/// <summary>
|
||||
/// Only use this as +=!
|
||||
/// </summary>
|
||||
public static fsResult operator +(fsResult a, fsResult b)
|
||||
{
|
||||
return a.Merge(b);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Did this result fail? If so, you can see the reasons why in
|
||||
/// `RawMessages`.
|
||||
/// </summary>
|
||||
public bool Failed => _success == false;
|
||||
|
||||
/// <summary>
|
||||
/// Was the result a success? Note that even successful operations may
|
||||
/// have warning messages (`RawMessages`) associated with them.
|
||||
/// </summary>
|
||||
public bool Succeeded => _success;
|
||||
|
||||
/// <summary>
|
||||
/// Does this result have any warnings? This says nothing about if it
|
||||
/// failed or succeeded, just if it has warning messages associated with
|
||||
/// it.
|
||||
/// </summary>
|
||||
public bool HasWarnings => _messages != null && _messages.Any();
|
||||
|
||||
/// <summary>
|
||||
/// A simply utility method that will assert that this result is
|
||||
/// successful. If it is not, then an exception is thrown.
|
||||
/// </summary>
|
||||
public fsResult AssertSuccess()
|
||||
{
|
||||
if (Failed)
|
||||
{
|
||||
throw AsException;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A simple utility method that will assert that this result is
|
||||
/// successful and that there are no warning messages. This throws an
|
||||
/// exception if either of those asserts are false.
|
||||
/// </summary>
|
||||
public fsResult AssertSuccessWithoutWarnings()
|
||||
{
|
||||
if (Failed || RawMessages.Any())
|
||||
{
|
||||
throw AsException;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Utility method to convert the result to an exception. This method is
|
||||
/// only defined is `Failed` returns true.
|
||||
/// </summary>
|
||||
public Exception AsException
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Failed && !RawMessages.Any())
|
||||
{
|
||||
throw new Exception("Only a failed result can be converted to an exception");
|
||||
}
|
||||
return new Exception(FormattedMessages);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> RawMessages
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_messages != null)
|
||||
{
|
||||
return _messages;
|
||||
}
|
||||
return EmptyStringArray;
|
||||
}
|
||||
}
|
||||
|
||||
public string FormattedMessages => string.Join(",\n", RawMessages.ToArray());
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
|
||||
public sealed class AllowsNullAttribute : Attribute
|
||||
{
|
||||
public AllowsNullAttribute() { }
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class DisableAnnotationAttribute : Attribute
|
||||
{
|
||||
public bool disableIcon { get; set; } = true;
|
||||
public bool disableGizmo { get; set; } = false;
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace Unity.VisualScripting
|
||||
{
|
||||
public static class EditorBindingUtility
|
||||
{
|
||||
public const string FactoryMethodWarning = "This parameterless factory method is only made public for the editor. Use the constructor instead.";
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user