From 4fbd229846894849c105120fde7d47534071cc8f Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 25 Aug 2021 20:40:24 +0200 Subject: [PATCH] Nodes - Support dynamic children in data model node --- .../Profile/DataBindings/DataBinding.cs | 6 +- .../Models/Profile/DataModel/DataModelPath.cs | 25 ++++++ .../Profile/DataModel/DataModelPathSegment.cs | 3 + src/Artemis.Core/Services/NodeService.cs | 2 +- .../VisualScripting/Interfaces/INode.cs | 2 + .../VisualScripting/NodeScript.cs | 88 +++++++++++-------- .../Controls/DataModelPicker.xaml.cs | 25 +++++- .../DataModelNodeCustomViewModel.cs | 22 +++-- .../Nodes/DataModelNode.cs | 68 ++++++++++---- 9 files changed, 165 insertions(+), 76 deletions(-) diff --git a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs index 9cb9c54be..bfe24e220 100644 --- a/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs +++ b/src/Artemis.Core/Models/Profile/DataBindings/DataBinding.cs @@ -60,7 +60,7 @@ namespace Artemis.Core if (!IsEnabled) return; - // TODO: Update the base node + // TODO: Update the 'base value' node Script.Run(); } @@ -77,7 +77,7 @@ namespace Artemis.Core if (_disposed) throw new ObjectDisposedException("DataBinding"); if (Properties.Any(d => d.DisplayName == displayName)) - throw new ArtemisCoreException($"A databinding property named '{displayName}' is already registered."); + throw new ArtemisCoreException($"A data binding property named '{displayName}' is already registered."); DataBindingProperty property = new(getter, setter, displayName); _properties.Add(property); @@ -110,6 +110,8 @@ namespace Artemis.Core if (disposing) { _disposed = true; + _isEnabled = false; + Script.Dispose(); } } diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs index 59dc7bc70..92ec6eaec 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPath.cs @@ -365,6 +365,31 @@ namespace Artemis.Core Entity.DataModelId = DataModelId; } + #region Equality members + + /// > + protected bool Equals(DataModelPath other) + { + return ReferenceEquals(Target, other.Target) && Path == other.Path; + } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj.GetType() != this.GetType()) return false; + return Equals((DataModelPath) obj); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Target, Path); + } + + #endregion + #endregion } } \ No newline at end of file diff --git a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs index 5c70eae3f..19c1d87ab 100644 --- a/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs +++ b/src/Artemis.Core/Models/Profile/DataModel/DataModelPathSegment.cs @@ -295,7 +295,10 @@ namespace Artemis.Core private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e) { if (e.Key == Identifier) + { + DataModelPath.Invalidate(); DataModelPath.Initialize(); + } } private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e) diff --git a/src/Artemis.Core/Services/NodeService.cs b/src/Artemis.Core/Services/NodeService.cs index 5af2a659a..9d3ba8549 100644 --- a/src/Artemis.Core/Services/NodeService.cs +++ b/src/Artemis.Core/Services/NodeService.cs @@ -83,7 +83,7 @@ namespace Artemis.Core.Services // ignored } } - + node.Initialize(script); return node; } diff --git a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs index 5f415d987..915f5bac1 100644 --- a/src/Artemis.Core/VisualScripting/Interfaces/INode.cs +++ b/src/Artemis.Core/VisualScripting/Interfaces/INode.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using Artemis.Storage.Entities.Profile.Nodes; namespace Artemis.Core { @@ -16,6 +17,7 @@ namespace Artemis.Core public IReadOnlyCollection Pins { get; } public IReadOnlyCollection PinCollections { get; } + event EventHandler Resetting; diff --git a/src/Artemis.Core/VisualScripting/NodeScript.cs b/src/Artemis.Core/VisualScripting/NodeScript.cs index faeac5616..5c5b1188e 100644 --- a/src/Artemis.Core/VisualScripting/NodeScript.cs +++ b/src/Artemis.Core/VisualScripting/NodeScript.cs @@ -10,6 +10,14 @@ namespace Artemis.Core { public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageModel { + private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e) + { + Load(); + } + + public event EventHandler? NodeAdded; + public event EventHandler? NodeRemoved; + #region Properties & Fields internal NodeScriptEntity Entity { get; } @@ -28,21 +36,14 @@ namespace Artemis.Core #endregion - #region Events - - public event EventHandler? NodeAdded; - public event EventHandler? NodeRemoved; - - #endregion - #region Constructors public NodeScript(string name, string description, object? context = null) { - this.Name = name; - this.Description = description; - this.Context = context; - this.Entity = new NodeScriptEntity(); + Name = name; + Description = description; + Context = context; + Entity = new NodeScriptEntity(); NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; @@ -50,10 +51,10 @@ namespace Artemis.Core internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null) { - this.Name = name; - this.Description = description; - this.Entity = entity; - this.Context = context; + Name = name; + Description = description; + Entity = entity; + Context = context; NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; @@ -89,6 +90,12 @@ namespace Artemis.Core { NodeTypeStore.NodeTypeAdded -= NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved -= NodeTypeStoreOnNodeTypeChanged; + + foreach (INode node in _nodes) + { + if (node is IDisposable disposable) + disposable.Dispose(); + } } #endregion @@ -98,20 +105,18 @@ namespace Artemis.Core /// public void Load() { + _nodes.Clear(); + // Create nodes - Dictionary nodes = new(); foreach (NodeEntity entityNode in Entity.Nodes) { INode? node = LoadNode(entityNode, entityNode.IsExitNode ? ExitNode : null); if (node == null) continue; - nodes.Add(entityNode.Id, node); + _nodes.Add(node); } - LoadConnections(nodes); - - _nodes.Clear(); - _nodes.AddRange(nodes.Values); + LoadConnections(); } private INode? LoadNode(NodeEntity nodeEntity, INode? node) @@ -147,12 +152,19 @@ namespace Artemis.Core return node; } - private void LoadConnections(Dictionary nodes) + /// + /// Loads missing connections between the nodes of this node script from the + /// + public void LoadConnections() { + List nodes = Nodes.ToList(); foreach (NodeConnectionEntity nodeConnectionEntity in Entity.Connections) { - // Find the source and target node - if (!nodes.TryGetValue(nodeConnectionEntity.SourceNode, out INode? source) || !nodes.TryGetValue(nodeConnectionEntity.TargetNode, out INode? target)) + INode? source = nodes.ElementAtOrDefault(nodeConnectionEntity.SourceNode); + if (source == null) + continue; + INode? target = nodes.ElementAtOrDefault(nodeConnectionEntity.TargetNode); + if (target == null) continue; IPin? sourcePin = nodeConnectionEntity.SourcePinCollectionId == -1 @@ -165,9 +177,15 @@ namespace Artemis.Core // Ensure both nodes have the required pins if (sourcePin == null || targetPin == null) continue; + // Ensure the connection is valid + if (sourcePin.Direction == targetPin.Direction) + continue; - targetPin.ConnectTo(sourcePin); - sourcePin.ConnectTo(targetPin); + // Only connect the nodes if they aren't already connected (LoadConnections may be called twice or more) + if (!targetPin.ConnectedTo.Contains(sourcePin)) + targetPin.ConnectTo(sourcePin); + if (!sourcePin.ConnectedTo.Contains(targetPin)) + sourcePin.ConnectTo(targetPin); } } @@ -178,13 +196,12 @@ namespace Artemis.Core Entity.Description = Description; Entity.Nodes.Clear(); - + // No need to save the exit node if that's all there is if (Nodes.Count() == 1) return; int id = 0; - foreach (INode node in Nodes) { NodeEntity nodeEntity = new() @@ -248,9 +265,11 @@ namespace Artemis.Core targetPinId = targetCollection.ToList().IndexOf(targetPin); } else + { targetPinId = targetPin.Node.Pins.IndexOf(targetPin); + } - Entity.Connections.Add(new NodeConnectionEntity() + Entity.Connections.Add(new NodeConnectionEntity { SourceType = sourcePin.Type.Name, SourceNode = nodes.IndexOf(node), @@ -259,7 +278,7 @@ namespace Artemis.Core TargetType = targetPin.Type.Name, TargetNode = nodes.IndexOf(targetPin.Node), TargetPinCollectionId = targetPinCollectionId, - TargetPinId = targetPinId, + TargetPinId = targetPinId }); } @@ -268,15 +287,6 @@ namespace Artemis.Core } #endregion - - #region Event handlers - - private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e) - { - Load(); - } - - #endregion } public class NodeScript : NodeScript, INodeScript diff --git a/src/Artemis.UI.Shared/Controls/DataModelPicker.xaml.cs b/src/Artemis.UI.Shared/Controls/DataModelPicker.xaml.cs index 036031368..5975109f4 100644 --- a/src/Artemis.UI.Shared/Controls/DataModelPicker.xaml.cs +++ b/src/Artemis.UI.Shared/Controls/DataModelPicker.xaml.cs @@ -44,7 +44,10 @@ namespace Artemis.UI.Shared.Controls /// /// Gets or sets the brush to use when drawing the button /// - public static readonly DependencyProperty ShowFullPathProperty = DependencyProperty.Register(nameof(ShowFullPath), typeof(bool), typeof(DataModelPicker)); + public static readonly DependencyProperty ShowFullPathProperty = DependencyProperty.Register( + nameof(ShowFullPath), typeof(bool), typeof(DataModelPicker), + new FrameworkPropertyMetadata(ShowFullPathPropertyCallback) + ); /// /// Gets or sets the brush to use when drawing the button @@ -86,7 +89,7 @@ namespace Artemis.UI.Shared.Controls public DataModelPicker() { SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand); - + Unloaded += (_, _) => DataModelViewModel?.Dispose(); InitializeComponent(); GetDataModel(); UpdateValueDisplay(); @@ -253,7 +256,12 @@ namespace Artemis.UI.Shared.Controls if (context is not DataModelVisualizationViewModel selected) return; - DataModelPath = selected.DataModelPath; + if (selected.DataModelPath == null) + return; + if (selected.DataModelPath.Equals(DataModelPath)) + return; + + DataModelPath = new DataModelPath(selected.DataModelPath); OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath)); } @@ -278,6 +286,14 @@ namespace Artemis.UI.Shared.Controls dataModelPicker.UpdateValueDisplay(); } + private static void ShowFullPathPropertyCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is not DataModelPicker dataModelPicker) + return; + + dataModelPicker.UpdateValueDisplay(); + } + private static void ModulesPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not DataModelPicker dataModelPicker) @@ -290,6 +306,9 @@ namespace Artemis.UI.Shared.Controls { if (d is not DataModelPicker dataModelPicker) return; + + if (e.OldValue is DataModelPropertiesViewModel vm) + vm.Dispose(); } private static void ExtraDataModelViewModelsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) diff --git a/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs b/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs index 4120f4fcf..eb3f10cca 100644 --- a/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs +++ b/src/Artemis.VisualScripting/Nodes/CustomViewModels/DataModelNodeCustomViewModel.cs @@ -11,10 +11,12 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels private readonly DataModelNode _node; private BindableCollection _modules; - public DataModelNodeCustomViewModel(DataModelNode node, ISettingsService settingsService) : base(node) + public DataModelNodeCustomViewModel(DataModelNode node, ISettingsService settingsService, IPluginManagementService test) : base(node) { _node = node; + var tessst = test.GetFeaturesOfType(); + ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true); ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); } @@ -33,19 +35,15 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels get => _node.DataModelPath; set { + if (ReferenceEquals(_node.DataModelPath, value)) + return; + + _node.DataModelPath.Dispose(); _node.DataModelPath = value; + _node.DataModelPath.Save(); - if (_node.DataModelPath != null) - { - _node.DataModelPath.Save(); - _node.Storage = _node.DataModelPath.Entity; - } - else - { - _node.Storage = null; - } - - _node.UpdateOutputPin(); + _node.Storage = _node.DataModelPath.Entity; + _node.UpdateOutputPin(false); } } diff --git a/src/Artemis.VisualScripting/Nodes/DataModelNode.cs b/src/Artemis.VisualScripting/Nodes/DataModelNode.cs index 4f7a825c4..dbbb0ded9 100644 --- a/src/Artemis.VisualScripting/Nodes/DataModelNode.cs +++ b/src/Artemis.VisualScripting/Nodes/DataModelNode.cs @@ -3,11 +3,12 @@ using System.Linq; using Artemis.Core; using Artemis.Storage.Entities.Profile; using Artemis.VisualScripting.Nodes.CustomViewModels; +using Stylet; namespace Artemis.VisualScripting.Nodes { [Node("Data Model-Value", "Outputs a selectable data model value.")] - public class DataModelNode : Node + public class DataModelNode : Node, IDisposable { private DataModelPath _dataModelPath; @@ -21,7 +22,7 @@ namespace Artemis.VisualScripting.Nodes public DataModelPath DataModelPath { get => _dataModelPath; - set => SetAndNotify(ref _dataModelPath , value); + set => SetAndNotify(ref _dataModelPath, value); } public override void Initialize(INodeScript script) @@ -32,29 +33,58 @@ namespace Artemis.VisualScripting.Nodes return; DataModelPath = new DataModelPath(null, pathEntity); - UpdateOutputPin(); + DataModelPath.PathValidated += DataModelPathOnPathValidated; + + UpdateOutputPin(false); } public override void Evaluate() { - if (DataModelPath.IsValid && Output != null) - Output.Value = DataModelPath.GetValue()!; - } - - public void UpdateOutputPin() - { - if (Output != null && Output.Type == DataModelPath?.GetPropertyType()) - return; - - if (Output != null && Pins.Contains(Output)) + if (DataModelPath.IsValid) { - RemovePin(Output); - Output = null; - } + if (Output == null) + UpdateOutputPin(false); - Type type = DataModelPath?.GetPropertyType(); - if (type != null) - Output = CreateOutputPin(type); + Output.Value = DataModelPath.GetValue() ?? Output.Type.GetDefault(); + } } + + public void UpdateOutputPin(bool loadConnections) + { + Execute.OnUIThread(() => + { + if (Output != null && Output.Type == DataModelPath?.GetPropertyType()) + return; + + if (Output != null) + { + RemovePin(Output); + Output = null; + } + + Type type = DataModelPath?.GetPropertyType(); + if (type != null) + Output = CreateOutputPin(type); + + if (loadConnections && Script is NodeScript nodeScript) + nodeScript.LoadConnections(); + }); + } + + private void DataModelPathOnPathValidated(object sender, EventArgs e) + { + UpdateOutputPin(true); + + } + + #region IDisposable + + /// + public void Dispose() + { + DataModelPath.Dispose(); + } + + #endregion } } \ No newline at end of file