This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,7 @@
namespace Unity.VisualScripting
{
public interface ISpecifiesCloner
{
ICloner cloner { get; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,13 @@
namespace Unity.VisualScripting
{
public interface INotifiedCollectionItem
{
void BeforeAdd();
void AfterAdd();
void BeforeRemove();
void AfterRemove();
}
}

View File

@@ -0,0 +1,13 @@
using System;
namespace Unity.VisualScripting
{
public interface INotifyCollectionChanged<T>
{
event Action<T> ItemAdded;
event Action<T> ItemRemoved;
event Action CollectionChanged;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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>()) { }
}
}

View File

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

View File

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

View File

@@ -0,0 +1,8 @@
namespace Unity.VisualScripting
{
public interface IConnection<out TSource, out TDestination>
{
TSource source { get; }
TDestination destination { get; }
}
}

View File

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

View File

@@ -0,0 +1,10 @@
using System;
namespace Unity.VisualScripting
{
public class InvalidConnectionException : Exception
{
public InvalidConnectionException() : base("") { }
public InvalidConnectionException(string message) : base(message) { }
}
}

View File

@@ -0,0 +1,9 @@
using System;
namespace Unity.VisualScripting
{
public interface IDecoratorAttribute
{
Type type { get; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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) =&gt;
/// 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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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.")
{ }
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
using System;
namespace Unity.VisualScripting
{
[AttributeUsage(AttributeTargets.All, AllowMultiple = false, Inherited = true)]
public sealed class AllowsNullAttribute : Attribute
{
public AllowsNullAttribute() { }
}
}

View File

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

View File

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