From 5f7429839f650b564c2dca2f7707920a98ebc510 Mon Sep 17 00:00:00 2001 From: Nicolas Musset Date: Fri, 21 Feb 2025 14:10:52 +0100 Subject: [PATCH 1/6] [Core.Presentation] Modernize code --- .../AutoUpdatingSortedObservableCollection.cs | 8 +- .../Collections/IObservableCollection.cs | 4 +- .../IReadOnlyObservableCollection.cs | 4 +- .../Collections/IReadOnlyObservableList.cs | 4 +- .../Commands/AnonymousCommand.cs | 8 +- .../Commands/DisabledCommand.cs | 2 +- .../Core/NaturalStringComparer.cs | 9 +- .../Stride.Core.Presentation/Core/Utils.cs | 13 +- .../Dirtiables/CollectionChangeOperation.cs | 205 +++++++++--------- .../Dirtiables/PropertyChangeOperation.cs | 2 +- .../Extensions/ObjectExtensions.cs | 7 +- .../Extensions/TypeExtensions.cs | 6 +- .../Services/DummyTransaction.cs | 2 +- .../Services/IDialogService.cs | 8 +- .../ViewModels/DirtiableEditableViewModel.cs | 2 +- .../ViewModels/EditableViewModel.cs | 38 ++-- .../ViewModels/ViewModelBase.cs | 26 +-- 17 files changed, 170 insertions(+), 178 deletions(-) diff --git a/sources/presentation/Stride.Core.Presentation/Collections/AutoUpdatingSortedObservableCollection.cs b/sources/presentation/Stride.Core.Presentation/Collections/AutoUpdatingSortedObservableCollection.cs index c8256723ae..52b3362b40 100644 --- a/sources/presentation/Stride.Core.Presentation/Collections/AutoUpdatingSortedObservableCollection.cs +++ b/sources/presentation/Stride.Core.Presentation/Collections/AutoUpdatingSortedObservableCollection.cs @@ -106,10 +106,10 @@ public override string ToString() protected virtual void ItemPropertyChanging(object? sender, PropertyChangingEventArgs e) { - if (propertyNames is not null && !propertyNames.Contains(e.PropertyName)) + if (e.PropertyName is null || propertyNames?.Contains(e.PropertyName) == false) return; - var item = (T)sender; + var item = (T?)sender; if (ChangingItem is not null && !ReferenceEquals(ChangingItem, item)) throw new InvalidOperationException("Multiple items in the collection are changing concurrently."); @@ -122,10 +122,10 @@ protected virtual void ItemPropertyChanging(object? sender, PropertyChangingEven protected virtual void ItemPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (propertyNames is not null && !propertyNames.Contains(e.PropertyName)) + if (e.PropertyName is null || propertyNames?.Contains(e.PropertyName) == false) return; - var item = (T)sender; + var item = (T?)sender; // An object has been added while a property of an existing object has been modified if (ChangingItem is not null && AddedItem is not null) diff --git a/sources/presentation/Stride.Core.Presentation/Collections/IObservableCollection.cs b/sources/presentation/Stride.Core.Presentation/Collections/IObservableCollection.cs index 51a6165c6b..3e16abc5e9 100644 --- a/sources/presentation/Stride.Core.Presentation/Collections/IObservableCollection.cs +++ b/sources/presentation/Stride.Core.Presentation/Collections/IObservableCollection.cs @@ -12,6 +12,4 @@ namespace Stride.Core.Presentation.Collections; /// interface. It has no additional members. /// /// The type of item contained in the collection. -public interface IObservableCollection : ICollection, INotifyCollectionChanged, INotifyPropertyChanged -{ -} +public interface IObservableCollection : ICollection, INotifyCollectionChanged, INotifyPropertyChanged; diff --git a/sources/presentation/Stride.Core.Presentation/Collections/IReadOnlyObservableCollection.cs b/sources/presentation/Stride.Core.Presentation/Collections/IReadOnlyObservableCollection.cs index 0a87bb60ff..dc9c33ebc2 100644 --- a/sources/presentation/Stride.Core.Presentation/Collections/IReadOnlyObservableCollection.cs +++ b/sources/presentation/Stride.Core.Presentation/Collections/IReadOnlyObservableCollection.cs @@ -12,6 +12,4 @@ namespace Stride.Core.Presentation.Collections; /// interface. It has no additional members. /// /// The type of item contained in the collection. -public interface IReadOnlyObservableCollection : IReadOnlyCollection, INotifyPropertyChanged, INotifyCollectionChanged -{ -} +public interface IReadOnlyObservableCollection : IReadOnlyCollection, INotifyPropertyChanged, INotifyCollectionChanged; diff --git a/sources/presentation/Stride.Core.Presentation/Collections/IReadOnlyObservableList.cs b/sources/presentation/Stride.Core.Presentation/Collections/IReadOnlyObservableList.cs index 9c83c7c798..8c06a55a9a 100644 --- a/sources/presentation/Stride.Core.Presentation/Collections/IReadOnlyObservableList.cs +++ b/sources/presentation/Stride.Core.Presentation/Collections/IReadOnlyObservableList.cs @@ -9,6 +9,4 @@ namespace Stride.Core.Presentation.Collections; /// interface. It has no additional members. /// /// The type of item contained in the collection. -public interface IReadOnlyObservableList : IReadOnlyList, IReadOnlyObservableCollection -{ -} +public interface IReadOnlyObservableList : IReadOnlyList, IReadOnlyObservableCollection; diff --git a/sources/presentation/Stride.Core.Presentation/Commands/AnonymousCommand.cs b/sources/presentation/Stride.Core.Presentation/Commands/AnonymousCommand.cs index 4d2121f1af..cc6f4be399 100644 --- a/sources/presentation/Stride.Core.Presentation/Commands/AnonymousCommand.cs +++ b/sources/presentation/Stride.Core.Presentation/Commands/AnonymousCommand.cs @@ -26,7 +26,7 @@ public AnonymousCommand(IViewModelServiceProvider serviceProvider, Action action { ArgumentNullException.ThrowIfNull(action); - this.action = x => action(); + this.action = _ => action(); this.canExecute = canExecute; } @@ -81,7 +81,7 @@ public class AnonymousTaskCommand : AnonymousCommand /// A method returning a task that will be called each time the command is executed. /// An anonymous method that will be called each time the command method is invoked. public AnonymousTaskCommand(IViewModelServiceProvider serviceProvider, Func task, Func? canExecute = null) - : base(serviceProvider, async x => await task(), canExecute) + : base(serviceProvider, async _ => await task(), canExecute) { ArgumentNullException.ThrowIfNull(task); } @@ -122,7 +122,7 @@ public override void Execute(object? parameter) parameter ??= default(T); // check the type if ((typeof(T).IsValueType || parameter != null) && parameter is not T) - throw new ArgumentException(@"Unexpected parameter type in the command.", nameof(parameter)); + throw new ArgumentException("Unexpected parameter type in the command.", nameof(parameter)); action((T)parameter!); } @@ -137,7 +137,7 @@ public override bool CanExecute(object? parameter) { parameter ??= default(T); if ((typeof(T).IsValueType || parameter != null) && parameter is not T) - throw new ArgumentException(@"Unexpected parameter type in the command.", nameof(parameter)); + throw new ArgumentException("Unexpected parameter type in the command.", nameof(parameter)); var result = base.CanExecute(parameter); return result && canExecute != null ? canExecute((T)parameter!) : result; diff --git a/sources/presentation/Stride.Core.Presentation/Commands/DisabledCommand.cs b/sources/presentation/Stride.Core.Presentation/Commands/DisabledCommand.cs index db1fa88caa..ffadbec622 100644 --- a/sources/presentation/Stride.Core.Presentation/Commands/DisabledCommand.cs +++ b/sources/presentation/Stride.Core.Presentation/Commands/DisabledCommand.cs @@ -25,7 +25,7 @@ private DisabledCommand() public bool IsEnabled { get { return false; } - set { if (value == false) throw new InvalidOperationException($"The {nameof(IsEnabled)} property of the {nameof(DisabledCommand)} cannot be modified"); } + set { if (!value) throw new InvalidOperationException($"The {nameof(IsEnabled)} property of the {nameof(DisabledCommand)} cannot be modified"); } } /// diff --git a/sources/presentation/Stride.Core.Presentation/Core/NaturalStringComparer.cs b/sources/presentation/Stride.Core.Presentation/Core/NaturalStringComparer.cs index bd013fd169..b68f787bea 100644 --- a/sources/presentation/Stride.Core.Presentation/Core/NaturalStringComparer.cs +++ b/sources/presentation/Stride.Core.Presentation/Core/NaturalStringComparer.cs @@ -5,9 +5,9 @@ namespace Stride.Core.Presentation.Core; -public class NaturalStringComparer : IComparer +public partial class NaturalStringComparer : IComparer { - private static readonly Regex NumericRegex = new("([0-9]+)", RegexOptions.Compiled); + private static readonly Regex NumericRegex = GetNumericRegex(); private readonly StringComparison comparison; public NaturalStringComparer(StringComparison comparison = StringComparison.InvariantCultureIgnoreCase) @@ -23,7 +23,7 @@ public int Compare(string? x, string? y) return -1; if (y is null) return 1; - + // Split strings by numbers var splitX = NumericRegex.Split(x.Replace(" ", string.Empty)); var splitY = NumericRegex.Split(y.Replace(" ", string.Empty)); @@ -60,4 +60,7 @@ public int Compare(string? x, string? y) return comparer; } + + [GeneratedRegex("([0-9]+)")] + private static partial Regex GetNumericRegex(); } diff --git a/sources/presentation/Stride.Core.Presentation/Core/Utils.cs b/sources/presentation/Stride.Core.Presentation/Core/Utils.cs index c153207de5..32915d6e46 100644 --- a/sources/presentation/Stride.Core.Presentation/Core/Utils.cs +++ b/sources/presentation/Stride.Core.Presentation/Core/Utils.cs @@ -10,8 +10,8 @@ namespace Stride.Core.Presentation.Core; /// /// A static class containing various useful methods and constants. /// -public static class Utils -{ +public static partial class Utils +{ /// /// An array of values that can be used for zooming. /// @@ -24,15 +24,18 @@ public static class Utils public static string SplitCamelCase(string input) { - return Regex.Replace(input, "([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))", "$1 "); + return GetCamelCaseRegex().Replace(input, "$1 "); } public static Color4 GetUniqueColor(this Type type) { var displayAttribute = TypeDescriptorFactory.Default.AttributeRegistry.GetAttribute(type); - var hash = displayAttribute?.Name.GetHashCode() ?? type.Name.GetHashCode(); + var hash = displayAttribute?.Name?.GetHashCode() ?? type.Name.GetHashCode(); hash = hash >> 16 ^ hash; var hue = TypeDescriptorFactory.Default.AttributeRegistry.GetAttribute(type)?.CustomHue ?? hash % 360; - return new ColorHSV(hue, 0.75f + (hash % 101) / 400f, 0.5f + (hash % 151) / 300f, 1).ToColor(); + return new ColorHSV(hue, 0.75f + (hash % 101 / 400f), 0.5f + (hash % 151 / 300f), 1).ToColor(); } + + [GeneratedRegex("([a-z](?=[A-Z])|[A-Z](?=[A-Z][a-z]))")] + private static partial Regex GetCamelCaseRegex(); } diff --git a/sources/presentation/Stride.Core.Presentation/Dirtiables/CollectionChangeOperation.cs b/sources/presentation/Stride.Core.Presentation/Dirtiables/CollectionChangeOperation.cs index a35d7163c8..b89de4a5b4 100644 --- a/sources/presentation/Stride.Core.Presentation/Dirtiables/CollectionChangeOperation.cs +++ b/sources/presentation/Stride.Core.Presentation/Dirtiables/CollectionChangeOperation.cs @@ -4,124 +4,123 @@ using System.Collections; using System.Collections.Specialized; -namespace Stride.Core.Presentation.Dirtiables +namespace Stride.Core.Presentation.Dirtiables; + +public class CollectionChangeOperation : DirtyingOperation { - public class CollectionChangeOperation : DirtyingOperation - { - private readonly int index; - private IList? list; - private IReadOnlyCollection? items; + private readonly int index; + private IList? list; + private IReadOnlyCollection? items; - private CollectionChangeOperation(IList list, NotifyCollectionChangedAction actionToUndo, IEnumerable dirtiables) - : base(dirtiables) - { - ArgumentNullException.ThrowIfNull(list); - if (actionToUndo == NotifyCollectionChangedAction.Reset) throw new ArgumentException("Reset is not supported by the undo stack."); + private CollectionChangeOperation(IList list, NotifyCollectionChangedAction actionToUndo, IEnumerable dirtiables) + : base(dirtiables) + { + ArgumentNullException.ThrowIfNull(list); + if (actionToUndo == NotifyCollectionChangedAction.Reset) throw new ArgumentException("Reset is not supported by the undo stack."); - ActionToUndo = actionToUndo; - this.list = list; - } + ActionToUndo = actionToUndo; + this.list = list; + } - public CollectionChangeOperation(IList list, NotifyCollectionChangedAction actionToUndo, IReadOnlyCollection items, int index, IEnumerable dirtiables) - : this(list, actionToUndo, dirtiables) - { - ArgumentNullException.ThrowIfNull(items); + public CollectionChangeOperation(IList list, NotifyCollectionChangedAction actionToUndo, IReadOnlyCollection items, int index, IEnumerable dirtiables) + : this(list, actionToUndo, dirtiables) + { + ArgumentNullException.ThrowIfNull(items); - this.items = items; - this.index = index; - } + this.items = items; + this.index = index; + } - public CollectionChangeOperation(IList list, NotifyCollectionChangedEventArgs args, IEnumerable dirtiables) - : this(list, args.Action, dirtiables) + public CollectionChangeOperation(IList list, NotifyCollectionChangedEventArgs args, IEnumerable dirtiables) + : this(list, args.Action, dirtiables) + { + switch (args.Action) { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - items = args.NewItems?.Cast().ToArray(); - index = args.NewStartingIndex; - break; - case NotifyCollectionChangedAction.Move: - // Intentionally ignored, move in collection are not tracked - return; - case NotifyCollectionChangedAction.Remove: - items = args.OldItems?.Cast().ToArray(); - index = args.OldStartingIndex; - break; - case NotifyCollectionChangedAction.Replace: - items = args.OldItems?.Cast().ToArray(); - index = args.OldStartingIndex; - break; - case NotifyCollectionChangedAction.Reset: - throw new NotSupportedException("Reset is not supported by the undo stack."); - default: - items = Array.Empty(); - index = -1; - break; - } + case NotifyCollectionChangedAction.Add: + items = args.NewItems?.Cast().ToArray(); + index = args.NewStartingIndex; + break; + case NotifyCollectionChangedAction.Move: + // Intentionally ignored, move in collection are not tracked + return; + case NotifyCollectionChangedAction.Remove: + items = args.OldItems?.Cast().ToArray(); + index = args.OldStartingIndex; + break; + case NotifyCollectionChangedAction.Replace: + items = args.OldItems?.Cast().ToArray(); + index = args.OldStartingIndex; + break; + case NotifyCollectionChangedAction.Reset: + throw new NotSupportedException("Reset is not supported by the undo stack."); + default: + items = []; + index = -1; + break; } + } - public NotifyCollectionChangedAction ActionToUndo { get; private set; } + public NotifyCollectionChangedAction ActionToUndo { get; private set; } - public int ItemCount => items?.Count ?? 0; + public int ItemCount => items?.Count ?? 0; - /// - public override string ToString() - { - return $"{{{nameof(CollectionChangeOperation)}: {ActionToUndo}}}"; - } - - /// - protected override void FreezeContent() - { - list = null; - items = null; - } + /// + public override string ToString() + { + return $"{{{nameof(CollectionChangeOperation)}: {ActionToUndo}}}"; + } - /// - protected override void Undo() - { - if (list is null || items is null) return; + /// + protected override void FreezeContent() + { + list = null; + items = null; + } - var i = 0; - switch (ActionToUndo) - { - case NotifyCollectionChangedAction.Add: - ActionToUndo = NotifyCollectionChangedAction.Remove; - for (i = 0; i < items.Count; ++i) - { - list.RemoveAt(index); - } - break; - case NotifyCollectionChangedAction.Remove: - ActionToUndo = NotifyCollectionChangedAction.Add; - foreach (var item in items) - { - list.Insert(index + i, item); - ++i; - } - break; - case NotifyCollectionChangedAction.Replace: - var replacedItems = new List(); - foreach (var item in items) - { - replacedItems.Add(list[index + i]); - list[index + i] = item; - ++i; - } - items = replacedItems; - break; - case NotifyCollectionChangedAction.Move: - throw new NotSupportedException("Move is not supported by the undo stack."); - case NotifyCollectionChangedAction.Reset: - throw new NotSupportedException("Reset is not supported by the undo stack."); - } - } + /// + protected override void Undo() + { + if (list is null || items is null) return; - /// - protected override void Redo() + var i = 0; + switch (ActionToUndo) { - // Once we have un-done, the previous value is updated so Redo is just Undoing the Undo - Undo(); + case NotifyCollectionChangedAction.Add: + ActionToUndo = NotifyCollectionChangedAction.Remove; + for (i = 0; i < items.Count; ++i) + { + list.RemoveAt(index); + } + break; + case NotifyCollectionChangedAction.Remove: + ActionToUndo = NotifyCollectionChangedAction.Add; + foreach (var item in items) + { + list.Insert(index + i, item); + ++i; + } + break; + case NotifyCollectionChangedAction.Replace: + var replacedItems = new List(); + foreach (var item in items) + { + replacedItems.Add(list[index + i]); + list[index + i] = item; + ++i; + } + items = replacedItems; + break; + case NotifyCollectionChangedAction.Move: + throw new NotSupportedException("Move is not supported by the undo stack."); + case NotifyCollectionChangedAction.Reset: + throw new NotSupportedException("Reset is not supported by the undo stack."); } } + + /// + protected override void Redo() + { + // Once we have un-done, the previous value is updated so Redo is just Undoing the Undo + Undo(); + } } diff --git a/sources/presentation/Stride.Core.Presentation/Dirtiables/PropertyChangeOperation.cs b/sources/presentation/Stride.Core.Presentation/Dirtiables/PropertyChangeOperation.cs index 55a4ecd3f7..128af0a407 100644 --- a/sources/presentation/Stride.Core.Presentation/Dirtiables/PropertyChangeOperation.cs +++ b/sources/presentation/Stride.Core.Presentation/Dirtiables/PropertyChangeOperation.cs @@ -12,7 +12,7 @@ public class PropertyChangeOperation : DirtyingOperation, IMergeableOperation private object? container; private object? previousValue; - public PropertyChangeOperation(string propertyName, object container, object previousValue, IEnumerable dirtiables, bool nonPublic = false) + public PropertyChangeOperation(string propertyName, object container, object? previousValue, IEnumerable dirtiables, bool nonPublic = false) : base(dirtiables) { ArgumentNullException.ThrowIfNull(propertyName); diff --git a/sources/presentation/Stride.Core.Presentation/Extensions/ObjectExtensions.cs b/sources/presentation/Stride.Core.Presentation/Extensions/ObjectExtensions.cs index cac2ab045f..957f331e53 100644 --- a/sources/presentation/Stride.Core.Presentation/Extensions/ObjectExtensions.cs +++ b/sources/presentation/Stride.Core.Presentation/Extensions/ObjectExtensions.cs @@ -16,7 +16,7 @@ public static object MemberwiseClone(this object instance) var instanceType = instance.GetType(); - if (CachedMemberwiseCloneMethods.TryGetValue(instanceType, out var method) == false) + if (!CachedMemberwiseCloneMethods.TryGetValue(instanceType, out var method)) { var dynamicMethod = GenerateDynamicMethod(instanceType); @@ -33,10 +33,9 @@ public static T MemberwiseClone(this T instance) { ArgumentNullException.ThrowIfNull(instance); - var instanceType = typeof(T); - if (CachedMemberwiseCloneMethods.TryGetValue(instanceType, out var method) == false) + if (!CachedMemberwiseCloneMethods.TryGetValue(instanceType, out var method)) { var dynamicMethod = GenerateDynamicMethod(instanceType); @@ -65,7 +64,7 @@ private static DynamicMethod GenerateDynamicMethod(Type instanceType) } else { - var constructorInfo = instanceType.GetConstructor(Array.Empty()); + var constructorInfo = instanceType.GetConstructor([]); generator.Emit(OpCodes.Newobj, constructorInfo); generator.Emit(OpCodes.Stloc_0); } diff --git a/sources/presentation/Stride.Core.Presentation/Extensions/TypeExtensions.cs b/sources/presentation/Stride.Core.Presentation/Extensions/TypeExtensions.cs index 6925b4cce0..eefc8ec58b 100644 --- a/sources/presentation/Stride.Core.Presentation/Extensions/TypeExtensions.cs +++ b/sources/presentation/Stride.Core.Presentation/Extensions/TypeExtensions.cs @@ -40,7 +40,7 @@ public static string ToSimpleCSharpName(this Type type) if (type.IsArray) { - return ToSimpleCSharpName(type.GetElementType()) + "[" + new string(',', type.GetArrayRank() - 1) + "]"; + return ToSimpleCSharpName(type.GetElementType()!) + "[" + new string(',', type.GetArrayRank() - 1) + "]"; } if (!type.IsGenericType) @@ -53,7 +53,7 @@ public static string ToSimpleCSharpName(this Type type) if (simpleNameStart >= fullTypeName.Length) return fullTypeName; - return fullTypeName.Substring(simpleNameStart); + return fullTypeName[simpleNameStart..]; } if (Nullable.GetUnderlyingType(type) is Type nullableType) @@ -68,6 +68,6 @@ public static string ToSimpleCSharpName(this Type type) return "(" + genericParameters + ")"; } - return type.Name.Substring(0, type.Name.LastIndexOf('`')) + "<" + genericParameters + ">"; + return $"{type.Name[..type.Name.LastIndexOf('`')]}<{genericParameters}>"; } } diff --git a/sources/presentation/Stride.Core.Presentation/Services/DummyTransaction.cs b/sources/presentation/Stride.Core.Presentation/Services/DummyTransaction.cs index f2188a3230..4972645819 100644 --- a/sources/presentation/Stride.Core.Presentation/Services/DummyTransaction.cs +++ b/sources/presentation/Stride.Core.Presentation/Services/DummyTransaction.cs @@ -15,7 +15,7 @@ internal class DummyTransaction : ITransaction, IReadOnlyTransaction public Guid Id { get; } = Guid.NewGuid(); - public IReadOnlyList Operations { get; } = Array.Empty(); + public IReadOnlyList Operations { get; } = []; public bool IsEmpty => true; diff --git a/sources/presentation/Stride.Core.Presentation/Services/IDialogService.cs b/sources/presentation/Stride.Core.Presentation/Services/IDialogService.cs index 3232323de9..44d3e047e0 100644 --- a/sources/presentation/Stride.Core.Presentation/Services/IDialogService.cs +++ b/sources/presentation/Stride.Core.Presentation/Services/IDialogService.cs @@ -93,18 +93,18 @@ static IReadOnlyCollection GetButtons(MessageBoxButton buttons case MessageBoxButton.OK: var buttonOk = ButtonOK; buttonOk.IsCancel = true; - return new[] { buttonOk }; + return [buttonOk]; case MessageBoxButton.OKCancel: - return new[] { ButtonOK, ButtonCancel }; + return [ButtonOK, ButtonCancel]; case MessageBoxButton.YesNoCancel: - return new[] { ButtonYes, ButtonNo, ButtonCancel }; + return [ButtonYes, ButtonNo, ButtonCancel]; case MessageBoxButton.YesNo: var buttonNo = ButtonNo; buttonNo.IsCancel = true; - return new[] { ButtonYes, buttonNo }; + return [ButtonYes, buttonNo]; default: throw new ArgumentOutOfRangeException(nameof(buttons), buttons, null); diff --git a/sources/presentation/Stride.Core.Presentation/ViewModels/DirtiableEditableViewModel.cs b/sources/presentation/Stride.Core.Presentation/ViewModels/DirtiableEditableViewModel.cs index b56a0655fe..fe431ca8ac 100644 --- a/sources/presentation/Stride.Core.Presentation/ViewModels/DirtiableEditableViewModel.cs +++ b/sources/presentation/Stride.Core.Presentation/ViewModels/DirtiableEditableViewModel.cs @@ -35,7 +35,7 @@ private set } /// - public override IEnumerable Dirtiables => this.Yield(); + public override IEnumerable Dirtiables => this.Yield()!; protected virtual void OnDirtyFlagSet() { diff --git a/sources/presentation/Stride.Core.Presentation/ViewModels/EditableViewModel.cs b/sources/presentation/Stride.Core.Presentation/ViewModels/EditableViewModel.cs index f9301600a4..2b47924f94 100644 --- a/sources/presentation/Stride.Core.Presentation/ViewModels/EditableViewModel.cs +++ b/sources/presentation/Stride.Core.Presentation/ViewModels/EditableViewModel.cs @@ -14,7 +14,7 @@ namespace Stride.Core.Presentation.ViewModels; public abstract class EditableViewModel : DispatcherViewModel { - private readonly Dictionary preEditValues = []; + private readonly Dictionary preEditValues = []; private readonly HashSet uncancellableChanges = []; private readonly List suspendedCollections = []; @@ -51,12 +51,12 @@ protected IDisposable SuspendNotificationForCollectionChange(string name) protected bool SetValueUncancellable(ref T field, T value, [CallerMemberName] string? propertyName = null) { - return SetValueUncancellable(ref field, value, null, new[] { propertyName! }); + return SetValueUncancellable(ref field, value, null, [propertyName!]); } protected bool SetValueUncancellable(ref T field, T value, Action updateAction, [CallerMemberName] string? propertyName = null) { - return SetValueUncancellable(ref field, value, updateAction, new[] { propertyName! }); + return SetValueUncancellable(ref field, value, updateAction, [propertyName!]); } protected bool SetValueUncancellable(ref T field, T value, params string[] propertyNames) @@ -76,8 +76,7 @@ protected bool SetValueUncancellable(ref T field, T value, Action? updateActi } try { - var result = SetValue(ref field, value, updateAction, false, propertyNames); - return result; + return SetValue(ref field, value, updateAction, false, propertyNames); } finally { @@ -94,7 +93,7 @@ protected bool SetValueUncancellable(ref T field, T value, Action? updateActi protected bool SetValueUncancellable(Action? updateAction, [CallerMemberName] string? propertyName = null) { - return SetValueUncancellable(null, updateAction, new[] { propertyName! }); + return SetValueUncancellable(null, updateAction, [propertyName!]); } protected bool SetValueUncancellable(Action? updateAction, params string[] propertyNames) @@ -104,12 +103,12 @@ protected bool SetValueUncancellable(Action? updateAction, params string[] prope protected bool SetValueUncancellable(Func? hasChangedFunction, Action? updateAction, [CallerMemberName] string? propertyName = null) { - return SetValueUncancellable(hasChangedFunction, updateAction, new[] { propertyName! }); + return SetValueUncancellable(hasChangedFunction, updateAction, [propertyName!]); } protected bool SetValueUncancellable(bool hasChanged, Action? updateAction, [CallerMemberName] string? propertyName = null) { - return SetValueUncancellable(() => hasChanged, updateAction, new[] { propertyName! }); + return SetValueUncancellable(() => hasChanged, updateAction, [propertyName!]); } protected bool SetValueUncancellable(bool hasChanged, Action? updateAction, params string[] propertyNames) @@ -129,8 +128,7 @@ protected virtual bool SetValueUncancellable(Func? hasChangedFunction, Act } try { - var result = SetValue(hasChangedFunction, updateAction, false, propertyNames); - return result; + return SetValue(hasChangedFunction, updateAction, false, propertyNames); } finally { @@ -152,7 +150,7 @@ protected override bool SetValue(ref T field, T value, Action? updateAction, } /// - protected override bool SetValue(Func hasChangedFunction, Action? updateAction, params string[] propertyNames) + protected override bool SetValue(Func? hasChangedFunction, Action? updateAction, params string[] propertyNames) { return SetValue(hasChangedFunction, updateAction, true, propertyNames); } @@ -183,7 +181,7 @@ protected override void OnPropertyChanged(params string[] propertyNames) if (preEditValues.TryGetValue(propertyName, out var preEditValue) && !uncancellableChanges.Contains(propertyName)) { var propertyInfo = GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public); - var postEditValue = propertyInfo.GetValue(this); + var postEditValue = propertyInfo?.GetValue(this); if (!UndoRedoService.UndoRedoInProgress && !Equals(preEditValue, postEditValue)) { var operation = CreatePropertyChangeOperation(displayName, propertyName, preEditValue); @@ -194,7 +192,7 @@ protected override void OnPropertyChanged(params string[] propertyNames) } } - protected virtual Operation CreatePropertyChangeOperation(string displayName, string propertyName, object preEditValue) + protected virtual Operation CreatePropertyChangeOperation(string displayName, string propertyName, object? preEditValue) { var operation = new PropertyChangeOperation(propertyName, this, preEditValue, Dirtiables); UndoRedoService.SetName(operation, displayName); @@ -211,11 +209,11 @@ protected virtual Operation CreateCollectionChangeOperation(string displayName, private bool SetValue(ref T field, T value, Action? updateAction, bool createTransaction, params string[] propertyNames) { if (propertyNames.Length == 0) - throw new ArgumentOutOfRangeException(nameof(propertyNames), @"This method must be invoked with at least one property name."); + throw new ArgumentOutOfRangeException(nameof(propertyNames), "This method must be invoked with at least one property name."); - if (EqualityComparer.Default.Equals(field, value) == false) + if (!EqualityComparer.Default.Equals(field, value)) { - ITransaction transaction = null; + ITransaction? transaction = null; if (!UndoRedoService.UndoRedoInProgress && createTransaction) { transaction = UndoRedoService.CreateTransaction(); @@ -239,14 +237,14 @@ private bool SetValue(ref T field, T value, Action? updateAction, bool create return false; } - private bool SetValue(Func hasChangedFunction, Action updateAction, bool createTransaction, params string[] propertyNames) + private bool SetValue(Func? hasChangedFunction, Action? updateAction, bool createTransaction, params string[] propertyNames) { if (propertyNames.Length == 0) - throw new ArgumentOutOfRangeException(nameof(propertyNames), @"This method must be invoked with at least one property name."); + throw new ArgumentOutOfRangeException(nameof(propertyNames), "This method must be invoked with at least one property name."); if (hasChangedFunction == null || hasChangedFunction()) { - ITransaction transaction = null; + ITransaction? transaction = null; if (!UndoRedoService.UndoRedoInProgress && createTransaction) { transaction = UndoRedoService.CreateTransaction(); @@ -278,7 +276,7 @@ private void CollectionChanged(object? sender, NotifyCollectionChangedEventArgs { var toIListMethod = sender?.GetType().GetMethod("ToIList"); if (toIListMethod != null) - list = (IList)toIListMethod.Invoke(sender, Array.Empty()); + list = (IList?)toIListMethod.Invoke(sender, []); } if (!UndoRedoService.UndoRedoInProgress && !suspendedCollections.Contains(collectionName)) { diff --git a/sources/presentation/Stride.Core.Presentation/ViewModels/ViewModelBase.cs b/sources/presentation/Stride.Core.Presentation/ViewModels/ViewModelBase.cs index 5f5e58b1ac..1046423504 100644 --- a/sources/presentation/Stride.Core.Presentation/ViewModels/ViewModelBase.cs +++ b/sources/presentation/Stride.Core.Presentation/ViewModels/ViewModelBase.cs @@ -54,7 +54,7 @@ public virtual void Destroy() } /// - /// Checks whether this view model has been disposed, and throws an if it is the case. + /// Checks whether this view model has been disposed, and throws an if it is the case. /// /// The name to supply to the . protected void EnsureNotDestroyed(string? name = null) @@ -77,7 +77,7 @@ protected void EnsureNotDestroyed(string? name = null) /// True if the field was modified and events were raised, False if the new value was equal to the old one and nothing was done. protected bool SetValue([NotNullIfNotNull(nameof(value))] ref T field, T value, [CallerMemberName] string propertyName = null!) { - return SetValue(ref field, value, null, new[] { propertyName }); + return SetValue(ref field, value, null, [propertyName]); } /// @@ -108,7 +108,7 @@ protected bool SetValue([NotNullIfNotNull(nameof(value))] ref T field, T valu /// True if the field was modified and events were raised, False if the new value was equal to the old one and nothing was done. protected bool SetValue([NotNullIfNotNull(nameof(value))] ref T field, T value, Action? updateAction, [CallerMemberName] string propertyName = null!) { - return SetValue(ref field, value, updateAction, new[] { propertyName }); + return SetValue(ref field, value, updateAction, [propertyName]); } /// @@ -125,9 +125,9 @@ protected bool SetValue([NotNullIfNotNull(nameof(value))] ref T field, T valu protected virtual bool SetValue([NotNullIfNotNull(nameof(value))] ref T field, T value, Action? updateAction, params string[] propertyNames) { if (propertyNames.Length == 0) - throw new ArgumentOutOfRangeException(nameof(propertyNames), @"This method must be invoked with at least one property name."); + throw new ArgumentOutOfRangeException(nameof(propertyNames), "This method must be invoked with at least one property name."); - if (EqualityComparer.Default.Equals(field, value) == false) + if (!EqualityComparer.Default.Equals(field, value)) { OnPropertyChanging(propertyNames); field = value; @@ -148,7 +148,7 @@ protected virtual bool SetValue([NotNullIfNotNull(nameof(value))] ref T field /// This method always returnsTrue since it always performs the update. protected bool SetValue(Action? updateAction, [CallerMemberName] string propertyName = null!) { - return SetValue(null, updateAction, new[] { propertyName }); + return SetValue(null, updateAction, [propertyName]); } /// @@ -172,9 +172,9 @@ protected bool SetValue(Action? updateAction, params string[] propertyNames) /// The update action that will actually manage the update of the property. /// The name of the property that must be notified as changing/changed. Can use . /// True if the update was done and events were raised, False if is not null and returned false. - protected bool SetValue(Func hasChangedFunction, Action? updateAction, [CallerMemberName] string propertyName = null!) + protected bool SetValue(Func? hasChangedFunction, Action? updateAction, [CallerMemberName] string propertyName = null!) { - return SetValue(hasChangedFunction, updateAction, new[] { propertyName }); + return SetValue(hasChangedFunction, updateAction, [propertyName]); } /// @@ -188,7 +188,7 @@ protected bool SetValue(Func hasChangedFunction, Action? updateAction, [Ca /// The value provided in the argument. protected bool SetValue(bool hasChanged, Action? updateAction, [CallerMemberName] string propertyName = null!) { - return SetValue(() => hasChanged, updateAction, new[] { propertyName }); + return SetValue(() => hasChanged, updateAction, [propertyName]); } /// @@ -217,13 +217,9 @@ protected bool SetValue(bool hasChanged, Action? updateAction, params string[] p protected virtual bool SetValue(Func? hasChangedFunction, Action? updateAction, params string[] propertyNames) { if (propertyNames.Length == 0) - throw new ArgumentOutOfRangeException(nameof(propertyNames), @"This method must be invoked with at least one property name."); + throw new ArgumentOutOfRangeException(nameof(propertyNames), "This method must be invoked with at least one property name."); - var hasChanged = true; - if (hasChangedFunction != null) - { - hasChanged = hasChangedFunction(); - } + var hasChanged = hasChangedFunction?.Invoke() ?? true; if (hasChanged) { OnPropertyChanging(propertyNames); From d7d57b10ae23dfcd17e7d8db32455ca3ace04b0b Mon Sep 17 00:00:00 2001 From: Nicolas Musset Date: Fri, 21 Feb 2025 14:12:02 +0100 Subject: [PATCH 2/6] [Core.Design] Make ToEnumerable extension safer It will not throw a cast exception for a type mismatch, but instead filter out bad items. --- .../Extensions/ObjectExtensions.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/sources/core/Stride.Core.Design/Extensions/ObjectExtensions.cs b/sources/core/Stride.Core.Design/Extensions/ObjectExtensions.cs index 1585c6f214..1c731e5676 100644 --- a/sources/core/Stride.Core.Design/Extensions/ObjectExtensions.cs +++ b/sources/core/Stride.Core.Design/Extensions/ObjectExtensions.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Runtime.CompilerServices; -using Stride.Core.Annotations; namespace Stride.Core.Extensions; @@ -17,8 +16,7 @@ public static class ObjectExtensions /// /// The object. /// The return value of , or "(null)" if is null, or (ExceptionInToString)" if thrown an exception. - [NotNull] - public static string ToStringSafe([CanBeNull] this object obj) + public static string ToStringSafe(this object? obj) { try { @@ -54,8 +52,16 @@ public static string ToStringSafe([CanBeNull] this object obj) if (obj is IEnumerable enumerableT) return enumerableT; - var enumerable = obj as IEnumerable; - return enumerable?.Cast() ?? Yield((T?)obj); + if (obj is IEnumerable enumerable) + return enumerable.OfType(); + + if (obj is null) + return Yield((T?)obj); + + if (obj is T t) + return Yield(t); + + return []; } /// @@ -67,7 +73,6 @@ public static string ToStringSafe([CanBeNull] this object obj) /// The name of the argument, in case an must be thrown. /// The given object. /// This method can be used to test for null argument when forwarding members of the object to the base or this constructor. - [NotNull] public static T SafeArgument(this T obj, [CallerArgumentExpression(nameof(obj))] string argumentName = "") where T : class { #if NET6_0_OR_GREATER From 933f47fdb407e597af71ee646abd95af8df04472 Mon Sep 17 00:00:00 2001 From: Nicolas Musset Date: Sat, 22 Feb 2025 15:53:21 +0100 Subject: [PATCH 3/6] [Core.Quantum] Modernize code --- .../ITypeDescriptorFactory.cs | 3 + .../MemberDescriptors/IMemberDescriptor.cs | 2 +- .../TypeDescriptorFactory.cs | 2 + .../TypeDescriptors/CollectionDescriptor.cs | 2 +- .../TypeDescriptors/ListDescriptor.cs | 2 +- .../OldCollectionDescriptor.cs | 2 +- .../TypeDescriptors/SetDescriptor.cs | 3 +- .../Stride.Core.Quantum/BoxedNode.cs | 89 +-- .../Stride.Core.Quantum/ContentChangeType.cs | 18 +- .../Stride.Core.Quantum/DefaultNodeBuilder.cs | 330 ++++---- .../Stride.Core.Quantum/DefaultNodeFactory.cs | 64 +- .../Stride.Core.Quantum/DynamicNode.cs | 533 ++++++------- .../Stride.Core.Quantum/GraphNodeBase.cs | 164 ++-- .../GraphNodeChangeListener.cs | 361 ++++----- .../Stride.Core.Quantum/GraphNodeLinker.cs | 302 ++++--- .../Stride.Core.Quantum/GraphNodePath.cs | 746 +++++++++--------- .../Stride.Core.Quantum/GraphVisitorBase.cs | 314 ++++---- .../Stride.Core.Quantum/IGraphNode.cs | 88 ++- .../IInitializingGraphNode.cs | 18 +- .../IInitializingObjectNode.cs | 19 +- .../Stride.Core.Quantum/IMemberNode.cs | 70 +- .../Stride.Core.Quantum/INodeBuilder.cs | 132 ++-- .../INodeChangeEventArgs.cs | 43 +- .../Stride.Core.Quantum/INodeContainer.cs | 63 +- .../Stride.Core.Quantum/INodeFactory.cs | 70 +- .../INotifyNodeItemChange.cs | 26 +- .../INotifyNodeValueChange.cs | 26 +- .../Stride.Core.Quantum/IObjectNode.cs | 120 ++- .../Stride.Core.Quantum/Internal/Content.cs | 71 +- .../Internal/IGraphNodeInternal.cs | 11 +- .../ItemChangeEventArgs.cs | 96 ++- .../Stride.Core.Quantum/MemberNode.cs | 198 +++-- .../MemberNodeChangeEventArgs.cs | 70 +- .../Stride.Core.Quantum/NodeAccessor.cs | 140 ++-- .../Stride.Core.Quantum/NodeContainer.cs | 267 +++---- .../Stride.Core.Quantum/NodeIndex.cs | 208 ++--- .../Stride.Core.Quantum/ObjectNode.cs | 413 +++++----- .../Properties/AssemblyInfo.cs | 2 +- .../QuantumConsistencyException.cs | 169 ++-- .../References/IReference.cs | 16 +- .../References/IReferenceInternal.cs | 21 +- .../References/ObjectReference.cs | 227 +++--- .../References/Reference.cs | 72 +- .../References/ReferenceEnumerable.cs | 394 +++++---- .../Stride.Core.Quantum.csproj | 7 +- 45 files changed, 2926 insertions(+), 3068 deletions(-) diff --git a/sources/core/Stride.Core.Reflection/ITypeDescriptorFactory.cs b/sources/core/Stride.Core.Reflection/ITypeDescriptorFactory.cs index dc8a5bf257..2b6a5343e4 100644 --- a/sources/core/Stride.Core.Reflection/ITypeDescriptorFactory.cs +++ b/sources/core/Stride.Core.Reflection/ITypeDescriptorFactory.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System.Diagnostics.CodeAnalysis; + namespace Stride.Core.Reflection; /// @@ -19,5 +21,6 @@ public interface ITypeDescriptorFactory /// /// The type. /// ITypeDescriptor. + [return: NotNullIfNotNull(nameof(type))] ITypeDescriptor? Find(Type? type); } diff --git a/sources/core/Stride.Core.Reflection/MemberDescriptors/IMemberDescriptor.cs b/sources/core/Stride.Core.Reflection/MemberDescriptors/IMemberDescriptor.cs index 148bd867a4..a2da4c4d70 100644 --- a/sources/core/Stride.Core.Reflection/MemberDescriptors/IMemberDescriptor.cs +++ b/sources/core/Stride.Core.Reflection/MemberDescriptors/IMemberDescriptor.cs @@ -124,5 +124,5 @@ public interface IMemberDescriptor /// /// The this object. /// The value. - void Set(object thisObject, object value); + void Set(object thisObject, object? value); } diff --git a/sources/core/Stride.Core.Reflection/TypeDescriptorFactory.cs b/sources/core/Stride.Core.Reflection/TypeDescriptorFactory.cs index 6630ba772b..526f0e5723 100644 --- a/sources/core/Stride.Core.Reflection/TypeDescriptorFactory.cs +++ b/sources/core/Stride.Core.Reflection/TypeDescriptorFactory.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System.Diagnostics.CodeAnalysis; using Stride.Core.Yaml.Serialization; namespace Stride.Core.Reflection; @@ -45,6 +46,7 @@ public TypeDescriptorFactory(IAttributeRegistry attributeRegistry, bool emitDefa public IAttributeRegistry AttributeRegistry { get; } + [return: NotNullIfNotNull(nameof(type))] public ITypeDescriptor? Find(Type? type) { if (type is null) diff --git a/sources/core/Stride.Core.Reflection/TypeDescriptors/CollectionDescriptor.cs b/sources/core/Stride.Core.Reflection/TypeDescriptors/CollectionDescriptor.cs index 03ecb29384..b5739fbbf3 100644 --- a/sources/core/Stride.Core.Reflection/TypeDescriptors/CollectionDescriptor.cs +++ b/sources/core/Stride.Core.Reflection/TypeDescriptors/CollectionDescriptor.cs @@ -153,5 +153,5 @@ public static bool IsCollection(Type type) /// /// The collection. /// The number of elements of a collection, -1 if it cannot determine the number of elements. - public abstract int GetCollectionCount(object collection); + public abstract int GetCollectionCount(object? collection); } diff --git a/sources/core/Stride.Core.Reflection/TypeDescriptors/ListDescriptor.cs b/sources/core/Stride.Core.Reflection/TypeDescriptors/ListDescriptor.cs index 241980fe39..8fe82c082d 100644 --- a/sources/core/Stride.Core.Reflection/TypeDescriptors/ListDescriptor.cs +++ b/sources/core/Stride.Core.Reflection/TypeDescriptors/ListDescriptor.cs @@ -193,7 +193,7 @@ public override void RemoveAt(object list, int index) /// /// The list. /// The number of elements of a list, -1 if it cannot determine the number of elements. - public override int GetCollectionCount(object list) + public override int GetCollectionCount(object? list) { return list == null || getListCountMethod == null ? -1 : getListCountMethod(list); } diff --git a/sources/core/Stride.Core.Reflection/TypeDescriptors/OldCollectionDescriptor.cs b/sources/core/Stride.Core.Reflection/TypeDescriptors/OldCollectionDescriptor.cs index de41799d11..84698b6714 100644 --- a/sources/core/Stride.Core.Reflection/TypeDescriptors/OldCollectionDescriptor.cs +++ b/sources/core/Stride.Core.Reflection/TypeDescriptors/OldCollectionDescriptor.cs @@ -236,7 +236,7 @@ public override bool IsReadOnly(object collection) /// /// The collection. /// The number of elements of a collection, -1 if it cannot determine the number of elements. - public override int GetCollectionCount(object collection) + public override int GetCollectionCount(object? collection) { return collection == null || getCollectionCountMethod == null ? -1 : getCollectionCountMethod(collection); } diff --git a/sources/core/Stride.Core.Reflection/TypeDescriptors/SetDescriptor.cs b/sources/core/Stride.Core.Reflection/TypeDescriptors/SetDescriptor.cs index 1abb44da0d..865602a556 100644 --- a/sources/core/Stride.Core.Reflection/TypeDescriptors/SetDescriptor.cs +++ b/sources/core/Stride.Core.Reflection/TypeDescriptors/SetDescriptor.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Stride.Core.Yaml.Serialization; @@ -132,7 +133,7 @@ public bool Contains(object set, object? value) /// /// The set. /// The number of elements of a set, -1 if it cannot determine the number of elements. - public override int GetCollectionCount(object set) + public override int GetCollectionCount([NotNull] object? set) { ArgumentNullException.ThrowIfNull(set); return countMethod.Invoke(set); diff --git a/sources/presentation/Stride.Core.Quantum/BoxedNode.cs b/sources/presentation/Stride.Core.Quantum/BoxedNode.cs index bd2f2ee525..61ebb24e00 100644 --- a/sources/presentation/Stride.Core.Quantum/BoxedNode.cs +++ b/sources/presentation/Stride.Core.Quantum/BoxedNode.cs @@ -1,68 +1,65 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; using Stride.Core.Reflection; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +public class BoxedNode : ObjectNode { - public class BoxedNode : ObjectNode - { - private GraphNodeBase boxedStructureOwner; - private NodeIndex boxedStructureOwnerIndex; + private GraphNodeBase? boxedStructureOwner; + private NodeIndex boxedStructureOwnerIndex; - public BoxedNode([NotNull] INodeBuilder nodeBuilder, object value, Guid guid, [NotNull] ITypeDescriptor descriptor) - : base(nodeBuilder, value, guid, descriptor, null) - { - } + public BoxedNode(INodeBuilder nodeBuilder, object value, Guid guid, ITypeDescriptor descriptor) + : base(nodeBuilder, value, guid, descriptor, null) + { + } - protected internal override void UpdateFromMember(object newValue, NodeIndex index) - { - Update(newValue, index, true); - } + protected internal override void UpdateFromMember(object newValue, NodeIndex index) + { + Update(newValue, index, true); + } - internal void UpdateFromOwner(object newValue) - { - Update(newValue, NodeIndex.Empty, false); - } + internal void UpdateFromOwner(object newValue) + { + Update(newValue, NodeIndex.Empty, false); + } - internal void SetOwnerContent(IGraphNode ownerNode, NodeIndex index) - { - boxedStructureOwner = (GraphNodeBase)ownerNode; - boxedStructureOwnerIndex = index; - } + internal void SetOwnerContent(IGraphNode ownerNode, NodeIndex index) + { + boxedStructureOwner = (GraphNodeBase)ownerNode; + boxedStructureOwnerIndex = index; + } - private void Update(object newValue, NodeIndex index, bool updateStructureOwner) + private void Update(object newValue, NodeIndex index, bool updateStructureOwner) + { + if (index.TryGetValue(out var indexValue)) { - if (!index.IsEmpty) + if (Descriptor is CollectionDescriptor collectionDescriptor) + { + collectionDescriptor.SetValue(Value, indexValue, newValue); + } + else if (Descriptor is DictionaryDescriptor dictionaryDescriptor) { - var collectionDescriptor = Descriptor as CollectionDescriptor; - var dictionaryDescriptor = Descriptor as DictionaryDescriptor; - if (collectionDescriptor != null) - { - collectionDescriptor.SetValue(Value, index.Value, newValue); - } - else if (dictionaryDescriptor != null) - { - dictionaryDescriptor.SetValue(Value, index, newValue); - } - else - throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); + dictionaryDescriptor.SetValue(Value, index, newValue); } else { - SetValue(newValue); - if (updateStructureOwner) - { - boxedStructureOwner?.UpdateFromMember(newValue, boxedStructureOwnerIndex); - } + throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); } } - - public override string ToString() + else { - return $"{{Node: Boxed {Type.Name} = [{Value}]}}"; + SetValue(newValue); + if (updateStructureOwner) + { + boxedStructureOwner?.UpdateFromMember(newValue, boxedStructureOwnerIndex); + } } } + + public override string ToString() + { + return $"{{Node: Boxed {Type.Name} = [{Value}]}}"; + } } diff --git a/sources/presentation/Stride.Core.Quantum/ContentChangeType.cs b/sources/presentation/Stride.Core.Quantum/ContentChangeType.cs index 4760c7bbf9..3448632fe6 100644 --- a/sources/presentation/Stride.Core.Quantum/ContentChangeType.cs +++ b/sources/presentation/Stride.Core.Quantum/ContentChangeType.cs @@ -1,13 +1,13 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Quantum + +namespace Stride.Core.Quantum; + +public enum ContentChangeType { - public enum ContentChangeType - { - None, - ValueChange, - CollectionUpdate, - CollectionAdd, - CollectionRemove, - } + None, + ValueChange, + CollectionUpdate, + CollectionAdd, + CollectionRemove, } diff --git a/sources/presentation/Stride.Core.Quantum/DefaultNodeBuilder.cs b/sources/presentation/Stride.Core.Quantum/DefaultNodeBuilder.cs index bdf3710e8f..77002c1042 100644 --- a/sources/presentation/Stride.Core.Quantum/DefaultNodeBuilder.cs +++ b/sources/presentation/Stride.Core.Quantum/DefaultNodeBuilder.cs @@ -1,218 +1,212 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Stride.Core.Annotations; -using Stride.Core.Reflection; +using System.Diagnostics.CodeAnalysis; using Stride.Core.Quantum.References; +using Stride.Core.Reflection; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// The default implementation that construct a graph from a data object. +/// +internal class DefaultNodeBuilder : DataVisitorBase, INodeBuilder { + private readonly Stack contextStack = new(); + private readonly HashSet referenceContents = []; + private readonly List primitiveTypes = []; + private static readonly Type[] InternalPrimitiveTypes = [typeof(decimal), typeof(string), typeof(Guid)]; + private IInitializingObjectNode? rootNode; + private Guid rootGuid; + + public DefaultNodeBuilder(NodeContainer nodeContainer) + { + NodeContainer = nodeContainer; + primitiveTypes.AddRange(InternalPrimitiveTypes); + } + + /// + public NodeContainer NodeContainer { get; } + + /// + public INodeFactory NodeFactory { get; set; } = new DefaultNodeFactory(); + /// - /// The default implementation that construct a graph from a data object. + /// Reset the visitor in order to use it to generate another model. /// - internal class DefaultNodeBuilder : DataVisitorBase, INodeBuilder + public override void Reset() { - private readonly Stack contextStack = new Stack(); - private readonly HashSet referenceContents = new HashSet(); - private readonly List primitiveTypes = new List(); - private static readonly Type[] InternalPrimitiveTypes = { typeof(decimal), typeof(string), typeof(Guid) }; - private IInitializingObjectNode rootNode; - private Guid rootGuid; + rootNode = null; + rootGuid = Guid.Empty; + contextStack.Clear(); + referenceContents.Clear(); + base.Reset(); + } - public DefaultNodeBuilder(NodeContainer nodeContainer) - { - NodeContainer = nodeContainer; - primitiveTypes.AddRange(InternalPrimitiveTypes); - } + public void RegisterPrimitiveType(Type type) + { + if (type.IsPrimitive || type.IsEnum || primitiveTypes.Contains(type)) + return; - /// - public NodeContainer NodeContainer { get; } + primitiveTypes.Add(type); + } - /// - public INodeFactory NodeFactory { get; set; } = new DefaultNodeFactory(); + public void UnregisterPrimitiveType(Type type) + { + if (type.IsPrimitive || type.IsEnum || InternalPrimitiveTypes.Contains(type)) + throw new InvalidOperationException("The given type cannot be unregistered from the list of primitive types"); - /// - /// Reset the visitor in order to use it to generate another model. - /// - public override void Reset() - { - rootNode = null; - rootGuid = Guid.Empty; - contextStack.Clear(); - referenceContents.Clear(); - base.Reset(); - } + primitiveTypes.Remove(type); + } - public void RegisterPrimitiveType([NotNull] Type type) - { - if (type.IsPrimitive || type.IsEnum || primitiveTypes.Contains(type)) - return; + public bool IsPrimitiveType([NotNullWhen(true)] Type? type) + { + if (type == null) + return false; - primitiveTypes.Add(type); - } + var underlyingType = Nullable.GetUnderlyingType(type); + if (underlyingType != null) + type = underlyingType; - public void UnregisterPrimitiveType([NotNull] Type type) - { - if (type.IsPrimitive || type.IsEnum || InternalPrimitiveTypes.Contains(type)) - throw new InvalidOperationException("The given type cannot be unregistered from the list of primitive types"); + return type.IsPrimitive || type.IsEnum || primitiveTypes.Any(x => x.IsAssignableFrom(type)); + } - primitiveTypes.Remove(type); - } + /// + public IObjectNode Build(object obj, Guid guid) + { + ArgumentNullException.ThrowIfNull(obj); + Reset(); + rootGuid = guid; + var typeDescriptor = TypeDescriptorFactory.Find(obj.GetType()); + VisitObject(obj, (ObjectDescriptor)typeDescriptor!, true); + return rootNode!; + } - public bool IsPrimitiveType(Type type) + /// + public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + { + ITypeDescriptor currentDescriptor = descriptor; + + bool isRootNode = contextStack.Count == 0; + if (isRootNode) { - if (type == null) - return false; + // If we're visiting a value type as "object" we need to use a special "boxed" node. + var content = descriptor.Type.IsValueType ? NodeFactory.CreateBoxedNode(this, rootGuid, obj, descriptor) + : NodeFactory.CreateObjectNode(this, rootGuid, obj, descriptor); - var underlyingType = Nullable.GetUnderlyingType(type); - if (underlyingType != null) - type = underlyingType; + currentDescriptor = content.Descriptor; + rootNode = (IInitializingObjectNode)content; + if (content.IsReference && currentDescriptor.Type.IsStruct()) + throw new QuantumConsistencyException("A collection type", "A structure type", rootNode); - return type.IsPrimitive || type.IsEnum || primitiveTypes.Any(x => x.IsAssignableFrom(type)); - } + if (content.IsReference) + referenceContents.Add(content); - /// - public IObjectNode Build([NotNull] object obj, Guid guid) - { - if (obj == null) throw new ArgumentNullException(nameof(obj)); - Reset(); - rootGuid = guid; - var typeDescriptor = TypeDescriptorFactory.Find(obj.GetType()); - VisitObject(obj, typeDescriptor as ObjectDescriptor, true); - return rootNode; + PushContextNode(rootNode); } - /// - public override void VisitObject(object obj, ObjectDescriptor descriptor, bool visitMembers) + if (!IsPrimitiveType(currentDescriptor.Type)) { - ITypeDescriptor currentDescriptor = descriptor; - - bool isRootNode = contextStack.Count == 0; - if (isRootNode) - { - // If we're visiting a value type as "object" we need to use a special "boxed" node. - var content = descriptor.Type.IsValueType ? NodeFactory.CreateBoxedNode(this, rootGuid, obj, descriptor) - : NodeFactory.CreateObjectNode(this, rootGuid, obj, descriptor); - - currentDescriptor = content.Descriptor; - rootNode = (IInitializingObjectNode)content; - if (content.IsReference && currentDescriptor.Type.IsStruct()) - throw new QuantumConsistencyException("A collection type", "A structure type", rootNode); - - if (content.IsReference) - referenceContents.Add(content); - - PushContextNode(rootNode); - } - - if (!IsPrimitiveType(currentDescriptor.Type)) - { - base.VisitObject(obj, descriptor, true); - } - - if (isRootNode) - { - PopContextNode(); - rootNode.Seal(); - } + base.VisitObject(obj, descriptor, true); } - /// - public override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor) + if (isRootNode) { - if (!descriptor.HasIndexerAccessors) - throw new NotSupportedException("Collections that do not have indexer accessors are not supported in Quantum."); - - // Don't visit items unless they are primitive or enumerable (collections within collections) - if (IsCollection(descriptor.ElementType)) - { - base.VisitCollection(collection, descriptor); - } + PopContextNode(); + rootNode!.Seal(); } + } - /// - public override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor) + /// + public override void VisitCollection(IEnumerable collection, CollectionDescriptor descriptor) + { + if (!descriptor.HasIndexerAccessors) + throw new NotSupportedException("Collections that do not have indexer accessors are not supported in Quantum."); + + // Don't visit items unless they are primitive or enumerable (collections within collections) + if (IsCollection(descriptor.ElementType)) { - if (!IsPrimitiveType(descriptor.KeyType)) - throw new InvalidOperationException("The type of dictionary key must be a primary type."); - - // Don't visit items unless they are primitive or enumerable (collections within collections) - if (IsCollection(descriptor.ValueType)) - { - base.VisitDictionary(dictionary, descriptor); - } + base.VisitCollection(collection, descriptor); } + } - /// - public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object value) - { - // If this member should contains a reference, create it now. - var containerNode = (IInitializingObjectNode)GetContextNode(); - var guid = Guid.NewGuid(); - var content = (MemberNode)NodeFactory.CreateMemberNode(this, guid, containerNode, member, value); - containerNode.AddMember(content); + /// + public override void VisitDictionary(object dictionary, DictionaryDescriptor descriptor) + { + if (!IsPrimitiveType(descriptor.KeyType)) + throw new InvalidOperationException("The type of dictionary key must be a primary type."); - if (content.IsReference) - referenceContents.Add(content); + // Don't visit items unless they are primitive or enumerable (collections within collections) + if (IsCollection(descriptor.ValueType)) + { + base.VisitDictionary(dictionary, descriptor); + } + } - PushContextNode(content); - if (content.TargetReference == null) - { - // For enumerable references, we visit the member to allow VisitList or VisitDictionary or VisitSet or VisitCollection to enrich correctly the node. - Visit(content.Retrieve()); - } - PopContextNode(); + /// + public override void VisitObjectMember(object container, ObjectDescriptor containerDescriptor, IMemberDescriptor member, object? value) + { + // If this member should contains a reference, create it now. + var containerNode = (IInitializingObjectNode)GetContextNode(); + var guid = Guid.NewGuid(); + var content = (MemberNode)NodeFactory.CreateMemberNode(this, guid, containerNode, member, value); + containerNode.AddMember(content); - content.Seal(); - } + if (content.IsReference) + referenceContents.Add(content); - [CanBeNull] - public IReference CreateReferenceForNode(Type type, object value, bool isMember) + PushContextNode(content); + if (content.TargetReference == null) { - if (isMember) - { - return !IsPrimitiveType(type) ? Reference.CreateReference(value, type, NodeIndex.Empty, true) : null; - } - - var descriptor = TypeDescriptorFactory.Find(value?.GetType()); - if (descriptor is CollectionDescriptor || descriptor is DictionaryDescriptor) - { - var valueType = GetElementValueType(descriptor); - return !IsPrimitiveType(valueType) ? Reference.CreateReference(value, type, NodeIndex.Empty, false) : null; - } - - return null; + // For enumerable references, we visit the member to allow VisitList or VisitDictionary or VisitSet or VisitCollection to enrich correctly the node. + Visit(content.Retrieve()); } + PopContextNode(); - private void PushContextNode(IInitializingGraphNode node) - { - contextStack.Push(node); - } + content.Seal(); + } - private void PopContextNode() + public IReference? CreateReferenceForNode(Type type, object? value, bool isMember) + { + if (isMember) { - contextStack.Pop(); + return !IsPrimitiveType(type) ? Reference.CreateReference(value, type, NodeIndex.Empty, true) : null; } - private IInitializingGraphNode GetContextNode() + var descriptor = TypeDescriptorFactory.Find(value?.GetType()); + if (descriptor is CollectionDescriptor || descriptor is DictionaryDescriptor) { - return contextStack.Peek(); + var valueType = GetElementValueType(descriptor); + return !IsPrimitiveType(valueType) ? Reference.CreateReference(value, type, NodeIndex.Empty, false) : null; } - private static bool IsCollection(Type type) - { - return typeof(ICollection).IsAssignableFrom(type); - } + return null; + } - [CanBeNull] - private static Type GetElementValueType(ITypeDescriptor descriptor) - { - var dictionaryDescriptor = descriptor as DictionaryDescriptor; - var collectionDescriptor = descriptor as CollectionDescriptor; - return dictionaryDescriptor != null ? dictionaryDescriptor.ValueType : collectionDescriptor?.ElementType; - } + private void PushContextNode(IInitializingGraphNode node) + { + contextStack.Push(node); + } + + private void PopContextNode() + { + contextStack.Pop(); + } + + private IInitializingGraphNode GetContextNode() + { + return contextStack.Peek(); + } + + private static bool IsCollection(Type type) + { + return typeof(ICollection).IsAssignableFrom(type); + } + + private static Type? GetElementValueType(ITypeDescriptor descriptor) + { + var collectionDescriptor = descriptor as CollectionDescriptor; + return descriptor is DictionaryDescriptor dictionaryDescriptor ? dictionaryDescriptor.ValueType : collectionDescriptor?.ElementType; } } diff --git a/sources/presentation/Stride.Core.Quantum/DefaultNodeFactory.cs b/sources/presentation/Stride.Core.Quantum/DefaultNodeFactory.cs index 587c922154..a9c1716bc6 100644 --- a/sources/presentation/Stride.Core.Quantum/DefaultNodeFactory.cs +++ b/sources/presentation/Stride.Core.Quantum/DefaultNodeFactory.cs @@ -1,45 +1,43 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using Stride.Core.Reflection; using Stride.Core.Quantum.References; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// This class is an implementation of the interface that can construct , +/// and instances. +/// +public class DefaultNodeFactory : INodeFactory { - /// - /// This class is an implementation of the interface that can construct , - /// and instances. - /// - public class DefaultNodeFactory : INodeFactory + /// + public virtual IObjectNode CreateObjectNode(INodeBuilder nodeBuilder, Guid guid, object obj, ITypeDescriptor descriptor) { - /// - public virtual IObjectNode CreateObjectNode(INodeBuilder nodeBuilder, Guid guid, object obj, ITypeDescriptor descriptor) - { - if (nodeBuilder == null) throw new ArgumentNullException(nameof(nodeBuilder)); - if (obj == null) throw new ArgumentNullException(nameof(obj)); - if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); - var reference = nodeBuilder.CreateReferenceForNode(descriptor.Type, obj, false) as ReferenceEnumerable; - return new ObjectNode(nodeBuilder, obj, guid, descriptor, reference); - } + ArgumentNullException.ThrowIfNull(nodeBuilder); + ArgumentNullException.ThrowIfNull(obj); + ArgumentNullException.ThrowIfNull(descriptor); + var reference = nodeBuilder.CreateReferenceForNode(descriptor.Type, obj, false) as ReferenceEnumerable; + return new ObjectNode(nodeBuilder, obj, guid, descriptor, reference); + } - /// - public virtual IObjectNode CreateBoxedNode(INodeBuilder nodeBuilder, Guid guid, object structure, ITypeDescriptor descriptor) - { - if (nodeBuilder == null) throw new ArgumentNullException(nameof(nodeBuilder)); - if (structure == null) throw new ArgumentNullException(nameof(structure)); - if (descriptor == null) throw new ArgumentNullException(nameof(descriptor)); - return new BoxedNode(nodeBuilder, structure, guid, descriptor); - } + /// + public virtual IObjectNode CreateBoxedNode(INodeBuilder nodeBuilder, Guid guid, object structure, ITypeDescriptor descriptor) + { + ArgumentNullException.ThrowIfNull(nodeBuilder); + ArgumentNullException.ThrowIfNull(structure); + ArgumentNullException.ThrowIfNull(descriptor); + return new BoxedNode(nodeBuilder, structure, guid, descriptor); + } - /// - public virtual IMemberNode CreateMemberNode(INodeBuilder nodeBuilder, Guid guid, IObjectNode parent, IMemberDescriptor member, object value) - { - if (nodeBuilder == null) throw new ArgumentNullException(nameof(nodeBuilder)); - if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (member == null) throw new ArgumentNullException(nameof(member)); - var reference = nodeBuilder.CreateReferenceForNode(member.Type, value, true); - return new MemberNode(nodeBuilder, guid, parent, member, reference); - } + /// + public virtual IMemberNode CreateMemberNode(INodeBuilder nodeBuilder, Guid guid, IObjectNode parent, IMemberDescriptor member, object? value) + { + ArgumentNullException.ThrowIfNull(nodeBuilder); + ArgumentNullException.ThrowIfNull(parent); + ArgumentNullException.ThrowIfNull(member); + var reference = nodeBuilder.CreateReferenceForNode(member.Type, value, true); + return new MemberNode(nodeBuilder, guid, parent, member, reference); } } diff --git a/sources/presentation/Stride.Core.Quantum/DynamicNode.cs b/sources/presentation/Stride.Core.Quantum/DynamicNode.cs index 229e6eab3d..04248eb48f 100644 --- a/sources/presentation/Stride.Core.Quantum/DynamicNode.cs +++ b/sources/presentation/Stride.Core.Quantum/DynamicNode.cs @@ -1,365 +1,352 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using System.Collections; -using System.Collections.Generic; using System.Dynamic; -using System.Linq; -using Stride.Core.Annotations; using Stride.Core.Reflection; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +public abstract class DynamicNode : DynamicObject, IEnumerable { - public abstract class DynamicNode : DynamicObject, IEnumerable + protected readonly IGraphNode Node; + + internal DynamicNode(IGraphNode node) { - protected readonly IGraphNode Node; + Node = node; + } - internal DynamicNode(IGraphNode node) - { - Node = node; - } + /// + /// Creates a dynamic node from the given . + /// + /// The node to use to create the dynamic node. + /// A representing the given node. + public static dynamic FromNode(IGraphNode node) + { + if (node is MemberNode) + throw new ArgumentException("Cannot create a dynamic node from a member node."); - /// - /// Creates a dynamic node from the given . - /// - /// The node to use to create the dynamic node. - /// A representing the given node. - [NotNull] - public static dynamic FromNode(IGraphNode node) - { - if (node is MemberNode) - throw new ArgumentException("Cannot create a dynamic node from a member node."); + return new DynamicDirectNode(node); + } - return new DynamicDirectNode(node); - } + /// + /// Returns the associated to the given dynamic node. + /// + /// The node from which to retrieve the graph node. + /// A associated to the given node. + public static IGraphNode GetNode(DynamicNode node) + { + ArgumentNullException.ThrowIfNull(node); + return node.Node; + } - /// - /// Returns the associated to the given dynamic node. - /// - /// The node from which to retrieve the graph node. - /// A associated to the given node. - public static IGraphNode GetNode([NotNull] DynamicNode node) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - return node.Node; - } + /// + /// Returns the associated to the given dynamic node. + /// + /// A associated to the given node. + public IGraphNode GetNode() => Node; + + /// + /// Adds an item to the content of this node, assuming it's a collection. + /// + /// The item to add. + public void Add(object item) + { + var targetNode = GetTargetNode() + ?? throw new InvalidOperationException($"Cannot invoke {nameof(Add)} on this property."); + targetNode.Add(item); + } - /// - /// Returns the associated to the given dynamic node. - /// - /// A associated to the given node. - public IGraphNode GetNode() => Node; - - /// - /// Adds an item to the content of this node, assuming it's a collection. - /// - /// The item to add. - public void Add(object item) - { - var targetNode = GetTargetNode(); - if (targetNode == null) - throw new InvalidOperationException($"Cannot invoke {nameof(Add)} on this property."); - targetNode.Add(item); - } + /// + /// Inserts an item to the content of this node, assuming it's a collection. + /// + /// The item to insert. + /// The index of the item to insert. + public void Insert(object item, NodeIndex index) + { + var targetNode = GetTargetNode() + ?? throw new InvalidOperationException($"Cannot invoke {nameof(Insert)} on this property."); + targetNode.Add(item, index); + } - /// - /// Inserts an item to the content of this node, assuming it's a collection. - /// - /// The item to insert. - /// The index of the item to insert. - public void Insert(object item, NodeIndex index) - { - var targetNode = GetTargetNode(); - if (targetNode == null) - throw new InvalidOperationException($"Cannot invoke {nameof(Insert)} on this property."); - targetNode.Add(item, index); - } + /// + /// Removes an item from the content if this node, assuming it's a collection. + /// + /// The item to remove. + /// The index of the item to remove. + public void Remove(object item, NodeIndex index) + { + var targetNode = GetTargetNode() + ?? throw new InvalidOperationException($"Cannot invoke {nameof(Remove)} on this property."); + targetNode.Remove(item, index); + } - /// - /// Removes an item from the content if this node, assuming it's a collection. - /// - /// The item to remove. - /// The index of the item to remove. - public void Remove(object item, NodeIndex index) - { - var targetNode = GetTargetNode(); - if (targetNode == null) - throw new InvalidOperationException($"Cannot invoke {nameof(Remove)} on this property."); - targetNode.Remove(item, index); - } + /// + /// Retrieves the actual value of this node. + /// + /// The actual value of this node. + public object? Retrieve() => RetrieveValue(); + + /// + /// Retrieves the actual value of this node. + /// + /// The type of expected value + /// The actual value of this node. + /// The actual type of the node value does not match the given type. + public T? Retrieve() => (T?)Retrieve(); + + /// + public override bool TryGetMember(GetMemberBinder binder, out object? result) + { + var memberNode = GetTargetMemberNode(binder.Name); + result = memberNode != null ? new DynamicDirectNode(memberNode) : null; + return result != null; + } - /// - /// Retrieves the actual value of this node. - /// - /// The actual value of this node. - public object Retrieve() => RetrieveValue(); - - /// - /// Retrieves the actual value of this node. - /// - /// The type of expected value - /// The actual value of this node. - /// The actual type of the node value does not match the given type. - public T Retrieve() => (T)Retrieve(); - - /// - public override bool TryGetMember(GetMemberBinder binder, out object result) - { - var memberNode = GetTargetMemberNode(binder.Name); - result = memberNode != null ? new DynamicDirectNode(memberNode) : null; - return result != null; - } + /// + public override bool TrySetMember(SetMemberBinder binder, object? value) + { + var memberNode = GetTargetMemberNode(binder.Name); + if (memberNode == null) + return false; - /// - public override bool TrySetMember(SetMemberBinder binder, object value) + try { - var memberNode = GetTargetMemberNode(binder.Name); - if (memberNode == null) - return false; - - try - { - // TODO: "changing" notifications will still be sent even if the update fails (but not the "changed") - we should detect preemptively if we can update (implements a bool TryUpdate?) - memberNode.Update(value); - return true; - } - catch (Exception) - { - return false; - } + // TODO: "changing" notifications will still be sent even if the update fails (but not the "changed") - we should detect preemptively if we can update (implements a bool TryUpdate?) + memberNode.Update(value); + return true; } - - /// - public override bool TryConvert([NotNull] ConvertBinder binder, out object result) + catch (Exception) { - var value = RetrieveValue(); - if (value == null && !binder.Type.IsValueType) - { - result = null; - return true; - } - if (binder.Type.IsInstanceOfType(value)) - { - result = value; - return true; - } - result = null; return false; } + } - /// - public override IEnumerable GetDynamicMemberNames() + /// + public override bool TryConvert(ConvertBinder binder, out object? result) + { + var value = RetrieveValue(); + if (value == null && !binder.Type.IsValueType) { - return GetTargetNode()?.Members.Select(x => x.Name) ?? Enumerable.Empty(); + result = null; + return true; } - - /// - IEnumerator IEnumerable.GetEnumerator() + if (binder.Type.IsInstanceOfType(value)) { - var node = GetTargetNode(); - // TODO: review this, is there a typo here? - var indices = (Node as IObjectNode)?.Indices.Select(x => x.Value); - if (indices == null) - throw new InvalidOperationException("This node is not enumerable."); - - dynamic thisNode = this; - return indices.Cast().Select(x => thisNode[x]).GetEnumerator(); + result = value; + return true; } + result = null; + return false; + } - [CanBeNull] - protected IMemberNode GetTargetMemberNode(string memberName) - { - var targetNode = GetTargetNode(); - var memberNode = targetNode?[memberName]; - return memberNode; - } + /// + public override IEnumerable GetDynamicMemberNames() + { + return GetTargetNode()?.Members.Select(x => x.Name) ?? []; + } - protected abstract object RetrieveValue(); + /// + IEnumerator IEnumerable.GetEnumerator() + { + var node = GetTargetNode(); + // TODO: review this, is there a typo here? + var indices = ((Node as IObjectNode)?.Indices?.Select(x => x.Value)) + ?? throw new InvalidOperationException("This node is not enumerable."); + dynamic thisNode = this; + return indices.Cast().Select(x => thisNode[x]).GetEnumerator(); + } - [CanBeNull] - protected abstract IObjectNode GetTargetNode(); + protected IMemberNode? GetTargetMemberNode(string memberName) + { + var targetNode = GetTargetNode(); + var memberNode = targetNode?[memberName]; + return memberNode; + } + + protected abstract object? RetrieveValue(); - protected static bool IsIndexExisting([NotNull] IGraphNode node, NodeIndex index) + protected abstract IObjectNode? GetTargetNode(); + + protected static bool IsIndexExisting(IGraphNode node, NodeIndex index) + { + if (node.IsReference) { - if (node.IsReference) + var reference = (node as IObjectNode)?.ItemReferences; + if (reference?.HasIndex(index) ?? false) { - var reference = (node as IObjectNode)?.ItemReferences; - if (reference?.HasIndex(index) ?? false) - { - return true; - } + return true; } - else + } + else + { + var value = node.Retrieve(); + var descriptor = node.Descriptor; + if (descriptor is CollectionDescriptor collectionDescriptor) { - var value = node.Retrieve(); - var descriptor = node.Descriptor; - if (descriptor is CollectionDescriptor collectionDescriptor) + if (descriptor is SetDescriptor setDescriptor) { - if (descriptor is SetDescriptor setDescriptor) - { - if (setDescriptor.Contains(value, index.Value)) - { - return true; - } - } - else + if (setDescriptor.Contains(value, index.Value)) { - if (index.IsInt - && index.Int >= 0 - && index.Int < collectionDescriptor.GetCollectionCount(value)) - { - return true; - } + return true; } } - else if (descriptor is DictionaryDescriptor dictionaryDescriptor) + else { - if (dictionaryDescriptor.KeyType.IsInstanceOfType(index.Value) - && dictionaryDescriptor.ContainsKey(value, index.Value)) + if (index.IsInt + && index.Int >= 0 + && index.Int < collectionDescriptor.GetCollectionCount(value)) { return true; } } } - return false; - } - - protected static bool IsIndexValid([NotNull] IGraphNode node, NodeIndex index) - { - if (node.IsReference) - { - var reference = (node as IObjectNode)?.ItemReferences; - return reference != null; - } - - if (node.Descriptor is CollectionDescriptor collectionDescriptor) + else if (descriptor is DictionaryDescriptor dictionaryDescriptor) { - if (node.Descriptor.Category == DescriptorCategory.Set) + if (dictionaryDescriptor.KeyType.IsInstanceOfType(index.Value) + && dictionaryDescriptor.ContainsKey(value, index.Value)) { - return collectionDescriptor.ElementType.IsInstanceOfType(index.Value); - } - else - { - return index.IsInt && index.Int >= 0; + return true; } } - else if (node.Descriptor is DictionaryDescriptor dictionaryDescriptor) - { - return dictionaryDescriptor.KeyType.IsInstanceOfType(index.Value); - } - return false; } + return false; + } - protected static bool UpdateCollection([NotNull] IObjectNode node, object value, NodeIndex index) + protected static bool IsIndexValid(IGraphNode node, NodeIndex index) + { + if (node.IsReference) { - if (IsIndexExisting(node, index)) + var reference = (node as IObjectNode)?.ItemReferences; + return reference != null; + } + + if (node.Descriptor is CollectionDescriptor collectionDescriptor) + { + if (node.Descriptor.Category == DescriptorCategory.Set) { - node.Update(value, index); - return true; + return collectionDescriptor.ElementType.IsInstanceOfType(index.Value); } - if (IsIndexValid(node, index)) + else { - node.Add(value, index); - return true; + return index.IsInt && index.Int >= 0; } - return false; } + else if (node.Descriptor is DictionaryDescriptor dictionaryDescriptor) + { + return dictionaryDescriptor.KeyType.IsInstanceOfType(index.Value); + } + return false; } - internal class DynamicDirectNode : DynamicNode + protected static bool UpdateCollection(IObjectNode node, object value, NodeIndex index) { - internal DynamicDirectNode(IGraphNode node) - : base(node) + if (IsIndexExisting(node, index)) { + node.Update(value, index); + return true; } - - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) + if (IsIndexValid(node, index)) { - if (indexes.Length == 1) - { - var index = new NodeIndex(indexes[0]); - if (IsIndexExisting(Node, index)) - { - result = new DynamicIndexedNode(Node, index); - return true; - } - } - result = null; - return false; + node.Add(value, index); + return true; } + return false; + } +} + +internal class DynamicDirectNode : DynamicNode +{ + internal DynamicDirectNode(IGraphNode node) + : base(node) + { + } - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result) + { + if (indexes.Length == 1) { - if (indexes.Length == 1) + var index = new NodeIndex(indexes[0]); + if (IsIndexExisting(Node, index)) { - var index = new NodeIndex(indexes[0]); - return UpdateCollection((IObjectNode)Node, value, index); + result = new DynamicIndexedNode(Node, index); + return true; } - return false; } + result = null; + return false; + } - protected override object RetrieveValue() + public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object? value) + { + if (indexes.Length == 1) { - return Node.Retrieve(); + var index = new NodeIndex(indexes[0]); + return UpdateCollection((IObjectNode)Node, value, index); } + return false; + } - protected override IObjectNode GetTargetNode() - { - return (Node as IMemberNode)?.Target; - } + protected override object RetrieveValue() + { + return Node.Retrieve(); } - internal class DynamicIndexedNode : DynamicNode + protected override IObjectNode? GetTargetNode() { - private readonly NodeIndex index; + return (Node as IMemberNode)?.Target; + } +} - internal DynamicIndexedNode(IGraphNode node, NodeIndex index) - : base(node) - { - this.index = index; - } +internal class DynamicIndexedNode : DynamicNode +{ + private readonly NodeIndex index; - public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result) - { - var targetNode = GetTargetNode(); - if (indexes.Length == 1 && targetNode != null) - { - var nextIndex = new NodeIndex(indexes[0]); - if (IsIndexExisting(targetNode, nextIndex)) - { - result = new DynamicIndexedNode(targetNode, nextIndex); - return true; - } - } - result = null; - return false; - } + internal DynamicIndexedNode(IGraphNode node, NodeIndex index) + : base(node) + { + this.index = index; + } - public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value) + public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object? result) + { + var targetNode = GetTargetNode(); + if (indexes.Length == 1 && targetNode != null) { - var targetNode = GetTargetNode(); - if (indexes.Length == 1 && targetNode != null) + var nextIndex = new NodeIndex(indexes[0]); + if (IsIndexExisting(targetNode, nextIndex)) { - var nextIndex = new NodeIndex(indexes[0]); - return UpdateCollection((IObjectNode)Node, value, nextIndex); + result = new DynamicIndexedNode(targetNode, nextIndex); + return true; } - return false; } + result = null; + return false; + } - protected override object RetrieveValue() + public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object? value) + { + var targetNode = GetTargetNode(); + if (indexes.Length == 1 && targetNode != null) { - return Node.Retrieve(index); + var nextIndex = new NodeIndex(indexes[0]); + return UpdateCollection((IObjectNode)Node, value, nextIndex); } + return false; + } - protected override IObjectNode GetTargetNode() + protected override object RetrieveValue() + { + return Node.Retrieve(index); + } + + protected override IObjectNode? GetTargetNode() + { + var reference = (Node as IObjectNode)?.ItemReferences; + if (Node.IsReference && (reference?.HasIndex(index) ?? false)) { - var reference = (Node as IObjectNode)?.ItemReferences; - if (Node.IsReference && (reference?.HasIndex(index) ?? false)) - { - return reference[index].TargetNode; - } - return null; + return reference[index].TargetNode; } + return null; } } diff --git a/sources/presentation/Stride.Core.Quantum/GraphNodeBase.cs b/sources/presentation/Stride.Core.Quantum/GraphNodeBase.cs index 6fe1053987..f55909b115 100644 --- a/sources/presentation/Stride.Core.Quantum/GraphNodeBase.cs +++ b/sources/presentation/Stride.Core.Quantum/GraphNodeBase.cs @@ -1,118 +1,112 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Stride.Core.Annotations; using Stride.Core.Reflection; using Stride.Core.TypeConverters; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// A base abstract implementation of the interface. +/// +public abstract class GraphNodeBase : IInitializingGraphNode { - /// - /// A base abstract implementation of the interface. - /// - public abstract class GraphNodeBase : IInitializingGraphNode + protected readonly NodeContainer NodeContainer; + + protected GraphNodeBase(NodeContainer nodeContainer, Guid guid, ITypeDescriptor descriptor) { - protected readonly NodeContainer NodeContainer; + if (guid == Guid.Empty) throw new ArgumentException("The guid must be different from Guid.Empty.", nameof(guid)); + NodeContainer = nodeContainer; + Guid = guid; + Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor)); + } - protected GraphNodeBase(NodeContainer nodeContainer, Guid guid, [NotNull] ITypeDescriptor descriptor) - { - if (guid == Guid.Empty) throw new ArgumentException(@"The guid must be different from Guid.Empty.", nameof(guid)); - NodeContainer = nodeContainer; - Guid = guid; - Descriptor = descriptor ?? throw new ArgumentNullException(nameof(descriptor)); - } + /// + public Type Type => Descriptor.Type; - /// - public Type Type => Descriptor.Type; + /// + public ITypeDescriptor Descriptor { get; } - /// - public ITypeDescriptor Descriptor { get; } + /// + public abstract bool IsReference { get; } - /// - public abstract bool IsReference { get; } + /// + public Guid Guid { get; } - /// - public Guid Guid { get; } + /// + protected abstract object? Value { get; } - /// - protected abstract object Value { get; } + /// + /// Gets whether this node has been sealed. + /// + protected bool IsSealed { get; private set; } - /// - /// Gets whether this node has been sealed. - /// - protected bool IsSealed { get; private set; } + /// + public object? Retrieve() => Retrieve(NodeIndex.Empty); - /// - public object Retrieve() => Retrieve(NodeIndex.Empty); + /// + public virtual object? Retrieve(NodeIndex index) + { + return Content.Retrieve(Value, index, Descriptor); + } - /// - public virtual object Retrieve(NodeIndex index) - { - return Content.Retrieve(Value, index, Descriptor); - } + /// + /// Updates this content from one of its member. + /// + /// The new value for this content. + /// new index of the value to update. + /// + /// This method is intended to update a boxed content when one of its member changes. + /// It allows to properly update boxed structs. + /// + protected internal abstract void UpdateFromMember(object newValue, NodeIndex index); - /// - /// Updates this content from one of its member. - /// - /// The new value for this content. - /// new index of the value to update. - /// - /// This method is intended to update a boxed content when one of its member changes. - /// It allows to properly update boxed structs. - /// - protected internal abstract void UpdateFromMember(object newValue, NodeIndex index); - - public static IEnumerable GetIndices([NotNull] IGraphNode node) + public static IEnumerable? GetIndices(IGraphNode node) + { + if (node.Descriptor is CollectionDescriptor collectionDescriptor) { - if (node.Descriptor is CollectionDescriptor collectionDescriptor) + if (node.Descriptor.Category == DescriptorCategory.Set) { - if (node.Descriptor.Category == DescriptorCategory.Set) + var enumerator = (node.Retrieve() as IEnumerable).GetEnumerator(); + NodeIndex[] valueArr = new NodeIndex[collectionDescriptor.GetCollectionCount(node.Retrieve())]; + int i = 0; + while (enumerator.MoveNext()) { - var enumerator = (node.Retrieve() as IEnumerable).GetEnumerator(); - NodeIndex[] valueArr = new NodeIndex[collectionDescriptor.GetCollectionCount(node.Retrieve())]; - int i = 0; - while (enumerator.MoveNext()) - { - valueArr[i++] = new NodeIndex(enumerator.Current); - } - return valueArr; + valueArr[i++] = new NodeIndex(enumerator.Current); } - else - { - return Enumerable.Range(0, collectionDescriptor.GetCollectionCount(node.Retrieve())).Select(x => new NodeIndex(x)); - } - } - else if (node.Descriptor is DictionaryDescriptor dictionaryDescriptor) - { - return dictionaryDescriptor?.GetKeys(node.Retrieve()).Cast().Select(x => new NodeIndex(x)); + return valueArr; } - else if (node.Descriptor is ArrayDescriptor arrayDescriptor) + else { - return Enumerable.Range(0, arrayDescriptor.GetLength(node.Retrieve())).Select(x => new NodeIndex(x)); + return Enumerable.Range(0, collectionDescriptor.GetCollectionCount(node.Retrieve())).Select(x => new NodeIndex(x)); } - return null; } - - /// - /// Seal the node, indicating its construction is finished and that no more children or commands will be added. - /// - public void Seal() + else if (node.Descriptor is DictionaryDescriptor dictionaryDescriptor) { - IsSealed = true; + return dictionaryDescriptor.GetKeys(node.Retrieve()).Cast().Select(x => new NodeIndex(x)); } - - protected static object ConvertValue(object value, Type type) + else if (node.Descriptor is ArrayDescriptor arrayDescriptor) { - if (value == null) - return null; - object convertedValue; - if (!TypeConverterHelper.TryConvert(value, type, out convertedValue)) - throw new InvalidOperationException("Can not convert value to the required type"); - return convertedValue; + return Enumerable.Range(0, arrayDescriptor.GetLength(node.Retrieve())).Select(x => new NodeIndex(x)); } + return null; + } + + /// + /// Seal the node, indicating its construction is finished and that no more children or commands will be added. + /// + public void Seal() + { + IsSealed = true; + } + + protected static object? ConvertValue(object? value, Type type) + { + if (value == null) + return null; + if (!TypeConverterHelper.TryConvert(value, type, out var convertedValue)) + throw new InvalidOperationException("Can not convert value to the required type"); + return convertedValue; } } diff --git a/sources/presentation/Stride.Core.Quantum/GraphNodeChangeListener.cs b/sources/presentation/Stride.Core.Quantum/GraphNodeChangeListener.cs index dd14d9425c..389c881a7c 100644 --- a/sources/presentation/Stride.Core.Quantum/GraphNodeChangeListener.cs +++ b/sources/presentation/Stride.Core.Quantum/GraphNodeChangeListener.cs @@ -1,223 +1,212 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core.Annotations; - -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// An object that tracks the changes in an extended graph of , given a root node. The graph extends to all target nodes +/// referenced by members and collections of the root node, recursively. Nodes can be excluded from the extended graph by providing a custom +/// visitor in . +/// +public class GraphNodeChangeListener : INotifyNodeValueChange, INotifyNodeItemChange, IDisposable { + private readonly IGraphNode rootNode; + protected readonly HashSet RegisteredNodes = []; + /// - /// An object that tracks the changes in an extended graph of , given a root node. The graph extends to all target nodes - /// referenced by members and collections of the root node, recursively. Nodes can be excluded from the extended graph by providing a custom - /// visitor in . + /// Initializes a new instance of the class. /// - public class GraphNodeChangeListener : INotifyNodeValueChange, INotifyNodeItemChange, IDisposable + /// The root node of the extended graph to listen to. + public GraphNodeChangeListener(IGraphNode rootNode) { - private readonly IGraphNode rootNode; - protected readonly HashSet RegisteredNodes = new HashSet(); - - /// - /// Initializes a new instance of the class. - /// - /// The root node of the extended graph to listen to. - public GraphNodeChangeListener(IGraphNode rootNode) - { - this.rootNode = rootNode; - } + this.rootNode = rootNode; + } - /// - /// Raised before the value of a member node referenced by the related root node changes. - /// - public event EventHandler ValueChanging; - - /// - /// Raised after the value of a member node referenced by the related root node has changed. - /// - public event EventHandler ValueChanged; - - /// - /// Raised before an item of a collection node referenced by the related root node changes. - /// - public event EventHandler ItemChanging; - - /// - /// Raised after an item of a collection node referenced by the related root node has changed. - /// - public event EventHandler ItemChanged; - - /// - /// Initializes the node listener. - /// - public void Initialize() - { - RegisterAllNodes(); - } + /// + /// Raised before the value of a member node referenced by the related root node changes. + /// + public event EventHandler? ValueChanging; - /// - public void Dispose() - { - var visitor = CreateVisitor(); - visitor.Visiting += (node, path) => UnregisterNode(node); - visitor.Visit(rootNode); - } + /// + /// Raised after the value of a member node referenced by the related root node has changed. + /// + public event EventHandler? ValueChanged; - /// - /// Creates a proper to visit the graph. - /// - /// A new instance of . - [NotNull] - protected virtual GraphVisitorBase CreateVisitor() - { - return new GraphVisitorBase(); - } + /// + /// Raised before an item of a collection node referenced by the related root node changes. + /// + public event EventHandler? ItemChanging; + + /// + /// Raised after an item of a collection node referenced by the related root node has changed. + /// + public event EventHandler? ItemChanged; + + /// + /// Initializes the node listener. + /// + public void Initialize() + { + RegisterAllNodes(); + } + + /// + public void Dispose() + { + var visitor = CreateVisitor(); + visitor.Visiting += (node, _) => UnregisterNode(node); + visitor.Visit(rootNode); + } + + /// + /// Creates a proper to visit the graph. + /// + /// A new instance of . + protected virtual GraphVisitorBase CreateVisitor() + { + return new GraphVisitorBase(); + } - protected virtual bool RegisterNode(IGraphNode node) + protected virtual bool RegisterNode(IGraphNode node) + { + // A node can be registered multiple times when it is referenced via multiple paths + if (RegisteredNodes.Add(node)) { - // A node can be registered multiple times when it is referenced via multiple paths - if (RegisteredNodes.Add(node)) + ((IGraphNodeInternal)node).PrepareChange += ContentPrepareChange; + ((IGraphNodeInternal)node).FinalizeChange += ContentFinalizeChange; + if (node is IMemberNode memberNode) { - ((IGraphNodeInternal)node).PrepareChange += ContentPrepareChange; - ((IGraphNodeInternal)node).FinalizeChange += ContentFinalizeChange; - var memberNode = node as IMemberNode; - if (memberNode != null) - { - memberNode.ValueChanging += OnValueChanging; - memberNode.ValueChanged += OnValueChanged; - } - var objectNode = node as IObjectNode; - if (objectNode != null) - { - objectNode.ItemChanging += OnItemChanging; - objectNode.ItemChanged += OnItemChanged; - } - return true; + memberNode.ValueChanging += OnValueChanging; + memberNode.ValueChanged += OnValueChanged; } - - return false; + if (node is IObjectNode objectNode) + { + objectNode.ItemChanging += OnItemChanging; + objectNode.ItemChanged += OnItemChanged; + } + return true; } - protected virtual bool UnregisterNode(IGraphNode node) + return false; + } + + protected virtual bool UnregisterNode(IGraphNode node) + { + if (RegisteredNodes.Remove(node)) { - if (RegisteredNodes.Remove(node)) + ((IGraphNodeInternal)node).PrepareChange -= ContentPrepareChange; + ((IGraphNodeInternal)node).FinalizeChange -= ContentFinalizeChange; + if (node is IMemberNode memberNode) { - ((IGraphNodeInternal)node).PrepareChange -= ContentPrepareChange; - ((IGraphNodeInternal)node).FinalizeChange -= ContentFinalizeChange; - var memberNode = node as IMemberNode; - if (memberNode != null) - { - memberNode.ValueChanging -= OnValueChanging; - memberNode.ValueChanged -= OnValueChanged; - } - var objectNode = node as IObjectNode; - if (objectNode != null) - { - objectNode.ItemChanging -= OnItemChanging; - objectNode.ItemChanged -= OnItemChanged; - } - return true; + memberNode.ValueChanging -= OnValueChanging; + memberNode.ValueChanged -= OnValueChanged; + } + if (node is IObjectNode objectNode) + { + objectNode.ItemChanging -= OnItemChanging; + objectNode.ItemChanged -= OnItemChanged; } - return false; + return true; } + return false; + } - private void RegisterAllNodes() - { - var visitor = CreateVisitor(); - visitor.Visiting += (node, path) => RegisterNode(node); - visitor.Visit(rootNode); - } + private void RegisterAllNodes() + { + var visitor = CreateVisitor(); + visitor.Visiting += (node, _) => RegisterNode(node); + visitor.Visit(rootNode); + } - private void ContentPrepareChange(object sender, [NotNull] INodeChangeEventArgs e) + private void ContentPrepareChange(object? sender, INodeChangeEventArgs e) + { + var node = e.Node; + var visitor = CreateVisitor(); + visitor.Visiting += (node1, _) => UnregisterNode(node1); + switch (e.ChangeType) { - var node = e.Node; - var visitor = CreateVisitor(); - visitor.Visiting += (node1, path) => UnregisterNode(node1); - switch (e.ChangeType) - { - case ContentChangeType.ValueChange: - case ContentChangeType.CollectionUpdate: - // The changed node itself is still valid, we don't want to unregister it - visitor.SkipRootNode = true; - visitor.Visit(node); - // TODO: In case of CollectionUpdate we could probably visit only the target node of the corresponding index - break; - case ContentChangeType.CollectionRemove: - if (node.IsReference && e.OldValue != null) + case ContentChangeType.ValueChange: + case ContentChangeType.CollectionUpdate: + // The changed node itself is still valid, we don't want to unregister it + visitor.SkipRootNode = true; + visitor.Visit(node); + // TODO: In case of CollectionUpdate we could probably visit only the target node of the corresponding index + break; + case ContentChangeType.CollectionRemove: + if (node.IsReference && e.OldValue != null) + { + var removedNode = (node as IObjectNode)?.ItemReferences[((ItemChangeEventArgs)e).Index].TargetNode; + if (removedNode != null) { - var removedNode = (node as IObjectNode)?.ItemReferences[((ItemChangeEventArgs)e).Index].TargetNode; - if (removedNode != null) - { - // TODO: review this - visitor.Visit(removedNode, node as MemberNode); - } + // TODO: review this + visitor.Visit(removedNode, node as MemberNode); } - break; - } + } + break; } + } - private void ContentFinalizeChange(object sender, [NotNull] INodeChangeEventArgs e) + private void ContentFinalizeChange(object? sender, INodeChangeEventArgs e) + { + var visitor = CreateVisitor(); + visitor.Visiting += (node, _) => RegisterNode(node); + switch (e.ChangeType) { - var visitor = CreateVisitor(); - visitor.Visiting += (node, path) => RegisterNode(node); - switch (e.ChangeType) - { - case ContentChangeType.ValueChange: - case ContentChangeType.CollectionUpdate: - // The changed node itself is still valid, we don't want to re-register it - visitor.SkipRootNode = true; - visitor.Visit(e.Node); - // TODO: In case of CollectionUpdate we could probably visit only the target node of the corresponding index - break; - case ContentChangeType.CollectionAdd: - if (e.Node.IsReference && e.NewValue != null) + case ContentChangeType.ValueChange: + case ContentChangeType.CollectionUpdate: + // The changed node itself is still valid, we don't want to re-register it + visitor.SkipRootNode = true; + visitor.Visit(e.Node); + // TODO: In case of CollectionUpdate we could probably visit only the target node of the corresponding index + break; + case ContentChangeType.CollectionAdd: + if (e.Node.IsReference && e.NewValue != null) + { + var objectNode = (IObjectNode)e.Node; + IGraphNode? addedNode; + NodeIndex index; + var arg = (ItemChangeEventArgs)e; + if (!arg.Index.IsEmpty) { - var objectNode = (IObjectNode)e.Node; - IGraphNode addedNode; - NodeIndex index; - var arg = (ItemChangeEventArgs)e; - if (!arg.Index.IsEmpty) - { - index = arg.Index; - addedNode = objectNode.ItemReferences[arg.Index].TargetNode; - } - else - { - // TODO: review this - var reference = objectNode.ItemReferences.First(x => x.TargetNode.Retrieve() == e.NewValue); - index = reference.Index; - addedNode = reference.TargetNode; - } - - if (addedNode != null && visitor.ShouldVisitTargetItem(objectNode, index)) - { - var path = new GraphNodePath(e.Node); - path.PushIndex(index); - visitor.Visit(addedNode, e.Node as MemberNode, path); - } + index = arg.Index; + addedNode = objectNode.ItemReferences[arg.Index].TargetNode; + } + else + { + // TODO: review this + var reference = objectNode.ItemReferences.First(x => x.TargetNode!.Retrieve() == e.NewValue); + index = reference.Index; + addedNode = reference.TargetNode; } - break; - } - } - private void OnValueChanging(object sender, MemberNodeChangeEventArgs e) - { - ValueChanging?.Invoke(sender, e); + if (addedNode != null && visitor.ShouldVisitTargetItem(objectNode, index)) + { + var path = new GraphNodePath(e.Node); + path.PushIndex(index); + visitor.Visit(addedNode, e.Node as MemberNode, path); + } + } + break; } + } - private void OnValueChanged(object sender, MemberNodeChangeEventArgs e) - { - ValueChanged?.Invoke(sender, e); - } + private void OnValueChanging(object? sender, MemberNodeChangeEventArgs e) + { + ValueChanging?.Invoke(sender, e); + } - private void OnItemChanging(object sender, ItemChangeEventArgs e) - { - ItemChanging?.Invoke(sender, e); - } + private void OnValueChanged(object? sender, MemberNodeChangeEventArgs e) + { + ValueChanged?.Invoke(sender, e); + } - private void OnItemChanged(object sender, ItemChangeEventArgs e) - { - ItemChanged?.Invoke(sender, e); - } + private void OnItemChanging(object? sender, ItemChangeEventArgs e) + { + ItemChanging?.Invoke(sender, e); + } + + private void OnItemChanged(object? sender, ItemChangeEventArgs e) + { + ItemChanged?.Invoke(sender, e); } } diff --git a/sources/presentation/Stride.Core.Quantum/GraphNodeLinker.cs b/sources/presentation/Stride.Core.Quantum/GraphNodeLinker.cs index b495cae38e..1e4cea9ca6 100644 --- a/sources/presentation/Stride.Core.Quantum/GraphNodeLinker.cs +++ b/sources/presentation/Stride.Core.Quantum/GraphNodeLinker.cs @@ -1,195 +1,191 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using Stride.Core.Annotations; using Stride.Core.Quantum.References; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// A class that is capable of visiting two object hierarchies and "link" corresponding nodes together. +/// +/// +/// One of the two hierarchies is considered to be the "source" for the linker. This hierarchy is visited by the , +/// and for each node, it will try to find a corresponding node in the other "target" hierarchy. +/// By deriving this class, the way this correspondance between two nodes is established can be customized. +/// +public class GraphNodeLinker { - /// - /// A class that is capable of visiting two object hierarchies and "link" corresponding nodes together. - /// - /// - /// One of the two hierarchies is considered to be the "source" for the linker. This hierarchy is visited by the , - /// and for each node, it will try to find a corresponding node in the other "target" hierarchy. - /// By deriving this class, the way this correspondance between two nodes is established can be customized. - /// - public class GraphNodeLinker + private sealed class GraphNodeLinkerVisitor : GraphVisitorBase { - private sealed class GraphNodeLinkerVisitor : GraphVisitorBase - { - private readonly GraphNodeLinker linker; - internal readonly Dictionary VisitedLinks = new Dictionary(); + private readonly GraphNodeLinker linker; + internal readonly Dictionary VisitedLinks = []; - public GraphNodeLinkerVisitor(GraphNodeLinker linker) - { - this.linker = linker; - } + public GraphNodeLinkerVisitor(GraphNodeLinker linker) + { + this.linker = linker; + } - public void Reset([NotNull] IGraphNode sourceNode, IGraphNode targetNode) - { - VisitedLinks.Clear(); - VisitedLinks.Add(sourceNode, targetNode); - } + public void Reset(IGraphNode sourceNode, IGraphNode targetNode) + { + VisitedLinks.Clear(); + VisitedLinks.Add(sourceNode, targetNode); + } - protected override void VisitNode(IGraphNode node) - { - var targetNode = linker.FindTarget(node); - // Override the target node, in case FindTarget returned a different one. - VisitedLinks[node] = targetNode; - linker.LinkNodes(node, targetNode); - base.VisitNode(node); - } + protected override void VisitNode(IGraphNode node) + { + var targetNode = linker.FindTarget(node); + // Override the target node, in case FindTarget returned a different one. + VisitedLinks[node] = targetNode; + linker.LinkNodes(node, targetNode); + base.VisitNode(node); + } - protected override void VisitChildren(IObjectNode node) + protected override void VisitChildren(IObjectNode node) + { + if (VisitedLinks.TryGetValue(node, out var targetNodeParent)) { - if (VisitedLinks.TryGetValue(node, out IGraphNode targetNodeParent)) + var objNode = targetNodeParent as IObjectNode; + var members = node.Members; + if (members is List asList) { - var objNode = targetNodeParent as IObjectNode; - var members = node.Members; - if (members is List asList) - { - foreach (var child in asList) - { - VisitedLinks.Add(child, objNode?.TryGetChild(child.Name)); - } - } - else if(members is Dictionary.ValueCollection asVCol) + foreach (var child in asList) { - foreach (var child in asVCol) - { - VisitedLinks.Add(child, objNode?.TryGetChild(child.Name)); - } + VisitedLinks.Add(child, objNode?.TryGetChild(child.Name)); } - else + } + else if(members is Dictionary.ValueCollection asVCol) + { + foreach (var child in asVCol) { - foreach (var child in members) - { - VisitedLinks.Add(child, objNode?.TryGetChild(child.Name)); - } + VisitedLinks.Add(child, objNode?.TryGetChild(child.Name)); } } - base.VisitChildren(node); - } - - protected override void VisitReference(IGraphNode referencer, ObjectReference reference) - { - if (reference.TargetNode != null) + else { - // Prevent re-entrancy in the same object - if (VisitedLinks.ContainsKey(reference.TargetNode)) - return; - - if (VisitedLinks.TryGetValue(referencer, out IGraphNode targetNode)) + foreach (var child in members) { - ObjectReference targetReference = null; - if (targetNode != null) - targetReference = linker.FindTargetReference(referencer, targetNode, reference); - - VisitedLinks.Add(reference.TargetNode, targetReference?.TargetNode); + VisitedLinks.Add(child, objNode?.TryGetChild(child.Name)); } } - base.VisitReference(referencer, reference); } + base.VisitChildren(node); + } - protected internal override bool ShouldVisitMemberTarget(IMemberNode memberNode) + protected override void VisitReference(IGraphNode referencer, ObjectReference reference) + { + if (reference.TargetNode != null) { - return linker.ShouldVisitMemberTarget(memberNode) && base.ShouldVisitMemberTarget(memberNode); - } + // Prevent re-entrancy in the same object + if (VisitedLinks.ContainsKey(reference.TargetNode)) + return; - protected internal override bool ShouldVisitTargetItem(IObjectNode collectionNode, NodeIndex index) - { - return linker.ShouldVisitTargetItem(collectionNode, index) && base.ShouldVisitTargetItem(collectionNode, index); + if (VisitedLinks.TryGetValue(referencer, out var targetNode)) + { + ObjectReference? targetReference = null; + if (targetNode != null) + targetReference = linker.FindTargetReference(referencer, targetNode, reference); + + VisitedLinks.Add(reference.TargetNode, targetReference?.TargetNode); + } } + base.VisitReference(referencer, reference); } - private readonly GraphNodeLinkerVisitor visitor; - - /// - /// Initializes a new instance of the class. - /// - public GraphNodeLinker() + protected internal override bool ShouldVisitMemberTarget(IMemberNode memberNode) { - visitor = new GraphNodeLinkerVisitor(this); + return linker.ShouldVisitMemberTarget(memberNode) && base.ShouldVisitMemberTarget(memberNode); } - /// - /// Gets or sets the action to execute when two nodes should be linked. - /// - public Action LinkAction { get; set; } - - /// - /// Visits and links the node of two different object hierarchies. - /// - /// The root node of the "source" object to link. - /// The root node of the "target" object to link. - public void LinkGraph([NotNull] IGraphNode sourceNode, IGraphNode targetNode) + protected internal override bool ShouldVisitTargetItem(IObjectNode collectionNode, NodeIndex index) { - visitor.Reset(sourceNode, targetNode); - visitor.Visit(sourceNode); + return linker.ShouldVisitTargetItem(collectionNode, index) && base.ShouldVisitTargetItem(collectionNode, index); } + } - protected virtual bool ShouldVisitMemberTarget(IMemberNode member) - { - return true; - } + private readonly GraphNodeLinkerVisitor visitor; - protected virtual bool ShouldVisitTargetItem(IObjectNode collectionNode, NodeIndex index) - { - return true; - } + /// + /// Initializes a new instance of the class. + /// + public GraphNodeLinker() + { + visitor = new GraphNodeLinkerVisitor(this); + } - /// - /// Links a single node of the "source" hierarchy to a node of the "target" hierarchy. - /// - /// The node from the source hierarchy. Cannot be null. - /// The node from the target hierarchy. Can be null. - /// The source node is null. - /// The default implementation will simply invoke . - protected virtual void LinkNodes([NotNull] IGraphNode sourceNode, IGraphNode targetNode) - { - if (sourceNode == null) throw new ArgumentNullException(nameof(sourceNode)); - LinkAction?.Invoke(sourceNode, targetNode); - } + /// + /// Gets or sets the action to execute when two nodes should be linked. + /// + public Action? LinkAction { get; set; } - /// - /// Finds the target of a given source node. - /// - /// The source node for which to find a target node. Cannot be null. - /// A node in the target hierarchy that corresponds to the given node. - /// - /// The default implementation looks for node with the same names and or the same types of reference. - /// This method can return null if there is no matching node in the target hierarchy. - /// - [CanBeNull] - protected virtual IGraphNode FindTarget([NotNull] IGraphNode sourceNode) - { - return visitor.VisitedLinks.TryGetValue(sourceNode, out IGraphNode targetNode) ? targetNode : null; - } + /// + /// Visits and links the node of two different object hierarchies. + /// + /// The root node of the "source" object to link. + /// The root node of the "target" object to link. + public void LinkGraph(IGraphNode sourceNode, IGraphNode targetNode) + { + visitor.Reset(sourceNode, targetNode); + visitor.Visit(sourceNode); + } - /// - /// Finds a reference in a target node to correspond with the given reference from the source node. - /// - /// The source node that contains the reference. - /// The target node in which to look for a matching reference. - /// The reference in the source node for which to look for a correspondance in the target node. - /// A reference of the target node corresponding to the given reference in the source node, or null if there is no match. - /// - /// The source reference can either be directly the of the source node if this reference is - /// an , or one of the reference contained inside if this reference - /// is a . The property indicates the index of the reference in this case. - /// The default implementation returns a reference in the target node that matches the index of the source reference, if available. - /// - // TODO: turn back protected! - [CanBeNull] - public virtual ObjectReference FindTargetReference(IGraphNode sourceNode, IGraphNode targetNode, [NotNull] ObjectReference sourceReference) - { - if (sourceNode is IMemberNode) - return (targetNode as IMemberNode)?.TargetReference; + protected virtual bool ShouldVisitMemberTarget(IMemberNode member) + { + return true; + } - var targetReference = (targetNode as IObjectNode)?.ItemReferences; - return targetReference != null && targetReference.HasIndex(sourceReference.Index) ? targetReference[sourceReference.Index] : null; - } + protected virtual bool ShouldVisitTargetItem(IObjectNode collectionNode, NodeIndex index) + { + return true; + } + + /// + /// Links a single node of the "source" hierarchy to a node of the "target" hierarchy. + /// + /// The node from the source hierarchy. Cannot be null. + /// The node from the target hierarchy. Can be null. + /// The source node is null. + /// The default implementation will simply invoke . + protected virtual void LinkNodes(IGraphNode sourceNode, IGraphNode targetNode) + { + ArgumentNullException.ThrowIfNull(sourceNode); + LinkAction?.Invoke(sourceNode, targetNode); + } + + /// + /// Finds the target of a given source node. + /// + /// The source node for which to find a target node. Cannot be null. + /// A node in the target hierarchy that corresponds to the given node. + /// + /// The default implementation looks for node with the same names and or the same types of reference. + /// This method can return null if there is no matching node in the target hierarchy. + /// + protected virtual IGraphNode? FindTarget(IGraphNode sourceNode) + { + return visitor.VisitedLinks.TryGetValue(sourceNode, out var targetNode) ? targetNode : null; + } + + /// + /// Finds a reference in a target node to correspond with the given reference from the source node. + /// + /// The source node that contains the reference. + /// The target node in which to look for a matching reference. + /// The reference in the source node for which to look for a correspondance in the target node. + /// A reference of the target node corresponding to the given reference in the source node, or null if there is no match. + /// + /// The source reference can either be directly the of the source node if this reference is + /// an , or one of the reference contained inside if this reference + /// is a . The property indicates the index of the reference in this case. + /// The default implementation returns a reference in the target node that matches the index of the source reference, if available. + /// + // TODO: turn back protected! + public virtual ObjectReference? FindTargetReference(IGraphNode sourceNode, IGraphNode targetNode, ObjectReference sourceReference) + { + if (sourceNode is IMemberNode) + return (targetNode as IMemberNode)?.TargetReference; + + var targetReference = (targetNode as IObjectNode)?.ItemReferences; + return targetReference?.HasIndex(sourceReference.Index) == true ? targetReference[sourceReference.Index] : null; } } diff --git a/sources/presentation/Stride.Core.Quantum/GraphNodePath.cs b/sources/presentation/Stride.Core.Quantum/GraphNodePath.cs index 7c74da1f69..a52b513a9f 100644 --- a/sources/presentation/Stride.Core.Quantum/GraphNodePath.cs +++ b/sources/presentation/Stride.Core.Quantum/GraphNodePath.cs @@ -1,486 +1,462 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; using System.Diagnostics.Contracts; -using System.Linq; -using Stride.Core.Annotations; using Stride.Core.Reflection; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// A class describing the path of a node, relative to a root node. The path can cross references, array, etc. +/// +/// This class is immutable. +public sealed class GraphNodePath : IEnumerable, IEquatable { /// - /// A class describing the path of a node, relative to a root node. The path can cross references, array, etc. + /// An enum that describes the type of an item of a model node path. /// - /// This class is immutable. - public sealed class GraphNodePath : IEnumerable, IEquatable + public enum ElementType { /// - /// An enum that describes the type of an item of a model node path. + /// This item is a member (child) of the previous node + /// + Member, + /// + /// This item is the target of the object reference of the previous node. /// - public enum ElementType + Target, + /// + /// This item is the target of a enumerable reference of the previous node corresponding to the associated index. + /// + Index, + } + + /// + /// A structure that represents an element of the path. + /// + public readonly struct NodePathElement : IEquatable + { + public readonly ElementType Type; + public readonly string? Name; + public readonly NodeIndex Index; + public readonly Guid Guid; + + private NodePathElement(string value) { - /// - /// This item is a member (child) of the previous node - /// - Member, - /// - /// This item is the target of the object reference of the previous node. - /// - Target, - /// - /// This item is the target of a enumerable reference of the previous node corresponding to the associated index. - /// - Index, + Name = value; + Index = NodeIndex.Empty; + Guid = Guid.Empty; + Type = ElementType.Member; } - /// - /// A structure that represents an element of the path. - /// - public struct NodePathElement : IEquatable + private NodePathElement(NodeIndex value) { - public readonly ElementType Type; - public readonly string Name; - public readonly NodeIndex Index; - public readonly Guid Guid; + Name = null; + Index = value; + Guid = Guid.Empty; + Type = ElementType.Index; + } - private NodePathElement(string value) - { - Name = value; - Index = NodeIndex.Empty; - Guid = Guid.Empty; - Type = ElementType.Member; - } + private NodePathElement(Guid value) + { + Name = null; + Index = NodeIndex.Empty; + Guid = value; + Type = ElementType.Target; + } - private NodePathElement(NodeIndex value) - { - Name = null; - Index = value; - Guid = Guid.Empty; - Type = ElementType.Index; - } + public static NodePathElement CreateMember(string name) + { + ArgumentNullException.ThrowIfNull(name); + return new NodePathElement(name); + } - private NodePathElement(Guid value) - { - Name = null; - Index = NodeIndex.Empty; - Guid = value; - Type = ElementType.Target; - } + public static NodePathElement CreateTarget() + { + // We use a guid to allow equality test to fail between two different instances returned by CreateTarget + return new NodePathElement(Guid.NewGuid()); + } - public static NodePathElement CreateMember([NotNull] string name) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - return new NodePathElement(name); - } + public static NodePathElement CreateIndex(NodeIndex index) + { + return new NodePathElement(index); + } - public static NodePathElement CreateTarget() - { - // We use a guid to allow equality test to fail between two different instances returned by CreateTarget - return new NodePathElement(Guid.NewGuid()); - } + public readonly bool EqualsInPath(NodePathElement other) + { + return (Type == ElementType.Target && other.Type == ElementType.Target) || Equals(other); + } - public static NodePathElement CreateIndex(NodeIndex index) - { - return new NodePathElement(index); - } + public readonly bool Equals(NodePathElement other) + { + return Type == other.Type && Equals(Guid, other.Guid) && Equals(Index, other.Index) && Equals(Name, other.Name); + } - public bool EqualsInPath(NodePathElement other) - { - return Type == ElementType.Target && other.Type == ElementType.Target || Equals(other); - } + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is NodePathElement nodePathElement && Equals(nodePathElement); + } - public bool Equals(NodePathElement other) - { - return Type == other.Type && Equals(Guid, other.Guid) && Equals(Index, other.Index) && Equals(Name, other.Name); - } + public override readonly int GetHashCode() + { + return HashCode.Combine(Type, Name, Index, Guid); + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - return obj is NodePathElement && Equals((NodePathElement)obj); - } + public static bool operator ==(NodePathElement left, NodePathElement right) + { + return left.Equals(right); + } - public override int GetHashCode() - { - unchecked - { - var hashCode = (int)Type; - hashCode = (hashCode * 397) ^ (Name?.GetHashCode() ?? 0); - hashCode = (hashCode * 397) ^ Index.GetHashCode(); - hashCode = (hashCode * 397) ^ Guid.GetHashCode(); - return hashCode; - } - } + public static bool operator !=(NodePathElement left, NodePathElement right) + { + return !left.Equals(right); + } - public static bool operator ==(NodePathElement left, NodePathElement right) + public override readonly string ToString() + { + return Type switch { - return left.Equals(right); - } + ElementType.Member => $".{Name}", + ElementType.Target => "-> (Target)", + ElementType.Index => $"[{Index}]", + _ => throw new ArgumentOutOfRangeException(), + }; + } + } - public static bool operator !=(NodePathElement left, NodePathElement right) - { - return !left.Equals(right); - } + /// + /// An enumerator for + /// + private struct GraphNodePathEnumerator : IEnumerator + { + private readonly GraphNodePath path; + private IGraphNode? current; + private int index = -1; - public override string ToString() - { - switch (Type) - { - case ElementType.Member: - return $".{Name}"; - case ElementType.Target: - return "-> (Target)"; - case ElementType.Index: - return $"[{Index}]"; - default: - throw new ArgumentOutOfRangeException(); - } - } + public GraphNodePathEnumerator(GraphNodePath path) + { + this.path = path; } - /// - /// An enumerator for - /// - private class GraphNodePathEnumerator : IEnumerator + public void Dispose() { - private readonly GraphNodePath path; - private int index = -1; + index = -1; + } - public GraphNodePathEnumerator(GraphNodePath path) - { - this.path = path; - } + public bool MoveNext() + { + if (index == path.path.Count) + return false; - public void Dispose() + if (index == -1) { - index = -1; + current = path.RootNode; } - - public bool MoveNext() + else { - if (index == path.path.Count) - return false; - - if (index == -1) + var element = path.path[index]; + current = element.Type switch { - Current = path.RootNode; - } - else - { - var element = path.path[index]; - switch (element.Type) - { - case ElementType.Member: - Current = ((IObjectNode)Current)[element.Name]; - break; - case ElementType.Target: - Current = ((IMemberNode)Current).Target; - break; - case ElementType.Index: - Current = ((IObjectNode)Current).ItemReferences[element.Index].TargetNode; - break; - default: - throw new ArgumentOutOfRangeException(); - } - } - ++index; - - // If a node that is not the last one is null, we cannot process the path. - if (Current == null && index < path.path.Count) - throw new InvalidOperationException("A node of the path is null but is not the last node."); - - return true; - } - - public void Reset() - { - index = -1; + ElementType.Member => ((IObjectNode)Current)[element.Name!], + ElementType.Target => ((IMemberNode)Current).Target, + ElementType.Index => ((IObjectNode)Current).ItemReferences?[element.Index].TargetNode, + _ => throw new ArgumentOutOfRangeException(), + }; } + ++index; - public IGraphNode Current { get; private set; } + // If a node that is not the last one is null, we cannot process the path. + if (current is null && index < path.path.Count) + throw new InvalidOperationException("A node of the path is null but is not the last node."); - object IEnumerator.Current => Current; + return true; } - private const int DefaultCapacity = 16; - private readonly List path; - - private GraphNodePath(IGraphNode rootNode, int defaultCapacity) + public void Reset() { - RootNode = rootNode; - path = new List(defaultCapacity); + index = -1; } - /// - /// Initializes a new instance of the with the given root node. - /// - /// The root node to represent with this instance of . - public GraphNodePath(IGraphNode rootNode) - : this(rootNode, DefaultCapacity) - { - } + public readonly IGraphNode Current => current!; - /// - /// Gets the root node of this path. - /// - public IGraphNode RootNode { get; } + readonly object IEnumerator.Current => current!; + } - /// - /// Gets whether this path is empty. - /// - /// An empty path resolves to . - public bool IsEmpty => path.Count == 0; + private const int DefaultCapacity = 16; + private readonly List path; - /// - /// Gets the number of items in this path. - /// - public int Count => path.Count; + private GraphNodePath(IGraphNode rootNode, int defaultCapacity) + { + RootNode = rootNode; + path = new List(defaultCapacity); + } - /// - /// Gets the items composing this path. - /// - public IReadOnlyList Path => path; + /// + /// Initializes a new instance of the with the given root node. + /// + /// The root node to represent with this instance of . + public GraphNodePath(IGraphNode rootNode) + : this(rootNode, DefaultCapacity) + { + } - /// - public IEnumerator GetEnumerator() - { - return new GraphNodePathEnumerator(this); - } + /// + /// Gets the root node of this path. + /// + public IGraphNode RootNode { get; } - /// - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + /// + /// Gets whether this path is empty. + /// + /// An empty path resolves to . + public bool IsEmpty => path.Count == 0; - /// - public bool Equals(GraphNodePath other) - { - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - if (!Equals(RootNode, other.RootNode) || path.Count != other.path.Count) return false; + /// + /// Gets the number of items in this path. + /// + public int Count => path.Count; - for (var i = 0; i < path.Count; ++i) - { - if (!path[i].EqualsInPath(other.path[i])) - return false; - } - return true; - } + /// + /// Gets the items composing this path. + /// + public IReadOnlyList Path => path; - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) return false; - if (ReferenceEquals(this, obj)) return true; - return Equals(obj as GraphNodePath); - } + /// + public IEnumerator GetEnumerator() + { + return new GraphNodePathEnumerator(this); + } - /// - public override int GetHashCode() - { - // Note: the only invariant is the root node. - return RootNode?.GetHashCode() ?? 0; - } + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public static bool operator ==(GraphNodePath left, GraphNodePath right) - { - return Equals(left, right); - } + /// + public bool Equals(GraphNodePath? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + if (!Equals(RootNode, other.RootNode) || path.Count != other.path.Count) return false; - public static bool operator !=(GraphNodePath left, GraphNodePath right) + for (var i = 0; i < path.Count; ++i) { - return !Equals(left, right); + if (!path[i].EqualsInPath(other.path[i])) + return false; } + return true; + } - /// - public override string ToString() - { - return "(root)" + (path.Count > 0 ? path.Select(x => x.ToString()).Aggregate((current, next) => current + next) : ""); - } + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) return true; + return obj is GraphNodePath path && Equals(path); + } - /// - /// Retrieves the node targeted by this path. - /// - /// - [Pure, NotNull] - public IGraphNode GetNode() => this.Last(); + /// + public override int GetHashCode() + { + // Note: the only invariant is the root node. + return RootNode?.GetHashCode() ?? 0; + } - /// - /// Retrieves an accessor to the target of the path. - /// - /// - [Pure] - public NodeAccessor GetAccessor() - { - if (path.Count == 0) - return new NodeAccessor(RootNode, NodeIndex.Empty); + public static bool operator ==(GraphNodePath left, GraphNodePath right) + { + return Equals(left, right); + } - var lastItem = path[path.Count - 1]; - return lastItem.Type == ElementType.Index ? new NodeAccessor(GetParent().GetNode(), lastItem.Index) : new NodeAccessor(GetNode(), NodeIndex.Empty); - } + public static bool operator !=(GraphNodePath left, GraphNodePath right) + { + return !Equals(left, right); + } - /// - /// Retrieve the parent path. - /// - /// A new instance representing the parent path. - [Pure, CanBeNull] - public GraphNodePath GetParent() - { - if (IsEmpty) - return null; + /// + public override string ToString() + { + return "(root)" + (path.Count > 0 ? path.Select(x => x.ToString()).Aggregate((current, next) => current + next) : ""); + } - var result = new GraphNodePath(RootNode, path.Count - 1); - for (var i = 0; i < path.Count - 1; ++i) - result.path.Add(path[i]); - return result; - } + /// + /// Retrieves the node targeted by this path. + /// + /// + [Pure] + public IGraphNode GetNode() => this.Last(); - /// - /// Clones this instance of and remap the new instance to a new root node. - /// - /// The root node for the cloned path. - /// A copy of this path with the given node as root node. - [Pure, NotNull] - public GraphNodePath Clone(IGraphNode newRoot) - { - var clone = new GraphNodePath(newRoot, Math.Max(path.Count, DefaultCapacity)); - clone.path.AddRange(path); - return clone; - } + /// + /// Retrieves an accessor to the target of the path. + /// + /// + [Pure] + public NodeAccessor GetAccessor() + { + if (path.Count == 0) + return new NodeAccessor(RootNode, NodeIndex.Empty); - /// - /// Clones this instance of . - /// - /// A copy of this path with the same root node. - [Pure, NotNull] - public GraphNodePath Clone() => Clone(RootNode); + var lastItem = path[^1]; + return lastItem.Type == ElementType.Index ? new NodeAccessor(GetParent()!.GetNode(), lastItem.Index) : new NodeAccessor(GetNode(), NodeIndex.Empty); + } + + /// + /// Retrieve the parent path. + /// + /// A new instance representing the parent path. + [Pure] + public GraphNodePath? GetParent() + { + if (IsEmpty) + return null; + + var result = new GraphNodePath(RootNode, path.Count - 1); + for (var i = 0; i < path.Count - 1; ++i) + result.path.Add(path[i]); + return result; + } + + /// + /// Clones this instance of and remap the new instance to a new root node. + /// + /// The root node for the cloned path. + /// A copy of this path with the given node as root node. + [Pure] + public GraphNodePath Clone(IGraphNode newRoot) + { + var clone = new GraphNodePath(newRoot, Math.Max(path.Count, DefaultCapacity)); + clone.path.AddRange(path); + return clone; + } - public void PushMember(string memberName) => PushElement(memberName, ElementType.Member); + /// + /// Clones this instance of . + /// + /// A copy of this path with the same root node. + [Pure] + public GraphNodePath Clone() => Clone(RootNode); - public void PushTarget() => PushElement(null, ElementType.Target); + public void PushMember(string memberName) => PushElement(memberName, ElementType.Member); - public void PushIndex(NodeIndex index) => PushElement(index, ElementType.Index); + public void PushTarget() => PushElement(null, ElementType.Target); - public void Pop() => path.RemoveAt(path.Count - 1); + public void PushIndex(NodeIndex index) => PushElement(index, ElementType.Index); - // TODO: Switch to tuple return as soon as we have C# 7.0 - [NotNull] - public static GraphNodePath From(IGraphNode root, [NotNull] MemberPath memberPath, out NodeIndex index) - { - if (memberPath == null) throw new ArgumentNullException(nameof(memberPath)); + public void Pop() => path.RemoveAt(path.Count - 1); - var result = new GraphNodePath(root); - index = NodeIndex.Empty; - var memberPathItems = memberPath.Decompose(); - for (int i = 0; i < memberPathItems.Count; i++) + // TODO: Switch to tuple return as soon as we have C# 7.0 + public static GraphNodePath From(IGraphNode root, MemberPath memberPath, out NodeIndex index) + { + ArgumentNullException.ThrowIfNull(memberPath); + + var result = new GraphNodePath(root); + index = NodeIndex.Empty; + var memberPathItems = memberPath.Decompose(); + for (int i = 0; i < memberPathItems.Count; i++) + { + var memberPathItem = memberPathItems[i]; + bool lastItem = i == memberPathItems.Count - 1; + if (memberPathItem.MemberDescriptor != null) { - var memberPathItem = memberPathItems[i]; - bool lastItem = i == memberPathItems.Count - 1; - if (memberPathItem.MemberDescriptor != null) - { - result.PushMember(memberPathItem.MemberDescriptor.Name); - } - else if (memberPathItem.GetIndex() != null) - { - var localIndex = new NodeIndex(memberPathItem.GetIndex()); + result.PushMember(memberPathItem.MemberDescriptor.Name); + } + else if (memberPathItem.GetIndex() != null) + { + var localIndex = new NodeIndex(memberPathItem.GetIndex()); - if (lastItem) - { - // If last item, we directly return the index rather than add it to the path - index = localIndex; - } - else - { - result.PushIndex(localIndex); - } + if (lastItem) + { + // If last item, we directly return the index rather than add it to the path + index = localIndex; } - - // Don't apply Target on last item - if (!lastItem) + else { - // If this is a reference, add a target element to the path - var node = result.GetNode(); - var objectReference = (node as IMemberNode)?.TargetReference; - if (objectReference?.TargetNode != null) - { - result.PushTarget(); - } + result.PushIndex(localIndex); } } - return result; - } - - [Pure, NotNull] - public MemberPath ToMemberPath() - { - var memberPath = new MemberPath(); - var node = RootNode; - for (var i = 0; i < path.Count; i++) + // Don't apply Target on last item + if (!lastItem) { - var itemPath = path[i]; - switch (itemPath.Type) + // If this is a reference, add a target element to the path + var node = result.GetNode(); + var objectReference = (node as IMemberNode)?.TargetReference; + if (objectReference?.TargetNode != null) { - case ElementType.Member: - var name = itemPath.Name; - node = ((IObjectNode)node)[name]; - memberPath.Push(((MemberNode)node).MemberDescriptor); - break; - case ElementType.Target: - if (i != path.Count - 1) - { - node = ((IMemberNode)node).Target; - } - break; - case ElementType.Index: - var index = itemPath.Index; - var enumerableReference = ((IObjectNode)node).ItemReferences; - var descriptor = node.Descriptor; - if (descriptor is CollectionDescriptor collectionDescriptor) - { - memberPath.Push(collectionDescriptor, index.Value); - } - else if (descriptor is DictionaryDescriptor dictionaryDescriptor) - { - memberPath.Push(dictionaryDescriptor, index.Value); - } - - if (i != path.Count - 1) - { - var objectRefererence = enumerableReference.Single(x => Equals(x.Index, index)); - node = objectRefererence.TargetNode; - } - break; - default: - throw new ArgumentOutOfRangeException(); + result.PushTarget(); } } - return memberPath; } - private void PushElement(object elementValue, ElementType type) + return result; + } + + [Pure] + public MemberPath ToMemberPath() + { + var memberPath = new MemberPath(); + var node = RootNode; + for (var i = 0; i < path.Count; i++) { - switch (type) + var itemPath = path[i]; + switch (itemPath.Type) { case ElementType.Member: - var name = elementValue as string; - if (name == null) - throw new ArgumentException("The value must be a non-null string when type is ElementType.Member."); - path.Add(NodePathElement.CreateMember(name)); + var name = itemPath.Name!; + node = ((IObjectNode)node!)[name]; + memberPath.Push(((MemberNode)node).MemberDescriptor); break; case ElementType.Target: - if (elementValue != null) - throw new ArgumentException("The value must be null when type is ElementType.Target."); - path.Add(NodePathElement.CreateTarget()); + if (i != path.Count - 1) + { + node = ((IMemberNode)node!).Target; + } break; case ElementType.Index: - if (!(elementValue is NodeIndex)) - throw new ArgumentException("The value must be an Index when type is ElementType.Index."); - path.Add(NodePathElement.CreateIndex((NodeIndex)elementValue)); + var index = itemPath.Index; + var enumerableReference = ((IObjectNode)node!).ItemReferences; + var descriptor = node.Descriptor; + if (descriptor is CollectionDescriptor collectionDescriptor) + { + memberPath.Push(collectionDescriptor, index.Value); + } + else if (descriptor is DictionaryDescriptor dictionaryDescriptor) + { + memberPath.Push(dictionaryDescriptor, index.Value); + } + + if (i != path.Count - 1) + { + var objectRefererence = enumerableReference!.Single(x => Equals(x.Index, index)); + node = objectRefererence.TargetNode; + } break; default: - throw new ArgumentOutOfRangeException(nameof(type)); + throw new ArgumentOutOfRangeException(); } } + return memberPath; + } + + private void PushElement(object? elementValue, ElementType type) + { + switch (type) + { + case ElementType.Member: + if (elementValue is not string name) + throw new ArgumentException("The value must be a non-null string when type is ElementType.Member."); + path.Add(NodePathElement.CreateMember(name)); + break; + case ElementType.Target: + if (elementValue != null) + throw new ArgumentException("The value must be null when type is ElementType.Target."); + path.Add(NodePathElement.CreateTarget()); + break; + case ElementType.Index: + if (!(elementValue is NodeIndex)) + throw new ArgumentException("The value must be an Index when type is ElementType.Index."); + path.Add(NodePathElement.CreateIndex((NodeIndex)elementValue)); + break; + default: + throw new ArgumentOutOfRangeException(nameof(type)); + } } } diff --git a/sources/presentation/Stride.Core.Quantum/GraphVisitorBase.cs b/sources/presentation/Stride.Core.Quantum/GraphVisitorBase.cs index 599f3368fe..7593d38ad5 100644 --- a/sources/presentation/Stride.Core.Quantum/GraphVisitorBase.cs +++ b/sources/presentation/Stride.Core.Quantum/GraphVisitorBase.cs @@ -1,199 +1,195 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core.Annotations; + using Stride.Core.Quantum.References; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// A class that visits all node referenced by a given root node, including members, targets of members, and targets of collection items. +/// The nodes to visit can be controlled by implementing and . +/// +public class GraphVisitorBase { + private readonly HashSet visitedNodes = []; + + /// + /// The current path in the visit. This path is mutable and must be cloned if used out of the visitor. + /// + protected GraphNodePath CurrentPath = null!; + + /// + /// Gets or sets whether to skip the root node passed to when raising the event. + /// + public bool SkipRootNode { get; set; } + + /// + /// Gets the root node of the current visit. + /// + protected IGraphNode? RootNode { get; private set; } + + /// + /// Raised when a node is visited. + /// + public event Action? Visiting; + + /// + /// Visits a hierarchy of node, starting by the given root node. + /// + /// The root node of the visit + /// The member content containing the node to visit, if relevant. This is used to properly check if the root node should be visited. + /// The initial path of the root node, if this visit occurs in the context of a sub-hierarchy. Can be null. + public virtual void Visit(IGraphNode node, MemberNode? memberNode = null, GraphNodePath? initialPath = null) + { + ArgumentNullException.ThrowIfNull(node); + CurrentPath = initialPath ?? new GraphNodePath(node); + RootNode = node; + VisitNode(node); + RootNode = null; + } + /// - /// A class that visits all node referenced by a given root node, including members, targets of members, and targets of collection items. - /// The nodes to visit can be controlled by implementing and . + /// Visits a single node. /// - public class GraphVisitorBase + /// The node being visited. + /// This method is in charge of pursuing the visit with the children and references of the given node, as well as raising the event. + protected virtual void VisitNode(IGraphNode node) { - private readonly HashSet visitedNodes = new HashSet(); - - /// - /// The current path in the visit. This path is mutable and must be cloned if used out of the visitor. - /// - protected GraphNodePath CurrentPath; - - /// - /// Gets or sets whether to skip the root node passed to when raising the event. - /// - public bool SkipRootNode { get; set; } - - /// - /// Gets the root node of the current visit. - /// - protected IGraphNode RootNode { get; private set; } - - /// - /// Raised when a node is visited. - /// - public event Action Visiting; - - /// - /// Visits a hierarchy of node, starting by the given root node. - /// - /// The root node of the visit - /// The member content containing the node to visit, if relevant. This is used to properly check if the root node should be visited. - /// The initial path of the root node, if this visit occurs in the context of a sub-hierarchy. Can be null. - public virtual void Visit([NotNull] IGraphNode node, [CanBeNull] MemberNode memberNode = null, [CanBeNull] GraphNodePath initialPath = null) + ArgumentNullException.ThrowIfNull(node); + visitedNodes.Add(node); + if (node != RootNode || !SkipRootNode) { - if (node == null) throw new ArgumentNullException(nameof(node)); - CurrentPath = initialPath ?? new GraphNodePath(node); - RootNode = node; - VisitNode(node); - RootNode = null; + Visiting?.Invoke(node, CurrentPath); } - /// - /// Visits a single node. - /// - /// The node being visited. - /// This method is in charge of pursuing the visit with the children and references of the given node, as well as raising the event. - protected virtual void VisitNode([NotNull] IGraphNode node) + if (node is IObjectNode objectNode) { - if (node == null) throw new ArgumentNullException(nameof(node)); - visitedNodes.Add(node); - if (node != RootNode || !SkipRootNode) - { - Visiting?.Invoke(node, CurrentPath); - } + VisitChildren(objectNode); + VisitItemTargets(objectNode); + } - if (node is IObjectNode objectNode) - { - VisitChildren(objectNode); - VisitItemTargets(objectNode); - } + if (node is IMemberNode memberNode) + { + VisitMemberTarget(memberNode); + } + visitedNodes.Remove(node); + } - if (node is IMemberNode memberNode) + /// + /// Visits the children of the given node. + /// + /// The node being visited. + protected virtual void VisitChildren(IObjectNode node) + { + ArgumentNullException.ThrowIfNull(node); + var members = node.Members; + if (members is List asList) + { + foreach (var child in asList) { - VisitMemberTarget(memberNode); + CurrentPath.PushMember(child.Name); + VisitNode(child); + CurrentPath.Pop(); } - visitedNodes.Remove(node); } - - /// - /// Visits the children of the given node. - /// - /// The node being visited. - protected virtual void VisitChildren([NotNull] IObjectNode node) + else if (members is Dictionary.ValueCollection asVCol) { - if (node == null) throw new ArgumentNullException(nameof(node)); - var members = node.Members; - if (members is List asList) + foreach (var child in asVCol) { - foreach (var child in asList) - { - CurrentPath.PushMember(child.Name); - VisitNode(child); - CurrentPath.Pop(); - } + CurrentPath.PushMember(child.Name); + VisitNode(child); + CurrentPath.Pop(); } - else if (members is Dictionary.ValueCollection asVCol) - { - foreach (var child in asVCol) - { - CurrentPath.PushMember(child.Name); - VisitNode(child); - CurrentPath.Pop(); - } - } - else + } + else + { + foreach (var child in members) { - foreach (var child in members) - { - CurrentPath.PushMember(child.Name); - VisitNode(child); - CurrentPath.Pop(); - } + CurrentPath.PushMember(child.Name); + VisitNode(child); + CurrentPath.Pop(); } } + } - /// - /// Visits the contained in the given node, if any. - /// - /// The node being visited. - protected virtual void VisitMemberTarget([NotNull] IMemberNode node) + /// + /// Visits the contained in the given node, if any. + /// + /// The node being visited. + protected virtual void VisitMemberTarget(IMemberNode node) + { + ArgumentNullException.ThrowIfNull(node); + if (node.TargetReference?.TargetNode != null) { - if (node == null) throw new ArgumentNullException(nameof(node)); - if (node.TargetReference?.TargetNode != null) + if (ShouldVisitMemberTarget(node)) { - if (ShouldVisitMemberTarget(node)) - { - CurrentPath.PushTarget(); - VisitReference(node, node.TargetReference); - CurrentPath.Pop(); - } + CurrentPath.PushTarget(); + VisitReference(node, node.TargetReference); + CurrentPath.Pop(); } } + } - /// - /// Visits the contained in the given node, if any. - /// - /// The node being visited. - protected virtual void VisitItemTargets([NotNull] IObjectNode node) + /// + /// Visits the contained in the given node, if any. + /// + /// The node being visited. + protected virtual void VisitItemTargets(IObjectNode node) + { + ArgumentNullException.ThrowIfNull(node); + var enumerableReference = node.ItemReferences; + if (enumerableReference != null) { - if (node == null) throw new ArgumentNullException(nameof(node)); - var enumerableReference = node.ItemReferences; - if (enumerableReference != null) + foreach (var reference in enumerableReference) { - foreach (var reference in enumerableReference) + if (reference.TargetNode == null) + continue; + + if (ShouldVisitTargetItem(node, reference.Index)) { - if (reference.TargetNode == null) - continue; - - if (ShouldVisitTargetItem(node, reference.Index)) - { - CurrentPath.PushIndex(reference.Index); - VisitReference(node, reference); - CurrentPath.Pop(); - } + CurrentPath.PushIndex(reference.Index); + VisitReference(node, reference); + CurrentPath.Pop(); } } } + } - /// - /// Visits an . - /// - /// The node containing the reference to visit. - /// The reference to visit. - protected virtual void VisitReference([NotNull] IGraphNode referencer, [NotNull] ObjectReference reference) - { - if (referencer == null) throw new ArgumentNullException(nameof(referencer)); - if (reference == null) throw new ArgumentNullException(nameof(reference)); - VisitNode(reference.TargetNode); - } + /// + /// Visits an . + /// + /// The node containing the reference to visit. + /// The reference to visit. + protected virtual void VisitReference(IGraphNode referencer, ObjectReference reference) + { + ArgumentNullException.ThrowIfNull(referencer); + ArgumentNullException.ThrowIfNull(reference); + VisitNode(reference.TargetNode); + } - /// - /// Indicates whether the of the given should be visited or not. - /// - /// The member node to evaluate. - /// True if the target of the member node should be visited, false otherwise. - /// This method is invoked only when the given contains a target node. - protected internal virtual bool ShouldVisitMemberTarget([NotNull] IMemberNode memberNode) - { - if (memberNode == null) throw new ArgumentNullException(nameof(memberNode)); - return !visitedNodes.Contains(memberNode.Target); - } + /// + /// Indicates whether the of the given should be visited or not. + /// + /// The member node to evaluate. + /// True if the target of the member node should be visited, false otherwise. + /// This method is invoked only when the given contains a target node. + protected internal virtual bool ShouldVisitMemberTarget(IMemberNode memberNode) + { + ArgumentNullException.ThrowIfNull(memberNode); + return !visitedNodes.Contains(memberNode.Target); + } - /// - /// Indicates whether the target node of the item corresponding to the given index in the collection contained in the given node should be visited or not. - /// - /// The node to evaluate. - /// The index of the item to evaluate. - /// True if the node of the item corresponding to the given index in the collection contained in the given node should be visited, false otherwise. - /// This method is invoked only when the given contains a collection with items being references. - protected internal virtual bool ShouldVisitTargetItem([NotNull] IObjectNode collectionNode, NodeIndex index) - { - if (collectionNode == null) throw new ArgumentNullException(nameof(collectionNode)); - var target = collectionNode.IndexedTarget(index); - return !visitedNodes.Contains(target); - } + /// + /// Indicates whether the target node of the item corresponding to the given index in the collection contained in the given node should be visited or not. + /// + /// The node to evaluate. + /// The index of the item to evaluate. + /// True if the node of the item corresponding to the given index in the collection contained in the given node should be visited, false otherwise. + /// This method is invoked only when the given contains a collection with items being references. + protected internal virtual bool ShouldVisitTargetItem(IObjectNode collectionNode, NodeIndex index) + { + ArgumentNullException.ThrowIfNull(collectionNode); + var target = collectionNode.IndexedTarget(index); + return !visitedNodes.Contains(target); } } diff --git a/sources/presentation/Stride.Core.Quantum/IGraphNode.cs b/sources/presentation/Stride.Core.Quantum/IGraphNode.cs index 734f80874e..9cab137779 100644 --- a/sources/presentation/Stride.Core.Quantum/IGraphNode.cs +++ b/sources/presentation/Stride.Core.Quantum/IGraphNode.cs @@ -1,49 +1,55 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Reflection; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +public interface IGraphNode1 +{ + Guid Guid { get; } + Type Type { get; } + ITypeDescriptor Descriptor { get; } + bool IsReference { get; } + + object? Retrieve(); + object? Retrieve(NodeIndex index); +} + +/// +/// The interface represents a node in a Quantum object graph. This node can represent an object or a member of an object. +/// +public interface IGraphNode { /// - /// The interface represents a node in a Quantum object graph. This node can represent an object or a member of an object. + /// Gets or sets the . + /// + Guid Guid { get; } + + /// + /// Gets the expected type of for the content of this node. + /// + /// The actual type of the content can be different, for example it could be a type inheriting from this type. + Type Type { get; } + + /// + /// Gets or sets the type descriptor of this content + /// + ITypeDescriptor Descriptor { get; } + + /// + /// Gets whether this node holds a reference or is a direct value. + /// + bool IsReference { get; } + + /// + /// Retrieves the value of this node. + /// + object? Retrieve(); + + /// + /// Retrieves the value of one of the item of this node, if it holds a collection. /// - public interface IGraphNode - { - /// - /// Gets or sets the . - /// - Guid Guid { get; } - - /// - /// Gets the expected type of for the content of this node. - /// - /// The actual type of the content can be different, for example it could be a type inheriting from this type. - [NotNull] - Type Type { get; } - - /// - /// Gets or sets the type descriptor of this content - /// - [NotNull] - ITypeDescriptor Descriptor { get; } - - /// - /// Gets whether this node holds a reference or is a direct value. - /// - bool IsReference { get; } - - /// - /// Retrieves the value of this node. - /// - object Retrieve(); - - /// - /// Retrieves the value of one of the item of this node, if it holds a collection. - /// - /// The index to use to retrieve the value. - object Retrieve(NodeIndex index); - } + /// The index to use to retrieve the value. + object? Retrieve(NodeIndex index); } diff --git a/sources/presentation/Stride.Core.Quantum/IInitializingGraphNode.cs b/sources/presentation/Stride.Core.Quantum/IInitializingGraphNode.cs index deea5a89f1..d2fa0a16dc 100644 --- a/sources/presentation/Stride.Core.Quantum/IInitializingGraphNode.cs +++ b/sources/presentation/Stride.Core.Quantum/IInitializingGraphNode.cs @@ -1,15 +1,15 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Quantum + +namespace Stride.Core.Quantum; + +/// +/// An interface representing an during its initialization phase. +/// +public interface IInitializingGraphNode : IGraphNode { /// - /// An interface representing an during its initialization phase. + /// Seal the node, indicating its construction is finished and that no more children will be added. /// - public interface IInitializingGraphNode : IGraphNode - { - /// - /// Seal the node, indicating its construction is finished and that no more children will be added. - /// - void Seal(); - } + void Seal(); } diff --git a/sources/presentation/Stride.Core.Quantum/IInitializingObjectNode.cs b/sources/presentation/Stride.Core.Quantum/IInitializingObjectNode.cs index d20765a0ec..9d912e69eb 100644 --- a/sources/presentation/Stride.Core.Quantum/IInitializingObjectNode.cs +++ b/sources/presentation/Stride.Core.Quantum/IInitializingObjectNode.cs @@ -3,15 +3,14 @@ using Stride.Core.Annotations; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +internal interface IInitializingObjectNode : IInitializingGraphNode, IObjectNode { - internal interface IInitializingObjectNode : IInitializingGraphNode, IObjectNode - { - /// - /// Add a member to this node. This node and the member node must not have been sealed yet. - /// - /// The member to add to this node. - /// if set to false throw an exception if or is not null. - void AddMember([NotNull] IMemberNode member, bool allowIfReference = false); - } + /// + /// Add a member to this node. This node and the member node must not have been sealed yet. + /// + /// The member to add to this node. + /// if set to false throw an exception if or is not null. + void AddMember([NotNull] IMemberNode member, bool allowIfReference = false); } diff --git a/sources/presentation/Stride.Core.Quantum/IMemberNode.cs b/sources/presentation/Stride.Core.Quantum/IMemberNode.cs index fd35a4b6c8..afd216fd28 100644 --- a/sources/presentation/Stride.Core.Quantum/IMemberNode.cs +++ b/sources/presentation/Stride.Core.Quantum/IMemberNode.cs @@ -1,45 +1,39 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum.References; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +public interface IMemberNode : IGraphNode, INotifyNodeValueChange { - public interface IMemberNode : IGraphNode, INotifyNodeValueChange - { - /// - /// Gets the member name. - /// - [NotNull] - string Name { get; } - - /// - /// Gets the containing this member node. - /// - [NotNull] - IObjectNode Parent { get; } - - ObjectReference TargetReference { get; } - - /// - /// Gets the target of this node, if this node contains a reference to another node. - /// - /// The node does not contain a reference to another node. - [CanBeNull] - IObjectNode Target { get; } - - /// - /// Gets the member descriptor corresponding to this member node. - /// - [NotNull] - IMemberDescriptor MemberDescriptor { get; } - - /// - /// Updates the value of this content with the given value. - /// - /// The new value to set. - void Update(object newValue); - } + /// + /// Gets the member name. + /// + string Name { get; } + + /// + /// Gets the containing this member node. + /// + IObjectNode Parent { get; } + + ObjectReference? TargetReference { get; } + + /// + /// Gets the target of this node, if this node contains a reference to another node. + /// + /// The node does not contain a reference to another node. + IObjectNode? Target { get; } + + /// + /// Gets the member descriptor corresponding to this member node. + /// + IMemberDescriptor MemberDescriptor { get; } + + /// + /// Updates the value of this content with the given value. + /// + /// The new value to set. + void Update(object? newValue); } diff --git a/sources/presentation/Stride.Core.Quantum/INodeBuilder.cs b/sources/presentation/Stride.Core.Quantum/INodeBuilder.cs index 7923d6c28a..a5f7098d3d 100644 --- a/sources/presentation/Stride.Core.Quantum/INodeBuilder.cs +++ b/sources/presentation/Stride.Core.Quantum/INodeBuilder.cs @@ -1,82 +1,80 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using Stride.Core.Reflection; using Stride.Core.Quantum.References; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// This interface provides objects and methods to build a nodal view model from a given object. +/// +public interface INodeBuilder { /// - /// This interface provides objects and methods to build a nodal view model from a given object. + /// Gets the instance of associated to this node builder. /// - public interface INodeBuilder - { - /// - /// Gets the instance of associated to this node builder. - /// - NodeContainer NodeContainer { get; } + NodeContainer NodeContainer { get; } - /// - /// Gets the type descriptor factory. - /// - /// The type descriptor factory. - ITypeDescriptorFactory TypeDescriptorFactory { get; } + /// + /// Gets the type descriptor factory. + /// + /// The type descriptor factory. + ITypeDescriptorFactory TypeDescriptorFactory { get; } - /// - /// Gets or sets the factory that will create instances of for nodes. - /// - INodeFactory NodeFactory { get; set; } - /// - /// Registers a type as a primitive type. - /// - /// The type to register. - /// - /// Any type can be registered as a primitive type. The node builder won't construct nodes for members of primitive types, and won't - /// use reference for them even if they are not value type. - /// - /// - /// - void RegisterPrimitiveType(Type type); + /// + /// Gets or sets the factory that will create instances of for nodes. + /// + INodeFactory NodeFactory { get; set; } + /// + /// Registers a type as a primitive type. + /// + /// The type to register. + /// + /// Any type can be registered as a primitive type. The node builder won't construct nodes for members of primitive types, and won't + /// use reference for them even if they are not value type. + /// + /// + /// + void RegisterPrimitiveType(Type type); - /// - /// Unregisters a type as a primitive type. - /// - /// The type to register. - /// - /// Any type can be registered as a primitive type. The node builder won't construct nodes for members of primitive types, and won't - /// use reference for them even if they are not value type. - /// - /// - /// - void UnregisterPrimitiveType(Type type); + /// + /// Unregisters a type as a primitive type. + /// + /// The type to register. + /// + /// Any type can be registered as a primitive type. The node builder won't construct nodes for members of primitive types, and won't + /// use reference for them even if they are not value type. + /// + /// + /// + void UnregisterPrimitiveType(Type type); - /// - /// Indicates whether a type is a primitive type for this node builder. - /// - /// The type to register. - /// - /// Any type can be registered as a primitive type. The node builder won't construct nodes for members of primitive types, and won't - /// use reference for them even if they are not value type. - /// - /// - /// - bool IsPrimitiveType(Type type); + /// + /// Indicates whether a type is a primitive type for this node builder. + /// + /// The type to register. + /// + /// Any type can be registered as a primitive type. The node builder won't construct nodes for members of primitive types, and won't + /// use reference for them even if they are not value type. + /// + /// + /// + bool IsPrimitiveType(Type type); - /// - /// Build the node hierarchy corresponding to the given object. - /// - /// The object. Can be null. - /// The To assign to the root node. - /// The root node of the node hierarchy corresponding to the given object. - IObjectNode Build(object obj, Guid rootGuid); + /// + /// Build the node hierarchy corresponding to the given object. + /// + /// The object. Can be null. + /// The To assign to the root node. + /// The root node of the node hierarchy corresponding to the given object. + IObjectNode Build(object obj, Guid rootGuid); - /// - /// Creates a reference for the specified type/value node. - /// - /// The type. - /// The value. - /// - IReference CreateReferenceForNode(Type type, object value, bool isMember); - } + /// + /// Creates a reference for the specified type/value node. + /// + /// The type. + /// The value. + /// + IReference? CreateReferenceForNode(Type type, object? value, bool isMember); } diff --git a/sources/presentation/Stride.Core.Quantum/INodeChangeEventArgs.cs b/sources/presentation/Stride.Core.Quantum/INodeChangeEventArgs.cs index 3fdc1ba87d..5ea6fc03c2 100644 --- a/sources/presentation/Stride.Core.Quantum/INodeChangeEventArgs.cs +++ b/sources/presentation/Stride.Core.Quantum/INodeChangeEventArgs.cs @@ -1,33 +1,30 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Annotations; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// A global interface representing any kind of change in a node. +/// +public interface INodeChangeEventArgs { /// - /// A global interface representing any kind of change in a node. + /// The node that has changed. /// - public interface INodeChangeEventArgs - { - /// - /// The node that has changed. - /// - [NotNull] - IGraphNode Node { get; } + IGraphNode Node { get; } - /// - /// The type of change. - /// - ContentChangeType ChangeType { get; } + /// + /// The type of change. + /// + ContentChangeType ChangeType { get; } - /// - /// The old value of the node or the item of the node that has changed. - /// - object OldValue { get; } + /// + /// The old value of the node or the item of the node that has changed. + /// + object? OldValue { get; } - /// - /// The new value of the node or the item of the node that has changed. - /// - object NewValue { get; } - } + /// + /// The new value of the node or the item of the node that has changed. + /// + object? NewValue { get; } } diff --git a/sources/presentation/Stride.Core.Quantum/INodeContainer.cs b/sources/presentation/Stride.Core.Quantum/INodeContainer.cs index 8ec839f8ee..5fe1ca88e2 100644 --- a/sources/presentation/Stride.Core.Quantum/INodeContainer.cs +++ b/sources/presentation/Stride.Core.Quantum/INodeContainer.cs @@ -1,43 +1,42 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; -namespace Stride.Core.Quantum +using System.Diagnostics.CodeAnalysis; + +namespace Stride.Core.Quantum; + +/// +/// A delegate representing a factory used to create a graph node from a content and its related information. +/// +/// The name of the node to create. +/// The content for which to create a node. +/// The unique identifier of the node to create. +/// A new instance of containing the given content. +public delegate IGraphNode NodeFactoryDelegate(string name, IGraphNode node, Guid guid); + +/// +/// An interface representing a container for graph nodes. +/// +public interface INodeContainer { /// - /// A delegate representing a factory used to create a graph node from a content and its related information. + /// Gets or set the visitor to use to create nodes. Default value is a constructed with default parameters. /// - /// The name of the node to create. - /// The content for which to create a node. - /// The unique identifier of the node to create. - /// A new instance of containing the given content. - public delegate IGraphNode NodeFactoryDelegate(string name, IGraphNode node, Guid guid); + INodeBuilder NodeBuilder { get; set; } /// - /// An interface representing a container for graph nodes. + /// Gets the node associated to a data object, if it exists, otherwise creates a new node for the object and its member recursively. /// - public interface INodeContainer - { - /// - /// Gets or set the visitor to use to create nodes. Default value is a constructed with default parameters. - /// - INodeBuilder NodeBuilder { get; set; } + /// The data object. + /// The associated to the given object. + [return: NotNullIfNotNull(nameof(rootObject))] + IObjectNode? GetOrCreateNode(object? rootObject); - /// - /// Gets the node associated to a data object, if it exists, otherwise creates a new node for the object and its member recursively. - /// - /// The data object. - /// The associated to the given object. - [CanBeNull] - IObjectNode GetOrCreateNode(object rootObject); - - /// - /// Gets the associated to a data object, if it exists. - /// - /// The data object. - /// The associated to the given object if available, or null otherwise. - /// Calling this method will update references of the returned node and its children, recursively. - IObjectNode GetNode(object rootObject); - } + /// + /// Gets the associated to a data object, if it exists. + /// + /// The data object. + /// The associated to the given object if available, or null otherwise. + /// Calling this method will update references of the returned node and its children, recursively. + IObjectNode? GetNode(object? rootObject); } diff --git a/sources/presentation/Stride.Core.Quantum/INodeFactory.cs b/sources/presentation/Stride.Core.Quantum/INodeFactory.cs index a150f7543f..dabcbb5a28 100644 --- a/sources/presentation/Stride.Core.Quantum/INodeFactory.cs +++ b/sources/presentation/Stride.Core.Quantum/INodeFactory.cs @@ -1,49 +1,43 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; using Stride.Core.Reflection; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// This interface represents a factory capable of creating instances. +/// +public interface INodeFactory { /// - /// This interface represents a factory capable of creating instances. + /// Creates an instance that represents a class object. /// - public interface INodeFactory - { - /// - /// Creates an instance that represents a class object. - /// - /// The node builder. - /// The unique identifier of the node to build. - /// The object represented by the instance to create. - /// The of the object represented by the instance to create. - /// A new instance representing the given class object. - [NotNull] - IObjectNode CreateObjectNode([NotNull] INodeBuilder nodeBuilder, Guid guid, [NotNull] object obj, [NotNull] ITypeDescriptor descriptor); + /// The node builder. + /// The unique identifier of the node to build. + /// The object represented by the instance to create. + /// The of the object represented by the instance to create. + /// A new instance representing the given class object. + IObjectNode CreateObjectNode(INodeBuilder nodeBuilder, Guid guid, object obj, ITypeDescriptor descriptor); - /// - /// Creates an instance that represents a boxed structure object. - /// - /// The node builder. - /// The unique identifier of the node to build. - /// The boxed structure object represented bu the instace to create. - /// The of the structure represented by the instance to create. - /// A new instance representing the given boxed structure object. - [NotNull] - IObjectNode CreateBoxedNode([NotNull] INodeBuilder nodeBuilder, Guid guid, [NotNull] object structure, [NotNull] ITypeDescriptor descriptor); + /// + /// Creates an instance that represents a boxed structure object. + /// + /// The node builder. + /// The unique identifier of the node to build. + /// The boxed structure object represented bu the instace to create. + /// The of the structure represented by the instance to create. + /// A new instance representing the given boxed structure object. + IObjectNode CreateBoxedNode(INodeBuilder nodeBuilder, Guid guid, object structure, ITypeDescriptor descriptor); - /// - /// Creates an instance that represents a member property of a parent object. - /// - /// The node builder. - /// The unique identifier of the node to build. - /// The node representing the parent container. - /// The of the member. - /// The value of this object. - /// A new instance representing the given member property. - [NotNull] - IMemberNode CreateMemberNode([NotNull] INodeBuilder nodeBuilder, Guid guid, [NotNull] IObjectNode parent, [NotNull] IMemberDescriptor member, object value); - } + /// + /// Creates an instance that represents a member property of a parent object. + /// + /// The node builder. + /// The unique identifier of the node to build. + /// The node representing the parent container. + /// The of the member. + /// The value of this object. + /// A new instance representing the given member property. + IMemberNode CreateMemberNode(INodeBuilder nodeBuilder, Guid guid, IObjectNode parent, IMemberDescriptor member, object? value); } diff --git a/sources/presentation/Stride.Core.Quantum/INotifyNodeItemChange.cs b/sources/presentation/Stride.Core.Quantum/INotifyNodeItemChange.cs index 74e565d16e..9a142d6dfd 100644 --- a/sources/presentation/Stride.Core.Quantum/INotifyNodeItemChange.cs +++ b/sources/presentation/Stride.Core.Quantum/INotifyNodeItemChange.cs @@ -1,22 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// An interface representing an object notifying changes when an item in the value of a related node is modified, added or removed. +/// +public interface INotifyNodeItemChange { /// - /// An interface representing an object notifying changes when an item in the value of a related node is modified, added or removed. + /// Raised just before a change to the related node occurs. /// - public interface INotifyNodeItemChange - { - /// - /// Raised just before a change to the related node occurs. - /// - event EventHandler ItemChanging; + event EventHandler? ItemChanging; - /// - /// Raised when a change to the related node has occurred. - /// - event EventHandler ItemChanged; - } + /// + /// Raised when a change to the related node has occurred. + /// + event EventHandler? ItemChanged; } diff --git a/sources/presentation/Stride.Core.Quantum/INotifyNodeValueChange.cs b/sources/presentation/Stride.Core.Quantum/INotifyNodeValueChange.cs index 155b5b4ef8..148e8006d8 100644 --- a/sources/presentation/Stride.Core.Quantum/INotifyNodeValueChange.cs +++ b/sources/presentation/Stride.Core.Quantum/INotifyNodeValueChange.cs @@ -1,22 +1,20 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// An interface representing an object notifying changes when the value of a related node changes. +/// +public interface INotifyNodeValueChange { /// - /// An interface representing an object notifying changes when the value of a related node changes. + /// Raised just before a change to the value of a related node occurs. /// - public interface INotifyNodeValueChange - { - /// - /// Raised just before a change to the value of a related node occurs. - /// - event EventHandler ValueChanging; + event EventHandler? ValueChanging; - /// - /// Raised when a change to the value of a related node has occurred. - /// - event EventHandler ValueChanged; - } + /// + /// Raised when a change to the value of a related node has occurred. + /// + event EventHandler? ValueChanged; } diff --git a/sources/presentation/Stride.Core.Quantum/IObjectNode.cs b/sources/presentation/Stride.Core.Quantum/IObjectNode.cs index 0ae206599f..8cd1264441 100644 --- a/sources/presentation/Stride.Core.Quantum/IObjectNode.cs +++ b/sources/presentation/Stride.Core.Quantum/IObjectNode.cs @@ -1,77 +1,75 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + +using System.Diagnostics.CodeAnalysis; using Stride.Core.Quantum.References; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +public interface IObjectNode : IGraphNode, INotifyNodeItemChange { - public interface IObjectNode : IGraphNode, INotifyNodeItemChange - { - /// - /// Gets the member corresponding to the given name. - /// - /// The name of the member to retrieve. - /// The member corresponding to the given name. - /// This node has no member that matches the given name. - IMemberNode this[string name] { get; } + /// + /// Gets the member corresponding to the given name. + /// + /// The name of the member to retrieve. + /// The member corresponding to the given name. + /// This node has no member that matches the given name. + IMemberNode this[string name] { get; } - /// - /// Gets the collection of members of this node. - /// - [NotNull] - IReadOnlyCollection Members { get; } + /// + /// Gets the collection of members of this node. + /// + IReadOnlyCollection Members { get; } - ReferenceEnumerable ItemReferences { get; } + ReferenceEnumerable? ItemReferences { get; } - /// - /// Gets all the indices in the value of this content, if it is a collection. Otherwise, this property returns null. - /// - IEnumerable Indices { get; } + /// + /// Gets all the indices in the value of this content, if it is a collection. Otherwise, this property returns null. + /// + IEnumerable? Indices { get; } - bool IsEnumerable { get; } + [MemberNotNullWhen(true, nameof(Indices))] + bool IsEnumerable { get; } - /// - /// Gets the target of this node corresponding to the given index, if this node contains a sequence of references to some other nodes. - /// - /// The node does not contain a sequence of references to some other nodes. - /// The index is empty. - /// The index does not exist. - IObjectNode IndexedTarget(NodeIndex index); + /// + /// Gets the target of this node corresponding to the given index, if this node contains a sequence of references to some other nodes. + /// + /// The node does not contain a sequence of references to some other nodes. + /// The index is empty. + /// The index does not exist. + IObjectNode? IndexedTarget(NodeIndex index); - /// - /// Attempts to retrieve the child node of this that matches the given name. - /// - /// The name of the child to retrieve. - /// The child node that matches the given name, or null if no child matches. - IMemberNode TryGetChild(string name); + /// + /// Attempts to retrieve the child node of this that matches the given name. + /// + /// The name of the child to retrieve. + /// The child node that matches the given name, or null if no child matches. + IMemberNode? TryGetChild(string name); - /// - /// Updates the value of this content at the given index with the given value. - /// - /// The new value to set. - /// The index where to update the value. - void Update(object newValue, NodeIndex index); + /// + /// Updates the value of this content at the given index with the given value. + /// + /// The new value to set. + /// The index where to update the value. + void Update(object newValue, NodeIndex index); - /// - /// Adds a new item to this content, assuming the content is a collection. - /// - /// The new item to add to the collection. - void Add(object newItem); + /// + /// Adds a new item to this content, assuming the content is a collection. + /// + /// The new item to add to the collection. + void Add(object newItem); - /// - /// Adds a new item at the given index to this content, assuming the content is a collection. - /// - /// The new item to add to the collection. - /// The index at which the new item must be added. - void Add(object newItem, NodeIndex itemIndex); + /// + /// Adds a new item at the given index to this content, assuming the content is a collection. + /// + /// The new item to add to the collection. + /// The index at which the new item must be added. + void Add(object newItem, NodeIndex itemIndex); - /// - /// Removes an item from this content, assuming the content is a collection. - /// - /// The item to remove. - /// The index from which the item must be removed. - void Remove(object item, NodeIndex itemIndex); - } + /// + /// Removes an item from this content, assuming the content is a collection. + /// + /// The item to remove. + /// The index from which the item must be removed. + void Remove(object item, NodeIndex itemIndex); } diff --git a/sources/presentation/Stride.Core.Quantum/Internal/Content.cs b/sources/presentation/Stride.Core.Quantum/Internal/Content.cs index a1fbde4224..ce0cf01386 100644 --- a/sources/presentation/Stride.Core.Quantum/Internal/Content.cs +++ b/sources/presentation/Stride.Core.Quantum/Internal/Content.cs @@ -1,50 +1,49 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using Stride.Core.Reflection; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// A helper class containing methods to manipulate contents. +/// +internal static class Content { /// - /// A helper class containing methods to manipulate contents. + /// Retrieves the value itself or the value of one of its item, depending on the given . /// - internal static class Content + /// The value on which this method applies. + /// The index of the item to retrieve. If is passed, this method will return the value itself. + /// The descriptor of the type of . + /// The value itself or the value of one of its item. + public static object? Retrieve(object value, NodeIndex index, ITypeDescriptor? descriptor) { - /// - /// Retrieves the value itself or the value of one of its item, depending on the given . - /// - /// The value on which this method applies. - /// The index of the item to retrieve. If is passed, this method will return the value itself. - /// The descriptor of the type of . - /// The value itself or the value of one of its item. - public static object Retrieve(object value, NodeIndex index, ITypeDescriptor descriptor) - { - if (index.IsEmpty) - return value; + if (!index.TryGetValue(out var indexValue)) + return value; - if (value == null) throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); - if (descriptor is CollectionDescriptor collectionDescriptor) - { - return collectionDescriptor.GetValue(value, index.Value); - } - else if (descriptor is DictionaryDescriptor dictionaryDescriptor) - { - return dictionaryDescriptor.GetValue(value, index.Value); - } - else if (descriptor is ArrayDescriptor arrayDescriptor) - { - return arrayDescriptor.GetValue(value, (int)index.Value); - } - - // Try with the concrete type descriptor - var objectDescriptor = TypeDescriptorFactory.Default.Find(value.GetType()); - if (objectDescriptor != descriptor) - { - return Retrieve(value, index, objectDescriptor); - } + if (descriptor is CollectionDescriptor collectionDescriptor) + { + return collectionDescriptor.GetValue(value, indexValue); + } + else if (descriptor is DictionaryDescriptor dictionaryDescriptor) + { + return dictionaryDescriptor.GetValue(value, indexValue); + } + else if (descriptor is ArrayDescriptor arrayDescriptor) + { + return arrayDescriptor.GetValue(value, (int)indexValue); + } - throw new NotSupportedException("Unable to retrieve the value at the given index, this collection is unsupported"); + // Try with the concrete type descriptor + var objectDescriptor = TypeDescriptorFactory.Default.Find(value.GetType()); + if (objectDescriptor != descriptor) + { + return Retrieve(value, index, objectDescriptor); } + + throw new NotSupportedException("Unable to retrieve the value at the given index, this collection is unsupported"); } } diff --git a/sources/presentation/Stride.Core.Quantum/Internal/IGraphNodeInternal.cs b/sources/presentation/Stride.Core.Quantum/Internal/IGraphNodeInternal.cs index 4ba1dbadc4..92caf93e9e 100644 --- a/sources/presentation/Stride.Core.Quantum/Internal/IGraphNodeInternal.cs +++ b/sources/presentation/Stride.Core.Quantum/Internal/IGraphNodeInternal.cs @@ -1,13 +1,10 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; +namespace Stride.Core.Quantum; -namespace Stride.Core.Quantum +internal interface IGraphNodeInternal : IInitializingGraphNode { - internal interface IGraphNodeInternal : IInitializingGraphNode - { - event EventHandler PrepareChange; - event EventHandler FinalizeChange; - } + event EventHandler? PrepareChange; + event EventHandler? FinalizeChange; } diff --git a/sources/presentation/Stride.Core.Quantum/ItemChangeEventArgs.cs b/sources/presentation/Stride.Core.Quantum/ItemChangeEventArgs.cs index 2b99313f22..2fa23985da 100644 --- a/sources/presentation/Stride.Core.Quantum/ItemChangeEventArgs.cs +++ b/sources/presentation/Stride.Core.Quantum/ItemChangeEventArgs.cs @@ -1,59 +1,55 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// Arguments of the and events. +/// +public class ItemChangeEventArgs : EventArgs, INodeChangeEventArgs { /// - /// Arguments of the and events. + /// Initializes a new instance of the class. /// - public class ItemChangeEventArgs : EventArgs, INodeChangeEventArgs + /// The node that has changed. + /// The index in the member where the change occurred. + /// The type of change that occurred. + /// The old value of the item that has changed. + /// The new value of the item that has changed. + public ItemChangeEventArgs(IObjectNode node, NodeIndex index, ContentChangeType changeType, object? oldValue, object? newValue) { - /// - /// Initializes a new instance of the class. - /// - /// The node that has changed. - /// The index in the member where the change occurred. - /// The type of change that occurred. - /// The old value of the item that has changed. - /// The new value of the item that has changed. - public ItemChangeEventArgs([NotNull] IObjectNode node, NodeIndex index, ContentChangeType changeType, object oldValue, object newValue) - { - Collection = node; - Index = index; - ChangeType = changeType; - OldValue = oldValue; - NewValue = newValue; - } - - /// - /// Gets the object node of the collection that has changed. - /// - [NotNull] - public IObjectNode Collection { get; } - - /// - /// Gets the index where the change occurred. - /// - public NodeIndex Index { get; } - - /// - /// The type of change. - /// - public ContentChangeType ChangeType { get; } - - /// - /// Gets the old value of the member or the item of the member that has changed. - /// - public object OldValue { get; } - - /// - /// Gets the new value of the member or the item of the member that has changed. - /// - public object NewValue { get; } - - /// - IGraphNode INodeChangeEventArgs.Node => Collection; + Collection = node; + Index = index; + ChangeType = changeType; + OldValue = oldValue; + NewValue = newValue; } + + /// + /// Gets the object node of the collection that has changed. + /// + public IObjectNode Collection { get; } + + /// + /// Gets the index where the change occurred. + /// + public NodeIndex Index { get; } + + /// + /// The type of change. + /// + public ContentChangeType ChangeType { get; } + + /// + /// Gets the old value of the member or the item of the member that has changed. + /// + public object? OldValue { get; } + + /// + /// Gets the new value of the member or the item of the member that has changed. + /// + public object? NewValue { get; } + + /// + IGraphNode INodeChangeEventArgs.Node => Collection; } diff --git a/sources/presentation/Stride.Core.Quantum/MemberNode.cs b/sources/presentation/Stride.Core.Quantum/MemberNode.cs index 227f0b0ce5..f1c5da7896 100644 --- a/sources/presentation/Stride.Core.Quantum/MemberNode.cs +++ b/sources/presentation/Stride.Core.Quantum/MemberNode.cs @@ -1,136 +1,132 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; -using Stride.Core.Annotations; using Stride.Core.Extensions; -using Stride.Core.Reflection; using Stride.Core.Quantum.References; +using Stride.Core.Reflection; + +namespace Stride.Core.Quantum; -namespace Stride.Core.Quantum +/// +/// An implementation of that gives access to a member of an object. +/// +public class MemberNode : GraphNodeBase, IMemberNode, IGraphNodeInternal { - /// - /// An implementation of that gives access to a member of an object. - /// - public class MemberNode : GraphNodeBase, IMemberNode, IGraphNodeInternal + public MemberNode(INodeBuilder nodeBuilder, Guid guid, IObjectNode parent, IMemberDescriptor memberDescriptor, IReference? reference) + : base(nodeBuilder.SafeArgument().NodeContainer, guid, nodeBuilder.TypeDescriptorFactory.Find(memberDescriptor.Type)) { - public MemberNode([NotNull] INodeBuilder nodeBuilder, Guid guid, [NotNull] IObjectNode parent, [NotNull] IMemberDescriptor memberDescriptor, IReference reference) - : base(nodeBuilder.SafeArgument(nameof(nodeBuilder)).NodeContainer, guid, nodeBuilder.TypeDescriptorFactory.Find(memberDescriptor.Type)) - { - if (nodeBuilder == null) throw new ArgumentNullException(nameof(nodeBuilder)); - Parent = parent ?? throw new ArgumentNullException(nameof(parent)); - MemberDescriptor = memberDescriptor ?? throw new ArgumentNullException(nameof(memberDescriptor)); - Name = memberDescriptor.Name; - TargetReference = reference as ObjectReference; - } + ArgumentNullException.ThrowIfNull(nodeBuilder); + Parent = parent ?? throw new ArgumentNullException(nameof(parent)); + MemberDescriptor = memberDescriptor ?? throw new ArgumentNullException(nameof(memberDescriptor)); + Name = memberDescriptor.Name; + TargetReference = reference as ObjectReference; + } - /// - public string Name { get; } + /// + public string Name { get; } - /// - public IObjectNode Parent { get; } + /// + public IObjectNode Parent { get; } - /// - /// The used to access the member of the container represented by this content. - /// - public IMemberDescriptor MemberDescriptor { get; protected set; } + /// + /// The used to access the member of the container represented by this content. + /// + public IMemberDescriptor MemberDescriptor { get; protected set; } - public override bool IsReference => TargetReference != null; + [MemberNotNullWhen(true, nameof(TargetReference))] + public override bool IsReference => TargetReference != null; - /// - public ObjectReference TargetReference { get; } + /// + public ObjectReference? TargetReference { get; } - /// - public IObjectNode Target => TargetReference?.TargetNode; + /// + public IObjectNode? Target => TargetReference?.TargetNode; - /// - public event EventHandler PrepareChange; + /// + public event EventHandler? PrepareChange; - /// - public event EventHandler FinalizeChange; + /// + public event EventHandler? FinalizeChange; - /// - public event EventHandler ValueChanging; + /// + public event EventHandler? ValueChanging; - /// - public event EventHandler ValueChanged; + /// + public event EventHandler? ValueChanged; - /// - protected sealed override object Value + /// + protected sealed override object? Value + { + get { - get - { - var container = Parent.Retrieve(); - if (container == null) throw new InvalidOperationException("Container's value is null"); - return MemberDescriptor.Get(container); - } + var container = Parent.Retrieve() ?? throw new InvalidOperationException("Container's value is null"); + return MemberDescriptor.Get(container); } + } - /// - public void Update(object newValue) - { - Update(newValue, true); - } + /// + public void Update(object? newValue) + { + Update(newValue, true); + } - /// - /// Raises the event with the given parameters. - /// - /// The arguments of the event. - protected void NotifyContentChanging(MemberNodeChangeEventArgs args) - { - PrepareChange?.Invoke(this, args); - ValueChanging?.Invoke(this, args); - } + /// + /// Raises the event with the given parameters. + /// + /// The arguments of the event. + protected void NotifyContentChanging(MemberNodeChangeEventArgs args) + { + PrepareChange?.Invoke(this, args); + ValueChanging?.Invoke(this, args); + } - /// - /// Raises the event with the given arguments. - /// - /// The arguments of the event. - protected void NotifyContentChanged(MemberNodeChangeEventArgs args) - { - ValueChanged?.Invoke(this, args); - FinalizeChange?.Invoke(this, args); - } + /// + /// Raises the event with the given arguments. + /// + /// The arguments of the event. + protected void NotifyContentChanged(MemberNodeChangeEventArgs args) + { + ValueChanged?.Invoke(this, args); + FinalizeChange?.Invoke(this, args); + } - protected internal override void UpdateFromMember(object newValue, NodeIndex index) - { - if (index != NodeIndex.Empty) throw new ArgumentException(@"index must be Index.Empty.", nameof(NodeIndex)); - Update(newValue, false); - } + protected internal override void UpdateFromMember(object newValue, NodeIndex index) + { + if (index != NodeIndex.Empty) throw new ArgumentException("index must be Index.Empty.", nameof(index)); + Update(newValue, false); + } - private void Update(object newValue, bool sendNotification) + private void Update(object? newValue, bool sendNotification) + { + var oldValue = Retrieve(); + MemberNodeChangeEventArgs? args = null; + if (sendNotification) { - var oldValue = Retrieve(); - MemberNodeChangeEventArgs args = null; - if (sendNotification) - { - args = new MemberNodeChangeEventArgs(this, oldValue, newValue); - NotifyContentChanging(args); - } - var containerValue = Parent.Retrieve(); - if (containerValue == null) - throw new InvalidOperationException("Container's value is null"); - MemberDescriptor.Set(containerValue, ConvertValue(newValue, MemberDescriptor.Type)); - - if (containerValue.GetType().GetTypeInfo().IsValueType) - ((GraphNodeBase)Parent).UpdateFromMember(containerValue, NodeIndex.Empty); - - UpdateReferences(); - if (sendNotification) - { - NotifyContentChanged(args); - } + args = new MemberNodeChangeEventArgs(this, oldValue, newValue); + NotifyContentChanging(args); } + var containerValue = Parent.Retrieve() ?? throw new InvalidOperationException("Container's value is null"); + MemberDescriptor.Set(containerValue, ConvertValue(newValue, MemberDescriptor.Type)); - private void UpdateReferences() - { - NodeContainer?.UpdateReferences(this); - } + if (containerValue.GetType().GetTypeInfo().IsValueType) + ((GraphNodeBase)Parent).UpdateFromMember(containerValue, NodeIndex.Empty); - public override string ToString() + UpdateReferences(); + if (sendNotification) { - return $"{{Node: Member {Name} = [{Value}]}}"; + NotifyContentChanged(args!); } } + + private void UpdateReferences() + { + NodeContainer?.UpdateReferences(this); + } + + public override string ToString() + { + return $"{{Node: Member {Name} = [{Value}]}}"; + } } diff --git a/sources/presentation/Stride.Core.Quantum/MemberNodeChangeEventArgs.cs b/sources/presentation/Stride.Core.Quantum/MemberNodeChangeEventArgs.cs index a1bdcc6dbe..51f7fe4d66 100644 --- a/sources/presentation/Stride.Core.Quantum/MemberNodeChangeEventArgs.cs +++ b/sources/presentation/Stride.Core.Quantum/MemberNodeChangeEventArgs.cs @@ -1,50 +1,46 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// Arguments of the and events. +/// +public class MemberNodeChangeEventArgs : EventArgs, INodeChangeEventArgs { /// - /// Arguments of the and events. + /// Initializes a new instance of the class. /// - public class MemberNodeChangeEventArgs : EventArgs, INodeChangeEventArgs + /// The member that has changed. + /// The old value of the member that has changed. + /// The new value of the member that has changed. + public MemberNodeChangeEventArgs(IMemberNode member, object? oldValue, object? newValue) { - /// - /// Initializes a new instance of the class. - /// - /// The member that has changed. - /// The old value of the member that has changed. - /// The new value of the member that has changed. - public MemberNodeChangeEventArgs([NotNull] IMemberNode member, object oldValue, object newValue) - { - Member = member; - OldValue = oldValue; - NewValue = newValue; - } + Member = member; + OldValue = oldValue; + NewValue = newValue; + } - /// - /// Gets the member node that has changed. - /// - [NotNull] - public IMemberNode Member { get; } + /// + /// Gets the member node that has changed. + /// + public IMemberNode Member { get; } - /// - /// The type of change. - /// - public ContentChangeType ChangeType => ContentChangeType.ValueChange; + /// + /// The type of change. + /// + public ContentChangeType ChangeType => ContentChangeType.ValueChange; - /// - /// Gets the old value of the member or the item of the member that has changed. - /// - public object OldValue { get; } + /// + /// Gets the old value of the member or the item of the member that has changed. + /// + public object? OldValue { get; } - /// - /// Gets the new value of the member or the item of the member that has changed. - /// - public object NewValue { get; } + /// + /// Gets the new value of the member or the item of the member that has changed. + /// + public object? NewValue { get; } - /// - IGraphNode INodeChangeEventArgs.Node => Member; - } + /// + IGraphNode INodeChangeEventArgs.Node => Member; } diff --git a/sources/presentation/Stride.Core.Quantum/NodeAccessor.cs b/sources/presentation/Stride.Core.Quantum/NodeAccessor.cs index 04539b0a2f..8923307971 100644 --- a/sources/presentation/Stride.Core.Quantum/NodeAccessor.cs +++ b/sources/presentation/Stride.Core.Quantum/NodeAccessor.cs @@ -1,92 +1,86 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Diagnostics.Contracts; -using Stride.Core.Annotations; + using Stride.Core.Extensions; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// An object representing a single accessor of the value of a node, or one if its item. +/// +public readonly struct NodeAccessor { /// - /// An object representing a single accessor of the value of a node, or one if its item. + /// The node of the accessor. /// - public struct NodeAccessor - { - /// - /// The node of the accessor. - /// - public readonly IGraphNode Node; - /// - /// The index of the accessor. - /// - public readonly NodeIndex Index; + public readonly IGraphNode Node; + /// + /// The index of the accessor. + /// + public readonly NodeIndex Index; - /// - /// Initializes a new instance of the structure. - /// - /// The target node of this accessor. - /// The index of the target item if this accessor target an item. otherwise. - public NodeAccessor([NotNull] IGraphNode node, NodeIndex index) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - if (node is IMemberNode && !index.IsEmpty) throw new ArgumentException($"Cannot create an accessor for an {nameof(IMemberNode)} that use a non-empty index."); - Node = node; - Index = index; - } + /// + /// Initializes a new instance of the structure. + /// + /// The target node of this accessor. + /// The index of the target item if this accessor target an item. otherwise. + public NodeAccessor(IGraphNode node, NodeIndex index) + { + ArgumentNullException.ThrowIfNull(node); + if (node is IMemberNode && !index.IsEmpty) throw new ArgumentException($"Cannot create an accessor for an {nameof(IMemberNode)} that use a non-empty index."); + Node = node; + Index = index; + } - /// - /// Gets whether this accessor is targeting a member of an object. - /// - public bool IsMember => Node is IMemberNode; + /// + /// Gets whether this accessor is targeting a member of an object. + /// + public readonly bool IsMember => Node is IMemberNode; - /// - /// Gets whether this accessor is targeting an item of a collection. - /// - public bool IsItem => Node is IObjectNode && !Index.IsEmpty; + /// + /// Gets whether this accessor is targeting an item of a collection. + /// + public readonly bool IsItem => Node is IObjectNode && !Index.IsEmpty; - /// - /// Retrieves the value backed by this accessor. - /// - /// The value backed by this accessor. - [Pure] - public object RetrieveValue() => Node.Retrieve(Index); + /// + /// Retrieves the value backed by this accessor. + /// + /// The value backed by this accessor. + public readonly object? RetrieveValue() => Node.Retrieve(Index); - /// - /// Updates the value backed by this accessor. - /// - /// The new value to set. - public void UpdateValue(object value) + /// + /// Updates the value backed by this accessor. + /// + /// The new value to set. + public readonly void UpdateValue(object value) + { + if (IsItem) { - if (IsItem) - { - ((IObjectNode)Node).Update(value, Index); - } - else if (IsMember) - { - ((IMemberNode)Node).Update(value); - } + ((IObjectNode)Node).Update(value, Index); } - - /// - /// Indicates whether this accessor can accept a value of the given type to update the targeted node. - /// - /// The type to evaluate. - /// True if this type is accepted, false otherwise. - [Pure] - public bool AcceptType(Type type) + else if (IsMember) { - return Node.Descriptor.GetInnerCollectionType().IsAssignableFrom(type); + ((IMemberNode)Node).Update(value); } + } - /// - /// Indicates whether this accessor can accept the given value to update the targeted node. - /// - /// The value to evaluate. - /// True if the value is accepted, false otherwise. - [Pure] - public bool AcceptValue(object value) - { - return value == null ? !Node.Descriptor.GetInnerCollectionType().IsValueType : Node.Descriptor.GetInnerCollectionType().IsInstanceOfType(value); - } + /// + /// Indicates whether this accessor can accept a value of the given type to update the targeted node. + /// + /// The type to evaluate. + /// True if this type is accepted, false otherwise. + public readonly bool AcceptType(Type type) + { + return Node.Descriptor.GetInnerCollectionType().IsAssignableFrom(type); + } + + /// + /// Indicates whether this accessor can accept the given value to update the targeted node. + /// + /// The value to evaluate. + /// True if the value is accepted, false otherwise. + public readonly bool AcceptValue(object value) + { + return value == null ? !Node.Descriptor.GetInnerCollectionType().IsValueType : Node.Descriptor.GetInnerCollectionType().IsInstanceOfType(value); } } diff --git a/sources/presentation/Stride.Core.Quantum/NodeContainer.cs b/sources/presentation/Stride.Core.Quantum/NodeContainer.cs index 0554675620..ade586f273 100644 --- a/sources/presentation/Stride.Core.Quantum/NodeContainer.cs +++ b/sources/presentation/Stride.Core.Quantum/NodeContainer.cs @@ -1,189 +1,182 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; -using System.Threading; -using Stride.Core.Annotations; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// A container used to store nodes and resolve references between them. +/// +public class NodeContainer : INodeContainer { + private readonly object lockObject = new(); + private readonly ThreadLocal> processedNodes = new(); + private ConditionalWeakTable nodesByObject = []; + /// - /// A container used to store nodes and resolve references between them. + /// Creates a new instance of class. /// - public class NodeContainer : INodeContainer + public NodeContainer() { - private readonly object lockObject = new object(); - private readonly ThreadLocal> processedNodes = new ThreadLocal>(); - private ConditionalWeakTable nodesByObject = new ConditionalWeakTable(); - - /// - /// Creates a new instance of class. - /// - public NodeContainer() - { - NodeBuilder = CreateDefaultNodeBuilder(); - } + NodeBuilder = CreateDefaultNodeBuilder(); + } - /// - public INodeBuilder NodeBuilder { get; set; } + /// + public INodeBuilder NodeBuilder { get; set; } - /// - public IObjectNode GetOrCreateNode(object rootObject) - { - if (rootObject == null) - return null; + /// + [return: NotNullIfNotNull(nameof(rootObject))] + public IObjectNode? GetOrCreateNode(object? rootObject) + { + if (rootObject == null) + return null; - lock (lockObject) - { - if (!processedNodes.IsValueCreated) - processedNodes.Value = new HashSet(); + lock (lockObject) + { + if (!processedNodes.IsValueCreated) + processedNodes.Value = []; - var node = GetOrCreateNodeInternal(rootObject); + var node = GetOrCreateNodeInternal(rootObject); - processedNodes.Value.Clear(); - return node; - } + processedNodes.Value!.Clear(); + return node; } + } - /// - public IObjectNode GetNode(object rootObject) + /// + public IObjectNode? GetNode(object? rootObject) + { + lock (lockObject) { - lock (lockObject) - { - if (!processedNodes.IsValueCreated) - processedNodes.Value = new HashSet(); + if (!processedNodes.IsValueCreated) + processedNodes.Value = []; - var node = GetNodeInternal(rootObject); + var node = GetNodeInternal(rootObject); - processedNodes.Value.Clear(); - return node; - } + processedNodes.Value!.Clear(); + return node; } + } - /// - /// Refresh all references contained in the given node, creating new nodes for newly referenced objects. - /// - /// The node to update - internal void UpdateReferences([NotNull] IGraphNode node) + /// + /// Refresh all references contained in the given node, creating new nodes for newly referenced objects. + /// + /// The node to update + internal void UpdateReferences(IGraphNode node) + { + lock (lockObject) { - lock (lockObject) - { - if (!processedNodes.IsValueCreated) - processedNodes.Value = new HashSet(); + if (!processedNodes.IsValueCreated) + processedNodes.Value = []; - UpdateReferencesInternal(node); + UpdateReferencesInternal(node); - processedNodes.Value.Clear(); - } + processedNodes.Value!.Clear(); } + } - /// - /// Removes all nodes that were previously registered. - /// - public void Clear() + /// + /// Removes all nodes that were previously registered. + /// + public void Clear() + { + lock (lockObject) { - lock (lockObject) - { - nodesByObject = new ConditionalWeakTable(); - } + nodesByObject = []; } + } - /// - /// Gets the associated to a data object, if it exists. If the NodeContainer has been constructed without , this method will throw an exception. - /// - /// The data object. - /// The associated to the given object if available, or null otherwise. - internal IObjectNode GetNodeInternal(object rootObject) + /// + /// Gets the associated to a data object, if it exists. If the NodeContainer has been constructed without , this method will throw an exception. + /// + /// The data object. + /// The associated to the given object if available, or null otherwise. + internal IObjectNode? GetNodeInternal(object? rootObject) + { + lock (lockObject) { - lock (lockObject) - { - if (rootObject == null) - return null; + if (rootObject == null) + return null; - IObjectNode node; - nodesByObject.TryGetValue(rootObject, out node); - return node; - } + nodesByObject.TryGetValue(rootObject, out var node); + return node; } + } - /// - /// Gets the node associated to a data object, if it exists, otherwise creates a new node for the object and its member recursively. - /// - /// The data object. - /// The associated to the given object. - [CanBeNull] - internal IObjectNode GetOrCreateNodeInternal(object rootObject) - { - if (rootObject == null) - return null; + /// + /// Gets the node associated to a data object, if it exists, otherwise creates a new node for the object and its member recursively. + /// + /// The data object. + /// The associated to the given object. + [return: NotNullIfNotNull(nameof(rootObject))] + internal IObjectNode? GetOrCreateNodeInternal(object? rootObject) + { + if (rootObject == null) + return null; - lock (lockObject) + lock (lockObject) + { + if (!rootObject.GetType().IsValueType) { - IObjectNode result; - if (!rootObject.GetType().IsValueType) - { - result = GetNodeInternal(rootObject); - if (result != null) - return result; - } - - result = NodeBuilder.Build(rootObject, Guid.NewGuid()); + if (GetNodeInternal(rootObject) is {} node) + return node; + } - if (result != null) + var result = NodeBuilder.Build(rootObject, Guid.NewGuid()); + // FIXME can it really be null here? + if (result != null) + { + // Register reference objects + if (result is not BoxedNode) { - // Register reference objects - if (!(result is BoxedNode)) - { - nodesByObject.Add(rootObject, result); - } - // Create or update nodes of referenced objects - UpdateReferencesInternal(result); + nodesByObject.Add(rootObject, result); } - return result; + // Create or update nodes of referenced objects + UpdateReferencesInternal(result); } + return result!; } + } - /// - /// Refresh all references contained in the given node, creating new nodes for newly referenced objects. - /// - /// The node to update - private void UpdateReferencesInternal([NotNull] IGraphNode node) - { - if (node == null) throw new ArgumentNullException(nameof(node)); + /// + /// Refresh all references contained in the given node, creating new nodes for newly referenced objects. + /// + /// The node to update + private void UpdateReferencesInternal(IGraphNode node) + { + ArgumentNullException.ThrowIfNull(node); - lock (lockObject) - { - if (processedNodes.Value.Contains(node)) - return; + lock (lockObject) + { + if (processedNodes.Value.Contains(node)) + return; - processedNodes.Value.Add(node); + processedNodes.Value.Add(node); - // If the node was holding a reference, refresh the reference - if (node.IsReference) - { - (node as IMemberNode)?.TargetReference?.Refresh(node, this); - (node as IObjectNode)?.ItemReferences?.Refresh(node, this); - } - else + // If the node was holding a reference, refresh the reference + if (node.IsReference) + { + (node as IMemberNode)?.TargetReference?.Refresh(node, this); + (node as IObjectNode)?.ItemReferences?.Refresh(node, this); + } + else + { + // Otherwise refresh potential references in its children. + if (node is IObjectNode objectNode) { - // Otherwise refresh potential references in its children. - var objectNode = node as IObjectNode; - if (objectNode != null) + foreach (var child in objectNode.Members) { - foreach (var child in objectNode.Members) - { - UpdateReferencesInternal(child); - } + UpdateReferencesInternal(child); } } } } + } - [NotNull] - private INodeBuilder CreateDefaultNodeBuilder() - { - return new DefaultNodeBuilder(this); - } + private DefaultNodeBuilder CreateDefaultNodeBuilder() + { + return new DefaultNodeBuilder(this); } } diff --git a/sources/presentation/Stride.Core.Quantum/NodeIndex.cs b/sources/presentation/Stride.Core.Quantum/NodeIndex.cs index 1c5f776893..85b15cd4ec 100644 --- a/sources/presentation/Stride.Core.Quantum/NodeIndex.cs +++ b/sources/presentation/Stride.Core.Quantum/NodeIndex.cs @@ -1,110 +1,116 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; -namespace Stride.Core.Quantum +using System.Diagnostics.CodeAnalysis; + +namespace Stride.Core.Quantum; + +/// +/// A container structure to represent indices in Quantum nodes. +/// +public readonly struct NodeIndex : IEquatable, IComparable, IComparable { /// - /// A container structure to represent indices in Quantum nodes. + /// An index that is null. + /// + public static readonly NodeIndex Empty = new(); + + /// + /// The value of the index. + /// + public readonly object? Value; + + /// + /// Initializes a new instance of the structure. + /// + /// The value of the index. + public NodeIndex(object? value) : this() + { + // Sanity check, to avoid boxing index into index + if (value is NodeIndex) throw new ArgumentException($"A {nameof(NodeIndex)} instance cannot be passed as the value of another {nameof(NodeIndex)} instance."); + Value = value; + } + + /// + /// Gets whether this index is empty. + /// + public readonly bool IsEmpty => Value is null; + + /// + /// Gets whether this index is an integer. + /// + public readonly bool IsInt => Value is int; + + /// + /// Gets the integer value of this index. + /// + /// The value of this index is not an integer. + public readonly int Int => Value is int i ? i : throw new InvalidCastException(); + + /// + /// Gets the value of this index. /// - public struct NodeIndex : IEquatable, IComparable, IComparable + /// When this method returns, contains the value of this index. + /// true if the index is not empty; otherwise, false. + public readonly bool TryGetValue([MaybeNullWhen(false)] out object value) + { + value = Value; + return value is not null; + } + + /// + public override readonly string ToString() + { + return Value?.ToString() ?? "(null)"; + } + + /// + public readonly bool Equals(NodeIndex other) + { + return Equals(Value, other.Value); + } + + /// + public readonly int CompareTo(NodeIndex other) + { + if (other.IsEmpty) + return IsEmpty ? 0 : 1; + if (Value is not IComparable thisValue) + return other.Value is IComparable ? -1 : 0; + + return thisValue.CompareTo(other.Value); + } + + /// + public override readonly bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + return obj is NodeIndex nodeIndex && Equals(nodeIndex); + } + + /// + public override readonly int GetHashCode() + { + return Value?.GetHashCode() ?? 0; + } + + /// + readonly int IComparable.CompareTo(object? obj) + { + if (obj is not NodeIndex nodeIndex) + throw new ArgumentException("obj is not the same type as this instance.", nameof(obj)); + + return CompareTo(nodeIndex); + } + + public static bool operator ==(NodeIndex a, NodeIndex b) + { + return Equals(a.Value, b.Value); + } + + /// + public static bool operator !=(NodeIndex a, NodeIndex b) { - /// - /// An index that is null. - /// - public static readonly NodeIndex Empty = new NodeIndex(); - - /// - /// The value of the index. - /// - public readonly object Value; - - /// - /// Initializes a new instance of the structure. - /// - /// The value of the index. - public NodeIndex(object value) : this() - { - // Sanity check, to avoid boxing index into index - if (value is NodeIndex) throw new ArgumentException($"A {nameof(NodeIndex)} instance cannot be passed as the value of another {nameof(NodeIndex)} instance."); - Value = value; - } - - /// - /// Gets whether this index is empty. - /// - public bool IsEmpty => Value == null; - - /// - /// Gets whether this index is an integer. - /// - public bool IsInt => Value is int; - - /// - /// Gets the integer value of this index. - /// - /// The value of this index is not an integer. - public int Int => (int)Value; - - /// - public override string ToString() - { - return Value?.ToString() ?? "(null)"; - } - - /// - public bool Equals(NodeIndex other) - { - return Equals(Value, other.Value); - } - - /// - public int CompareTo(NodeIndex other) - { - if (other.IsEmpty) - return IsEmpty ? 0 : 1; - - var thisValue = Value as IComparable; - var otherValue = other.Value as IComparable; - if (thisValue == null) - return otherValue != null ? -1 : 0; - - return thisValue.CompareTo(other.Value); - } - - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; - return obj is NodeIndex && Equals((NodeIndex)obj); - } - - /// - public override int GetHashCode() - { - return Value?.GetHashCode() ?? 0; - } - - int IComparable.CompareTo([NotNull] object obj) - { - if (obj == null || obj.GetType() != GetType()) - throw new ArgumentException(@"obj is not the same type as this instance.", nameof(obj)); - - var other = (NodeIndex)obj; - return CompareTo(other); - } - - public static bool operator==(NodeIndex a, NodeIndex b) - { - return Equals(a.Value, b.Value); - } - - /// - public static bool operator!=(NodeIndex a, NodeIndex b) - { - return !Equals(a.Value, b.Value); - } + return !Equals(a.Value, b.Value); } } diff --git a/sources/presentation/Stride.Core.Quantum/ObjectNode.cs b/sources/presentation/Stride.Core.Quantum/ObjectNode.cs index c948b2563c..8b9b03c513 100644 --- a/sources/presentation/Stride.Core.Quantum/ObjectNode.cs +++ b/sources/presentation/Stride.Core.Quantum/ObjectNode.cs @@ -1,279 +1,282 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; using Stride.Core.Collections; using Stride.Core.Extensions; using Stride.Core.Reflection; using Stride.Core.Quantum.References; +using System.Diagnostics.CodeAnalysis; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// An implementation of that gives access to an object or a boxed struct. +/// +/// This content is not serialized by default. +public class ObjectNode : GraphNodeBase, IInitializingObjectNode, IGraphNodeInternal { - /// - /// An implementation of that gives access to an object or a boxed struct. - /// - /// This content is not serialized by default. - public class ObjectNode : GraphNodeBase, IInitializingObjectNode, IGraphNodeInternal + private readonly HybridDictionary childrenMap = []; + private object value; + + public ObjectNode(INodeBuilder nodeBuilder, object value, Guid guid, ITypeDescriptor descriptor, IReference? reference) + : base(nodeBuilder.SafeArgument().NodeContainer, guid, descriptor) { - private readonly HybridDictionary childrenMap = new HybridDictionary(); - private object value; + if (reference is ObjectReference) + throw new ArgumentException($"An {nameof(ObjectNode)} cannot contain an {nameof(ObjectReference)}"); + this.value = value; + ItemReferences = reference as ReferenceEnumerable; + } - public ObjectNode([NotNull] INodeBuilder nodeBuilder, object value, Guid guid, [NotNull] ITypeDescriptor descriptor, IReference reference) - : base(nodeBuilder.SafeArgument(nameof(nodeBuilder)).NodeContainer, guid, descriptor) - { - if (reference is ObjectReference) - throw new ArgumentException($"An {nameof(ObjectNode)} cannot contain an {nameof(ObjectReference)}"); - this.value = value; - ItemReferences = reference as ReferenceEnumerable; - } + /// + public IMemberNode this[string name] => childrenMap[name]; - /// - [NotNull] - public IMemberNode this[[NotNull] string name] => childrenMap[name]; + /// + public IReadOnlyCollection Members => (IReadOnlyCollection)childrenMap.Values; - /// - public IReadOnlyCollection Members => (IReadOnlyCollection)childrenMap.Values; + /// + public IEnumerable? Indices => GetIndices(); - /// - public IEnumerable Indices => GetIndices(); + /// + [MemberNotNullWhen(true, nameof(Indices))] + public bool IsEnumerable => Descriptor is CollectionDescriptor || Descriptor is DictionaryDescriptor || Descriptor is ArrayDescriptor; - /// - public bool IsEnumerable => Descriptor is CollectionDescriptor || Descriptor is DictionaryDescriptor || Descriptor is ArrayDescriptor; + /// + [MemberNotNullWhen(true, nameof(ItemReferences))] + public override bool IsReference => ItemReferences is not null; - /// - public override bool IsReference => ItemReferences != null; + /// + public ReferenceEnumerable? ItemReferences { get; } - /// - public ReferenceEnumerable ItemReferences { get; } + /// + protected sealed override object Value => value; - /// - protected sealed override object Value => value; + /// + public event EventHandler? PrepareChange; - /// - public event EventHandler PrepareChange; + /// + public event EventHandler? FinalizeChange; - /// - public event EventHandler FinalizeChange; + /// + public event EventHandler? ItemChanging; - /// - public event EventHandler ItemChanging; + /// + public event EventHandler? ItemChanged; - /// - public event EventHandler ItemChanged; + /// + public IMemberNode? TryGetChild(string name) + { + childrenMap.TryGetValue(name, out var child); + return child; + } - /// - [CanBeNull] - public IMemberNode TryGetChild([NotNull] string name) - { - IMemberNode child; - childrenMap.TryGetValue(name, out child); - return child; - } + /// + public IObjectNode? IndexedTarget(NodeIndex index) + { + if (index == NodeIndex.Empty) throw new ArgumentException("index cannot be Index.Empty when invoking this method.", nameof(index)); + if (ItemReferences == null) throw new InvalidOperationException("The node does not contain enumerable references."); + return ItemReferences[index].TargetNode; + } - /// - public IObjectNode IndexedTarget(NodeIndex index) + /// + public void Update(object newValue, NodeIndex index) + { + Update(newValue, index, true); + } + + /// + public void Add(object newItem) + { + if (Descriptor is CollectionDescriptor collectionDescriptor) { - if (index == NodeIndex.Empty) throw new ArgumentException(@"index cannot be Index.Empty when invoking this method.", nameof(index)); - if (ItemReferences == null) throw new InvalidOperationException(@"The node does not contain enumerable references."); - return ItemReferences[index].TargetNode; + NodeIndex index = NodeIndex.Empty; + switch (collectionDescriptor.Category) + { + case DescriptorCategory.List: + index = new NodeIndex(collectionDescriptor.GetCollectionCount(value)); + break; + case DescriptorCategory.Set: + index = new NodeIndex(newItem); + break; + } + var args = new ItemChangeEventArgs(this, index, ContentChangeType.CollectionAdd, null, newItem); + NotifyItemChanging(args); + collectionDescriptor.Add(value, newItem); + UpdateReferences(); + NotifyItemChanged(args); } - - /// - public void Update(object newValue, NodeIndex index) + else { - Update(newValue, index, true); + throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); } + } - /// - public void Add(object newItem) + /// + public void Add(object newItem, NodeIndex itemIndex) + { + if (Descriptor is CollectionDescriptor collectionDescriptor) { - var collectionDescriptor = Descriptor as CollectionDescriptor; - if (collectionDescriptor != null) + var index = collectionDescriptor.Category == DescriptorCategory.Collection + ? NodeIndex.Empty + : itemIndex; + var args = new ItemChangeEventArgs(this, index, ContentChangeType.CollectionAdd, null, newItem); + NotifyItemChanging(args); + if (!collectionDescriptor.HasInsert || collectionDescriptor.GetCollectionCount(value) == itemIndex.Int) { - NodeIndex index = NodeIndex.Empty; - switch (collectionDescriptor.Category) - { - case DescriptorCategory.List: - index = new NodeIndex(collectionDescriptor.GetCollectionCount(value)); - break; - case DescriptorCategory.Set: - index = new NodeIndex(newItem); - break; - } - var args = new ItemChangeEventArgs(this, index, ContentChangeType.CollectionAdd, null, newItem); - NotifyItemChanging(args); collectionDescriptor.Add(value, newItem); - UpdateReferences(); - NotifyItemChanged(args); } else - throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); - } - - /// - public void Add(object newItem, NodeIndex itemIndex) - { - if (Descriptor is CollectionDescriptor collectionDescriptor) - { - var index = collectionDescriptor.Category == DescriptorCategory.Collection - ? NodeIndex.Empty - : itemIndex; - var args = new ItemChangeEventArgs(this, index, ContentChangeType.CollectionAdd, null, newItem); - NotifyItemChanging(args); - if (!collectionDescriptor.HasInsert || collectionDescriptor.GetCollectionCount(value) == itemIndex.Int) - { - collectionDescriptor.Add(value, newItem); - } - else - { - collectionDescriptor.Insert(value, itemIndex.Int, newItem); - } - UpdateReferences(); - NotifyItemChanged(args); - } - else if (Descriptor is DictionaryDescriptor dictionaryDescriptor) { - var args = new ItemChangeEventArgs(this, itemIndex, ContentChangeType.CollectionAdd, null, newItem); - NotifyItemChanging(args); - dictionaryDescriptor.AddToDictionary(value, itemIndex.Value, newItem); - UpdateReferences(); - NotifyItemChanged(args); + collectionDescriptor.Insert(value, itemIndex.Int, newItem); } - else - throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); - + UpdateReferences(); + NotifyItemChanged(args); } - - /// - public void Remove(object item, NodeIndex itemIndex) + else if (Descriptor is DictionaryDescriptor dictionaryDescriptor) { - if (itemIndex.IsEmpty) throw new ArgumentException(@"The given index should not be empty.", nameof(itemIndex)); - var args = new ItemChangeEventArgs(this, itemIndex, ContentChangeType.CollectionRemove, item, null); + var args = new ItemChangeEventArgs(this, itemIndex, ContentChangeType.CollectionAdd, null, newItem); NotifyItemChanging(args); - if (Descriptor is CollectionDescriptor collectionDescriptor) - { - if (collectionDescriptor.HasRemoveAt) - { - collectionDescriptor.RemoveAt(value, itemIndex.Int); - } - else - { - collectionDescriptor.Remove(value, item); - } - } - else if (Descriptor is DictionaryDescriptor dictionaryDescriptor) - { - dictionaryDescriptor.Remove(value, itemIndex.Value); - } - else - throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); - + dictionaryDescriptor.AddToDictionary(value, itemIndex.Value, newItem); UpdateReferences(); NotifyItemChanged(args); } + else + { + throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); + } + } + + /// + public void Remove(object item, NodeIndex itemIndex) + { + if (!itemIndex.TryGetValue(out var itemIndexValue)) + throw new ArgumentException("index cannot be empty.", nameof(itemIndex)); - /// - protected internal override void UpdateFromMember(object newValue, NodeIndex index) + var args = new ItemChangeEventArgs(this, itemIndex, ContentChangeType.CollectionRemove, item, null); + NotifyItemChanging(args); + if (Descriptor is CollectionDescriptor collectionDescriptor) { - if (index == NodeIndex.Empty) + if (collectionDescriptor.HasRemoveAt) + { + collectionDescriptor.RemoveAt(value, itemIndex.Int); + } + else { - throw new InvalidOperationException("An ObjectNode value cannot be modified after it has been constructed"); + collectionDescriptor.Remove(value, item); } - Update(newValue, index, true); } - - protected void SetValue(object newValue) + else if (Descriptor is DictionaryDescriptor dictionaryDescriptor) { - value = newValue; + dictionaryDescriptor.Remove(value, itemIndexValue); } - - protected void NotifyItemChanging(ItemChangeEventArgs args) + else { - PrepareChange?.Invoke(this, args); - ItemChanging?.Invoke(this, args); + throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); } - protected void NotifyItemChanged(ItemChangeEventArgs args) + UpdateReferences(); + NotifyItemChanged(args); + } + + /// + protected internal override void UpdateFromMember(object newValue, NodeIndex index) + { + if (index == NodeIndex.Empty) { - ItemChanged?.Invoke(this, args); - FinalizeChange?.Invoke(this, args); + throw new InvalidOperationException("An ObjectNode value cannot be modified after it has been constructed"); } + Update(newValue, index, true); + } - private void Update(object newValue, NodeIndex index, bool sendNotification) - { - if (index == NodeIndex.Empty) - throw new ArgumentException("index cannot be empty."); + protected void SetValue(object newValue) + { + value = newValue; + } - if (Descriptor is SetDescriptor setDescriptor) - { - if (setDescriptor.Contains(Value, newValue)) - { - return; - } - } + protected void NotifyItemChanging(ItemChangeEventArgs args) + { + PrepareChange?.Invoke(this, args); + ItemChanging?.Invoke(this, args); + } - var oldValue = Retrieve(index); - ItemChangeEventArgs itemArgs = null; - if (sendNotification) - { - itemArgs = new ItemChangeEventArgs(this, index, ContentChangeType.CollectionUpdate, oldValue, newValue); - NotifyItemChanging(itemArgs); - } - if (Descriptor is CollectionDescriptor collectionDescriptor) - { - collectionDescriptor.SetValue(Value, index.Value, ConvertValue(newValue, collectionDescriptor.ElementType)); - } - else if (Descriptor is DictionaryDescriptor dictionaryDescriptor) - { - dictionaryDescriptor.SetValue(Value, index.Value, ConvertValue(newValue, dictionaryDescriptor.ValueType)); - } - else if (Descriptor is ArrayDescriptor arrayDescriptor) - { - arrayDescriptor.SetValue(Value, (int)index.Value, ConvertValue(newValue, arrayDescriptor.ElementType)); - } - else - throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); + protected void NotifyItemChanged(ItemChangeEventArgs args) + { + ItemChanged?.Invoke(this, args); + FinalizeChange?.Invoke(this, args); + } - UpdateReferences(); - if (sendNotification) + private void Update(object newValue, NodeIndex index, bool sendNotification) + { + if (!index.TryGetValue(out var indexValue)) + throw new ArgumentException("index cannot be empty.", nameof(index)); + + if (Descriptor is SetDescriptor setDescriptor) + { + if (setDescriptor.Contains(Value, newValue)) { - NotifyItemChanged(itemArgs); + return; } } - - private void UpdateReferences() + var oldValue = Retrieve(index); + ItemChangeEventArgs? itemArgs = null; + if (sendNotification) { - NodeContainer?.UpdateReferences(this); + itemArgs = new ItemChangeEventArgs(this, index, ContentChangeType.CollectionUpdate, oldValue, newValue); + NotifyItemChanging(itemArgs); } - - private IEnumerable GetIndices() + if (Descriptor is CollectionDescriptor collectionDescriptor) { - var enumRef = ItemReferences; - if (enumRef != null) - return enumRef.Indices; - - return GetIndices(this); + collectionDescriptor.SetValue(Value, indexValue, ConvertValue(newValue, collectionDescriptor.ElementType)); } - - public override string ToString() + else if (Descriptor is DictionaryDescriptor dictionaryDescriptor) + { + dictionaryDescriptor.SetValue(Value, indexValue, ConvertValue(newValue, dictionaryDescriptor.ValueType)); + } + else if (Descriptor is ArrayDescriptor arrayDescriptor) + { + arrayDescriptor.SetValue(Value, (int)indexValue, ConvertValue(newValue, arrayDescriptor.ElementType)); + } + else { - return $"{{Node: Object {Type.Name} = [{Value}]}}"; + throw new NotSupportedException("Unable to set the node value, the collection is unsupported"); } - /// - void IInitializingObjectNode.AddMember(IMemberNode member, bool allowIfReference) + UpdateReferences(); + if (sendNotification) { - if (IsSealed) - throw new InvalidOperationException("Unable to add a child to a GraphNode that has been sealed"); + NotifyItemChanged(itemArgs); + } + } + + private void UpdateReferences() + { + NodeContainer?.UpdateReferences(this); + } + + private IEnumerable? GetIndices() + { + var enumRef = ItemReferences; + if (enumRef != null) + return enumRef.Indices; - // ReSharper disable once HeuristicUnreachableCode - this code is reachable only at the specific moment we call this method! - if (ItemReferences != null && !allowIfReference) - throw new InvalidOperationException("A GraphNode cannot have children when its content hold a reference."); + return GetIndices(this); + } - childrenMap.Add(member.Name, (MemberNode)member); - } + public override string ToString() + { + return $"{{Node: Object {Type.Name} = [{Value}]}}"; + } + + /// + void IInitializingObjectNode.AddMember(IMemberNode member, bool allowIfReference) + { + if (IsSealed) + throw new InvalidOperationException("Unable to add a child to a GraphNode that has been sealed"); + + // ReSharper disable once HeuristicUnreachableCode - this code is reachable only at the specific moment we call this method! + if (ItemReferences != null && !allowIfReference) + throw new InvalidOperationException("A GraphNode cannot have children when its content hold a reference."); + + childrenMap.Add(member.Name, (MemberNode)member); } } diff --git a/sources/presentation/Stride.Core.Quantum/Properties/AssemblyInfo.cs b/sources/presentation/Stride.Core.Quantum/Properties/AssemblyInfo.cs index 4d2db16224..c9a62064d9 100644 --- a/sources/presentation/Stride.Core.Quantum/Properties/AssemblyInfo.cs +++ b/sources/presentation/Stride.Core.Quantum/Properties/AssemblyInfo.cs @@ -1,6 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Reflection; + using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Stride.Core.Quantum.Tests")] diff --git a/sources/presentation/Stride.Core.Quantum/QuantumConsistencyException.cs b/sources/presentation/Stride.Core.Quantum/QuantumConsistencyException.cs index fedcd1f1d8..d28ab4d5af 100644 --- a/sources/presentation/Stride.Core.Quantum/QuantumConsistencyException.cs +++ b/sources/presentation/Stride.Core.Quantum/QuantumConsistencyException.cs @@ -1,112 +1,105 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; -namespace Stride.Core.Quantum +namespace Stride.Core.Quantum; + +/// +/// An exception that occurs during consistency checks of Quantum objects, indicating that a is un an unexpected state. +/// +public class QuantumConsistencyException : Exception { /// - /// An exception that occurs during consistency checks of Quantum objects, indicating that a is un an unexpected state. + /// Initializes a new instance of the QuantumConsistencyException class. + /// + /// A string representing the expected result. + /// A string representing the observed result. + /// The node that is related to this error. + public QuantumConsistencyException(string expected, string observed, IGraphNode node) + : base(GetMessage(expected, observed)) + { + Expected = expected ?? "(NullMessage)"; + Observed = observed ?? "(NullMessage)"; + Node = node; + } + + /// + /// Initializes a new instance of the QuantumConsistencyException class, with advanced string formatting. /// - public class QuantumConsistencyException : Exception + /// A string representing the expected result. This string must contains a + /// + /// A string representing the observed result. + /// + /// The node that is related to this error. + public QuantumConsistencyException(string expected, string expectedArg, string observed, string observedArg, IGraphNode node) + : base(GetMessage(expected, expectedArg, observed, observedArg)) { - /// - /// Initializes a new instance of the QuantumConsistencyException class. - /// - /// A string representing the expected result. - /// A string representing the observed result. - /// The node that is related to this error. - public QuantumConsistencyException(string expected, string observed, IGraphNode node) - : base(GetMessage(expected, observed)) + try { - Expected = expected ?? "(NullMessage)"; - Observed = observed ?? "(NullMessage)"; - Node = node; + Expected = string.Format(expected ?? "(NullMessage) [{0}]", expectedArg ?? "(NullArgument)"); } - - /// - /// Initializes a new instance of the QuantumConsistencyException class, with advanced string formatting. - /// - /// A string representing the expected result. This string must contains a - /// - /// A string representing the observed result. - /// - /// The node that is related to this error. - public QuantumConsistencyException(string expected, string expectedArg, string observed, string observedArg, IGraphNode node) - : base(GetMessage(expected, expectedArg, observed, observedArg)) + catch (Exception) { - try - { - Expected = string.Format(expected ?? "(NullMessage) [{0}]", expectedArg ?? "(NullArgument)"); - } - catch (Exception) - { - Expected = expected ?? "(NullMessage) [{0}]"; - } - try - { - Observed = string.Format(observed ?? "(NullMessage) [{0}]", observedArg ?? "(NullArgument)"); - } - catch (Exception) - { - Observed = observed ?? "(NullMessage) [{0}]"; - } - - Node = node; + Expected = expected ?? "(NullMessage) [{0}]"; + } + try + { + Observed = string.Format(observed ?? "(NullMessage) [{0}]", observedArg ?? "(NullArgument)"); + } + catch (Exception) + { + Observed = observed ?? "(NullMessage) [{0}]"; } - /// - /// Gets a string representing the expected result. - /// - public string Expected { get; } - - /// - /// Gets a string representing the observed result. - /// - public string Observed { get; } + Node = node; + } - /// - /// Gets the that triggered this exception. - /// - public IGraphNode Node { get; } + /// + /// Gets a string representing the expected result. + /// + public string Expected { get; } - ///// - //public override string ToString() - //{ - // return GetMessage(Expected, Observed); - //} + /// + /// Gets a string representing the observed result. + /// + public string Observed { get; } - [NotNull] - private static string Format(string message, string argument) - { - try - { - return string.Format(message ?? "(NullMessage) [{0}]", argument ?? "(NullArgument)"); - } - catch (Exception) - { - return message ?? "(NullMessage) [(NullArgument)]"; - } + /// + /// Gets the that triggered this exception. + /// + public IGraphNode Node { get; } - } + ///// + //public override string ToString() + //{ + // return GetMessage(Expected, Observed); + //} - [NotNull] - private static string Format(string message) + private static string Format(string message, string argument) + { + try { - return message ?? "(NullMessage)"; - + return string.Format(message ?? "(NullMessage) [{0}]", argument ?? "(NullArgument)"); } - - [NotNull] - private static string GetMessage(string expected, string observed) + catch (Exception) { - return $"Quantum consistency exception. Expected: {Format(expected)} - Observed: {Format(observed)}"; + return message ?? "(NullMessage) [(NullArgument)]"; } - [NotNull] - private static string GetMessage(string expected, string expectedArg, string observed, string observedArg) - { - return $"Quantum consistency exception. Expected: {Format(expected, expectedArg)} - Observed: {Format(observed, observedArg)}"; - } + } + + private static string Format(string message) + { + return message ?? "(NullMessage)"; + + } + + private static string GetMessage(string expected, string observed) + { + return $"Quantum consistency exception. Expected: {Format(expected)} - Observed: {Format(observed)}"; + } + + private static string GetMessage(string expected, string expectedArg, string observed, string observedArg) + { + return $"Quantum consistency exception. Expected: {Format(expected, expectedArg)} - Observed: {Format(observed, observedArg)}"; } } diff --git a/sources/presentation/Stride.Core.Quantum/References/IReference.cs b/sources/presentation/Stride.Core.Quantum/References/IReference.cs index 0e14f604c7..3f47f1ed0f 100644 --- a/sources/presentation/Stride.Core.Quantum/References/IReference.cs +++ b/sources/presentation/Stride.Core.Quantum/References/IReference.cs @@ -1,14 +1,12 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -namespace Stride.Core.Quantum.References +namespace Stride.Core.Quantum.References; + +public interface IReference : IEquatable { - public interface IReference : IEquatable - { - /// - /// Gets the data object targeted by this reference, if available. - /// - object ObjectValue { get; } - } + /// + /// Gets the data object targeted by this reference, if available. + /// + object? ObjectValue { get; } } diff --git a/sources/presentation/Stride.Core.Quantum/References/IReferenceInternal.cs b/sources/presentation/Stride.Core.Quantum/References/IReferenceInternal.cs index a2c4765e9e..587e8f6b80 100644 --- a/sources/presentation/Stride.Core.Quantum/References/IReferenceInternal.cs +++ b/sources/presentation/Stride.Core.Quantum/References/IReferenceInternal.cs @@ -1,18 +1,15 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Annotations; +namespace Stride.Core.Quantum.References; -namespace Stride.Core.Quantum.References +internal interface IReferenceInternal : IReference { - internal interface IReferenceInternal : IReference - { - /// - /// Refreshes this reference by making point to the proper target nodes. If no node exists yet for some of the target, they will be created - /// using the given node factory. - /// - /// The node owning this reference. - /// The node container containing the and the target nodes. - void Refresh([NotNull] IGraphNode ownerNode, NodeContainer nodeContainer); - } + /// + /// Refreshes this reference by making point to the proper target nodes. If no node exists yet for some of the target, they will be created + /// using the given node factory. + /// + /// The node owning this reference. + /// The node container containing the and the target nodes. + void Refresh(IGraphNode ownerNode, NodeContainer nodeContainer); } diff --git a/sources/presentation/Stride.Core.Quantum/References/ObjectReference.cs b/sources/presentation/Stride.Core.Quantum/References/ObjectReference.cs index 2ec16395f6..0605314274 100644 --- a/sources/presentation/Stride.Core.Quantum/References/ObjectReference.cs +++ b/sources/presentation/Stride.Core.Quantum/References/ObjectReference.cs @@ -1,149 +1,140 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core.Annotations; -namespace Stride.Core.Quantum.References +namespace Stride.Core.Quantum.References; + +/// +/// A class representing a reference to another object that has a different model. +/// +public sealed class ObjectReference : IReferenceInternal { + private readonly Type type; + private object? orphanObject; + /// - /// A class representing a reference to another object that has a different model. + /// Initialize a new instance of the class using a data object. /// - public sealed class ObjectReference : IReferenceInternal + /// This constructor should be used when the given has no mode node yet existing. + /// A data object to reference. Can be null. + /// The type of data object to reference. + /// The index of this reference in its parent reference, if it is a . + internal ObjectReference(object? objectValue, Type objectType, NodeIndex index) { - private readonly Type type; - private object orphanObject; - - /// - /// Initialize a new instance of the class using a data object. - /// - /// This constructor should be used when the given has no mode node yet existing. - /// A data object to reference. Can be null. - /// The type of data object to reference. - /// The index of this reference in its parent reference, if it is a . - internal ObjectReference(object objectValue, [NotNull] Type objectType, NodeIndex index) - { - Reference.CheckReferenceCreationSafeGuard(); - if (objectType == null) throw new ArgumentNullException(nameof(objectType)); - if (objectValue != null && !objectType.IsInstanceOfType(objectValue)) throw new ArgumentException(@"The given type does not match the given object.", nameof(objectValue)); - orphanObject = objectValue; - type = objectType; - Index = index; - } + Reference.CheckReferenceCreationSafeGuard(); + ArgumentNullException.ThrowIfNull(objectType); + if (objectValue != null && !objectType.IsInstanceOfType(objectValue)) throw new ArgumentException("The given type does not match the given object.", nameof(objectValue)); + orphanObject = objectValue; + type = objectType; + Index = index; + } - /// - /// Gets the model node targeted by this reference, if available. - /// - public IObjectNode TargetNode { get; private set; } + /// + /// Gets the model node targeted by this reference, if available. + /// + public IObjectNode? TargetNode { get; private set; } - /// - public object ObjectValue => TargetNode != null ? TargetNode.Retrieve() : orphanObject; + /// + public object? ObjectValue => TargetNode?.Retrieve() ?? orphanObject; - /// - /// Gets the index of this reference in its parent collection. If the reference is not in a collection, this will return . - /// - public NodeIndex Index { get; } + /// + /// Gets the index of this reference in its parent collection. If the reference is not in a collection, this will return . + /// + public NodeIndex Index { get; } - /// - /// Gets the of the model node targeted by this reference, if available. - /// - public Guid TargetGuid { get; private set; } + /// + /// Gets the of the model node targeted by this reference, if available. + /// + public Guid TargetGuid { get; private set; } - /// - public bool HasIndex(NodeIndex index) - { - return index.IsEmpty; - } + /// + public bool HasIndex(NodeIndex index) + { + return index.IsEmpty; + } - public void Refresh(IGraphNode ownerNode, NodeContainer nodeContainer) - { - Refresh(ownerNode, nodeContainer, NodeIndex.Empty); - } + public void Refresh(IGraphNode ownerNode, NodeContainer nodeContainer) + { + Refresh(ownerNode, nodeContainer, NodeIndex.Empty); + } - internal void Refresh([NotNull] IGraphNode ownerNode, NodeContainer nodeContainer, NodeIndex index) - { - var objectValue = ownerNode.Retrieve(index); + internal void Refresh(IGraphNode ownerNode, NodeContainer nodeContainer, NodeIndex index) + { + var objectValue = ownerNode.Retrieve(index); - var boxedTarget = TargetNode as BoxedNode; - if (boxedTarget != null && objectValue?.GetType() == TargetNode.Type) + if (TargetNode is BoxedNode boxedTarget && objectValue?.GetType() == TargetNode.Type) + { + // If we are boxing a struct, and the targeted type didn't change, we reuse the same nodes and just overwrite the struct value. + boxedTarget.UpdateFromOwner(objectValue); + // But we still need to refresh inner references! + foreach (var member in TargetNode.Members.Where(x => x.IsReference)) { - // If we are boxing a struct, and the targeted type didn't change, we reuse the same nodes and just overwrite the struct value. - boxedTarget.UpdateFromOwner(objectValue); - // But we still need to refresh inner references! - foreach (var member in TargetNode.Members.Where(x => x.IsReference)) - { - nodeContainer?.UpdateReferences(member); - } + nodeContainer?.UpdateReferences(member); } - else if (TargetNode?.Retrieve() != objectValue) + } + else if (TargetNode?.Retrieve() != objectValue) + { + // This call will recursively update the references. + var target = SetTarget(objectValue, nodeContainer); + if (target != null) { - // This call will recursively update the references. - var target = SetTarget(objectValue, nodeContainer); - if (target != null) - { - var boxedContent = target as BoxedNode; - boxedContent?.SetOwnerContent(ownerNode, index); - } + var boxedContent = target as BoxedNode; + boxedContent?.SetOwnerContent(ownerNode, index); } - // This reference is not orphan anymore. - orphanObject = null; } + // This reference is not orphan anymore. + orphanObject = null; + } - /// - public bool Equals(IReference other) - { - var otherReference = other as ObjectReference; - if (otherReference == null) - return false; + /// + public bool Equals(IReference? other) + { + if (other is not ObjectReference otherReference) + return false; - return TargetGuid == otherReference.TargetGuid && TargetNode == otherReference.TargetNode; - } + return TargetGuid == otherReference.TargetGuid && TargetNode == otherReference.TargetNode; + } - /// - public override string ToString() - { - return $"{{{(Index != NodeIndex.Empty? $"[{Index.Value}]" : "")} -> {TargetNode}}}"; - } + /// + public override string ToString() + { + return $"{{{(Index != NodeIndex.Empty? $"[{Index.Value}]" : "")} -> {TargetNode}}}"; + } - /// - /// Set the and of the targeted object by retrieving it from or creating it to the given . - /// - /// The value for which to set the target node. - /// The used to retrieve or create the target node. - [CanBeNull] - internal IGraphNode SetTarget(object objectValue, [NotNull] NodeContainer nodeContainer) - { - if (nodeContainer == null) throw new ArgumentNullException(nameof(nodeContainer)); - var targetNode = nodeContainer.GetOrCreateNodeInternal(objectValue); - SetTarget(targetNode); - return targetNode; - } + /// + /// Set the and of the targeted object by retrieving it from or creating it to the given . + /// + /// The value for which to set the target node. + /// The used to retrieve or create the target node. + internal IGraphNode? SetTarget(object? objectValue, NodeContainer nodeContainer) + { + ArgumentNullException.ThrowIfNull(nodeContainer); + var targetNode = nodeContainer.GetOrCreateNodeInternal(objectValue); + SetTarget(targetNode); + return targetNode; + } - /// - /// Set the and of the targeted object by retrieving it from or creating it to the given . - /// - /// The used to retrieve or create the target node. - [CanBeNull] - internal IObjectNode SetTarget(IObjectNode targetNode) + /// + /// Set the and of the targeted object by retrieving it from or creating it to the given . + /// + /// The used to retrieve or create the target node. + internal IObjectNode? SetTarget(IObjectNode? targetNode) + { + if (targetNode != null) { - if (targetNode != null) - { - var targetValue = targetNode.Retrieve(); - - if (targetValue != null && !type.IsInstanceOfType(targetValue)) - throw new InvalidOperationException(@"The type of the retrieved node content does not match the type of this reference"); + var targetValue = targetNode.Retrieve(); - TargetNode = targetNode; - TargetGuid = targetNode.Guid; - } - else - { - TargetNode = null; - TargetGuid = Guid.Empty; - } + if (targetValue != null && !type.IsInstanceOfType(targetValue)) + throw new InvalidOperationException("The type of the retrieved node content does not match the type of this reference"); - return targetNode; + TargetNode = targetNode; + TargetGuid = targetNode.Guid; } + else + { + TargetNode = null; + TargetGuid = Guid.Empty; + } + + return targetNode; } } diff --git a/sources/presentation/Stride.Core.Quantum/References/Reference.cs b/sources/presentation/Stride.Core.Quantum/References/Reference.cs index 5a1cda233b..c867e5932b 100644 --- a/sources/presentation/Stride.Core.Quantum/References/Reference.cs +++ b/sources/presentation/Stride.Core.Quantum/References/Reference.cs @@ -1,58 +1,56 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Threading; using Stride.Core.Reflection; -namespace Stride.Core.Quantum.References +namespace Stride.Core.Quantum.References; + +internal static class Reference { - internal static class Reference - { - private static readonly ThreadLocal CreatingReference = new ThreadLocal(); + private static readonly ThreadLocal CreatingReference = new(); - [Obsolete("This method will be rewritten to handle separately the different types of references")] - internal static IReference CreateReference(object objectValue, Type objectType, NodeIndex index, bool isMember) - { - if (objectValue != null && !objectType.IsInstanceOfType(objectValue)) throw new ArgumentException(@"objectValue type does not match objectType", nameof(objectValue)); + [Obsolete("This method will be rewritten to handle separately the different types of references")] + internal static IReference? CreateReference(object? objectValue, Type objectType, NodeIndex index, bool isMember) + { + if (objectValue != null && !objectType.IsInstanceOfType(objectValue)) throw new ArgumentException(@"objectValue type does not match objectType", nameof(objectValue)); - if (!CreatingReference.IsValueCreated) - CreatingReference.Value = 0; + if (!CreatingReference.IsValueCreated) + CreatingReference.Value = 0; - ++CreatingReference.Value; + ++CreatingReference.Value; - IReference reference; - var isCollection = HasCollectionReference(objectValue?.GetType() ?? objectType); - if (isMember) + IReference? reference; + var isCollection = HasCollectionReference(objectValue?.GetType() ?? objectType); + if (isMember) + { + reference = new ObjectReference(objectValue, objectType, index); + } + else + { + if (objectValue != null && isCollection && index.IsEmpty) { - reference = new ObjectReference(objectValue, objectType, index); + reference = new ReferenceEnumerable((IEnumerable)objectValue, objectType); } else { - if (objectValue != null && isCollection && index.IsEmpty) - { - reference = new ReferenceEnumerable((IEnumerable)objectValue, objectType); - } - else - { - reference = null; - } + reference = null; } + } - --CreatingReference.Value; + --CreatingReference.Value; - return reference; - } + return reference; + } - private static bool HasCollectionReference(Type type) - { - return type.IsArray || ListDescriptor.IsList(type) || DictionaryDescriptor.IsDictionary(type) || SetDescriptor.IsSet(type) || OldCollectionDescriptor.IsCollection(type); - } + private static bool HasCollectionReference(Type type) + { + return type.IsArray || ListDescriptor.IsList(type) || DictionaryDescriptor.IsDictionary(type) || SetDescriptor.IsSet(type) || CollectionDescriptor.IsCollection(type); + } - internal static void CheckReferenceCreationSafeGuard() - { - if (!CreatingReference.IsValueCreated || CreatingReference.Value == 0) - throw new InvalidOperationException("A reference can only be constructed with the method Reference.CreateReference"); - } + internal static void CheckReferenceCreationSafeGuard() + { + if (!CreatingReference.IsValueCreated || CreatingReference.Value == 0) + throw new InvalidOperationException("A reference can only be constructed with the method Reference.CreateReference"); } } diff --git a/sources/presentation/Stride.Core.Quantum/References/ReferenceEnumerable.cs b/sources/presentation/Stride.Core.Quantum/References/ReferenceEnumerable.cs index 1db8b39ff4..0659329109 100644 --- a/sources/presentation/Stride.Core.Quantum/References/ReferenceEnumerable.cs +++ b/sources/presentation/Stride.Core.Quantum/References/ReferenceEnumerable.cs @@ -1,274 +1,260 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Stride.Core.Annotations; using Stride.Core.Collections; using Stride.Core.Reflection; -namespace Stride.Core.Quantum.References +namespace Stride.Core.Quantum.References; + +/// +/// A class representing an enumeration of references to multiple objects. +/// +public sealed class ReferenceEnumerable : IReferenceInternal, IEnumerable { - /// - /// A class representing an enumeration of references to multiple objects. - /// - public sealed class ReferenceEnumerable : IReferenceInternal, IEnumerable - { - private HybridDictionary items; + private HybridDictionary items; - internal ReferenceEnumerable(IEnumerable enumerable, [NotNull] Type enumerableType) - { - Reference.CheckReferenceCreationSafeGuard(); - ObjectValue = enumerable; - - if (enumerableType.HasInterface(typeof(IDictionary<,>))) - ElementType = enumerableType.GetInterface(typeof(IDictionary<,>)).GetGenericArguments()[1]; - else if (enumerableType.HasInterface(typeof(IEnumerable<>))) - ElementType = enumerableType.GetInterface(typeof(IEnumerable<>)).GetGenericArguments()[0]; - else - ElementType = typeof(object); - } + internal ReferenceEnumerable(IEnumerable enumerable, Type enumerableType) + { + Reference.CheckReferenceCreationSafeGuard(); + ObjectValue = enumerable; + + if (enumerableType.GetInterface(typeof(IDictionary<,>)) is Type dictionaryInterface) + ElementType = dictionaryInterface.GetGenericArguments()[1]; + else if (enumerableType.GetInterface(typeof(IEnumerable<>)) is Type enumerableInterface) + ElementType = enumerableInterface.GetGenericArguments()[0]; + else + ElementType = typeof(object); + } - /// - public object ObjectValue { get; private set; } + /// + public object ObjectValue { get; private set; } - public Type ElementType { get; } + public Type ElementType { get; } - /// - /// Gets whether this reference enumerates a dictionary collection. - /// - public bool IsDictionary => ObjectValue is IDictionary || ObjectValue.GetType().HasInterface(typeof(IDictionary<,>)); + /// + /// Gets whether this reference enumerates a dictionary collection. + /// + public bool IsDictionary => ObjectValue is IDictionary || ObjectValue.GetType().HasInterface(typeof(IDictionary<,>)); - /// - public int Count => items?.Count ?? 0; + /// + public int Count => items?.Count ?? 0; - /// - /// Gets the indices of each reference in this instance. - /// - internal IReadOnlyCollection Indices { get; private set; } + /// + /// Gets the indices of each reference in this instance. + /// + internal IReadOnlyCollection Indices { get; private set; } - /// - public ObjectReference this[NodeIndex index] => items[index]; + /// + public ObjectReference this[NodeIndex index] => items[index]; - /// - /// Indicates whether the reference contains the given index. - /// - /// The index to check. - /// True if the reference contains the given index, False otherwise. - /// If it is an it will return true only for null. - public bool HasIndex(NodeIndex index) - { - return items?.ContainsKey(index) ?? false; - } + /// + /// Indicates whether the reference contains the given index. + /// + /// The index to check. + /// True if the reference contains the given index, False otherwise. + /// If it is an it will return true only for null. + public bool HasIndex(NodeIndex index) + { + return items?.ContainsKey(index) ?? false; + } - public void Refresh(IGraphNode ownerNode, NodeContainer nodeContainer) - { - var newObjectValue = ownerNode.Retrieve(); - if (!(newObjectValue is IEnumerable)) throw new ArgumentException(@"The object is not an IEnumerable", nameof(newObjectValue)); + public void Refresh(IGraphNode ownerNode, NodeContainer nodeContainer) + { + var newObjectValue = ownerNode.Retrieve(); + if (newObjectValue is not IEnumerable) throw new ArgumentException("The object is not an IEnumerable", nameof(ownerNode)); - ObjectValue = newObjectValue; + ObjectValue = newObjectValue; - var newReferences = new HybridDictionary(); - if (IsDictionary) + var newReferences = new HybridDictionary(); + if (IsDictionary) + { + foreach (var item in (IEnumerable)ObjectValue) { - foreach (var item in (IEnumerable)ObjectValue) - { - var key = GetKey(item); - var value = (ObjectReference)Reference.CreateReference(GetValue(item), ElementType, key, true); - newReferences.Add(key, value); - } + var key = GetKey(item); + var value = (ObjectReference)Reference.CreateReference(GetValue(item), ElementType, key, true); + newReferences.Add(key, value); } - else + } + else + { + var i = 0; + foreach (var item in (IEnumerable)ObjectValue) { - var i = 0; - foreach (var item in (IEnumerable)ObjectValue) - { - var key = new NodeIndex(i); - var value = (ObjectReference)Reference.CreateReference(item, ElementType, key, true); - newReferences.Add(key, value); - ++i; - } + var key = new NodeIndex(i); + var value = (ObjectReference)Reference.CreateReference(item, ElementType, key, true); + newReferences.Add(key, value); + ++i; } + } - // The reference need to be updated if it has never been initialized, if the number of items is different, or if any index or any value is different. - var needUpdate = items == null || newReferences.Count != items.Count || !AreItemsEqual(items, newReferences); - if (needUpdate) + // The reference need to be updated if it has never been initialized, if the number of items is different, or if any index or any value is different. + var needUpdate = items == null || newReferences.Count != items.Count || !AreItemsEqual(items, newReferences); + if (needUpdate) + { + // We create a mapping values of the old list of references to their corresponding target node. We use a list because we can have multiple times the same target in items. + var oldReferenceMapping = new List>(); + if (items != null) { - // We create a mapping values of the old list of references to their corresponding target node. We use a list because we can have multiple times the same target in items. - var oldReferenceMapping = new List>(); - if (items != null) + var existingIndices = GraphNodeBase.GetIndices(ownerNode).ToList(); + foreach (var item in items) { - var existingIndices = GraphNodeBase.GetIndices(ownerNode).ToList(); - foreach (var item in items) + // For collection of struct, we need to update the target nodes first so equity comparer will work. Careful tho, we need to skip removed items! + if (item.Value.TargetNode is BoxedNode boxedTarget && existingIndices.Contains(item.Key)) { - var boxedTarget = item.Value.TargetNode as BoxedNode; - // For collection of struct, we need to update the target nodes first so equity comparer will work. Careful tho, we need to skip removed items! - if (boxedTarget != null && existingIndices.Contains(item.Key)) - { - // If we are boxing a struct, we reuse the same nodes if they are type-compatible and just overwrite the struct value. - var value = ownerNode.Retrieve(item.Key); - if (value?.GetType() == item.Value.TargetNode?.Type) - { - boxedTarget.UpdateFromOwner(ownerNode.Retrieve(item.Key)); - } - } - if (item.Value.ObjectValue != null) + // If we are boxing a struct, we reuse the same nodes if they are type-compatible and just overwrite the struct value. + var value = ownerNode.Retrieve(item.Key); + if (value?.GetType() == item.Value.TargetNode?.Type) { - oldReferenceMapping.Add(new KeyValuePair(item.Value.ObjectValue, item.Value)); + boxedTarget.UpdateFromOwner(ownerNode.Retrieve(item.Key)); } } + if (item.Value.ObjectValue != null) + { + oldReferenceMapping.Add(new KeyValuePair(item.Value.ObjectValue, item.Value)); + } } + } - foreach (var newReference in newReferences) + foreach (var newReference in newReferences) + { + if (newReference.Value.ObjectValue != null) { - if (newReference.Value.ObjectValue != null) + var found = false; + var i = 0; + foreach (var item in oldReferenceMapping) { - var found = false; - var i = 0; - foreach (var item in oldReferenceMapping) - { - if (Equals(newReference.Value.ObjectValue, item.Key)) - { - // If this value was already present in the old list of reference, just use the same target node in the new list. - newReference.Value.SetTarget(item.Value.TargetNode); - // Remove consumed existing reference so if there is a second entry with the same "key", it will be the other reference that will be used. - oldReferenceMapping.RemoveAt(i); - found = true; - break; - } - ++i; - } - if (!found) + if (Equals(newReference.Value.ObjectValue, item.Key)) { - // Otherwise, do a full update that will properly initialize the new reference. - newReference.Value.Refresh(ownerNode, nodeContainer, newReference.Key); + // If this value was already present in the old list of reference, just use the same target node in the new list. + newReference.Value.SetTarget(item.Value.TargetNode); + // Remove consumed existing reference so if there is a second entry with the same "key", it will be the other reference that will be used. + oldReferenceMapping.RemoveAt(i); + found = true; + break; } + ++i; + } + if (!found) + { + // Otherwise, do a full update that will properly initialize the new reference. + newReference.Value.Refresh(ownerNode, nodeContainer, newReference.Key); } } - items = newReferences; - // Remark: this works because both KeyCollection and List implements IReadOnlyCollection. Any internal change to HybridDictionary might break this! - Indices = (IReadOnlyCollection)newReferences.Keys; } + items = newReferences; + // Remark: this works because both KeyCollection and List implements IReadOnlyCollection. Any internal change to HybridDictionary might break this! + Indices = (IReadOnlyCollection)newReferences.Keys; } + } - /// - public ReferenceEnumerator GetEnumerator() => new ReferenceEnumerator(this); - - IEnumerator IEnumerable.GetEnumerator() - { - return new ReferenceEnumerator(this); - } + /// + public ReferenceEnumerator GetEnumerator() => new(this); - /// - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - /// - public bool Equals(IReference other) - { - var otherEnumerable = other as ReferenceEnumerable; - if (otherEnumerable == null) - return false; + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - return ReferenceEquals(this, otherEnumerable) || AreItemsEqual(items, otherEnumerable.items); - } + /// + public bool Equals(IReference? other) + { + if (other is not ReferenceEnumerable otherEnumerable) + return false; + + return ReferenceEquals(this, otherEnumerable) || AreItemsEqual(items, otherEnumerable.items); + } + + private static bool AreItemsEqual(HybridDictionary items1, HybridDictionary items2) + { + if (ReferenceEquals(items1, items2)) + return true; + + if (items1 == null || items2 == null) + return false; - private static bool AreItemsEqual(HybridDictionary items1, HybridDictionary items2) + if (items1.Count != items2.Count) + return false; + + foreach (var item in items1) { - if (ReferenceEquals(items1, items2)) - return true; + if (!items2.TryGetValue(item.Key, out var otherValue)) + return false; - if (items1 == null || items2 == null) + if (!otherValue.Index.Equals(item.Value.Index)) return false; - if (items1.Count != items2.Count) + if (otherValue.ObjectValue == null && item.Value.ObjectValue != null) return false; - foreach (var item in items1) - { - ObjectReference otherValue; - if (!items2.TryGetValue(item.Key, out otherValue)) - return false; + if (otherValue.ObjectValue?.Equals(item.Value.ObjectValue) == false) + return false; + } - if (!otherValue.Index.Equals(item.Value.Index)) - return false; + return true; + } - if (otherValue.ObjectValue == null && item.Value.ObjectValue != null) - return false; + /// + public override string ToString() + { + string text = "(" + items.Count + " references"; + if (items.Count > 0) + { + text += ": "; + text += string.Join(", ", items.Values); + } + text += ")"; + return text; + } - if (otherValue.ObjectValue != null && !otherValue.ObjectValue.Equals(item.Value.ObjectValue)) - return false; - } + private static NodeIndex GetKey(object keyValuePair) + { + var type = keyValuePair.GetType(); + if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(KeyValuePair<,>)) throw new ArgumentException("The given object is not a KeyValuePair."); + var keyProperty = type.GetProperty(nameof(KeyValuePair.Key)); + return new NodeIndex(keyProperty?.GetValue(keyValuePair)); + } - return true; + private static object? GetValue(object keyValuePair) + { + var type = keyValuePair.GetType(); + if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(KeyValuePair<,>)) throw new ArgumentException("The given object is not a KeyValuePair."); + var valueProperty = type.GetProperty(nameof(KeyValuePair.Value)); + return valueProperty?.GetValue(keyValuePair); + } - } + /// + /// An enumerator for that enumerates in proper item order. + /// + public readonly struct ReferenceEnumerator : IEnumerator + { + private readonly IEnumerator indexEnumerator; + private readonly ReferenceEnumerable obj; - /// - public override string ToString() + public ReferenceEnumerator(ReferenceEnumerable obj) { - string text = "(" + items.Count + " references"; - if (items.Count > 0) - { - text += ": "; - text += string.Join(", ", items.Values); - } - text += ")"; - return text; + this.obj = obj; + indexEnumerator = obj.Indices.GetEnumerator(); } - private static NodeIndex GetKey([NotNull] object keyValuePair) + public readonly void Dispose() { - var type = keyValuePair.GetType(); - if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(KeyValuePair<,>)) throw new ArgumentException("The given object is not a KeyValuePair."); - var keyProperty = type.GetProperty(nameof(KeyValuePair.Key)); - return new NodeIndex(keyProperty.GetValue(keyValuePair)); + indexEnumerator.Dispose(); } - private static object GetValue([NotNull] object keyValuePair) + public readonly bool MoveNext() { - var type = keyValuePair.GetType(); - if (!type.IsGenericType || type.GetGenericTypeDefinition() != typeof(KeyValuePair<,>)) throw new ArgumentException("The given object is not a KeyValuePair."); - var valueProperty = type.GetProperty(nameof(KeyValuePair.Value)); - return valueProperty.GetValue(keyValuePair); + return indexEnumerator.MoveNext(); } - /// - /// An enumerator for that enumerates in proper item order. - /// - public struct ReferenceEnumerator : IEnumerator + public readonly void Reset() { - private readonly IEnumerator indexEnumerator; - private ReferenceEnumerable obj; - - public ReferenceEnumerator([NotNull] ReferenceEnumerable obj) - { - this.obj = obj; - indexEnumerator = obj.Indices.GetEnumerator(); - } - - public void Dispose() - { - obj = null; - indexEnumerator.Dispose(); - } - - public bool MoveNext() - { - return indexEnumerator.MoveNext(); - } - - public void Reset() - { - indexEnumerator.Reset(); - } + indexEnumerator.Reset(); + } - public ObjectReference Current => obj.items[indexEnumerator.Current]; + public readonly ObjectReference Current => obj.items[indexEnumerator.Current]; - object IEnumerator.Current => obj.items[indexEnumerator.Current]; - } + readonly object IEnumerator.Current => obj.items[indexEnumerator.Current]; } } diff --git a/sources/presentation/Stride.Core.Quantum/Stride.Core.Quantum.csproj b/sources/presentation/Stride.Core.Quantum/Stride.Core.Quantum.csproj index b664ad9c61..e1cc3c6498 100644 --- a/sources/presentation/Stride.Core.Quantum/Stride.Core.Quantum.csproj +++ b/sources/presentation/Stride.Core.Quantum/Stride.Core.Quantum.csproj @@ -1,11 +1,13 @@ - true --auto-module-initializer --serialization $(StrideXplatEditorTargetFramework) + enable + latest + enable @@ -19,5 +21,4 @@ - - \ No newline at end of file + From ae12b9ff7d7576dadb6ef3bc05acdc88537b2be7 Mon Sep 17 00:00:00 2001 From: Nicolas Musset Date: Mon, 24 Feb 2025 19:35:15 +0100 Subject: [PATCH 4/6] [Core.Quantum.Tests] Modernize code --- .../Stride.Core.Quantum.Tests/Helper.cs | 314 +++--- .../Stride.Core.Quantum.Tests/Module.cs | 14 +- .../Obsolete/ObsoleteTestDictionaries.cs | 213 ++-- .../Obsolete/ObsoleteTestLists.cs | 313 +++--- .../Stride.Core.Quantum.Tests.csproj | 2 + .../TestCollections.cs | 324 +++--- .../TestDynamicNode.cs | 929 +++++++++-------- .../TestGraphNodeChangeListener.cs | 966 +++++++++--------- .../TestGraphNodeLinker.cs | 543 +++++----- .../TestGraphNodePath.cs | 673 ++++++------ .../TestGraphVisitor.cs | 828 ++++++++------- .../TestPrimitiveMembers.cs | 459 +++++---- .../TestReferences.cs | 744 +++++++------- .../Stride.Core.Quantum.Tests/TestStructs.cs | 476 +++++---- 14 files changed, 3389 insertions(+), 3409 deletions(-) diff --git a/sources/presentation/Stride.Core.Quantum.Tests/Helper.cs b/sources/presentation/Stride.Core.Quantum.Tests/Helper.cs index c21d312709..e6a4b10fa1 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/Helper.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/Helper.cs @@ -1,182 +1,180 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; + using System.Collections; -using System.Linq; -using Xunit; using Stride.Core.Quantum.References; +using Xunit; -namespace Stride.Core.Quantum.Tests +namespace Stride.Core.Quantum.Tests; + +public static class Helper { - public static class Helper + /// + /// Tests the validity of a node that is an object that is not a collection + /// + /// The node to validate. + /// The value represented by this node. + /// The number of members expected in the node. + public static void TestNonCollectionObjectNode(IGraphNode node, object obj, int childCount) { - /// - /// Tests the validity of a node that is an object that is not a collection - /// - /// The node to validate. - /// The value represented by this node. - /// The number of members expected in the node. - public static void TestNonCollectionObjectNode(IGraphNode node, object obj, int childCount) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - if (obj == null) throw new ArgumentNullException(nameof(obj)); + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(obj); - // Check that the content is of the expected type. - Assert.IsAssignableFrom(node); - // A node with an ObjectNode should have the related object as value of its content. - Assert.Equal(obj, node.Retrieve()); - // A node with an ObjectNode should not contain a reference if it does not represent a collection. - Assert.False(node.IsReference); - // Check that we have the expected number of children. - Assert.Equal(childCount, ((IObjectNode)node).Members.Count); - } - - /// - /// Tests the validity of a node that is an object that is a collection - /// - /// The node to validate. - /// The value represented by this node. - /// Indicate whether the node is expected to contain an enumerable reference to the collection items. - public static void TestCollectionObjectContentNode(IGraphNode node, object obj, bool isReference) - { - if (node == null) throw new ArgumentNullException(nameof(node)); - if (obj == null) throw new ArgumentNullException(nameof(obj)); + // Check that the content is of the expected type. + Assert.IsAssignableFrom(node); + // A node with an ObjectNode should have the related object as value of its content. + Assert.Equal(obj, node.Retrieve()); + // A node with an ObjectNode should not contain a reference if it does not represent a collection. + Assert.False(node.IsReference); + // Check that we have the expected number of children. + Assert.Equal(childCount, ((IObjectNode)node).Members.Count); + } - // Check that the content is of the expected type. - Assert.IsAssignableFrom(node); - // A node with an ObjectNode should have the related object as value of its content. - Assert.Equal(obj, node.Retrieve()); - if (isReference) - { - // A node with an ObjectNode representing a collection of reference types should contain an enumerable reference. - Assert.True(node.IsReference); - Assert.NotNull(((IObjectNode)node).ItemReferences); - } - else - { - // A node with an ObjectNode representing a collection of primitive or struct types should not contain a refernce. - Assert.False(node.IsReference); - } - // A node with an ObjectNode representing a collection should not have any child. - Assert.Equal(0, ((IObjectNode)node).Members.Count); - } + /// + /// Tests the validity of a node that is an object that is a collection + /// + /// The node to validate. + /// The value represented by this node. + /// Indicate whether the node is expected to contain an enumerable reference to the collection items. + public static void TestCollectionObjectContentNode(IGraphNode node, object obj, bool isReference) + { + ArgumentNullException.ThrowIfNull(node); + ArgumentNullException.ThrowIfNull(obj); - /// - /// Tests the validity of a node that is a member of an object. - /// - /// The node of the container of this member. - /// The memeber node to validate. - /// The value represented by the container node. - /// The value of the member represented by the member node. - /// The name of the member to validate. - /// Indicate whether the member node is expected to contain a reference to the value it represents. - public static void TestMemberNode(IGraphNode containerNode, IGraphNode memberNode, object container, object member, string memberName, bool isReference) + // Check that the content is of the expected type. + Assert.IsAssignableFrom(node); + // A node with an ObjectNode should have the related object as value of its content. + Assert.Equal(obj, node.Retrieve()); + if (isReference) { - if (containerNode == null) throw new ArgumentNullException(nameof(containerNode)); - if (memberNode == null) throw new ArgumentNullException(nameof(memberNode)); - if (container == null) throw new ArgumentNullException(nameof(container)); - - // Check that the content is of the expected type. - Assert.Equal(typeof(MemberNode), memberNode.GetType()); - // A node with a MemberNode should have the same name that the member in the container. - Assert.Equal(memberName, ((IMemberNode)memberNode).Name); - // A node with a MemberNode should have its container as parent. - Assert.Equal(containerNode, ((IMemberNode)memberNode).Parent); - // A node with a MemberNode should have the member value as value of its content. - Assert.Equal(member, memberNode.Retrieve()); - // A node with a primitive MemberNode should not contain a reference. - Assert.Equal(isReference, memberNode.IsReference); + // A node with an ObjectNode representing a collection of reference types should contain an enumerable reference. + Assert.True(node.IsReference); + Assert.NotNull(((IObjectNode)node).ItemReferences); } - - /// - /// Tests the validity of a reference to a non-null target object. - /// - /// The reference to test. - /// The actual value pointed by the reference. - /// Indicates whether the reference has an index. - public static void TestNonNullObjectReference(ObjectReference reference, object targetValue, bool hasIndex) + else { - // Check that the reference is not null. - Assert.NotNull(reference); - // Check that the values match. - Assert.Equal(targetValue, reference.TargetNode.Retrieve()); - // Check that the values match. - Assert.Equal(targetValue, reference.ObjectValue); - // Check that that we have an index if expected. - Assert.Equal(hasIndex, !reference.Index.IsEmpty); - // Check that the target is an object content node. - TestNonCollectionObjectNode(reference.TargetNode, targetValue, reference.TargetNode.Members.Count); + // A node with an ObjectNode representing a collection of primitive or struct types should not contain a refernce. + Assert.False(node.IsReference); } + // A node with an ObjectNode representing a collection should not have any child. + Assert.Empty(((IObjectNode)node).Members); + } - /// - /// Tests the validity of a reference to a non-null target node. - /// - /// The reference to test. - /// The node that is supposed to be the target of the reference. - /// The actual value pointed by the reference. - public static void TestNonNullObjectReference(ObjectReference reference, IGraphNode targetNode, object targetValue) - { - if (targetNode == null) throw new ArgumentNullException(nameof(targetNode)); + /// + /// Tests the validity of a node that is a member of an object. + /// + /// The node of the container of this member. + /// The memeber node to validate. + /// The value represented by the container node. + /// The value of the member represented by the member node. + /// The name of the member to validate. + /// Indicate whether the member node is expected to contain a reference to the value it represents. + public static void TestMemberNode(IGraphNode containerNode, IGraphNode memberNode, object container, object member, string memberName, bool isReference) + { + ArgumentNullException.ThrowIfNull(containerNode); + ArgumentNullException.ThrowIfNull(memberNode); + ArgumentNullException.ThrowIfNull(container); - // Check that the reference is not null. - Assert.NotNull(reference); - // Check that the target node is of the expected type. - Assert.IsType(targetNode); - // Check that the Guids match. - Assert.Equal(targetNode.Guid, reference.TargetGuid); - // Check that the nodes match. - Assert.Equal(targetNode, reference.TargetNode); - // Check that the values match. - Assert.Equal(targetValue, reference.ObjectValue); - // Check that the target is an object content node. - TestNonCollectionObjectNode(targetNode, targetValue, ((IObjectNode)targetNode).Members.Count); - } + // Check that the content is of the expected type. + Assert.Equal(typeof(MemberNode), memberNode.GetType()); + // A node with a MemberNode should have the same name that the member in the container. + Assert.Equal(memberName, ((IMemberNode)memberNode).Name); + // A node with a MemberNode should have its container as parent. + Assert.Equal(containerNode, ((IMemberNode)memberNode).Parent); + // A node with a MemberNode should have the member value as value of its content. + Assert.Equal(member, memberNode.Retrieve()); + // A node with a primitive MemberNode should not contain a reference. + Assert.Equal(isReference, memberNode.IsReference); + } - /// - /// Tests the validity of a reference to a null target object. - /// - /// The reference to test. - public static void TestNullObjectReference(ObjectReference reference) - { - // Check that the reference is not null. - Assert.NotNull(reference); - // Check that the Guids match. - Assert.Equal(Guid.Empty, reference.TargetGuid); - // Check that the nodes match. - Assert.Null(reference.TargetNode); - // Check that the values match. - Assert.Null(reference.ObjectValue); - } + /// + /// Tests the validity of a reference to a non-null target object. + /// + /// The reference to test. + /// The actual value pointed by the reference. + /// Indicates whether the reference has an index. + public static void TestNonNullObjectReference(ObjectReference reference, object targetValue, bool hasIndex) + { + // Check that the reference is not null. + Assert.NotNull(reference); + // Check that the values match. + Assert.Equal(targetValue, reference.TargetNode.Retrieve()); + // Check that the values match. + Assert.Equal(targetValue, reference.ObjectValue); + // Check that that we have an index if expected. + Assert.Equal(hasIndex, !reference.Index.IsEmpty); + // Check that the target is an object content node. + TestNonCollectionObjectNode(reference.TargetNode, targetValue, reference.TargetNode.Members.Count); + } - /// - /// Tests the validity of an enumerable reference. - /// - /// The reference to test. - /// The actual collection pointed by the reference. - public static void TestReferenceEnumerable(ReferenceEnumerable reference, object targetValue) - { - var collection = (ICollection)targetValue; + /// + /// Tests the validity of a reference to a non-null target node. + /// + /// The reference to test. + /// The node that is supposed to be the target of the reference. + /// The actual value pointed by the reference. + public static void TestNonNullObjectReference(ObjectReference reference, IGraphNode targetNode, object targetValue) + { + ArgumentNullException.ThrowIfNull(targetNode); + + // Check that the reference is not null. + Assert.NotNull(reference); + // Check that the target node is of the expected type. + Assert.IsType(targetNode); + // Check that the Guids match. + Assert.Equal(targetNode.Guid, reference.TargetGuid); + // Check that the nodes match. + Assert.Equal(targetNode, reference.TargetNode); + // Check that the values match. + Assert.Equal(targetValue, reference.ObjectValue); + // Check that the target is an object content node. + TestNonCollectionObjectNode(targetNode, targetValue, ((IObjectNode)targetNode).Members.Count); + } + + /// + /// Tests the validity of a reference to a null target object. + /// + /// The reference to test. + public static void TestNullObjectReference(ObjectReference reference) + { + // Check that the reference is not null. + Assert.NotNull(reference); + // Check that the Guids match. + Assert.Equal(Guid.Empty, reference.TargetGuid); + // Check that the nodes match. + Assert.Null(reference.TargetNode); + // Check that the values match. + Assert.Null(reference.ObjectValue); + } + + /// + /// Tests the validity of an enumerable reference. + /// + /// The reference to test. + /// The actual collection pointed by the reference. + public static void TestReferenceEnumerable(ReferenceEnumerable reference, object targetValue) + { + var collection = (ICollection)targetValue; - // Check that the reference is not null. - Assert.NotNull(reference); - // Check that the counts match. - Assert.Equal(collection.Count, reference.Count); - Assert.Equal(collection.Count, reference.Indices.Count); - // Check that the object references match. - foreach (var objReference in reference.Zip(collection.Cast(), Tuple.Create)) + // Check that the reference is not null. + Assert.NotNull(reference); + // Check that the counts match. + Assert.Equal(collection.Count, reference.Count); + Assert.Equal(collection.Count, reference.Indices.Count); + // Check that the object references match. + foreach (var objReference in reference.Zip(collection.Cast(), Tuple.Create)) + { + Assert.Equal(objReference.Item2, objReference.Item1.ObjectValue); + if (objReference.Item2 != null) + { + TestNonNullObjectReference(objReference.Item1, objReference.Item2, true); + } + else { - Assert.Equal(objReference.Item2, objReference.Item1.ObjectValue); - if (objReference.Item2 != null) - { - TestNonNullObjectReference(objReference.Item1, objReference.Item2, true); - } - else - { - TestNullObjectReference(objReference.Item1); - } + TestNullObjectReference(objReference.Item1); } - // TODO: rework reference system and enable this - //Assert.Null(reference.Index); } + // TODO: rework reference system and enable this + //Assert.Null(reference.Index); } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/Module.cs b/sources/presentation/Stride.Core.Quantum.Tests/Module.cs index f5f7e9e7a6..1976b36760 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/Module.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/Module.cs @@ -1,17 +1,15 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core; using Stride.Core.Reflection; -namespace Stride.Core.Quantum.Tests +namespace Stride.Core.Quantum.Tests; + +public class Module { - public class Module + [ModuleInitializer] + internal static void Initialize() { - [ModuleInitializer] - internal static void Initialize() - { - ShadowObject.Enable = true; - } + ShadowObject.Enable = true; } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/Obsolete/ObsoleteTestDictionaries.cs b/sources/presentation/Stride.Core.Quantum.Tests/Obsolete/ObsoleteTestDictionaries.cs index a7cada1ca8..7b0aad812b 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/Obsolete/ObsoleteTestDictionaries.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/Obsolete/ObsoleteTestDictionaries.cs @@ -2,129 +2,128 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System.Collections.Generic; -using Xunit; using Stride.Core; using Stride.Core.Quantum.References; +using Xunit; -namespace Stride.Core.Quantum.Tests.Obsolete +namespace Stride.Core.Quantum.Tests.Obsolete; + +class ObsoleteTestDictionaries { - class ObsoleteTestDictionaries + #region Test class definitions + public class SimpleClass { - #region Test class definitions - public class SimpleClass - { - [DataMember(1)] - public int FirstValue { get; set; } + [DataMember(1)] + public int FirstValue { get; set; } - [DataMember(2)] - public int SecondValue { get; set; } - } + [DataMember(2)] + public int SecondValue { get; set; } + } - public struct SimpleStruct - { - [DataMember(1)] - public int FirstValue { get; set; } + public struct SimpleStruct + { + [DataMember(1)] + public int FirstValue { get; set; } - [DataMember(2)] - public int SecondValue { get; set; } - } + [DataMember(2)] + public int SecondValue { get; set; } + } - public struct NestedStruct - { - [DataMember(1)] - public SimpleStruct Struct { get; set; } - } + public struct NestedStruct + { + [DataMember(1)] + public SimpleStruct Struct { get; set; } + } - public class ClassWithDictionaries + public class ClassWithDictionaries + { + public ClassWithDictionaries() { - public ClassWithDictionaries() - { - StringIntDic = new Dictionary { { "a", 1 }, { "b", 2 }, { "c", 3 } }; - StringClassDic = new Dictionary { { "a", new SimpleClass() }, { "b", new SimpleClass() } }; - // TODO: test with primitive struct - // TODO: test with nested struct - //SimpleStructList = new List { new SimpleStruct(), new SimpleStruct() }; - //NestedStructList = new List { new NestedStruct(), new NestedStruct() }; - //ListOfSimpleStructLists = new List> { new List { new SimpleStruct() }, new List { new SimpleStruct() } }; - //ListOfNestedStructLists = new List> { new List { new NestedStruct() }, new List { new NestedStruct() } }; - } - - [DataMember(1)] - public Dictionary StringIntDic { get; private set; } - - [DataMember(2)] - public Dictionary StringClassDic { get; private set; } - - //[DataMember(3)] - //public List SimpleStructList { get; private set; } - - //[DataMember(4)] - //public List NestedStructList { get; private set; } - - //[DataMember(5)] - //public List> ListOfSimpleStructLists { get; private set; } - - //[DataMember(6)] - //public List> ListOfNestedStructLists { get; private set; } + StringIntDic = new Dictionary { { "a", 1 }, { "b", 2 }, { "c", 3 } }; + StringClassDic = new Dictionary { { "a", new SimpleClass() }, { "b", new SimpleClass() } }; + // TODO: test with primitive struct + // TODO: test with nested struct + //SimpleStructList = new List { new SimpleStruct(), new SimpleStruct() }; + //NestedStructList = new List { new NestedStruct(), new NestedStruct() }; + //ListOfSimpleStructLists = new List> { new List { new SimpleStruct() }, new List { new SimpleStruct() } }; + //ListOfNestedStructLists = new List> { new List { new NestedStruct() }, new List { new NestedStruct() } }; } - #endregion Test class definitions - [Fact] - public void TestConstruction() - { - var obj = new ClassWithDictionaries(); - var container = new NodeContainer(); - var model = container.GetOrCreateNode(obj); - - Assert.That(model["StringIntDic"].Retrieve(), Is.SameAs(obj.StringIntDic)); - Assert.That(model["StringIntDic"].IsReference, Is.False); - Assert.That(model["StringClassDic"].Retrieve(), Is.SameAs(obj.StringClassDic)); - //Assert.That(model["StringClassDic"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - var enumerator = obj.StringClassDic.GetEnumerator(); - foreach (var reference in model["StringClassDic"].Target.ItemReferences) - { - enumerator.MoveNext(); - var keyValuePair = enumerator.Current; - Assert.Equal(keyValuePair.Key, reference.Index); - Assert.Equal(keyValuePair.Value, reference.ObjectValue); - } - //Assert.Equal(0, model.GetChild("SimpleStructList").Children.Count); - //Assert.That(model.GetChild("SimpleStructList").Content.Value, Is.SameAs(obj.SimpleStructList)); - //Assert.That(model.GetChild("SimpleStructList").Content.Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - //Assert.Equal(0, model.GetChild("NestedStructList").Children.Count); - //Assert.That(model.GetChild("NestedStructList").Content.Value, Is.SameAs(obj.NestedStructList)); - //Assert.That(model.GetChild("NestedStructList").Content.Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - //Assert.Equal(0, model.GetChild("ListOfSimpleStructLists").Children.Count); - //Assert.That(model.GetChild("ListOfSimpleStructLists").Content.Value, Is.SameAs(obj.ListOfSimpleStructLists)); - //Assert.That(model.GetChild("ListOfSimpleStructLists").Content.Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - //foreach (var reference in (ReferenceEnumerable)model.GetChild("ListOfSimpleStructLists").Content.Reference) - //{ - // Assert.That(reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - //} - //Assert.Equal(0, model.GetChild("ListOfNestedStructLists").Children.Count); - //Assert.That(model.GetChild("ListOfNestedStructLists").Content.Value, Is.SameAs(obj.ListOfNestedStructLists)); - //Assert.That(model.GetChild("ListOfNestedStructLists").Content.Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - //foreach (var reference in (ReferenceEnumerable)model.GetChild("ListOfNestedStructLists").Content.Reference) - //{ - // Assert.That(reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - //} - - //Assert.That(container.GetNode(obj.ClassList[0]), !Is.Null); - //Assert.Equal(10, container.Guids.Count()); - } + [DataMember(1)] + public Dictionary StringIntDic { get; private set; } + + [DataMember(2)] + public Dictionary StringClassDic { get; private set; } + + //[DataMember(3)] + //public List SimpleStructList { get; private set; } + + //[DataMember(4)] + //public List NestedStructList { get; private set; } + + //[DataMember(5)] + //public List> ListOfSimpleStructLists { get; private set; } - [Fact] - public void TestPrimitiveItemUpdate() + //[DataMember(6)] + //public List> ListOfNestedStructLists { get; private set; } + } + #endregion Test class definitions + + [Fact] + public void TestConstruction() + { + var obj = new ClassWithDictionaries(); + var container = new NodeContainer(); + var model = container.GetOrCreateNode(obj); + + Assert.That(model["StringIntDic"].Retrieve(), Is.SameAs(obj.StringIntDic)); + Assert.That(model["StringIntDic"].IsReference, Is.False); + Assert.That(model["StringClassDic"].Retrieve(), Is.SameAs(obj.StringClassDic)); + //Assert.That(model["StringClassDic"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + var enumerator = obj.StringClassDic.GetEnumerator(); + foreach (var reference in model["StringClassDic"].Target.ItemReferences) { - var obj = new ClassWithDictionaries(); - var container = new NodeContainer(); - var model = container.GetOrCreateNode(obj); - ((Dictionary)model["StringIntDic"].Retrieve())["b"] = 42; - ((Dictionary)model["StringIntDic"].Retrieve()).Add("d", 26); - Assert.Equal(4, obj.StringIntDic.Count); - Assert.Equal(42, obj.StringIntDic["b"]); - Assert.Equal(26, obj.StringIntDic["d"]); + enumerator.MoveNext(); + var keyValuePair = enumerator.Current; + Assert.Equal(keyValuePair.Key, reference.Index); + Assert.Equal(keyValuePair.Value, reference.ObjectValue); } + //Assert.Equal(0, model.GetChild("SimpleStructList").Children.Count); + //Assert.That(model.GetChild("SimpleStructList").Content.Value, Is.SameAs(obj.SimpleStructList)); + //Assert.That(model.GetChild("SimpleStructList").Content.Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + //Assert.Equal(0, model.GetChild("NestedStructList").Children.Count); + //Assert.That(model.GetChild("NestedStructList").Content.Value, Is.SameAs(obj.NestedStructList)); + //Assert.That(model.GetChild("NestedStructList").Content.Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + //Assert.Equal(0, model.GetChild("ListOfSimpleStructLists").Children.Count); + //Assert.That(model.GetChild("ListOfSimpleStructLists").Content.Value, Is.SameAs(obj.ListOfSimpleStructLists)); + //Assert.That(model.GetChild("ListOfSimpleStructLists").Content.Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + //foreach (var reference in (ReferenceEnumerable)model.GetChild("ListOfSimpleStructLists").Content.Reference) + //{ + // Assert.That(reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + //} + //Assert.Equal(0, model.GetChild("ListOfNestedStructLists").Children.Count); + //Assert.That(model.GetChild("ListOfNestedStructLists").Content.Value, Is.SameAs(obj.ListOfNestedStructLists)); + //Assert.That(model.GetChild("ListOfNestedStructLists").Content.Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + //foreach (var reference in (ReferenceEnumerable)model.GetChild("ListOfNestedStructLists").Content.Reference) + //{ + // Assert.That(reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + //} + + //Assert.That(container.GetNode(obj.ClassList[0]), !Is.Null); + //Assert.Equal(10, container.Guids.Count()); + } + [Fact] + public void TestPrimitiveItemUpdate() + { + var obj = new ClassWithDictionaries(); + var container = new NodeContainer(); + var model = container.GetOrCreateNode(obj); + ((Dictionary)model["StringIntDic"].Retrieve())["b"] = 42; + ((Dictionary)model["StringIntDic"].Retrieve()).Add("d", 26); + Assert.Equal(4, obj.StringIntDic.Count); + Assert.Equal(42, obj.StringIntDic["b"]); + Assert.Equal(26, obj.StringIntDic["d"]); } + } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/Obsolete/ObsoleteTestLists.cs b/sources/presentation/Stride.Core.Quantum.Tests/Obsolete/ObsoleteTestLists.cs index 8d93458fb6..725c7f818b 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/Obsolete/ObsoleteTestLists.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/Obsolete/ObsoleteTestLists.cs @@ -2,196 +2,195 @@ // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. using System.Collections.Generic; -using Xunit; using Stride.Core; using Stride.Core.Quantum.References; +using Xunit; + +namespace Stride.Core.Quantum.Tests.Obsolete; -namespace Stride.Core.Quantum.Tests.Obsolete +public class ObsoleteTestLists { - public class ObsoleteTestLists + #region Test class definitions + public class SimpleClass { - #region Test class definitions - public class SimpleClass - { - [DataMember(1)] - public int FirstValue { get; set; } + [DataMember(1)] + public int FirstValue { get; set; } - [DataMember(2)] - public int SecondValue { get; set; } - } + [DataMember(2)] + public int SecondValue { get; set; } + } - public struct SimpleStruct - { - [DataMember(1)] - public int FirstValue { get; set; } + public struct SimpleStruct + { + [DataMember(1)] + public int FirstValue { get; set; } - [DataMember(2)] - public int SecondValue { get; set; } - } + [DataMember(2)] + public int SecondValue { get; set; } + } - public struct NestedStruct - { - [DataMember(1)] - public SimpleStruct Struct { get; set; } - } + public struct NestedStruct + { + [DataMember(1)] + public SimpleStruct Struct { get; set; } + } - public class ClassWithLists + public class ClassWithLists + { + public ClassWithLists() { - public ClassWithLists() - { - IntList = new List { 1, 2, 3 }; - ClassList = new List { new SimpleClass() }; - SimpleStructList = new List { new SimpleStruct(), new SimpleStruct() }; - NestedStructList = new List { new NestedStruct(), new NestedStruct() }; - ListOfSimpleStructLists = new List> { new List { new SimpleStruct() }, new List { new SimpleStruct() } }; - ListOfNestedStructLists = new List> { new List { new NestedStruct() }, new List { new NestedStruct() } }; - } + IntList = new List { 1, 2, 3 }; + ClassList = new List { new SimpleClass() }; + SimpleStructList = new List { new SimpleStruct(), new SimpleStruct() }; + NestedStructList = new List { new NestedStruct(), new NestedStruct() }; + ListOfSimpleStructLists = new List> { new List { new SimpleStruct() }, new List { new SimpleStruct() } }; + ListOfNestedStructLists = new List> { new List { new NestedStruct() }, new List { new NestedStruct() } }; + } - [DataMember(1)] - public List IntList { get; } + [DataMember(1)] + public List IntList { get; } - [DataMember(2)] - public List ClassList { get; } + [DataMember(2)] + public List ClassList { get; } - [DataMember(3)] - public List SimpleStructList { get; } + [DataMember(3)] + public List SimpleStructList { get; } - [DataMember(4)] - public List NestedStructList { get; } + [DataMember(4)] + public List NestedStructList { get; } - [DataMember(5)] - public List> ListOfSimpleStructLists { get; } + [DataMember(5)] + public List> ListOfSimpleStructLists { get; } - [DataMember(6)] - public List> ListOfNestedStructLists { get; } - } + [DataMember(6)] + public List> ListOfNestedStructLists { get; } + } - public class ClassWithNullLists - { - [DataMember(1)] - public List IntList { get; set; } + public class ClassWithNullLists + { + [DataMember(1)] + public List IntList { get; set; } - [DataMember(2)] - public List ClassList { get; set; } + [DataMember(2)] + public List ClassList { get; set; } - [DataMember(3)] - public List SimpleStructList { get; set; } + [DataMember(3)] + public List SimpleStructList { get; set; } - [DataMember(4)] - public List NestedStructList { get; set; } + [DataMember(4)] + public List NestedStructList { get; set; } - [DataMember(5)] - public List> ListOfSimpleStructLists { get; set; } + [DataMember(5)] + public List> ListOfSimpleStructLists { get; set; } - [DataMember(6)] - public List> ListOfNestedStructLists { get; set; } - } + [DataMember(6)] + public List> ListOfNestedStructLists { get; set; } + } - #endregion Test class definitions + #endregion Test class definitions - [Fact] - public void TestConstruction() + [Fact] + public void TestConstruction() + { + var obj = new ClassWithLists(); + var container = new NodeContainer(); + var model = container.GetOrCreateNode(obj); + + Assert.That(model["IntList"].Retrieve(), Is.SameAs(obj.IntList)); + Assert.That(model["IntList"].IsReference, Is.False); + Assert.That(model["ClassList"].Retrieve(), Is.SameAs(obj.ClassList)); + //Assert.That(model["ClassList"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + Assert.That(model["SimpleStructList"].Retrieve(), Is.SameAs(obj.SimpleStructList)); + //Assert.That(model["SimpleStructList"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + Assert.That(model["NestedStructList"].Retrieve(), Is.SameAs(obj.NestedStructList)); + //Assert.That(model["NestedStructList"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + Assert.That(model["ListOfSimpleStructLists"].Retrieve(), Is.SameAs(obj.ListOfSimpleStructLists)); + //Assert.That(model["ListOfSimpleStructLists"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + foreach (var reference in model["ListOfSimpleStructLists"].Target.ItemReferences) { - var obj = new ClassWithLists(); - var container = new NodeContainer(); - var model = container.GetOrCreateNode(obj); - - Assert.That(model["IntList"].Retrieve(), Is.SameAs(obj.IntList)); - Assert.That(model["IntList"].IsReference, Is.False); - Assert.That(model["ClassList"].Retrieve(), Is.SameAs(obj.ClassList)); - //Assert.That(model["ClassList"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - Assert.That(model["SimpleStructList"].Retrieve(), Is.SameAs(obj.SimpleStructList)); - //Assert.That(model["SimpleStructList"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - Assert.That(model["NestedStructList"].Retrieve(), Is.SameAs(obj.NestedStructList)); - //Assert.That(model["NestedStructList"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - Assert.That(model["ListOfSimpleStructLists"].Retrieve(), Is.SameAs(obj.ListOfSimpleStructLists)); - //Assert.That(model["ListOfSimpleStructLists"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - foreach (var reference in model["ListOfSimpleStructLists"].Target.ItemReferences) - { - Assert.That(reference, Is.AssignableFrom(typeof(ObjectReference))); - } - Assert.That(model["ListOfNestedStructLists"].Retrieve(), Is.SameAs(obj.ListOfNestedStructLists)); - //Assert.That(model["ListOfNestedStructLists"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); - foreach (var reference in model["ListOfNestedStructLists"].Target.ItemReferences) - { - Assert.That(reference, Is.AssignableFrom(typeof(ObjectReference))); - } - - Assert.That(container.GetNode(obj.ClassList[0]), !Is.Null); + Assert.That(reference, Is.AssignableFrom(typeof(ObjectReference))); } - - [Fact] - public void TestPrimitiveItemUpdate() + Assert.That(model["ListOfNestedStructLists"].Retrieve(), Is.SameAs(obj.ListOfNestedStructLists)); + //Assert.That(model["ListOfNestedStructLists"].Reference, Is.AssignableFrom(typeof(ReferenceEnumerable))); + foreach (var reference in model["ListOfNestedStructLists"].Target.ItemReferences) { - var obj = new ClassWithLists(); - var container = new NodeContainer(); - var model = container.GetOrCreateNode(obj); - ((List)model["IntList"].Retrieve())[1] = 42; - ((List)model["IntList"].Retrieve()).Add(26); - Assert.Equal(4, obj.IntList.Count); - Assert.Equal(42, obj.IntList[1]); - Assert.Equal(26, obj.IntList[3]); + Assert.That(reference, Is.AssignableFrom(typeof(ObjectReference))); } - [Fact] - public void TestObjectItemUpdate() - { - var obj = new ClassWithLists(); - var container = new NodeContainer(); - var model = container.GetOrCreateNode(obj); - //var objRef = ((ReferenceEnumerable)model["ClassList"].Reference).First(); - //objRef.TargetNode["SecondValue"].Update(32); - Assert.Equal(32, obj.ClassList[0].SecondValue); - } + Assert.That(container.GetNode(obj.ClassList[0]), !Is.Null); + } - [Fact] - public void TestStructItemUpdate() - { - var obj = new ClassWithLists(); - var container = new NodeContainer(); - var model = container.GetOrCreateNode(obj); - //var objRef = ((ReferenceEnumerable)model["SimpleStructList"].Reference).First(); - //objRef.TargetNode["SecondValue"].Update(32); - Assert.Equal(32, obj.SimpleStructList[0].SecondValue); - } + [Fact] + public void TestPrimitiveItemUpdate() + { + var obj = new ClassWithLists(); + var container = new NodeContainer(); + var model = container.GetOrCreateNode(obj); + ((List)model["IntList"].Retrieve())[1] = 42; + ((List)model["IntList"].Retrieve()).Add(26); + Assert.Equal(4, obj.IntList.Count); + Assert.Equal(42, obj.IntList[1]); + Assert.Equal(26, obj.IntList[3]); + } - [Fact] - public void TestNestedStructItemUpdate() - { - var obj = new ClassWithLists(); - var container = new NodeContainer(); - var model = container.GetOrCreateNode(obj); - //var objRef = ((ReferenceEnumerable)model["NestedStructList"].Reference).First(); - //var structNode = container.GetNode(((ObjectReference)objRef.TargetNode["Struct"].Reference).TargetGuid); - //structNode["SecondValue"].Update(32); - Assert.Equal(32, obj.NestedStructList[0].Struct.SecondValue); - //var visitor = new ModelConsistencyCheckVisitor(container.NodeBuilder); - //visitor.Check((GraphNode)model, obj, typeof(ClassWithLists), true); - } + [Fact] + public void TestObjectItemUpdate() + { + var obj = new ClassWithLists(); + var container = new NodeContainer(); + var model = container.GetOrCreateNode(obj); + //var objRef = ((ReferenceEnumerable)model["ClassList"].Reference).First(); + //objRef.TargetNode["SecondValue"].Update(32); + Assert.Equal(32, obj.ClassList[0].SecondValue); + } - [Fact] - public void TestListOfSimpleStructListsUpdate() - { - var obj = new ClassWithLists(); - var container = new NodeContainer(); - var model = container.GetOrCreateNode(obj); - //var listRef = ((ReferenceEnumerable)model["ListOfSimpleStructLists"].Reference).Last(); - //var objRef = ((ReferenceEnumerable)listRef.TargetNode.Reference).Last(); - //objRef.TargetNode["SecondValue"].Update(32); - Assert.Equal(32, obj.ListOfSimpleStructLists[1][0].SecondValue); - } + [Fact] + public void TestStructItemUpdate() + { + var obj = new ClassWithLists(); + var container = new NodeContainer(); + var model = container.GetOrCreateNode(obj); + //var objRef = ((ReferenceEnumerable)model["SimpleStructList"].Reference).First(); + //objRef.TargetNode["SecondValue"].Update(32); + Assert.Equal(32, obj.SimpleStructList[0].SecondValue); + } - [Fact] - public void TestListOfNestedStructListsUpdate() - { - var obj = new ClassWithLists(); - var container = new NodeContainer(); - var model = container.GetOrCreateNode(obj); - //var listRef = ((ReferenceEnumerable)model["ListOfNestedStructLists"].Reference).Last(); - //var objRef = ((ReferenceEnumerable)listRef.TargetNode.Reference).Last(); - //var structNode = container.GetNode(((ObjectReference)objRef.TargetNode["Struct"].Reference).TargetGuid); - //structNode["SecondValue"].Update(32); - Assert.Equal(32, obj.ListOfNestedStructLists[1][0].Struct.SecondValue); - } + [Fact] + public void TestNestedStructItemUpdate() + { + var obj = new ClassWithLists(); + var container = new NodeContainer(); + var model = container.GetOrCreateNode(obj); + //var objRef = ((ReferenceEnumerable)model["NestedStructList"].Reference).First(); + //var structNode = container.GetNode(((ObjectReference)objRef.TargetNode["Struct"].Reference).TargetGuid); + //structNode["SecondValue"].Update(32); + Assert.Equal(32, obj.NestedStructList[0].Struct.SecondValue); + //var visitor = new ModelConsistencyCheckVisitor(container.NodeBuilder); + //visitor.Check((GraphNode)model, obj, typeof(ClassWithLists), true); + } + + [Fact] + public void TestListOfSimpleStructListsUpdate() + { + var obj = new ClassWithLists(); + var container = new NodeContainer(); + var model = container.GetOrCreateNode(obj); + //var listRef = ((ReferenceEnumerable)model["ListOfSimpleStructLists"].Reference).Last(); + //var objRef = ((ReferenceEnumerable)listRef.TargetNode.Reference).Last(); + //objRef.TargetNode["SecondValue"].Update(32); + Assert.Equal(32, obj.ListOfSimpleStructLists[1][0].SecondValue); + } + + [Fact] + public void TestListOfNestedStructListsUpdate() + { + var obj = new ClassWithLists(); + var container = new NodeContainer(); + var model = container.GetOrCreateNode(obj); + //var listRef = ((ReferenceEnumerable)model["ListOfNestedStructLists"].Reference).Last(); + //var objRef = ((ReferenceEnumerable)listRef.TargetNode.Reference).Last(); + //var structNode = container.GetNode(((ObjectReference)objRef.TargetNode["Struct"].Reference).TargetGuid); + //structNode["SecondValue"].Update(32); + Assert.Equal(32, obj.ListOfNestedStructLists[1][0].Struct.SecondValue); } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/Stride.Core.Quantum.Tests.csproj b/sources/presentation/Stride.Core.Quantum.Tests/Stride.Core.Quantum.Tests.csproj index 6a0e45d544..80eaa7319c 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/Stride.Core.Quantum.Tests.csproj +++ b/sources/presentation/Stride.Core.Quantum.Tests/Stride.Core.Quantum.Tests.csproj @@ -3,6 +3,8 @@ $(StrideEditorTargetFramework) win-x64 + enable + latest WindowsTools true --auto-module-initializer --serialization diff --git a/sources/presentation/Stride.Core.Quantum.Tests/TestCollections.cs b/sources/presentation/Stride.Core.Quantum.Tests/TestCollections.cs index e64ef4f162..3226d85113 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/TestCollections.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/TestCollections.cs @@ -1,172 +1,170 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using System.Collections; -using Xunit; -using System.Collections.Generic; -using System.Linq; using Stride.Core.Reflection; +using Xunit; + +namespace Stride.Core.Quantum.Tests; -namespace Stride.Core.Quantum.Tests +/// +/// This fixture tests advanced scenario that uses collections. +/// +public class TestCollections { - /// - /// This fixture tests advanced scenario that uses collections. - /// - public class TestCollections + public class SimpleObject + { + public string Name; + public override string ToString() => $"{{SimpleObject: {Name}}}"; + } + + public class ListContainer + { + public List List { get; set; } = []; + public List ObjectList { get; set; } = []; + + public object ObjectMember { get; set; } + } + + [Fact] + public void TestOverwriteCollection() + { + var nodeContainer = new NodeContainer(); + var container = new ListContainer { List = { new SimpleObject(), new SimpleObject() } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var oldList = container.List; + var oldItem1Node = containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(0)); + var oldItem2Node = containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(1)); + containerNode[nameof(ListContainer.List)].Update(new List { new(), new() }); + Assert.NotEqual(oldList, containerNode[nameof(ListContainer.List)].Retrieve()); + Assert.NotEqual(oldItem1Node, containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(0))); + Assert.NotEqual(oldItem1Node.Retrieve(), containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(0)).Retrieve()); + Assert.NotEqual(oldItem2Node, containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(1))); + Assert.NotEqual(oldItem2Node.Retrieve(), containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(1)).Retrieve()); + } + + [Fact] + public void TestMultipleTimesSameObject() + { + SimpleObject[] items = [new(), new()]; + var nodeContainer = new NodeContainer(); + var container = new ListContainer { List = { items[0], items[1], items[0], items[1] } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode[nameof(ListContainer.List)]; + VerifyItem(memberNode, 0, items[0], false); + VerifyItem(memberNode, 1, items[1], false); + VerifyItem(memberNode, 2, items[0], false); + VerifyItem(memberNode, 3, items[1], false); + + memberNode.Target.Add(items[0]); + VerifyItem(memberNode, 0, items[0], false); + VerifyItem(memberNode, 1, items[1], false); + VerifyItem(memberNode, 2, items[0], false); + VerifyItem(memberNode, 3, items[1], false); + VerifyItem(memberNode, 4, items[0], false); + + memberNode.Target.Update(items[1], new NodeIndex(2)); + VerifyItem(memberNode, 0, items[0], false); + VerifyItem(memberNode, 1, items[1], false); + VerifyItem(memberNode, 2, items[1], false); + VerifyItem(memberNode, 3, items[1], false); + VerifyItem(memberNode, 4, items[0], false); + } + + [Fact] + public void TestObjectListWithSimpleObjects() + { + SimpleObject[] items = [new(), new(), new(), new()]; + var obj = new ListContainer { ObjectList = { items[0], items[1] } }; + var nodeContainer = new NodeContainer(); + var containerNode = nodeContainer.GetOrCreateNode(obj); + var memberNode = containerNode[nameof(ListContainer.ObjectList)]; + Helper.TestMemberNode(containerNode, memberNode, obj, obj.ObjectList, nameof(ListContainer.ObjectList), true); + VerifyItem(memberNode, 0, items[0], false); + VerifyItem(memberNode, 1, items[1], false); + + // Add a new item + memberNode.Target.Add(items[2]); + VerifyItem(memberNode, 0, items[0], false); + VerifyItem(memberNode, 1, items[1], false); + VerifyItem(memberNode, 2, items[2], false); + + // Update existing item (with a different type here) + memberNode.Target.Update(items[3], new NodeIndex(2)); + VerifyItem(memberNode, 0, items[0], false); + VerifyItem(memberNode, 1, items[1], false); + VerifyItem(memberNode, 2, items[3], false); + } + + [Fact] + public void TestObjectListWithBoxedPrimitiveValue() + { + var obj = new ListContainer { ObjectList = { 1.0f, 2.0f } }; + var nodeContainer = new NodeContainer(); + + // Construction + var containerNode = nodeContainer.GetOrCreateNode(obj); + var memberNode = containerNode[nameof(ListContainer.ObjectList)]; + Helper.TestMemberNode(containerNode, memberNode, obj, obj.ObjectList, nameof(ListContainer.ObjectList), true); + VerifyItem(memberNode, 0, 1.0f, true); + VerifyItem(memberNode, 1, 2.0f, true); + + // Add a new item + memberNode.Target.Add(3.0f); + VerifyItem(memberNode, 0, 1.0f, true); + VerifyItem(memberNode, 1, 2.0f, true); + VerifyItem(memberNode, 2, 3.0f, true); + + // Update existing item (with a different type here) + memberNode.Target.Update(4.0, new NodeIndex(2)); + VerifyItem(memberNode, 0, 1.0f, true); + VerifyItem(memberNode, 1, 2.0f, true); + VerifyItem(memberNode, 2, 4.0, true); + } + + [Fact(Skip = "Update for object members not fully supported yet")] + public void TestListInObjectMember() + { + var nodeContainer = new NodeContainer(); + var values = new object[] { new SimpleObject(), new List { new(), new() }, new SimpleObject() }; + var container = new ListContainer { ObjectMember = values[0] }; + var containerNode = nodeContainer.GetOrCreateNode(container); + Assert.Equal(container.ObjectMember, containerNode[nameof(ListContainer.ObjectMember)].Retrieve()); + containerNode[nameof(ListContainer.ObjectMember)].Update(values[1]); + Assert.Equal(values[1], container.ObjectMember); + Assert.Equal(container.ObjectMember, containerNode[nameof(ListContainer.ObjectMember)].Retrieve()); + Assert.True(containerNode[nameof(ListContainer.ObjectMember)].IsReference); + Assert.NotNull(containerNode[nameof(ListContainer.ObjectMember)].Target.Indices); + Assert.Equal(2, containerNode[nameof(ListContainer.ObjectMember)].Target.Indices.Count()); + Assert.Equal(new NodeIndex(0), containerNode[nameof(ListContainer.ObjectMember)].Target.Indices.ToList()[0]); + Assert.Equal(new NodeIndex(1), containerNode[nameof(ListContainer.ObjectMember)].Target.Indices.ToList()[1]); + Assert.Equal(TypeDescriptorFactory.Default.Find(typeof(object)), containerNode[nameof(ListContainer.ObjectMember)].Descriptor); + Assert.Equal(((IList)values[1])[0], containerNode[nameof(ListContainer.ObjectMember)].Target.IndexedTarget(new NodeIndex(0))); + Assert.Equal(((IList)values[1])[1], containerNode[nameof(ListContainer.ObjectMember)].Target.IndexedTarget(new NodeIndex(1))); + Assert.Equal(typeof(object), containerNode[nameof(ListContainer.ObjectMember)].Type); + // TODO: more things could be checked! + containerNode[nameof(ListContainer.ObjectMember)].Update(values[2]); + Assert.Equal(values[2], container.ObjectMember); + Assert.Equal(container.ObjectMember, containerNode[nameof(ListContainer.ObjectMember)]); + } + + private static void VerifyItem(IMemberNode listMemberNode, int index, object expectedValue, bool isPrimitive) { - public class SimpleObject - { - public string Name; - public override string ToString() => $"{{SimpleObject: {Name}}}"; - } - - public class ListContainer - { - public List List { get; set; } = new List(); - public List ObjectList { get; set; } = new List(); - - public object ObjectMember { get; set; } - } - - [Fact] - public void TestOverwriteCollection() - { - var nodeContainer = new NodeContainer(); - var container = new ListContainer { List = { new SimpleObject(), new SimpleObject() } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var oldList = container.List; - var oldItem1Node = containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(0)); - var oldItem2Node = containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(1)); - containerNode[nameof(ListContainer.List)].Update(new List { new SimpleObject(), new SimpleObject() }); - Assert.NotEqual(oldList, containerNode[nameof(ListContainer.List)].Retrieve()); - Assert.NotEqual(oldItem1Node, containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(0))); - Assert.NotEqual(oldItem1Node.Retrieve(), containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(0)).Retrieve()); - Assert.NotEqual(oldItem2Node, containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(1))); - Assert.NotEqual(oldItem2Node.Retrieve(), containerNode[nameof(ListContainer.List)].Target.IndexedTarget(new NodeIndex(1)).Retrieve()); - } - - [Fact] - public void TestMultipleTimesSameObject() - { - var items = new[] { new SimpleObject(), new SimpleObject() }; - var nodeContainer = new NodeContainer(); - var container = new ListContainer { List = { items[0], items[1], items[0], items[1] } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode[nameof(ListContainer.List)]; - VerifyItem(memberNode, 0, items[0], false); - VerifyItem(memberNode, 1, items[1], false); - VerifyItem(memberNode, 2, items[0], false); - VerifyItem(memberNode, 3, items[1], false); - - memberNode.Target.Add(items[0]); - VerifyItem(memberNode, 0, items[0], false); - VerifyItem(memberNode, 1, items[1], false); - VerifyItem(memberNode, 2, items[0], false); - VerifyItem(memberNode, 3, items[1], false); - VerifyItem(memberNode, 4, items[0], false); - - memberNode.Target.Update(items[1], new NodeIndex(2)); - VerifyItem(memberNode, 0, items[0], false); - VerifyItem(memberNode, 1, items[1], false); - VerifyItem(memberNode, 2, items[1], false); - VerifyItem(memberNode, 3, items[1], false); - VerifyItem(memberNode, 4, items[0], false); - } - - [Fact] - public void TestObjectListWithSimpleObjects() - { - var items = new[] { new SimpleObject(), new SimpleObject(), new SimpleObject(), new SimpleObject() }; - var obj = new ListContainer { ObjectList = { items[0], items[1] } }; - var nodeContainer = new NodeContainer(); - var containerNode = nodeContainer.GetOrCreateNode(obj); - var memberNode = containerNode[nameof(ListContainer.ObjectList)]; - Helper.TestMemberNode(containerNode, memberNode, obj, obj.ObjectList, nameof(ListContainer.ObjectList), true); - VerifyItem(memberNode, 0, items[0], false); - VerifyItem(memberNode, 1, items[1], false); - - // Add a new item - memberNode.Target.Add(items[2]); - VerifyItem(memberNode, 0, items[0], false); - VerifyItem(memberNode, 1, items[1], false); - VerifyItem(memberNode, 2, items[2], false); - - // Update existing item (with a different type here) - memberNode.Target.Update(items[3], new NodeIndex(2)); - VerifyItem(memberNode, 0, items[0], false); - VerifyItem(memberNode, 1, items[1], false); - VerifyItem(memberNode, 2, items[3], false); - } - - [Fact] - public void TestObjectListWithBoxedPrimitiveValue() - { - var obj = new ListContainer { ObjectList = { 1.0f, 2.0f } }; - var nodeContainer = new NodeContainer(); - - // Construction - var containerNode = nodeContainer.GetOrCreateNode(obj); - var memberNode = containerNode[nameof(ListContainer.ObjectList)]; - Helper.TestMemberNode(containerNode, memberNode, obj, obj.ObjectList, nameof(ListContainer.ObjectList), true); - VerifyItem(memberNode, 0, 1.0f, true); - VerifyItem(memberNode, 1, 2.0f, true); - - // Add a new item - memberNode.Target.Add(3.0f); - VerifyItem(memberNode, 0, 1.0f, true); - VerifyItem(memberNode, 1, 2.0f, true); - VerifyItem(memberNode, 2, 3.0f, true); - - // Update existing item (with a different type here) - memberNode.Target.Update(4.0, new NodeIndex(2)); - VerifyItem(memberNode, 0, 1.0f, true); - VerifyItem(memberNode, 1, 2.0f, true); - VerifyItem(memberNode, 2, 4.0, true); - } - - [Fact(Skip = "Update for object members not fully supported yet")] - public void TestListInObjectMember() - { - var nodeContainer = new NodeContainer(); - var values = new object[] { new SimpleObject(), new List { new SimpleObject(), new SimpleObject() }, new SimpleObject() }; - var container = new ListContainer { ObjectMember = values[0] }; - var containerNode = nodeContainer.GetOrCreateNode(container); - Assert.Equal(container.ObjectMember, containerNode[nameof(ListContainer.ObjectMember)].Retrieve()); - containerNode[nameof(ListContainer.ObjectMember)].Update(values[1]); - Assert.Equal(values[1], container.ObjectMember); - Assert.Equal(container.ObjectMember, containerNode[nameof(ListContainer.ObjectMember)].Retrieve()); - Assert.True(containerNode[nameof(ListContainer.ObjectMember)].IsReference); - Assert.NotNull(containerNode[nameof(ListContainer.ObjectMember)].Target.Indices); - Assert.Equal(2, containerNode[nameof(ListContainer.ObjectMember)].Target.Indices.Count()); - Assert.Equal(new NodeIndex(0), containerNode[nameof(ListContainer.ObjectMember)].Target.Indices.ToList()[0]); - Assert.Equal(new NodeIndex(1), containerNode[nameof(ListContainer.ObjectMember)].Target.Indices.ToList()[1]); - Assert.Equal(TypeDescriptorFactory.Default.Find(typeof(object)), containerNode[nameof(ListContainer.ObjectMember)].Descriptor); - Assert.Equal(((IList)values[1])[0], containerNode[nameof(ListContainer.ObjectMember)].Target.IndexedTarget(new NodeIndex(0))); - Assert.Equal(((IList)values[1])[1], containerNode[nameof(ListContainer.ObjectMember)].Target.IndexedTarget(new NodeIndex(1))); - Assert.Equal(typeof(object), containerNode[nameof(ListContainer.ObjectMember)].Type); - // TODO: more things could be checked! - containerNode[nameof(ListContainer.ObjectMember)].Update(values[2]); - Assert.Equal(values[2], container.ObjectMember); - Assert.Equal(container.ObjectMember, containerNode[nameof(ListContainer.ObjectMember)]); - } - - private static void VerifyItem(IMemberNode listMemberNode, int index, object expectedValue, bool isPrimitive) - { - var targetNode = listMemberNode.Target; - var enumRef = targetNode.ItemReferences; - var indexValue = new NodeIndex(index); - - Assert.NotNull(enumRef); - Assert.NotNull(targetNode.Indices); - Assert.Equal(indexValue, targetNode.Indices.ToList()[index]); - Assert.Equal(indexValue, enumRef.Indices.ToList()[index]); - Assert.Equal(indexValue, enumRef.ToList()[index].Index); - Assert.Equal(expectedValue, enumRef.ToList()[index].ObjectValue); - Assert.NotNull(enumRef.ToList()[index].TargetNode); - Assert.Equal(expectedValue, enumRef.ToList()[index].TargetNode.Retrieve()); - Assert.Equal(TypeDescriptorFactory.Default.Find(expectedValue.GetType()), enumRef.ToList()[index].TargetNode.Descriptor); - Assert.False(enumRef.ToList()[index].TargetNode.IsReference); - Assert.Equal(expectedValue.GetType(), enumRef.ToList()[index].TargetNode.Type); - Assert.Equal(expectedValue, enumRef.ToList()[index].TargetNode.Retrieve()); - } + var targetNode = listMemberNode.Target; + var enumRef = targetNode.ItemReferences; + var indexValue = new NodeIndex(index); + + Assert.NotNull(enumRef); + Assert.NotNull(targetNode.Indices); + Assert.Equal(indexValue, targetNode.Indices.ToList()[index]); + Assert.Equal(indexValue, enumRef.Indices.ToList()[index]); + Assert.Equal(indexValue, enumRef.ToList()[index].Index); + Assert.Equal(expectedValue, enumRef.ToList()[index].ObjectValue); + Assert.NotNull(enumRef.ToList()[index].TargetNode); + Assert.Equal(expectedValue, enumRef.ToList()[index].TargetNode.Retrieve()); + Assert.Equal(TypeDescriptorFactory.Default.Find(expectedValue.GetType()), enumRef.ToList()[index].TargetNode.Descriptor); + Assert.False(enumRef.ToList()[index].TargetNode.IsReference); + Assert.Equal(expectedValue.GetType(), enumRef.ToList()[index].TargetNode.Type); + Assert.Equal(expectedValue, enumRef.ToList()[index].TargetNode.Retrieve()); } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/TestDynamicNode.cs b/sources/presentation/Stride.Core.Quantum.Tests/TestDynamicNode.cs index 8cf240b669..826f8a7c9d 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/TestDynamicNode.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/TestDynamicNode.cs @@ -1,499 +1,498 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using Xunit; -namespace Stride.Core.Quantum.Tests +namespace Stride.Core.Quantum.Tests; + +public class TestDynamicNode { - public class TestDynamicNode + // TODO: test enumeration with the three cases (reference, primitive collection, primitive dictionary) + public class SimpleClass { - // TODO: test enumeration with the three cases (reference, primitive collection, primitive dictionary) - public class SimpleClass - { - public int Member1; - public SimpleClass Member2; - } + public int Member1; + public SimpleClass Member2; + } - public class ComplexClass - { - public int Member1; - public SimpleClass Member2; - public object Member3; - public Struct Member4; - public List Member5; - public List Member6; - public List Member7; - } + public class ComplexClass + { + public int Member1; + public SimpleClass Member2; + public object Member3; + public Struct Member4; + public List Member5; + public List Member6; + public List Member7; + } - public struct Struct - { - public string Member1; - public SimpleClass Member2; - } + public struct Struct + { + public string Member1; + public SimpleClass Member2; + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangePrimitiveMember() - { - var nodeContainer = new NodeContainer(); - var instance = new ComplexClass { Member1 = 3 }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member1, (int)dynNode.Member1); - dynNode.Member1 = 4; - Assert.Equal(4, instance.Member1); - Assert.Equal(instance.Member1, (int)dynNode.Member1); - rootNode[nameof(ComplexClass.Member1)].Update(5); - Assert.Equal(5, instance.Member1); - Assert.Equal(instance.Member1, (int)dynNode.Member1); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangePrimitiveMember() + { + var nodeContainer = new NodeContainer(); + var instance = new ComplexClass { Member1 = 3 }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member1, (int)dynNode.Member1); + dynNode.Member1 = 4; + Assert.Equal(4, instance.Member1); + Assert.Equal(instance.Member1, (int)dynNode.Member1); + rootNode[nameof(ComplexClass.Member1)].Update(5); + Assert.Equal(5, instance.Member1); + Assert.Equal(instance.Member1, (int)dynNode.Member1); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeReferenceMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member2 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); - dynNode.Member2 = obj[1]; - Assert.Equal(obj[1], instance.Member2); - Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); - rootNode[nameof(ComplexClass.Member2)].Update(obj[2]); - Assert.Equal(obj[2], instance.Member2); - Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeReferenceMember() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new(), new()]; + var instance = new ComplexClass { Member2 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); + dynNode.Member2 = obj[1]; + Assert.Equal(obj[1], instance.Member2); + Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); + rootNode[nameof(ComplexClass.Member2)].Update(obj[2]); + Assert.Equal(obj[2], instance.Member2); + Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeReferenceMemberToNull() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), null, new SimpleClass() }; - var instance = new ComplexClass { Member2 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); - dynNode.Member2 = obj[1]; - Assert.Equal(obj[1], instance.Member2); - Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); - rootNode[nameof(ComplexClass.Member2)].Update(obj[2]); - Assert.Equal(obj[2], instance.Member2); - Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeReferenceMemberToNull() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), null, new()]; + var instance = new ComplexClass { Member2 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); + dynNode.Member2 = obj[1]; + Assert.Equal(obj[1], instance.Member2); + Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); + rootNode[nameof(ComplexClass.Member2)].Update(obj[2]); + Assert.Equal(obj[2], instance.Member2); + Assert.Equal(instance.Member2, (SimpleClass)dynNode.Member2); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeBoxedPrimitiveMember() - { - var nodeContainer = new NodeContainer(); - var instance = new ComplexClass { Member3 = 3 }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member3, (int)dynNode.Member3); - dynNode.Member3 = 4; - Assert.Equal(4, instance.Member3); - Assert.Equal(instance.Member3, (int)dynNode.Member3); - rootNode[nameof(ComplexClass.Member3)].Update(5); - Assert.Equal(5, instance.Member3); - Assert.Equal(instance.Member3, (int)dynNode.Member3); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeBoxedPrimitiveMember() + { + var nodeContainer = new NodeContainer(); + var instance = new ComplexClass { Member3 = 3 }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member3, (int)dynNode.Member3); + dynNode.Member3 = 4; + Assert.Equal(4, instance.Member3); + Assert.Equal(instance.Member3, (int)dynNode.Member3); + rootNode[nameof(ComplexClass.Member3)].Update(5); + Assert.Equal(5, instance.Member3); + Assert.Equal(instance.Member3, (int)dynNode.Member3); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeReferenceInObjectMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member3 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member3, (SimpleClass)dynNode.Member3); - dynNode.Member3 = obj[1]; - Assert.Equal(obj[1], instance.Member3); - Assert.Equal(instance.Member3, (SimpleClass)dynNode.Member3); - rootNode[nameof(ComplexClass.Member3)].Update(obj[2]); - Assert.Equal(obj[2], instance.Member3); - Assert.Equal(instance.Member3, (SimpleClass)dynNode.Member3); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeReferenceInObjectMember() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new(), new()]; + var instance = new ComplexClass { Member3 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member3, (SimpleClass)dynNode.Member3); + dynNode.Member3 = obj[1]; + Assert.Equal(obj[1], instance.Member3); + Assert.Equal(instance.Member3, (SimpleClass)dynNode.Member3); + rootNode[nameof(ComplexClass.Member3)].Update(obj[2]); + Assert.Equal(obj[2], instance.Member3); + Assert.Equal(instance.Member3, (SimpleClass)dynNode.Member3); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeStruct() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; - var instance = new ComplexClass { Member4 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member4, (Struct)dynNode.Member4); - Assert.Equal(obj[0].Member1, (string)dynNode.Member4.Member1); - dynNode.Member4 = obj[1]; - Assert.Equal(obj[1], instance.Member4); - Assert.Equal(obj[1].Member1, (string)dynNode.Member4.Member1); - Assert.Equal(instance.Member4, (Struct)dynNode.Member4); - rootNode[nameof(ComplexClass.Member4)].Update(obj[2]); - Assert.Equal(obj[2], instance.Member4); - Assert.Equal(obj[2].Member1, (string)dynNode.Member4.Member1); - Assert.Equal(instance.Member4, (Struct)dynNode.Member4); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeStruct() + { + var nodeContainer = new NodeContainer(); + Struct[] obj = [new() { Member1 = "aa" }, new() { Member1 = "bb" }, new() { Member1 = "cc" }]; + var instance = new ComplexClass { Member4 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member4, (Struct)dynNode.Member4); + Assert.Equal(obj[0].Member1, (string)dynNode.Member4.Member1); + dynNode.Member4 = obj[1]; + Assert.Equal(obj[1], instance.Member4); + Assert.Equal(obj[1].Member1, (string)dynNode.Member4.Member1); + Assert.Equal(instance.Member4, (Struct)dynNode.Member4); + rootNode[nameof(ComplexClass.Member4)].Update(obj[2]); + Assert.Equal(obj[2], instance.Member4); + Assert.Equal(obj[2].Member1, (string)dynNode.Member4.Member1); + Assert.Equal(instance.Member4, (Struct)dynNode.Member4); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeStructMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member4 = new Struct { Member1 = obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member4, (Struct)dynNode.Member4); - Assert.Equal(obj[0], (string)dynNode.Member4.Member1); - dynNode.Member4.Member1 = obj[1]; - Assert.Equal(obj[1], (string)dynNode.Member4.Member1); - Assert.Equal(instance.Member4, (Struct)dynNode.Member4); - rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)].Update(obj[2]); - Assert.Equal(obj[2], (string)dynNode.Member4.Member1); - Assert.Equal(instance.Member4, (Struct)dynNode.Member4); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeStructMember() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member4 = new Struct { Member1 = obj[0] } }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member4, (Struct)dynNode.Member4); + Assert.Equal(obj[0], (string)dynNode.Member4.Member1); + dynNode.Member4.Member1 = obj[1]; + Assert.Equal(obj[1], (string)dynNode.Member4.Member1); + Assert.Equal(instance.Member4, (Struct)dynNode.Member4); + rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)].Update(obj[2]); + Assert.Equal(obj[2], (string)dynNode.Member4.Member1); + Assert.Equal(instance.Member4, (Struct)dynNode.Member4); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangePrimitiveList() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new List { "aa" }, new List { "bb" }, new List { "cc" } }; - var instance = new ComplexClass { Member5 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member5, (List)dynNode.Member5); - Assert.Equal(obj[0], (List)dynNode.Member5); - dynNode.Member5 = obj[1]; - Assert.Equal(instance.Member5, (List)dynNode.Member5); - Assert.Equal(obj[1], (List)dynNode.Member5); - rootNode[nameof(ComplexClass.Member5)].Update(obj[2]); - Assert.Equal(instance.Member5, (List)dynNode.Member5); - Assert.Equal(obj[2], (List)dynNode.Member5); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangePrimitiveList() + { + var nodeContainer = new NodeContainer(); + List[] obj = [["aa"], ["bb"], ["cc"]]; + var instance = new ComplexClass { Member5 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member5, (List)dynNode.Member5); + Assert.Equal(obj[0], (List)dynNode.Member5); + dynNode.Member5 = obj[1]; + Assert.Equal(instance.Member5, (List)dynNode.Member5); + Assert.Equal(obj[1], (List)dynNode.Member5); + rootNode[nameof(ComplexClass.Member5)].Update(obj[2]); + Assert.Equal(instance.Member5, (List)dynNode.Member5); + Assert.Equal(obj[2], (List)dynNode.Member5); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangePrimitiveListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member5 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(obj[0], (string)dynNode.Member5[0]); - dynNode.Member5[0] = obj[1]; - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(obj[1], (string)dynNode.Member5[0]); - rootNode[nameof(ComplexClass.Member5)].Target.Update(obj[2], new NodeIndex(0)); - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(obj[2], (string)dynNode.Member5[0]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangePrimitiveListItem() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member5 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(obj[0], (string)dynNode.Member5[0]); + dynNode.Member5[0] = obj[1]; + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(obj[1], (string)dynNode.Member5[0]); + rootNode[nameof(ComplexClass.Member5)].Target.Update(obj[2], new NodeIndex(0)); + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(obj[2], (string)dynNode.Member5[0]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestAddPrimitiveListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member5 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(obj[0], (string)dynNode.Member5[0]); - dynNode.Member5.Add(obj[1]); - Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); - Assert.Equal(obj[1], (string)dynNode.Member5[1]); - rootNode[nameof(ComplexClass.Member5)].Target.Add(obj[2], new NodeIndex(2)); - Assert.Equal(instance.Member5[2], (string)dynNode.Member5[2]); - Assert.Equal(obj[2], (string)dynNode.Member5[2]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestAddPrimitiveListItem() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member5 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(obj[0], (string)dynNode.Member5[0]); + dynNode.Member5.Add(obj[1]); + Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); + Assert.Equal(obj[1], (string)dynNode.Member5[1]); + rootNode[nameof(ComplexClass.Member5)].Target.Add(obj[2], new NodeIndex(2)); + Assert.Equal(instance.Member5[2], (string)dynNode.Member5[2]); + Assert.Equal(obj[2], (string)dynNode.Member5[2]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestInsertPrimitiveListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member5 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(obj[0], (string)dynNode.Member5[0]); - dynNode.Member5.Insert(obj[1], new NodeIndex(0)); - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); - Assert.Equal(obj[1], (string)dynNode.Member5[0]); - Assert.Equal(obj[0], (string)dynNode.Member5[1]); - rootNode[nameof(ComplexClass.Member5)].Target.Add(obj[2], new NodeIndex(1)); - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); - Assert.Equal(instance.Member5[2], (string)dynNode.Member5[2]); - Assert.Equal(obj[1], (string)dynNode.Member5[0]); - Assert.Equal(obj[2], (string)dynNode.Member5[1]); - Assert.Equal(obj[0], (string)dynNode.Member5[2]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestInsertPrimitiveListItem() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member5 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(obj[0], (string)dynNode.Member5[0]); + dynNode.Member5.Insert(obj[1], new NodeIndex(0)); + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); + Assert.Equal(obj[1], (string)dynNode.Member5[0]); + Assert.Equal(obj[0], (string)dynNode.Member5[1]); + rootNode[nameof(ComplexClass.Member5)].Target.Add(obj[2], new NodeIndex(1)); + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); + Assert.Equal(instance.Member5[2], (string)dynNode.Member5[2]); + Assert.Equal(obj[1], (string)dynNode.Member5[0]); + Assert.Equal(obj[2], (string)dynNode.Member5[1]); + Assert.Equal(obj[0], (string)dynNode.Member5[2]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestRemovePrimitiveListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member5 = new List { obj[0], obj[1], obj[2] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); - Assert.Equal(instance.Member5[2], (string)dynNode.Member5[2]); - Assert.Equal(obj[0], (string)dynNode.Member5[0]); - Assert.Equal(obj[1], (string)dynNode.Member5[1]); - Assert.Equal(obj[2], (string)dynNode.Member5[2]); - dynNode.Member5.Remove(obj[1], new NodeIndex(1)); - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); - Assert.Equal(obj[0], (string)dynNode.Member5[0]); - Assert.Equal(obj[2], (string)dynNode.Member5[1]); - rootNode[nameof(ComplexClass.Member5)].Target.Remove(obj[2], new NodeIndex(1)); - Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); - Assert.Equal(obj[0], (string)dynNode.Member5[0]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestRemovePrimitiveListItem() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member5 = [obj[0], obj[1], obj[2]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); + Assert.Equal(instance.Member5[2], (string)dynNode.Member5[2]); + Assert.Equal(obj[0], (string)dynNode.Member5[0]); + Assert.Equal(obj[1], (string)dynNode.Member5[1]); + Assert.Equal(obj[2], (string)dynNode.Member5[2]); + dynNode.Member5.Remove(obj[1], new NodeIndex(1)); + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(instance.Member5[1], (string)dynNode.Member5[1]); + Assert.Equal(obj[0], (string)dynNode.Member5[0]); + Assert.Equal(obj[2], (string)dynNode.Member5[1]); + rootNode[nameof(ComplexClass.Member5)].Target.Remove(obj[2], new NodeIndex(1)); + Assert.Equal(instance.Member5[0], (string)dynNode.Member5[0]); + Assert.Equal(obj[0], (string)dynNode.Member5[0]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeReferenceList() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new List { new SimpleClass() }, new List { new SimpleClass() }, new List { new SimpleClass() } }; - var instance = new ComplexClass { Member6 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member6, (List)dynNode.Member6); - Assert.Equal(obj[0], (List)dynNode.Member6); - dynNode.Member6 = obj[1]; - Assert.Equal(instance.Member6, (List)dynNode.Member6); - Assert.Equal(obj[1], (List)dynNode.Member6); - rootNode[nameof(ComplexClass.Member6)].Update(obj[2]); - Assert.Equal(instance.Member6, (List)dynNode.Member6); - Assert.Equal(obj[2], (List)dynNode.Member6); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeReferenceList() + { + var nodeContainer = new NodeContainer(); + List[] obj = [[new()], [new()], [new()]]; + var instance = new ComplexClass { Member6 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member6, (List)dynNode.Member6); + Assert.Equal(obj[0], (List)dynNode.Member6); + dynNode.Member6 = obj[1]; + Assert.Equal(instance.Member6, (List)dynNode.Member6); + Assert.Equal(obj[1], (List)dynNode.Member6); + rootNode[nameof(ComplexClass.Member6)].Update(obj[2]); + Assert.Equal(instance.Member6, (List)dynNode.Member6); + Assert.Equal(obj[2], (List)dynNode.Member6); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeReferenceListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member6 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); - dynNode.Member6[0] = obj[1]; - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[0]); - rootNode[nameof(ComplexClass.Member6)].Target.Update(obj[2], new NodeIndex(0)); - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[0]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeReferenceListItem() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new(), new()]; + var instance = new ComplexClass { Member6 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); + dynNode.Member6[0] = obj[1]; + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[0]); + rootNode[nameof(ComplexClass.Member6)].Target.Update(obj[2], new NodeIndex(0)); + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[0]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestAddReferenceListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member6 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); - dynNode.Member6.Add(obj[1]); - Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); - Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[1]); - rootNode[nameof(ComplexClass.Member6)].Target.Add(obj[2], new NodeIndex(2)); - Assert.Equal(instance.Member6[2], (SimpleClass)dynNode.Member6[2]); - Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[2]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestAddReferenceListItem() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new(), new()]; + var instance = new ComplexClass { Member6 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); + dynNode.Member6.Add(obj[1]); + Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); + Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[1]); + rootNode[nameof(ComplexClass.Member6)].Target.Add(obj[2], new NodeIndex(2)); + Assert.Equal(instance.Member6[2], (SimpleClass)dynNode.Member6[2]); + Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[2]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestInsertReferenceListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member6 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); - dynNode.Member6.Insert(obj[1], new NodeIndex(0)); - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); - Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[1]); - rootNode[nameof(ComplexClass.Member6)].Target.Add(obj[2], new NodeIndex(1)); - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); - Assert.Equal(instance.Member6[2], (SimpleClass)dynNode.Member6[2]); - Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[1]); - Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[2]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestInsertReferenceListItem() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new(), new()]; + var instance = new ComplexClass { Member6 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); + dynNode.Member6.Insert(obj[1], new NodeIndex(0)); + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); + Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[1]); + rootNode[nameof(ComplexClass.Member6)].Target.Add(obj[2], new NodeIndex(1)); + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); + Assert.Equal(instance.Member6[2], (SimpleClass)dynNode.Member6[2]); + Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[1]); + Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[2]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestRemoveReferenceListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member6 = new List { obj[0], obj[1], obj[2] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); - Assert.Equal(instance.Member6[2], (SimpleClass)dynNode.Member6[2]); - Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[1]); - Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[2]); - dynNode.Member6.Remove(obj[1], new NodeIndex(1)); - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); - Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[1]); - rootNode[nameof(ComplexClass.Member6)].Target.Remove(obj[2], new NodeIndex(1)); - Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); - Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestRemoveReferenceListItem() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new(), new()]; + var instance = new ComplexClass { Member6 = [obj[0], obj[1], obj[2]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); + Assert.Equal(instance.Member6[2], (SimpleClass)dynNode.Member6[2]); + Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[1], (SimpleClass)dynNode.Member6[1]); + Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[2]); + dynNode.Member6.Remove(obj[1], new NodeIndex(1)); + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(instance.Member6[1], (SimpleClass)dynNode.Member6[1]); + Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[2], (SimpleClass)dynNode.Member6[1]); + rootNode[nameof(ComplexClass.Member6)].Target.Remove(obj[2], new NodeIndex(1)); + Assert.Equal(instance.Member6[0], (SimpleClass)dynNode.Member6[0]); + Assert.Equal(obj[0], (SimpleClass)dynNode.Member6[0]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeReferenceListItemMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { 3, 4, 5 }; - var instance = new ComplexClass { Member6 = new List { new SimpleClass(), new SimpleClass { Member1 = obj[0] } } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member6[1].Member1, (int)dynNode.Member6[1].Member1); - Assert.Equal(obj[0], (int)dynNode.Member6[1].Member1); - dynNode.Member6[1].Member1 = obj[1]; - Assert.Equal(obj[1], (int)dynNode.Member6[1].Member1); - Assert.Equal(instance.Member6[1].Member1, (int)dynNode.Member6[1].Member1); - NodeIndex index = new NodeIndex(1); - rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)].Update(obj[2]); - Assert.Equal(obj[2], (int)dynNode.Member6[1].Member1); - Assert.Equal(instance.Member6[1].Member1, (int)dynNode.Member6[1].Member1); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeReferenceListItemMember() + { + var nodeContainer = new NodeContainer(); + int[] obj = [3, 4, 5]; + var instance = new ComplexClass { Member6 = [new(), new() { Member1 = obj[0] }] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member6[1].Member1, (int)dynNode.Member6[1].Member1); + Assert.Equal(obj[0], (int)dynNode.Member6[1].Member1); + dynNode.Member6[1].Member1 = obj[1]; + Assert.Equal(obj[1], (int)dynNode.Member6[1].Member1); + Assert.Equal(instance.Member6[1].Member1, (int)dynNode.Member6[1].Member1); + NodeIndex index = new NodeIndex(1); + rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)].Update(obj[2]); + Assert.Equal(obj[2], (int)dynNode.Member6[1].Member1); + Assert.Equal(instance.Member6[1].Member1, (int)dynNode.Member6[1].Member1); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeStructList() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new List { new Struct() }, new List { new Struct() }, new List { new Struct() } }; - var instance = new ComplexClass { Member7 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member7, (List)dynNode.Member7); - Assert.Equal(obj[0], (List)dynNode.Member7); - dynNode.Member7 = obj[1]; - Assert.Equal(instance.Member7, (List)dynNode.Member7); - Assert.Equal(obj[1], (List)dynNode.Member7); - rootNode[nameof(ComplexClass.Member7)].Update(obj[2]); - Assert.Equal(instance.Member7, (List)dynNode.Member7); - Assert.Equal(obj[2], (List)dynNode.Member7); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeStructList() + { + var nodeContainer = new NodeContainer(); + List[] obj = [[new()], [new()], [new()]]; + var instance = new ComplexClass { Member7 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member7, (List)dynNode.Member7); + Assert.Equal(obj[0], (List)dynNode.Member7); + dynNode.Member7 = obj[1]; + Assert.Equal(instance.Member7, (List)dynNode.Member7); + Assert.Equal(obj[1], (List)dynNode.Member7); + rootNode[nameof(ComplexClass.Member7)].Update(obj[2]); + Assert.Equal(instance.Member7, (List)dynNode.Member7); + Assert.Equal(obj[2], (List)dynNode.Member7); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeStructListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; - var instance = new ComplexClass { Member7 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); - dynNode.Member7[0] = obj[1]; - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[1], (Struct)dynNode.Member7[0]); - rootNode[nameof(ComplexClass.Member7)].Target.Update(obj[2], new NodeIndex(0)); - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[2], (Struct)dynNode.Member7[0]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeStructListItem() + { + var nodeContainer = new NodeContainer(); + Struct[] obj = [new() { Member1 = "aa" }, new() { Member1 = "bb" }, new() { Member1 = "cc" }]; + var instance = new ComplexClass { Member7 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); + dynNode.Member7[0] = obj[1]; + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[1], (Struct)dynNode.Member7[0]); + rootNode[nameof(ComplexClass.Member7)].Target.Update(obj[2], new NodeIndex(0)); + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[2], (Struct)dynNode.Member7[0]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestAddStructListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; - var instance = new ComplexClass { Member7 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); - dynNode.Member7.Add(obj[1]); - Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); - Assert.Equal(obj[1], (Struct)dynNode.Member7[1]); - rootNode[nameof(ComplexClass.Member7)].Target.Add(obj[2], new NodeIndex(2)); - Assert.Equal(instance.Member7[2], (Struct)dynNode.Member7[2]); - Assert.Equal(obj[2], (Struct)dynNode.Member7[2]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestAddStructListItem() + { + var nodeContainer = new NodeContainer(); + Struct[] obj = [new() { Member1 = "aa" }, new() { Member1 = "bb" }, new() { Member1 = "cc" }]; + var instance = new ComplexClass { Member7 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); + dynNode.Member7.Add(obj[1]); + Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); + Assert.Equal(obj[1], (Struct)dynNode.Member7[1]); + rootNode[nameof(ComplexClass.Member7)].Target.Add(obj[2], new NodeIndex(2)); + Assert.Equal(instance.Member7[2], (Struct)dynNode.Member7[2]); + Assert.Equal(obj[2], (Struct)dynNode.Member7[2]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestInsertStructListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; - var instance = new ComplexClass { Member7 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); - dynNode.Member7.Insert(obj[1], new NodeIndex(0)); - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); - Assert.Equal(obj[1], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[0], (Struct)dynNode.Member7[1]); - rootNode[nameof(ComplexClass.Member7)].Target.Add(obj[2], new NodeIndex(1)); - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); - Assert.Equal(instance.Member7[2], (Struct)dynNode.Member7[2]); - Assert.Equal(obj[1], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[2], (Struct)dynNode.Member7[1]); - Assert.Equal(obj[0], (Struct)dynNode.Member7[2]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestInsertStructListItem() + { + var nodeContainer = new NodeContainer(); + Struct[] obj = [new() { Member1 = "aa" }, new() { Member1 = "bb" }, new() { Member1 = "cc" }]; + var instance = new ComplexClass { Member7 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); + dynNode.Member7.Insert(obj[1], new NodeIndex(0)); + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); + Assert.Equal(obj[1], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[0], (Struct)dynNode.Member7[1]); + rootNode[nameof(ComplexClass.Member7)].Target.Add(obj[2], new NodeIndex(1)); + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); + Assert.Equal(instance.Member7[2], (Struct)dynNode.Member7[2]); + Assert.Equal(obj[1], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[2], (Struct)dynNode.Member7[1]); + Assert.Equal(obj[0], (Struct)dynNode.Member7[2]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestRemoveStructListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; - var instance = new ComplexClass { Member7 = new List { obj[0], obj[1], obj[2] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); - Assert.Equal(instance.Member7[2], (Struct)dynNode.Member7[2]); - Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[1], (Struct)dynNode.Member7[1]); - Assert.Equal(obj[2], (Struct)dynNode.Member7[2]); - dynNode.Member7.Remove(obj[1], new NodeIndex(1)); - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); - Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[2], (Struct)dynNode.Member7[1]); - rootNode[nameof(ComplexClass.Member7)].Target.Remove(obj[2], new NodeIndex(1)); - Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); - Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestRemoveStructListItem() + { + var nodeContainer = new NodeContainer(); + Struct[] obj = [new() { Member1 = "aa" }, new() { Member1 = "bb" }, new() { Member1 = "cc" }]; + var instance = new ComplexClass { Member7 = [obj[0], obj[1], obj[2]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); + Assert.Equal(instance.Member7[2], (Struct)dynNode.Member7[2]); + Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[1], (Struct)dynNode.Member7[1]); + Assert.Equal(obj[2], (Struct)dynNode.Member7[2]); + dynNode.Member7.Remove(obj[1], new NodeIndex(1)); + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(instance.Member7[1], (Struct)dynNode.Member7[1]); + Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[2], (Struct)dynNode.Member7[1]); + rootNode[nameof(ComplexClass.Member7)].Target.Remove(obj[2], new NodeIndex(1)); + Assert.Equal(instance.Member7[0], (Struct)dynNode.Member7[0]); + Assert.Equal(obj[0], (Struct)dynNode.Member7[0]); + } - [Fact(Skip = "DynamicNode will be fixed later")] - public void TestChangeStructListItemMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member7 = new List { new Struct(), new Struct { Member1 = obj[0] } } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var dynNode = DynamicNode.FromNode(rootNode); - Assert.Equal(instance.Member7[1].Member1, (string)dynNode.Member7[1].Member1); - Assert.Equal(obj[0], (string)dynNode.Member7[1].Member1); - dynNode.Member7[1].Member1 = obj[1]; - Assert.Equal(obj[1], (string)dynNode.Member7[1].Member1); - Assert.Equal(instance.Member7[1].Member1, (string)dynNode.Member7[1].Member1); - NodeIndex index = new NodeIndex(1); - rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)].Update(obj[2]); - Assert.Equal(obj[2], (string)dynNode.Member7[1].Member1); - Assert.Equal(instance.Member7[1].Member1, (string)dynNode.Member7[1].Member1); - } + [Fact(Skip = "DynamicNode will be fixed later")] + public void TestChangeStructListItemMember() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member7 = [new(), new() { Member1 = obj[0] }] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var dynNode = DynamicNode.FromNode(rootNode); + Assert.Equal(instance.Member7[1].Member1, (string)dynNode.Member7[1].Member1); + Assert.Equal(obj[0], (string)dynNode.Member7[1].Member1); + dynNode.Member7[1].Member1 = obj[1]; + Assert.Equal(obj[1], (string)dynNode.Member7[1].Member1); + Assert.Equal(instance.Member7[1].Member1, (string)dynNode.Member7[1].Member1); + NodeIndex index = new NodeIndex(1); + rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)].Update(obj[2]); + Assert.Equal(obj[2], (string)dynNode.Member7[1].Member1); + Assert.Equal(instance.Member7[1].Member1, (string)dynNode.Member7[1].Member1); } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodeChangeListener.cs b/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodeChangeListener.cs index d7225c6e34..06cb0aa744 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodeChangeListener.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodeChangeListener.cs @@ -1,521 +1,519 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Xunit; + using Stride.Core.Annotations; +using Xunit; + +namespace Stride.Core.Quantum.Tests; -namespace Stride.Core.Quantum.Tests +public class TestGraphNodeChangeListener { - public class TestGraphNodeChangeListener + public class SimpleClass { - public class SimpleClass - { - public int Member1; - public SimpleClass Member2; - } + public int Member1; + public SimpleClass Member2; + } - public class ComplexClass - { - public int Member1; - public SimpleClass Member2; - public object Member3; - public Struct Member4; - public List Member5; - public List Member6; - public List Member7; - } + public class ComplexClass + { + public int Member1; + public SimpleClass Member2; + public object Member3; + public Struct Member4; + public List Member5; + public List Member6; + public List Member7; + } - public struct Struct - { - public string Member1; - public SimpleClass Member2; - } + public struct Struct + { + public string Member1; + public SimpleClass Member2; + } - [Fact] - public void TestChangePrimitiveMember() - { - var nodeContainer = new NodeContainer(); - var instance = new ComplexClass { Member1 = 3 }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member1)]; - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, 3, 4, x => x.Update(4)); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member1)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, 4, 5, x => x.Update(5)); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member1)]); - } + [Fact] + public void TestChangePrimitiveMember() + { + var nodeContainer = new NodeContainer(); + var instance = new ComplexClass { Member1 = 3 }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member1)]; + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, 3, 4, x => x.Update(4)); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member1)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, 4, 5, x => x.Update(5)); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member1)]); + } - [Fact] - public void TestChangeReferenceMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member2 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member2)]; - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member2)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member2)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member2)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member2)]); - } + [Fact] + public void TestChangeReferenceMember() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new(), new()]; + var instance = new ComplexClass { Member2 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member2)]; + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member2)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member2)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member2)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member2)]); + } - [Fact] - public void TestChangeReferenceMemberToNull() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), null, new SimpleClass() }; - var instance = new ComplexClass { Member2 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member2)]; - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member2)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member2)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member2)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member2)]); - } + [Fact] + public void TestChangeReferenceMemberToNull() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), null, new()]; + var instance = new ComplexClass { Member2 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member2)]; + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member2)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member2)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member2)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member2)]); + } - [Fact] - public void TestChangeBoxedPrimitiveMember() - { - var nodeContainer = new NodeContainer(); - var instance = new ComplexClass { Member3 = 3 }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member3)]; - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member3)], ContentChangeType.ValueChange, NodeIndex.Empty, 3, 4, x => x.Update(4)); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member3)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member3)], ContentChangeType.ValueChange, NodeIndex.Empty, 4, 5, x => x.Update(5)); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member3)]); - } + [Fact] + public void TestChangeBoxedPrimitiveMember() + { + var nodeContainer = new NodeContainer(); + var instance = new ComplexClass { Member3 = 3 }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member3)]; + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member3)], ContentChangeType.ValueChange, NodeIndex.Empty, 3, 4, x => x.Update(4)); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member3)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member3)], ContentChangeType.ValueChange, NodeIndex.Empty, 4, 5, x => x.Update(5)); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member3)]); + } - [Fact] - public void TestChangeReferenceInObjectMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member3 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member3)]; - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member3)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member3)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member3)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member3)]); - } + [Fact] + public void TestChangeReferenceInObjectMember() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new(), new()]; + var instance = new ComplexClass { Member3 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member3)]; + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member3)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member3)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member3)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member3)]); + } - [Fact] - public void TestChangeStruct() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; - var instance = new ComplexClass { Member4 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member4)]; - Assert.Equal("aa", node.Target[nameof(Struct.Member1)].Retrieve()); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal("bb", node.Target[nameof(Struct.Member1)].Retrieve()); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal("cc", node.Target[nameof(Struct.Member1)].Retrieve()); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, "cc", "dd", x => x.Update("dd")); - Assert.Equal("dd", node.Target[nameof(Struct.Member1)].Retrieve()); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)]); - } + [Fact] + public void TestChangeStruct() + { + var nodeContainer = new NodeContainer(); + Struct[] obj = [new() { Member1 = "aa" }, new() { Member1 = "bb" }, new() { Member1 = "cc" }]; + var instance = new ComplexClass { Member4 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member4)]; + Assert.Equal("aa", node.Target[nameof(Struct.Member1)].Retrieve()); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal("bb", node.Target[nameof(Struct.Member1)].Retrieve()); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal("cc", node.Target[nameof(Struct.Member1)].Retrieve()); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, "cc", "dd", x => x.Update("dd")); + Assert.Equal("dd", node.Target[nameof(Struct.Member1)].Retrieve()); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)]); + } - [Fact] - public void TestChangeStructMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member4 = new Struct { Member1 = obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var targetNode = rootNode[nameof(ComplexClass.Member4)].Target; - var node = targetNode[nameof(Struct.Member1)]; - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal(targetNode, rootNode[nameof(ComplexClass.Member4)].Target); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal(targetNode, rootNode[nameof(ComplexClass.Member4)].Target); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)]); - } + [Fact] + public void TestChangeStructMember() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member4 = new Struct { Member1 = obj[0] } }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var targetNode = rootNode[nameof(ComplexClass.Member4)].Target; + var node = targetNode[nameof(Struct.Member1)]; + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal(targetNode, rootNode[nameof(ComplexClass.Member4)].Target); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal(targetNode, rootNode[nameof(ComplexClass.Member4)].Target); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member4)].Target[nameof(Struct.Member1)]); + } - [Fact] - public void TestChangePrimitiveList() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new List { "aa" }, new List { "bb" }, new List { "cc" } }; - var instance = new ComplexClass { Member5 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member5)]; - Assert.Equal("aa", node.Retrieve(new NodeIndex(0))); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member5)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal("bb", node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member5)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal("cc", node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), "cc", "dd", x => x.Update("dd", new NodeIndex(0))); - Assert.Equal("dd", node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); - } + [Fact] + public void TestChangePrimitiveList() + { + var nodeContainer = new NodeContainer(); + List[] obj = [["aa"], ["bb"], ["cc"]]; + var instance = new ComplexClass { Member5 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member5)]; + Assert.Equal("aa", node.Retrieve(new NodeIndex(0))); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member5)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal("bb", node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member5)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal("cc", node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), "cc", "dd", x => x.Update("dd", new NodeIndex(0))); + Assert.Equal("dd", node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); + } - [Fact] - public void TestChangePrimitiveListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member5 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member5)]; - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[0], obj[1], x => x.Update(obj[1], new NodeIndex(0))); - Assert.Equal(obj[1], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[1], obj[2], x => x.Update(obj[2], new NodeIndex(0))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); - } + [Fact] + public void TestChangePrimitiveListItem() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member5 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member5)]; + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[0], obj[1], x => x.Update(obj[1], new NodeIndex(0))); + Assert.Equal(obj[1], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[1], obj[2], x => x.Update(obj[2], new NodeIndex(0))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); + } - [Fact] - public void TestAddPrimitiveListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member5 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member5)]; - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionAdd, new NodeIndex(1), null, obj[1], x => x.Add(obj[1], new NodeIndex(1))); - Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionAdd, new NodeIndex(2), null, obj[2], x => x.Add(obj[2], new NodeIndex(2))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); - } + [Fact] + public void TestAddPrimitiveListItem() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member5 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member5)]; + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionAdd, new NodeIndex(1), null, obj[1], x => x.Add(obj[1], new NodeIndex(1))); + Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionAdd, new NodeIndex(2), null, obj[2], x => x.Add(obj[2], new NodeIndex(2))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); + } - [Fact] - public void TestRemovePrimitiveListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member5 = new List { obj[0], obj[1], obj[2] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member5)]; - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[1], null, x => x.Remove(obj[1], new NodeIndex(1))); - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(1))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[2], null, x => x.Remove(obj[2], new NodeIndex(1))); - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); - } + [Fact] + public void TestRemovePrimitiveListItem() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member5 = [obj[0], obj[1], obj[2]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member5)]; + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[1], null, x => x.Remove(obj[1], new NodeIndex(1))); + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(1))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member5)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[2], null, x => x.Remove(obj[2], new NodeIndex(1))); + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member5)]); + } - [Fact] - public void TestChangeReferenceList() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new List { new SimpleClass() }, new List { new SimpleClass() }, new List { new SimpleClass() } }; - var instance = new ComplexClass { Member6 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member6)]; - Assert.Equal(obj[0][0], node.Retrieve(new NodeIndex(0))); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member6)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal(obj[1][0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member6)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal(obj[2][0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); - var newItem = new SimpleClass(); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[2][0], newItem, x => x.Update(newItem, new NodeIndex(0))); - Assert.Equal(newItem, node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); - } + [Fact] + public void TestChangeReferenceList() + { + var nodeContainer = new NodeContainer(); + List[] obj = [[new()], [new()], [new()]]; + var instance = new ComplexClass { Member6 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member6)]; + Assert.Equal(obj[0][0], node.Retrieve(new NodeIndex(0))); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member6)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal(obj[1][0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member6)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal(obj[2][0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); + var newItem = new SimpleClass(); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[2][0], newItem, x => x.Update(newItem, new NodeIndex(0))); + Assert.Equal(newItem, node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); + } - [Fact] - public void TestChangeReferenceListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member6 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member6)]; - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[0], obj[1], x => x.Update(obj[1], new NodeIndex(0))); - Assert.Equal(obj[1], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[1], obj[2], x => x.Update(obj[2], new NodeIndex(0))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); - } + [Fact] + public void TestChangeReferenceListItem() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new(), new()]; + var instance = new ComplexClass { Member6 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member6)]; + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[0], obj[1], x => x.Update(obj[1], new NodeIndex(0))); + Assert.Equal(obj[1], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[1], obj[2], x => x.Update(obj[2], new NodeIndex(0))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); + } - [Fact] - public void TestAddReferenceListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member6 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member6)]; - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionAdd, new NodeIndex(1), null, obj[1], x => x.Add(obj[1], new NodeIndex(1))); - Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionAdd, new NodeIndex(2), null, obj[2], x => x.Add(obj[2], new NodeIndex(2))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); - } + [Fact] + public void TestAddReferenceListItem() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new SimpleClass(), new SimpleClass(), new SimpleClass()]; + var instance = new ComplexClass { Member6 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member6)]; + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionAdd, new NodeIndex(1), null, obj[1], x => x.Add(obj[1], new NodeIndex(1))); + Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionAdd, new NodeIndex(2), null, obj[2], x => x.Add(obj[2], new NodeIndex(2))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); + } - [Fact] - public void TestRemoveReferenceListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member6 = new List { obj[0], obj[1], obj[2] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member6)]; - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[1], null, x => x.Remove(obj[1], new NodeIndex(1))); - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(1))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[2], null, x => x.Remove(obj[2], new NodeIndex(1))); - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); - } + [Fact] + public void TestRemoveReferenceListItem() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new SimpleClass(), new SimpleClass(), new SimpleClass()]; + var instance = new ComplexClass { Member6 = [obj[0], obj[1], obj[2]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member6)]; + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[1], null, x => x.Remove(obj[1], new NodeIndex(1))); + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(1))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[2], null, x => x.Remove(obj[2], new NodeIndex(1))); + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)]); + } - [Fact] - public void TestChangeReferenceListItemMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { 3, 4, 5 }; - var instance = new ComplexClass { Member6 = new List { new SimpleClass(), new SimpleClass { Member1 = obj[0] } } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - NodeIndex index = new NodeIndex(1); - var node = rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)]; - Assert.Equal(obj[0], node.Retrieve()); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal(obj[1], node.Retrieve()); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal(obj[2], node.Retrieve()); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)]); - } + [Fact] + public void TestChangeReferenceListItemMember() + { + var nodeContainer = new NodeContainer(); + int[] obj = [3, 4, 5]; + var instance = new ComplexClass { Member6 = [new(), new() { Member1 = obj[0] }] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + NodeIndex index = new NodeIndex(1); + var node = rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)]; + Assert.Equal(obj[0], node.Retrieve()); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal(obj[1], node.Retrieve()); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal(obj[2], node.Retrieve()); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member6)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)]); + } - [Fact] - public void TestChangeStructList() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new List { new Struct() }, new List { new Struct() }, new List { new Struct() } }; - var instance = new ComplexClass { Member7 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member7)]; - Assert.Equal(obj[0][0], node.Retrieve(new NodeIndex(0))); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member7)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal(obj[1][0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member7)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal(obj[2][0], node.Retrieve(new NodeIndex(0))); - var newItem = new Struct(); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); - var itemNode = rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0)); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[2][0], newItem, x => x.Update(newItem, new NodeIndex(0))); - Assert.Equal(newItem, node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); - Assert.Equal(itemNode, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0))); - } + [Fact] + public void TestChangeStructList() + { + var nodeContainer = new NodeContainer(); + List[] obj = [[new()], [new()], [new()]]; + var instance = new ComplexClass { Member7 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member7)]; + Assert.Equal(obj[0][0], node.Retrieve(new NodeIndex(0))); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member7)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal(obj[1][0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member7)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal(obj[2][0], node.Retrieve(new NodeIndex(0))); + var newItem = new Struct(); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); + var itemNode = rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0)); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[2][0], newItem, x => x.Update(newItem, new NodeIndex(0))); + Assert.Equal(newItem, node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); + Assert.Equal(itemNode, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0))); + } - [Fact] - public void TestChangeStructListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; - var instance = new ComplexClass { Member7 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member7)]; - var itemNode = rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0)); - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[0], obj[1], x => x.Update(obj[1], new NodeIndex(0))); - Assert.Equal(obj[1], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); - // TODO: would be nice to be able to keep the same boxed node! - Assert.Equal(itemNode, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[1], obj[2], x => x.Update(obj[2], new NodeIndex(0))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); - // TODO: would be nice to be able to keep the same boxed node! - Assert.Equal(itemNode, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0))); - } + [Fact] + public void TestChangeStructListItem() + { + var nodeContainer = new NodeContainer(); + Struct[] obj = [new() { Member1 = "aa" }, new() { Member1 = "bb" }, new() { Member1 = "cc" }]; + var instance = new ComplexClass { Member7 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member7)]; + var itemNode = rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0)); + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[0], obj[1], x => x.Update(obj[1], new NodeIndex(0))); + Assert.Equal(obj[1], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); + // TODO: would be nice to be able to keep the same boxed node! + Assert.Equal(itemNode, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionUpdate, new NodeIndex(0), obj[1], obj[2], x => x.Update(obj[2], new NodeIndex(0))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); + // TODO: would be nice to be able to keep the same boxed node! + Assert.Equal(itemNode, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(0))); + } - [Fact] - public void TestAddStructListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; - var instance = new ComplexClass { Member7 = new List { obj[0] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member7)]; - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionAdd, new NodeIndex(1), null, obj[1], x => x.Add(obj[1], new NodeIndex(1))); - Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionAdd, new NodeIndex(2), null, obj[2], x => x.Add(obj[2], new NodeIndex(2))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); - } + [Fact] + public void TestAddStructListItem() + { + var nodeContainer = new NodeContainer(); + Struct[] obj = [new() { Member1 = "aa" }, new() { Member1 = "bb" }, new() { Member1 = "cc" }]; + var instance = new ComplexClass { Member7 = [obj[0]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member7)]; + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionAdd, new NodeIndex(1), null, obj[1], x => x.Add(obj[1], new NodeIndex(1))); + Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionAdd, new NodeIndex(2), null, obj[2], x => x.Add(obj[2], new NodeIndex(2))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); + } - [Fact] - public void TestRemoveStructListItem() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new Struct { Member1 = "aa" }, new Struct { Member1 = "bb" }, new Struct { Member1 = "cc" } }; - var instance = new ComplexClass { Member7 = new List { obj[0], obj[1], obj[2] } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - var node = rootNode[nameof(ComplexClass.Member7)]; - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[1], null, x => x.Remove(obj[1], new NodeIndex(1))); - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(obj[2], node.Retrieve(new NodeIndex(1))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); - TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[2], null, x => x.Remove(obj[2], new NodeIndex(1))); - Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); - } + [Fact] + public void TestRemoveStructListItem() + { + var nodeContainer = new NodeContainer(); + Struct[] obj = [new() { Member1 = "aa" }, new() { Member1 = "bb" }, new() { Member1 = "cc" }]; + var instance = new ComplexClass { Member7 = [obj[0], obj[1], obj[2]] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + var node = rootNode[nameof(ComplexClass.Member7)]; + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(obj[1], node.Retrieve(new NodeIndex(1))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(2))); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[1], null, x => x.Remove(obj[1], new NodeIndex(1))); + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(obj[2], node.Retrieve(new NodeIndex(1))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); + TestItemChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target, ContentChangeType.CollectionRemove, new NodeIndex(1), obj[2], null, x => x.Remove(obj[2], new NodeIndex(1))); + Assert.Equal(obj[0], node.Retrieve(new NodeIndex(0))); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)]); + } - [Fact] - public void TestChangeStructListItemMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { "aa", "bb", "cc" }; - var instance = new ComplexClass { Member7 = new List { new Struct(), new Struct { Member1 = obj[0] } } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - NodeIndex index = new NodeIndex(1); - var node = rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)]; - Assert.Equal(obj[0], node.Retrieve()); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); - Assert.Equal(obj[1], node.Retrieve()); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)]); - TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); - Assert.Equal(obj[2], node.Retrieve()); - Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)]); - } + [Fact] + public void TestChangeStructListItemMember() + { + var nodeContainer = new NodeContainer(); + string[] obj = ["aa", "bb", "cc"]; + var instance = new ComplexClass { Member7 = [new(), new() { Member1 = obj[0] }] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + NodeIndex index = new NodeIndex(1); + var node = rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)]; + Assert.Equal(obj[0], node.Retrieve()); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[0], obj[1], x => x.Update(obj[1])); + Assert.Equal(obj[1], node.Retrieve()); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)]); + TestContentChange(listener, () => rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(index)[nameof(SimpleClass.Member1)], ContentChangeType.ValueChange, NodeIndex.Empty, obj[1], obj[2], x => x.Update(obj[2])); + Assert.Equal(obj[2], node.Retrieve()); + Assert.Equal(node, rootNode[nameof(ComplexClass.Member7)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)]); + } - [Fact] - public void TestDiscardedReferenceMember() - { - var nodeContainer = new NodeContainer(); - var obj = new[] { new SimpleClass(), new SimpleClass() }; - var instance = new ComplexClass { Member2 = obj[0] }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var obj0Node = nodeContainer.GetOrCreateNode(obj[0]); - var obj1Node = nodeContainer.GetOrCreateNode(obj[1]); - var listener = new GraphNodeChangeListener(rootNode); - listener.Initialize(); - int changingCount = 0; - int changedCount = 0; - listener.ValueChanging += (sender, e) => ++changingCount; - listener.ValueChanged += (sender, e) => ++changedCount; - obj0Node[nameof(SimpleClass.Member1)].Update(1); - Assert.Equal(1, changingCount); - Assert.Equal(1, changedCount); - rootNode[nameof(ComplexClass.Member2)].Update(obj[1]); - Assert.Equal(2, changingCount); - Assert.Equal(2, changedCount); - obj0Node[nameof(SimpleClass.Member1)].Update(2); - Assert.Equal(2, changingCount); - Assert.Equal(2, changedCount); - obj1Node[nameof(SimpleClass.Member1)].Update(3); - Assert.Equal(3, changingCount); - Assert.Equal(3, changedCount); - } + [Fact] + public void TestDiscardedReferenceMember() + { + var nodeContainer = new NodeContainer(); + SimpleClass[] obj = [new(), new()]; + var instance = new ComplexClass { Member2 = obj[0] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var obj0Node = nodeContainer.GetOrCreateNode(obj[0]); + var obj1Node = nodeContainer.GetOrCreateNode(obj[1]); + var listener = new GraphNodeChangeListener(rootNode); + listener.Initialize(); + int changingCount = 0; + int changedCount = 0; + listener.ValueChanging += (sender, e) => ++changingCount; + listener.ValueChanged += (sender, e) => ++changedCount; + obj0Node[nameof(SimpleClass.Member1)].Update(1); + Assert.Equal(1, changingCount); + Assert.Equal(1, changedCount); + rootNode[nameof(ComplexClass.Member2)].Update(obj[1]); + Assert.Equal(2, changingCount); + Assert.Equal(2, changedCount); + obj0Node[nameof(SimpleClass.Member1)].Update(2); + Assert.Equal(2, changingCount); + Assert.Equal(2, changedCount); + obj1Node[nameof(SimpleClass.Member1)].Update(3); + Assert.Equal(3, changingCount); + Assert.Equal(3, changedCount); + } - private static void VerifyListenerEvent(INodeChangeEventArgs e, IGraphNode nodeOwner, ContentChangeType type, NodeIndex index, object oldValue, object newValue, bool changeApplied) + private static void VerifyListenerEvent(INodeChangeEventArgs e, IGraphNode nodeOwner, ContentChangeType type, NodeIndex index, object oldValue, object newValue, bool changeApplied) + { + Assert.NotNull(e); + Assert.NotNull(nodeOwner); + Assert.Equal(type, e.ChangeType); + Assert.Equal(nodeOwner, e.Node); + Assert.Equal(index, (e as ItemChangeEventArgs)?.Index ?? NodeIndex.Empty); + Assert.Equal(newValue, e.NewValue); + Assert.Equal(oldValue, e.OldValue); + if (type == ContentChangeType.ValueChange) { - Assert.NotNull(e); - Assert.NotNull(nodeOwner); - Assert.Equal(type, e.ChangeType); - Assert.Equal(nodeOwner, e.Node); - Assert.Equal(index, (e as ItemChangeEventArgs)?.Index ?? NodeIndex.Empty); - Assert.Equal(newValue, e.NewValue); - Assert.Equal(oldValue, e.OldValue); - if (type == ContentChangeType.ValueChange) - { - Assert.Equal(changeApplied ? newValue : oldValue, nodeOwner.Retrieve(index)); - } + Assert.Equal(changeApplied ? newValue : oldValue, nodeOwner.Retrieve(index)); } + } - private static void TestContentChange([NotNull] GraphNodeChangeListener listener, [NotNull] Func fetchNode, ContentChangeType type, NodeIndex index, object oldValue, object newValue, [NotNull] Action change) - { - var i = 0; - var contentOwner = fetchNode(); - var changing = new EventHandler((sender, e) => { Assert.Equal(0, i); VerifyListenerEvent(e, contentOwner, type, index, oldValue, newValue, false); ++i; }); - var changed = new EventHandler((sender, e) => { Assert.Equal(1, i); VerifyListenerEvent(e, contentOwner, type, index, oldValue, newValue, true); ++i; }); - listener.ValueChanging += changing; - listener.ValueChanged += changed; - change(contentOwner); - Assert.Equal(2, i); - listener.ValueChanging -= changing; - listener.ValueChanged -= changed; - } + private static void TestContentChange([NotNull] GraphNodeChangeListener listener, [NotNull] Func fetchNode, ContentChangeType type, NodeIndex index, object oldValue, object newValue, [NotNull] Action change) + { + var i = 0; + var contentOwner = fetchNode(); + var changing = new EventHandler((sender, e) => { Assert.Equal(0, i); VerifyListenerEvent(e, contentOwner, type, index, oldValue, newValue, false); ++i; }); + var changed = new EventHandler((sender, e) => { Assert.Equal(1, i); VerifyListenerEvent(e, contentOwner, type, index, oldValue, newValue, true); ++i; }); + listener.ValueChanging += changing; + listener.ValueChanged += changed; + change(contentOwner); + Assert.Equal(2, i); + listener.ValueChanging -= changing; + listener.ValueChanged -= changed; + } - private static void TestItemChange([NotNull] GraphNodeChangeListener listener, [NotNull] Func fetchNode, ContentChangeType type, NodeIndex index, object oldValue, object newValue, [NotNull] Action change) - { - var i = 0; - var contentOwner = fetchNode(); - var changing = new EventHandler((sender, e) => { Assert.Equal(0, i); VerifyListenerEvent(e, contentOwner, type, index, oldValue, newValue, false); ++i; }); - var changed = new EventHandler((sender, e) => { Assert.Equal(1, i); VerifyListenerEvent(e, contentOwner, type, index, oldValue, newValue, true); ++i; }); - listener.ItemChanging += changing; - listener.ItemChanged += changed; - change(contentOwner); - Assert.Equal(2, i); - listener.ItemChanging -= changing; - listener.ItemChanged -= changed; - } + private static void TestItemChange([NotNull] GraphNodeChangeListener listener, [NotNull] Func fetchNode, ContentChangeType type, NodeIndex index, object oldValue, object newValue, [NotNull] Action change) + { + var i = 0; + var contentOwner = fetchNode(); + var changing = new EventHandler((sender, e) => { Assert.Equal(0, i); VerifyListenerEvent(e, contentOwner, type, index, oldValue, newValue, false); ++i; }); + var changed = new EventHandler((sender, e) => { Assert.Equal(1, i); VerifyListenerEvent(e, contentOwner, type, index, oldValue, newValue, true); ++i; }); + listener.ItemChanging += changing; + listener.ItemChanged += changed; + change(contentOwner); + Assert.Equal(2, i); + listener.ItemChanging -= changing; + listener.ItemChanged -= changed; } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodeLinker.cs b/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodeLinker.cs index c006f3e098..6cfab7768a 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodeLinker.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodeLinker.cs @@ -1,324 +1,321 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; -using Xunit; + using Stride.Core.Quantum.References; +using Xunit; -namespace Stride.Core.Quantum.Tests +namespace Stride.Core.Quantum.Tests; + +public class TestGraphNodeLinker { - public class TestGraphNodeLinker + public class SimpleClass { - public class SimpleClass - { - public int Member1; - public SimpleClass Member2; - } + public int Member1; + public SimpleClass Member2; + } - public class InterfaceMember - { - public int Member1; - public IInterface Member2; - } + public class InterfaceMember + { + public int Member1; + public IInterface Member2; + } - public interface IInterface - { - int Member1Common { get; set; } - } + public interface IInterface + { + int Member1Common { get; set; } + } - public class Implem1 : IInterface - { - public int Member1Common { get; set; } - public SimpleClass Member2Implem1 { get; set; } - } + public class Implem1 : IInterface + { + public int Member1Common { get; set; } + public SimpleClass Member2Implem1 { get; set; } + } - public class Implem2 : IInterface - { - public int Member1Common { get; set; } - public SimpleClass Member2Implem2 { get; set; } - } + public class Implem2 : IInterface + { + public int Member1Common { get; set; } + public SimpleClass Member2Implem2 { get; set; } + } - public class StructClass - { - public int Member1; - public Struct Member2; - } + public class StructClass + { + public int Member1; + public Struct Member2; + } - public class PrimitiveListClass - { - public int Member1; - public List Member2; - } + public class PrimitiveListClass + { + public int Member1; + public List Member2; + } - public class ObjectListClass - { - public int Member1; - public List Member2; - } + public class ObjectListClass + { + public int Member1; + public List Member2; + } - public class StructListClass - { - public int Member1; - public List Member2; - } + public class StructListClass + { + public int Member1; + public List Member2; + } - public struct Struct - { - public string Member1; - public SimpleClass Member2; - } + public struct Struct + { + public string Member1; + public SimpleClass Member2; + } - public class TestLinker : GraphNodeLinker - { - public Dictionary LinkedNodes = new Dictionary(); + public class TestLinker : GraphNodeLinker + { + public Dictionary LinkedNodes = []; - protected override void LinkNodes(IGraphNode sourceNode, IGraphNode targetNode) - { - LinkedNodes.Add(sourceNode, targetNode); - base.LinkNodes(sourceNode, targetNode); - } + protected override void LinkNodes(IGraphNode sourceNode, IGraphNode targetNode) + { + LinkedNodes.Add(sourceNode, targetNode); + base.LinkNodes(sourceNode, targetNode); } + } - public class CustomFindTargetLinker : TestLinker - { - private readonly IObjectNode root; + public class CustomFindTargetLinker : TestLinker + { + private readonly IObjectNode root; - public CustomFindTargetLinker(NodeContainer container, IObjectNode root) - { - this.root = root; - CustomTarget = container.GetOrCreateNode(new SimpleClass()); - } + public CustomFindTargetLinker(NodeContainer container, IObjectNode root) + { + this.root = root; + CustomTarget = container.GetOrCreateNode(new SimpleClass()); + } - public IObjectNode CustomTarget { get; } + public IObjectNode CustomTarget { get; } - protected override IGraphNode FindTarget(IGraphNode sourceNode) + protected override IGraphNode FindTarget(IGraphNode sourceNode) + { + if (sourceNode is IObjectNode && sourceNode.Type == typeof(SimpleClass) && sourceNode != root) { - if (sourceNode is IObjectNode && sourceNode.Type == typeof(SimpleClass) && sourceNode != root) - { - return CustomTarget; - } - return base.FindTarget(sourceNode); + return CustomTarget; } + return base.FindTarget(sourceNode); } + } - public class CustomFindTargetReferenceLinker : TestLinker + public class CustomFindTargetReferenceLinker : TestLinker + { + public override ObjectReference FindTargetReference(IGraphNode sourceNode, IGraphNode targetNode, ObjectReference sourceReference) { - public override ObjectReference FindTargetReference(IGraphNode sourceNode, IGraphNode targetNode, ObjectReference sourceReference) - { - if (sourceReference.Index.IsEmpty) - return base.FindTargetReference(sourceNode, targetNode, sourceReference); + if (sourceReference.Index.IsEmpty) + return base.FindTargetReference(sourceNode, targetNode, sourceReference); - var matchValue = 0; - if (sourceReference.TargetNode != null) - matchValue = (int)sourceReference.TargetNode[nameof(SimpleClass.Member1)].Retrieve(); + var matchValue = 0; + if (sourceReference.TargetNode != null) + matchValue = (int)sourceReference.TargetNode[nameof(SimpleClass.Member1)].Retrieve(); - var targetReference = (targetNode as IObjectNode)?.ItemReferences; - return targetReference?.FirstOrDefault(x => (int)x.TargetNode[nameof(SimpleClass.Member1)].Retrieve() == matchValue); - } + var targetReference = (targetNode as IObjectNode)?.ItemReferences; + return targetReference?.FirstOrDefault(x => (int)x.TargetNode[nameof(SimpleClass.Member1)].Retrieve() == matchValue); } + } - [Fact] - public void TestSimpleObject() + [Fact] + public void TestSimpleObject() + { + var nodeContainer = new NodeContainer(); + var instance1 = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; + var instance2 = new SimpleClass { Member1 = 4, Member2 = new SimpleClass() }; + var source = nodeContainer.GetOrCreateNode(instance1); + var target = nodeContainer.GetOrCreateNode(instance2); + var linker = new TestLinker(); + linker.LinkGraph(source, target); + var expectedLinks = new Dictionary { - var nodeContainer = new NodeContainer(); - var instance1 = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; - var instance2 = new SimpleClass { Member1 = 4, Member2 = new SimpleClass() }; - var source = nodeContainer.GetOrCreateNode(instance1); - var target = nodeContainer.GetOrCreateNode(instance2); - var linker = new TestLinker(); - linker.LinkGraph(source, target); - var expectedLinks = new Dictionary - { - { source, target }, - { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, - { source[nameof(SimpleClass.Member2)].Target, target[nameof(SimpleClass.Member2)].Target }, - { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)] }, - }; - VerifyLinks(expectedLinks, linker); - } + { source, target }, + { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, + { source[nameof(SimpleClass.Member2)].Target, target[nameof(SimpleClass.Member2)].Target }, + { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)] }, + }; + VerifyLinks(expectedLinks, linker); + } - [Fact] - public void TestObjectWithListOfReferences() + [Fact] + public void TestObjectWithListOfReferences() + { + var nodeContainer = new NodeContainer(); + var instance1 = new ObjectListClass { Member1 = 3, Member2 = [new(), new()] }; + var instance2 = new ObjectListClass { Member1 = 3, Member2 = [new(), new()] }; + var source = nodeContainer.GetOrCreateNode(instance1); + var target = nodeContainer.GetOrCreateNode(instance2); + var linker = new TestLinker(); + linker.LinkGraph(source, target); + var expectedLinks = new Dictionary { - var nodeContainer = new NodeContainer(); - var instance1 = new ObjectListClass { Member1 = 3, Member2 = new List { new SimpleClass(), new SimpleClass() } }; - var instance2 = new ObjectListClass { Member1 = 3, Member2 = new List { new SimpleClass(), new SimpleClass() } }; - var source = nodeContainer.GetOrCreateNode(instance1); - var target = nodeContainer.GetOrCreateNode(instance2); - var linker = new TestLinker(); - linker.LinkGraph(source, target); - var expectedLinks = new Dictionary - { - { source, target }, - { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, - { source[nameof(SimpleClass.Member2)].Target, target[nameof(SimpleClass.Member2)].Target }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0)), target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0)) }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member2)] }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1)), target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1)) }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member2)] }, - }; - VerifyLinks(expectedLinks, linker); - } + { source, target }, + { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, + { source[nameof(SimpleClass.Member2)].Target, target[nameof(SimpleClass.Member2)].Target }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0)), target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0)) }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member2)] }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1)), target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1)) }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member2)] }, + }; + VerifyLinks(expectedLinks, linker); + } - [Fact] - public void TestSimpleObjectWithNullInTarget() + [Fact] + public void TestSimpleObjectWithNullInTarget() + { + var nodeContainer = new NodeContainer(); + var instance1 = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; + var instance2 = new SimpleClass { Member1 = 4, Member2 = null }; + var source = nodeContainer.GetOrCreateNode(instance1); + var target = nodeContainer.GetOrCreateNode(instance2); + var linker = new TestLinker(); + linker.LinkGraph(source, target); + var expectedLinks = new Dictionary { - var nodeContainer = new NodeContainer(); - var instance1 = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; - var instance2 = new SimpleClass { Member1 = 4, Member2 = null }; - var source = nodeContainer.GetOrCreateNode(instance1); - var target = nodeContainer.GetOrCreateNode(instance2); - var linker = new TestLinker(); - linker.LinkGraph(source, target); - var expectedLinks = new Dictionary - { - { source, target }, - { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, - { source[nameof(SimpleClass.Member2)].Target, null }, - { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], null }, - { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], null }, - }; - VerifyLinks(expectedLinks, linker); - } + { source, target }, + { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, + { source[nameof(SimpleClass.Member2)].Target, null }, + { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], null }, + { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], null }, + }; + VerifyLinks(expectedLinks, linker); + } - [Fact] - public void TestObjectWithStruct() + [Fact] + public void TestObjectWithStruct() + { + var nodeContainer = new NodeContainer(); + var instance1 = new StructClass { Member1 = 3, Member2 = new Struct { Member2 = new SimpleClass() } }; + var instance2 = new StructClass { Member1 = 3, Member2 = new Struct { Member2 = new SimpleClass() } }; + var source = nodeContainer.GetOrCreateNode(instance1); + var target = nodeContainer.GetOrCreateNode(instance2); + var linker = new TestLinker(); + linker.LinkGraph(source, target); + var expectedLinks = new Dictionary { - var nodeContainer = new NodeContainer(); - var instance1 = new StructClass { Member1 = 3, Member2 = new Struct { Member2 = new SimpleClass() } }; - var instance2 = new StructClass { Member1 = 3, Member2 = new Struct { Member2 = new SimpleClass() } }; - var source = nodeContainer.GetOrCreateNode(instance1); - var target = nodeContainer.GetOrCreateNode(instance2); - var linker = new TestLinker(); - linker.LinkGraph(source, target); - var expectedLinks = new Dictionary - { - { source, target }, - { source[nameof(StructClass.Member1)], target[nameof(StructClass.Member1)] }, - { source[nameof(StructClass.Member2)], target[nameof(StructClass.Member2)] }, - { source[nameof(StructClass.Member2)].Target, target[nameof(StructClass.Member2)].Target }, - { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member1)], target[nameof(StructClass.Member2)].Target[nameof(Struct.Member1)] }, - { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)], target[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)] }, - { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target, target[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target }, - { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target[nameof(SimpleClass.Member1)], target[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target[nameof(SimpleClass.Member1)] }, - { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target[nameof(SimpleClass.Member2)], target[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target[nameof(SimpleClass.Member2)] }, - }; - VerifyLinks(expectedLinks, linker); - } + { source, target }, + { source[nameof(StructClass.Member1)], target[nameof(StructClass.Member1)] }, + { source[nameof(StructClass.Member2)], target[nameof(StructClass.Member2)] }, + { source[nameof(StructClass.Member2)].Target, target[nameof(StructClass.Member2)].Target }, + { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member1)], target[nameof(StructClass.Member2)].Target[nameof(Struct.Member1)] }, + { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)], target[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)] }, + { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target, target[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target }, + { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target[nameof(SimpleClass.Member1)], target[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target[nameof(SimpleClass.Member1)] }, + { source[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target[nameof(SimpleClass.Member2)], target[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)].Target[nameof(SimpleClass.Member2)] }, + }; + VerifyLinks(expectedLinks, linker); + } - [Fact] - public void TestInterfaceMemberDifferentImplementations() + [Fact] + public void TestInterfaceMemberDifferentImplementations() + { + var nodeContainer = new NodeContainer(); + var instance1 = new InterfaceMember { Member1 = 3, Member2 = new Implem1 { Member2Implem1 = new SimpleClass() } }; + var instance2 = new InterfaceMember { Member1 = 3, Member2 = new Implem2 { Member2Implem2 = new SimpleClass() } }; + var source = nodeContainer.GetOrCreateNode(instance1); + var target = nodeContainer.GetOrCreateNode(instance2); + var linker = new TestLinker(); + linker.LinkGraph(source, target); + var expectedLinks = new Dictionary { - var nodeContainer = new NodeContainer(); - var instance1 = new InterfaceMember { Member1 = 3, Member2 = new Implem1 { Member2Implem1 = new SimpleClass() } }; - var instance2 = new InterfaceMember { Member1 = 3, Member2 = new Implem2 { Member2Implem2 = new SimpleClass() } }; - var source = nodeContainer.GetOrCreateNode(instance1); - var target = nodeContainer.GetOrCreateNode(instance2); - var linker = new TestLinker(); - linker.LinkGraph(source, target); - var expectedLinks = new Dictionary - { - { source, target }, - { source[nameof(InterfaceMember.Member1)], target[nameof(InterfaceMember.Member1)] }, - { source[nameof(InterfaceMember.Member2)], target[nameof(InterfaceMember.Member2)] }, - { source[nameof(InterfaceMember.Member2)].Target, target[nameof(InterfaceMember.Member2)].Target }, - { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member1Common)], target[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member1Common)] }, - { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member2Implem1)], null }, - { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member2Implem1)].Target, null }, - { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member2Implem1)].Target[nameof(SimpleClass.Member1)], null }, - { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member2Implem1)].Target[nameof(SimpleClass.Member2)], null }, - }; - VerifyLinks(expectedLinks, linker); - } + { source, target }, + { source[nameof(InterfaceMember.Member1)], target[nameof(InterfaceMember.Member1)] }, + { source[nameof(InterfaceMember.Member2)], target[nameof(InterfaceMember.Member2)] }, + { source[nameof(InterfaceMember.Member2)].Target, target[nameof(InterfaceMember.Member2)].Target }, + { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member1Common)], target[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member1Common)] }, + { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member2Implem1)], null }, + { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member2Implem1)].Target, null }, + { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member2Implem1)].Target[nameof(SimpleClass.Member1)], null }, + { source[nameof(InterfaceMember.Member2)].Target[nameof(Implem1.Member2Implem1)].Target[nameof(SimpleClass.Member2)], null }, + }; + VerifyLinks(expectedLinks, linker); + } - [Fact] - public void TestCustomFindTarget() + [Fact] + public void TestCustomFindTarget() + { + var nodeContainer = new NodeContainer(); + var instance1 = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; + var instance2 = new SimpleClass { Member1 = 4, Member2 = new SimpleClass() }; + var source = nodeContainer.GetOrCreateNode(instance1); + var target = nodeContainer.GetOrCreateNode(instance2); + var linker = new CustomFindTargetLinker(nodeContainer, source); + linker.LinkGraph(source, target); + var expectedLinks = new Dictionary { - var nodeContainer = new NodeContainer(); - var instance1 = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; - var instance2 = new SimpleClass { Member1 = 4, Member2 = new SimpleClass() }; - var source = nodeContainer.GetOrCreateNode(instance1); - var target = nodeContainer.GetOrCreateNode(instance2); - var linker = new CustomFindTargetLinker(nodeContainer, source); - linker.LinkGraph(source, target); - var expectedLinks = new Dictionary - { - { source, target }, - { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, - { source[nameof(SimpleClass.Member2)].Target, linker.CustomTarget }, - { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], linker.CustomTarget[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], linker.CustomTarget[nameof(SimpleClass.Member2)] }, - }; - VerifyLinks(expectedLinks, linker); - } + { source, target }, + { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, + { source[nameof(SimpleClass.Member2)].Target, linker.CustomTarget }, + { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], linker.CustomTarget[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], linker.CustomTarget[nameof(SimpleClass.Member2)] }, + }; + VerifyLinks(expectedLinks, linker); + } - [Fact] - public void TestCustomFindTargetReference() + [Fact] + public void TestCustomFindTargetReference() + { + var nodeContainer = new NodeContainer(); + var instance1 = new ObjectListClass { Member1 = 3, Member2 = [new() { Member1 = 1 }, new() { Member1 = 2 }, new() { Member1 = 3 }] }; + var instance2 = new ObjectListClass { Member1 = 3, Member2 = [new() { Member1 = 2 }, new() { Member1 = 4 }, new() { Member1 = 1 }] }; + var source = nodeContainer.GetOrCreateNode(instance1); + var target = nodeContainer.GetOrCreateNode(instance2); + var linker = new CustomFindTargetReferenceLinker(); + linker.LinkGraph(source, target); + // Expected links by index: 0 -> 2, 1 -> 0, 2 -> null + var expectedLinks = new Dictionary { - var nodeContainer = new NodeContainer(); - var instance1 = new ObjectListClass { Member1 = 3, Member2 = new List { new SimpleClass { Member1 = 1 }, new SimpleClass { Member1 = 2 }, new SimpleClass { Member1 = 3 } } }; - var instance2 = new ObjectListClass { Member1 = 3, Member2 = new List { new SimpleClass { Member1 = 2 }, new SimpleClass { Member1 = 4 }, new SimpleClass { Member1 = 1 } } }; - var source = nodeContainer.GetOrCreateNode(instance1); - var target = nodeContainer.GetOrCreateNode(instance2); - var linker = new CustomFindTargetReferenceLinker(); - linker.LinkGraph(source, target); - // Expected links by index: 0 -> 2, 1 -> 0, 2 -> null - var expectedLinks = new Dictionary - { - { source, target }, - { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, - { source[nameof(SimpleClass.Member2)].Target, target[nameof(SimpleClass.Member2)].Target }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0)), target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2)) }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(SimpleClass.Member2)] }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1)), target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0)) }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member2)] }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2)), null }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(SimpleClass.Member1)], null }, - { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(SimpleClass.Member2)], null }, - }; - VerifyLinks(expectedLinks, linker); - } + { source, target }, + { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, + { source[nameof(SimpleClass.Member2)].Target, target[nameof(SimpleClass.Member2)].Target }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0)), target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2)) }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(SimpleClass.Member2)] }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1)), target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0)) }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(SimpleClass.Member2)] }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2)), null }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(SimpleClass.Member1)], null }, + { source[nameof(SimpleClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(SimpleClass.Member2)], null }, + }; + VerifyLinks(expectedLinks, linker); + } - [Fact] - public void TestReentrancy() + [Fact] + public void TestReentrancy() + { + var nodeContainer = new NodeContainer(); + var instance1 = new SimpleClass { Member1 = 3 }; + var instance2 = new SimpleClass { Member1 = 4 }; + instance1.Member2 = instance1; + instance2.Member2 = instance2; + var source = nodeContainer.GetOrCreateNode(instance1); + var target = nodeContainer.GetOrCreateNode(instance2); + var linker = new TestLinker(); + linker.LinkGraph(source, target); + var expectedLinks = new Dictionary { - var nodeContainer = new NodeContainer(); - var instance1 = new SimpleClass { Member1 = 3 }; - var instance2 = new SimpleClass { Member1 = 4 }; - instance1.Member2 = instance1; - instance2.Member2 = instance2; - var source = nodeContainer.GetOrCreateNode(instance1); - var target = nodeContainer.GetOrCreateNode(instance2); - var linker = new TestLinker(); - linker.LinkGraph(source, target); - var expectedLinks = new Dictionary - { - { source, target }, - { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, - { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, - }; - VerifyLinks(expectedLinks, linker); - } + { source, target }, + { source[nameof(SimpleClass.Member1)], target[nameof(SimpleClass.Member1)] }, + { source[nameof(SimpleClass.Member2)], target[nameof(SimpleClass.Member2)] }, + }; + VerifyLinks(expectedLinks, linker); + } - private static void VerifyLinks(Dictionary expectedLinks, TestLinker linker) + private static void VerifyLinks(Dictionary expectedLinks, TestLinker linker) + { + Assert.Equal(expectedLinks.Count, linker.LinkedNodes.Count); + foreach (var link in expectedLinks) { - Assert.Equal(expectedLinks.Count, linker.LinkedNodes.Count); - foreach (var link in expectedLinks) - { - IGraphNode actualTarget; - Assert.True(linker.LinkedNodes.TryGetValue(link.Key, out actualTarget)); - Assert.Equal(link.Value, actualTarget); - } + Assert.True(linker.LinkedNodes.TryGetValue(link.Key, out var actualTarget)); + Assert.Equal(link.Value, actualTarget); } } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodePath.cs b/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodePath.cs index 687358a675..bf5a45df3d 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodePath.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/TestGraphNodePath.cs @@ -1,382 +1,381 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; + using Xunit; -namespace Stride.Core.Quantum.Tests +namespace Stride.Core.Quantum.Tests; + +public class TestGraphNodePath { - public class TestGraphNodePath + public struct Struct { - public struct Struct - { - public string StringMember; - public Class ClassMember; - } - public class Class - { - public int IntMember; - public Struct StructMember; - public Class ClassMember; - public List ListMember = new List(); - } + public string StringMember; + public Class ClassMember; + } + public class Class + { + public int IntMember; + public Struct StructMember; + public Class ClassMember; + public List ListMember = []; + } - [Fact] - public void TestConstructor() - { - var obj = new Class(); - var nodeContainer = new NodeContainer(); - var rootNode = nodeContainer.GetOrCreateNode(obj); - var path = new GraphNodePath(rootNode); - Assert.True(path.IsEmpty); - AssertAreEqual(rootNode, path.RootNode); - } + [Fact] + public void TestConstructor() + { + var obj = new Class(); + var nodeContainer = new NodeContainer(); + var rootNode = nodeContainer.GetOrCreateNode(obj); + var path = new GraphNodePath(rootNode); + Assert.True(path.IsEmpty); + AssertAreEqual(rootNode, path.RootNode); + } - [Fact] - public void TestEquals() - { - // Note: comparing GraphNodePath.GetHashCode() returns true when the root node is equivalent. This is because the root node is the only invariant. + [Fact] + public void TestEquals() + { + // Note: comparing GraphNodePath.GetHashCode() returns true when the root node is equivalent. This is because the root node is the only invariant. - var obj = new Class { StructMember = { StringMember = "aa" }, ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; - var nodeContainer = new NodeContainer(); - var path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path1.PushMember(nameof(Class.IntMember)); - var path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path2.PushMember(nameof(Class.IntMember)); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreEqual(path1, path2); + var obj = new Class { StructMember = { StringMember = "aa" }, ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; + var nodeContainer = new NodeContainer(); + var path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path1.PushMember(nameof(Class.IntMember)); + var path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path2.PushMember(nameof(Class.IntMember)); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreEqual(path1, path2); - path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path1.PushMember(nameof(Class.ClassMember)); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreNotEqual(path1, path2); - path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path2.PushMember(nameof(Class.ClassMember)); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreEqual(path1, path2); + path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path1.PushMember(nameof(Class.ClassMember)); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreNotEqual(path1, path2); + path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path2.PushMember(nameof(Class.ClassMember)); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreEqual(path1, path2); - path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path1.PushMember(nameof(Class.ClassMember)); - path1.PushTarget(); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreNotEqual(path1, path2); - path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path2.PushMember(nameof(Class.ClassMember)); - path2.PushTarget(); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreEqual(path1, path2); + path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path1.PushMember(nameof(Class.ClassMember)); + path1.PushTarget(); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreNotEqual(path1, path2); + path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path2.PushMember(nameof(Class.ClassMember)); + path2.PushTarget(); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreEqual(path1, path2); - path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path1.PushMember(nameof(Class.ClassMember)); - path1.PushTarget(); - path1.PushMember(nameof(Class.IntMember)); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreNotEqual(path1, path2); - path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path2.PushMember(nameof(Class.ClassMember)); - path2.PushTarget(); - path2.PushMember(nameof(Class.IntMember)); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreEqual(path1, path2); + path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path1.PushMember(nameof(Class.ClassMember)); + path1.PushTarget(); + path1.PushMember(nameof(Class.IntMember)); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreNotEqual(path1, path2); + path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path2.PushMember(nameof(Class.ClassMember)); + path2.PushTarget(); + path2.PushMember(nameof(Class.IntMember)); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreEqual(path1, path2); - path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path1.PushMember(nameof(Class.ListMember)); - path1.PushIndex(new NodeIndex(0)); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreNotEqual(path1, path2); - path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path2.PushMember(nameof(Class.ListMember)); - path2.PushIndex(new NodeIndex(0)); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreEqual(path1, path2); + path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path1.PushMember(nameof(Class.ListMember)); + path1.PushIndex(new NodeIndex(0)); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreNotEqual(path1, path2); + path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path2.PushMember(nameof(Class.ListMember)); + path2.PushIndex(new NodeIndex(0)); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreEqual(path1, path2); - path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - path2.PushMember(nameof(Class.ListMember)); - path2.PushIndex(new NodeIndex(1)); - AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); - AssertAreNotEqual(path1, path2); - } + path2 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + path2.PushMember(nameof(Class.ListMember)); + path2.PushIndex(new NodeIndex(1)); + AssertAreEqual(path1.GetHashCode(), path2.GetHashCode()); + AssertAreNotEqual(path1, path2); + } - [Fact] - public void TestClone() - { - var obj = new Class { ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; - var nodeContainer = new NodeContainer(); - var path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); - var clone = path1.Clone(); - AssertAreEqual(path1, clone); - AssertAreEqual(path1.GetHashCode(), clone.GetHashCode()); - AssertAreEqual(path1.RootNode, clone.RootNode); - AssertAreEqual(path1.IsEmpty, clone.IsEmpty); - AssertAreEqual(path1.GetNode(), clone.GetNode()); - var path2 = path1.Clone(); - path2.PushMember(nameof(Class.ClassMember)); - path2.PushTarget(); - path2.PushMember(nameof(Class.IntMember)); - clone = path2.Clone(); - AssertAreEqual(path2, clone); - AssertAreEqual(path2.RootNode, clone.RootNode); - AssertAreEqual(path2.IsEmpty, clone.IsEmpty); - AssertAreEqual(path2.GetNode(), clone.GetNode()); - var path3 = path1.Clone(); - path3.PushMember(nameof(Class.ListMember)); - path3.PushTarget(); - path3.PushIndex(new NodeIndex(1)); - path3.PushMember(nameof(Class.IntMember)); - clone = path3.Clone(); - AssertAreEqual(path3, clone); - AssertAreEqual(path3.RootNode, clone.RootNode); - AssertAreEqual(path3.IsEmpty, clone.IsEmpty); - AssertAreEqual(path3.GetNode(), clone.GetNode()); - } + [Fact] + public void TestClone() + { + var obj = new Class { ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; + var nodeContainer = new NodeContainer(); + var path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj)); + var clone = path1.Clone(); + AssertAreEqual(path1, clone); + AssertAreEqual(path1.GetHashCode(), clone.GetHashCode()); + AssertAreEqual(path1.RootNode, clone.RootNode); + AssertAreEqual(path1.IsEmpty, clone.IsEmpty); + AssertAreEqual(path1.GetNode(), clone.GetNode()); + var path2 = path1.Clone(); + path2.PushMember(nameof(Class.ClassMember)); + path2.PushTarget(); + path2.PushMember(nameof(Class.IntMember)); + clone = path2.Clone(); + AssertAreEqual(path2, clone); + AssertAreEqual(path2.RootNode, clone.RootNode); + AssertAreEqual(path2.IsEmpty, clone.IsEmpty); + AssertAreEqual(path2.GetNode(), clone.GetNode()); + var path3 = path1.Clone(); + path3.PushMember(nameof(Class.ListMember)); + path3.PushTarget(); + path3.PushIndex(new NodeIndex(1)); + path3.PushMember(nameof(Class.IntMember)); + clone = path3.Clone(); + AssertAreEqual(path3, clone); + AssertAreEqual(path3.RootNode, clone.RootNode); + AssertAreEqual(path3.IsEmpty, clone.IsEmpty); + AssertAreEqual(path3.GetNode(), clone.GetNode()); + } - [Fact] - public void TestCloneNewRoot() - { - var obj1 = new Class { ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; - var obj2 = new Class { ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; - var nodeContainer = new NodeContainer(); - var newRoot = nodeContainer.GetOrCreateNode(obj2); - var path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj1)); - var clone = path1.Clone(newRoot); - AssertAreNotEqual(path1, clone); - AssertAreNotEqual(path1.GetHashCode(), clone.GetHashCode()); - AssertAreNotEqual(newRoot, path1.RootNode); - AssertAreEqual(newRoot, clone.RootNode); - AssertAreEqual(path1.IsEmpty, clone.IsEmpty); - var path2 = path1.Clone(); - path2.PushMember(nameof(Class.ClassMember)); - path2.PushTarget(); - path2.PushMember(nameof(Class.IntMember)); - clone = path2.Clone(newRoot); - AssertAreNotEqual(path2, clone); - AssertAreNotEqual(path2.GetHashCode(), clone.GetHashCode()); - AssertAreNotEqual(newRoot, path2.RootNode); - AssertAreEqual(newRoot, clone.RootNode); - AssertAreEqual(path2.IsEmpty, clone.IsEmpty); - var path3 = path1.Clone(); - path3.PushMember(nameof(Class.ListMember)); - path3.PushIndex(new NodeIndex(1)); - path3.PushMember(nameof(Class.IntMember)); - clone = path3.Clone(newRoot); - AssertAreNotEqual(path3, clone); - AssertAreNotEqual(path3.GetHashCode(), clone.GetHashCode()); - AssertAreNotEqual(newRoot, path3.RootNode); - AssertAreEqual(newRoot, clone.RootNode); - AssertAreEqual(path3.IsEmpty, clone.IsEmpty); - } + [Fact] + public void TestCloneNewRoot() + { + var obj1 = new Class { ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; + var obj2 = new Class { ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; + var nodeContainer = new NodeContainer(); + var newRoot = nodeContainer.GetOrCreateNode(obj2); + var path1 = new GraphNodePath(nodeContainer.GetOrCreateNode(obj1)); + var clone = path1.Clone(newRoot); + AssertAreNotEqual(path1, clone); + AssertAreNotEqual(path1.GetHashCode(), clone.GetHashCode()); + AssertAreNotEqual(newRoot, path1.RootNode); + AssertAreEqual(newRoot, clone.RootNode); + AssertAreEqual(path1.IsEmpty, clone.IsEmpty); + var path2 = path1.Clone(); + path2.PushMember(nameof(Class.ClassMember)); + path2.PushTarget(); + path2.PushMember(nameof(Class.IntMember)); + clone = path2.Clone(newRoot); + AssertAreNotEqual(path2, clone); + AssertAreNotEqual(path2.GetHashCode(), clone.GetHashCode()); + AssertAreNotEqual(newRoot, path2.RootNode); + AssertAreEqual(newRoot, clone.RootNode); + AssertAreEqual(path2.IsEmpty, clone.IsEmpty); + var path3 = path1.Clone(); + path3.PushMember(nameof(Class.ListMember)); + path3.PushIndex(new NodeIndex(1)); + path3.PushMember(nameof(Class.IntMember)); + clone = path3.Clone(newRoot); + AssertAreNotEqual(path3, clone); + AssertAreNotEqual(path3.GetHashCode(), clone.GetHashCode()); + AssertAreNotEqual(newRoot, path3.RootNode); + AssertAreEqual(newRoot, clone.RootNode); + AssertAreEqual(path3.IsEmpty, clone.IsEmpty); + } - [Fact] - public void TestPushMember() + [Fact] + public void TestPushMember() + { + var obj = new Class(); + var nodeContainer = new NodeContainer(); + var rootNode = nodeContainer.GetOrCreateNode(obj); + var path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.IntMember)); + var intNode = rootNode[nameof(Class.IntMember)]; + IGraphNode[] nodes = [rootNode, intNode]; + Assert.NotNull(intNode); + Assert.False(path.IsEmpty); + AssertAreEqual(rootNode, path.RootNode); + AssertAreEqual(intNode, path.GetNode()); + var i = 0; + foreach (var node in path) { - var obj = new Class(); - var nodeContainer = new NodeContainer(); - var rootNode = nodeContainer.GetOrCreateNode(obj); - var path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.IntMember)); - var intNode = rootNode[nameof(Class.IntMember)]; - var nodes = new IGraphNode[] { rootNode, intNode }; - Assert.NotNull(intNode); - Assert.False(path.IsEmpty); - AssertAreEqual(rootNode, path.RootNode); - AssertAreEqual(intNode, path.GetNode()); - var i = 0; - foreach (var node in path) - { - AssertAreEqual(nodes[i++], node); - } - AssertAreEqual(nodes.Length, i); + AssertAreEqual(nodes[i++], node); } + AssertAreEqual(nodes.Length, i); + } - [Fact] - public void TestPushStructMember() + [Fact] + public void TestPushStructMember() + { + var obj = new Class { StructMember = { StringMember = "aa" } }; + var nodeContainer = new NodeContainer(); + var rootNode = nodeContainer.GetOrCreateNode(obj); + var path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.StructMember)); + path.PushTarget(); + path.PushMember(nameof(Struct.StringMember)); + var structNode = rootNode[nameof(Class.StructMember)]; + var targetNode = rootNode[nameof(Class.StructMember)].Target; + var memberNode = rootNode[nameof(Class.StructMember)].Target[nameof(Struct.StringMember)]; + IGraphNode[] nodes = [rootNode, structNode, targetNode, memberNode]; + Assert.NotNull(targetNode); + Assert.NotNull(memberNode); + Assert.False(path.IsEmpty); + AssertAreEqual(rootNode, path.RootNode); + AssertAreEqual(memberNode, path.GetNode()); + var i = 0; + foreach (var node in path) { - var obj = new Class { StructMember = { StringMember = "aa" } }; - var nodeContainer = new NodeContainer(); - var rootNode = nodeContainer.GetOrCreateNode(obj); - var path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.StructMember)); - path.PushTarget(); - path.PushMember(nameof(Struct.StringMember)); - var structNode = rootNode[nameof(Class.StructMember)]; - var targetNode = rootNode[nameof(Class.StructMember)].Target; - var memberNode = rootNode[nameof(Class.StructMember)].Target[nameof(Struct.StringMember)]; - var nodes = new IGraphNode[] { rootNode, structNode, targetNode, memberNode }; - Assert.NotNull(targetNode); - Assert.NotNull(memberNode); - Assert.False(path.IsEmpty); - AssertAreEqual(rootNode, path.RootNode); - AssertAreEqual(memberNode, path.GetNode()); - var i = 0; - foreach (var node in path) - { - AssertAreEqual(nodes[i++], node); - } - AssertAreEqual(nodes.Length, i); + AssertAreEqual(nodes[i++], node); } + AssertAreEqual(nodes.Length, i); + } - [Fact] - public void TestPushTarget() + [Fact] + public void TestPushTarget() + { + var obj = new Class { ClassMember = new Class() }; + var nodeContainer = new NodeContainer(); + var rootNode = nodeContainer.GetOrCreateNode(obj); + var path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.ClassMember)); + path.PushTarget(); + var targetNode = nodeContainer.GetNode(obj.ClassMember); + IGraphNode[] nodes = [rootNode, rootNode[nameof(Class.ClassMember)], targetNode]; + Assert.NotNull(targetNode); + Assert.False(path.IsEmpty); + AssertAreEqual(rootNode, path.RootNode); + AssertAreEqual(targetNode, path.GetNode()); + var i = 0; + foreach (var node in path) { - var obj = new Class { ClassMember = new Class() }; - var nodeContainer = new NodeContainer(); - var rootNode = nodeContainer.GetOrCreateNode(obj); - var path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.ClassMember)); - path.PushTarget(); - var targetNode = nodeContainer.GetNode(obj.ClassMember); - var nodes = new IGraphNode[] { rootNode, rootNode[nameof(Class.ClassMember)], targetNode }; - Assert.NotNull(targetNode); - Assert.False(path.IsEmpty); - AssertAreEqual(rootNode, path.RootNode); - AssertAreEqual(targetNode, path.GetNode()); - var i = 0; - foreach (var node in path) - { - AssertAreEqual(nodes[i++], node); - } - AssertAreEqual(nodes.Length, i); + AssertAreEqual(nodes[i++], node); } + AssertAreEqual(nodes.Length, i); + } - [Fact] - public void TestPushTargetAndMember() + [Fact] + public void TestPushTargetAndMember() + { + var obj = new Class { ClassMember = new Class() }; + var nodeContainer = new NodeContainer(); + var rootNode = nodeContainer.GetOrCreateNode(obj); + var path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.ClassMember)); + path.PushTarget(); + path.PushMember(nameof(Class.IntMember)); + var targetNode = nodeContainer.GetNode(obj.ClassMember); + var intNode = targetNode[nameof(Class.IntMember)]; + IGraphNode[] nodes = [rootNode, rootNode[nameof(Class.ClassMember)], targetNode, intNode]; + Assert.NotNull(targetNode); + Assert.NotNull(intNode); + Assert.False(path.IsEmpty); + AssertAreEqual(rootNode, path.RootNode); + AssertAreEqual(intNode, path.GetNode()); + var i = 0; + foreach (var node in path) { - var obj = new Class { ClassMember = new Class() }; - var nodeContainer = new NodeContainer(); - var rootNode = nodeContainer.GetOrCreateNode(obj); - var path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.ClassMember)); - path.PushTarget(); - path.PushMember(nameof(Class.IntMember)); - var targetNode = nodeContainer.GetNode(obj.ClassMember); - var intNode = targetNode[nameof(Class.IntMember)]; - var nodes = new IGraphNode[] { rootNode, rootNode[nameof(Class.ClassMember)], targetNode, intNode }; - Assert.NotNull(targetNode); - Assert.NotNull(intNode); - Assert.False(path.IsEmpty); - AssertAreEqual(rootNode, path.RootNode); - AssertAreEqual(intNode, path.GetNode()); - var i = 0; - foreach (var node in path) - { - AssertAreEqual(nodes[i++], node); - } - AssertAreEqual(nodes.Length, i); + AssertAreEqual(nodes[i++], node); } + AssertAreEqual(nodes.Length, i); + } - [Fact] - public void TestPushIndex() + [Fact] + public void TestPushIndex() + { + var obj = new Class { ListMember = { new Class(), new Class(), new Class() } }; + var nodeContainer = new NodeContainer(); + var rootNode = nodeContainer.GetOrCreateNode(obj); + var path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.ListMember)); + path.PushTarget(); + path.PushIndex(new NodeIndex(1)); + var targetNode = nodeContainer.GetNode(obj.ListMember[1]); + IGraphNode[] nodes = [rootNode, rootNode[nameof(Class.ListMember)], rootNode[nameof(Class.ListMember)].Target, targetNode]; + Assert.NotNull(targetNode); + Assert.False(path.IsEmpty); + AssertAreEqual(rootNode, path.RootNode); + AssertAreEqual(targetNode, path.GetNode()); + var i = 0; + foreach (var node in path) { - var obj = new Class { ListMember = { new Class(), new Class(), new Class() } }; - var nodeContainer = new NodeContainer(); - var rootNode = nodeContainer.GetOrCreateNode(obj); - var path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.ListMember)); - path.PushTarget(); - path.PushIndex(new NodeIndex(1)); - var targetNode = nodeContainer.GetNode(obj.ListMember[1]); - var nodes = new IGraphNode[] { rootNode, rootNode[nameof(Class.ListMember)], rootNode[nameof(Class.ListMember)].Target, targetNode }; - Assert.NotNull(targetNode); - Assert.False(path.IsEmpty); - AssertAreEqual(rootNode, path.RootNode); - AssertAreEqual(targetNode, path.GetNode()); - var i = 0; - foreach (var node in path) - { - AssertAreEqual(nodes[i++], node); - } - AssertAreEqual(nodes.Length, i); + AssertAreEqual(nodes[i++], node); } + AssertAreEqual(nodes.Length, i); + } - [Fact] - public void TestPushIndexAndMember() + [Fact] + public void TestPushIndexAndMember() + { + var obj = new Class { ListMember = { new Class(), new Class(), new Class() } }; + var nodeContainer = new NodeContainer(); + var rootNode = nodeContainer.GetOrCreateNode(obj); + var path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.ListMember)); + path.PushTarget(); + path.PushIndex(new NodeIndex(1)); + path.PushMember(nameof(Class.IntMember)); + var targetNode = nodeContainer.GetNode(obj.ListMember[1]); + var intNode = targetNode[nameof(Class.IntMember)]; + IGraphNode[] nodes = [rootNode, rootNode[nameof(Class.ListMember)], rootNode[nameof(Class.ListMember)].Target, targetNode, intNode]; + Assert.NotNull(targetNode); + Assert.NotNull(intNode); + Assert.False(path.IsEmpty); + AssertAreEqual(rootNode, path.RootNode); + AssertAreEqual(intNode, path.GetNode()); + var i = 0; + foreach (var node in path) { - var obj = new Class { ListMember = { new Class(), new Class(), new Class() } }; - var nodeContainer = new NodeContainer(); - var rootNode = nodeContainer.GetOrCreateNode(obj); - var path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.ListMember)); - path.PushTarget(); - path.PushIndex(new NodeIndex(1)); - path.PushMember(nameof(Class.IntMember)); - var targetNode = nodeContainer.GetNode(obj.ListMember[1]); - var intNode = targetNode[nameof(Class.IntMember)]; - var nodes = new IGraphNode[] { rootNode, rootNode[nameof(Class.ListMember)], rootNode[nameof(Class.ListMember)].Target, targetNode, intNode }; - Assert.NotNull(targetNode); - Assert.NotNull(intNode); - Assert.False(path.IsEmpty); - AssertAreEqual(rootNode, path.RootNode); - AssertAreEqual(intNode, path.GetNode()); - var i = 0; - foreach (var node in path) - { - AssertAreEqual(nodes[i++], node); - } - AssertAreEqual(nodes.Length, i); + AssertAreEqual(nodes[i++], node); } + AssertAreEqual(nodes.Length, i); + } - [Fact] - public void TestGetParent() - { - var obj = new Class { StructMember = { StringMember = "aa" }, ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; - var nodeContainer = new NodeContainer(); - var rootNode = nodeContainer.GetOrCreateNode(obj); + [Fact] + public void TestGetParent() + { + var obj = new Class { StructMember = { StringMember = "aa" }, ClassMember = new Class(), ListMember = { new Class(), new Class(), new Class() } }; + var nodeContainer = new NodeContainer(); + var rootNode = nodeContainer.GetOrCreateNode(obj); - var path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.IntMember)); - var parentPath = new GraphNodePath(rootNode); - AssertAreEqual(parentPath, path.GetParent()); + var path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.IntMember)); + var parentPath = new GraphNodePath(rootNode); + AssertAreEqual(parentPath, path.GetParent()); - path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.StructMember)); - path.PushMember(nameof(Struct.StringMember)); - parentPath = new GraphNodePath(rootNode); - parentPath.PushMember(nameof(Class.StructMember)); - AssertAreEqual(parentPath, path.GetParent()); + path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.StructMember)); + path.PushMember(nameof(Struct.StringMember)); + parentPath = new GraphNodePath(rootNode); + parentPath.PushMember(nameof(Class.StructMember)); + AssertAreEqual(parentPath, path.GetParent()); - path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.ClassMember)); - path.PushTarget(); - parentPath = new GraphNodePath(rootNode); - parentPath.PushMember(nameof(Class.ClassMember)); - AssertAreEqual(parentPath, path.GetParent()); + path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.ClassMember)); + path.PushTarget(); + parentPath = new GraphNodePath(rootNode); + parentPath.PushMember(nameof(Class.ClassMember)); + AssertAreEqual(parentPath, path.GetParent()); - path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.ClassMember)); - path.PushTarget(); - path.PushMember(nameof(Class.IntMember)); - parentPath = new GraphNodePath(rootNode); - parentPath.PushMember(nameof(Class.ClassMember)); - parentPath.PushTarget(); - AssertAreEqual(parentPath, path.GetParent()); + path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.ClassMember)); + path.PushTarget(); + path.PushMember(nameof(Class.IntMember)); + parentPath = new GraphNodePath(rootNode); + parentPath.PushMember(nameof(Class.ClassMember)); + parentPath.PushTarget(); + AssertAreEqual(parentPath, path.GetParent()); - path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.ListMember)); - path.PushIndex(new NodeIndex(1)); - parentPath = new GraphNodePath(rootNode); - parentPath.PushMember(nameof(Class.ListMember)); - AssertAreEqual(parentPath, path.GetParent()); + path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.ListMember)); + path.PushIndex(new NodeIndex(1)); + parentPath = new GraphNodePath(rootNode); + parentPath.PushMember(nameof(Class.ListMember)); + AssertAreEqual(parentPath, path.GetParent()); - path = new GraphNodePath(rootNode); - path.PushMember(nameof(Class.ListMember)); - path.PushIndex(new NodeIndex(1)); - path.PushMember(nameof(Class.IntMember)); - parentPath = new GraphNodePath(rootNode); - parentPath.PushMember(nameof(Class.ListMember)); - parentPath.PushIndex(new NodeIndex(1)); - AssertAreEqual(parentPath, path.GetParent()); - } + path = new GraphNodePath(rootNode); + path.PushMember(nameof(Class.ListMember)); + path.PushIndex(new NodeIndex(1)); + path.PushMember(nameof(Class.IntMember)); + parentPath = new GraphNodePath(rootNode); + parentPath.PushMember(nameof(Class.ListMember)); + parentPath.PushIndex(new NodeIndex(1)); + AssertAreEqual(parentPath, path.GetParent()); + } - // NUnit does not use the Equals method for objects that implement IEnumerable, but that's what we want to use for GraphNodePath - // ReSharper disable UnusedParameter.Local - private static void AssertAreEqual(object expected, object actual) - { - Assert.True(expected.Equals(actual)); - } + // NUnit does not use the Equals method for objects that implement IEnumerable, but that's what we want to use for GraphNodePath + // ReSharper disable UnusedParameter.Local + private static void AssertAreEqual(object expected, object actual) + { + Assert.True(expected.Equals(actual)); + } - private static void AssertAreNotEqual(object expected, object actual) - { - Assert.False(expected.Equals(actual)); - } - // ReSharper restore UnusedParameter.Local + private static void AssertAreNotEqual(object expected, object actual) + { + Assert.False(expected.Equals(actual)); } + // ReSharper restore UnusedParameter.Local } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/TestGraphVisitor.cs b/sources/presentation/Stride.Core.Quantum.Tests/TestGraphVisitor.cs index fff06141b2..15d199896b 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/TestGraphVisitor.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/TestGraphVisitor.cs @@ -1,446 +1,444 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using Xunit; -namespace Stride.Core.Quantum.Tests +namespace Stride.Core.Quantum.Tests; + +public class TestGraphVisitor { - public class TestGraphVisitor + public class SimpleClass { - public class SimpleClass - { - public int Member1; - public SimpleClass Member2; - } - - public class SimpleClass2 - { - public int Member1; - public SimpleClass Member2; - public SimpleClass Member3; - } - - public class StructClass - { - public int Member1; - public Struct Member2; - } + public int Member1; + public SimpleClass Member2; + } - public class PrimitiveListClass - { - public int Member1; - public List Member2; - } + public class SimpleClass2 + { + public int Member1; + public SimpleClass Member2; + public SimpleClass Member3; + } - public class ObjectListClass - { - public int Member1; - public List Member2; - } + public class StructClass + { + public int Member1; + public Struct Member2; + } - public class StructListClass - { - public int Member1; - public List Member2; - } + public class PrimitiveListClass + { + public int Member1; + public List Member2; + } - public struct Struct - { - public string Member1; - public SimpleClass Member2; - } + public class ObjectListClass + { + public int Member1; + public List Member2; + } - public class TestVisitor : GraphVisitorBase - { - public readonly List> Result = new List>(); + public class StructListClass + { + public int Member1; + public List Member2; + } - public override void Visit(IGraphNode node, MemberNode memberNode = null, GraphNodePath initialPath = null) - { - Result.Clear(); - base.Visit(node, memberNode, initialPath); - } + public struct Struct + { + public string Member1; + public SimpleClass Member2; + } - protected override void VisitNode(IGraphNode node) - { - var currentPath = CurrentPath.Clone(); - Result.Add(Tuple.Create(node, currentPath)); - base.VisitNode(node); - } - } + public class TestVisitor : GraphVisitorBase + { + public readonly List> Result = []; - [Fact] - public void TestSimpleObject() + public override void Visit(IGraphNode node, MemberNode memberNode = null, GraphNodePath initialPath = null) { - var nodeContainer = new NodeContainer(); - var instance = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var visitor = new TestVisitor(); - visitor.Visit(rootNode); - var expectedNodes = new IGraphNode[] - { - rootNode, - rootNode[nameof(SimpleClass.Member1)], - rootNode[nameof(SimpleClass.Member2)], - rootNode[nameof(SimpleClass.Member2)].Target, - rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], - rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], - }; - var expectedPaths = new GraphNodePath[6]; - expectedPaths[0] = new GraphNodePath(rootNode); - expectedPaths[1] = new GraphNodePath(rootNode); - expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[2] = new GraphNodePath(rootNode); - expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); - expectedPaths[3] = expectedPaths[2].Clone(); - expectedPaths[3].PushTarget(); - expectedPaths[4] = expectedPaths[3].Clone(); - expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[5] = expectedPaths[3].Clone(); - expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + Result.Clear(); + base.Visit(node, memberNode, initialPath); } - [Fact] - public void TestSimpleObjectInitialPath() + protected override void VisitNode(IGraphNode node) { - var nodeContainer = new NodeContainer(); - var instance = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var container = new SimpleClass { Member2 = instance }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var initialPath = new GraphNodePath(containerNode); - initialPath.PushMember(nameof(SimpleClass.Member2)); - initialPath.PushTarget(); - var visitor = new TestVisitor(); - visitor.Visit(rootNode, null, initialPath); - var expectedNodes = new IGraphNode[] - { - rootNode, - rootNode[nameof(SimpleClass.Member1)], - rootNode[nameof(SimpleClass.Member2)], - rootNode[nameof(SimpleClass.Member2)].Target, - rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], - rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], - }; - var expectedPaths = new GraphNodePath[6]; - expectedPaths[0] = initialPath.Clone(); - expectedPaths[1] = initialPath.Clone(); - expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[2] = initialPath.Clone(); - expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); - expectedPaths[3] = expectedPaths[2].Clone(); - expectedPaths[3].PushTarget(); - expectedPaths[4] = expectedPaths[3].Clone(); - expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[5] = expectedPaths[3].Clone(); - expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + var currentPath = CurrentPath.Clone(); + Result.Add(Tuple.Create(node, currentPath)); + base.VisitNode(node); } + } - [Fact] - public void TestSimpleObjectWithNull() - { - var nodeContainer = new NodeContainer(); - var instance = new SimpleClass { Member1 = 3, Member2 = null }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var visitor = new TestVisitor(); - visitor.Visit(rootNode); - var expectedNodes = new IGraphNode[] - { - rootNode, - rootNode[nameof(SimpleClass.Member1)], - rootNode[nameof(SimpleClass.Member2)], - }; - var expectedPaths = new GraphNodePath[3]; - expectedPaths[0] = new GraphNodePath(rootNode); - expectedPaths[1] = expectedPaths[0].Clone(); - expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[2] = expectedPaths[0].Clone(); - expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); - } + [Fact] + public void TestSimpleObject() + { + var nodeContainer = new NodeContainer(); + var instance = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var visitor = new TestVisitor(); + visitor.Visit(rootNode); + IGraphNode[] expectedNodes = + [ + rootNode, + rootNode[nameof(SimpleClass.Member1)], + rootNode[nameof(SimpleClass.Member2)], + rootNode[nameof(SimpleClass.Member2)].Target, + rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], + rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], + ]; + var expectedPaths = new GraphNodePath[6]; + expectedPaths[0] = new GraphNodePath(rootNode); + expectedPaths[1] = new GraphNodePath(rootNode); + expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[2] = new GraphNodePath(rootNode); + expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); + expectedPaths[3] = expectedPaths[2].Clone(); + expectedPaths[3].PushTarget(); + expectedPaths[4] = expectedPaths[3].Clone(); + expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[5] = expectedPaths[3].Clone(); + expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + } - [Fact] - public void TestObjectWithStruct() - { - var nodeContainer = new NodeContainer(); - var instance = new StructClass { Member1 = 3, Member2 = new Struct() }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var visitor = new TestVisitor(); - visitor.Visit(rootNode); - var expectedNodes = new IGraphNode[] - { - rootNode, - rootNode[nameof(StructClass.Member1)], - rootNode[nameof(StructClass.Member2)], - rootNode[nameof(StructClass.Member2)].Target, - rootNode[nameof(StructClass.Member2)].Target[nameof(Struct.Member1)], - rootNode[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)], - }; - var expectedPaths = new GraphNodePath[6]; - expectedPaths[0] = new GraphNodePath(rootNode); - expectedPaths[1] = expectedPaths[0].Clone(); - expectedPaths[1].PushMember(nameof(StructClass.Member1)); - expectedPaths[2] = expectedPaths[0].Clone(); - expectedPaths[2].PushMember(nameof(StructClass.Member2)); - expectedPaths[3] = expectedPaths[2].Clone(); - expectedPaths[3].PushTarget(); - expectedPaths[4] = expectedPaths[3].Clone(); - expectedPaths[4].PushMember(nameof(Struct.Member1)); - expectedPaths[5] = expectedPaths[3].Clone(); - expectedPaths[5].PushMember(nameof(Struct.Member2)); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); - } + [Fact] + public void TestSimpleObjectInitialPath() + { + var nodeContainer = new NodeContainer(); + var instance = new SimpleClass { Member1 = 3, Member2 = new SimpleClass() }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var container = new SimpleClass { Member2 = instance }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var initialPath = new GraphNodePath(containerNode); + initialPath.PushMember(nameof(SimpleClass.Member2)); + initialPath.PushTarget(); + var visitor = new TestVisitor(); + visitor.Visit(rootNode, null, initialPath); + IGraphNode[] expectedNodes = + [ + rootNode, + rootNode[nameof(SimpleClass.Member1)], + rootNode[nameof(SimpleClass.Member2)], + rootNode[nameof(SimpleClass.Member2)].Target, + rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], + rootNode[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], + ]; + var expectedPaths = new GraphNodePath[6]; + expectedPaths[0] = initialPath.Clone(); + expectedPaths[1] = initialPath.Clone(); + expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[2] = initialPath.Clone(); + expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); + expectedPaths[3] = expectedPaths[2].Clone(); + expectedPaths[3].PushTarget(); + expectedPaths[4] = expectedPaths[3].Clone(); + expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[5] = expectedPaths[3].Clone(); + expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + } - [Fact] - public void TestObjectWithPrimitiveList() - { - var nodeContainer = new NodeContainer(); - var instance = new PrimitiveListClass { Member1 = 3, Member2 = new List { "aaa", "bbb", "ccc" } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var visitor = new TestVisitor(); - visitor.Visit(rootNode); - var expectedNodes = new IGraphNode[] - { - rootNode, - rootNode[nameof(PrimitiveListClass.Member1)], - rootNode[nameof(PrimitiveListClass.Member2)], - rootNode[nameof(PrimitiveListClass.Member2)].Target, - }; - var expectedPaths = new GraphNodePath[4]; - expectedPaths[0] = new GraphNodePath(rootNode); - expectedPaths[1] = expectedPaths[0].Clone(); - expectedPaths[1].PushMember(nameof(PrimitiveListClass.Member1)); - expectedPaths[2] = expectedPaths[0].Clone(); - expectedPaths[2].PushMember(nameof(PrimitiveListClass.Member2)); - expectedPaths[3] = expectedPaths[2].Clone(); - expectedPaths[3].PushTarget(); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); - } - - [Fact] - public void TestObjectWithObjectList() - { - var nodeContainer = new NodeContainer(); - // We also test a null item in the list - var instance = new ObjectListClass { Member1 = 3, Member2 = new List { new SimpleClass(), null, new SimpleClass() } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var visitor = new TestVisitor(); - visitor.Visit(rootNode); - var expectedNodes = new IGraphNode[] - { - rootNode, - rootNode[nameof(ObjectListClass.Member1)], - rootNode[nameof(ObjectListClass.Member2)], - rootNode[nameof(ObjectListClass.Member2)].Target, - rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(0)), - rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(ObjectListClass.Member1)], - rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(ObjectListClass.Member2)], - rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(2)), - rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(ObjectListClass.Member1)], - rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(ObjectListClass.Member2)], - }; - var expectedPaths = new GraphNodePath[10]; - expectedPaths[0] = new GraphNodePath(rootNode); - expectedPaths[1] = expectedPaths[0].Clone(); - expectedPaths[1].PushMember(nameof(ObjectListClass.Member1)); - expectedPaths[2] = expectedPaths[0].Clone(); - expectedPaths[2].PushMember(nameof(ObjectListClass.Member2)); - expectedPaths[3] = expectedPaths[2].Clone(); - expectedPaths[3].PushTarget(); - expectedPaths[4] = expectedPaths[3].Clone(); - expectedPaths[4].PushIndex(new NodeIndex(0)); - expectedPaths[5] = expectedPaths[4].Clone(); - expectedPaths[5].PushMember(nameof(ObjectListClass.Member1)); - expectedPaths[6] = expectedPaths[4].Clone(); - expectedPaths[6].PushMember(nameof(ObjectListClass.Member2)); - expectedPaths[7] = expectedPaths[3].Clone(); - expectedPaths[7].PushIndex(new NodeIndex(2)); - expectedPaths[8] = expectedPaths[7].Clone(); - expectedPaths[8].PushMember(nameof(ObjectListClass.Member1)); - expectedPaths[9] = expectedPaths[7].Clone(); - expectedPaths[9].PushMember(nameof(ObjectListClass.Member2)); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); - } + [Fact] + public void TestSimpleObjectWithNull() + { + var nodeContainer = new NodeContainer(); + var instance = new SimpleClass { Member1 = 3, Member2 = null }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var visitor = new TestVisitor(); + visitor.Visit(rootNode); + IGraphNode[] expectedNodes = + [ + rootNode, + rootNode[nameof(SimpleClass.Member1)], + rootNode[nameof(SimpleClass.Member2)], + ]; + var expectedPaths = new GraphNodePath[3]; + expectedPaths[0] = new GraphNodePath(rootNode); + expectedPaths[1] = expectedPaths[0].Clone(); + expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[2] = expectedPaths[0].Clone(); + expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + } - [Fact] - public void TestObjectWithStructList() - { - var nodeContainer = new NodeContainer(); - // We also test a null item in the list - var instance = new StructListClass { Member1 = 3, Member2 = new List { new Struct(), new Struct() } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var visitor = new TestVisitor(); - visitor.Visit(rootNode); - var expectedNodes = new IGraphNode[] - { - rootNode, - rootNode[nameof(StructListClass.Member1)], - rootNode[nameof(StructListClass.Member2)], - rootNode[nameof(StructListClass.Member2)].Target, - rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(0)), - rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(StructListClass.Member1)], - rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(StructListClass.Member2)], - rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(1)), - rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(StructListClass.Member1)], - rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(StructListClass.Member2)], - }; - var expectedPaths = new GraphNodePath[10]; - expectedPaths[0] = new GraphNodePath(rootNode); - expectedPaths[1] = expectedPaths[0].Clone(); - expectedPaths[1].PushMember(nameof(StructListClass.Member1)); - expectedPaths[2] = expectedPaths[0].Clone(); - expectedPaths[2].PushMember(nameof(StructListClass.Member2)); - expectedPaths[3] = expectedPaths[2].Clone(); - expectedPaths[3].PushTarget(); - expectedPaths[4] = expectedPaths[3].Clone(); - expectedPaths[4].PushIndex(new NodeIndex(0)); - expectedPaths[5] = expectedPaths[4].Clone(); - expectedPaths[5].PushMember(nameof(StructListClass.Member1)); - expectedPaths[6] = expectedPaths[4].Clone(); - expectedPaths[6].PushMember(nameof(StructListClass.Member2)); - expectedPaths[7] = expectedPaths[3].Clone(); - expectedPaths[7].PushIndex(new NodeIndex(1)); - expectedPaths[8] = expectedPaths[7].Clone(); - expectedPaths[8].PushMember(nameof(StructListClass.Member1)); - expectedPaths[9] = expectedPaths[7].Clone(); - expectedPaths[9].PushMember(nameof(StructListClass.Member2)); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); - } + [Fact] + public void TestObjectWithStruct() + { + var nodeContainer = new NodeContainer(); + var instance = new StructClass { Member1 = 3, Member2 = new Struct() }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var visitor = new TestVisitor(); + visitor.Visit(rootNode); + IGraphNode[] expectedNodes = + [ + rootNode, + rootNode[nameof(StructClass.Member1)], + rootNode[nameof(StructClass.Member2)], + rootNode[nameof(StructClass.Member2)].Target, + rootNode[nameof(StructClass.Member2)].Target[nameof(Struct.Member1)], + rootNode[nameof(StructClass.Member2)].Target[nameof(Struct.Member2)], + ]; + var expectedPaths = new GraphNodePath[6]; + expectedPaths[0] = new GraphNodePath(rootNode); + expectedPaths[1] = expectedPaths[0].Clone(); + expectedPaths[1].PushMember(nameof(StructClass.Member1)); + expectedPaths[2] = expectedPaths[0].Clone(); + expectedPaths[2].PushMember(nameof(StructClass.Member2)); + expectedPaths[3] = expectedPaths[2].Clone(); + expectedPaths[3].PushTarget(); + expectedPaths[4] = expectedPaths[3].Clone(); + expectedPaths[4].PushMember(nameof(Struct.Member1)); + expectedPaths[5] = expectedPaths[3].Clone(); + expectedPaths[5].PushMember(nameof(Struct.Member2)); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + } - [Fact] - public void TestCircularReference() - { - var nodeContainer = new NodeContainer(); - var obj1 = new SimpleClass { Member1 = 3 }; - var obj2 = new SimpleClass { Member1 = 3 }; - obj1.Member2 = obj2; - obj2.Member2 = obj1; - var rootNode1 = nodeContainer.GetOrCreateNode(obj1); - var rootNode2 = nodeContainer.GetOrCreateNode(obj2); - var visitor = new TestVisitor(); - visitor.Visit(rootNode1); - var expectedNodes = new IGraphNode[] - { - rootNode1, - rootNode1[nameof(SimpleClass.Member1)], - rootNode1[nameof(SimpleClass.Member2)], - rootNode2, - rootNode2[nameof(SimpleClass.Member1)], - rootNode2[nameof(SimpleClass.Member2)], - }; - var expectedPaths = new GraphNodePath[6]; - expectedPaths[0] = new GraphNodePath(rootNode1); - expectedPaths[1] = expectedPaths[0].Clone(); - expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[2] = expectedPaths[0].Clone(); - expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); - expectedPaths[3] = expectedPaths[2].Clone(); - expectedPaths[3].PushTarget(); - expectedPaths[4] = expectedPaths[3].Clone(); - expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[5] = expectedPaths[3].Clone(); - expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + [Fact] + public void TestObjectWithPrimitiveList() + { + var nodeContainer = new NodeContainer(); + var instance = new PrimitiveListClass { Member1 = 3, Member2 = ["aaa", "bbb", "ccc"] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var visitor = new TestVisitor(); + visitor.Visit(rootNode); + IGraphNode[] expectedNodes = + [ + rootNode, + rootNode[nameof(PrimitiveListClass.Member1)], + rootNode[nameof(PrimitiveListClass.Member2)], + rootNode[nameof(PrimitiveListClass.Member2)].Target, + ]; + var expectedPaths = new GraphNodePath[4]; + expectedPaths[0] = new GraphNodePath(rootNode); + expectedPaths[1] = expectedPaths[0].Clone(); + expectedPaths[1].PushMember(nameof(PrimitiveListClass.Member1)); + expectedPaths[2] = expectedPaths[0].Clone(); + expectedPaths[2].PushMember(nameof(PrimitiveListClass.Member2)); + expectedPaths[3] = expectedPaths[2].Clone(); + expectedPaths[3].PushTarget(); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + } - visitor.Visit(rootNode2); - expectedNodes = new IGraphNode[] - { - rootNode2, - rootNode2[nameof(SimpleClass.Member1)], - rootNode2[nameof(SimpleClass.Member2)], - rootNode1, - rootNode1[nameof(SimpleClass.Member1)], - rootNode1[nameof(SimpleClass.Member2)], - }; - expectedPaths = new GraphNodePath[6]; - expectedPaths[0] = new GraphNodePath(rootNode2); - expectedPaths[1] = expectedPaths[0].Clone(); - expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[2] = expectedPaths[0].Clone(); - expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); - expectedPaths[3] = expectedPaths[2].Clone(); - expectedPaths[3].PushTarget(); - expectedPaths[4] = expectedPaths[3].Clone(); - expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[5] = expectedPaths[3].Clone(); - expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); - } + [Fact] + public void TestObjectWithObjectList() + { + var nodeContainer = new NodeContainer(); + // We also test a null item in the list + var instance = new ObjectListClass { Member1 = 3, Member2 = [new(), null, new()] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var visitor = new TestVisitor(); + visitor.Visit(rootNode); + IGraphNode[] expectedNodes = + [ + rootNode, + rootNode[nameof(ObjectListClass.Member1)], + rootNode[nameof(ObjectListClass.Member2)], + rootNode[nameof(ObjectListClass.Member2)].Target, + rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(0)), + rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(ObjectListClass.Member1)], + rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(ObjectListClass.Member2)], + rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(2)), + rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(ObjectListClass.Member1)], + rootNode[nameof(ObjectListClass.Member2)].Target.IndexedTarget(new NodeIndex(2))[nameof(ObjectListClass.Member2)], + ]; + var expectedPaths = new GraphNodePath[10]; + expectedPaths[0] = new GraphNodePath(rootNode); + expectedPaths[1] = expectedPaths[0].Clone(); + expectedPaths[1].PushMember(nameof(ObjectListClass.Member1)); + expectedPaths[2] = expectedPaths[0].Clone(); + expectedPaths[2].PushMember(nameof(ObjectListClass.Member2)); + expectedPaths[3] = expectedPaths[2].Clone(); + expectedPaths[3].PushTarget(); + expectedPaths[4] = expectedPaths[3].Clone(); + expectedPaths[4].PushIndex(new NodeIndex(0)); + expectedPaths[5] = expectedPaths[4].Clone(); + expectedPaths[5].PushMember(nameof(ObjectListClass.Member1)); + expectedPaths[6] = expectedPaths[4].Clone(); + expectedPaths[6].PushMember(nameof(ObjectListClass.Member2)); + expectedPaths[7] = expectedPaths[3].Clone(); + expectedPaths[7].PushIndex(new NodeIndex(2)); + expectedPaths[8] = expectedPaths[7].Clone(); + expectedPaths[8].PushMember(nameof(ObjectListClass.Member1)); + expectedPaths[9] = expectedPaths[7].Clone(); + expectedPaths[9].PushMember(nameof(ObjectListClass.Member2)); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + } - [Fact] - public void TestMultipleReferences() - { - var nodeContainer = new NodeContainer(); - var commonObj = new SimpleClass(); - var instance = new SimpleClass2 { Member1 = 3, Member2 = new SimpleClass { Member1 = 4, Member2 = commonObj }, Member3 = new SimpleClass { Member1 = 5, Member2 = commonObj } }; - var rootNode = nodeContainer.GetOrCreateNode(instance); - var visitor = new TestVisitor(); - visitor.Visit(rootNode); - var expectedNodes = new IGraphNode[] - { - rootNode, - rootNode[nameof(SimpleClass2.Member1)], - rootNode[nameof(SimpleClass2.Member2)], - rootNode[nameof(SimpleClass2.Member2)].Target, - rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member1)], - rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member2)], - rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member2)].Target, - rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], - rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], - rootNode[nameof(SimpleClass2.Member3)], - rootNode[nameof(SimpleClass2.Member3)].Target, - rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member1)], - rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member2)], - rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member2)].Target, - rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], - rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], - }; - var expectedPaths = new GraphNodePath[16]; - expectedPaths[0] = new GraphNodePath(rootNode); - expectedPaths[1] = expectedPaths[0].Clone(); - expectedPaths[1].PushMember(nameof(SimpleClass2.Member1)); - expectedPaths[2] = expectedPaths[0].Clone(); - expectedPaths[2].PushMember(nameof(SimpleClass2.Member2)); - expectedPaths[3] = expectedPaths[2].Clone(); - expectedPaths[3].PushTarget(); - expectedPaths[4] = expectedPaths[3].Clone(); - expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[5] = expectedPaths[3].Clone(); - expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); - expectedPaths[6] = expectedPaths[5].Clone(); - expectedPaths[6].PushTarget(); - expectedPaths[7] = expectedPaths[6].Clone(); - expectedPaths[7].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[8] = expectedPaths[6].Clone(); - expectedPaths[8].PushMember(nameof(SimpleClass.Member2)); - expectedPaths[9] = expectedPaths[0].Clone(); - expectedPaths[9].PushMember(nameof(SimpleClass2.Member3)); - expectedPaths[10] = expectedPaths[9].Clone(); - expectedPaths[10].PushTarget(); - expectedPaths[11] = expectedPaths[10].Clone(); - expectedPaths[11].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[12] = expectedPaths[10].Clone(); - expectedPaths[12].PushMember(nameof(SimpleClass.Member2)); - expectedPaths[13] = expectedPaths[12].Clone(); - expectedPaths[13].PushTarget(); - expectedPaths[14] = expectedPaths[13].Clone(); - expectedPaths[14].PushMember(nameof(SimpleClass.Member1)); - expectedPaths[15] = expectedPaths[13].Clone(); - expectedPaths[15].PushMember(nameof(SimpleClass.Member2)); - VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); - } + [Fact] + public void TestObjectWithStructList() + { + var nodeContainer = new NodeContainer(); + // We also test a null item in the list + var instance = new StructListClass { Member1 = 3, Member2 = [new(), new()] }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var visitor = new TestVisitor(); + visitor.Visit(rootNode); + IGraphNode[] expectedNodes = + [ + rootNode, + rootNode[nameof(StructListClass.Member1)], + rootNode[nameof(StructListClass.Member2)], + rootNode[nameof(StructListClass.Member2)].Target, + rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(0)), + rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(StructListClass.Member1)], + rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(0))[nameof(StructListClass.Member2)], + rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(1)), + rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(StructListClass.Member1)], + rootNode[nameof(StructListClass.Member2)].Target.IndexedTarget(new NodeIndex(1))[nameof(StructListClass.Member2)], + ]; + var expectedPaths = new GraphNodePath[10]; + expectedPaths[0] = new GraphNodePath(rootNode); + expectedPaths[1] = expectedPaths[0].Clone(); + expectedPaths[1].PushMember(nameof(StructListClass.Member1)); + expectedPaths[2] = expectedPaths[0].Clone(); + expectedPaths[2].PushMember(nameof(StructListClass.Member2)); + expectedPaths[3] = expectedPaths[2].Clone(); + expectedPaths[3].PushTarget(); + expectedPaths[4] = expectedPaths[3].Clone(); + expectedPaths[4].PushIndex(new NodeIndex(0)); + expectedPaths[5] = expectedPaths[4].Clone(); + expectedPaths[5].PushMember(nameof(StructListClass.Member1)); + expectedPaths[6] = expectedPaths[4].Clone(); + expectedPaths[6].PushMember(nameof(StructListClass.Member2)); + expectedPaths[7] = expectedPaths[3].Clone(); + expectedPaths[7].PushIndex(new NodeIndex(1)); + expectedPaths[8] = expectedPaths[7].Clone(); + expectedPaths[8].PushMember(nameof(StructListClass.Member1)); + expectedPaths[9] = expectedPaths[7].Clone(); + expectedPaths[9].PushMember(nameof(StructListClass.Member2)); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + } - private static void VerifyNodesAndPath(IReadOnlyList expectedNodes, IReadOnlyList expectedPaths, TestVisitor visitor) + [Fact] + public void TestCircularReference() + { + var nodeContainer = new NodeContainer(); + var obj1 = new SimpleClass { Member1 = 3 }; + var obj2 = new SimpleClass { Member1 = 3 }; + obj1.Member2 = obj2; + obj2.Member2 = obj1; + var rootNode1 = nodeContainer.GetOrCreateNode(obj1); + var rootNode2 = nodeContainer.GetOrCreateNode(obj2); + var visitor = new TestVisitor(); + visitor.Visit(rootNode1); + IGraphNode[] expectedNodes = + [ + rootNode1, + rootNode1[nameof(SimpleClass.Member1)], + rootNode1[nameof(SimpleClass.Member2)], + rootNode2, + rootNode2[nameof(SimpleClass.Member1)], + rootNode2[nameof(SimpleClass.Member2)], + ]; + var expectedPaths = new GraphNodePath[6]; + expectedPaths[0] = new GraphNodePath(rootNode1); + expectedPaths[1] = expectedPaths[0].Clone(); + expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[2] = expectedPaths[0].Clone(); + expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); + expectedPaths[3] = expectedPaths[2].Clone(); + expectedPaths[3].PushTarget(); + expectedPaths[4] = expectedPaths[3].Clone(); + expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[5] = expectedPaths[3].Clone(); + expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + + visitor.Visit(rootNode2); + expectedNodes = + [ + rootNode2, + rootNode2[nameof(SimpleClass.Member1)], + rootNode2[nameof(SimpleClass.Member2)], + rootNode1, + rootNode1[nameof(SimpleClass.Member1)], + rootNode1[nameof(SimpleClass.Member2)], + ]; + expectedPaths = new GraphNodePath[6]; + expectedPaths[0] = new GraphNodePath(rootNode2); + expectedPaths[1] = expectedPaths[0].Clone(); + expectedPaths[1].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[2] = expectedPaths[0].Clone(); + expectedPaths[2].PushMember(nameof(SimpleClass.Member2)); + expectedPaths[3] = expectedPaths[2].Clone(); + expectedPaths[3].PushTarget(); + expectedPaths[4] = expectedPaths[3].Clone(); + expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[5] = expectedPaths[3].Clone(); + expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + } + + [Fact] + public void TestMultipleReferences() + { + var nodeContainer = new NodeContainer(); + var commonObj = new SimpleClass(); + var instance = new SimpleClass2 { Member1 = 3, Member2 = new SimpleClass { Member1 = 4, Member2 = commonObj }, Member3 = new SimpleClass { Member1 = 5, Member2 = commonObj } }; + var rootNode = nodeContainer.GetOrCreateNode(instance); + var visitor = new TestVisitor(); + visitor.Visit(rootNode); + IGraphNode[] expectedNodes = + [ + rootNode, + rootNode[nameof(SimpleClass2.Member1)], + rootNode[nameof(SimpleClass2.Member2)], + rootNode[nameof(SimpleClass2.Member2)].Target, + rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member1)], + rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member2)], + rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member2)].Target, + rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], + rootNode[nameof(SimpleClass2.Member2)].Target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], + rootNode[nameof(SimpleClass2.Member3)], + rootNode[nameof(SimpleClass2.Member3)].Target, + rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member1)], + rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member2)], + rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member2)].Target, + rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member1)], + rootNode[nameof(SimpleClass2.Member3)].Target[nameof(SimpleClass.Member2)].Target[nameof(SimpleClass.Member2)], + ]; + var expectedPaths = new GraphNodePath[16]; + expectedPaths[0] = new GraphNodePath(rootNode); + expectedPaths[1] = expectedPaths[0].Clone(); + expectedPaths[1].PushMember(nameof(SimpleClass2.Member1)); + expectedPaths[2] = expectedPaths[0].Clone(); + expectedPaths[2].PushMember(nameof(SimpleClass2.Member2)); + expectedPaths[3] = expectedPaths[2].Clone(); + expectedPaths[3].PushTarget(); + expectedPaths[4] = expectedPaths[3].Clone(); + expectedPaths[4].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[5] = expectedPaths[3].Clone(); + expectedPaths[5].PushMember(nameof(SimpleClass.Member2)); + expectedPaths[6] = expectedPaths[5].Clone(); + expectedPaths[6].PushTarget(); + expectedPaths[7] = expectedPaths[6].Clone(); + expectedPaths[7].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[8] = expectedPaths[6].Clone(); + expectedPaths[8].PushMember(nameof(SimpleClass.Member2)); + expectedPaths[9] = expectedPaths[0].Clone(); + expectedPaths[9].PushMember(nameof(SimpleClass2.Member3)); + expectedPaths[10] = expectedPaths[9].Clone(); + expectedPaths[10].PushTarget(); + expectedPaths[11] = expectedPaths[10].Clone(); + expectedPaths[11].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[12] = expectedPaths[10].Clone(); + expectedPaths[12].PushMember(nameof(SimpleClass.Member2)); + expectedPaths[13] = expectedPaths[12].Clone(); + expectedPaths[13].PushTarget(); + expectedPaths[14] = expectedPaths[13].Clone(); + expectedPaths[14].PushMember(nameof(SimpleClass.Member1)); + expectedPaths[15] = expectedPaths[13].Clone(); + expectedPaths[15].PushMember(nameof(SimpleClass.Member2)); + VerifyNodesAndPath(expectedNodes, expectedPaths, visitor); + } + + private static void VerifyNodesAndPath(IReadOnlyList expectedNodes, IReadOnlyList expectedPaths, TestVisitor visitor) + { + Assert.Equal(expectedNodes.Count, visitor.Result.Count); + Assert.Equal(expectedPaths.Count, visitor.Result.Count); + for (var i = 0; i < expectedNodes.Count; i++) { - Assert.Equal(expectedNodes.Count, visitor.Result.Count); - Assert.Equal(expectedPaths.Count, visitor.Result.Count); - for (var i = 0; i < expectedNodes.Count; i++) - { - Assert.Equal(expectedNodes[i], visitor.Result[i].Item1); - Assert.Equal(expectedPaths[i], visitor.Result[i].Item2); - } + Assert.Equal(expectedNodes[i], visitor.Result[i].Item1); + Assert.Equal(expectedPaths[i], visitor.Result[i].Item2); } } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/TestPrimitiveMembers.cs b/sources/presentation/Stride.Core.Quantum.Tests/TestPrimitiveMembers.cs index 17937fec1d..dc53747c1f 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/TestPrimitiveMembers.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/TestPrimitiveMembers.cs @@ -1,237 +1,236 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Linq; -using Xunit; + using Stride.Core.Reflection; +using Xunit; + +namespace Stride.Core.Quantum.Tests; -namespace Stride.Core.Quantum.Tests +public class TestPrimitiveMembers { - public class TestPrimitiveMembers - { - public enum TestEnum - { - Value1, - Value2, - Value3, - } - - public class IntMember - { - public int Member { get; set; } - } - - public class StringMember - { - public string Member { get; set; } - } - - public class GuidMember - { - public Guid Member { get; set; } - } - - public class EnumMember - { - public TestEnum Member { get; set; } - } - - public class PrimitiveClass - { - public int Value { get; set; } - } - - public struct PrimitiveStruct - { - public int Value { get; set; } - } - - public class RegisteredPrimitiveClassMember - { - public PrimitiveClass Member { get; set; } - } - public class RegisteredPrimitiveStructMember - { - public PrimitiveStruct Member { get; set; } - } - - public class BoxedPrimitiveMember - { - public object Member { get; set; } - } - - [Fact] - public void TestIntMember() - { - var obj = new IntMember { Member = 5 }; - var container = new NodeContainer(); - - // Construction - var containerNode = container.GetOrCreateNode(obj); - Helper.TestNonCollectionObjectNode(containerNode, obj, 1); - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(IntMember.Member), false); - - // Update from object - obj.Member = 6; - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(IntMember.Member), false); - - // Update from Quantum - containerNode.Members.First().Update(7); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(IntMember.Member), false); - } - - [Fact] - public void TestStringMember() - { - var obj = new StringMember { Member = "aaa" }; - var container = new NodeContainer(); - - // Construction - var containerNode = container.GetOrCreateNode(obj); - Helper.TestNonCollectionObjectNode(containerNode, obj, 1); - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(StringMember.Member), false); - - // Update from object - obj.Member = "bbb"; - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(StringMember.Member), false); - - // Update from Quantum - containerNode.Members.First().Update("ccc"); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(StringMember.Member), false); - } - - [Fact] - public void TestGuidMember() - { - var obj = new GuidMember { Member = Guid.NewGuid() }; - var container = new NodeContainer(); - - // Construction - var containerNode = container.GetOrCreateNode(obj); - Helper.TestNonCollectionObjectNode(containerNode, obj, 1); - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(GuidMember.Member), false); - - // Update from object - obj.Member = Guid.NewGuid(); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); - - // Update from Quantum - containerNode.Members.First().Update(Guid.NewGuid()); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); - } - - [Fact] - public void TestEnumMember() - { - var obj = new EnumMember { Member = TestEnum.Value1 }; - var container = new NodeContainer(); - - // Construction - var containerNode = container.GetOrCreateNode(obj); - Helper.TestNonCollectionObjectNode(containerNode, obj, 1); - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); - - // Update from object - obj.Member = TestEnum.Value2; - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); - - // Update from Quantum - containerNode.Members.First().Update(TestEnum.Value3); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); - } - - [Fact] - public void TestRegisteredPrimitiveClassMember() - { - var obj = new RegisteredPrimitiveClassMember { Member = new PrimitiveClass { Value = 1 } }; - var container = new NodeContainer(); - container.NodeBuilder.RegisterPrimitiveType(typeof(PrimitiveClass)); - - // Construction - var containerNode = container.GetOrCreateNode(obj); - Helper.TestNonCollectionObjectNode(containerNode, obj, 1); - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveClassMember.Member), false); - - // Update from object - obj.Member = new PrimitiveClass { Value = 2 }; - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveClassMember.Member), false); - - // Update from Quantum - containerNode.Members.First().Update(new PrimitiveClass { Value = 3 }); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveClassMember.Member), false); - } - - [Fact] - public void TestRegisteredPrimitiveStructMember() - { - var obj = new RegisteredPrimitiveStructMember { Member = new PrimitiveStruct { Value = 1 } }; - var container = new NodeContainer(); - container.NodeBuilder.RegisterPrimitiveType(typeof(PrimitiveStruct)); - - // Construction - var containerNode = container.GetOrCreateNode(obj); - Helper.TestNonCollectionObjectNode(containerNode, obj, 1); - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveStructMember.Member), false); - - // Update from object - obj.Member = new PrimitiveStruct { Value = 2 }; - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveStructMember.Member), false); - - // Update from Quantum - containerNode.Members.Last().Update(new PrimitiveStruct { Value = 3 }); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveStructMember.Member), false); - } - - [Fact] - public void TestBoxedPrimitiveMember() - { - var obj = new BoxedPrimitiveMember { Member = 1.0f }; - var container = new NodeContainer(); - - // Construction - var containerNode = container.GetOrCreateNode(obj); - Helper.TestNonCollectionObjectNode(containerNode, obj, 1); - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(BoxedPrimitiveMember.Member), true); - Assert.NotNull(memberNode.Target); - Assert.Equal(0, memberNode.Target.Members.Count); - Assert.Null(memberNode.Target.Indices); - Assert.Equal(TypeDescriptorFactory.Default.Find(typeof(float)), memberNode.Target.Descriptor); - Assert.False(memberNode.Target.IsReference); - Assert.Null(memberNode.Target.ItemReferences); - Assert.Equal(typeof(float), memberNode.Target.Type); - Assert.Equal(1.0f, memberNode.Target.Retrieve()); - - // Update from object (note: value WILL mismatch here due to the boxing node, so we don't test the value of the target node) - obj.Member = 2.0f; - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(BoxedPrimitiveMember.Member), true); - Assert.True(memberNode.IsReference); - Assert.NotNull(memberNode.Target); - Assert.Equal(0, memberNode.Target.Members.Count); - Assert.Null(memberNode.Target.Indices); - Assert.Equal(TypeDescriptorFactory.Default.Find(typeof(float)), memberNode.Target.Descriptor); - Assert.False(memberNode.Target.IsReference); - Assert.Null(memberNode.Target.ItemReferences); - Assert.Equal(typeof(float), memberNode.Target.Type); - - // Update from Quantum - containerNode.Members.Last().Update(3.0f); - Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(BoxedPrimitiveMember.Member), true); - Assert.True(memberNode.IsReference); - Assert.NotNull(memberNode.Target); - Assert.Equal(0, memberNode.Target.Members.Count); - Assert.Null(memberNode.Target.Indices); - Assert.Equal(TypeDescriptorFactory.Default.Find(typeof(float)), memberNode.Target.Descriptor); - Assert.False(memberNode.Target.IsReference); - Assert.Null(memberNode.Target.ItemReferences); - Assert.Equal(typeof(float), memberNode.Target.Type); - Assert.Equal(3.0f, memberNode.Target.Retrieve()); - } + public enum TestEnum + { + Value1, + Value2, + Value3, + } + + public class IntMember + { + public int Member { get; set; } + } + + public class StringMember + { + public string Member { get; set; } + } + + public class GuidMember + { + public Guid Member { get; set; } + } + + public class EnumMember + { + public TestEnum Member { get; set; } + } + + public class PrimitiveClass + { + public int Value { get; set; } + } + + public struct PrimitiveStruct + { + public int Value { get; set; } + } + + public class RegisteredPrimitiveClassMember + { + public PrimitiveClass Member { get; set; } + } + + public class RegisteredPrimitiveStructMember + { + public PrimitiveStruct Member { get; set; } + } + + public class BoxedPrimitiveMember + { + public object Member { get; set; } + } + + [Fact] + public void TestIntMember() + { + var obj = new IntMember { Member = 5 }; + var container = new NodeContainer(); + + // Construction + var containerNode = container.GetOrCreateNode(obj); + Helper.TestNonCollectionObjectNode(containerNode, obj, 1); + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(IntMember.Member), false); + + // Update from object + obj.Member = 6; + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(IntMember.Member), false); + + // Update from Quantum + containerNode.Members.First().Update(7); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(IntMember.Member), false); + } + + [Fact] + public void TestStringMember() + { + var obj = new StringMember { Member = "aaa" }; + var container = new NodeContainer(); + + // Construction + var containerNode = container.GetOrCreateNode(obj); + Helper.TestNonCollectionObjectNode(containerNode, obj, 1); + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(StringMember.Member), false); + + // Update from object + obj.Member = "bbb"; + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(StringMember.Member), false); + + // Update from Quantum + containerNode.Members.First().Update("ccc"); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(StringMember.Member), false); + } + + [Fact] + public void TestGuidMember() + { + var obj = new GuidMember { Member = Guid.NewGuid() }; + var container = new NodeContainer(); + + // Construction + var containerNode = container.GetOrCreateNode(obj); + Helper.TestNonCollectionObjectNode(containerNode, obj, 1); + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(GuidMember.Member), false); + + // Update from object + obj.Member = Guid.NewGuid(); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); + + // Update from Quantum + containerNode.Members.First().Update(Guid.NewGuid()); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); + } + + [Fact] + public void TestEnumMember() + { + var obj = new EnumMember { Member = TestEnum.Value1 }; + var container = new NodeContainer(); + + // Construction + var containerNode = container.GetOrCreateNode(obj); + Helper.TestNonCollectionObjectNode(containerNode, obj, 1); + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); + + // Update from object + obj.Member = TestEnum.Value2; + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); + + // Update from Quantum + containerNode.Members.First().Update(TestEnum.Value3); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(EnumMember.Member), false); + } + + [Fact] + public void TestRegisteredPrimitiveClassMember() + { + var obj = new RegisteredPrimitiveClassMember { Member = new PrimitiveClass { Value = 1 } }; + var container = new NodeContainer(); + container.NodeBuilder.RegisterPrimitiveType(typeof(PrimitiveClass)); + + // Construction + var containerNode = container.GetOrCreateNode(obj); + Helper.TestNonCollectionObjectNode(containerNode, obj, 1); + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveClassMember.Member), false); + + // Update from object + obj.Member = new PrimitiveClass { Value = 2 }; + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveClassMember.Member), false); + + // Update from Quantum + containerNode.Members.First().Update(new PrimitiveClass { Value = 3 }); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveClassMember.Member), false); + } + + [Fact] + public void TestRegisteredPrimitiveStructMember() + { + var obj = new RegisteredPrimitiveStructMember { Member = new PrimitiveStruct { Value = 1 } }; + var container = new NodeContainer(); + container.NodeBuilder.RegisterPrimitiveType(typeof(PrimitiveStruct)); + + // Construction + var containerNode = container.GetOrCreateNode(obj); + Helper.TestNonCollectionObjectNode(containerNode, obj, 1); + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveStructMember.Member), false); + + // Update from object + obj.Member = new PrimitiveStruct { Value = 2 }; + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveStructMember.Member), false); + + // Update from Quantum + containerNode.Members.Last().Update(new PrimitiveStruct { Value = 3 }); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(RegisteredPrimitiveStructMember.Member), false); + } + + [Fact] + public void TestBoxedPrimitiveMember() + { + var obj = new BoxedPrimitiveMember { Member = 1.0f }; + var container = new NodeContainer(); + + // Construction + var containerNode = container.GetOrCreateNode(obj); + Helper.TestNonCollectionObjectNode(containerNode, obj, 1); + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(BoxedPrimitiveMember.Member), true); + Assert.NotNull(memberNode.Target); + Assert.Empty(memberNode.Target.Members); + Assert.Null(memberNode.Target.Indices); + Assert.Equal(TypeDescriptorFactory.Default.Find(typeof(float)), memberNode.Target.Descriptor); + Assert.False(memberNode.Target.IsReference); + Assert.Null(memberNode.Target.ItemReferences); + Assert.Equal(typeof(float), memberNode.Target.Type); + Assert.Equal(1.0f, memberNode.Target.Retrieve()); + + // Update from object (note: value WILL mismatch here due to the boxing node, so we don't test the value of the target node) + obj.Member = 2.0f; + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(BoxedPrimitiveMember.Member), true); + Assert.True(memberNode.IsReference); + Assert.NotNull(memberNode.Target); + Assert.Empty(memberNode.Target.Members); + Assert.Null(memberNode.Target.Indices); + Assert.Equal(TypeDescriptorFactory.Default.Find(typeof(float)), memberNode.Target.Descriptor); + Assert.False(memberNode.Target.IsReference); + Assert.Null(memberNode.Target.ItemReferences); + Assert.Equal(typeof(float), memberNode.Target.Type); + + // Update from Quantum + containerNode.Members.Last().Update(3.0f); + Helper.TestMemberNode(containerNode, memberNode, obj, obj.Member, nameof(BoxedPrimitiveMember.Member), true); + Assert.True(memberNode.IsReference); + Assert.NotNull(memberNode.Target); + Assert.Empty(memberNode.Target.Members); + Assert.Null(memberNode.Target.Indices); + Assert.Equal(TypeDescriptorFactory.Default.Find(typeof(float)), memberNode.Target.Descriptor); + Assert.False(memberNode.Target.IsReference); + Assert.Null(memberNode.Target.ItemReferences); + Assert.Equal(typeof(float), memberNode.Target.Type); + Assert.Equal(3.0f, memberNode.Target.Retrieve()); } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/TestReferences.cs b/sources/presentation/Stride.Core.Quantum.Tests/TestReferences.cs index 5003a414a7..14c71d1da5 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/TestReferences.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/TestReferences.cs @@ -1,381 +1,379 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; + using Xunit; -namespace Stride.Core.Quantum.Tests +namespace Stride.Core.Quantum.Tests; + +public class TestReferences { - public class TestReferences + public class TestObject + { + public string Name; + public override string ToString() => $"{{TestObject: {Name}}}"; + } + + public class ObjectContainer + { + public object Instance { get; set; } + public override string ToString() => "{ObjectContainer}"; + } + + public class ObjectsContainer + { + public TestObject Instance1 { get; set; } + public TestObject Instance2 { get; set; } + public override string ToString() => $"{{ObjectsContainer: {Instance1}, {Instance2}}}"; + } + + public class MultipleObjectContainer + { + public List Instances { get; set; } = []; + public override string ToString() => $"{{MultipleObjectContainer: {string.Join(", ", Instances.Select(x => x.ToString()))}}}"; + } + + /// + /// This test creates an object referencing another and verifies that the reference is properly created in the node graph. + /// + [Fact] + public void TestObjectReference() + { + var nodeContainer = new NodeContainer(); + var instance = new TestObject { Name = "Test" }; + var container = new ObjectContainer { Instance = instance }; + var containerNode = nodeContainer.GetOrCreateNode(container); + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, container, instance, nameof(ObjectContainer.Instance), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, instance, false); + var instanceNode = nodeContainer.GetNode(instance); + Helper.TestNonNullObjectReference(memberNode.TargetReference, instanceNode, instance); + + memberNode = instanceNode.Members.First(); + Helper.TestMemberNode(instanceNode, memberNode, instance, instance.Name, nameof(TestObject.Name), false); + } + + /// + /// This test creates an object with a reference that is null and verifies that the node graph is consistent. + /// + [Fact] + public void TestNullObjectReference() + { + var nodeContainer = new NodeContainer(); + var container = new ObjectContainer { Instance = null }; + var containerNode = nodeContainer.GetOrCreateNode(container); + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, container, null, nameof(ObjectContainer.Instance), true); + Helper.TestNullObjectReference(memberNode.TargetReference); + } + + /// + /// This test creates an object referencing another and verifies that the reference is properly created in the node graph. + /// + [Fact] + public void TestObjectReferenceUpdate() + { + var nodeContainer = new NodeContainer(); + var instance = new TestObject { Name = "Test" }; + var container = new ObjectContainer { Instance = instance }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode.Members.First(); + var instanceNode = memberNode.TargetReference.TargetNode; + + // Update to a new instance + var newInstance = new TestObject { Name = "Test2" }; + memberNode.Update(newInstance); + Helper.TestMemberNode(containerNode, memberNode, container, newInstance, nameof(ObjectContainer.Instance), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, newInstance, false); + + var newInstanceNode = nodeContainer.GetNode(newInstance); + Helper.TestNonNullObjectReference(memberNode.TargetReference, newInstanceNode, newInstance); + Assert.NotEqual(instanceNode.Guid, newInstanceNode.Guid); + + // Update to null + memberNode.Update(null); + Helper.TestMemberNode(containerNode, memberNode, container, null, nameof(ObjectContainer.Instance), true); + Helper.TestNullObjectReference(memberNode.TargetReference); + + // Update back to the initial instance + memberNode.Update(instance); + Helper.TestMemberNode(containerNode, memberNode, container, instance, nameof(ObjectContainer.Instance), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, instanceNode, instance); + } + + /// + /// This test creates an object containing a collection of references to other objects and verifies that + /// the references are properly created in the node graph. + /// + [Fact] + public void TestEnumerableReference() + { + var nodeContainer = new NodeContainer(); + var instance1 = new TestObject { Name = "Test1" }; + var instance2 = new TestObject { Name = "Test2" }; + var container = new MultipleObjectContainer { Instances = { instance1, instance2 } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, container, container.Instances, nameof(MultipleObjectContainer.Instances), true); + Helper.TestReferenceEnumerable(memberNode.Target.ItemReferences, container.Instances); + + Assert.Equal(container.Instances, memberNode.Retrieve()); + Assert.Equal(instance1, memberNode.Retrieve(new NodeIndex(0))); + Assert.Equal(instance2, memberNode.Retrieve(new NodeIndex(1))); + + var reference1 = memberNode.Target.ItemReferences.First(); + Helper.TestMemberNode(reference1.TargetNode, reference1.TargetNode.Members.First(), instance1, instance1.Name, nameof(TestObject.Name), false); + + var reference2 = memberNode.Target.ItemReferences.Last(); + Helper.TestMemberNode(reference2.TargetNode, reference2.TargetNode.Members.First(), instance2, instance2.Name, nameof(TestObject.Name), false); + } + + /// + /// This test creates an object containing a collection of references to null and verifies that + /// the references in the node graph are consistent. + /// + [Fact] + public void TestNullEnumerableReference() + { + var nodeContainer = new NodeContainer(); + var container = new MultipleObjectContainer { Instances = { null, null } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, container, container.Instances, nameof(MultipleObjectContainer.Instances), true); + Helper.TestReferenceEnumerable(memberNode.Target.ItemReferences, container.Instances); + + Assert.Equal(container.Instances, memberNode.Retrieve()); + Assert.Null(memberNode.Retrieve(new NodeIndex(0))); + Assert.Null(memberNode.Retrieve(new NodeIndex(1))); + } + + /// + /// This test creates an object containing a collection of references to other objects and verifies that + /// the references are properly created in the node graph. + /// + [Fact] + public void TestEnumerableReferenceUpdate() + { + var nodeContainer = new NodeContainer(); + var instance1 = new TestObject { Name = "Test1" }; + var instance2 = new TestObject { Name = "Test2" }; + var container = new MultipleObjectContainer { Instances = { instance1, instance2 } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode.Members.First(); + var reference = memberNode.Target.ItemReferences; + var reference1 = reference.First(); + var reference2 = reference.Last(); + + // Update item 0 to a new instance and item 1 to null + var newInstance = new TestObject { Name = "Test3" }; + memberNode.Target.Update(newInstance, new NodeIndex(0)); + memberNode.Target.Update(null, new NodeIndex(1)); + Assert.Equal(container.Instances, memberNode.Retrieve()); + Assert.Equal(newInstance, memberNode.Retrieve(new NodeIndex(0))); + Assert.Null(memberNode.Retrieve(new NodeIndex(1))); + Helper.TestReferenceEnumerable(memberNode.Target.ItemReferences, container.Instances); + + var newReference = memberNode.Target.ItemReferences; + Assert.Equal(reference, newReference); + Assert.Equal(2, newReference.Count); + var newReference1 = newReference.First(); + var newReference2 = newReference.Last(); + + Assert.NotEqual(reference1, newReference1); + Assert.NotEqual(reference1.TargetGuid, newReference1.TargetGuid); + Assert.NotEqual(reference1.TargetNode, newReference1.TargetNode); + Assert.NotEqual(reference1.ObjectValue, newReference1.ObjectValue); + Assert.NotEqual(reference2, newReference2); + Assert.NotEqual(reference2.TargetGuid, newReference2.TargetGuid); + Assert.NotEqual(reference2.TargetNode, newReference2.TargetNode); + Assert.NotEqual(reference2.ObjectValue, newReference2.ObjectValue); + } + + /// + /// This test creates an object referencing another twice and verifies that the reference is properly created in the node graph. + /// + [Fact] + public void TestObjectReferenceSameInstance() + { + var nodeContainer = new NodeContainer(); + var instance = new TestObject { Name = "Test" }; + var container = new ObjectsContainer { Instance1 = instance, Instance2 = instance }; + var containerNode = nodeContainer.GetOrCreateNode(container); + Helper.TestNonCollectionObjectNode(containerNode, container, 2); + + var member1Node = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, member1Node, container, instance, nameof(ObjectsContainer.Instance1), true); + Helper.TestNonNullObjectReference(member1Node.TargetReference, instance, false); + + var member2Node = containerNode.Members.Last(); + Helper.TestMemberNode(containerNode, member2Node, container, instance, nameof(ObjectsContainer.Instance2), true); + Helper.TestNonNullObjectReference(member2Node.TargetReference, instance, false); + + var reference1 = member1Node.TargetReference; + var reference2 = member2Node.TargetReference; + Assert.Equal(reference1.TargetGuid, reference2.TargetGuid); + Assert.Equal(reference1.TargetNode, reference2.TargetNode); + Assert.Equal(reference1.ObjectValue, reference2.ObjectValue); + } + + /// + /// This test creates an object containing a collection of references to the same object and verifies that + /// the references are properly created in the node graph. + /// + [Fact] + public void TestEnumerableReferenceSameInstance() + { + var nodeContainer = new NodeContainer(); + var instance = new TestObject { Name = "Test" }; + var container = new MultipleObjectContainer { Instances = { instance, instance } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + + var memberNode = containerNode.Members.First(); + Helper.TestMemberNode(containerNode, memberNode, container, container.Instances, nameof(MultipleObjectContainer.Instances), true); + Helper.TestReferenceEnumerable(memberNode.Target.ItemReferences, container.Instances); + + var reference = memberNode.Target.ItemReferences; + Assert.Equal(2, reference.Count); + var reference1 = reference.First(); + var reference2 = reference.Last(); + Assert.Equal(reference1.ObjectValue, reference2.ObjectValue); + Assert.Equal(reference1.TargetGuid, reference2.TargetGuid); + Assert.Equal(reference1.TargetNode, reference2.TargetNode); + } + + /// + /// This test creates two objects, one referencing the other. It verifies that when constructing the node of the referenced object first, + /// the referencer object will reuse the same node as target of the reference + /// + [Fact] + public void TestUseExistingNodeAsReference() + { + var nodeContainer = new NodeContainer(); + var instance = new TestObject { Name = "Test" }; + var container = new ObjectContainer { Instance = instance }; + var instanceNode = nodeContainer.GetOrCreateNode(instance); + var containerNode = nodeContainer.GetOrCreateNode(container); + Assert.Single(containerNode.Members); + var memberNode = containerNode.Members.First(); + Assert.Equal(instance, memberNode.Retrieve()); + Assert.True(memberNode.IsReference); + Assert.Equal(instanceNode, memberNode.TargetReference.TargetNode); + } + + /// + /// This test creates two objects, one referencing the other. It verifies that when constructing the node of the referencer object first, + /// the referenced object will reuse the same node as target of the reference + /// + [Fact] + public void TestUseExistingReferenceAsNode() + { + var nodeContainer = new NodeContainer(); + var instance = new TestObject { Name = "Test" }; + var container = new ObjectContainer { Instance = instance }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var instanceNode = nodeContainer.GetOrCreateNode(instance); + Assert.Single(containerNode.Members); + var memberNode = containerNode.Members.First(); + Assert.Equal(instance, memberNode.Retrieve()); + Assert.True(memberNode.IsReference); + Assert.Equal(instanceNode, memberNode.TargetReference.TargetNode); + } + + /// + /// This test creates a container object that reference multiples other object. It verifies that the same nodes are reused between the instances + /// of objects and the references + /// + [Fact] + public void TestUseExistingNodesAsReference() + { + var nodeContainer = new NodeContainer(); + var instance1 = new TestObject { Name = "Test1" }; + var instance2 = new TestObject { Name = "Test2" }; + var container = new MultipleObjectContainer { Instances = { instance1, instance2 } }; + var instance1Node = nodeContainer.GetOrCreateNode(instance1); + var containerNode = nodeContainer.GetOrCreateNode(container); + var instance2Node = nodeContainer.GetOrCreateNode(instance2); + Assert.Single(containerNode.Members); + var memberNode = containerNode.Members.First(); + Assert.True(memberNode.IsReference); + var reference = memberNode.Target.ItemReferences; + Assert.Equal(2, reference.Indices.Count); + var reference1 = reference.First(); + var reference2 = reference.Last(); + Assert.Equal(instance1Node, reference1.TargetNode); + Assert.Equal(instance2Node, reference2.TargetNode); + } + + /// + /// This test creates two objects referencing each other. It verifies that the same nodes are reused between instances and references. + /// + [Fact] + public void TestCircularReferences() + { + var nodeContainer = new NodeContainer(); + var instance1 = new ObjectContainer(); + var instance2 = new ObjectContainer(); + instance1.Instance = instance2; + instance2.Instance = instance1; + var instance1Node = nodeContainer.GetOrCreateNode(instance1); + var instance2Node = nodeContainer.GetOrCreateNode(instance2); + Assert.Single(instance1Node.Members); + Assert.Single(instance2Node.Members); + Assert.Equal(instance1Node.Members.First().TargetReference.TargetNode, instance2Node); + Assert.Equal(instance2Node.Members.First().TargetReference.TargetNode, instance1Node); + } + + /// + /// This test creates two objects and updates them to referencing each other. It verifies that the same nodes are reused between instances and references. + /// + [Fact] + public void TestCircularReferencesUpdate() + { + var nodeContainer = new NodeContainer(); + var instance1 = new ObjectContainer(); + var instance2 = new ObjectContainer(); + var instance1Node = nodeContainer.GetOrCreateNode(instance1); + var instance2Node = nodeContainer.GetOrCreateNode(instance2); + instance1Node.Members.First().Update(instance2); + instance2Node.Members.First().Update(instance1); + Assert.Equal(instance1Node.Members.First().TargetReference.TargetNode, instance2Node); + Assert.Equal(instance2Node.Members.First().TargetReference.TargetNode, instance1Node); + } + + /// + /// This test creates an object and make it reference itself. It verifies that the same nodes are reused. + /// + [Fact] + public void TestSelfReference() + { + var nodeContainer = new NodeContainer(); + var instance = new ObjectContainer(); + instance.Instance = instance; + var instanceNode = nodeContainer.GetOrCreateNode(instance); + Assert.Single(instanceNode.Members); + Assert.Equal(instanceNode.Members.First().TargetReference.TargetNode, instanceNode); + } + + /// + /// This test creates an object and update it to make it reference itself. It verifies that the same nodes are reused. + /// + [Fact] + public void TestSelfReferenceUpdate() { - public class TestObject - { - public string Name; - public override string ToString() => $"{{TestObject: {Name}}}"; - } - - public class ObjectContainer - { - public object Instance { get; set; } - public override string ToString() => "{ObjectContainer}"; - } - - public class ObjectsContainer - { - public TestObject Instance1 { get; set; } - public TestObject Instance2 { get; set; } - public override string ToString() => $"{{ObjectsContainer: {Instance1}, {Instance2}}}"; - } - - public class MultipleObjectContainer - { - public List Instances { get; set; } = new List(); - public override string ToString() => $"{{MultipleObjectContainer: {string.Join(", ", Instances.Select(x => x.ToString()))}}}"; - } - - /// - /// This test creates an object referencing another and verifies that the reference is properly created in the node graph. - /// - [Fact] - public void TestObjectReference() - { - var nodeContainer = new NodeContainer(); - var instance = new TestObject { Name = "Test" }; - var container = new ObjectContainer { Instance = instance }; - var containerNode = nodeContainer.GetOrCreateNode(container); - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, container, instance, nameof(ObjectContainer.Instance), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, instance, false); - var instanceNode = nodeContainer.GetNode(instance); - Helper.TestNonNullObjectReference(memberNode.TargetReference, instanceNode, instance); - - memberNode = instanceNode.Members.First(); - Helper.TestMemberNode(instanceNode, memberNode, instance, instance.Name, nameof(TestObject.Name), false); - } - - /// - /// This test creates an object with a reference that is null and verifies that the node graph is consistent. - /// - [Fact] - public void TestNullObjectReference() - { - var nodeContainer = new NodeContainer(); - var container = new ObjectContainer { Instance = null }; - var containerNode = nodeContainer.GetOrCreateNode(container); - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, container, null, nameof(ObjectContainer.Instance), true); - Helper.TestNullObjectReference(memberNode.TargetReference); - } - - /// - /// This test creates an object referencing another and verifies that the reference is properly created in the node graph. - /// - [Fact] - public void TestObjectReferenceUpdate() - { - var nodeContainer = new NodeContainer(); - var instance = new TestObject { Name = "Test" }; - var container = new ObjectContainer { Instance = instance }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode.Members.First(); - var instanceNode = memberNode.TargetReference.TargetNode; - - // Update to a new instance - var newInstance = new TestObject { Name = "Test2" }; - memberNode.Update(newInstance); - Helper.TestMemberNode(containerNode, memberNode, container, newInstance, nameof(ObjectContainer.Instance), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, newInstance, false); - - var newInstanceNode = nodeContainer.GetNode(newInstance); - Helper.TestNonNullObjectReference(memberNode.TargetReference, newInstanceNode, newInstance); - Assert.NotEqual(instanceNode.Guid, newInstanceNode.Guid); - - // Update to null - memberNode.Update(null); - Helper.TestMemberNode(containerNode, memberNode, container, null, nameof(ObjectContainer.Instance), true); - Helper.TestNullObjectReference(memberNode.TargetReference); - - // Update back to the initial instance - memberNode.Update(instance); - Helper.TestMemberNode(containerNode, memberNode, container, instance, nameof(ObjectContainer.Instance), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, instanceNode, instance); - } - - /// - /// This test creates an object containing a collection of references to other objects and verifies that - /// the references are properly created in the node graph. - /// - [Fact] - public void TestEnumerableReference() - { - var nodeContainer = new NodeContainer(); - var instance1 = new TestObject { Name = "Test1" }; - var instance2 = new TestObject { Name = "Test2" }; - var container = new MultipleObjectContainer { Instances = { instance1, instance2 } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, container, container.Instances, nameof(MultipleObjectContainer.Instances), true); - Helper.TestReferenceEnumerable(memberNode.Target.ItemReferences, container.Instances); - - Assert.Equal(container.Instances, memberNode.Retrieve()); - Assert.Equal(instance1, memberNode.Retrieve(new NodeIndex(0))); - Assert.Equal(instance2, memberNode.Retrieve(new NodeIndex(1))); - - var reference1 = memberNode.Target.ItemReferences.First(); - Helper.TestMemberNode(reference1.TargetNode, reference1.TargetNode.Members.First(), instance1, instance1.Name, nameof(TestObject.Name), false); - - var reference2 = memberNode.Target.ItemReferences.Last(); - Helper.TestMemberNode(reference2.TargetNode, reference2.TargetNode.Members.First(), instance2, instance2.Name, nameof(TestObject.Name), false); - } - - /// - /// This test creates an object containing a collection of references to null and verifies that - /// the references in the node graph are consistent. - /// - [Fact] - public void TestNullEnumerableReference() - { - var nodeContainer = new NodeContainer(); - var container = new MultipleObjectContainer { Instances = { null, null } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, container, container.Instances, nameof(MultipleObjectContainer.Instances), true); - Helper.TestReferenceEnumerable(memberNode.Target.ItemReferences, container.Instances); - - Assert.Equal(container.Instances, memberNode.Retrieve()); - Assert.Null(memberNode.Retrieve(new NodeIndex(0))); - Assert.Null(memberNode.Retrieve(new NodeIndex(1))); - } - - /// - /// This test creates an object containing a collection of references to other objects and verifies that - /// the references are properly created in the node graph. - /// - [Fact] - public void TestEnumerableReferenceUpdate() - { - var nodeContainer = new NodeContainer(); - var instance1 = new TestObject { Name = "Test1" }; - var instance2 = new TestObject { Name = "Test2" }; - var container = new MultipleObjectContainer { Instances = { instance1, instance2 } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode.Members.First(); - var reference = memberNode.Target.ItemReferences; - var reference1 = reference.First(); - var reference2 = reference.Last(); - - // Update item 0 to a new instance and item 1 to null - var newInstance = new TestObject { Name = "Test3" }; - memberNode.Target.Update(newInstance, new NodeIndex(0)); - memberNode.Target.Update(null, new NodeIndex(1)); - Assert.Equal(container.Instances, memberNode.Retrieve()); - Assert.Equal(newInstance, memberNode.Retrieve(new NodeIndex(0))); - Assert.Null(memberNode.Retrieve(new NodeIndex(1))); - Helper.TestReferenceEnumerable(memberNode.Target.ItemReferences, container.Instances); - - var newReference = memberNode.Target.ItemReferences; - Assert.Equal(reference, newReference); - Assert.Equal(2, newReference.Count); - var newReference1 = newReference.First(); - var newReference2 = newReference.Last(); - - Assert.NotEqual(reference1, newReference1); - Assert.NotEqual(reference1.TargetGuid, newReference1.TargetGuid); - Assert.NotEqual(reference1.TargetNode, newReference1.TargetNode); - Assert.NotEqual(reference1.ObjectValue, newReference1.ObjectValue); - Assert.NotEqual(reference2, newReference2); - Assert.NotEqual(reference2.TargetGuid, newReference2.TargetGuid); - Assert.NotEqual(reference2.TargetNode, newReference2.TargetNode); - Assert.NotEqual(reference2.ObjectValue, newReference2.ObjectValue); - } - - /// - /// This test creates an object referencing another twice and verifies that the reference is properly created in the node graph. - /// - [Fact] - public void TestObjectReferenceSameInstance() - { - var nodeContainer = new NodeContainer(); - var instance = new TestObject { Name = "Test" }; - var container = new ObjectsContainer { Instance1 = instance, Instance2 = instance }; - var containerNode = nodeContainer.GetOrCreateNode(container); - Helper.TestNonCollectionObjectNode(containerNode, container, 2); - - var member1Node = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, member1Node, container, instance, nameof(ObjectsContainer.Instance1), true); - Helper.TestNonNullObjectReference(member1Node.TargetReference, instance, false); - - var member2Node = containerNode.Members.Last(); - Helper.TestMemberNode(containerNode, member2Node, container, instance, nameof(ObjectsContainer.Instance2), true); - Helper.TestNonNullObjectReference(member2Node.TargetReference, instance, false); - - var reference1 = member1Node.TargetReference; - var reference2 = member2Node.TargetReference; - Assert.Equal(reference1.TargetGuid, reference2.TargetGuid); - Assert.Equal(reference1.TargetNode, reference2.TargetNode); - Assert.Equal(reference1.ObjectValue, reference2.ObjectValue); - } - - /// - /// This test creates an object containing a collection of references to the same object and verifies that - /// the references are properly created in the node graph. - /// - [Fact] - public void TestEnumerableReferenceSameInstance() - { - var nodeContainer = new NodeContainer(); - var instance = new TestObject { Name = "Test" }; - var container = new MultipleObjectContainer { Instances = { instance, instance } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - - var memberNode = containerNode.Members.First(); - Helper.TestMemberNode(containerNode, memberNode, container, container.Instances, nameof(MultipleObjectContainer.Instances), true); - Helper.TestReferenceEnumerable(memberNode.Target.ItemReferences, container.Instances); - - var reference = memberNode.Target.ItemReferences; - Assert.Equal(2, reference.Count); - var reference1 = reference.First(); - var reference2 = reference.Last(); - Assert.Equal(reference1.ObjectValue, reference2.ObjectValue); - Assert.Equal(reference1.TargetGuid, reference2.TargetGuid); - Assert.Equal(reference1.TargetNode, reference2.TargetNode); - } - - /// - /// This test creates two objects, one referencing the other. It verifies that when constructing the node of the referenced object first, - /// the referencer object will reuse the same node as target of the reference - /// - [Fact] - public void TestUseExistingNodeAsReference() - { - var nodeContainer = new NodeContainer(); - var instance = new TestObject { Name = "Test" }; - var container = new ObjectContainer { Instance = instance }; - var instanceNode = nodeContainer.GetOrCreateNode(instance); - var containerNode = nodeContainer.GetOrCreateNode(container); - Assert.Equal(1, containerNode.Members.Count); - var memberNode = containerNode.Members.First(); - Assert.Equal(instance, memberNode.Retrieve()); - Assert.True(memberNode.IsReference); - Assert.Equal(instanceNode, memberNode.TargetReference.TargetNode); - } - - /// - /// This test creates two objects, one referencing the other. It verifies that when constructing the node of the referencer object first, - /// the referenced object will reuse the same node as target of the reference - /// - [Fact] - public void TestUseExistingReferenceAsNode() - { - var nodeContainer = new NodeContainer(); - var instance = new TestObject { Name = "Test" }; - var container = new ObjectContainer { Instance = instance }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var instanceNode = nodeContainer.GetOrCreateNode(instance); - Assert.Equal(1, containerNode.Members.Count); - var memberNode = containerNode.Members.First(); - Assert.Equal(instance, memberNode.Retrieve()); - Assert.True(memberNode.IsReference); - Assert.Equal(instanceNode, memberNode.TargetReference.TargetNode); - } - - /// - /// This test creates a container object that reference multiples other object. It verifies that the same nodes are reused between the instances - /// of objects and the references - /// - [Fact] - public void TestUseExistingNodesAsReference() - { - var nodeContainer = new NodeContainer(); - var instance1 = new TestObject { Name = "Test1" }; - var instance2 = new TestObject { Name = "Test2" }; - var container = new MultipleObjectContainer { Instances = { instance1, instance2 } }; - var instance1Node = nodeContainer.GetOrCreateNode(instance1); - var containerNode = nodeContainer.GetOrCreateNode(container); - var instance2Node = nodeContainer.GetOrCreateNode(instance2); - Assert.Equal(1, containerNode.Members.Count); - var memberNode = containerNode.Members.First(); - Assert.True(memberNode.IsReference); - var reference = memberNode.Target.ItemReferences; - Assert.Equal(2, reference.Indices.Count); - var reference1 = reference.First(); - var reference2 = reference.Last(); - Assert.Equal(instance1Node, reference1.TargetNode); - Assert.Equal(instance2Node, reference2.TargetNode); - } - - /// - /// This test creates two objects referencing each other. It verifies that the same nodes are reused between instances and references. - /// - [Fact] - public void TestCircularReferences() - { - var nodeContainer = new NodeContainer(); - var instance1 = new ObjectContainer(); - var instance2 = new ObjectContainer(); - instance1.Instance = instance2; - instance2.Instance = instance1; - var instance1Node = nodeContainer.GetOrCreateNode(instance1); - var instance2Node = nodeContainer.GetOrCreateNode(instance2); - Assert.Equal(1, instance1Node.Members.Count); - Assert.Equal(1, instance2Node.Members.Count); - Assert.Equal(instance1Node.Members.First().TargetReference.TargetNode, instance2Node); - Assert.Equal(instance2Node.Members.First().TargetReference.TargetNode, instance1Node); - } - - /// - /// This test creates two objects and updates them to referencing each other. It verifies that the same nodes are reused between instances and references. - /// - [Fact] - public void TestCircularReferencesUpdate() - { - var nodeContainer = new NodeContainer(); - var instance1 = new ObjectContainer(); - var instance2 = new ObjectContainer(); - var instance1Node = nodeContainer.GetOrCreateNode(instance1); - var instance2Node = nodeContainer.GetOrCreateNode(instance2); - instance1Node.Members.First().Update(instance2); - instance2Node.Members.First().Update(instance1); - Assert.Equal(instance1Node.Members.First().TargetReference.TargetNode, instance2Node); - Assert.Equal(instance2Node.Members.First().TargetReference.TargetNode, instance1Node); - } - - /// - /// This test creates an object and make it reference itself. It verifies that the same nodes are reused. - /// - [Fact] - public void TestSelfReference() - { - var nodeContainer = new NodeContainer(); - var instance = new ObjectContainer(); - instance.Instance = instance; - var instanceNode = nodeContainer.GetOrCreateNode(instance); - Assert.Equal(1, instanceNode.Members.Count); - Assert.Equal(instanceNode.Members.First().TargetReference.TargetNode, instanceNode); - } - - /// - /// This test creates an object and update it to make it reference itself. It verifies that the same nodes are reused. - /// - [Fact] - public void TestSelfReferenceUpdate() - { - var nodeContainer = new NodeContainer(); - var instance = new ObjectContainer(); - var instanceNode = nodeContainer.GetOrCreateNode(instance); - instanceNode.Members.First().Update(instance); - instance.Instance = instance; - Assert.Equal(1, instanceNode.Members.Count); - Assert.Equal(instanceNode.Members.First().TargetReference.TargetNode, instanceNode); - } + var nodeContainer = new NodeContainer(); + var instance = new ObjectContainer(); + var instanceNode = nodeContainer.GetOrCreateNode(instance); + instanceNode.Members.First().Update(instance); + instance.Instance = instance; + Assert.Single(instanceNode.Members); + Assert.Equal(instanceNode.Members.First().TargetReference.TargetNode, instanceNode); } } diff --git a/sources/presentation/Stride.Core.Quantum.Tests/TestStructs.cs b/sources/presentation/Stride.Core.Quantum.Tests/TestStructs.cs index 88f9172206..22ff76e568 100644 --- a/sources/presentation/Stride.Core.Quantum.Tests/TestStructs.cs +++ b/sources/presentation/Stride.Core.Quantum.Tests/TestStructs.cs @@ -1,274 +1,272 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; + using Xunit; -namespace Stride.Core.Quantum.Tests +namespace Stride.Core.Quantum.Tests; + +public class TestStructs { - public class TestStructs + public struct SimpleStruct { - public struct SimpleStruct - { - public string Name { get; set; } - public int Value { get; set; } - public override string ToString() => $"{{SimpleStruct: ({Value}), {Name}}}"; - } + public string Name { get; set; } + public int Value { get; set; } + public override readonly string ToString() => $"{{SimpleStruct: ({Value}), {Name}}}"; + } - public struct StructWithCollection - { - public List Strings { get; set; } - public override string ToString() => $"{{StructWithCollection: ({Strings})}}"; - } + public struct StructWithCollection + { + public List Strings { get; set; } + public override readonly string ToString() => $"{{StructWithCollection: ({Strings})}}"; + } - public struct FirstNestingStruct - { - public SecondNestingStruct Struct1 { get; set; } - public override string ToString() => $"{{FirstNestingStruct: {Struct1}}}"; - } + public struct FirstNestingStruct + { + public SecondNestingStruct Struct1 { get; set; } + public override readonly string ToString() => $"{{FirstNestingStruct: {Struct1}}}"; + } - public struct SecondNestingStruct - { - public SimpleStruct Struct2 { get; set; } - public override string ToString() => $"{{SecondNestingStruct: {Struct2}}}"; - } + public struct SecondNestingStruct + { + public SimpleStruct Struct2 { get; set; } + public override readonly string ToString() => $"{{SecondNestingStruct: {Struct2}}}"; + } - public class StructContainer - { - public SimpleStruct Struct { get; set; } - public override string ToString() => $"{{StructContainer: {Struct}}}"; - } + public class StructContainer + { + public SimpleStruct Struct { get; set; } + public override string ToString() => $"{{StructContainer: {Struct}}}"; + } - public class StructWithCollectionContainer - { - public StructWithCollection Struct { get; set; } - public override string ToString() => $"{{StructWithCollectionContainer: {Struct}}}"; - } + public class StructWithCollectionContainer + { + public StructWithCollection Struct { get; set; } + public override string ToString() => $"{{StructWithCollectionContainer: {Struct}}}"; + } - public class NestingStructContainer - { - public FirstNestingStruct Struct { get; set; } - public override string ToString() => $"{{NestingStructContainer: {Struct}}}"; - } + public class NestingStructContainer + { + public FirstNestingStruct Struct { get; set; } + public override string ToString() => $"{{NestingStructContainer: {Struct}}}"; + } - [Fact] - public void TestSimpleStruct() - { - var nodeContainer = new NodeContainer(); - var container = new StructContainer { Struct = new SimpleStruct { Name = "Test", Value = 1 } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - Helper.TestNonCollectionObjectNode(containerNode, container, 1); + [Fact] + public void TestSimpleStruct() + { + var nodeContainer = new NodeContainer(); + var container = new StructContainer { Struct = new SimpleStruct { Name = "Test", Value = 1 } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + Helper.TestNonCollectionObjectNode(containerNode, container, 1); - var memberNode = containerNode[nameof(StructContainer.Struct)]; - Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); - var structMember1Node = memberNode.Target[nameof(SimpleStruct.Name)]; - Helper.TestMemberNode(memberNode.Target, structMember1Node, container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); - var structMember2Node = memberNode.Target[nameof(SimpleStruct.Value)]; - Helper.TestMemberNode(memberNode.Target, structMember2Node, container.Struct, container.Struct.Value, nameof(SimpleStruct.Value), false); - } + var memberNode = containerNode[nameof(StructContainer.Struct)]; + Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); + var structMember1Node = memberNode.Target[nameof(SimpleStruct.Name)]; + Helper.TestMemberNode(memberNode.Target, structMember1Node, container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); + var structMember2Node = memberNode.Target[nameof(SimpleStruct.Value)]; + Helper.TestMemberNode(memberNode.Target, structMember2Node, container.Struct, container.Struct.Value, nameof(SimpleStruct.Value), false); + } - [Fact] - public void TestSimpleStructUpdate() - { - var nodeContainer = new NodeContainer(); - var container = new StructContainer { Struct = new SimpleStruct { Name = "Test", Value = 1 } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode[nameof(StructContainer.Struct)]; - var targetNode = memberNode.Target; - var structMember1Node = targetNode[nameof(SimpleStruct.Name)]; - var structMember2Node = targetNode[nameof(SimpleStruct.Value)]; - memberNode.Update(new SimpleStruct { Name = "Test2", Value = 2 }); + [Fact] + public void TestSimpleStructUpdate() + { + var nodeContainer = new NodeContainer(); + var container = new StructContainer { Struct = new SimpleStruct { Name = "Test", Value = 1 } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode[nameof(StructContainer.Struct)]; + var targetNode = memberNode.Target; + var structMember1Node = targetNode[nameof(SimpleStruct.Name)]; + var structMember2Node = targetNode[nameof(SimpleStruct.Value)]; + memberNode.Update(new SimpleStruct { Name = "Test2", Value = 2 }); - Assert.Equal("Test2", container.Struct.Name); - Assert.Equal(2, container.Struct.Value); - Assert.Equal(targetNode, memberNode.Target); - Assert.Equal(structMember1Node, memberNode.Target[nameof(SimpleStruct.Name)]); - Assert.Equal(structMember2Node, memberNode.Target[nameof(SimpleStruct.Value)]); - structMember1Node = memberNode.Target[nameof(SimpleStruct.Name)]; - Helper.TestMemberNode(memberNode.Target, structMember1Node, container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); - structMember2Node = memberNode.Target[nameof(SimpleStruct.Value)]; - Helper.TestMemberNode(memberNode.Target, structMember2Node, container.Struct, container.Struct.Value, nameof(SimpleStruct.Value), false); - } + Assert.Equal("Test2", container.Struct.Name); + Assert.Equal(2, container.Struct.Value); + Assert.Equal(targetNode, memberNode.Target); + Assert.Equal(structMember1Node, memberNode.Target[nameof(SimpleStruct.Name)]); + Assert.Equal(structMember2Node, memberNode.Target[nameof(SimpleStruct.Value)]); + structMember1Node = memberNode.Target[nameof(SimpleStruct.Name)]; + Helper.TestMemberNode(memberNode.Target, structMember1Node, container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); + structMember2Node = memberNode.Target[nameof(SimpleStruct.Value)]; + Helper.TestMemberNode(memberNode.Target, structMember2Node, container.Struct, container.Struct.Value, nameof(SimpleStruct.Value), false); + } - [Fact] - public void TestSimpleStructMemberUpdate() - { - var nodeContainer = new NodeContainer(); - var container = new StructContainer { Struct = new SimpleStruct { Name = "Test", Value = 1 } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode[nameof(StructContainer.Struct)]; - var targetNode = memberNode.Target; - var structMember1Node = targetNode[nameof(SimpleStruct.Name)]; - var structMember2Node = targetNode[nameof(SimpleStruct.Value)]; + [Fact] + public void TestSimpleStructMemberUpdate() + { + var nodeContainer = new NodeContainer(); + var container = new StructContainer { Struct = new SimpleStruct { Name = "Test", Value = 1 } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode[nameof(StructContainer.Struct)]; + var targetNode = memberNode.Target; + var structMember1Node = targetNode[nameof(SimpleStruct.Name)]; + var structMember2Node = targetNode[nameof(SimpleStruct.Value)]; - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); - Helper.TestMemberNode(memberNode.Target, structMember1Node, container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); + Helper.TestMemberNode(memberNode.Target, structMember1Node, container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); - structMember1Node.Update("Test2"); - Assert.Equal("Test2", container.Struct.Name); - Assert.Equal(targetNode, memberNode.Target); - Assert.Equal(structMember1Node, memberNode.Target[nameof(SimpleStruct.Name)]); - Assert.Equal(structMember2Node, memberNode.Target[nameof(SimpleStruct.Value)]); - Helper.TestMemberNode(memberNode.Target, structMember1Node, container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); - Helper.TestMemberNode(memberNode.Target, memberNode.Target.Members.First(), container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); - } + structMember1Node.Update("Test2"); + Assert.Equal("Test2", container.Struct.Name); + Assert.Equal(targetNode, memberNode.Target); + Assert.Equal(structMember1Node, memberNode.Target[nameof(SimpleStruct.Name)]); + Assert.Equal(structMember2Node, memberNode.Target[nameof(SimpleStruct.Value)]); + Helper.TestMemberNode(memberNode.Target, structMember1Node, container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); + Helper.TestMemberNode(memberNode.Target, memberNode.Target.Members.First(), container.Struct, container.Struct.Name, nameof(SimpleStruct.Name), false); + } - [Fact] - public void TestSimpleStructCollectionUpdate() - { - var nodeContainer = new NodeContainer(); - var container = new StructWithCollectionContainer { Struct = new StructWithCollection { Strings = new List { "aaa", "bbb" } } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode[nameof(StructWithCollectionContainer.Struct)]; - var targetNode = memberNode.Target; - var structMemberNode = targetNode[nameof(StructWithCollection.Strings)]; + [Fact] + public void TestSimpleStructCollectionUpdate() + { + var nodeContainer = new NodeContainer(); + var container = new StructWithCollectionContainer { Struct = new StructWithCollection { Strings = ["aaa", "bbb"] } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode[nameof(StructWithCollectionContainer.Struct)]; + var targetNode = memberNode.Target; + var structMemberNode = targetNode[nameof(StructWithCollection.Strings)]; - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructWithCollectionContainer.Struct), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); - Helper.TestMemberNode(targetNode, structMemberNode, container.Struct, container.Struct.Strings, nameof(StructWithCollection.Strings), true); + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructWithCollectionContainer.Struct), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); + Helper.TestMemberNode(targetNode, structMemberNode, container.Struct, container.Struct.Strings, nameof(StructWithCollection.Strings), true); - structMemberNode.Target.Update("ddd", new NodeIndex(1)); - Assert.Equal("ddd", container.Struct.Strings[1]); - Assert.Equal(targetNode, memberNode.Target); - Assert.Equal(structMemberNode, targetNode[nameof(StructWithCollection.Strings)]); - Helper.TestMemberNode(targetNode, structMemberNode, container.Struct, container.Struct.Strings, nameof(StructWithCollection.Strings), true); - } + structMemberNode.Target.Update("ddd", new NodeIndex(1)); + Assert.Equal("ddd", container.Struct.Strings[1]); + Assert.Equal(targetNode, memberNode.Target); + Assert.Equal(structMemberNode, targetNode[nameof(StructWithCollection.Strings)]); + Helper.TestMemberNode(targetNode, structMemberNode, container.Struct, container.Struct.Strings, nameof(StructWithCollection.Strings), true); + } - [Fact] - public void TestNestedStruct() - { - var nodeContainer = new NodeContainer(); - var container = new NestingStructContainer { Struct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test", Value = 1 } } } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode[nameof(NestingStructContainer.Struct)]; - var firstTargetNode = memberNode.Target; - var firstNestingMemberNode = firstTargetNode[nameof(FirstNestingStruct.Struct1)]; - var secondTargetNode = firstNestingMemberNode.Target; - var secondNestingMemberNode = secondTargetNode[nameof(SecondNestingStruct.Struct2)]; - var structMember1Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]; - var structMember2Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]; + [Fact] + public void TestNestedStruct() + { + var nodeContainer = new NodeContainer(); + var container = new NestingStructContainer { Struct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test", Value = 1 } } } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode[nameof(NestingStructContainer.Struct)]; + var firstTargetNode = memberNode.Target; + var firstNestingMemberNode = firstTargetNode[nameof(FirstNestingStruct.Struct1)]; + var secondTargetNode = firstNestingMemberNode.Target; + var secondNestingMemberNode = secondTargetNode[nameof(SecondNestingStruct.Struct2)]; + var structMember1Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]; + var structMember2Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]; - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); - Helper.TestMemberNode(memberNode.Target, firstNestingMemberNode, container.Struct, container.Struct.Struct1, nameof(FirstNestingStruct.Struct1), true); - Helper.TestNonNullObjectReference(firstNestingMemberNode.TargetReference, container.Struct.Struct1, false); - Helper.TestMemberNode(firstNestingMemberNode.Target, secondNestingMemberNode, container.Struct.Struct1, container.Struct.Struct1.Struct2, nameof(SecondNestingStruct.Struct2), true); - Helper.TestNonNullObjectReference(secondNestingMemberNode.TargetReference, container.Struct.Struct1.Struct2, false); - Helper.TestMemberNode(secondNestingMemberNode.Target, structMember1Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Name, nameof(SimpleStruct.Name), false); - Helper.TestMemberNode(secondNestingMemberNode.Target, structMember2Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Value, nameof(SimpleStruct.Value), false); - } + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); + Helper.TestMemberNode(memberNode.Target, firstNestingMemberNode, container.Struct, container.Struct.Struct1, nameof(FirstNestingStruct.Struct1), true); + Helper.TestNonNullObjectReference(firstNestingMemberNode.TargetReference, container.Struct.Struct1, false); + Helper.TestMemberNode(firstNestingMemberNode.Target, secondNestingMemberNode, container.Struct.Struct1, container.Struct.Struct1.Struct2, nameof(SecondNestingStruct.Struct2), true); + Helper.TestNonNullObjectReference(secondNestingMemberNode.TargetReference, container.Struct.Struct1.Struct2, false); + Helper.TestMemberNode(secondNestingMemberNode.Target, structMember1Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Name, nameof(SimpleStruct.Name), false); + Helper.TestMemberNode(secondNestingMemberNode.Target, structMember2Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Value, nameof(SimpleStruct.Value), false); + } - [Fact] - public void TestFirstNestedStructUpdate() - { - var nodeContainer = new NodeContainer(); - var container = new NestingStructContainer { Struct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test", Value = 1 } } } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode[nameof(NestingStructContainer.Struct)]; - var firstTargetNode = memberNode.Target; - var firstNestingMemberNode = firstTargetNode[nameof(FirstNestingStruct.Struct1)]; - var secondTargetNode = firstNestingMemberNode.Target; - var secondNestingMemberNode = secondTargetNode[nameof(SecondNestingStruct.Struct2)]; - var structMember1Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]; - var structMember2Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]; + [Fact] + public void TestFirstNestedStructUpdate() + { + var nodeContainer = new NodeContainer(); + var container = new NestingStructContainer { Struct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test", Value = 1 } } } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode[nameof(NestingStructContainer.Struct)]; + var firstTargetNode = memberNode.Target; + var firstNestingMemberNode = firstTargetNode[nameof(FirstNestingStruct.Struct1)]; + var secondTargetNode = firstNestingMemberNode.Target; + var secondNestingMemberNode = secondTargetNode[nameof(SecondNestingStruct.Struct2)]; + var structMember1Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]; + var structMember2Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]; - var newStruct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test2", Value = 2 } } }; - memberNode.Update(newStruct); - Assert.Equal("Test2", container.Struct.Struct1.Struct2.Name); - Assert.Equal(2, container.Struct.Struct1.Struct2.Value); - Assert.Equal(firstTargetNode, memberNode.Target); - Assert.Equal(firstNestingMemberNode, firstTargetNode[nameof(FirstNestingStruct.Struct1)]); - Assert.Equal(secondTargetNode, firstNestingMemberNode.Target); - Assert.Equal(secondNestingMemberNode, secondTargetNode[nameof(SecondNestingStruct.Struct2)]); - Assert.Equal(structMember1Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]); - Assert.Equal(structMember2Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]); + var newStruct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test2", Value = 2 } } }; + memberNode.Update(newStruct); + Assert.Equal("Test2", container.Struct.Struct1.Struct2.Name); + Assert.Equal(2, container.Struct.Struct1.Struct2.Value); + Assert.Equal(firstTargetNode, memberNode.Target); + Assert.Equal(firstNestingMemberNode, firstTargetNode[nameof(FirstNestingStruct.Struct1)]); + Assert.Equal(secondTargetNode, firstNestingMemberNode.Target); + Assert.Equal(secondNestingMemberNode, secondTargetNode[nameof(SecondNestingStruct.Struct2)]); + Assert.Equal(structMember1Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]); + Assert.Equal(structMember2Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]); - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); - Helper.TestMemberNode(memberNode.Target, firstNestingMemberNode, container.Struct, container.Struct.Struct1, nameof(FirstNestingStruct.Struct1), true); - Helper.TestNonNullObjectReference(firstNestingMemberNode.TargetReference, container.Struct.Struct1, false); - Helper.TestMemberNode(firstNestingMemberNode.Target, secondNestingMemberNode, container.Struct.Struct1, container.Struct.Struct1.Struct2, nameof(SecondNestingStruct.Struct2), true); - Helper.TestNonNullObjectReference(secondNestingMemberNode.TargetReference, container.Struct.Struct1.Struct2, false); - Helper.TestMemberNode(secondNestingMemberNode.Target, structMember1Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Name, nameof(SimpleStruct.Name), false); - Helper.TestMemberNode(secondNestingMemberNode.Target, structMember2Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Value, nameof(SimpleStruct.Value), false); - } + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); + Helper.TestMemberNode(memberNode.Target, firstNestingMemberNode, container.Struct, container.Struct.Struct1, nameof(FirstNestingStruct.Struct1), true); + Helper.TestNonNullObjectReference(firstNestingMemberNode.TargetReference, container.Struct.Struct1, false); + Helper.TestMemberNode(firstNestingMemberNode.Target, secondNestingMemberNode, container.Struct.Struct1, container.Struct.Struct1.Struct2, nameof(SecondNestingStruct.Struct2), true); + Helper.TestNonNullObjectReference(secondNestingMemberNode.TargetReference, container.Struct.Struct1.Struct2, false); + Helper.TestMemberNode(secondNestingMemberNode.Target, structMember1Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Name, nameof(SimpleStruct.Name), false); + Helper.TestMemberNode(secondNestingMemberNode.Target, structMember2Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Value, nameof(SimpleStruct.Value), false); + } - [Fact] - public void TestSecondNestedStructUpdate() - { - var nodeContainer = new NodeContainer(); - var container = new NestingStructContainer { Struct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test", Value = 1 } } } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode[nameof(NestingStructContainer.Struct)]; - var firstTargetNode = memberNode.Target; - var firstNestingMemberNode = firstTargetNode[nameof(FirstNestingStruct.Struct1)]; - var secondTargetNode = firstNestingMemberNode.Target; - var secondNestingMemberNode = secondTargetNode[nameof(SecondNestingStruct.Struct2)]; - var structMember1Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]; - var structMember2Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]; + [Fact] + public void TestSecondNestedStructUpdate() + { + var nodeContainer = new NodeContainer(); + var container = new NestingStructContainer { Struct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test", Value = 1 } } } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode[nameof(NestingStructContainer.Struct)]; + var firstTargetNode = memberNode.Target; + var firstNestingMemberNode = firstTargetNode[nameof(FirstNestingStruct.Struct1)]; + var secondTargetNode = firstNestingMemberNode.Target; + var secondNestingMemberNode = secondTargetNode[nameof(SecondNestingStruct.Struct2)]; + var structMember1Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]; + var structMember2Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]; - var newStruct = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test2", Value = 2 } }; - firstNestingMemberNode.Update(newStruct); - Assert.Equal("Test2", container.Struct.Struct1.Struct2.Name); - Assert.Equal(2, container.Struct.Struct1.Struct2.Value); - Assert.Equal(firstTargetNode, memberNode.Target); - Assert.Equal(firstNestingMemberNode, firstTargetNode[nameof(FirstNestingStruct.Struct1)]); - Assert.Equal(secondTargetNode, firstNestingMemberNode.Target); - Assert.Equal(secondNestingMemberNode, secondTargetNode[nameof(SecondNestingStruct.Struct2)]); - Assert.Equal(structMember1Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]); - Assert.Equal(structMember2Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]); + var newStruct = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test2", Value = 2 } }; + firstNestingMemberNode.Update(newStruct); + Assert.Equal("Test2", container.Struct.Struct1.Struct2.Name); + Assert.Equal(2, container.Struct.Struct1.Struct2.Value); + Assert.Equal(firstTargetNode, memberNode.Target); + Assert.Equal(firstNestingMemberNode, firstTargetNode[nameof(FirstNestingStruct.Struct1)]); + Assert.Equal(secondTargetNode, firstNestingMemberNode.Target); + Assert.Equal(secondNestingMemberNode, secondTargetNode[nameof(SecondNestingStruct.Struct2)]); + Assert.Equal(structMember1Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]); + Assert.Equal(structMember2Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]); - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); - Helper.TestMemberNode(firstTargetNode, firstNestingMemberNode, container.Struct, container.Struct.Struct1, nameof(FirstNestingStruct.Struct1), true); - Helper.TestNonNullObjectReference(firstNestingMemberNode.TargetReference, container.Struct.Struct1, false); - Helper.TestMemberNode(secondTargetNode, secondNestingMemberNode, container.Struct.Struct1, container.Struct.Struct1.Struct2, nameof(SecondNestingStruct.Struct2), true); - Helper.TestNonNullObjectReference(secondNestingMemberNode.TargetReference, container.Struct.Struct1.Struct2, false); - Helper.TestMemberNode(secondNestingMemberNode.Target, structMember1Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Name, nameof(SimpleStruct.Name), false); - Helper.TestMemberNode(secondNestingMemberNode.Target, structMember2Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Value, nameof(SimpleStruct.Value), false); - } + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); + Helper.TestMemberNode(firstTargetNode, firstNestingMemberNode, container.Struct, container.Struct.Struct1, nameof(FirstNestingStruct.Struct1), true); + Helper.TestNonNullObjectReference(firstNestingMemberNode.TargetReference, container.Struct.Struct1, false); + Helper.TestMemberNode(secondTargetNode, secondNestingMemberNode, container.Struct.Struct1, container.Struct.Struct1.Struct2, nameof(SecondNestingStruct.Struct2), true); + Helper.TestNonNullObjectReference(secondNestingMemberNode.TargetReference, container.Struct.Struct1.Struct2, false); + Helper.TestMemberNode(secondNestingMemberNode.Target, structMember1Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Name, nameof(SimpleStruct.Name), false); + Helper.TestMemberNode(secondNestingMemberNode.Target, structMember2Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Value, nameof(SimpleStruct.Value), false); + } - [Fact] - public void TestNestedStructMemberUpdate() - { - var nodeContainer = new NodeContainer(); - var container = new NestingStructContainer { Struct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test", Value = 1 } } } }; - var containerNode = nodeContainer.GetOrCreateNode(container); - var memberNode = containerNode[nameof(NestingStructContainer.Struct)]; - var firstTargetNode = memberNode.Target; - var firstNestingMemberNode = firstTargetNode[nameof(FirstNestingStruct.Struct1)]; - var secondTargetNode = firstNestingMemberNode.Target; - var secondNestingMemberNode = secondTargetNode[nameof(SecondNestingStruct.Struct2)]; - var structMember1Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]; - var structMember2Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]; + [Fact] + public void TestNestedStructMemberUpdate() + { + var nodeContainer = new NodeContainer(); + var container = new NestingStructContainer { Struct = new FirstNestingStruct { Struct1 = new SecondNestingStruct { Struct2 = new SimpleStruct { Name = "Test", Value = 1 } } } }; + var containerNode = nodeContainer.GetOrCreateNode(container); + var memberNode = containerNode[nameof(NestingStructContainer.Struct)]; + var firstTargetNode = memberNode.Target; + var firstNestingMemberNode = firstTargetNode[nameof(FirstNestingStruct.Struct1)]; + var secondTargetNode = firstNestingMemberNode.Target; + var secondNestingMemberNode = secondTargetNode[nameof(SecondNestingStruct.Struct2)]; + var structMember1Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]; + var structMember2Node = secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]; - structMember1Node.Update("Test2"); - structMember2Node.Update(2); - Assert.Equal("Test2", container.Struct.Struct1.Struct2.Name); - Assert.Equal(2, container.Struct.Struct1.Struct2.Value); - Assert.Equal(firstTargetNode, memberNode.Target); - Assert.Equal(firstNestingMemberNode, firstTargetNode[nameof(FirstNestingStruct.Struct1)]); - Assert.Equal(secondTargetNode, firstNestingMemberNode.Target); - Assert.Equal(secondNestingMemberNode, secondTargetNode[nameof(SecondNestingStruct.Struct2)]); - Assert.Equal(structMember1Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]); - Assert.Equal(structMember2Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]); + structMember1Node.Update("Test2"); + structMember2Node.Update(2); + Assert.Equal("Test2", container.Struct.Struct1.Struct2.Name); + Assert.Equal(2, container.Struct.Struct1.Struct2.Value); + Assert.Equal(firstTargetNode, memberNode.Target); + Assert.Equal(firstNestingMemberNode, firstTargetNode[nameof(FirstNestingStruct.Struct1)]); + Assert.Equal(secondTargetNode, firstNestingMemberNode.Target); + Assert.Equal(secondNestingMemberNode, secondTargetNode[nameof(SecondNestingStruct.Struct2)]); + Assert.Equal(structMember1Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Name)]); + Assert.Equal(structMember2Node, secondNestingMemberNode.Target[nameof(SimpleStruct.Value)]); - Helper.TestNonCollectionObjectNode(containerNode, container, 1); - Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); - Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); - Helper.TestMemberNode(firstTargetNode, firstNestingMemberNode, container.Struct, container.Struct.Struct1, nameof(FirstNestingStruct.Struct1), true); - Helper.TestNonNullObjectReference(firstNestingMemberNode.TargetReference, container.Struct.Struct1, false); - Helper.TestMemberNode(secondTargetNode, secondNestingMemberNode, container.Struct.Struct1, container.Struct.Struct1.Struct2, nameof(SecondNestingStruct.Struct2), true); - Helper.TestNonNullObjectReference(secondNestingMemberNode.TargetReference, container.Struct.Struct1.Struct2, false); - Helper.TestMemberNode(secondNestingMemberNode.Target, structMember1Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Name, nameof(SimpleStruct.Name), false); - Helper.TestMemberNode(secondNestingMemberNode.Target, structMember2Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Value, nameof(SimpleStruct.Value), false); - } + Helper.TestNonCollectionObjectNode(containerNode, container, 1); + Helper.TestMemberNode(containerNode, memberNode, container, container.Struct, nameof(StructContainer.Struct), true); + Helper.TestNonNullObjectReference(memberNode.TargetReference, container.Struct, false); + Helper.TestMemberNode(firstTargetNode, firstNestingMemberNode, container.Struct, container.Struct.Struct1, nameof(FirstNestingStruct.Struct1), true); + Helper.TestNonNullObjectReference(firstNestingMemberNode.TargetReference, container.Struct.Struct1, false); + Helper.TestMemberNode(secondTargetNode, secondNestingMemberNode, container.Struct.Struct1, container.Struct.Struct1.Struct2, nameof(SecondNestingStruct.Struct2), true); + Helper.TestNonNullObjectReference(secondNestingMemberNode.TargetReference, container.Struct.Struct1.Struct2, false); + Helper.TestMemberNode(secondNestingMemberNode.Target, structMember1Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Name, nameof(SimpleStruct.Name), false); + Helper.TestMemberNode(secondNestingMemberNode.Target, structMember2Node, container.Struct.Struct1.Struct2, container.Struct.Struct1.Struct2.Value, nameof(SimpleStruct.Value), false); } } From 6975a0efdea0df464067457bbeba92ee858d631f Mon Sep 17 00:00:00 2001 From: Nicolas Musset Date: Fri, 21 Feb 2025 16:00:51 +0100 Subject: [PATCH 5/6] [Core.Presentation.Quantum] Modernize code --- .../AssetVirtualNodePresenter.cs | 13 +- .../CombineMode.cs | 37 +- .../GraphNodeBinding.cs | 100 +- .../GraphViewModelService.cs | 37 +- .../IPropertyProviderViewModel.cs | 57 +- .../MemberGraphNodeBinding.cs | 116 +- .../ObjectGraphNodeBinding.cs | 116 +- .../AnonymousNodePresenterCommand.cs | 52 +- .../Presenters/IInitializingNodePresenter.cs | 13 +- .../Presenters/INodePresenter.cs | 91 +- .../Presenters/INodePresenterCommand.cs | 26 +- .../Presenters/INodePresenterFactory.cs | 15 +- .../INodePresenterFactoryInternal.cs | 15 +- .../Presenters/INodePresenterUpdater.cs | 38 +- .../Presenters/ItemNodePresenter.cs | 238 ++-- .../Presenters/MemberNodePresenter.cs | 242 ++-- .../Presenters/NodePresenterBase.cs | 231 ++-- .../Presenters/NodePresenterCommandBase.cs | 43 +- .../Presenters/NodePresenterFactory.cs | 256 ++-- .../Presenters/RootNodePresenter.cs | 24 +- .../SyncAnonymousNodePresenterCommand.cs | 52 +- .../SyncNodePresenterCommandBase.cs | 30 +- .../Presenters/ValueChangedEventArgs.cs | 33 +- .../Presenters/ValueChangingEventArgs.cs | 33 +- .../Presenters/VirtualNodePresenter.cs | 256 ++-- .../Stride.Core.Presentation.Quantum.csproj | 5 +- .../ViewModels/GraphViewModel.cs | 165 ++- .../ViewModels/INodeViewModelFactory.cs | 16 +- .../ViewModels/NodePresenterCommandWrapper.cs | 120 +- .../ViewModels/NodeViewModel.cs | 1129 ++++++++--------- .../NodeViewModelDynamicMetaObject.cs | 96 +- .../ViewModels/NodeViewModelFactory.cs | 126 +- .../NodeViewModelValueChangedArgs.cs | 20 +- 33 files changed, 1867 insertions(+), 1974 deletions(-) diff --git a/sources/editor/Stride.Core.Assets.Editor/Quantum/NodePresenters/AssetVirtualNodePresenter.cs b/sources/editor/Stride.Core.Assets.Editor/Quantum/NodePresenters/AssetVirtualNodePresenter.cs index 4e30587984..1c792fe7fa 100644 --- a/sources/editor/Stride.Core.Assets.Editor/Quantum/NodePresenters/AssetVirtualNodePresenter.cs +++ b/sources/editor/Stride.Core.Assets.Editor/Quantum/NodePresenters/AssetVirtualNodePresenter.cs @@ -25,13 +25,16 @@ public AssetVirtualNodePresenter([NotNull] INodePresenterFactoryInternal factory this.isOverridden = isOverridden; } - public override void Dispose() + protected override void Dispose(bool disposing) { - base.Dispose(); - if (AssociatedNode.Node != null) + base.Dispose(disposing); + if (disposing) { - ((IAssetNode)AssociatedNode.Node).OverrideChanging -= OnOverrideChanging; - ((IAssetNode)AssociatedNode.Node).OverrideChanged -= OnOverrideChanged; + if (AssociatedNode.Node != null) + { + ((IAssetNode)AssociatedNode.Node).OverrideChanging -= OnOverrideChanging; + ((IAssetNode)AssociatedNode.Node).OverrideChanged -= OnOverrideChanged; + } } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/CombineMode.cs b/sources/presentation/Stride.Core.Presentation.Quantum/CombineMode.cs index 9a8ce347cf..c634aeebdb 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/CombineMode.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/CombineMode.cs @@ -1,26 +1,25 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -namespace Stride.Core.Presentation.Quantum +namespace Stride.Core.Presentation.Quantum; + +/// +/// An enum that describes what to do with a node or a command when combining view models. +/// +public enum CombineMode { /// - /// An enum that describes what to do with a node or a command when combining view models. + /// The command or the node should never be combined. + /// + DoNotCombine, + /// + /// The command should always be combined, even if some of the combined nodes do not have it. + /// The nodes should always be combined, even if some single view models does not have it. + /// + AlwaysCombine, + /// + /// The command should be combined only if all combined nodes have it. + /// The nodes should be combined only if all single view models have it. /// - public enum CombineMode - { - /// - /// The command or the node should never be combined. - /// - DoNotCombine, - /// - /// The command should always be combined, even if some of the combined nodes do not have it. - /// The nodes should always be combined, even if some single view models does not have it. - /// - AlwaysCombine, - /// - /// The command should be combined only if all combined nodes have it. - /// The nodes should be combined only if all single view models have it. - /// - CombineOnlyForAll, - } + CombineOnlyForAll, } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/GraphNodeBinding.cs b/sources/presentation/Stride.Core.Presentation.Quantum/GraphNodeBinding.cs index b0454955f8..47dadca6b6 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/GraphNodeBinding.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/GraphNodeBinding.cs @@ -1,68 +1,72 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; + using Stride.Core.Presentation.Services; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum +namespace Stride.Core.Presentation.Quantum; + +public abstract class GraphNodeBinding : IDisposable { - public abstract class GraphNodeBinding : IDisposable - { - public delegate void PropertyChangeDelegate(string[] propertyNames); + public delegate void PropertyChangeDelegate(string[] propertyNames); - protected readonly IUndoRedoService ActionService; - protected readonly string PropertyName; - protected readonly Func Converter; + protected readonly IUndoRedoService ActionService; + protected readonly string PropertyName; + protected readonly Func Converter; - private readonly PropertyChangeDelegate propertyChanging; - private readonly PropertyChangeDelegate propertyChanged; - private readonly bool notifyChangesOnly; + private readonly PropertyChangeDelegate propertyChanging; + private readonly PropertyChangeDelegate propertyChanged; + private readonly bool notifyChangesOnly; - internal GraphNodeBinding(string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, [NotNull] Func converter, IUndoRedoService actionService, bool notifyChangesOnly = true) - { - PropertyName = propertyName; - this.propertyChanging = propertyChanging; - this.propertyChanged = propertyChanged; - Converter = converter ?? throw new ArgumentNullException(nameof(converter)); - ActionService = actionService; - this.notifyChangesOnly = notifyChangesOnly; - } + internal GraphNodeBinding(string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, Func converter, IUndoRedoService actionService, bool notifyChangesOnly = true) + { + PropertyName = propertyName; + this.propertyChanging = propertyChanging; + this.propertyChanged = propertyChanged; + Converter = converter ?? throw new ArgumentNullException(nameof(converter)); + ActionService = actionService; + this.notifyChangesOnly = notifyChangesOnly; + } - /// - public virtual void Dispose() - { - } + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } - /// - /// Gets the current value of the graph node. - /// - /// The current value of the graph node. - /// This method can be invoked from a property getter. - public abstract TContentType GetNodeValue(); + /// + /// Gets the current value of the graph node. + /// + /// The current value of the graph node. + /// This method can be invoked from a property getter. + public abstract TContentType GetNodeValue(); - /// - /// Sets the current value of the graph node. - /// - /// The value to set for the graph node content. - /// This method can be invoked from a property setter. - /// This method will invoke the delegates passed to the constructor of this instance if the new value is different from the previous one. - public abstract void SetNodeValue(TTargetType value); + /// + /// Sets the current value of the graph node. + /// + /// The value to set for the graph node content. + /// This method can be invoked from a property setter. + /// This method will invoke the delegates passed to the constructor of this instance if the new value is different from the previous one. + public abstract void SetNodeValue(TTargetType value); - protected void ValueChanging(object sender, INodeChangeEventArgs e) + protected void ValueChanging(object? _, INodeChangeEventArgs e) + { + if (!notifyChangesOnly || !Equals(e.OldValue, e.NewValue)) { - if (!notifyChangesOnly || !Equals(e.OldValue, e.NewValue)) - { - propertyChanging?.Invoke(new[] { PropertyName }); - } + propertyChanging?.Invoke([PropertyName]); } + } - protected void ValueChanged(object sender, INodeChangeEventArgs e) + protected void ValueChanged(object? _, INodeChangeEventArgs e) + { + if (!notifyChangesOnly || !Equals(e.OldValue,e.NewValue)) { - if (!notifyChangesOnly || !Equals(e.OldValue,e.NewValue)) - { - propertyChanged?.Invoke(new[] { PropertyName }); - } + propertyChanged?.Invoke([PropertyName]); } } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/GraphViewModelService.cs b/sources/presentation/Stride.Core.Presentation.Quantum/GraphViewModelService.cs index 3ed89a1487..b5bbb6385e 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/GraphViewModelService.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/GraphViewModelService.cs @@ -1,35 +1,32 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Presentation.Quantum.Presenters; using Stride.Core.Presentation.Quantum.ViewModels; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum +namespace Stride.Core.Presentation.Quantum; + +/// +/// A class that provides various services to objects +/// +public class GraphViewModelService { /// - /// A class that provides various services to objects + /// Initializes a new instance of the class. /// - public class GraphViewModelService + public GraphViewModelService(NodeContainer nodeContainer) { - /// - /// Initializes a new instance of the class. - /// - public GraphViewModelService([NotNull] NodeContainer nodeContainer) - { - if (nodeContainer == null) throw new ArgumentNullException(nameof(nodeContainer)); - NodePresenterFactory = new NodePresenterFactory(nodeContainer.NodeBuilder, AvailableCommands, AvailableUpdaters); - NodeViewModelFactory = new NodeViewModelFactory(); - } + ArgumentNullException.ThrowIfNull(nodeContainer); + NodePresenterFactory = new NodePresenterFactory(nodeContainer.NodeBuilder, AvailableCommands, AvailableUpdaters); + NodeViewModelFactory = new NodeViewModelFactory(); + } - public INodePresenterFactory NodePresenterFactory { get; set; } + public INodePresenterFactory NodePresenterFactory { get; set; } - public NodeViewModelFactory NodeViewModelFactory { get; set; } + public NodeViewModelFactory NodeViewModelFactory { get; set; } - public List AvailableCommands { get; } = new List(); + public List AvailableCommands { get; } = []; - public List AvailableUpdaters { get; } = new List(); - } + public List AvailableUpdaters { get; } = []; } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/IPropertyProviderViewModel.cs b/sources/presentation/Stride.Core.Presentation.Quantum/IPropertyProviderViewModel.cs index 373225fca0..ee2bf30264 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/IPropertyProviderViewModel.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/IPropertyProviderViewModel.cs @@ -1,42 +1,39 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; -using Stride.Core.Presentation.Quantum.Presenters; + using Stride.Core.Presentation.Quantum.ViewModels; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum +namespace Stride.Core.Presentation.Quantum; + +/// +/// An interface representing an view model that can provide properties to build an . +/// +public interface IPropertyProviderViewModel { /// - /// An interface representing an view model that can provide properties to build an . + /// Gets whether this view model is currently able to provide properties. /// - public interface IPropertyProviderViewModel - { - /// - /// Gets whether this view model is currently able to provide properties. - /// - bool CanProvidePropertiesViewModel { get; } + bool CanProvidePropertiesViewModel { get; } - /// - /// Retrieves the root to use to generate properties. - /// - /// The root to use to generate properties. - IObjectNode GetRootNode(); + /// + /// Retrieves the root to use to generate properties. + /// + /// The root to use to generate properties. + IObjectNode GetRootNode(); - /// - /// Indicates whether the view model of a specific member should be constructed. - /// - /// The member to evaluate. - /// True if the member node should be constructed, False otherwise. - bool ShouldConstructMember([NotNull] IMemberNode member); + /// + /// Indicates whether the view model of a specific member should be constructed. + /// + /// The member to evaluate. + /// True if the member node should be constructed, False otherwise. + bool ShouldConstructMember(IMemberNode member); - /// - /// Indicates whether the view model of a specific item of a collection should be constructed. - /// - /// The collection to evaluate. - /// The index of the item to evaluate. - /// True if the member node should be constructed, False otherwise. - bool ShouldConstructItem([NotNull] IObjectNode collection, NodeIndex index); - } + /// + /// Indicates whether the view model of a specific item of a collection should be constructed. + /// + /// The collection to evaluate. + /// The index of the item to evaluate. + /// True if the member node should be constructed, False otherwise. + bool ShouldConstructItem(IObjectNode collection, NodeIndex index); } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/MemberGraphNodeBinding.cs b/sources/presentation/Stride.Core.Presentation.Quantum/MemberGraphNodeBinding.cs index 54803db228..502ff37c4e 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/MemberGraphNodeBinding.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/MemberGraphNodeBinding.cs @@ -1,80 +1,80 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; + using Stride.Core.Presentation.Services; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum +namespace Stride.Core.Presentation.Quantum; + +/// +/// This class allows to bind a property of a view model to an and properly trigger property change notifications +/// when the node value is modified. +/// +/// The type of property bound to the graph node. +/// The type of content in the graph node. +public class MemberGraphNodeBinding : GraphNodeBinding { - /// - /// This class allows to bind a property of a view model to an and properly trigger property change notifications - /// when the node value is modified. - /// - /// The type of property bound to the graph node. - /// The type of content in the graph node. - public class MemberGraphNodeBinding : GraphNodeBinding + protected IMemberNode Node; + + public MemberGraphNodeBinding(IMemberNode node, string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, Func converter, IUndoRedoService actionService, bool notifyChangesOnly = true) + : base(propertyName, propertyChanging, propertyChanged, converter, actionService, notifyChangesOnly) { - protected IMemberNode Node; + Node = node; + node.ValueChanged += ValueChanged; + node.ValueChanging += ValueChanging; + } - public MemberGraphNodeBinding([NotNull] IMemberNode node, string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, [NotNull] Func converter, IUndoRedoService actionService, bool notifyChangesOnly = true) - : base(propertyName, propertyChanging, propertyChanged, converter, actionService, notifyChangesOnly) - { - Node = node; - node.ValueChanged += ValueChanged; - node.ValueChanging += ValueChanging; - } + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); - public override void Dispose() + if (disposing) { - base.Dispose(); Node.ValueChanged -= ValueChanged; Node.ValueChanging -= ValueChanging; } + } - public override TContentType GetNodeValue() - { - var value = (TContentType)Node.Retrieve(); - return value; - } + public override TContentType GetNodeValue() + { + var value = (TContentType)Node.Retrieve(); + return value; + } - public override void SetNodeValue(TTargetType value) - { - using (var transaction = ActionService?.CreateTransaction()) - { - Node.Update(Converter(value)); - ActionService?.SetName(transaction, $"Update property {PropertyName}"); - } - } + public override void SetNodeValue(TTargetType value) + { + using var transaction = ActionService?.CreateTransaction(); + Node.Update(Converter(value)); + ActionService?.SetName(transaction!, $"Update property {PropertyName}"); } +} +/// +/// This is a specialization of the class, when the target type is the same that the +/// content type. +/// This class allows to bind a property of a view model to a and properly trigger property change notifications +/// when the node value is modified. +/// +/// The type of the node content and the property bound to the graph node. +public class MemberGraphNodeBinding : MemberGraphNodeBinding +{ /// - /// This is a specialization of the class, when the target type is the same that the - /// content type. - /// This class allows to bind a property of a view model to a and properly trigger property change notifications - /// when the node value is modified. + /// Initializes a new instance of the class. /// - /// The type of the node content and the property bound to the graph node. - public class MemberGraphNodeBinding : MemberGraphNodeBinding + /// The graph node bound to this instance. + /// The name of the property of the view model that is bound to this instance. + /// The delegate to invoke when the node content is about to change. + /// The delegate to invoke when the node content has changed. + /// + /// If True, delegates will be invoked only if the content of the node has actually changed. Otherwise, they will be invoked every time the node is updated, even if the new value is equal to the previous one. + public MemberGraphNodeBinding(IMemberNode node, string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, IUndoRedoService actionService, bool notifyChangesOnly = true) + : base(node, propertyName, propertyChanging, propertyChanged, x => x, actionService, notifyChangesOnly) { - /// - /// Initializes a new instance of the class. - /// - /// The graph node bound to this instance. - /// The name of the property of the view model that is bound to this instance. - /// The delegate to invoke when the node content is about to change. - /// The delegate to invoke when the node content has changed. - /// - /// If True, delegates will be invoked only if the content of the node has actually changed. Otherwise, they will be invoked every time the node is updated, even if the new value is equal to the previous one. - public MemberGraphNodeBinding([NotNull] IMemberNode node, string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, IUndoRedoService actionService, bool notifyChangesOnly = true) - : base(node, propertyName, propertyChanging, propertyChanged, x => x, actionService, notifyChangesOnly) - { - } - - /// - /// Gets or sets the current node value. - /// - /// The setter of this property will invoke the delegates passed to the constructor of this instance if the new value is different from the previous one. - public TContentType Value { get => GetNodeValue(); set => SetNodeValue(value); } } + + /// + /// Gets or sets the current node value. + /// + /// The setter of this property will invoke the delegates passed to the constructor of this instance if the new value is different from the previous one. + public TContentType Value { get => GetNodeValue(); set => SetNodeValue(value); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/ObjectGraphNodeBinding.cs b/sources/presentation/Stride.Core.Presentation.Quantum/ObjectGraphNodeBinding.cs index 47f4626370..a76f0a185f 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/ObjectGraphNodeBinding.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/ObjectGraphNodeBinding.cs @@ -1,80 +1,80 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using Stride.Core.Annotations; + using Stride.Core.Presentation.Services; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum +namespace Stride.Core.Presentation.Quantum; + +/// +/// This class allows to bind a property of a view model to an and properly trigger property change notifications +/// when the node value is modified. +/// +/// The type of property bound to the graph node. +/// The type of content in the graph node. +public class ObjectGraphNodeBinding : GraphNodeBinding { - /// - /// This class allows to bind a property of a view model to an and properly trigger property change notifications - /// when the node value is modified. - /// - /// The type of property bound to the graph node. - /// The type of content in the graph node. - public class ObjectGraphNodeBinding : GraphNodeBinding + protected IObjectNode Node; + + public ObjectGraphNodeBinding(IObjectNode node, string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, Func converter, IUndoRedoService actionService, bool notifyChangesOnly = true) + : base(propertyName, propertyChanging, propertyChanged, converter, actionService, notifyChangesOnly) { - protected IObjectNode Node; + Node = node; + node.ItemChanged += ValueChanged; + node.ItemChanging += ValueChanging; + } - public ObjectGraphNodeBinding([NotNull] IObjectNode node, string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, [NotNull] Func converter, IUndoRedoService actionService, bool notifyChangesOnly = true) - : base(propertyName, propertyChanging, propertyChanged, converter, actionService, notifyChangesOnly) - { - Node = node; - node.ItemChanged += ValueChanged; - node.ItemChanging += ValueChanging; - } + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); - public override void Dispose() + if (disposing) { - base.Dispose(); Node.ItemChanged -= ValueChanged; Node.ItemChanging -= ValueChanging; } + } - public override TContentType GetNodeValue() - { - var value = (TContentType)Node.Retrieve(); - return value; - } + public override TContentType GetNodeValue() + { + var value = (TContentType)Node.Retrieve(); + return value; + } - public override void SetNodeValue(TTargetType value) - { - using (var transaction = ActionService?.CreateTransaction()) - { - Node.Update(Converter(value), NodeIndex.Empty); - ActionService?.SetName(transaction, $"Update property {PropertyName}"); - } - } + public override void SetNodeValue(TTargetType value) + { + using var transaction = ActionService?.CreateTransaction(); + Node.Update(Converter(value), NodeIndex.Empty); + ActionService?.SetName(transaction!, $"Update property {PropertyName}"); } +} +/// +/// This is a specialization of the class, when the target type is the same that the +/// content type. +/// This class allows to bind a property of a view model to a and properly trigger property change notifications +/// when the node value is modified. +/// +/// The type of the node content and the property bound to the graph node. +public class ObjectGraphNodeBinding : ObjectGraphNodeBinding +{ /// - /// This is a specialization of the class, when the target type is the same that the - /// content type. - /// This class allows to bind a property of a view model to a and properly trigger property change notifications - /// when the node value is modified. + /// Initializes a new instance of the class. /// - /// The type of the node content and the property bound to the graph node. - public class ObjectGraphNodeBinding : ObjectGraphNodeBinding + /// The graph node bound to this instance. + /// The name of the property of the view model that is bound to this instance. + /// The delegate to invoke when the node content is about to change. + /// The delegate to invoke when the node content has changed. + /// + /// If True, delegates will be invoked only if the content of the node has actually changed. Otherwise, they will be invoked every time the node is updated, even if the new value is equal to the previous one. + public ObjectGraphNodeBinding(IObjectNode node, string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, IUndoRedoService actionService, bool notifyChangesOnly = true) + : base(node, propertyName, propertyChanging, propertyChanged, x => x, actionService, notifyChangesOnly) { - /// - /// Initializes a new instance of the class. - /// - /// The graph node bound to this instance. - /// The name of the property of the view model that is bound to this instance. - /// The delegate to invoke when the node content is about to change. - /// The delegate to invoke when the node content has changed. - /// - /// If True, delegates will be invoked only if the content of the node has actually changed. Otherwise, they will be invoked every time the node is updated, even if the new value is equal to the previous one. - public ObjectGraphNodeBinding([NotNull] IObjectNode node, string propertyName, PropertyChangeDelegate propertyChanging, PropertyChangeDelegate propertyChanged, IUndoRedoService actionService, bool notifyChangesOnly = true) - : base(node, propertyName, propertyChanging, propertyChanged, x => x, actionService, notifyChangesOnly) - { - } - - /// - /// Gets or sets the current node value. - /// - /// The setter of this property will invoke the delegates passed to the constructor of this instance if the new value is different from the previous one. - public TContentType Value { get => GetNodeValue(); set => SetNodeValue(value); } } + + /// + /// Gets or sets the current node value. + /// + /// The setter of this property will invoke the delegates passed to the constructor of this instance if the new value is different from the previous one. + public TContentType Value { get => GetNodeValue(); set => SetNodeValue(value); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/AnonymousNodePresenterCommand.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/AnonymousNodePresenterCommand.cs index 714181bfee..69c006ecfe 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/AnonymousNodePresenterCommand.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/AnonymousNodePresenterCommand.cs @@ -1,38 +1,34 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Threading.Tasks; -using Stride.Core.Annotations; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public class AnonymousNodePresenterCommand : NodePresenterCommandBase { - public class AnonymousNodePresenterCommand : NodePresenterCommandBase - { - private readonly Func execute; - private readonly Func canAttach; + private readonly Func execute; + private readonly Func? canAttach; - public AnonymousNodePresenterCommand([NotNull] string name, [NotNull] Func execute, [CanBeNull] Func canAttach = null) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (execute == null) throw new ArgumentNullException(nameof(execute)); - this.execute = execute; - this.canAttach = canAttach; - Name = name; - } + public AnonymousNodePresenterCommand(string name, Func execute, Func? canAttach = null) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(execute); + this.execute = execute; + this.canAttach = canAttach; + Name = name; + } - /// - public override string Name { get; } + /// + public override string Name { get; } - /// - public override bool CanAttach(INodePresenter nodePresenter) - { - return canAttach?.Invoke(nodePresenter) ?? true; - } + /// + public override bool CanAttach(INodePresenter nodePresenter) + { + return canAttach?.Invoke(nodePresenter) ?? true; + } - /// - public override Task Execute(INodePresenter nodePresenter, object parameter, object preExecuteResult) - { - return execute(nodePresenter, parameter); - } + /// + public override Task Execute(INodePresenter nodePresenter, object? parameter, object? preExecuteResult) + { + return execute(nodePresenter, parameter); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/IInitializingNodePresenter.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/IInitializingNodePresenter.cs index cd5b1ed553..8fb8f673e8 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/IInitializingNodePresenter.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/IInitializingNodePresenter.cs @@ -1,14 +1,11 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public interface IInitializingNodePresenter : INodePresenter { - public interface IInitializingNodePresenter : INodePresenter - { - void AddChild(IInitializingNodePresenter child); + void AddChild(IInitializingNodePresenter child); - void FinalizeInitialization(); - } + void FinalizeInitialization(); } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenter.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenter.cs index 3eba0f766e..2f1a25df4d 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenter.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenter.cs @@ -1,87 +1,78 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core; -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public interface INodePresenter : IDisposable { - public interface INodePresenter : IDisposable - { - [NotNull] - INodePresenter this[string childName] { get; } + INodePresenter this[string childName] { get; } - string DisplayName { get; set; } + string DisplayName { get; set; } - string Name { get; } + string Name { get; } - [NotNull] - INodePresenter Root { get; } + INodePresenter Root { get; } - [CanBeNull] - INodePresenter Parent { get; } + INodePresenter? Parent { get; } - IReadOnlyList Children { get; } + IReadOnlyList Children { get; } - List Commands { get; } + List Commands { get; } - PropertyContainerClass AttachedProperties { get; } + PropertyContainerClass AttachedProperties { get; } - [NotNull] - Type Type { get; } + Type Type { get; } - bool IsEnumerable { get; } + bool IsEnumerable { get; } - bool IsReadOnly { get; set; } + bool IsReadOnly { get; set; } - bool IsVisible { get; set; } + bool IsVisible { get; set; } - NodeIndex Index { get; } + NodeIndex Index { get; } - ITypeDescriptor Descriptor { get; } + ITypeDescriptor? Descriptor { get; } - int? Order { get; set; } + int? Order { get; set; } - object Value { get; } + object Value { get; } - string CombineKey { get; set; } + string CombineKey { get; set; } - IPropertyProviderViewModel PropertyProvider { get; } + IPropertyProviderViewModel? PropertyProvider { get; } - INodePresenterFactory Factory { get; } + INodePresenterFactory Factory { get; } - event EventHandler ValueChanging; + event EventHandler? ValueChanging; - event EventHandler ValueChanged; + event EventHandler? ValueChanged; - void UpdateValue(object newValue); + void UpdateValue(object newValue); - void AddItem(object value); + void AddItem(object value); - void AddItem(object value, NodeIndex index); + void AddItem(object value, NodeIndex index); - void RemoveItem(object value, NodeIndex index); + void RemoveItem(object value, NodeIndex index); - // TODO: this should probably be removed, UpdateValue should be called on the corresponding child node presenter itself + // TODO: this should probably be removed, UpdateValue should be called on the corresponding child node presenter itself - NodeAccessor GetNodeAccessor(); + NodeAccessor GetNodeAccessor(); - /// - /// Adds a dependency to the given node. - /// - /// The node that should be a dependency of this node. - /// If true, this node will also be refreshed when one of the child node of the dependency node changes. - /// A node that is a dependency to this node will trigger a refresh of this node each time its value is modified (or the value of one of its parent). - void AddDependency(INodePresenter node, bool refreshOnNestedNodeChanges); + /// + /// Adds a dependency to the given node. + /// + /// The node that should be a dependency of this node. + /// If true, this node will also be refreshed when one of the child node of the dependency node changes. + /// A node that is a dependency to this node will trigger a refresh of this node each time its value is modified (or the value of one of its parent). + void AddDependency(INodePresenter node, bool refreshOnNestedNodeChanges); - void ChangeParent([NotNull] INodePresenter newParent); + void ChangeParent(INodePresenter newParent); - void Rename(string newName, bool overwriteCombineKey = true); + void Rename(string newName, bool overwriteCombineKey = true); - [CanBeNull] - INodePresenter TryGetChild(string childName); - } + INodePresenter? TryGetChild(string childName); } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterCommand.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterCommand.cs index 530b2af26b..44f19792c6 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterCommand.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterCommand.cs @@ -1,27 +1,21 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Stride.Core.Annotations; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public interface INodePresenterCommand { - public interface INodePresenterCommand - { - [NotNull] - string Name { get; } + string Name { get; } - CombineMode CombineMode { get; } + CombineMode CombineMode { get; } - bool CanAttach([NotNull] INodePresenter nodePresenter); + bool CanAttach(INodePresenter nodePresenter); - bool CanExecute([NotNull] IReadOnlyCollection nodePresenters, object parameter); + bool CanExecute(IReadOnlyCollection nodePresenters, object? parameter); - Task PreExecute([NotNull] IReadOnlyCollection nodePresenters, object parameter); + Task PreExecute(IReadOnlyCollection nodePresenters, object? parameter); - Task Execute([NotNull] INodePresenter nodePresenter, object parameter, object preExecuteResult); + Task Execute(INodePresenter nodePresenter, object? parameter, object? preExecuteResult); - Task PostExecute([NotNull] IReadOnlyCollection nodePresenters, object parameter); - } + Task PostExecute(IReadOnlyCollection nodePresenters, object? parameter); } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterFactory.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterFactory.cs index 8378d52803..9f948b96f9 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterFactory.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterFactory.cs @@ -1,16 +1,13 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public interface INodePresenterFactory { - public interface INodePresenterFactory - { - INodePresenter CreateNodeHierarchy([NotNull] IObjectNode rootNode, [NotNull] GraphNodePath rootNodePath, [CanBeNull] IPropertyProviderViewModel propertyProvider = null); + INodePresenter CreateNodeHierarchy(IObjectNode rootNode, GraphNodePath rootNodePath, IPropertyProviderViewModel? propertyProvider = null); - bool IsPrimitiveType(Type type); - } + bool IsPrimitiveType(Type type); } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterFactoryInternal.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterFactoryInternal.cs index 884a36a7aa..10f37f9481 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterFactoryInternal.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterFactoryInternal.cs @@ -1,16 +1,13 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public interface INodePresenterFactoryInternal : INodePresenterFactory { - public interface INodePresenterFactoryInternal : INodePresenterFactory - { - IReadOnlyCollection AvailableCommands { get; } + IReadOnlyCollection AvailableCommands { get; } - void CreateChildren([NotNull] IInitializingNodePresenter parentPresenter, IObjectNode objectNode, [CanBeNull] IPropertyProviderViewModel propertyProvider); - } + void CreateChildren(IInitializingNodePresenter parentPresenter, IObjectNode? objectNode, IPropertyProviderViewModel? propertyProvider); } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterUpdater.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterUpdater.cs index 55861c8c1d..3b219b0acd 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterUpdater.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/INodePresenterUpdater.cs @@ -1,28 +1,26 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Stride.Core.Annotations; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +/// +/// An object that will customize an instance of . +/// +public interface INodePresenterUpdater { /// - /// An object that will customize an instance of . + /// Updates the given node if required. This method is called once all the children of the node have been created, + /// but its siblings and the siblings of its parent are not guaranteed to exist. This method is also called again + /// when the node is being refreshed, after its value has changed. /// - public interface INodePresenterUpdater - { - /// - /// Updates the given node if required. This method is called once all the children of the node have been created, - /// but its siblings and the siblings of its parent are not guaranteed to exist. This method is also called again - /// when the node is being refreshed, after its value has changed. - /// - /// The node to update. - void UpdateNode([NotNull] INodePresenter node); + /// The node to update. + void UpdateNode(INodePresenter node); - /// - /// Finalizes the tree of node presenters. This method is called once the full tree has been generated, when - /// any of its node has been refreshed, or when a virtual node has been added. - /// - /// The root of of the tree. - /// This method should not be used to modify the hierarchy. - void FinalizeTree([NotNull] INodePresenter root); - } + /// + /// Finalizes the tree of node presenters. This method is called once the full tree has been generated, when + /// any of its node has been refreshed, or when a virtual node has been added. + /// + /// The root of the tree. + /// This method should not be used to modify the hierarchy. + void FinalizeTree(INodePresenter root); } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ItemNodePresenter.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ItemNodePresenter.cs index 376f87c6a0..fefbf0948f 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ItemNodePresenter.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ItemNodePresenter.cs @@ -1,168 +1,164 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Extensions; using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public class ItemNodePresenter : NodePresenterBase { - public class ItemNodePresenter : NodePresenterBase - { - protected readonly IObjectNode Container; + protected readonly IObjectNode Container; - public ItemNodePresenter([NotNull] INodePresenterFactoryInternal factory, IPropertyProviderViewModel propertyProvider, [NotNull] INodePresenter parent, [NotNull] IObjectNode container, NodeIndex index) - : base(factory, propertyProvider, parent) + public ItemNodePresenter(INodePresenterFactoryInternal factory, IPropertyProviderViewModel? propertyProvider, INodePresenter parent, IObjectNode container, NodeIndex index) + : base(factory, propertyProvider, parent) + { + ArgumentNullException.ThrowIfNull(factory); + ArgumentNullException.ThrowIfNull(parent); + Container = container ?? throw new ArgumentNullException(nameof(container)); + Descriptor = TypeDescriptorFactory.Default.Find(container.Descriptor.GetInnerCollectionType()); + OwnerCollection = parent; + if (container.Descriptor is CollectionDescriptor collectionDescriptor) + { + Type = collectionDescriptor.ElementType; + } + else if (container.Descriptor is DictionaryDescriptor dictionaryDescriptor) + { + Type = dictionaryDescriptor.ValueType; + } + else if (container.Descriptor is ArrayDescriptor arrayDescriptor) { - if (factory == null) throw new ArgumentNullException(nameof(factory)); - if (parent == null) throw new ArgumentNullException(nameof(parent)); - Container = container ?? throw new ArgumentNullException(nameof(container)); - Descriptor = TypeDescriptorFactory.Default.Find(container.Descriptor.GetInnerCollectionType()); - OwnerCollection = parent; - if (container.Descriptor is CollectionDescriptor collectionDescriptor) - { - Type = collectionDescriptor.ElementType; - } - else if (container.Descriptor is DictionaryDescriptor dictionaryDescriptor) - { - Type = dictionaryDescriptor.ValueType; - } - else if (container.Descriptor is ArrayDescriptor arrayDescriptor) - { - Type = arrayDescriptor.ElementType; - } - Index = index; - Name = index.ToString(); - Order = index.IsInt ? (int?)index.Int : null; // So items are sorted by index instead of string - CombineKey = Name; - DisplayName = (container.Descriptor.Category != DescriptorCategory.Dictionary && Index.IsInt) - ? "Item " + Index - : Index.ToString(); - - container.ItemChanging += OnItemChanging; - container.ItemChanged += OnItemChanged; - AttachCommands(); + Type = arrayDescriptor.ElementType; } + Index = index; + Name = index.ToString(); + Order = index.IsInt ? (int?)index.Int : null; // So items are sorted by index instead of string + CombineKey = Name; + DisplayName = (container.Descriptor.Category != DescriptorCategory.Dictionary && Index.IsInt) + ? "Item " + Index + : Index.ToString(); + + container.ItemChanging += OnItemChanging; + container.ItemChanged += OnItemChanged; + AttachCommands(); + } - public override void Dispose() + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) { - base.Dispose(); Container.ItemChanging -= OnItemChanging; Container.ItemChanged -= OnItemChanged; } + } - public INodePresenter OwnerCollection { get; } + public INodePresenter OwnerCollection { get; } - public sealed override NodeIndex Index { get; } + public sealed override NodeIndex Index { get; } - public override Type Type { get; } + public override Type Type { get; } - public override bool IsEnumerable => Container.IsEnumerable; + public override bool IsEnumerable => Container.IsEnumerable; - public override ITypeDescriptor Descriptor { get; } + public override ITypeDescriptor? Descriptor { get; } - public override object Value => Container.Retrieve(Index); + public override object Value => Container.Retrieve(Index); - protected override IObjectNode ParentingNode => Container.ItemReferences != null ? Container.IndexedTarget(Index) : null; + protected override IObjectNode? ParentingNode => Container.ItemReferences != null ? Container.IndexedTarget(Index) : null; - public override void UpdateValue(object newValue) + public override void UpdateValue(object newValue) + { + try { - try - { - Container.Update(newValue, Index); - } - catch (Exception e) - { - throw new NodePresenterException("An error occurred while updating the value of the node, see the inner exception for more information.", e); - } + Container.Update(newValue, Index); } - - public override void AddItem(object value) + catch (Exception e) { - if (Container.IndexedTarget(Index) == null || !Container.IndexedTarget(Index).IsEnumerable) - throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); - - try - { - Container.IndexedTarget(Index).Add(value); - } - catch (Exception e) - { - throw new NodePresenterException("An error occurred while adding an item to the node, see the inner exception for more information.", e); - } + throw new NodePresenterException("An error occurred while updating the value of the node, see the inner exception for more information.", e); } + } + + public override void AddItem(object value) + { + if (Container.IndexedTarget(Index)?.IsEnumerable != true) + throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); - public override void AddItem(object value, NodeIndex index) + try { - if (Container.IndexedTarget(Index) == null || !Container.IndexedTarget(Index).IsEnumerable) - throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); - - try - { - Container.IndexedTarget(Index).Add(value, index); - } - catch (Exception e) - { - throw new NodePresenterException("An error occurred while adding an item to the node, see the inner exception for more information.", e); - } + Container.IndexedTarget(Index).Add(value); } - - public override void RemoveItem(object value, NodeIndex index) + catch (Exception e) { - if (Container.IndexedTarget(Index) == null || !Container.IndexedTarget(Index).IsEnumerable) - throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); - - try - { - Container.IndexedTarget(Index).Remove(value, index); - } - catch (Exception e) - { - throw new NodePresenterException("An error occurred while removing an item to the node, see the inner exception for more information.", e); - } + throw new NodePresenterException("An error occurred while adding an item to the node, see the inner exception for more information.", e); } + } + + public override void AddItem(object value, NodeIndex index) + { + if (Container.IndexedTarget(Index)?.IsEnumerable != true) + throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); - public override NodeAccessor GetNodeAccessor() + try { - return new NodeAccessor(Container, Index); + Container.IndexedTarget(Index).Add(value, index); } - - private void OnItemChanging(object sender, [NotNull] ItemChangeEventArgs e) + catch (Exception e) { - if (IsValidChange(e)) - RaiseValueChanging(e.NewValue); + throw new NodePresenterException("An error occurred while adding an item to the node, see the inner exception for more information.", e); } + } - private void OnItemChanged(object sender, [NotNull] ItemChangeEventArgs e) + public override void RemoveItem(object value, NodeIndex index) + { + if (Container.IndexedTarget(Index)?.IsEnumerable != true) + throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); + + try { - if (IsValidChange(e)) - { - Refresh(); - RaiseValueChanged(e.OldValue); - } + Container.IndexedTarget(Index).Remove(value, index); } - - private bool IsValidChange([NotNull] ItemChangeEventArgs e) + catch (Exception e) { - return IsValidChange(e.ChangeType, e.Index, Index); + throw new NodePresenterException("An error occurred while removing an item to the node, see the inner exception for more information.", e); } + } + + public override NodeAccessor GetNodeAccessor() + { + return new NodeAccessor(Container, Index); + } + + private void OnItemChanging(object? sender, ItemChangeEventArgs e) + { + if (IsValidChange(e)) + RaiseValueChanging(e.NewValue); + } - public static bool IsValidChange(ContentChangeType changeType, NodeIndex changeIndex, NodeIndex presenterIndex) + private void OnItemChanged(object? sender, ItemChangeEventArgs e) + { + if (IsValidChange(e)) { - // We should care only if the change is an update at the same index. - // Other scenarios (add, remove) are handled by the parent node. - switch (changeType) - { - case ContentChangeType.CollectionUpdate: - return Equals(changeIndex, presenterIndex); - case ContentChangeType.CollectionAdd: - case ContentChangeType.CollectionRemove: - return false; - default: - throw new ArgumentOutOfRangeException(); - } + Refresh(); + RaiseValueChanged(e.OldValue); } } + + private bool IsValidChange(ItemChangeEventArgs e) + { + return IsValidChange(e.ChangeType, e.Index, Index); + } + + public static bool IsValidChange(ContentChangeType changeType, NodeIndex changeIndex, NodeIndex presenterIndex) + { + // We should care only if the change is an update at the same index. + // Other scenarios (add, remove) are handled by the parent node. + return changeType switch + { + ContentChangeType.CollectionUpdate => Equals(changeIndex, presenterIndex), + ContentChangeType.CollectionAdd or ContentChangeType.CollectionRemove => false, + _ => throw new ArgumentOutOfRangeException(nameof(changeType)), + }; + } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/MemberNodePresenter.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/MemberNodePresenter.cs index f91d8b8127..bf1df837d7 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/MemberNodePresenter.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/MemberNodePresenter.cs @@ -1,49 +1,48 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core; -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public class MemberNodePresenter : NodePresenterBase { - public class MemberNodePresenter : NodePresenterBase - { - protected readonly IMemberNode Member; - private readonly List memberAttributes = new List(); + protected readonly IMemberNode Member; + private readonly List memberAttributes = []; - public MemberNodePresenter([NotNull] INodePresenterFactoryInternal factory, IPropertyProviderViewModel propertyProvider, [NotNull] INodePresenter parent, [NotNull] IMemberNode member) - : base(factory, propertyProvider, parent) + public MemberNodePresenter(INodePresenterFactoryInternal factory, IPropertyProviderViewModel? propertyProvider, INodePresenter parent, IMemberNode member) + : base(factory, propertyProvider, parent) + { + ArgumentNullException.ThrowIfNull(factory); + ArgumentNullException.ThrowIfNull(parent); + Member = member ?? throw new ArgumentNullException(nameof(member)); + Name = member.Name; + CombineKey = Name; + DisplayName = Name; + IsReadOnly = !Member.MemberDescriptor.HasSet; + memberAttributes.AddRange(TypeDescriptorFactory.Default.AttributeRegistry.GetAttributes(member.MemberDescriptor.MemberInfo)); + + member.ValueChanging += OnMemberChanging; + member.ValueChanged += OnMemberChanged; + + if (member.Target != null) { - if (factory == null) throw new ArgumentNullException(nameof(factory)); - if (parent == null) throw new ArgumentNullException(nameof(parent)); - Member = member ?? throw new ArgumentNullException(nameof(member)); - Name = member.Name; - CombineKey = Name; - DisplayName = Name; - IsReadOnly = !Member.MemberDescriptor.HasSet; - memberAttributes.AddRange(TypeDescriptorFactory.Default.AttributeRegistry.GetAttributes(member.MemberDescriptor.MemberInfo)); - - member.ValueChanging += OnMemberChanging; - member.ValueChanged += OnMemberChanged; - - if (member.Target != null) - { - member.Target.ItemChanging += OnItemChanging; - member.Target.ItemChanged += OnItemChanged; - } - var displayAttribute = memberAttributes.OfType().FirstOrDefault(); - Order = displayAttribute?.Order ?? member.MemberDescriptor.Order; - - AttachCommands(); + member.Target.ItemChanging += OnItemChanging; + member.Target.ItemChanged += OnItemChanged; } + var displayAttribute = memberAttributes.OfType().FirstOrDefault(); + Order = displayAttribute?.Order ?? member.MemberDescriptor.Order; + + AttachCommands(); + } - public override void Dispose() + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (disposing) { - base.Dispose(); Member.ValueChanging -= OnMemberChanging; Member.ValueChanged -= OnMemberChanged; if (Member.Target != null) @@ -52,122 +51,125 @@ public override void Dispose() Member.Target.ItemChanged -= OnItemChanged; } } + } - public override Type Type => Member.Type; + public override Type Type => Member.Type; - public override bool IsEnumerable => Member.Target?.IsEnumerable ?? false; + public override bool IsEnumerable => Member.Target?.IsEnumerable ?? false; - public override NodeIndex Index => NodeIndex.Empty; + public override NodeIndex Index => NodeIndex.Empty; - [NotNull] - public override ITypeDescriptor Descriptor => Member.Descriptor; + public override ITypeDescriptor Descriptor => Member.Descriptor; - public override object Value => Member.Retrieve(); + public override object Value => Member.Retrieve(); - [NotNull] - public IMemberDescriptor MemberDescriptor => Member.MemberDescriptor; + public IMemberDescriptor MemberDescriptor => Member.MemberDescriptor; - public IReadOnlyList MemberAttributes => memberAttributes; + public IReadOnlyList MemberAttributes => memberAttributes; - protected override IObjectNode ParentingNode => Member.Target; + protected override IObjectNode ParentingNode => Member.Target; - public override void UpdateValue(object newValue) + public override void UpdateValue(object newValue) + { + // Do not update member node presenter value to null if it does not allow null values (related to issue #668). + // FIXME With the obsoleting of Stride.Core.Annotations.NotNullAttribute, it might become partially broken. + // Non-null members are no-longer annotated. + // + // What are the failing use cases? Should we just check for value types here? + // We could also decide to keep (non obsolete) NotNullAttribute just for that purpose. + // For now, check for our NotNullAttribute as well as from CodeAnalysis + if ((newValue == null) && memberAttributes.Any(x => x is Annotations.NotNullAttribute or System.Diagnostics.CodeAnalysis.NotNullAttribute)) + return; + + try { - // Do not update member node presenter value to null if it does not - // allow null values (related to issue #668) - if ((newValue == null) && (memberAttributes.Any(x => x is NotNullAttribute))) - return; - - try - { - Member.Update(newValue); - } - catch (Exception e) - { - throw new NodePresenterException("An error occurred while updating the value of the node, see the inner exception for more information.", e); - } + Member.Update(newValue); } - - public override void AddItem(object value) + catch (Exception e) { - if (Member.Target == null || !Member.Target.IsEnumerable) - throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); - - try - { - Member.Target.Add(value); - } - catch (Exception e) - { - throw new NodePresenterException("An error occurred while adding an item to the node, see the inner exception for more information.", e); - } + throw new NodePresenterException("An error occurred while updating the value of the node, see the inner exception for more information.", e); } + } - public override void AddItem(object value, NodeIndex index) - { - if (Member.Target == null || !Member.Target.IsEnumerable) - throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); + public override void AddItem(object value) + { + if (Member.Target == null || !Member.Target.IsEnumerable) + throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); - try - { - Member.Target.Add(value, index); - } - catch (Exception e) - { - throw new NodePresenterException("An error occurred while adding an item to the node, see the inner exception for more information.", e); - } + try + { + Member.Target.Add(value); } - - public override void RemoveItem(object value, NodeIndex index) + catch (Exception e) { - if (Member.Target == null || !Member.Target.IsEnumerable) - throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(RemoveItem)} cannot be invoked on members that are not collection."); - - try - { - Member.Target.Remove(value, index); - } - catch (Exception e) - { - throw new NodePresenterException("An error occurred while removing an item to the node, see the inner exception for more information.", e); - } + throw new NodePresenterException("An error occurred while adding an item to the node, see the inner exception for more information.", e); } + } - public override NodeAccessor GetNodeAccessor() + public override void AddItem(object value, NodeIndex index) + { + if (Member.Target == null || !Member.Target.IsEnumerable) + throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(AddItem)} cannot be invoked on members that are not collection."); + + try { - return new NodeAccessor(Member, NodeIndex.Empty); + Member.Target.Add(value, index); } - - private void OnMemberChanging(object sender, MemberNodeChangeEventArgs e) + catch (Exception e) { - RaiseValueChanging(Value); - if (Member.Target != null) - { - Member.Target.ItemChanging -= OnItemChanging; - Member.Target.ItemChanged -= OnItemChanged; - } + throw new NodePresenterException("An error occurred while adding an item to the node, see the inner exception for more information.", e); } + } + + public override void RemoveItem(object value, NodeIndex index) + { + if (Member.Target == null || !Member.Target.IsEnumerable) + throw new NodePresenterException($"{nameof(MemberNodePresenter)}.{nameof(RemoveItem)} cannot be invoked on members that are not collection."); - private void OnMemberChanged(object sender, MemberNodeChangeEventArgs e) + try { - Refresh(); - if (Member.Target != null) - { - Member.Target.ItemChanging += OnItemChanging; - Member.Target.ItemChanged += OnItemChanged; - } - RaiseValueChanged(Value); + Member.Target.Remove(value, index); } + catch (Exception e) + { + throw new NodePresenterException("An error occurred while removing an item to the node, see the inner exception for more information.", e); + } + } - private void OnItemChanging(object sender, ItemChangeEventArgs e) + public override NodeAccessor GetNodeAccessor() + { + return new NodeAccessor(Member, NodeIndex.Empty); + } + + private void OnMemberChanging(object? sender, MemberNodeChangeEventArgs e) + { + RaiseValueChanging(Value); + if (Member.Target != null) { - RaiseValueChanging(Value); + Member.Target.ItemChanging -= OnItemChanging; + Member.Target.ItemChanged -= OnItemChanged; } + } - private void OnItemChanged(object sender, ItemChangeEventArgs e) + private void OnMemberChanged(object? sender, MemberNodeChangeEventArgs e) + { + Refresh(); + if (Member.Target != null) { - Refresh(); - RaiseValueChanged(Value); + Member.Target.ItemChanging += OnItemChanging; + Member.Target.ItemChanged += OnItemChanged; } + RaiseValueChanged(Value); + } + + private void OnItemChanging(object? sender, ItemChangeEventArgs e) + { + RaiseValueChanging(Value); + } + + private void OnItemChanged(object? sender, ItemChangeEventArgs e) + { + Refresh(); + RaiseValueChanged(Value); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterBase.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterBase.cs index 9c3bd12704..3ed669fb30 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterBase.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterBase.cs @@ -1,30 +1,34 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core; -using Stride.Core.Annotations; + using Stride.Core.Extensions; using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public abstract class NodePresenterBase : IInitializingNodePresenter { - public abstract class NodePresenterBase : IInitializingNodePresenter + private readonly INodePresenterFactoryInternal factory; + private readonly List children = []; + private HashSet? dependencies; + + protected NodePresenterBase(INodePresenterFactoryInternal factory, IPropertyProviderViewModel? propertyProvider, INodePresenter? parent) { - private readonly INodePresenterFactoryInternal factory; - private readonly List children = new List(); - private HashSet dependencies; + this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); + Parent = parent; + PropertyProvider = propertyProvider; + } - protected NodePresenterBase([NotNull] INodePresenterFactoryInternal factory, [CanBeNull] IPropertyProviderViewModel propertyProvider, [CanBeNull] INodePresenter parent) - { - this.factory = factory ?? throw new ArgumentNullException(nameof(factory)); - Parent = parent; - PropertyProvider = propertyProvider; - } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } - public virtual void Dispose() + protected virtual void Dispose(bool disposing) + { + if (disposing) { if (dependencies != null) { @@ -34,152 +38,151 @@ public virtual void Dispose() } } } + } - public INodePresenter this[string childName] => TryGetChild(childName) ?? throw new KeyNotFoundException($"Key {childName} not found in {nameof(INodePresenter)}"); - - public INodePresenter Root => Parent?.Root ?? this; + public INodePresenter this[string childName] => TryGetChild(childName) ?? throw new KeyNotFoundException($"Key {childName} not found in {nameof(INodePresenter)}"); - public INodePresenter Parent { get; private set; } + public INodePresenter Root => Parent?.Root ?? this; - public IReadOnlyList Children => children; + public INodePresenter? Parent { get; private set; } - public string DisplayName { get; set; } + public IReadOnlyList Children => children; - public string Name { get; protected set; } + public string DisplayName { get; set; } - public List Commands { get; } = new List(); + public string Name { get; protected set; } - public abstract Type Type { get; } + public List Commands { get; } = []; - public abstract bool IsEnumerable { get; } + public abstract Type Type { get; } - public bool IsVisible { get; set; } = true; + public abstract bool IsEnumerable { get; } - public bool IsReadOnly { get; set; } + public bool IsVisible { get; set; } = true; - public int? Order { get; set; } + public bool IsReadOnly { get; set; } - public abstract NodeIndex Index { get; } + public int? Order { get; set; } - public abstract ITypeDescriptor Descriptor { get; } + public abstract NodeIndex Index { get; } - public abstract object Value { get; } + public abstract ITypeDescriptor? Descriptor { get; } - public string CombineKey { get; set; } + public abstract object Value { get; } - public PropertyContainerClass AttachedProperties { get; } = new PropertyContainerClass(); + public string CombineKey { get; set; } - public event EventHandler ValueChanging; + public PropertyContainerClass AttachedProperties { get; } = []; - public event EventHandler ValueChanged; + public event EventHandler? ValueChanging; - [CanBeNull] - protected abstract IObjectNode ParentingNode { get; } + public event EventHandler? ValueChanged; - public abstract void UpdateValue(object newValue); + protected abstract IObjectNode? ParentingNode { get; } - public abstract void AddItem(object value); + public abstract void UpdateValue(object newValue); - public abstract void AddItem(object value, NodeIndex index); + public abstract void AddItem(object value); - public abstract void RemoveItem(object value, NodeIndex index); + public abstract void AddItem(object value, NodeIndex index); - public abstract NodeAccessor GetNodeAccessor(); + public abstract void RemoveItem(object value, NodeIndex index); - public IPropertyProviderViewModel PropertyProvider { get; } + public abstract NodeAccessor GetNodeAccessor(); - public INodePresenterFactory Factory => factory; + public IPropertyProviderViewModel? PropertyProvider { get; } - public override string ToString() - { - return $"[{GetType().Name}] {Name} (Count = {Children.Count}"; - } + public INodePresenterFactory Factory => factory; - public void ChangeParent(INodePresenter newParent) - { - if (newParent == null) throw new ArgumentNullException(nameof(newParent)); + public override string ToString() + { + return $"[{GetType().Name}] {Name} (Count = {Children.Count}"; + } - var parent = (NodePresenterBase)Parent; - parent?.children.Remove(this); + public void ChangeParent(INodePresenter newParent) + { + ArgumentNullException.ThrowIfNull(newParent); - parent = (NodePresenterBase)newParent; - parent.children.Add(this); + var parent = (NodePresenterBase?)Parent; + parent?.children.Remove(this); - Parent = newParent; - } + parent = (NodePresenterBase)newParent; + parent.children.Add(this); - public void Rename(string newName, bool overwriteCombineKey = true) - { - Name = newName; - if (overwriteCombineKey) - { - CombineKey = newName; - } - } + Parent = newParent; + } - public INodePresenter TryGetChild(string childName) + public void Rename(string newName, bool overwriteCombineKey = true) + { + Name = newName; + if (overwriteCombineKey) { - return children.FirstOrDefault(x => string.Equals(x.Name, childName, StringComparison.Ordinal)); + CombineKey = newName; } + } - public void AddDependency([NotNull] INodePresenter node, bool refreshOnNestedNodeChanges) - { - if (node == null) throw new ArgumentNullException(nameof(node)); + public INodePresenter? TryGetChild(string childName) + { + return children.FirstOrDefault(x => string.Equals(x.Name, childName, StringComparison.Ordinal)); + } - dependencies = dependencies ?? new HashSet(); - if (dependencies.Add(node)) - { - node.ValueChanged += DependencyChanged; - } - } + public void AddDependency(INodePresenter node, bool refreshOnNestedNodeChanges) + { + ArgumentNullException.ThrowIfNull(node); - protected void Refresh() + dependencies ??= []; + if (dependencies.Add(node)) { - // Remove existing children and attached properties - foreach (var child in children.DepthFirst(x => x.Children)) - { - child.Dispose(); - } - children.Clear(); - AttachedProperties.Clear(); - - // And recompute them from the current value. - factory.CreateChildren(this, ParentingNode, PropertyProvider); + node.ValueChanged += DependencyChanged; } + } - protected void AttachCommands() + protected void Refresh() + { + // Remove existing children and attached properties + foreach (var child in children.DepthFirst(x => x.Children)) { - foreach (var command in factory.AvailableCommands) - { - if (command.CanAttach(this)) - Commands.Add(command); - } + child.Dispose(); } + children.Clear(); + AttachedProperties.Clear(); - protected void RaiseValueChanging(object newValue) - { - ValueChanging?.Invoke(this, new ValueChangingEventArgs(newValue)); - } + // And recompute them from the current value. + factory.CreateChildren(this, ParentingNode, PropertyProvider); + } - protected void RaiseValueChanged(object oldValue) + protected void AttachCommands() + { + foreach (var command in factory.AvailableCommands) { - ValueChanged?.Invoke(this, new ValueChangedEventArgs(oldValue)); + if (command.CanAttach(this)) + Commands.Add(command); } + } - private void DependencyChanged(object sender, ValueChangedEventArgs e) - { - RaiseValueChanging(Value); - Refresh(); - RaiseValueChanged(Value); - } + protected void RaiseValueChanging(object? newValue) + { + ValueChanging?.Invoke(this, new ValueChangingEventArgs(newValue)); + } - void IInitializingNodePresenter.AddChild([NotNull] IInitializingNodePresenter child) - { - children.Add(child); - } + protected void RaiseValueChanged(object? oldValue) + { + ValueChanged?.Invoke(this, new ValueChangedEventArgs(oldValue)); + } - void IInitializingNodePresenter.FinalizeInitialization() - { - } + private void DependencyChanged(object? sender, ValueChangedEventArgs e) + { + RaiseValueChanging(Value); + Refresh(); + RaiseValueChanged(Value); + } + + void IInitializingNodePresenter.AddChild(IInitializingNodePresenter child) + { + children.Add(child); + } + + void IInitializingNodePresenter.FinalizeInitialization() + { } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterCommandBase.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterCommandBase.cs index 5444b51bec..90fd4dae4c 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterCommandBase.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterCommandBase.cs @@ -1,37 +1,30 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Stride.Core.Annotations; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public abstract class NodePresenterCommandBase : INodePresenterCommand { - public abstract class NodePresenterCommandBase : INodePresenterCommand - { - public abstract string Name { get; } + public abstract string Name { get; } - public virtual CombineMode CombineMode => CombineMode.CombineOnlyForAll; + public virtual CombineMode CombineMode => CombineMode.CombineOnlyForAll; - public abstract bool CanAttach(INodePresenter nodePresenter); + public abstract bool CanAttach(INodePresenter nodePresenter); - public virtual bool CanExecute(IReadOnlyCollection nodePresenters, object parameter) - { - return true; - } + public virtual bool CanExecute(IReadOnlyCollection nodePresenters, object? parameter) + { + return true; + } - [NotNull] - public virtual Task PreExecute(IReadOnlyCollection nodePresenters, object parameter) - { - return Task.FromResult(null); - } + public virtual Task PreExecute(IReadOnlyCollection nodePresenters, object? parameter) + { + return Task.FromResult(null); + } - public abstract Task Execute(INodePresenter nodePresenter, object parameter, object preExecuteResult); + public abstract Task Execute(INodePresenter nodePresenter, object? parameter, object? preExecuteResult); - [NotNull] - public virtual Task PostExecute(IReadOnlyCollection nodePresenters, object parameter) - { - return Task.CompletedTask; - } + public virtual Task PostExecute(IReadOnlyCollection nodePresenters, object? parameter) + { + return Task.CompletedTask; } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterFactory.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterFactory.cs index 4a63e4a551..12a6c6a861 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterFactory.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/NodePresenterFactory.cs @@ -1,182 +1,174 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Threading; -using Stride.Core.Annotations; + using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public class NodePresenterFactory : INodePresenterFactoryInternal { - public class NodePresenterFactory : INodePresenterFactoryInternal + private readonly INodeBuilder nodeBuilder; + private readonly ThreadLocal buildingNodes = new(); + + public NodePresenterFactory(INodeBuilder nodeBuilder, IReadOnlyCollection availableCommands, IReadOnlyCollection availableUpdaters) { - [NotNull] private readonly INodeBuilder nodeBuilder; - [NotNull] private readonly ThreadLocal buildingNodes = new ThreadLocal(); + ArgumentNullException.ThrowIfNull(nodeBuilder); + this.nodeBuilder = nodeBuilder; + AvailableCommands = availableCommands ?? throw new ArgumentNullException(nameof(availableCommands)); + AvailableUpdaters = availableUpdaters ?? throw new ArgumentNullException(nameof(availableUpdaters)); + } - public NodePresenterFactory([NotNull] INodeBuilder nodeBuilder, [NotNull] IReadOnlyCollection availableCommands, [NotNull] IReadOnlyCollection availableUpdaters) - { - this.nodeBuilder = nodeBuilder; - if (nodeBuilder == null) throw new ArgumentNullException(nameof(nodeBuilder)); - AvailableCommands = availableCommands ?? throw new ArgumentNullException(nameof(availableCommands)); - AvailableUpdaters = availableUpdaters ?? throw new ArgumentNullException(nameof(availableUpdaters)); - } + public IReadOnlyCollection AvailableCommands { get; } - public IReadOnlyCollection AvailableCommands { get; } + public IReadOnlyCollection AvailableUpdaters { get; } - public IReadOnlyCollection AvailableUpdaters { get; } + public bool IsPrimitiveType(Type type) + { + return nodeBuilder.IsPrimitiveType(type); + } - public bool IsPrimitiveType(Type type) - { - return nodeBuilder.IsPrimitiveType(type); - } + public INodePresenter CreateNodeHierarchy(IObjectNode rootNode, GraphNodePath rootNodePath, IPropertyProviderViewModel? propertyProvider) + { + ArgumentNullException.ThrowIfNull(rootNode); + buildingNodes.Value = true; + var rootPresenter = CreateRootPresenter(propertyProvider, rootNode); + GenerateChildren(rootPresenter, rootNode, propertyProvider); + RunUpdaters(rootPresenter); + buildingNodes.Value = false; + FinalizeTree(rootPresenter); + return rootPresenter; + } - [NotNull] - public INodePresenter CreateNodeHierarchy(IObjectNode rootNode, GraphNodePath rootNodePath, IPropertyProviderViewModel propertyProvider) + public void CreateChildren(IInitializingNodePresenter parentPresenter, IObjectNode? objectNode, IPropertyProviderViewModel? propertyProvider) + { + buildingNodes.Value = true; + if (objectNode != null) { - if (rootNode == null) throw new ArgumentNullException(nameof(rootNode)); - buildingNodes.Value = true; - var rootPresenter = CreateRootPresenter(propertyProvider, rootNode); - GenerateChildren(rootPresenter, rootNode, propertyProvider); - RunUpdaters(rootPresenter); - buildingNodes.Value = false; - FinalizeTree(rootPresenter); - return rootPresenter; - } - - public void CreateChildren(IInitializingNodePresenter parentPresenter, IObjectNode objectNode, IPropertyProviderViewModel propertyProvider) - { - buildingNodes.Value = true; - if (objectNode != null) - { - GenerateChildren(parentPresenter, objectNode, propertyProvider); - } - RunUpdaters(parentPresenter); - buildingNodes.Value = false; - FinalizeTree(parentPresenter.Root); + GenerateChildren(parentPresenter, objectNode, propertyProvider); } + RunUpdaters(parentPresenter); + buildingNodes.Value = false; + FinalizeTree(parentPresenter.Root); + } - private void GenerateChildren([NotNull] IInitializingNodePresenter parentPresenter, [NotNull] IObjectNode objectNode, IPropertyProviderViewModel propertyProvider) - { - if (parentPresenter == null) throw new ArgumentNullException(nameof(parentPresenter)); - if (objectNode == null) throw new ArgumentNullException(nameof(objectNode)); - CreateMembers(propertyProvider, parentPresenter, objectNode); - CreateItems(propertyProvider, parentPresenter, objectNode); - parentPresenter.FinalizeInitialization(); - } + private void GenerateChildren(IInitializingNodePresenter parentPresenter, IObjectNode objectNode, IPropertyProviderViewModel? propertyProvider) + { + ArgumentNullException.ThrowIfNull(parentPresenter); + ArgumentNullException.ThrowIfNull(objectNode); + CreateMembers(propertyProvider, parentPresenter, objectNode); + CreateItems(propertyProvider, parentPresenter, objectNode); + parentPresenter.FinalizeInitialization(); + } - [NotNull] - protected virtual IInitializingNodePresenter CreateRootPresenter(IPropertyProviderViewModel propertyProvider, [NotNull] IObjectNode rootNode) - { - if (rootNode == null) throw new ArgumentNullException(nameof(rootNode)); - return new RootNodePresenter(this, propertyProvider, rootNode); - } + protected virtual IInitializingNodePresenter CreateRootPresenter(IPropertyProviderViewModel? propertyProvider, IObjectNode rootNode) + { + ArgumentNullException.ThrowIfNull(rootNode); + return new RootNodePresenter(this, propertyProvider, rootNode); + } - protected virtual bool ShouldCreateMemberPresenter([NotNull] INodePresenter parent, [NotNull] IMemberNode member, [CanBeNull] IPropertyProviderViewModel propertyProvider) - { - if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (member == null) throw new ArgumentNullException(nameof(member)); - // Ask the property provider if we have one, otherwise always construct. - return propertyProvider?.ShouldConstructMember(member) ?? true; - } + protected virtual bool ShouldCreateMemberPresenter(INodePresenter parent, IMemberNode member, IPropertyProviderViewModel? propertyProvider) + { + ArgumentNullException.ThrowIfNull(parent); + ArgumentNullException.ThrowIfNull(member); + // Ask the property provider if we have one, otherwise always construct. + return propertyProvider?.ShouldConstructMember(member) ?? true; + } - [NotNull] - protected virtual IInitializingNodePresenter CreateMember(IPropertyProviderViewModel propertyProvider, [NotNull] INodePresenter parentPresenter, [NotNull] IMemberNode member) - { - if (parentPresenter == null) throw new ArgumentNullException(nameof(parentPresenter)); - if (member == null) throw new ArgumentNullException(nameof(member)); - return new MemberNodePresenter(this, propertyProvider, parentPresenter, member); - } + protected virtual IInitializingNodePresenter CreateMember(IPropertyProviderViewModel? propertyProvider, INodePresenter parentPresenter, IMemberNode member) + { + ArgumentNullException.ThrowIfNull(parentPresenter); + ArgumentNullException.ThrowIfNull(member); + return new MemberNodePresenter(this, propertyProvider, parentPresenter, member); + } - protected virtual bool ShouldCreateItemPresenter([NotNull] INodePresenter parent, [NotNull] IObjectNode collectionNode, NodeIndex index, [CanBeNull] IPropertyProviderViewModel propertyProvider) - { - if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (collectionNode == null) throw new ArgumentNullException(nameof(collectionNode)); - // Ask the property provider if we have one, otherwise always construct. - return propertyProvider?.ShouldConstructItem(collectionNode, index) ?? true; - } + protected virtual bool ShouldCreateItemPresenter(INodePresenter parent, IObjectNode collectionNode, NodeIndex index, IPropertyProviderViewModel? propertyProvider) + { + ArgumentNullException.ThrowIfNull(parent); + ArgumentNullException.ThrowIfNull(collectionNode); + // Ask the property provider if we have one, otherwise always construct. + return propertyProvider?.ShouldConstructItem(collectionNode, index) ?? true; + } - [NotNull] - protected virtual IInitializingNodePresenter CreateItem(IPropertyProviderViewModel propertyProvider, [NotNull] INodePresenter containerPresenter, [NotNull] IObjectNode containerNode, NodeIndex index) - { - if (containerPresenter == null) throw new ArgumentNullException(nameof(containerPresenter)); - if (containerNode == null) throw new ArgumentNullException(nameof(containerNode)); - return new ItemNodePresenter(this, propertyProvider, containerPresenter, containerNode, index); - } + protected virtual IInitializingNodePresenter CreateItem(IPropertyProviderViewModel? propertyProvider, INodePresenter containerPresenter, IObjectNode containerNode, NodeIndex index) + { + ArgumentNullException.ThrowIfNull(containerPresenter); + ArgumentNullException.ThrowIfNull(containerNode); + return new ItemNodePresenter(this, propertyProvider, containerPresenter, containerNode, index); + } - private void CreateMembers(IPropertyProviderViewModel propertyProvider, IInitializingNodePresenter parentPresenter, [NotNull] IObjectNode objectNode) + private void CreateMembers(IPropertyProviderViewModel? propertyProvider, IInitializingNodePresenter parentPresenter, IObjectNode objectNode) + { + foreach (var member in objectNode.Members) { - foreach (var member in objectNode.Members) + if (ShouldCreateMemberPresenter(parentPresenter, member, propertyProvider)) { - if (ShouldCreateMemberPresenter(parentPresenter, member, propertyProvider)) + var memberPresenter = CreateMember(propertyProvider, parentPresenter, member); + if (member.Target != null) { - var memberPresenter = CreateMember(propertyProvider, parentPresenter, member); - if (member.Target != null) - { - GenerateChildren(memberPresenter, member.Target, propertyProvider); - } - parentPresenter.AddChild(memberPresenter); - RunUpdaters(memberPresenter); + GenerateChildren(memberPresenter, member.Target, propertyProvider); } + parentPresenter.AddChild(memberPresenter); + RunUpdaters(memberPresenter); } } + } - private void CreateItems(IPropertyProviderViewModel propertyProvider, IInitializingNodePresenter parentPresenter, [NotNull] IObjectNode objectNode) + private void CreateItems(IPropertyProviderViewModel? propertyProvider, IInitializingNodePresenter parentPresenter, IObjectNode objectNode) + { + if (objectNode.IsEnumerable) { - if (objectNode.IsEnumerable) + if (objectNode.ItemReferences != null) { - if (objectNode.ItemReferences != null) + foreach (var item in objectNode.ItemReferences) { - foreach (var item in objectNode.ItemReferences) + if (ShouldCreateItemPresenter(parentPresenter, objectNode, item.Index, propertyProvider)) { - if (ShouldCreateItemPresenter(parentPresenter, objectNode, item.Index, propertyProvider)) + var itemPresenter = CreateItem(propertyProvider, parentPresenter, objectNode, item.Index); + if (item.TargetNode != null) { - var itemPresenter = CreateItem(propertyProvider, parentPresenter, objectNode, item.Index); - if (item.TargetNode != null) - { - GenerateChildren(itemPresenter, item.TargetNode, propertyProvider); - } - parentPresenter.AddChild(itemPresenter); - RunUpdaters(itemPresenter); + GenerateChildren(itemPresenter, item.TargetNode, propertyProvider); } + parentPresenter.AddChild(itemPresenter); + RunUpdaters(itemPresenter); } } - else + } + else + { + foreach (var item in objectNode.Indices) { - foreach (var item in objectNode.Indices) + if (ShouldCreateItemPresenter(parentPresenter, objectNode, item, propertyProvider)) { - if (ShouldCreateItemPresenter(parentPresenter, objectNode, item, propertyProvider)) - { - var itemPresenter = CreateItem(propertyProvider, parentPresenter, objectNode, item); - parentPresenter.AddChild(itemPresenter); - RunUpdaters(itemPresenter); - } + var itemPresenter = CreateItem(propertyProvider, parentPresenter, objectNode, item); + parentPresenter.AddChild(itemPresenter); + RunUpdaters(itemPresenter); } } } } + } - protected void RunUpdaters(IInitializingNodePresenter nodePresenter) + protected void RunUpdaters(IInitializingNodePresenter nodePresenter) + { + foreach (var updater in AvailableUpdaters) { - foreach (var updater in AvailableUpdaters) - { - updater.UpdateNode(nodePresenter); - } + updater.UpdateNode(nodePresenter); } + } - protected void FinalizeTree([NotNull] INodePresenter rootPresenter) - { - if (rootPresenter == null) throw new ArgumentNullException(nameof(rootPresenter)); + protected void FinalizeTree(INodePresenter rootPresenter) + { + ArgumentNullException.ThrowIfNull(rootPresenter); - // We might enter here while we're still constructing the hierarchy, if for example we create - // a virtual node in one updater. In this case we skip this call because our hierarchy is still - // incomplete. It's guaranteed that this method will be called again at the end of the creation. - if (buildingNodes.IsValueCreated && buildingNodes.Value) - return; + // We might enter here while we're still constructing the hierarchy, if for example we create + // a virtual node in one updater. In this case we skip this call because our hierarchy is still + // incomplete. It's guaranteed that this method will be called again at the end of the creation. + if (buildingNodes.IsValueCreated && buildingNodes.Value) + return; - foreach (var updater in AvailableUpdaters) - { - updater.FinalizeTree(rootPresenter); - } + foreach (var updater in AvailableUpdaters) + { + updater.FinalizeTree(rootPresenter); } } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/RootNodePresenter.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/RootNodePresenter.cs index 410261fb9e..80409da3ec 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/RootNodePresenter.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/RootNodePresenter.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; @@ -12,10 +10,10 @@ public class RootNodePresenter : NodePresenterBase { protected readonly IObjectNode RootNode; - public RootNodePresenter([NotNull] INodePresenterFactoryInternal factory, IPropertyProviderViewModel propertyProvider, [NotNull] IObjectNode rootNode) + public RootNodePresenter(INodePresenterFactoryInternal factory, IPropertyProviderViewModel? propertyProvider, IObjectNode rootNode) : base(factory, propertyProvider, null) { - if (factory == null) throw new ArgumentNullException(nameof(factory)); + ArgumentNullException.ThrowIfNull(factory); RootNode = rootNode ?? throw new ArgumentNullException(nameof(rootNode)); Name = "Root"; DisplayName = string.Empty; @@ -37,18 +35,20 @@ public RootNodePresenter([NotNull] INodePresenterFactoryInternal factory, IPrope public override bool IsEnumerable => RootNode.IsEnumerable; - [NotNull] public override ITypeDescriptor Descriptor => RootNode.Descriptor; public override object Value => RootNode.Retrieve(); protected override IObjectNode ParentingNode => RootNode; - public override void Dispose() + protected override void Dispose(bool disposing) { - base.Dispose(); - RootNode.ItemChanging -= OnItemChanging; - RootNode.ItemChanged -= OnItemChanged; + base.Dispose(disposing); + if (disposing) + { + RootNode.ItemChanging -= OnItemChanging; + RootNode.ItemChanged -= OnItemChanged; + } } public override void UpdateValue(object newValue) @@ -106,12 +106,12 @@ public override NodeAccessor GetNodeAccessor() return new NodeAccessor(RootNode, NodeIndex.Empty); } - private void OnItemChanging(object sender, ItemChangeEventArgs e) + private void OnItemChanging(object? sender, ItemChangeEventArgs e) { RaiseValueChanging(Value); } - private void OnItemChanged(object sender, ItemChangeEventArgs e) + private void OnItemChanged(object? sender, ItemChangeEventArgs e) { Refresh(); RaiseValueChanged(Value); diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/SyncAnonymousNodePresenterCommand.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/SyncAnonymousNodePresenterCommand.cs index 73b131b3a1..26fab00aa6 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/SyncAnonymousNodePresenterCommand.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/SyncAnonymousNodePresenterCommand.cs @@ -1,38 +1,34 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public class SyncAnonymousNodePresenterCommand : SyncNodePresenterCommandBase { - public class SyncAnonymousNodePresenterCommand : SyncNodePresenterCommandBase - { - private readonly Action execute; - private readonly Func canAttach; + private readonly Action execute; + private readonly Func? canAttach; - public SyncAnonymousNodePresenterCommand([NotNull] string name, [NotNull] Action execute, [CanBeNull] Func canAttach = null) - { - if (name == null) throw new ArgumentNullException(nameof(name)); - if (execute == null) throw new ArgumentNullException(nameof(execute)); - this.execute = execute; - this.canAttach = canAttach; - Name = name; - } + public SyncAnonymousNodePresenterCommand(string name, Action execute, Func? canAttach = null) + { + ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(execute); + this.execute = execute; + this.canAttach = canAttach; + Name = name; + } - /// - public override string Name { get; } + /// + public override string Name { get; } - /// - public override bool CanAttach(INodePresenter nodePresenter) - { - return canAttach?.Invoke(nodePresenter) ?? true; - } + /// + public override bool CanAttach(INodePresenter nodePresenter) + { + return canAttach?.Invoke(nodePresenter) ?? true; + } - /// - protected override void ExecuteSync(INodePresenter nodePresenter, object parameter, object preExecuteResult) - { - execute(nodePresenter, parameter); - } + /// + protected override void ExecuteSync(INodePresenter nodePresenter, object? parameter, object? preExecuteResult) + { + execute(nodePresenter, parameter); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/SyncNodePresenterCommandBase.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/SyncNodePresenterCommandBase.cs index c080d3f9b7..f7a18fc1bc 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/SyncNodePresenterCommandBase.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/SyncNodePresenterCommandBase.cs @@ -1,25 +1,19 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Stride.Core.Annotations; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +/// +/// Base class for node commands that are not asynchronous. +/// +public abstract class SyncNodePresenterCommandBase : NodePresenterCommandBase { - /// - /// Base class for node commands that are not asynchronous. - /// - public abstract class SyncNodePresenterCommandBase : NodePresenterCommandBase + /// + public sealed override Task Execute(INodePresenter nodePresenter, object? parameter, object? preExecuteResult) { - /// - [NotNull] - public sealed override Task Execute(INodePresenter nodePresenter, object parameter, object preExecuteResult) - { - ExecuteSync(nodePresenter, parameter, preExecuteResult); - return Task.CompletedTask; - } - - protected abstract void ExecuteSync([NotNull] INodePresenter nodePresenter, object parameter, object preExecuteResult); + ExecuteSync(nodePresenter, parameter, preExecuteResult); + return Task.CompletedTask; } + + protected abstract void ExecuteSync(INodePresenter nodePresenter, object? parameter, object? preExecuteResult); } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ValueChangedEventArgs.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ValueChangedEventArgs.cs index c1ffea8e38..f64932a34e 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ValueChangedEventArgs.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ValueChangedEventArgs.cs @@ -1,27 +1,24 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +/// +/// Arguments of the event. +/// +public class ValueChangedEventArgs : EventArgs { /// - /// Arguments of the event. + /// Initializes a new instance of the class. /// - public class ValueChangedEventArgs : EventArgs + /// The old value of the node. + public ValueChangedEventArgs(object? oldValue) { - /// - /// Initializes a new instance of the class. - /// - /// The old value of the node. - public ValueChangedEventArgs(object oldValue) - { - OldValue = oldValue; - } - - /// - /// The old value of the node. - /// - public object OldValue { get; } + OldValue = oldValue; } + + /// + /// The old value of the node. + /// + public object? OldValue { get; } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ValueChangingEventArgs.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ValueChangingEventArgs.cs index 45827ec97c..326f7ad205 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ValueChangingEventArgs.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/ValueChangingEventArgs.cs @@ -1,27 +1,24 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +/// +/// Arguments of the event. +/// +public class ValueChangingEventArgs : EventArgs { /// - /// Arguments of the event. + /// Initializes a new instance of the class. /// - public class ValueChangingEventArgs : EventArgs + /// The new value of the node. + public ValueChangingEventArgs(object? newValue) { - /// - /// Initializes a new instance of the class. - /// - /// The new value of the node. - public ValueChangingEventArgs(object newValue) - { - NewValue = newValue; - } - - /// - /// The new value of the node. - /// - public object NewValue { get; private set; } + NewValue = newValue; } + + /// + /// The new value of the node. + /// + public object? NewValue { get; private set; } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/VirtualNodePresenter.cs b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/VirtualNodePresenter.cs index 7e8c2853e7..c844cd5464 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/VirtualNodePresenter.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Presenters/VirtualNodePresenter.cs @@ -1,180 +1,176 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Reflection; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Presenters +namespace Stride.Core.Presentation.Quantum.Presenters; + +public class VirtualNodePresenter : NodePresenterBase { - public class VirtualNodePresenter : NodePresenterBase + protected NodeAccessor AssociatedNode; + private readonly Func getter; + private readonly Action setter; + private readonly List memberAttributes = []; + private bool updatingValue; + + public VirtualNodePresenter(INodePresenterFactoryInternal factory, IPropertyProviderViewModel propertyProvider, INodePresenter parent, string name, Type type, int? order, Func getter, Action setter) + : base(factory, propertyProvider, parent) { - protected NodeAccessor AssociatedNode; - [NotNull] private readonly Func getter; - private readonly Action setter; - private readonly List memberAttributes = new List(); - private bool updatingValue; - - public VirtualNodePresenter([NotNull] INodePresenterFactoryInternal factory, IPropertyProviderViewModel propertyProvider, [NotNull] INodePresenter parent, string name, [NotNull] Type type, int? order, [NotNull] Func getter, Action setter) - : base(factory, propertyProvider, parent) - { - this.getter = getter; - this.setter = setter; - if (factory == null) throw new ArgumentNullException(nameof(factory)); - if (parent == null) throw new ArgumentNullException(nameof(parent)); - if (getter == null) throw new ArgumentNullException(nameof(getter)); - Name = name; - CombineKey = Name; - DisplayName = Name; - Type = type; - Order = order; - Descriptor = TypeDescriptorFactory.Default.Find(type); - - AttachCommands(); - } + ArgumentNullException.ThrowIfNull(factory); + ArgumentNullException.ThrowIfNull(parent); + ArgumentNullException.ThrowIfNull(getter); + this.getter = getter; + this.setter = setter; + Name = name; + CombineKey = Name; + DisplayName = Name; + Type = type; + Order = order; + Descriptor = TypeDescriptorFactory.Default.Find(type); + + AttachCommands(); + } - public override Type Type { get; } + public override Type Type { get; } - public override bool IsEnumerable => false; + public override bool IsEnumerable => false; - public override NodeIndex Index => AssociatedNode.Node != null ? AssociatedNode.Index : NodeIndex.Empty; + public override NodeIndex Index => AssociatedNode.Node != null ? AssociatedNode.Index : NodeIndex.Empty; - public override ITypeDescriptor Descriptor { get; } + public override ITypeDescriptor Descriptor { get; } - public override object Value => getter(); + public override object Value => getter(); - public IReadOnlyList MemberAttributes => memberAttributes; + public IReadOnlyList MemberAttributes => memberAttributes; - protected override IObjectNode ParentingNode => null; + protected override IObjectNode? ParentingNode => null; - /// - /// Registers an object to this virtual node so when the node vakye is modified, it will raise the - /// and events. - /// - /// An accessor to the node to register. - /// Events subscriptions are cleaned when this virtual node is disposed. - public virtual void RegisterAssociatedNode(NodeAccessor associatedNodeAccessor) - { - if (AssociatedNode.Node != null) - throw new InvalidOperationException("A content has already been registered to this virtual node"); + /// + /// Registers an object to this virtual node so when the node vakye is modified, it will raise the + /// and events. + /// + /// An accessor to the node to register. + /// Events subscriptions are cleaned when this virtual node is disposed. + public virtual void RegisterAssociatedNode(NodeAccessor associatedNodeAccessor) + { + if (AssociatedNode.Node != null) + throw new InvalidOperationException("A content has already been registered to this virtual node"); - AssociatedNode = associatedNodeAccessor; - var memberNode = AssociatedNode.Node as IMemberNode; - if (memberNode != null) - { - memberNode.ValueChanging += AssociatedNodeChanging; - memberNode.ValueChanged += AssociatedNodeChanged; - } - var objectNode = AssociatedNode.Node as IObjectNode; - if (objectNode != null) - { - objectNode.ItemChanging += AssociatedNodeChanging; - objectNode.ItemChanged += AssociatedNodeChanged; - } + AssociatedNode = associatedNodeAccessor; + if (AssociatedNode.Node is IMemberNode memberNode) + { + memberNode.ValueChanging += AssociatedNodeChanging; + memberNode.ValueChanged += AssociatedNodeChanged; } + if (AssociatedNode.Node is IObjectNode objectNode) + { + objectNode.ItemChanging += AssociatedNodeChanging; + objectNode.ItemChanged += AssociatedNodeChanged; + } + } - /// - public override void Dispose() + /// + protected override void Dispose(bool disposing) + { + if (disposing) { - var memberNode = AssociatedNode.Node as IMemberNode; - if (memberNode != null) + if (AssociatedNode.Node is IMemberNode memberNode) { memberNode.ValueChanging -= AssociatedNodeChanging; memberNode.ValueChanged -= AssociatedNodeChanged; } - var objectNode = AssociatedNode.Node as IObjectNode; - if (objectNode != null) + if (AssociatedNode.Node is IObjectNode objectNode) { objectNode.ItemChanging -= AssociatedNodeChanging; objectNode.ItemChanged -= AssociatedNodeChanged; } - base.Dispose(); } + base.Dispose(disposing); + } - /// - public override void UpdateValue(object newValue) + /// + public override void UpdateValue(object newValue) + { + try { - try - { - var oldValue = getter(); - var changeType = Index == NodeIndex.Empty ? ContentChangeType.ValueChange : ContentChangeType.CollectionUpdate; - RaiseNodeChanging(newValue, changeType, Index); - updatingValue = true; - setter(newValue); - updatingValue = false; - RaiseNodeChanged(oldValue, changeType, Index); - } - catch (Exception e) - { - throw new NodePresenterException("An error occurred while updating the value of the node, see the inner exception for more information.", e); - } - finally - { - // Note: not sure it is worth doing this in finally block. Currently if we have an exception here we're already screwed. - updatingValue = false; - } + var oldValue = getter(); + var changeType = Index == NodeIndex.Empty ? ContentChangeType.ValueChange : ContentChangeType.CollectionUpdate; + RaiseNodeChanging(newValue, changeType, Index); + updatingValue = true; + setter(newValue); + updatingValue = false; + RaiseNodeChanged(oldValue, changeType, Index); } - - /// - public override void AddItem(object value) + catch (Exception e) { - throw new NodePresenterException($"{nameof(AddItem)} cannot be used on a {nameof(VirtualNodePresenter)}."); + throw new NodePresenterException("An error occurred while updating the value of the node, see the inner exception for more information.", e); } - - /// - public override void AddItem(object value, NodeIndex index) + finally { - throw new NodePresenterException($"{nameof(AddItem)} cannot be used on a {nameof(VirtualNodePresenter)}."); + // Note: not sure it is worth doing this in finally block. Currently if we have an exception here we're already screwed. + updatingValue = false; } + } - /// - public override void RemoveItem(object value, NodeIndex index) - { - throw new NodePresenterException($"{nameof(RemoveItem)} cannot be used on a {nameof(VirtualNodePresenter)}."); - } + /// + public override void AddItem(object value) + { + throw new NodePresenterException($"{nameof(AddItem)} cannot be used on a {nameof(VirtualNodePresenter)}."); + } - /// - public override NodeAccessor GetNodeAccessor() - { - return AssociatedNode; - } + /// + public override void AddItem(object value, NodeIndex index) + { + throw new NodePresenterException($"{nameof(AddItem)} cannot be used on a {nameof(VirtualNodePresenter)}."); + } - private void AssociatedNodeChanging(object sender, [NotNull] INodeChangeEventArgs e) - { - RaiseNodeChanging(e.NewValue, e.ChangeType, (e as ItemChangeEventArgs)?.Index ?? NodeIndex.Empty); - } + /// + public override void RemoveItem(object value, NodeIndex index) + { + throw new NodePresenterException($"{nameof(RemoveItem)} cannot be used on a {nameof(VirtualNodePresenter)}."); + } - private void AssociatedNodeChanged(object sender, [NotNull] INodeChangeEventArgs e) - { - RaiseNodeChanged(e.OldValue, e.ChangeType, (e as ItemChangeEventArgs)?.Index ?? NodeIndex.Empty); - } + /// + public override NodeAccessor GetNodeAccessor() + { + return AssociatedNode; + } + + private void AssociatedNodeChanging(object? sender, INodeChangeEventArgs e) + { + RaiseNodeChanging(e.NewValue, e.ChangeType, (e as ItemChangeEventArgs)?.Index ?? NodeIndex.Empty); + } + + private void AssociatedNodeChanged(object? sender, INodeChangeEventArgs e) + { + RaiseNodeChanged(e.OldValue, e.ChangeType, (e as ItemChangeEventArgs)?.Index ?? NodeIndex.Empty); + } - private void RaiseNodeChanging(object newValue, ContentChangeType changeType, NodeIndex index) + private void RaiseNodeChanging(object? newValue, ContentChangeType changeType, NodeIndex index) + { + if (ShouldRaiseEvent(changeType, index)) { - if (ShouldRaiseEvent(changeType, index)) - { - RaiseValueChanging(newValue); - } + RaiseValueChanging(newValue); } + } - private void RaiseNodeChanged(object oldValue, ContentChangeType changeType, NodeIndex index) + private void RaiseNodeChanged(object? oldValue, ContentChangeType changeType, NodeIndex index) + { + if (ShouldRaiseEvent(changeType, index)) { - if (ShouldRaiseEvent(changeType, index)) - { - RaiseValueChanged(oldValue); - } + RaiseValueChanged(oldValue); } + } - private bool ShouldRaiseEvent(ContentChangeType changeType, NodeIndex index) - { - if (updatingValue) - return false; + private bool ShouldRaiseEvent(ContentChangeType changeType, NodeIndex index) + { + if (updatingValue) + return false; - if (AssociatedNode.Node == null || AssociatedNode.Index == NodeIndex.Empty) - return true; + if (AssociatedNode.Node == null || AssociatedNode.Index == NodeIndex.Empty) + return true; - return index != NodeIndex.Empty && ItemNodePresenter.IsValidChange(changeType, index, Index); - } + return index != NodeIndex.Empty && ItemNodePresenter.IsValidChange(changeType, index, Index); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/Stride.Core.Presentation.Quantum.csproj b/sources/presentation/Stride.Core.Presentation.Quantum/Stride.Core.Presentation.Quantum.csproj index 1af26dbb5f..1f4afc8357 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/Stride.Core.Presentation.Quantum.csproj +++ b/sources/presentation/Stride.Core.Presentation.Quantum/Stride.Core.Presentation.Quantum.csproj @@ -3,6 +3,9 @@ $(StrideXplatEditorTargetFramework) + enable + latest + enable @@ -17,4 +20,4 @@ - \ No newline at end of file + diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/GraphViewModel.cs b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/GraphViewModel.cs index d43eea26f3..b4a85949fc 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/GraphViewModel.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/GraphViewModel.cs @@ -1,11 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Diagnostics.Contracts; -using System.Linq; -using Stride.Core.Annotations; using Stride.Core.Diagnostics; using Stride.Core.Extensions; using Stride.Core.Presentation.Quantum.Presenters; @@ -13,99 +8,97 @@ using Stride.Core.Presentation.ViewModels; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.ViewModels +namespace Stride.Core.Presentation.Quantum.ViewModels; + +/// +/// A view model class representing one or multiple trees of instances. +/// +public class GraphViewModel : DispatcherViewModel { + public const string DefaultLoggerName = "Quantum"; + public const string HasChildPrefix = "HasChild_"; + public const string HasCommandPrefix = "HasCommand_"; + public const string HasAssociatedDataPrefix = "HasAssociatedData_"; + private NodeViewModel rootNode; + /// - /// A view model class representing one or multiple trees of instances. + /// Initializes a new instance of the class. /// - public class GraphViewModel : DispatcherViewModel + /// A service provider that can provide a and an to use for this view model. + /// + /// The root instances. + private GraphViewModel(IViewModelServiceProvider serviceProvider, Type type, IEnumerable rootPresenters) + : base(serviceProvider) { - public const string DefaultLoggerName = "Quantum"; - public const string HasChildPrefix = "HasChild_"; - public const string HasCommandPrefix = "HasCommand_"; - public const string HasAssociatedDataPrefix = "HasAssociatedData_"; - private NodeViewModel rootNode; - - /// - /// Initializes a new instance of the class. - /// - /// A service provider that can provide a and an to use for this view model. - /// - /// The root instances. - private GraphViewModel([NotNull] IViewModelServiceProvider serviceProvider, [NotNull] Type type, [NotNull] IEnumerable rootPresenters) - : base(serviceProvider) - { - if (serviceProvider == null) throw new ArgumentNullException(nameof(serviceProvider)); - if (type == null) throw new ArgumentNullException(nameof(type)); - if (rootPresenters == null) throw new ArgumentNullException(nameof(rootPresenters)); - GraphViewModelService = serviceProvider.TryGet(); - if (GraphViewModelService == null) throw new InvalidOperationException($"{nameof(GraphViewModel)} requires a {nameof(GraphViewModelService)} in the service provider."); - Logger = GlobalLogger.GetLogger(DefaultLoggerName); - var viewModelFactory = serviceProvider.Get().NodeViewModelFactory; - viewModelFactory.CreateGraph(this, type, rootPresenters); - } + ArgumentNullException.ThrowIfNull(serviceProvider); + ArgumentNullException.ThrowIfNull(type); + ArgumentNullException.ThrowIfNull(rootPresenters); + GraphViewModelService = serviceProvider.TryGet() + ?? throw new InvalidOperationException($"{nameof(GraphViewModel)} requires a {nameof(GraphViewModelService)} in the service provider."); + Logger = GlobalLogger.GetLogger(DefaultLoggerName); + var viewModelFactory = serviceProvider.Get().NodeViewModelFactory; + viewModelFactory.CreateGraph(this, type, rootPresenters); + } - /// - public override void Destroy() - { - RootNode.Children.SelectDeep(x => x.Children).ForEach(x => x.Destroy()); - RootNode.Destroy(); - } + /// + public override void Destroy() + { + RootNode.Children.SelectDeep(x => x.Children).ForEach(x => x.Destroy()); + RootNode.Destroy(); + } + + public static GraphViewModel? Create(IViewModelServiceProvider serviceProvider, IReadOnlyCollection propertyProviders) + { + ArgumentNullException.ThrowIfNull(serviceProvider); + ArgumentNullException.ThrowIfNull(propertyProviders); - [CanBeNull] - public static GraphViewModel Create([NotNull] IViewModelServiceProvider serviceProvider, [NotNull] IReadOnlyCollection propertyProviders) + var rootNodes = new List(); + Type? type = null; + var factory = serviceProvider.Get().NodePresenterFactory; + foreach (var propertyProvider in propertyProviders) { - if (serviceProvider == null) throw new ArgumentNullException(nameof(serviceProvider)); - if (propertyProviders == null) throw new ArgumentNullException(nameof(propertyProviders)); - - var rootNodes = new List(); - Type type = null; - var factory = serviceProvider.Get().NodePresenterFactory; - foreach (var propertyProvider in propertyProviders) - { - if (!propertyProvider.CanProvidePropertiesViewModel) - return null; - - var rootNode = propertyProvider.GetRootNode(); - if (rootNode == null) - return null; - - if (type == null) - type = rootNode.Type; - else if (type != rootNode.Type) - return null; - - var node = factory.CreateNodeHierarchy(rootNode, new GraphNodePath(rootNode), propertyProvider); - rootNodes.Add(node); - } - - if (propertyProviders.Count == 0 || type == null) throw new ArgumentException($@"{nameof(propertyProviders)} cannot be empty.", nameof(propertyProviders)); - return new GraphViewModel(serviceProvider, type, rootNodes); + if (!propertyProvider.CanProvidePropertiesViewModel) + return null; + + var rootNode = propertyProvider.GetRootNode(); + if (rootNode == null) + return null; + + if (type == null) + type = rootNode.Type; + else if (type != rootNode.Type) + return null; + + var node = factory.CreateNodeHierarchy(rootNode, new GraphNodePath(rootNode), propertyProvider); + rootNodes.Add(node); } - /// - /// Gets the root node of this . - /// - public NodeViewModel RootNode { get { return rootNode; } set { SetValue(ref rootNode, value); } } + if (propertyProviders.Count == 0 || type == null) throw new ArgumentException($@"{nameof(propertyProviders)} cannot be empty.", nameof(propertyProviders)); + return new GraphViewModel(serviceProvider, type, rootNodes); + } - /// - /// Gets the associated to this view model. - /// - public GraphViewModelService GraphViewModelService { get; } + /// + /// Gets the root node of this . + /// + public NodeViewModel RootNode { get { return rootNode; } set { SetValue(ref rootNode, value); } } - /// - /// Gets the associated to this view model. - /// - public Logger Logger { get; private set; } + /// + /// Gets the associated to this view model. + /// + public GraphViewModelService GraphViewModelService { get; } - /// - /// Raised when the value of an contained into this view model has changed. - /// - public event EventHandler NodeValueChanged; + /// + /// Gets the associated to this view model. + /// + public Logger Logger { get; private set; } - internal void NotifyNodeChanged(NodeViewModel node) - { - NodeValueChanged?.Invoke(this, new NodeViewModelValueChangedArgs(this, node)); - } + /// + /// Raised when the value of an contained into this view model has changed. + /// + public event EventHandler? NodeValueChanged; + + internal void NotifyNodeChanged(NodeViewModel node) + { + NodeValueChanged?.Invoke(this, new NodeViewModelValueChangedArgs(this, node)); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/INodeViewModelFactory.cs b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/INodeViewModelFactory.cs index e60bf01356..c995a3ae74 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/INodeViewModelFactory.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/INodeViewModelFactory.cs @@ -1,17 +1,13 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using Stride.Core.Annotations; + using Stride.Core.Presentation.Quantum.Presenters; -namespace Stride.Core.Presentation.Quantum.ViewModels +namespace Stride.Core.Presentation.Quantum.ViewModels; + +public interface INodeViewModelFactory { - public interface INodeViewModelFactory - { - [NotNull] - NodeViewModel CreateGraph([NotNull] GraphViewModel owner, [NotNull] Type rootType, [NotNull] IEnumerable rootNodes); + NodeViewModel CreateGraph(GraphViewModel owner, Type rootType, IEnumerable rootNodes); - void GenerateChildren([NotNull] GraphViewModel owner, NodeViewModel parent, [NotNull] List nodePresenters); - } + void GenerateChildren(GraphViewModel owner, NodeViewModel parent, List nodePresenters); } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodePresenterCommandWrapper.cs b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodePresenterCommandWrapper.cs index 00518e8a9f..dcfb496ddb 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodePresenterCommandWrapper.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodePresenterCommandWrapper.cs @@ -1,86 +1,78 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Stride.Core.Annotations; + using Stride.Core.Extensions; using Stride.Core.Presentation.Commands; using Stride.Core.Presentation.Quantum.Presenters; using Stride.Core.Presentation.ViewModels; -namespace Stride.Core.Presentation.Quantum.ViewModels +namespace Stride.Core.Presentation.Quantum.ViewModels; + +/// +/// A class that wraps an instance of into an instance. +/// +public class NodePresenterCommandWrapper : CommandBase { + private readonly IReadOnlyCollection presenters; + /// - /// A class that wraps an instance of into an instance. + /// Initializes a new instance of the class. /// - public class NodePresenterCommandWrapper : CommandBase + /// The service provider of the view model. + /// The instances on which to invoke the command. + /// The command to invoke. + public NodePresenterCommandWrapper(IViewModelServiceProvider serviceProvider, IReadOnlyCollection presenters, INodePresenterCommand command) + : base(serviceProvider) { - [NotNull] private readonly IReadOnlyCollection presenters; - - /// - /// Initializes a new instance of the class. - /// - /// The service provider of the view model. - /// The instances on which to invoke the command. - /// The command to invoke. - public NodePresenterCommandWrapper([NotNull] IViewModelServiceProvider serviceProvider, [NotNull] IReadOnlyCollection presenters, [NotNull] INodePresenterCommand command) - : base(serviceProvider) - { - this.presenters = presenters ?? throw new ArgumentNullException(nameof(presenters)); - Command = command ?? throw new ArgumentNullException(nameof(command)); - } + this.presenters = presenters ?? throw new ArgumentNullException(nameof(presenters)); + Command = command ?? throw new ArgumentNullException(nameof(command)); + } - /// - /// The name of the action executed by this command. - /// - [NotNull] - public string ActionName => $"Execute {Name}"; + /// + /// The name of the action executed by this command. + /// + public string ActionName => $"Execute {Name}"; - /// - /// The name of this command. - /// - [NotNull] - public string Name => Command.Name; + /// + /// The name of this command. + /// + public string Name => Command.Name; - /// - /// The command wrapped by this instance. - /// - [NotNull] - public INodePresenterCommand Command { get; } + /// + /// The command wrapped by this instance. + /// + public INodePresenterCommand Command { get; } - /// - public override void Execute(object parameter) - { - Invoke(parameter).Forget(); - } + /// + public override void Execute(object? parameter) + { + Invoke(parameter).Forget(); + } - /// - public override bool CanExecute(object parameter) - { - return Command.CanExecute(presenters, parameter); - } + /// + public override bool CanExecute(object? parameter) + { + return Command.CanExecute(presenters, parameter); + } - /// - /// Invokes the command on each node presenters attached to this wrapper. - /// - /// The parameter of the command. - /// A task that completes when the execution of the command is finished. - public virtual async Task Invoke(object parameter) + /// + /// Invokes the command on each node presenters attached to this wrapper. + /// + /// The parameter of the command. + /// A task that completes when the execution of the command is finished. + public virtual async Task Invoke(object parameter) + { + var preExecuteResult = await Command.PreExecute(presenters, parameter); + foreach (var presenter in presenters) { - var preExecuteResult = await Command.PreExecute(presenters, parameter); - foreach (var presenter in presenters) - { - await Command.Execute(presenter, parameter, preExecuteResult); - } - await Command.PostExecute(presenters.ToList(), parameter); + await Command.Execute(presenter, parameter, preExecuteResult); } + await Command.PostExecute([.. presenters], parameter); + } - /// - public override string ToString() - { - return !string.IsNullOrEmpty(Name) ? Name : base.ToString(); - } + /// + public override string? ToString() + { + return !string.IsNullOrEmpty(Name) ? Name : base.ToString(); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModel.cs b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModel.cs index 2518786374..9cf93ec730 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModel.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModel.cs @@ -1,12 +1,9 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; + using System.Collections.ObjectModel; using System.Dynamic; -using System.Linq; using System.Reflection; -using Stride.Core.Annotations; using Stride.Core.Extensions; using Stride.Core.Reflection; using Stride.Core.Presentation.Collections; @@ -18,709 +15,699 @@ using Stride.Core.TypeConverters; using Expression = System.Linq.Expressions.Expression; -namespace Stride.Core.Presentation.Quantum.ViewModels +namespace Stride.Core.Presentation.Quantum.ViewModels; + +public class NodeViewModel : DispatcherViewModel, IDynamicMetaObjectProvider { - public class NodeViewModel : DispatcherViewModel, IDynamicMetaObjectProvider - { - internal class DifferentValuesObject { public readonly string Name = "DifferentValues"; }; - - protected static readonly HashSet ReservedNames = new HashSet(); - private readonly AutoUpdatingSortedObservableCollection children = new AutoUpdatingSortedObservableCollection(new AnonymousComparer(CompareChildren), nameof(Name), nameof(NodeIndex), nameof(Order)); - private readonly ObservableCollection commands = new ObservableCollection(); - private readonly Dictionary associatedData = new Dictionary(); - private readonly List changingProperties = new List(); - private readonly GraphViewModel owner; - private readonly List nodePresenters; - private List initializingChildren = new List(); - private bool isVisible; - private bool isReadOnly; - private string displayName; - private bool isHighlighted; - private bool valueChanging; - private readonly (Type declarer, int token)? sortingHint; + internal class DifferentValuesObject { public readonly string Name = "DifferentValues"; }; + + protected static readonly HashSet ReservedNames = []; + private readonly AutoUpdatingSortedObservableCollection children = new(new AnonymousComparer(CompareChildren), nameof(Name), nameof(NodeIndex), nameof(Order)); + private readonly ObservableCollection commands = []; + private readonly Dictionary associatedData = []; + private readonly List changingProperties = []; + private readonly GraphViewModel owner; + private readonly List nodePresenters; + private List? initializingChildren = []; + private bool isVisible; + private bool isReadOnly; + private string displayName; + private bool isHighlighted; + private bool valueChanging; + private readonly (Type declarer, int token)? sortingHint; #if DEBUG - private static bool DebugQuantumPropertyChanges = true; + private static bool DebugQuantumPropertyChanges = true; #else - private static bool DebugQuantumPropertyChanges = false; + private static bool DebugQuantumPropertyChanges = false; #endif - public static readonly object DifferentValues = new DifferentValuesObject(); + public static readonly object DifferentValues = new DifferentValuesObject(); - public static object UnsetValue; + public static object UnsetValue; - static NodeViewModel() - { - typeof(NodeViewModel).GetProperties().Select(x => x.Name).ForEach(x => ReservedNames.Add(x)); - } + static NodeViewModel() + { + typeof(NodeViewModel).GetProperties().Select(x => x.Name).ForEach(x => ReservedNames.Add(x)); + } - protected internal NodeViewModel([NotNull] GraphViewModel ownerViewModel, NodeViewModel parent, [NotNull] string baseName, Type nodeType, [NotNull] List nodePresenters) - : base(ownerViewModel.ServiceProvider) - { - owner = ownerViewModel; - Type = nodeType; + protected internal NodeViewModel(GraphViewModel ownerViewModel, NodeViewModel parent, string baseName, Type nodeType, List nodePresenters) + : base(ownerViewModel.ServiceProvider) + { + owner = ownerViewModel; + Type = nodeType; - if (baseName == null) - throw new ArgumentException("baseName and index can't be both null."); + if (baseName == null) + throw new ArgumentException("baseName and index can't be both null."); - Name = EscapeName(baseName); + Name = EscapeName(baseName); - this.nodePresenters = nodePresenters; - foreach (var nodePresenter in nodePresenters) - { - nodePresenter.ValueChanging += ValueChanging; - nodePresenter.ValueChanged += ValueChanged; - nodePresenter.AttachedProperties.PropertyUpdated += AttachedPropertyUpdated; + this.nodePresenters = nodePresenters; + foreach (var nodePresenter in nodePresenters) + { + nodePresenter.ValueChanging += ValueChanging; + nodePresenter.ValueChanged += ValueChanged; + nodePresenter.AttachedProperties.PropertyUpdated += AttachedPropertyUpdated; - if (nodePresenter is MemberNodePresenter memberNodePresenter) - { - var descriptor = memberNodePresenter.MemberDescriptor; - sortingHint = (descriptor.MemberInfo.DeclaringType, descriptor.MemberInfo.MetadataToken); - } + if (nodePresenter is MemberNodePresenter memberNodePresenter) + { + var descriptor = memberNodePresenter.MemberDescriptor; + sortingHint = (descriptor.MemberInfo.DeclaringType, descriptor.MemberInfo.MetadataToken); } + } - UpdateViewModelProperties(); + UpdateViewModelProperties(); - parent?.AddChild(this); - } + parent?.AddChild(this); + } - public override void Destroy() + public override void Destroy() + { + foreach (var nodePresenter in nodePresenters) { - foreach (var nodePresenter in nodePresenters) - { - nodePresenter.ValueChanging -= ValueChanging; - nodePresenter.ValueChanged -= ValueChanged; - nodePresenter.AttachedProperties.PropertyUpdated -= AttachedPropertyUpdated; - nodePresenter.Dispose(); - } - base.Destroy(); + nodePresenter.ValueChanging -= ValueChanging; + nodePresenter.ValueChanged -= ValueChanged; + nodePresenter.AttachedProperties.PropertyUpdated -= AttachedPropertyUpdated; + nodePresenter.Dispose(); } + base.Destroy(); + } + + /// + /// Gets or sets the name of this node. Note that the name can be used to access this node from its parent using a dynamic object. + /// + public string Name { get; } + + /// + /// Gets or sets the name used to display the node to the user. + /// + public string DisplayName { get => displayName; set => SetValue(ref displayName, value); } + + /// + /// Gets the display path of this node. The path is constructed from the of all nodes from the root to this one, separated by periods. + /// + public string DisplayPath { get { if (Parent == null) return string.Empty; var parentPath = Parent.DisplayPath; return parentPath != string.Empty ? parentPath + '.' + DisplayName : DisplayName; } } + + /// + /// Gets or sets the value of the nodes represented by this view model. + /// + public object NodeValue { get => GetNodeValue(); set => SetNodeValue(ConvertValue(value)); } + + /// + /// Gets the expected type of . + /// + public Type Type { get; } + + /// + /// Gets the parent of this node. + /// + public NodeViewModel? Parent { get; private set; } + + /// + /// Gets the root of this node. + /// + public NodeViewModel Root { + get + { + var root = this; + while (root.Parent != null) + root = root.Parent; + return root; + } + } - /// - /// Gets or sets the name of this node. Note that the name can be used to access this node from its parent using a dynamic object. - /// - public string Name { get; } - - /// - /// Gets or sets the name used to display the node to the user. - /// - public string DisplayName { get => displayName; set => SetValue(ref displayName, value); } - - /// - /// Gets the display path of this node. The path is constructed from the of all nodes from the root to this one, separated by periods. - /// - public string DisplayPath { get { if (Parent == null) return string.Empty; var parentPath = Parent.DisplayPath; return parentPath != string.Empty ? parentPath + '.' + DisplayName : DisplayName; } } - - /// - /// Gets or sets the value of the nodes represented by this view model. - /// - public object NodeValue { get => GetNodeValue(); set => SetNodeValue(ConvertValue(value)); } - - /// - /// Gets the expected type of . - /// - public Type Type { get; } - - /// - /// Gets the parent of this node. - /// - public NodeViewModel Parent { get; private set; } - - /// - /// Gets the root of this node. - /// - [NotNull] - public NodeViewModel Root { - get + /// + /// Gets whether this node should be displayed in the view. + /// + public bool IsVisible { get => isVisible; private set => SetValue(ref isVisible, value); } + + /// + /// Gets whether this node can be modified in the view. + /// + public bool IsReadOnly { get => isReadOnly; private set => SetValue(ref isReadOnly, value); } + + /// + /// Gets the list of children nodes. + /// + public IReadOnlyCollection Children => initializingChildren != null ? initializingChildren : children; + + /// + /// Gets the list of commands available in this node. + /// + public IEnumerable Commands => commands; + + /// + /// Gets additional data associated to this content. This can be used when the content itself does not contain enough information to be used as a view model. + /// + public IReadOnlyDictionary AssociatedData => associatedData; + + /// + /// Gets the level of depth of this node, starting from 0 for the root node. + /// + public int Level => Parent?.Level + 1 ?? 0; + + /// + /// Gets the member info (if any). + /// + [Obsolete] + public MemberInfo? MemberInfo => null; + + /// + /// Gets whether this node contains a list. + /// + /// Used mostly for sorting purpose. + /// + public bool HasList => ListDescriptor.IsList(Type); + + /// + /// Gets whether this node contains a collection. + /// + /// Used mostly for sorting purpose. + /// + public bool HasCollection => CollectionDescriptor.IsCollection(Type); + + /// + /// Gets whether this node contains a dictionary. + /// + /// Usually a dictionary is also a collection. + /// + public bool HasDictionary => DictionaryDescriptor.IsDictionary(Type); + + /// + /// Gets whether this node contains a set. + /// + /// Usually a set is also a collection. + /// + public bool HasSet => SetDescriptor.IsSet(Type); + + /// + /// Gets the number of visible children. + /// + public int VisibleChildrenCount => Children.Count(x => x.IsVisible); + + // TODO: generalize usage in the templates + public bool IsHighlighted { get => isHighlighted; set => SetValue(ref isHighlighted, value); } + + public IReadOnlyCollection NodePresenters => nodePresenters; + + /// + /// Gets the path of this node. The path is constructed from the name of all nodes from the root to this one, separated by periods. + /// + protected string Path => Parent != null ? Parent.Path + '.' + Name : Name; + + /// + /// Gets the order number of this node in its parent. + /// + protected int? Order => NodePresenters.First().Order; + + public void FinishInitialization() + { + if (initializingChildren != null) + { + OnPropertyChanging(nameof(Children)); + foreach (var child in initializingChildren) { - var root = this; - while (root.Parent != null) - root = root.Parent; - return root; + children.Add(child); } + initializingChildren = null; + OnPropertyChanged(nameof(Children)); } - /// - /// Gets whether this node should be displayed in the view. - /// - public bool IsVisible { get => isVisible; private set => SetValue(ref isVisible, value); } - - /// - /// Gets whether this node can be modified in the view. - /// - public bool IsReadOnly { get => isReadOnly; private set => SetValue(ref isReadOnly, value); } - - /// - /// Gets the list of children nodes. - /// - public IReadOnlyCollection Children => initializingChildren != null ? (IReadOnlyCollection)initializingChildren : children; - - /// - /// Gets the list of commands available in this node. - /// - public IEnumerable Commands => commands; - - /// - /// Gets additional data associated to this content. This can be used when the content itself does not contain enough information to be used as a view model. - /// - public IReadOnlyDictionary AssociatedData => associatedData; - - /// - /// Gets the level of depth of this node, starting from 0 for the root node. - /// - public int Level => Parent?.Level + 1 ?? 0; - - /// - /// Gets the member info (if any). - /// - [CanBeNull] - [Obsolete] - public MemberInfo MemberInfo => null; - - /// - /// Gets whether this node contains a list. - /// - /// Used mostly for sorting purpose. - /// - public bool HasList => ListDescriptor.IsList(Type); - - /// - /// Gets whether this node contains a collection. - /// - /// Used mostly for sorting purpose. - /// - public bool HasCollection => OldCollectionDescriptor.IsCollection(Type); - - /// - /// Gets whether this node contains a dictionary. - /// - /// Usually a dictionary is also a collection. - /// - public bool HasDictionary => DictionaryDescriptor.IsDictionary(Type); - - /// - /// Gets whether this node contains a set. - /// - /// Usually a set is also a collection. - /// - public bool HasSet => SetDescriptor.IsSet(Type); - - /// - /// Gets the number of visible children. - /// - public int VisibleChildrenCount => Children.Count(x => x.IsVisible); - - // TODO: generalize usage in the templates - public bool IsHighlighted { get => isHighlighted; set => SetValue(ref isHighlighted, value); } - - public IReadOnlyCollection NodePresenters => nodePresenters; - - /// - /// Gets the path of this node. The path is constructed from the name of all nodes from the root to this one, separated by periods. - /// - protected string Path => Parent != null ? Parent.Path + '.' + Name : Name; - - /// - /// Gets the order number of this node in its parent. - /// - protected int? Order => NodePresenters.First().Order; - - public void FinishInitialization() + var commonCommands = new Dictionary(); + foreach (var nodePresenter in nodePresenters) { - if (initializingChildren != null) + foreach (var command in nodePresenter.Commands) { - OnPropertyChanging(nameof(Children)); - foreach (var child in initializingChildren) + if (!commonCommands.TryGetValue(command, out var count)) { - children.Add(child); + commonCommands.Add(command, 1); } - initializingChildren = null; - OnPropertyChanged(nameof(Children)); - } - - var commonCommands = new Dictionary(); - foreach (var nodePresenter in nodePresenters) - { - foreach (var command in nodePresenter.Commands) + else { - int count; - if (!commonCommands.TryGetValue(command, out count)) - { - commonCommands.Add(command, 1); - } - else - { - commonCommands[command] = count + 1; - } + commonCommands[command] = count + 1; } } - foreach (var command in commonCommands) - { - if (command.Key.CombineMode == CombineMode.DoNotCombine && command.Value > 1) - continue; - - if (command.Key.CombineMode == CombineMode.CombineOnlyForAll && command.Value < nodePresenters.Count) - continue; + } + foreach (var command in commonCommands) + { + if (command.Key.CombineMode == CombineMode.DoNotCombine && command.Value > 1) + continue; - var commandWrapper = ConstructCommandWrapper(command.Key); - AddCommand(commandWrapper); - } + if (command.Key.CombineMode == CombineMode.CombineOnlyForAll && command.Value < nodePresenters.Count) + continue; - var commonAttachedProperties = nodePresenters.SelectMany(x => x.AttachedProperties).GroupBy(x => x.Key).ToList(); - foreach (var attachedProperty in commonAttachedProperties) - { - var combiner = attachedProperty.Key.Metadatas.OfType().FirstOrDefault()?.Combiner ?? DefaultCombineAttachedProperty; - var values = attachedProperty.Select(x => x.Value).ToList(); - var value = values.Count == 1 ? values[0] : combiner(values); - AddAssociatedData(attachedProperty.Key.Name, value); - } + var commandWrapper = ConstructCommandWrapper(command.Key); + AddCommand(commandWrapper); } - /// - /// Returns the child node with the matching name. - /// - /// The name of the to look for. - /// The corresponding child node, or null if no child with the given name exists. - [CanBeNull] - public NodeViewModel GetChild(string name) + var commonAttachedProperties = nodePresenters.SelectMany(x => x.AttachedProperties).GroupBy(x => x.Key).ToList(); + foreach (var attachedProperty in commonAttachedProperties) { - name = EscapeName(name); - return Children.FirstOrDefault(x => x.Name == name); + var combiner = attachedProperty.Key.Metadatas.OfType().FirstOrDefault()?.Combiner ?? DefaultCombineAttachedProperty; + var values = attachedProperty.Select(x => x.Value).ToList(); + var value = values.Count == 1 ? values[0] : combiner(values); + AddAssociatedData(attachedProperty.Key.Name, value); } + } - /// - /// Returns the command with the matching name. - /// - /// The name of the command to look for. - /// The corresponding command, or null if no command with the given name exists. - [CanBeNull] - public ICommandBase GetCommand(string name) - { - name = EscapeName(name); - return Commands.FirstOrDefault(x => x.Name == name); - } + /// + /// Returns the child node with the matching name. + /// + /// The name of the to look for. + /// The corresponding child node, or null if no child with the given name exists. + public NodeViewModel? GetChild(string name) + { + name = EscapeName(name); + return Children.FirstOrDefault(x => x.Name == name); + } - /// - /// Returns the additionnal data with the matching name. - /// - /// The name of the additionnal data to look for. - /// The corresponding additionnal data, or null if no data with the given name exists. - public object GetAssociatedData(string name) - { - name = EscapeName(name); - return AssociatedData.FirstOrDefault(x => x.Key == name).Value; - } + /// + /// Returns the command with the matching name. + /// + /// The name of the command to look for. + /// The corresponding command, or null if no command with the given name exists. + public ICommandBase? GetCommand(string name) + { + name = EscapeName(name); + return Commands.FirstOrDefault(x => x.Name == name); + } + + /// + /// Returns the additionnal data with the matching name. + /// + /// The name of the additionnal data to look for. + /// The corresponding additionnal data, or null if no data with the given name exists. + public object GetAssociatedData(string name) + { + name = EscapeName(name); + return AssociatedData.FirstOrDefault(x => x.Key == name).Value; + } - /// - /// Returns the child node, the command or the additional data with the matching name. - /// - /// The name of the object to look for. - /// The corresponding object, or null if no object with the given name exists. - public object GetDynamicObject(string name) + /// + /// Returns the child node, the command or the additional data with the matching name. + /// + /// The name of the object to look for. + /// The corresponding object, or null if no object with the given name exists. + public object GetDynamicObject(string name) + { + name = EscapeName(name); + return GetChild(name) ?? GetCommand(name) ?? GetAssociatedData(name) ?? UnsetValue; + } + + /// + public DynamicMetaObject GetMetaObject(Expression parameter) + { + return new NodeViewModelDynamicMetaObject(parameter, this); + } + + protected void NotifyPropertyChanging(string propertyName) + { + if (!changingProperties.Contains(propertyName)) { - name = EscapeName(name); - return GetChild(name) ?? GetCommand(name) ?? GetAssociatedData(name) ?? UnsetValue; + changingProperties.Add(propertyName); + OnPropertyChanging(propertyName); } + } - /// - public DynamicMetaObject GetMetaObject(Expression parameter) + protected void NotifyPropertyChanged(string propertyName) + { + if (changingProperties.Remove(propertyName)) { - return new NodeViewModelDynamicMetaObject(parameter, this); + OnPropertyChanged(propertyName); } + } + + protected virtual NodePresenterCommandWrapper ConstructCommandWrapper(INodePresenterCommand command) + { + return new NodePresenterCommandWrapper(ServiceProvider, nodePresenters, command); + } - protected void NotifyPropertyChanging(string propertyName) + protected virtual object GetNodeValue() + { + object? currentValue = null; + var isFirst = true; + foreach (var nodePresenter in NodePresenters) { - if (!changingProperties.Contains(propertyName)) + if (isFirst) + { + currentValue = nodePresenter.Value; + } + else if (nodePresenter.Factory.IsPrimitiveType(nodePresenter.Value?.GetType())) { - changingProperties.Add(propertyName); - OnPropertyChanging(propertyName); + if (!AreValueEqual(currentValue, nodePresenter.Value)) + return DifferentValues; } + else + { + if (!AreValueEquivalent(currentValue, nodePresenter.Value)) + return DifferentValues; + } + isFirst = false; } + return currentValue; + } - protected void NotifyPropertyChanged(string propertyName) + protected virtual void SetNodeValue(object newValue) + { + foreach (var nodePresenter in NodePresenters) { - if (changingProperties.Remove(propertyName)) + // TODO: normally it shouldn't take that path (since it uses commands), but this is not safe with newly instantiated values + // fixme adding a test to check whether it's a content type from Quantum point of view might be safe enough. + var oldValue = nodePresenter.Value; + if (!Equals(oldValue, newValue)) { - OnPropertyChanged(propertyName); + nodePresenter.UpdateValue(newValue); } } + } - [NotNull] - protected virtual NodePresenterCommandWrapper ConstructCommandWrapper([NotNull] INodePresenterCommand command) + /// + /// Refreshes this node, updating its properties and its child nodes. + /// + public void Refresh() + { + foreach (var child in Children.ToList()) { - return new NodePresenterCommandWrapper(ServiceProvider, nodePresenters, command); + RemoveChild(child); + child.Destroy(); } - - protected virtual object GetNodeValue() + foreach (var command in Commands.ToList()) + { + RemoveCommand(command); + } + foreach (var data in AssociatedData.ToList()) { - object currentValue = null; - var isFirst = true; - foreach (var nodePresenter in NodePresenters) + RemoveAssociatedData(data.Key); + } + + owner.GraphViewModelService.NodeViewModelFactory.GenerateChildren(owner, this, nodePresenters); + FinishInitialization(); + } + + protected void CheckDynamicMemberConsistency() + { + var memberNames = new HashSet(); + // We should allow space or empty or null or '.' appear when it's a string key dictionary + // And we should allow space or '.' appear when it's a char key dictionary + bool freeName = false; + bool allowSpCharOnly = false; + if (HasDictionary) + { + foreach (var iType in Type.GetTypeInfo().ImplementedInterfaces) { - if (isFirst) - { - currentValue = nodePresenter.Value; - } - else if (nodePresenter.Factory.IsPrimitiveType(nodePresenter.Value?.GetType())) + var iTypeInfo = iType.GetTypeInfo(); + if (iTypeInfo.IsGenericType == false) + continue; + if (iTypeInfo.GetGenericTypeDefinition() != typeof(IDictionary<,>)) + continue; + Type[] genericTypes = iTypeInfo.GetGenericArguments(); + if (genericTypes[0] == typeof(string)) { - if (!AreValueEqual(currentValue, nodePresenter.Value)) - return DifferentValues; + freeName = true; } - else + else if (genericTypes[0] == typeof(char)) { - if (!AreValueEquivalent(currentValue, nodePresenter.Value)) - return DifferentValues; + allowSpCharOnly = true; } - isFirst = false; + break; } - return currentValue; } - protected virtual void SetNodeValue(object newValue) + foreach (var child in Children) { - foreach (var nodePresenter in NodePresenters) + if (!freeName) { - // TODO: normally it shouldn't take that path (since it uses commands), but this is not safe with newly instantiated values - // fixme adding a test to check whether it's a content type from Quantum point of view might be safe enough. - var oldValue = nodePresenter.Value; - if (!Equals(oldValue, newValue)) + if (allowSpCharOnly) { - nodePresenter.UpdateValue(newValue); + if (child.Name == null) + throw new InvalidOperationException("This node has a child with a null name"); } - } - } - - /// - /// Refreshes this node, updating its properties and its child nodes. - /// - public void Refresh() - { - foreach (var child in Children.ToList()) - { - RemoveChild(child); - child.Destroy(); - } - foreach (var command in Commands.ToList()) - { - RemoveCommand(command); - } - foreach (var data in AssociatedData.ToList()) - { - RemoveAssociatedData(data.Key); - } - - owner.GraphViewModelService.NodeViewModelFactory.GenerateChildren(owner, this, nodePresenters); - FinishInitialization(); - } - - protected void CheckDynamicMemberConsistency() - { - var memberNames = new HashSet(); - // We should allow space or empty or null or '.' appear when it's a string key dictionary - // And we should allow space or '.' appear when it's a char key dictionary - bool freeName = false; - bool allowSpCharOnly = false; - if (HasDictionary) - { - foreach (var iType in Type.GetTypeInfo().ImplementedInterfaces) + else { - var iTypeInfo = iType.GetTypeInfo(); - if (iTypeInfo.IsGenericType == false) - continue; - if (iTypeInfo.GetGenericTypeDefinition() != typeof(IDictionary<,>)) - continue; - Type[] genericTypes = iTypeInfo.GetGenericArguments(); - if (genericTypes[0] == typeof(string)) - { - freeName = true; - } - else if (genericTypes[0] == typeof(char)) - { - allowSpCharOnly = true; - } - break; + if (string.IsNullOrWhiteSpace(child.Name)) + throw new InvalidOperationException("This node has a child with a null or blank name"); } - } - foreach (var child in Children) - { - if (!freeName) + if (!allowSpCharOnly) { - if (allowSpCharOnly) - { - if (child.Name == null) - throw new InvalidOperationException("This node has a child with a null name"); - } - else - { - if (string.IsNullOrWhiteSpace(child.Name)) - throw new InvalidOperationException("This node has a child with a null or blank name"); - } - - if (!allowSpCharOnly) - { - if (child.Name.Contains('.')) - throw new InvalidOperationException($"This node has a child which contains a period (.) in its name: {child.Name}"); - } + if (child.Name.Contains('.')) + throw new InvalidOperationException($"This node has a child which contains a period (.) in its name: {child.Name}"); } + } - if (memberNames.Contains(child.Name)) - throw new InvalidOperationException($"This node contains several members named {child.Name}"); + if (memberNames.Contains(child.Name)) + throw new InvalidOperationException($"This node contains several members named {child.Name}"); - memberNames.Add(child.Name); - } + memberNames.Add(child.Name); + } - foreach (var command in Commands) - { - if (string.IsNullOrWhiteSpace(command.Name)) - throw new InvalidOperationException("This node has a command with a null or blank name {0}"); + foreach (var command in Commands) + { + if (string.IsNullOrWhiteSpace(command.Name)) + throw new InvalidOperationException("This node has a command with a null or blank name {0}"); - if (memberNames.Contains(command.Name)) - throw new InvalidOperationException($"This node contains several members named {command.Name}"); + if (memberNames.Contains(command.Name)) + throw new InvalidOperationException($"This node contains several members named {command.Name}"); - memberNames.Add(command.Name); - } + memberNames.Add(command.Name); + } - foreach (var associatedDataKey in AssociatedData.Keys) - { - if (string.IsNullOrWhiteSpace(associatedDataKey)) - throw new InvalidOperationException("This node has associated data with a null or blank name {0}"); + foreach (var associatedDataKey in AssociatedData.Keys) + { + if (string.IsNullOrWhiteSpace(associatedDataKey)) + throw new InvalidOperationException("This node has associated data with a null or blank name {0}"); - if (memberNames.Contains(associatedDataKey)) - throw new InvalidOperationException($"This node contains several members named {associatedDataKey}"); + if (memberNames.Contains(associatedDataKey)) + throw new InvalidOperationException($"This node contains several members named {associatedDataKey}"); - memberNames.Add(associatedDataKey); - } + memberNames.Add(associatedDataKey); } + } - protected override void OnPropertyChanging(params string[] propertyNames) + protected override void OnPropertyChanging(params string[] propertyNames) + { + if (DebugQuantumPropertyChanges && HasPropertyChangingSubscriber) { - if (DebugQuantumPropertyChanges && HasPropertyChangingSubscriber) + foreach (var property in propertyNames) { - foreach (var property in propertyNames) - { - owner.Logger.Debug($"Node Property changing: [{Path}].{property}"); - } + owner.Logger.Debug($"Node Property changing: [{Path}].{property}"); } - base.OnPropertyChanging(propertyNames); } + base.OnPropertyChanging(propertyNames); + } - protected override void OnPropertyChanged(params string[] propertyNames) + protected override void OnPropertyChanged(params string[] propertyNames) + { + if (DebugQuantumPropertyChanges && HasPropertyChangedSubscriber) { - if (DebugQuantumPropertyChanges && HasPropertyChangedSubscriber) + foreach (var property in propertyNames) { - foreach (var property in propertyNames) - { - owner.Logger.Debug($"Node Property changed: [{Path}].{property}"); - } + owner.Logger.Debug($"Node Property changed: [{Path}].{property}"); } - base.OnPropertyChanged(propertyNames); } + base.OnPropertyChanged(propertyNames); + } - /// - /// Indicates whether two values of are considered equal. - /// This method is used to compare values of primitive types to decide if this view model should use . - /// - /// The first value to compare. - /// The second value to compare. - /// True if both values are considered equal, false otherwise. - protected virtual bool AreValueEqual(object value1, object value2) - { - return Equals(value1, value2); - } + /// + /// Indicates whether two values of are considered equal. + /// This method is used to compare values of primitive types to decide if this view model should use . + /// + /// The first value to compare. + /// The second value to compare. + /// True if both values are considered equal, false otherwise. + protected virtual bool AreValueEqual(object value1, object value2) + { + return Equals(value1, value2); + } - /// - /// Indicates whether two values of are considered equivalent. - /// This method is used to compare values that are not primitive types to decide if this view model should use . - /// - /// The first value to compare. - /// The second value to compare. - /// True if both values are considered equivalent, false otherwise. - protected virtual bool AreValueEquivalent(object value1, object value2) - { - return value1?.GetType() == value2?.GetType(); - } + /// + /// Indicates whether two values of are considered equivalent. + /// This method is used to compare values that are not primitive types to decide if this view model should use . + /// + /// The first value to compare. + /// The second value to compare. + /// True if both values are considered equivalent, false otherwise. + protected virtual bool AreValueEquivalent(object value1, object value2) + { + return value1?.GetType() == value2?.GetType(); + } - private void ValueChanging(object sender, ValueChangingEventArgs valueChangingEventArgs) - { - OnValueChanging(); - } + private void ValueChanging(object? sender, ValueChangingEventArgs valueChangingEventArgs) + { + OnValueChanging(); + } + + private void ValueChanged(object? sender, ValueChangedEventArgs valueChangedEventArgs) + { + OnValueChanged(); + } - private void ValueChanged(object sender, ValueChangedEventArgs valueChangedEventArgs) + private void AttachedPropertyUpdated(ref PropertyContainer propertycontainer, PropertyKey propertykey, object newvalue, object? oldvalue) + { + // This is the only legit scenario where we don't want re-entrancy. valueChanging should not be tested directly in one + // of those methods, because other cases could actually be problem. + if (!valueChanging) { + OnValueChanging(); OnValueChanged(); } + } - private void AttachedPropertyUpdated(ref PropertyContainer propertycontainer, PropertyKey propertykey, object newvalue, object oldvalue) - { - // This is the only legit scenario where we don't want re-entrancy. valueChanging should not be tested directly in one - // of those methods, because other cases could actually be problem. - if (!valueChanging) - { - OnValueChanging(); - OnValueChanged(); - } - } + private void OnValueChanging() + { + valueChanging = true; + Parent?.NotifyPropertyChanging(Name); + OnPropertyChanging(nameof(NodeValue)); + } - private void OnValueChanging() + private void OnValueChanged() + { + Parent?.NotifyPropertyChanged(Name); + + OnPropertyChanging(nameof(VisibleChildrenCount)); + // This node can have been disposed by its parent already (if its parent is being refreshed and share the same source node) + // In this case, let's trigger the notifications gracefully before being discarded, but skip refresh + if (!IsDestroyed) { - valueChanging = true; - Parent?.NotifyPropertyChanging(Name); - OnPropertyChanging(nameof(NodeValue)); + Refresh(); } + UpdateViewModelProperties(); + OnPropertyChanged(nameof(VisibleChildrenCount)); - private void OnValueChanged() - { - Parent?.NotifyPropertyChanged(Name); + OnPropertyChanged(nameof(NodeValue)); + owner.NotifyNodeChanged(this); - OnPropertyChanging(nameof(VisibleChildrenCount)); - // This node can have been disposed by its parent already (if its parent is being refreshed and share the same source node) - // In this case, let's trigger the notifications gracefully before being discarded, but skip refresh - if (!IsDestroyed) - { - Refresh(); - } - UpdateViewModelProperties(); - OnPropertyChanged(nameof(VisibleChildrenCount)); + valueChanging = false; + } - OnPropertyChanged(nameof(NodeValue)); - owner.NotifyNodeChanged(this); + private object? ConvertValue(object value) + { + if (value == null) + return null; + if (!TypeConverterHelper.TryConvert(value, Type, out var convertedValue)) + throw new InvalidCastException("Can not convert value to the required type"); + return convertedValue; + } - valueChanging = false; - } + private void AddChild(NodeViewModel child) => ChangeAndNotify(() => { child.Parent = this; ((ICollection?)initializingChildren ?? children).Add(child); }, $"{GraphViewModel.HasChildPrefix}{child.Name}", child.Name); - private object ConvertValue(object value) - { - if (value == null) - return null; - object convertedValue; - if (!TypeConverterHelper.TryConvert(value, Type, out convertedValue)) - throw new InvalidCastException("Can not convert value to the required type"); - return convertedValue; - } + private void RemoveChild(NodeViewModel child) => ChangeAndNotify(() => { child.Parent = null; ((ICollection?)initializingChildren ?? children).Remove(child); }, $"{GraphViewModel.HasChildPrefix}{child.Name}", child.Name); - private void AddChild([NotNull] NodeViewModel child) => ChangeAndNotify(() => { child.Parent = this; ((ICollection)initializingChildren ?? children).Add(child); }, $"{GraphViewModel.HasChildPrefix}{child.Name}", child.Name); + private void AddCommand(NodePresenterCommandWrapper command) => ChangeAndNotify(() => commands.Add(command), $"{GraphViewModel.HasCommandPrefix}{command.Name}", command.Name); - private void RemoveChild([NotNull] NodeViewModel child) => ChangeAndNotify(() => { child.Parent = null; ((ICollection)initializingChildren ?? children).Remove(child); }, $"{GraphViewModel.HasChildPrefix}{child.Name}", child.Name); + private void RemoveCommand(NodePresenterCommandWrapper command) => ChangeAndNotify(() => commands.Remove(command), $"{GraphViewModel.HasCommandPrefix}{command.Name}", command.Name); - private void AddCommand([NotNull] NodePresenterCommandWrapper command) => ChangeAndNotify(() => commands.Add(command), $"{GraphViewModel.HasCommandPrefix}{command.Name}", command.Name); + private void AddAssociatedData(string key, object value) => ChangeAndNotify(() => associatedData.Add(key, value), $"{GraphViewModel.HasAssociatedDataPrefix}{key}", key); - private void RemoveCommand([NotNull] NodePresenterCommandWrapper command) => ChangeAndNotify(() => commands.Remove(command), $"{GraphViewModel.HasCommandPrefix}{command.Name}", command.Name); + private void RemoveAssociatedData(string key) => ChangeAndNotify(() => associatedData.Remove(key), $"{GraphViewModel.HasAssociatedDataPrefix}{key}", key); - private void AddAssociatedData([NotNull] string key, object value) => ChangeAndNotify(() => associatedData.Add(key, value), $"{GraphViewModel.HasAssociatedDataPrefix}{key}", key); + private void ChangeAndNotify(Action changeAction, params string[] propertyNames) + { + ArgumentNullException.ThrowIfNull(changeAction); + if (initializingChildren == null) + { + foreach (var propertyName in propertyNames) + NotifyPropertyChanging(propertyName); + } + changeAction(); + if (initializingChildren == null) + { + foreach (var propertyName in propertyNames) + NotifyPropertyChanged(propertyName); + } + } - private void RemoveAssociatedData([NotNull] string key) => ChangeAndNotify(() => associatedData.Remove(key), $"{GraphViewModel.HasAssociatedDataPrefix}{key}", key); + private void UpdateViewModelProperties() + { + var shouldBeVisible = false; + var shouldBeReadOnly = false; - private void ChangeAndNotify([NotNull] Action changeAction, params string[] propertyNames) + foreach (var nodePresenter in nodePresenters) { - if (changeAction == null) throw new ArgumentNullException(nameof(changeAction)); - if (initializingChildren == null) - { - foreach (var propertyName in propertyNames) - NotifyPropertyChanging(propertyName); - } - changeAction(); - if (initializingChildren == null) - { - foreach (var propertyName in propertyNames) - NotifyPropertyChanged(propertyName); - } + // Display this node if at least one presenter is visible + if (nodePresenter.IsVisible) + shouldBeVisible = true; + + // Make it read-only if at least one presenter is read-only + if (nodePresenter.IsReadOnly) + shouldBeReadOnly = true; } - private void UpdateViewModelProperties() - { - var shouldBeVisible = false; - var shouldBeReadOnly = false; + IsVisible = shouldBeVisible; + IsReadOnly = shouldBeReadOnly; - foreach (var nodePresenter in nodePresenters) - { - // Display this node if at least one presenter is visible - if (nodePresenter.IsVisible) - shouldBeVisible = true; + // TODO: find a way to "merge" display name if they are different (string.Join?) + DisplayName = Utils.SplitCamelCase(nodePresenters[0].DisplayName); - // Make it read-only if at least one presenter is read-only - if (nodePresenter.IsReadOnly) - shouldBeReadOnly = true; - } + CheckDynamicMemberConsistency(); + } - IsVisible = shouldBeVisible; - IsReadOnly = shouldBeReadOnly; + /// + public override string ToString() + { + return $"{Name}: [{GetNodeValue()}]"; + } - // TODO: find a way to "merge" display name if they are different (string.Join?) - DisplayName = Utils.SplitCamelCase(nodePresenters.First().DisplayName); + /// + /// Indicates whether the given name is reserved for the name of a property in an . Any children node with a colliding name will + /// be escaped with the method. + /// + /// The name to check. + /// True if the name is reserved, false otherwise. + public static bool IsReserved(string name) + { + return ReservedNames.Contains(name); + } - CheckDynamicMemberConsistency(); - } + /// + /// Escapes the name of a child to avoid name collision with a property. + /// + /// The name to escape. + /// The escaped name. + /// Names are escaped using a trailing underscore character. + public static string EscapeName(string name) + { + var escaped = !IsReserved(name) ? name : name + "_"; + return escaped.Replace(".", "-"); + } - /// - public override string ToString() - { - return $"{Name}: [{GetNodeValue()}]"; - } + private static int CompareChildren(NodeViewModel? a, NodeViewModel? b) + { + // Order has the best priority for comparison, if set. + if ((a?.Order ?? 0) != (b?.Order ?? 0)) + return (a?.Order ?? 0).CompareTo(b?.Order ?? 0); - /// - /// Indicates whether the given name is reserved for the name of a property in an . Any children node with a colliding name will - /// be escaped with the method. - /// - /// The name to check. - /// True if the name is reserved, false otherwise. - public static bool IsReserved(string name) + // Then, try to use metadata token (if members) + if (a?.sortingHint is var (leftType, leftToken) && b?.sortingHint is var (rightType, rightToken)) { - return ReservedNames.Contains(name); + if (leftType == rightType) + return leftToken.CompareTo(rightToken); + else + return leftType.IsSubclassOf(rightType) ? 1 : -1; } - /// - /// Escapes the name of a child to avoid name collision with a property. - /// - /// The name to escape. - /// The escaped name. - /// Names are escaped using a trailing underscore character. - [NotNull] - public static string EscapeName(string name) + // Then we use name, only if both orders are unset. + if (a?.Order == null && b?.Order == null) { - var escaped = !IsReserved(name) ? name : name + "_"; - return escaped.Replace(".", "-"); + return string.Compare(a?.Name, b?.Name, StringComparison.InvariantCultureIgnoreCase); } - private static int CompareChildren([NotNull] NodeViewModel a, [NotNull] NodeViewModel b) - { - // Order has the best priority for comparison, if set. - if ((a.Order ?? 0) != (b.Order ?? 0)) - return (a.Order ?? 0).CompareTo(b.Order ?? 0); - - // Then, try to use metadata token (if members) - if (a.sortingHint is var (leftType, leftToken) && b.sortingHint is var (rightType, rightToken)) - { - if (leftType == rightType) - return leftToken.CompareTo(rightToken); - else - return leftType.IsSubclassOf(rightType) ? 1 : -1; - } - - // Then we use name, only if both orders are unset. - if (a.Order == null && b.Order == null) - { - return string.Compare(a.Name, b.Name, StringComparison.InvariantCultureIgnoreCase); - } - - // Otherwise, the first child would be the one who have an order value. - return a.Order == null ? 1 : -1; - } + // Otherwise, the first child would be the one who have an order value. + return a?.Order == null ? 1 : -1; + } - [CanBeNull] - private static object DefaultCombineAttachedProperty([NotNull] IEnumerable arg) + private static object? DefaultCombineAttachedProperty(IEnumerable arg) + { + object? result = null; + bool isFirst = true; + foreach (var value in arg) { - object result = null; - bool isFirst = true; - foreach (var value in arg) - { - if (isFirst) - result = value; - else - result = Equals(result, value) ? result : DifferentValues; - isFirst = false; - } - return result; + if (isFirst) + result = value; + else + result = Equals(result, value) ? result : DifferentValues; + isFirst = false; } + return result; } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelDynamicMetaObject.cs b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelDynamicMetaObject.cs index c161f11e80..09a1bdc7d2 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelDynamicMetaObject.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelDynamicMetaObject.cs @@ -1,69 +1,63 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; using System.Dynamic; -using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Stride.Core.Annotations; -namespace Stride.Core.Presentation.Quantum.ViewModels +namespace Stride.Core.Presentation.Quantum.ViewModels; + +internal class NodeViewModelDynamicMetaObject : DynamicMetaObject { - internal class NodeViewModelDynamicMetaObject : DynamicMetaObject + private readonly NodeViewModel node; + + public NodeViewModelDynamicMetaObject(Expression parameter, NodeViewModel node) + : base(parameter, BindingRestrictions.Empty, node) + { + this.node = node; + } + + public override DynamicMetaObject BindGetMember(GetMemberBinder binder) { - private readonly NodeViewModel node; + var self = Expression.Convert(Expression, LimitType); - public NodeViewModelDynamicMetaObject([NotNull] Expression parameter, NodeViewModel node) - : base(parameter, BindingRestrictions.Empty, node) + Expression expression; + var propertyName = binder.Name; + var args = new Expression[1]; + + if (binder.Name.StartsWith(GraphViewModel.HasChildPrefix, StringComparison.Ordinal)) { - this.node = node; + propertyName = binder.Name[GraphViewModel.HasChildPrefix.Length..]; + args[0] = Expression.Constant(propertyName); + expression = Expression.Call(self, typeof(NodeViewModel).GetMethod(nameof(NodeViewModel.GetChild), BindingFlags.Public | BindingFlags.Instance)!, args); + expression = Expression.Convert(Expression.NotEqual(expression, Expression.Constant(null)), binder.ReturnType); } - - [NotNull] - public override DynamicMetaObject BindGetMember(GetMemberBinder binder) + else if (binder.Name.StartsWith(GraphViewModel.HasCommandPrefix, StringComparison.Ordinal)) { - var self = Expression.Convert(Expression, LimitType); - - Expression expression; - var propertyName = binder.Name; - var args = new Expression[1]; - - if (binder.Name.StartsWith(GraphViewModel.HasChildPrefix, StringComparison.Ordinal)) - { - propertyName = binder.Name.Substring(GraphViewModel.HasChildPrefix.Length); - args[0] = Expression.Constant(propertyName); - expression = Expression.Call(self, typeof(NodeViewModel).GetMethod(nameof(NodeViewModel.GetChild), BindingFlags.Public | BindingFlags.Instance), args); - expression = Expression.Convert(Expression.NotEqual(expression, Expression.Constant(null)), binder.ReturnType); - } - else if (binder.Name.StartsWith(GraphViewModel.HasCommandPrefix, StringComparison.Ordinal)) - { - propertyName = binder.Name.Substring(GraphViewModel.HasCommandPrefix.Length); - args[0] = Expression.Constant(propertyName); - expression = Expression.Call(self, typeof(NodeViewModel).GetMethod(nameof(NodeViewModel.GetCommand), BindingFlags.Public | BindingFlags.Instance), args); - expression = Expression.Convert(Expression.NotEqual(expression, Expression.Constant(null)), binder.ReturnType); - } - else if (binder.Name.StartsWith(GraphViewModel.HasAssociatedDataPrefix, StringComparison.Ordinal)) - { - propertyName = binder.Name.Substring(GraphViewModel.HasAssociatedDataPrefix.Length); - args[0] = Expression.Constant(propertyName); - expression = Expression.Call(self, typeof(NodeViewModel).GetMethod(nameof(NodeViewModel.GetAssociatedData), BindingFlags.Public | BindingFlags.Instance), args); - expression = Expression.Convert(Expression.NotEqual(expression, Expression.Constant(null)), binder.ReturnType); - } - else - { - args[0] = Expression.Constant(propertyName); - expression = Expression.Call(self, typeof(NodeViewModel).GetMethod(nameof(NodeViewModel.GetDynamicObject), BindingFlags.Public | BindingFlags.Instance), args); - } - - var getMember = new DynamicMetaObject(expression, BindingRestrictions.GetTypeRestriction(Expression, LimitType)); - return getMember; + propertyName = binder.Name[GraphViewModel.HasCommandPrefix.Length..]; + args[0] = Expression.Constant(propertyName); + expression = Expression.Call(self, typeof(NodeViewModel).GetMethod(nameof(NodeViewModel.GetCommand), BindingFlags.Public | BindingFlags.Instance)!, args); + expression = Expression.Convert(Expression.NotEqual(expression, Expression.Constant(null)), binder.ReturnType); } - - public override IEnumerable GetDynamicMemberNames() + else if (binder.Name.StartsWith(GraphViewModel.HasAssociatedDataPrefix, StringComparison.Ordinal)) + { + propertyName = binder.Name[GraphViewModel.HasAssociatedDataPrefix.Length..]; + args[0] = Expression.Constant(propertyName); + expression = Expression.Call(self, typeof(NodeViewModel).GetMethod(nameof(NodeViewModel.GetAssociatedData), BindingFlags.Public | BindingFlags.Instance)!, args); + expression = Expression.Convert(Expression.NotEqual(expression, Expression.Constant(null)), binder.ReturnType); + } + else { - return node.Children.Select(x => x.Name).Concat(node.Commands.Select(x => x.Name)).Concat(node.AssociatedData.Select(x => x.Key)); + args[0] = Expression.Constant(propertyName); + expression = Expression.Call(self, typeof(NodeViewModel).GetMethod(nameof(NodeViewModel.GetDynamicObject), BindingFlags.Public | BindingFlags.Instance)!, args); } + + var getMember = new DynamicMetaObject(expression, BindingRestrictions.GetTypeRestriction(Expression, LimitType)); + return getMember; + } + + public override IEnumerable GetDynamicMemberNames() + { + return node.Children.Select(x => x.Name).Concat(node.Commands.Select(x => x.Name)).Concat(node.AssociatedData.Select(x => x.Key)); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelFactory.cs b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelFactory.cs index b2ef15ac44..57d63672c2 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelFactory.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelFactory.cs @@ -1,98 +1,94 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using Stride.Core; + using Stride.Core.Annotations; using Stride.Core.Presentation.Quantum.Presenters; -namespace Stride.Core.Presentation.Quantum.ViewModels +namespace Stride.Core.Presentation.Quantum.ViewModels; + +public class NodeViewModelFactory : INodeViewModelFactory { - public class NodeViewModelFactory : INodeViewModelFactory + public NodeViewModel CreateGraph(GraphViewModel owner, Type rootType, IEnumerable rootNodes) { - public NodeViewModel CreateGraph(GraphViewModel owner, Type rootType, IEnumerable rootNodes) - { - var rootViewModelNode = CreateNodeViewModel(owner, null, rootType, rootNodes.ToList(), true); - return rootViewModelNode; - } + var rootViewModelNode = CreateNodeViewModel(owner, null, rootType, rootNodes.ToList(), true); + return rootViewModelNode; + } - public void GenerateChildren(GraphViewModel owner, NodeViewModel parent, List nodePresenters) + public void GenerateChildren(GraphViewModel owner, NodeViewModel parent, List nodePresenters) + { + foreach (var child in CombineChildren(nodePresenters)) { - foreach (var child in CombineChildren(nodePresenters)) + if (ShouldConstructViewModel(child)) { - if (ShouldConstructViewModel(child)) + Type? type = null; + var typeMatch = true; + foreach (var childPresenter in child) { - Type type = null; - var typeMatch = true; - foreach (var childPresenter in child) + if (type == null) { - if (type == null) - { - type = childPresenter.Type; - } - else if (type != childPresenter.Type && type.IsAssignableFrom(childPresenter.Type)) - { - type = childPresenter.Type; - } - else if (type != childPresenter.Type) - { - typeMatch = false; - break; - } + type = childPresenter.Type; } - if (typeMatch) + else if (type != childPresenter.Type && type.IsAssignableFrom(childPresenter.Type)) { - CreateNodeViewModel(owner, parent, child.First().Type, child); + type = childPresenter.Type; + } + else if (type != childPresenter.Type) + { + typeMatch = false; + break; } } + if (typeMatch) + { + CreateNodeViewModel(owner, parent, child.First().Type, child); + } } } + } - [NotNull] - protected virtual NodeViewModel CreateNodeViewModel([NotNull] GraphViewModel owner, NodeViewModel parent, Type nodeType, [NotNull] List nodePresenters, bool isRootNode = false) + [NotNull] + protected virtual NodeViewModel CreateNodeViewModel(GraphViewModel owner, NodeViewModel parent, Type nodeType, List nodePresenters, bool isRootNode = false) + { + // TODO: properly compute the name + var viewModel = new NodeViewModel(owner, parent, nodePresenters.First().Name, nodeType, nodePresenters); + if (isRootNode) { - // TODO: properly compute the name - var viewModel = new NodeViewModel(owner, parent, nodePresenters.First().Name, nodeType, nodePresenters); - if (isRootNode) - { - owner.RootNode = viewModel; - } - viewModel.Refresh(); - return viewModel; + owner.RootNode = viewModel; } + viewModel.Refresh(); + return viewModel; + } - [NotNull] - protected virtual IEnumerable> CombineChildren([NotNull] List nodePresenters) + [NotNull] + protected virtual IEnumerable> CombineChildren(List nodePresenters) + { + var dictionary = new Dictionary>(); + foreach (var nodePresenter in nodePresenters) { - var dictionary = new Dictionary>(); - foreach (var nodePresenter in nodePresenters) + foreach (var child in nodePresenter.Children) { - foreach (var child in nodePresenter.Children) + List presenters; + // TODO: properly implement CombineKey + if (!dictionary.TryGetValue(child.CombineKey, out presenters)) { - List presenters; - // TODO: properly implement CombineKey - if (!dictionary.TryGetValue(child.CombineKey, out presenters)) - { - presenters = new List(); - dictionary.Add(child.CombineKey, presenters); - } - presenters.Add(child); + presenters = new List(); + dictionary.Add(child.CombineKey, presenters); } + presenters.Add(child); } - return dictionary.Values.Where(x => x.Count == nodePresenters.Count); } + return dictionary.Values.Where(x => x.Count == nodePresenters.Count); + } - private static bool ShouldConstructViewModel([NotNull] List nodePresenters) + private static bool ShouldConstructViewModel(List nodePresenters) + { + foreach (var nodePresenter in nodePresenters) { - foreach (var nodePresenter in nodePresenters) - { - var member = nodePresenter as MemberNodePresenter; - var displayAttribute = member?.MemberAttributes.OfType().FirstOrDefault(); - if (displayAttribute != null && !displayAttribute.Browsable) - return false; - } - return true; + var member = nodePresenter as MemberNodePresenter; + var displayAttribute = member?.MemberAttributes.OfType().FirstOrDefault(); + if (displayAttribute != null && !displayAttribute.Browsable) + return false; } + return true; } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelValueChangedArgs.cs b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelValueChangedArgs.cs index d72b702bcc..787c738898 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelValueChangedArgs.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum/ViewModels/NodeViewModelValueChangedArgs.cs @@ -1,21 +1,17 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System; -using System.Collections.Generic; +namespace Stride.Core.Presentation.Quantum.ViewModels; -namespace Stride.Core.Presentation.Quantum.ViewModels +public class NodeViewModelValueChangedArgs : EventArgs { - public class NodeViewModelValueChangedArgs : EventArgs + public NodeViewModelValueChangedArgs(GraphViewModel viewModel, NodeViewModel node) { - public NodeViewModelValueChangedArgs(GraphViewModel viewModel, NodeViewModel node) - { - ViewModel = viewModel; - Node = node; - } + ViewModel = viewModel; + Node = node; + } - public GraphViewModel ViewModel { get; private set; } + public GraphViewModel ViewModel { get; } - public NodeViewModel Node { get; private set; } - } + public NodeViewModel Node { get; } } From e2303c59d42e90c09482387eb54a3cc9e933b1bb Mon Sep 17 00:00:00 2001 From: Nicolas Musset Date: Tue, 25 Feb 2025 11:51:36 +0100 Subject: [PATCH 6/6] [Core.Presentation.Quantum.Tests] Modernize code --- .../Helpers/TestContainerContext.cs | 41 +- .../Helpers/TestInstanceContext.cs | 34 +- .../Helpers/Types.cs | 108 +- .../Properties/AssemblyInfo.cs | 3 +- ...ide.Core.Presentation.Quantum.Tests.csproj | 12 +- .../TestDependentProperties.cs | 434 ++++---- .../TestNodePresenterProperties.cs | 321 +++--- .../TestNodePresenterUpdates.cs | 949 +++++++++--------- 8 files changed, 946 insertions(+), 956 deletions(-) diff --git a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/TestContainerContext.cs b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/TestContainerContext.cs index be98a2c1b7..e07c9666ec 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/TestContainerContext.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/TestContainerContext.cs @@ -1,34 +1,33 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Windows.Threading; -using Stride.Core.Presentation.View; + +using Stride.Core.Presentation.Services; using Stride.Core.Presentation.ViewModels; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Tests.Helpers +namespace Stride.Core.Presentation.Quantum.Tests.Helpers; + +public class TestContainerContext { - public class TestContainerContext + public TestContainerContext() { - public TestContainerContext() - { - NodeContainer = new NodeContainer(); - GraphViewModelService = new GraphViewModelService(NodeContainer); - ServiceProvider = new ViewModelServiceProvider(); - ServiceProvider.RegisterService(new DispatcherService(Dispatcher.CurrentDispatcher)); - ServiceProvider.RegisterService(GraphViewModelService); - } + NodeContainer = new NodeContainer(); + GraphViewModelService = new GraphViewModelService(NodeContainer); + ServiceProvider = new ViewModelServiceProvider(); + ServiceProvider.RegisterService(new NullDispatcherService()); + ServiceProvider.RegisterService(GraphViewModelService); + } - public ViewModelServiceProvider ServiceProvider { get; } + public ViewModelServiceProvider ServiceProvider { get; } - public GraphViewModelService GraphViewModelService { get; } + public GraphViewModelService GraphViewModelService { get; } - public NodeContainer NodeContainer { get; } + public NodeContainer NodeContainer { get; } - public TestInstanceContext CreateInstanceContext(object instance) - { - var rootNode = NodeContainer.GetOrCreateNode(instance); - var context = new TestInstanceContext(this, rootNode); - return context; - } + public TestInstanceContext CreateInstanceContext(object instance) + { + var rootNode = NodeContainer.GetOrCreateNode(instance); + var context = new TestInstanceContext(this, rootNode); + return context; } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/TestInstanceContext.cs b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/TestInstanceContext.cs index 153fc2d42a..d6711f8f7f 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/TestInstanceContext.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/TestInstanceContext.cs @@ -1,31 +1,31 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. + using Stride.Core.Presentation.Quantum.Presenters; using Stride.Core.Presentation.Quantum.ViewModels; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Tests.Helpers +namespace Stride.Core.Presentation.Quantum.Tests.Helpers; + +public class TestInstanceContext { - public class TestInstanceContext - { - private readonly TestContainerContext context; + private readonly TestContainerContext context; - public TestInstanceContext(TestContainerContext context, IObjectNode rootNode) - { - this.context = context; - RootNode = rootNode; - PropertyProvider = new Types.TestPropertyProvider(rootNode); - } + public TestInstanceContext(TestContainerContext context, IObjectNode rootNode) + { + this.context = context; + RootNode = rootNode; + PropertyProvider = new Types.TestPropertyProvider(rootNode); + } - public IPropertyProviderViewModel PropertyProvider { get; } + public IPropertyProviderViewModel PropertyProvider { get; } - public IObjectNode RootNode { get; } + public IObjectNode RootNode { get; } - public INodePresenterFactory Factory => context.GraphViewModelService.NodePresenterFactory; + public INodePresenterFactory Factory => context.GraphViewModelService.NodePresenterFactory; - public GraphViewModel CreateViewModel() - { - return GraphViewModel.Create(context.ServiceProvider, new[] { PropertyProvider }); - } + public GraphViewModel CreateViewModel() + { + return GraphViewModel.Create(context.ServiceProvider, [PropertyProvider])!; } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/Types.cs b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/Types.cs index dfbca6810b..293af19d8e 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/Types.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Helpers/Types.cs @@ -1,76 +1,72 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Stride.Core; -using Stride.Core.Presentation.Quantum.Presenters; + using Stride.Core.Quantum; -using Stride.Core.Quantum.References; -namespace Stride.Core.Presentation.Quantum.Tests.Helpers +namespace Stride.Core.Presentation.Quantum.Tests.Helpers; + +public static class Types { - public static class Types + public class TestPropertyProvider : IPropertyProviderViewModel { - public class TestPropertyProvider : IPropertyProviderViewModel - { - private readonly IObjectNode rootNode; + private readonly IObjectNode rootNode; - public TestPropertyProvider(IObjectNode rootNode) - { - this.rootNode = rootNode; - } - public bool CanProvidePropertiesViewModel => true; + public TestPropertyProvider(IObjectNode rootNode) + { + this.rootNode = rootNode; + } + public bool CanProvidePropertiesViewModel => true; - public IObjectNode GetRootNode() - { - return rootNode; - } + public IObjectNode GetRootNode() + { + return rootNode; + } - bool IPropertyProviderViewModel.ShouldConstructMember(IMemberNode member) => true; + bool IPropertyProviderViewModel.ShouldConstructMember(IMemberNode member) => true; - bool IPropertyProviderViewModel.ShouldConstructItem(IObjectNode collection, NodeIndex index) => true; - } + bool IPropertyProviderViewModel.ShouldConstructItem(IObjectNode collection, NodeIndex index) => true; + } - public class SimpleObject - { - public string Name { get; set; } + public class SimpleObject + { + public required string Name { get; set; } - public string Nam { get; set; } // To test scenario when Name.StartsWith(Nam) is true - } + public string? Nam { get; set; } // To test scenario when Name.StartsWith(Nam) is true + } - public class DependentPropertyContainer - { - public string Title { get; set; } + public class DependentPropertyContainer + { + public required string Title { get; set; } - public SimpleObject Instance { get; set; } - } + public required SimpleObject Instance { get; set; } + } - public class SimpleType - { - public string String { get; set; } - } + public class SimpleType + { + public required string String { get; set; } + } - public class ClassWithRef - { - [Display(1)] - public string String { get; set; } - [Display(2)] - public ClassWithRef Ref { get; set; } - } + public class ClassWithRef + { + [Display(1)] + public required string String { get; set; } + [Display(2)] + public ClassWithRef? Ref { get; set; } + } - public class ClassWithCollection - { - [Display(1)] - public string String { get; set; } - [Display(2)] - public List List { get; set; } = new List(); - } + public class ClassWithCollection + { + [Display(1)] + public required string String { get; set; } + [Display(2)] + public List List { get; set; } = []; + } - public class ClassWithRefCollection - { - [Display(1)] - public string String { get; set; } - [Display(2)] - public List List { get; set; } = new List(); - } + public class ClassWithRefCollection + { + [Display(1)] + public required string String { get; set; } + [Display(2)] + public List List { get; set; } = []; } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Properties/AssemblyInfo.cs b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Properties/AssemblyInfo.cs index e4a851278e..771fc09ab3 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Properties/AssemblyInfo.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Properties/AssemblyInfo.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Reflection; -using System.Runtime.CompilerServices; + using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Stride.Core.Presentation.Quantum.Tests.csproj b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Stride.Core.Presentation.Quantum.Tests.csproj index 6f1197be1a..7a49c5adbe 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Stride.Core.Presentation.Quantum.Tests.csproj +++ b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/Stride.Core.Presentation.Quantum.Tests.csproj @@ -1,11 +1,16 @@ - $(StrideEditorTargetFramework) + $(StrideXplatEditorTargetFramework) win-x64 + enable + latest + enable WindowsTools - true + + + @@ -16,7 +21,6 @@ - @@ -24,4 +28,4 @@ - \ No newline at end of file + diff --git a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestDependentProperties.cs b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestDependentProperties.cs index 2c42d29f98..00c360a1db 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestDependentProperties.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestDependentProperties.cs @@ -1,247 +1,245 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using Xunit; -using Stride.Core; + using Stride.Core.Presentation.Quantum.Presenters; using Stride.Core.Presentation.Quantum.Tests.Helpers; -namespace Stride.Core.Presentation.Quantum.Tests -{ - // TODO: this class should be rewritten to properly match the new design of dependent properties, which is using hard-link between nodes instead of path-based. - public class TestDependentProperties - { - private const string Title = nameof(Types.DependentPropertyContainer.Title); - private const string Instance = nameof(Types.DependentPropertyContainer.Instance); - private const string Name = nameof(Types.SimpleObject.Name); - private const string Nam = nameof(Types.SimpleObject.Nam); +namespace Stride.Core.Presentation.Quantum.Tests; - private const string TestDataKey = "TestData"; - private const string UpdateCountKey = "UpdateCount"; - private static readonly PropertyKey TestData = new PropertyKey(TestDataKey, typeof(TestDependentProperties)); - private static readonly PropertyKey UpdateCount = new PropertyKey(UpdateCountKey, typeof(TestDependentProperties)); +// TODO: this class should be rewritten to properly match the new design of dependent properties, which is using hard-link between nodes instead of path-based. +public class TestDependentProperties +{ + private const string Title = nameof(Types.DependentPropertyContainer.Title); + private const string Instance = nameof(Types.DependentPropertyContainer.Instance); + private const string Name = nameof(Types.SimpleObject.Name); + private const string Nam = nameof(Types.SimpleObject.Nam); - private abstract class DependentPropertiesUpdater : NodePresenterUpdaterBase - { - private int count; - protected abstract bool IsRecursive { get; } + private const string TestDataKey = "TestData"; + private const string UpdateCountKey = "UpdateCount"; + private static readonly PropertyKey TestData = new(TestDataKey, typeof(TestDependentProperties)); + private static readonly PropertyKey UpdateCount = new(UpdateCountKey, typeof(TestDependentProperties)); - public override void UpdateNode(INodePresenter node) - { - if (node.Name == nameof(Types.DependentPropertyContainer.Title)) - { - var instance = (Types.DependentPropertyContainer)node.Root.Value; - node.AttachedProperties.Set(TestData, instance.Instance.Name); - node.AttachedProperties.Set(UpdateCount, count++); - } - } + private abstract class DependentPropertiesUpdater : NodePresenterUpdaterBase + { + private int count; + protected abstract bool IsRecursive { get; } - public override void FinalizeTree(INodePresenter root) + public override void UpdateNode(INodePresenter node) + { + if (node.Name == nameof(Types.DependentPropertyContainer.Title)) { - var node = root[Title]; - var dependencyNode = GetDependencyNode(node.Root); - node.AddDependency(dependencyNode, IsRecursive); + var instance = (Types.DependentPropertyContainer)node.Root.Value; + node.AttachedProperties.Set(TestData, instance.Instance.Name); + node.AttachedProperties.Set(UpdateCount, count++); } - - protected abstract INodePresenter GetDependencyNode(INodePresenter rootNode); } - private class SimpleDependentPropertiesUpdater : DependentPropertiesUpdater + public override void FinalizeTree(INodePresenter root) { - protected override bool IsRecursive => false; - - protected override INodePresenter GetDependencyNode(INodePresenter rootNode) - { - return rootNode[Instance][Name]; - } + var node = root[Title]; + var dependencyNode = GetDependencyNode(node.Root); + node.AddDependency(dependencyNode, IsRecursive); } - private class RecursiveDependentPropertiesUpdater : DependentPropertiesUpdater - { - protected override bool IsRecursive => true; + protected abstract INodePresenter GetDependencyNode(INodePresenter rootNode); + } - protected override INodePresenter GetDependencyNode(INodePresenter rootNode) - { - return rootNode[Instance]; - } - } + private class SimpleDependentPropertiesUpdater : DependentPropertiesUpdater + { + protected override bool IsRecursive => false; - [Fact] - public void TestSimpleDependency() + protected override INodePresenter GetDependencyNode(INodePresenter rootNode) { - var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; - var testContext = new TestContainerContext(); - var instanceContext = testContext.CreateInstanceContext(container); - testContext.GraphViewModelService.AvailableUpdaters.Add(new SimpleDependentPropertiesUpdater()); - var viewModel = instanceContext.CreateViewModel(); - var titleNode = viewModel.RootNode.GetChild(Title); - var nameNode = viewModel.RootNode.GetChild(Instance).GetChild(Name); - - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); - - nameNode.NodeValue = "NewValue"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); - - nameNode.NodeValue = "NewValue2"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); + return rootNode[Instance][Name]; } + } - [Fact(Skip = "Review why this test is failing and refactor to match new design of dependent properties.")] - public void TestSimpleDependencyChangeParent() - { - var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; - var testContext = new TestContainerContext(); - var instanceContext = testContext.CreateInstanceContext(container); - testContext.GraphViewModelService.AvailableUpdaters.Add(new SimpleDependentPropertiesUpdater()); - var viewModel = instanceContext.CreateViewModel(); - var titleNode = viewModel.RootNode.GetChild(Title); - var instanceNode = viewModel.RootNode.GetChild(Instance); - - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); - - instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue" }; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); - - instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue2" }; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); - } + private class RecursiveDependentPropertiesUpdater : DependentPropertiesUpdater + { + protected override bool IsRecursive => true; - [Fact] - public void TestRecursiveDependency() + protected override INodePresenter GetDependencyNode(INodePresenter rootNode) { - var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; - var testContext = new TestContainerContext(); - var instanceContext = testContext.CreateInstanceContext(container); - testContext.GraphViewModelService.AvailableUpdaters.Add(new RecursiveDependentPropertiesUpdater()); - var viewModel = instanceContext.CreateViewModel(); - var titleNode = viewModel.RootNode.GetChild(Title); - var instanceNode = viewModel.RootNode.GetChild(Instance); - - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); - - instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue" }; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); - - instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue2" }; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); + return rootNode[Instance]; } + } - [Fact(Skip = "Review why this test is failing and refactor to match new design of dependent properties.")] - public void TestRecursiveDependencyChangeChild() - { - var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; - var testContext = new TestContainerContext(); - var instanceContext = testContext.CreateInstanceContext(container); - testContext.GraphViewModelService.AvailableUpdaters.Add(new RecursiveDependentPropertiesUpdater()); - var viewModel = instanceContext.CreateViewModel(); - var titleNode = viewModel.RootNode.GetChild(Title); - var nameNode = viewModel.RootNode.GetChild(Instance).GetChild(Name); - - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); - - nameNode.NodeValue = "NewValue"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); - - nameNode.NodeValue = "NewValue2"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); - } + [Fact] + public void TestSimpleDependency() + { + var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; + var testContext = new TestContainerContext(); + var instanceContext = testContext.CreateInstanceContext(container); + testContext.GraphViewModelService.AvailableUpdaters.Add(new SimpleDependentPropertiesUpdater()); + var viewModel = instanceContext.CreateViewModel(); + var titleNode = viewModel.RootNode.GetChild(Title)!; + var nameNode = viewModel.RootNode.GetChild(Instance)!.GetChild(Name)!; + + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); + + nameNode.NodeValue = "NewValue"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); + + nameNode.NodeValue = "NewValue2"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); + } - [Fact(Skip = "Review why this test is failing and refactor to match new design of dependent properties.")] - public void TestRecursiveDependencyMixedChanges() - { - var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; - var testContext = new TestContainerContext(); - var instanceContext = testContext.CreateInstanceContext(container); - testContext.GraphViewModelService.AvailableUpdaters.Add(new RecursiveDependentPropertiesUpdater()); - var viewModel = instanceContext.CreateViewModel(); - var titleNode = viewModel.RootNode.GetChild(Title); - var instanceNode = viewModel.RootNode.GetChild(Instance); - - var nameNode = viewModel.RootNode.GetChild(Instance).GetChild(Name); - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); - - nameNode.NodeValue = "NewValue"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); - - instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue2" }; - nameNode = viewModel.RootNode.GetChild(Instance).GetChild(Name); - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); - - nameNode.NodeValue = "NewValue3"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue3", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(3, titleNode.AssociatedData[UpdateCountKey]); - - instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue4" }; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue4", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(4, titleNode.AssociatedData[UpdateCountKey]); - } + [Fact(Skip = "Review why this test is failing and refactor to match new design of dependent properties.")] + public void TestSimpleDependencyChangeParent() + { + var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; + var testContext = new TestContainerContext(); + var instanceContext = testContext.CreateInstanceContext(container); + testContext.GraphViewModelService.AvailableUpdaters.Add(new SimpleDependentPropertiesUpdater()); + var viewModel = instanceContext.CreateViewModel(); + var titleNode = viewModel.RootNode.GetChild(Title)!; + var instanceNode = viewModel.RootNode.GetChild(Instance)!; + + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); + + instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue" }; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); + + instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue2" }; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); + } - [Fact] - public void TestChangeDifferentPropertyWithSameStart() - { - var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; - var testContext = new TestContainerContext(); - var instanceContext = testContext.CreateInstanceContext(container); - testContext.GraphViewModelService.AvailableUpdaters.Add(new SimpleDependentPropertiesUpdater()); - var viewModel = instanceContext.CreateViewModel(); - var titleNode = viewModel.RootNode.GetChild(Title); - var nameNode = viewModel.RootNode.GetChild(Instance).GetChild(Name); - var namNode = viewModel.RootNode.GetChild(Instance).GetChild(Nam); - - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); - - namNode.NodeValue = "NewValue"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); - - nameNode.NodeValue = "NewValue2"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); - - namNode.NodeValue = "NewValue3"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); - - nameNode.NodeValue = "NewValue4"; - Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); - Assert.Equal("NewValue4", titleNode.AssociatedData[TestDataKey]); - Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); - } + [Fact] + public void TestRecursiveDependency() + { + var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; + var testContext = new TestContainerContext(); + var instanceContext = testContext.CreateInstanceContext(container); + testContext.GraphViewModelService.AvailableUpdaters.Add(new RecursiveDependentPropertiesUpdater()); + var viewModel = instanceContext.CreateViewModel(); + var titleNode = viewModel.RootNode.GetChild(Title)!; + var instanceNode = viewModel.RootNode.GetChild(Instance)!; + + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); + + instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue" }; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); + + instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue2" }; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); + } + + [Fact(Skip = "Review why this test is failing and refactor to match new design of dependent properties.")] + public void TestRecursiveDependencyChangeChild() + { + var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; + var testContext = new TestContainerContext(); + var instanceContext = testContext.CreateInstanceContext(container); + testContext.GraphViewModelService.AvailableUpdaters.Add(new RecursiveDependentPropertiesUpdater()); + var viewModel = instanceContext.CreateViewModel(); + var titleNode = viewModel.RootNode.GetChild(Title)!; + var nameNode = viewModel.RootNode.GetChild(Instance)!.GetChild(Name)!; + + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); + + nameNode.NodeValue = "NewValue"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); + + nameNode.NodeValue = "NewValue2"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); + } + + [Fact(Skip = "Review why this test is failing and refactor to match new design of dependent properties.")] + public void TestRecursiveDependencyMixedChanges() + { + var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; + var testContext = new TestContainerContext(); + var instanceContext = testContext.CreateInstanceContext(container); + testContext.GraphViewModelService.AvailableUpdaters.Add(new RecursiveDependentPropertiesUpdater()); + var viewModel = instanceContext.CreateViewModel(); + var titleNode = viewModel.RootNode.GetChild(Title)!; + var instanceNode = viewModel.RootNode.GetChild(Instance)!; + + var nameNode = viewModel.RootNode.GetChild(Instance)!.GetChild(Name)!; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); + + nameNode.NodeValue = "NewValue"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); + + instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue2" }; + nameNode = viewModel.RootNode.GetChild(Instance)!.GetChild(Name)!; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); + + nameNode.NodeValue = "NewValue3"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue3", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(3, titleNode.AssociatedData[UpdateCountKey]); + + instanceNode.NodeValue = new Types.SimpleObject { Name = "NewValue4" }; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue4", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(4, titleNode.AssociatedData[UpdateCountKey]); + } + + [Fact] + public void TestChangeDifferentPropertyWithSameStart() + { + var container = new Types.DependentPropertyContainer { Title = "Title", Instance = new Types.SimpleObject { Name = "Test" } }; + var testContext = new TestContainerContext(); + var instanceContext = testContext.CreateInstanceContext(container); + testContext.GraphViewModelService.AvailableUpdaters.Add(new SimpleDependentPropertiesUpdater()); + var viewModel = instanceContext.CreateViewModel(); + var titleNode = viewModel.RootNode.GetChild(Title)!; + var nameNode = viewModel.RootNode.GetChild(Instance)!.GetChild(Name)!; + var namNode = viewModel.RootNode.GetChild(Instance)!.GetChild(Nam)!; + + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); + + namNode.NodeValue = "NewValue"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("Test", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(0, titleNode.AssociatedData[UpdateCountKey]); + + nameNode.NodeValue = "NewValue2"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); + + namNode.NodeValue = "NewValue3"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue2", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(1, titleNode.AssociatedData[UpdateCountKey]); + + nameNode.NodeValue = "NewValue4"; + Assert.True(titleNode.AssociatedData.ContainsKey(TestDataKey)); + Assert.Equal("NewValue4", titleNode.AssociatedData[TestDataKey]); + Assert.Equal(2, titleNode.AssociatedData[UpdateCountKey]); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestNodePresenterProperties.cs b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestNodePresenterProperties.cs index 4c7ff53e37..d3e49c1c03 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestNodePresenterProperties.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestNodePresenterProperties.cs @@ -1,182 +1,179 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using Xunit; -using Stride.Core; + using Stride.Core.Presentation.Quantum.Tests.Helpers; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Tests +namespace Stride.Core.Presentation.Quantum.Tests; + +public class TestNodePresenterProperties { - public class TestNodePresenterProperties + [DataContract] + public class SimpleMember { - [DataContract] - public class SimpleMember - { - public float FloatValue { get; set; } - } + public float FloatValue { get; set; } + } - [DataContract] - public class SimpleMemberWithContract - { - [DataMember(10)] - public float FloatValue { get; set; } - } + [DataContract] + public class SimpleMemberWithContract + { + [DataMember(10)] + public float FloatValue { get; set; } + } - public class NestedMemberClass - { - [DataMember(20)] - public SimpleMemberWithContract MemberClass { get; set; } = new SimpleMemberWithContract(); - } + public class NestedMemberClass + { + [DataMember(20)] + public SimpleMemberWithContract MemberClass { get; set; } = new SimpleMemberWithContract(); + } - public class NestedReadonlyMemberClass - { - [DataMember(30)] - public SimpleMemberWithContract MemberClass { get; } = new SimpleMemberWithContract(); - } + public class NestedReadonlyMemberClass + { + [DataMember(30)] + public SimpleMemberWithContract MemberClass { get; } = new SimpleMemberWithContract(); + } - public class ListMember - { - [DataMember(40)] - public List List { get; set; } - } + public class ListMember + { + [DataMember(40)] + public List? List { get; set; } + } - [Fact] - public void TestSimpleMember() - { - var instance = new SimpleMember { FloatValue = 1.0f }; - var context = BuildContext(instance); - var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - var member = root[nameof(SimpleMember.FloatValue)]; - Assert.Equal(0, member.Children.Count); - Assert.Equal(nameof(SimpleMember.FloatValue), member.DisplayName); - Assert.Equal(NodeIndex.Empty, member.Index); - Assert.False(member.IsEnumerable); - Assert.False(member.IsReadOnly); - Assert.True(member.IsVisible); - Assert.Equal(nameof(SimpleMember.FloatValue), member.Name); - Assert.Null(member.Order); - Assert.Equal(root, member.Parent); - Assert.Equal(1.0f, member.Value); - } + [Fact] + public void TestSimpleMember() + { + var instance = new SimpleMember { FloatValue = 1.0f }; + var context = BuildContext(instance); + var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + var member = root[nameof(SimpleMember.FloatValue)]; + Assert.Empty(member.Children); + Assert.Equal(nameof(SimpleMember.FloatValue), member.DisplayName); + Assert.Equal(NodeIndex.Empty, member.Index); + Assert.False(member.IsEnumerable); + Assert.False(member.IsReadOnly); + Assert.True(member.IsVisible); + Assert.Equal(nameof(SimpleMember.FloatValue), member.Name); + Assert.Null(member.Order); + Assert.Equal(root, member.Parent); + Assert.Equal(1.0f, member.Value); + } - [Fact] - public void TestSimpleMemberWithContract() - { - var instance = new SimpleMemberWithContract { FloatValue = 1.0f }; - var context = BuildContext(instance); - var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - var member = root[nameof(SimpleMember.FloatValue)]; - Assert.Equal(0, member.Children.Count); - Assert.Equal(nameof(SimpleMember.FloatValue), member.DisplayName); - Assert.Equal(NodeIndex.Empty, member.Index); - Assert.False(member.IsEnumerable); - Assert.False(member.IsReadOnly); - Assert.True(member.IsVisible); - Assert.Equal(nameof(SimpleMember.FloatValue), member.Name); - Assert.Equal(10, member.Order); - Assert.Equal(root, member.Parent); - Assert.Equal(1.0f, member.Value); - } + [Fact] + public void TestSimpleMemberWithContract() + { + var instance = new SimpleMemberWithContract { FloatValue = 1.0f }; + var context = BuildContext(instance); + var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + var member = root[nameof(SimpleMember.FloatValue)]; + Assert.Empty(member.Children); + Assert.Equal(nameof(SimpleMember.FloatValue), member.DisplayName); + Assert.Equal(NodeIndex.Empty, member.Index); + Assert.False(member.IsEnumerable); + Assert.False(member.IsReadOnly); + Assert.True(member.IsVisible); + Assert.Equal(nameof(SimpleMember.FloatValue), member.Name); + Assert.Equal(10, member.Order); + Assert.Equal(root, member.Parent); + Assert.Equal(1.0f, member.Value); + } - [Fact] - public void TestNestedMember() - { - var instance = new NestedMemberClass { MemberClass = { FloatValue = 1.0f } }; - var context = BuildContext(instance); - var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - var member = root[nameof(NestedMemberClass.MemberClass)]; - Assert.Equal(1, member.Children.Count); - Assert.Equal(nameof(NestedMemberClass.MemberClass), member.DisplayName); - Assert.Equal(NodeIndex.Empty, member.Index); - Assert.False(member.IsEnumerable); - Assert.False(member.IsReadOnly); - Assert.True(member.IsVisible); - Assert.Equal(nameof(NestedMemberClass.MemberClass), member.Name); - Assert.Equal(20, member.Order); - Assert.Equal(root, member.Parent); - Assert.Equal(instance.MemberClass, member.Value); - var innerMember = member[nameof(SimpleMember.FloatValue)]; - Assert.Equal(0, innerMember.Children.Count); - Assert.Equal(nameof(SimpleMember.FloatValue), innerMember.DisplayName); - Assert.Equal(NodeIndex.Empty, innerMember.Index); - Assert.False(innerMember.IsEnumerable); - Assert.False(innerMember.IsReadOnly); - Assert.True(innerMember.IsVisible); - Assert.Equal(nameof(SimpleMember.FloatValue), innerMember.Name); - Assert.Equal(10, innerMember.Order); - Assert.Equal(member, innerMember.Parent); - Assert.Equal(1.0f, innerMember.Value); - } + [Fact] + public void TestNestedMember() + { + var instance = new NestedMemberClass { MemberClass = { FloatValue = 1.0f } }; + var context = BuildContext(instance); + var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + var member = root[nameof(NestedMemberClass.MemberClass)]; + Assert.Single(member.Children); + Assert.Equal(nameof(NestedMemberClass.MemberClass), member.DisplayName); + Assert.Equal(NodeIndex.Empty, member.Index); + Assert.False(member.IsEnumerable); + Assert.False(member.IsReadOnly); + Assert.True(member.IsVisible); + Assert.Equal(nameof(NestedMemberClass.MemberClass), member.Name); + Assert.Equal(20, member.Order); + Assert.Equal(root, member.Parent); + Assert.Equal(instance.MemberClass, member.Value); + var innerMember = member[nameof(SimpleMember.FloatValue)]; + Assert.Empty(innerMember.Children); + Assert.Equal(nameof(SimpleMember.FloatValue), innerMember.DisplayName); + Assert.Equal(NodeIndex.Empty, innerMember.Index); + Assert.False(innerMember.IsEnumerable); + Assert.False(innerMember.IsReadOnly); + Assert.True(innerMember.IsVisible); + Assert.Equal(nameof(SimpleMember.FloatValue), innerMember.Name); + Assert.Equal(10, innerMember.Order); + Assert.Equal(member, innerMember.Parent); + Assert.Equal(1.0f, innerMember.Value); + } - [Fact] - public void TestNestedReadOnlyMember() - { - var instance = new NestedReadonlyMemberClass { MemberClass = { FloatValue = 1.0f } }; - var context = BuildContext(instance); - var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - var member = root[nameof(NestedMemberClass.MemberClass)]; - Assert.Equal(1, member.Children.Count); - Assert.Equal(nameof(NestedMemberClass.MemberClass), member.DisplayName); - Assert.Equal(NodeIndex.Empty, member.Index); - Assert.False(member.IsEnumerable); - Assert.True(member.IsReadOnly); - Assert.True(member.IsVisible); - Assert.Equal(nameof(NestedMemberClass.MemberClass), member.Name); - Assert.Equal(30, member.Order); - Assert.Equal(root, member.Parent); - Assert.Equal(instance.MemberClass, member.Value); - var innerMember = member[nameof(SimpleMember.FloatValue)]; - Assert.Equal(0, innerMember.Children.Count); - Assert.Equal(nameof(SimpleMember.FloatValue), innerMember.DisplayName); - Assert.Equal(NodeIndex.Empty, innerMember.Index); - Assert.False(innerMember.IsEnumerable); - Assert.False(innerMember.IsReadOnly); - Assert.True(innerMember.IsVisible); - Assert.Equal(nameof(SimpleMember.FloatValue), innerMember.Name); - Assert.Equal(10, innerMember.Order); - Assert.Equal(member, innerMember.Parent); - Assert.Equal(1.0f, innerMember.Value); - } + [Fact] + public void TestNestedReadOnlyMember() + { + var instance = new NestedReadonlyMemberClass { MemberClass = { FloatValue = 1.0f } }; + var context = BuildContext(instance); + var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + var member = root[nameof(NestedMemberClass.MemberClass)]; + Assert.Single(member.Children); + Assert.Equal(nameof(NestedMemberClass.MemberClass), member.DisplayName); + Assert.Equal(NodeIndex.Empty, member.Index); + Assert.False(member.IsEnumerable); + Assert.True(member.IsReadOnly); + Assert.True(member.IsVisible); + Assert.Equal(nameof(NestedMemberClass.MemberClass), member.Name); + Assert.Equal(30, member.Order); + Assert.Equal(root, member.Parent); + Assert.Equal(instance.MemberClass, member.Value); + var innerMember = member[nameof(SimpleMember.FloatValue)]; + Assert.Empty(innerMember.Children); + Assert.Equal(nameof(SimpleMember.FloatValue), innerMember.DisplayName); + Assert.Equal(NodeIndex.Empty, innerMember.Index); + Assert.False(innerMember.IsEnumerable); + Assert.False(innerMember.IsReadOnly); + Assert.True(innerMember.IsVisible); + Assert.Equal(nameof(SimpleMember.FloatValue), innerMember.Name); + Assert.Equal(10, innerMember.Order); + Assert.Equal(member, innerMember.Parent); + Assert.Equal(1.0f, innerMember.Value); + } - [Fact] - public void TestListMember() - { - var instance = new ListMember { List = new List() }; - var context = BuildContext(instance); - var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - var member = root[nameof(ListMember.List)]; - Assert.Equal(0, member.Children.Count); - Assert.Equal(nameof(ListMember.List), member.DisplayName); - Assert.Equal(NodeIndex.Empty, member.Index); - Assert.True(member.IsEnumerable); - Assert.False(member.IsReadOnly); - Assert.True(member.IsVisible); - Assert.Equal(nameof(ListMember.List), member.Name); - Assert.Equal(40, member.Order); - Assert.Equal(root, member.Parent); - Assert.Equal(instance.List, member.Value); + [Fact] + public void TestListMember() + { + var instance = new ListMember { List = [] }; + var context = BuildContext(instance); + var root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + var member = root[nameof(ListMember.List)]; + Assert.Empty(member.Children); + Assert.Equal(nameof(ListMember.List), member.DisplayName); + Assert.Equal(NodeIndex.Empty, member.Index); + Assert.True(member.IsEnumerable); + Assert.False(member.IsReadOnly); + Assert.True(member.IsVisible); + Assert.Equal(nameof(ListMember.List), member.Name); + Assert.Equal(40, member.Order); + Assert.Equal(root, member.Parent); + Assert.Equal(instance.List, member.Value); - instance = new ListMember(); - context = BuildContext(instance); - root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - member = root[nameof(ListMember.List)]; - Assert.Equal(0, member.Children.Count); - Assert.Equal(nameof(ListMember.List), member.DisplayName); - Assert.Equal(NodeIndex.Empty, member.Index); - Assert.False(member.IsEnumerable); - Assert.False(member.IsReadOnly); - Assert.True(member.IsVisible); - Assert.Equal(nameof(ListMember.List), member.Name); - Assert.Equal(40, member.Order); - Assert.Equal(root, member.Parent); - Assert.Equal(instance.List, member.Value); - } + instance = new ListMember(); + context = BuildContext(instance); + root = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + member = root[nameof(ListMember.List)]; + Assert.Empty(member.Children); + Assert.Equal(nameof(ListMember.List), member.DisplayName); + Assert.Equal(NodeIndex.Empty, member.Index); + Assert.False(member.IsEnumerable); + Assert.False(member.IsReadOnly); + Assert.True(member.IsVisible); + Assert.Equal(nameof(ListMember.List), member.Name); + Assert.Equal(40, member.Order); + Assert.Equal(root, member.Parent); + Assert.Equal(instance.List, member.Value); + } - private static TestInstanceContext BuildContext(object instance) - { - var context = new TestContainerContext(); - return context.CreateInstanceContext(instance); - } + private static TestInstanceContext BuildContext(object instance) + { + var context = new TestContainerContext(); + return context.CreateInstanceContext(instance); } } diff --git a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestNodePresenterUpdates.cs b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestNodePresenterUpdates.cs index c31b35beef..83decbba42 100644 --- a/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestNodePresenterUpdates.cs +++ b/sources/presentation/Stride.Core.Presentation.Quantum.Tests/TestNodePresenterUpdates.cs @@ -1,485 +1,482 @@ // Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp) // Distributed under the MIT license. See the LICENSE.md file in the project root for more information. -using System.Collections.Generic; -using System.Linq; -using Xunit; + using Stride.Core.Presentation.Quantum.Tests.Helpers; using Stride.Core.Quantum; -namespace Stride.Core.Presentation.Quantum.Tests +namespace Stride.Core.Presentation.Quantum.Tests; + +public class TestNodePresenterUpdates { - public class TestNodePresenterUpdates + [Fact] + public void TestPrimitiveMemberUpdate() + { + var instance = new Types.SimpleType { String = "aaa" }; + var context = BuildContext(instance); + var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + + presenter.Children.Single().UpdateValue("bbb"); + Assert.Single(presenter.Children); + var child = presenter.Children.Single(); + Assert.Equal("String", child.Name); + Assert.Equal(presenter, child.Parent); + Assert.Empty(child.Children); + Assert.Equal(typeof(string), child.Type); + Assert.Equal("bbb", child.Value); + + presenter.Children.Single().UpdateValue("ccc"); + Assert.Single(presenter.Children); + child = presenter.Children.Single(); + Assert.Equal("String", child.Name); + Assert.Equal(presenter, child.Parent); + Assert.Empty(child.Children); + Assert.Equal(typeof(string), child.Type); + Assert.Equal("ccc", child.Value); + } + + [Fact] + public void TestReferenceMemberUpdate() + { + var instance = new Types.ClassWithRef { String = "aaa", Ref = new Types.ClassWithRef { String = "bbb" } }; + var context = BuildContext(instance); + var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + + presenter.Children[1].UpdateValue(new Types.ClassWithRef { String = "ccc" }); + Assert.Equal(2, presenter.Children.Count); + Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Type); + Assert.Equal(instance.Ref, presenter.Children[1].Value); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Type); + Assert.Equal(instance.Ref, presenter.Children[1].Value); + Assert.Equal("String", presenter.Children[1].Children[0].Name); + Assert.Equal("ccc", presenter.Children[1].Children[0].Value); + Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Children[1].Type); + Assert.Equal(instance.Ref.Ref, presenter.Children[1].Children[1].Value); + + presenter.Children[1].UpdateValue(new Types.ClassWithRef { String = "ddd" }); + Assert.Equal(2, presenter.Children.Count); + Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Type); + Assert.Equal(instance.Ref, presenter.Children[1].Value); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Type); + Assert.Equal(instance.Ref, presenter.Children[1].Value); + Assert.Equal("String", presenter.Children[1].Children[0].Name); + Assert.Equal("ddd", presenter.Children[1].Children[0].Value); + Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Children[1].Type); + Assert.Equal(instance.Ref.Ref, presenter.Children[1].Children[1].Value); + } + + [Fact] + public void TestPrimitiveListUpdate() + { + var instance = new Types.ClassWithCollection { String = "aaa", List = { "bbb", "ccc" } }; + var context = BuildContext(instance); + var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + + presenter[nameof(Types.ClassWithCollection.List)].Children[1].UpdateValue("ddd"); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Value); + Assert.Equal("ddd", presenter.Children[1].Children[1].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + + presenter[nameof(Types.ClassWithCollection.List)].Children[1].UpdateValue("eee"); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[1].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + + presenter[nameof(Types.ClassWithCollection.List)].Children[0].UpdateValue("fff"); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("fff", presenter.Children[1].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[1].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + } + + [Fact] + public void TestPrimitiveListAdd() + { + var instance = new Types.ClassWithCollection { String = "aaa", List = { "bbb", "ccc" } }; + var context = BuildContext(instance); + var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + + presenter.Children[1].AddItem("ddd", new NodeIndex(2)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(3, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("2", presenter.Children[1].Children[2].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Value); + Assert.Equal("ccc", presenter.Children[1].Children[1].Value); + Assert.Equal("ddd", presenter.Children[1].Children[2].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + + presenter.Children[1].AddItem("eee", new NodeIndex(1)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(4, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("2", presenter.Children[1].Children[2].Name); + Assert.Equal("3", presenter.Children[1].Children[3].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[1].Value); + Assert.Equal("ccc", presenter.Children[1].Children[2].Value); + Assert.Equal("ddd", presenter.Children[1].Children[3].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[3].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); + + presenter.Children[1].AddItem("fff", new NodeIndex(0)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(5, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("2", presenter.Children[1].Children[2].Name); + Assert.Equal("3", presenter.Children[1].Children[3].Name); + Assert.Equal("4", presenter.Children[1].Children[4].Name); + Assert.Equal("fff", presenter.Children[1].Children[0].Value); + Assert.Equal("bbb", presenter.Children[1].Children[1].Value); + Assert.Equal("eee", presenter.Children[1].Children[2].Value); + Assert.Equal("ccc", presenter.Children[1].Children[3].Value); + Assert.Equal("ddd", presenter.Children[1].Children[4].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[3].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[4].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); + Assert.Equal(instance.List[4], presenter.Children[1].Children[4].Value); + } + + [Fact] + public void TestPrimitiveListRemove() + { + var instance = new Types.ClassWithCollection { String = "aaa", List = { "bbb", "ccc", "ddd", "eee", "fff" } }; + var context = BuildContext(instance); + var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + + presenter.Children[1].RemoveItem("fff", new NodeIndex(4)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(4, presenter.Children[1].Children.Count); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("2", presenter.Children[1].Children[2].Name); + Assert.Equal("3", presenter.Children[1].Children[3].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Value); + Assert.Equal("ccc", presenter.Children[1].Children[1].Value); + Assert.Equal("ddd", presenter.Children[1].Children[2].Value); + Assert.Equal("eee", presenter.Children[1].Children[3].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[3].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); + + presenter.Children[1].RemoveItem("bbb", new NodeIndex(0)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(3, presenter.Children[1].Children.Count); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("2", presenter.Children[1].Children[2].Name); + Assert.Equal("ccc", presenter.Children[1].Children[0].Value); + Assert.Equal("ddd", presenter.Children[1].Children[1].Value); + Assert.Equal("eee", presenter.Children[1].Children[2].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + + presenter.Children[1].RemoveItem("ddd", new NodeIndex(1)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("ccc", presenter.Children[1].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[1].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + + presenter.Children[1].RemoveItem("eee", new NodeIndex(1)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Single(presenter.Children[1].Children); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("ccc", presenter.Children[1].Children[0].Value); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + + presenter.Children[1].RemoveItem("ccc", new NodeIndex(0)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Empty(presenter.Children[1].Children); + Assert.Equal(instance.List, presenter.Children[1].Value); + } + + [Fact] + public void TestReferenceListUpdate() + { + var instance = new Types.ClassWithRefCollection { String = "aaa", List = { new Types.SimpleType { String = "bbb" }, new Types.SimpleType { String = "ccc" } } }; + var context = BuildContext(instance); + var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + + presenter[nameof(Types.ClassWithRefCollection.List)].Children[1].UpdateValue(new Types.SimpleType { String = "ddd" }); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal("ddd", presenter.Children[1].Children[1].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + + presenter[nameof(Types.ClassWithRefCollection.List)].Children[1].UpdateValue(new Types.SimpleType { String = "eee" }); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[1].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + + presenter[nameof(Types.ClassWithRefCollection.List)].Children[0].UpdateValue(new Types.SimpleType { String = "fff" }); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("fff", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[1].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + } + + [Fact] + public void TestReferenceListAdd() + { + var instance = new Types.ClassWithRefCollection { String = "aaa", List = { new Types.SimpleType { String = "bbb" }, new Types.SimpleType { String = "ccc" } } }; + var context = BuildContext(instance); + var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + + presenter.Children[1].AddItem(new Types.SimpleType { String = "ddd" }, new NodeIndex(2)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(3, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("2", presenter.Children[1].Children[2].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal("ccc", presenter.Children[1].Children[1].Children[0].Value); + Assert.Equal("ddd", presenter.Children[1].Children[2].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + + presenter.Children[1].AddItem(new Types.SimpleType { String = "eee" }, new NodeIndex(1)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(4, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("2", presenter.Children[1].Children[2].Name); + Assert.Equal("3", presenter.Children[1].Children[3].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[1].Children[0].Value); + Assert.Equal("ccc", presenter.Children[1].Children[2].Children[0].Value); + Assert.Equal("ddd", presenter.Children[1].Children[3].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[3].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[3].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); + + presenter.Children[1].AddItem(new Types.SimpleType { String = "fff" }, new NodeIndex(0)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal(5, presenter.Children[1].Children.Count); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("2", presenter.Children[1].Children[2].Name); + Assert.Equal("3", presenter.Children[1].Children[3].Name); + Assert.Equal("4", presenter.Children[1].Children[4].Name); + Assert.Equal("fff", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal("bbb", presenter.Children[1].Children[1].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[2].Children[0].Value); + Assert.Equal("ccc", presenter.Children[1].Children[3].Children[0].Value); + Assert.Equal("ddd", presenter.Children[1].Children[4].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[3].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[4].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[3].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[4].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); + Assert.Equal(instance.List[4], presenter.Children[1].Children[4].Value); + } + + [Fact] + public void TestReferenceListRemove() + { + var instance = new Types.ClassWithRefCollection { String = "aaa", List = { new Types.SimpleType { String = "bbb" }, new Types.SimpleType { String = "ccc" }, new Types.SimpleType { String = "ddd" }, new Types.SimpleType { String = "eee" }, new Types.SimpleType { String = "fff" }, } }; + var context = BuildContext(instance); + var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); + + presenter.Children[1].RemoveItem(instance.List[4], new NodeIndex(4)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(4, presenter.Children[1].Children.Count); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal("ccc", presenter.Children[1].Children[1].Children[0].Value); + Assert.Equal("ddd", presenter.Children[1].Children[2].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[3].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[3].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[3].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); + + presenter.Children[1].RemoveItem("bbb", new NodeIndex(0)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(3, presenter.Children[1].Children.Count); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("2", presenter.Children[1].Children[2].Name); + Assert.Equal("ccc", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal("ddd", presenter.Children[1].Children[1].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[2].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); + + presenter.Children[1].RemoveItem("ddd", new NodeIndex(1)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Equal(2, presenter.Children[1].Children.Count); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("1", presenter.Children[1].Children[1].Name); + Assert.Equal("ccc", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal("eee", presenter.Children[1].Children[1].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); + + presenter.Children[1].RemoveItem("eee", new NodeIndex(1)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Single(presenter.Children[1].Children); + Assert.Equal(instance.List, presenter.Children[1].Value); + Assert.Equal("0", presenter.Children[1].Children[0].Name); + Assert.Equal("ccc", presenter.Children[1].Children[0].Children[0].Value); + Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); + Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); + Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); + + presenter.Children[1].RemoveItem("ccc", new NodeIndex(0)); + Assert.Equal(typeof(List), presenter.Children[1].Type); + Assert.Empty(presenter.Children[1].Children); + Assert.Equal(instance.List, presenter.Children[1].Value); + } + + private static TestInstanceContext BuildContext(object instance) { - [Fact] - public void TestPrimitiveMemberUpdate() - { - var instance = new Types.SimpleType { String = "aaa" }; - var context = BuildContext(instance); - var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - - presenter.Children.Single().UpdateValue("bbb"); - Assert.Equal(1, presenter.Children.Count); - var child = presenter.Children.Single(); - Assert.Equal("String", child.Name); - Assert.Equal(presenter, child.Parent); - Assert.Equal(0, child.Children.Count); - Assert.Equal(typeof(string), child.Type); - Assert.Equal("bbb", child.Value); - - presenter.Children.Single().UpdateValue("ccc"); - Assert.Equal(1, presenter.Children.Count); - child = presenter.Children.Single(); - Assert.Equal("String", child.Name); - Assert.Equal(presenter, child.Parent); - Assert.Equal(0, child.Children.Count); - Assert.Equal(typeof(string), child.Type); - Assert.Equal("ccc", child.Value); - } - - [Fact] - public void TestReferenceMemberUpdate() - { - var instance = new Types.ClassWithRef { String = "aaa", Ref = new Types.ClassWithRef { String = "bbb" } }; - var context = BuildContext(instance); - var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - - presenter.Children[1].UpdateValue(new Types.ClassWithRef { String = "ccc" }); - Assert.Equal(2, presenter.Children.Count); - Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Type); - Assert.Equal(instance.Ref, presenter.Children[1].Value); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Type); - Assert.Equal(instance.Ref, presenter.Children[1].Value); - Assert.Equal("String", presenter.Children[1].Children[0].Name); - Assert.Equal("ccc", presenter.Children[1].Children[0].Value); - Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Children[1].Type); - Assert.Equal(instance.Ref.Ref, presenter.Children[1].Children[1].Value); - - presenter.Children[1].UpdateValue(new Types.ClassWithRef { String = "ddd" }); - Assert.Equal(2, presenter.Children.Count); - Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Type); - Assert.Equal(instance.Ref, presenter.Children[1].Value); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Type); - Assert.Equal(instance.Ref, presenter.Children[1].Value); - Assert.Equal("String", presenter.Children[1].Children[0].Name); - Assert.Equal("ddd", presenter.Children[1].Children[0].Value); - Assert.Equal(typeof(Types.ClassWithRef), presenter.Children[1].Children[1].Type); - Assert.Equal(instance.Ref.Ref, presenter.Children[1].Children[1].Value); - } - - [Fact] - public void TestPrimitiveListUpdate() - { - var instance = new Types.ClassWithCollection { String = "aaa", List = { "bbb", "ccc" } }; - var context = BuildContext(instance); - var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - - presenter[nameof(Types.ClassWithCollection.List)].Children[1].UpdateValue("ddd"); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Value); - Assert.Equal("ddd", presenter.Children[1].Children[1].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - - presenter[nameof(Types.ClassWithCollection.List)].Children[1].UpdateValue("eee"); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[1].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - - presenter[nameof(Types.ClassWithCollection.List)].Children[0].UpdateValue("fff"); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("fff", presenter.Children[1].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[1].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - } - - [Fact] - public void TestPrimitiveListAdd() - { - var instance = new Types.ClassWithCollection { String = "aaa", List = { "bbb", "ccc" } }; - var context = BuildContext(instance); - var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - - presenter.Children[1].AddItem("ddd", new NodeIndex(2)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(3, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("2", presenter.Children[1].Children[2].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Value); - Assert.Equal("ccc", presenter.Children[1].Children[1].Value); - Assert.Equal("ddd", presenter.Children[1].Children[2].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - - presenter.Children[1].AddItem("eee", new NodeIndex(1)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(4, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("2", presenter.Children[1].Children[2].Name); - Assert.Equal("3", presenter.Children[1].Children[3].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[1].Value); - Assert.Equal("ccc", presenter.Children[1].Children[2].Value); - Assert.Equal("ddd", presenter.Children[1].Children[3].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[3].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); - - presenter.Children[1].AddItem("fff", new NodeIndex(0)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(5, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("2", presenter.Children[1].Children[2].Name); - Assert.Equal("3", presenter.Children[1].Children[3].Name); - Assert.Equal("4", presenter.Children[1].Children[4].Name); - Assert.Equal("fff", presenter.Children[1].Children[0].Value); - Assert.Equal("bbb", presenter.Children[1].Children[1].Value); - Assert.Equal("eee", presenter.Children[1].Children[2].Value); - Assert.Equal("ccc", presenter.Children[1].Children[3].Value); - Assert.Equal("ddd", presenter.Children[1].Children[4].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[3].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[4].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); - Assert.Equal(instance.List[4], presenter.Children[1].Children[4].Value); - } - - [Fact] - public void TestPrimitiveListRemove() - { - var instance = new Types.ClassWithCollection { String = "aaa", List = { "bbb", "ccc", "ddd", "eee", "fff" } }; - var context = BuildContext(instance); - var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - - presenter.Children[1].RemoveItem("fff", new NodeIndex(4)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(4, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("2", presenter.Children[1].Children[2].Name); - Assert.Equal("3", presenter.Children[1].Children[3].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Value); - Assert.Equal("ccc", presenter.Children[1].Children[1].Value); - Assert.Equal("ddd", presenter.Children[1].Children[2].Value); - Assert.Equal("eee", presenter.Children[1].Children[3].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[3].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); - - presenter.Children[1].RemoveItem("bbb", new NodeIndex(0)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(3, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("2", presenter.Children[1].Children[2].Name); - Assert.Equal("ccc", presenter.Children[1].Children[0].Value); - Assert.Equal("ddd", presenter.Children[1].Children[1].Value); - Assert.Equal("eee", presenter.Children[1].Children[2].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - - presenter.Children[1].RemoveItem("ddd", new NodeIndex(1)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("ccc", presenter.Children[1].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[1].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - - presenter.Children[1].RemoveItem("eee", new NodeIndex(1)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(1, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("ccc", presenter.Children[1].Children[0].Value); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - - presenter.Children[1].RemoveItem("ccc", new NodeIndex(0)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(0, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - } - - [Fact] - public void TestReferenceListUpdate() - { - var instance = new Types.ClassWithRefCollection { String = "aaa", List = { new Types.SimpleType { String = "bbb" }, new Types.SimpleType { String = "ccc" } } }; - var context = BuildContext(instance); - var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - - presenter[nameof(Types.ClassWithRefCollection.List)].Children[1].UpdateValue(new Types.SimpleType { String = "ddd" }); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal("ddd", presenter.Children[1].Children[1].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - - presenter[nameof(Types.ClassWithRefCollection.List)].Children[1].UpdateValue(new Types.SimpleType { String = "eee" }); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[1].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - - presenter[nameof(Types.ClassWithRefCollection.List)].Children[0].UpdateValue(new Types.SimpleType { String = "fff" }); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("fff", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[1].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - } - - [Fact] - public void TestReferenceListAdd() - { - var instance = new Types.ClassWithRefCollection { String = "aaa", List = { new Types.SimpleType { String = "bbb" }, new Types.SimpleType { String = "ccc" } } }; - var context = BuildContext(instance); - var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - - presenter.Children[1].AddItem(new Types.SimpleType { String = "ddd" }, new NodeIndex(2)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(3, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("2", presenter.Children[1].Children[2].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal("ccc", presenter.Children[1].Children[1].Children[0].Value); - Assert.Equal("ddd", presenter.Children[1].Children[2].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - - presenter.Children[1].AddItem(new Types.SimpleType { String = "eee" }, new NodeIndex(1)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(4, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("2", presenter.Children[1].Children[2].Name); - Assert.Equal("3", presenter.Children[1].Children[3].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[1].Children[0].Value); - Assert.Equal("ccc", presenter.Children[1].Children[2].Children[0].Value); - Assert.Equal("ddd", presenter.Children[1].Children[3].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[3].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[3].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); - - presenter.Children[1].AddItem(new Types.SimpleType { String = "fff" }, new NodeIndex(0)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal(5, presenter.Children[1].Children.Count); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("2", presenter.Children[1].Children[2].Name); - Assert.Equal("3", presenter.Children[1].Children[3].Name); - Assert.Equal("4", presenter.Children[1].Children[4].Name); - Assert.Equal("fff", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal("bbb", presenter.Children[1].Children[1].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[2].Children[0].Value); - Assert.Equal("ccc", presenter.Children[1].Children[3].Children[0].Value); - Assert.Equal("ddd", presenter.Children[1].Children[4].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[3].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[4].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[3].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[4].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); - Assert.Equal(instance.List[4], presenter.Children[1].Children[4].Value); - } - - [Fact] - public void TestReferenceListRemove() - { - var instance = new Types.ClassWithRefCollection { String = "aaa", List = { new Types.SimpleType { String = "bbb" }, new Types.SimpleType { String = "ccc" }, new Types.SimpleType { String = "ddd" }, new Types.SimpleType { String = "eee" }, new Types.SimpleType { String = "fff" }, } }; - var context = BuildContext(instance); - var presenter = context.Factory.CreateNodeHierarchy(context.RootNode, new GraphNodePath(context.RootNode)); - - presenter.Children[1].RemoveItem(instance.List[4], new NodeIndex(4)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(4, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("bbb", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal("ccc", presenter.Children[1].Children[1].Children[0].Value); - Assert.Equal("ddd", presenter.Children[1].Children[2].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[3].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[3].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[3].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - Assert.Equal(instance.List[3], presenter.Children[1].Children[3].Value); - - presenter.Children[1].RemoveItem("bbb", new NodeIndex(0)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(3, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("2", presenter.Children[1].Children[2].Name); - Assert.Equal("ccc", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal("ddd", presenter.Children[1].Children[1].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[2].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[2].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[2].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - Assert.Equal(instance.List[2], presenter.Children[1].Children[2].Value); - - presenter.Children[1].RemoveItem("ddd", new NodeIndex(1)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(2, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("1", presenter.Children[1].Children[1].Name); - Assert.Equal("ccc", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal("eee", presenter.Children[1].Children[1].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[1].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[1].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - Assert.Equal(instance.List[1], presenter.Children[1].Children[1].Value); - - presenter.Children[1].RemoveItem("eee", new NodeIndex(1)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(1, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - Assert.Equal("0", presenter.Children[1].Children[0].Name); - Assert.Equal("ccc", presenter.Children[1].Children[0].Children[0].Value); - Assert.Equal(typeof(Types.SimpleType), presenter.Children[1].Children[0].Type); - Assert.Equal(typeof(string), presenter.Children[1].Children[0].Children[0].Type); - Assert.Equal(instance.List[0], presenter.Children[1].Children[0].Value); - - presenter.Children[1].RemoveItem("ccc", new NodeIndex(0)); - Assert.Equal(typeof(List), presenter.Children[1].Type); - Assert.Equal(0, presenter.Children[1].Children.Count); - Assert.Equal(instance.List, presenter.Children[1].Value); - } - - private static TestInstanceContext BuildContext(object instance) - { - var context = new TestContainerContext(); - return context.CreateInstanceContext(instance); - } + var context = new TestContainerContext(); + return context.CreateInstanceContext(instance); } }