1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00

Nodes - Support dynamic children in data model node

This commit is contained in:
Robert 2021-08-25 20:40:24 +02:00
parent bd3bf20d92
commit 4fbd229846
9 changed files with 165 additions and 76 deletions

View File

@ -60,7 +60,7 @@ namespace Artemis.Core
if (!IsEnabled) if (!IsEnabled)
return; return;
// TODO: Update the base node // TODO: Update the 'base value' node
Script.Run(); Script.Run();
} }
@ -77,7 +77,7 @@ namespace Artemis.Core
if (_disposed) if (_disposed)
throw new ObjectDisposedException("DataBinding"); throw new ObjectDisposedException("DataBinding");
if (Properties.Any(d => d.DisplayName == displayName)) 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<TProperty> property = new(getter, setter, displayName); DataBindingProperty<TProperty> property = new(getter, setter, displayName);
_properties.Add(property); _properties.Add(property);
@ -110,6 +110,8 @@ namespace Artemis.Core
if (disposing) if (disposing)
{ {
_disposed = true; _disposed = true;
_isEnabled = false;
Script.Dispose(); Script.Dispose();
} }
} }

View File

@ -365,6 +365,31 @@ namespace Artemis.Core
Entity.DataModelId = DataModelId; Entity.DataModelId = DataModelId;
} }
#region Equality members
/// <inheritdoc cref="Equals(object)"/>>
protected bool Equals(DataModelPath other)
{
return ReferenceEquals(Target, other.Target) && Path == other.Path;
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public override int GetHashCode()
{
return HashCode.Combine(Target, Path);
}
#endregion
#endregion #endregion
} }
} }

View File

@ -295,8 +295,11 @@ namespace Artemis.Core
private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e) private void DynamicChildOnDynamicChildAdded(object? sender, DynamicDataModelChildEventArgs e)
{ {
if (e.Key == Identifier) if (e.Key == Identifier)
{
DataModelPath.Invalidate();
DataModelPath.Initialize(); DataModelPath.Initialize();
} }
}
private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e) private void DynamicChildOnDynamicChildRemoved(object? sender, DynamicDataModelChildEventArgs e)
{ {

View File

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using Artemis.Storage.Entities.Profile.Nodes;
namespace Artemis.Core namespace Artemis.Core
{ {
@ -17,6 +18,7 @@ namespace Artemis.Core
public IReadOnlyCollection<IPin> Pins { get; } public IReadOnlyCollection<IPin> Pins { get; }
public IReadOnlyCollection<IPinCollection> PinCollections { get; } public IReadOnlyCollection<IPinCollection> PinCollections { get; }
event EventHandler Resetting; event EventHandler Resetting;
void Initialize(INodeScript script); void Initialize(INodeScript script);

View File

@ -10,6 +10,14 @@ namespace Artemis.Core
{ {
public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageModel public abstract class NodeScript : CorePropertyChanged, INodeScript, IStorageModel
{ {
private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e)
{
Load();
}
public event EventHandler<INode>? NodeAdded;
public event EventHandler<INode>? NodeRemoved;
#region Properties & Fields #region Properties & Fields
internal NodeScriptEntity Entity { get; } internal NodeScriptEntity Entity { get; }
@ -28,21 +36,14 @@ namespace Artemis.Core
#endregion #endregion
#region Events
public event EventHandler<INode>? NodeAdded;
public event EventHandler<INode>? NodeRemoved;
#endregion
#region Constructors #region Constructors
public NodeScript(string name, string description, object? context = null) public NodeScript(string name, string description, object? context = null)
{ {
this.Name = name; Name = name;
this.Description = description; Description = description;
this.Context = context; Context = context;
this.Entity = new NodeScriptEntity(); Entity = new NodeScriptEntity();
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged;
@ -50,10 +51,10 @@ namespace Artemis.Core
internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null) internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null)
{ {
this.Name = name; Name = name;
this.Description = description; Description = description;
this.Entity = entity; Entity = entity;
this.Context = context; Context = context;
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeChanged;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeChanged;
@ -89,6 +90,12 @@ namespace Artemis.Core
{ {
NodeTypeStore.NodeTypeAdded -= NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeAdded -= NodeTypeStoreOnNodeTypeChanged;
NodeTypeStore.NodeTypeRemoved -= NodeTypeStoreOnNodeTypeChanged; NodeTypeStore.NodeTypeRemoved -= NodeTypeStoreOnNodeTypeChanged;
foreach (INode node in _nodes)
{
if (node is IDisposable disposable)
disposable.Dispose();
}
} }
#endregion #endregion
@ -98,20 +105,18 @@ namespace Artemis.Core
/// <inheritdoc /> /// <inheritdoc />
public void Load() public void Load()
{ {
_nodes.Clear();
// Create nodes // Create nodes
Dictionary<int, INode> nodes = new();
foreach (NodeEntity entityNode in Entity.Nodes) foreach (NodeEntity entityNode in Entity.Nodes)
{ {
INode? node = LoadNode(entityNode, entityNode.IsExitNode ? ExitNode : null); INode? node = LoadNode(entityNode, entityNode.IsExitNode ? ExitNode : null);
if (node == null) if (node == null)
continue; continue;
nodes.Add(entityNode.Id, node); _nodes.Add(node);
} }
LoadConnections(nodes); LoadConnections();
_nodes.Clear();
_nodes.AddRange(nodes.Values);
} }
private INode? LoadNode(NodeEntity nodeEntity, INode? node) private INode? LoadNode(NodeEntity nodeEntity, INode? node)
@ -147,12 +152,19 @@ namespace Artemis.Core
return node; return node;
} }
private void LoadConnections(Dictionary<int, INode> nodes) /// <summary>
/// Loads missing connections between the nodes of this node script from the <see cref="Entity"/>
/// </summary>
public void LoadConnections()
{ {
List<INode> nodes = Nodes.ToList();
foreach (NodeConnectionEntity nodeConnectionEntity in Entity.Connections) foreach (NodeConnectionEntity nodeConnectionEntity in Entity.Connections)
{ {
// Find the source and target node INode? source = nodes.ElementAtOrDefault(nodeConnectionEntity.SourceNode);
if (!nodes.TryGetValue(nodeConnectionEntity.SourceNode, out INode? source) || !nodes.TryGetValue(nodeConnectionEntity.TargetNode, out INode? target)) if (source == null)
continue;
INode? target = nodes.ElementAtOrDefault(nodeConnectionEntity.TargetNode);
if (target == null)
continue; continue;
IPin? sourcePin = nodeConnectionEntity.SourcePinCollectionId == -1 IPin? sourcePin = nodeConnectionEntity.SourcePinCollectionId == -1
@ -165,8 +177,14 @@ namespace Artemis.Core
// Ensure both nodes have the required pins // Ensure both nodes have the required pins
if (sourcePin == null || targetPin == null) if (sourcePin == null || targetPin == null)
continue; continue;
// Ensure the connection is valid
if (sourcePin.Direction == targetPin.Direction)
continue;
// 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); targetPin.ConnectTo(sourcePin);
if (!sourcePin.ConnectedTo.Contains(targetPin))
sourcePin.ConnectTo(targetPin); sourcePin.ConnectTo(targetPin);
} }
} }
@ -184,7 +202,6 @@ namespace Artemis.Core
return; return;
int id = 0; int id = 0;
foreach (INode node in Nodes) foreach (INode node in Nodes)
{ {
NodeEntity nodeEntity = new() NodeEntity nodeEntity = new()
@ -248,9 +265,11 @@ namespace Artemis.Core
targetPinId = targetCollection.ToList().IndexOf(targetPin); targetPinId = targetCollection.ToList().IndexOf(targetPin);
} }
else else
{
targetPinId = targetPin.Node.Pins.IndexOf(targetPin); targetPinId = targetPin.Node.Pins.IndexOf(targetPin);
}
Entity.Connections.Add(new NodeConnectionEntity() Entity.Connections.Add(new NodeConnectionEntity
{ {
SourceType = sourcePin.Type.Name, SourceType = sourcePin.Type.Name,
SourceNode = nodes.IndexOf(node), SourceNode = nodes.IndexOf(node),
@ -259,7 +278,7 @@ namespace Artemis.Core
TargetType = targetPin.Type.Name, TargetType = targetPin.Type.Name,
TargetNode = nodes.IndexOf(targetPin.Node), TargetNode = nodes.IndexOf(targetPin.Node),
TargetPinCollectionId = targetPinCollectionId, TargetPinCollectionId = targetPinCollectionId,
TargetPinId = targetPinId, TargetPinId = targetPinId
}); });
} }
@ -268,15 +287,6 @@ namespace Artemis.Core
} }
#endregion #endregion
#region Event handlers
private void NodeTypeStoreOnNodeTypeChanged(object? sender, NodeTypeStoreEvent e)
{
Load();
}
#endregion
} }
public class NodeScript<T> : NodeScript, INodeScript<T> public class NodeScript<T> : NodeScript, INodeScript<T>

View File

@ -44,7 +44,10 @@ namespace Artemis.UI.Shared.Controls
/// <summary> /// <summary>
/// Gets or sets the brush to use when drawing the button /// Gets or sets the brush to use when drawing the button
/// </summary> /// </summary>
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)
);
/// <summary> /// <summary>
/// Gets or sets the brush to use when drawing the button /// Gets or sets the brush to use when drawing the button
@ -86,7 +89,7 @@ namespace Artemis.UI.Shared.Controls
public DataModelPicker() public DataModelPicker()
{ {
SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand); SelectPropertyCommand = new DelegateCommand(ExecuteSelectPropertyCommand);
Unloaded += (_, _) => DataModelViewModel?.Dispose();
InitializeComponent(); InitializeComponent();
GetDataModel(); GetDataModel();
UpdateValueDisplay(); UpdateValueDisplay();
@ -253,7 +256,12 @@ namespace Artemis.UI.Shared.Controls
if (context is not DataModelVisualizationViewModel selected) if (context is not DataModelVisualizationViewModel selected)
return; return;
DataModelPath = selected.DataModelPath; if (selected.DataModelPath == null)
return;
if (selected.DataModelPath.Equals(DataModelPath))
return;
DataModelPath = new DataModelPath(selected.DataModelPath);
OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath)); OnDataModelPathSelected(new DataModelSelectedEventArgs(DataModelPath));
} }
@ -278,6 +286,14 @@ namespace Artemis.UI.Shared.Controls
dataModelPicker.UpdateValueDisplay(); 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) private static void ModulesPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{ {
if (d is not DataModelPicker dataModelPicker) if (d is not DataModelPicker dataModelPicker)
@ -290,6 +306,9 @@ namespace Artemis.UI.Shared.Controls
{ {
if (d is not DataModelPicker dataModelPicker) if (d is not DataModelPicker dataModelPicker)
return; return;
if (e.OldValue is DataModelPropertiesViewModel vm)
vm.Dispose();
} }
private static void ExtraDataModelViewModelsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) private static void ExtraDataModelViewModelsPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)

View File

@ -11,10 +11,12 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels
private readonly DataModelNode _node; private readonly DataModelNode _node;
private BindableCollection<Module> _modules; private BindableCollection<Module> _modules;
public DataModelNodeCustomViewModel(DataModelNode node, ISettingsService settingsService) : base(node) public DataModelNodeCustomViewModel(DataModelNode node, ISettingsService settingsService, IPluginManagementService test) : base(node)
{ {
_node = node; _node = node;
var tessst = test.GetFeaturesOfType<Module>();
ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true); ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true);
ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false); ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
} }
@ -33,19 +35,15 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels
get => _node.DataModelPath; get => _node.DataModelPath;
set set
{ {
if (ReferenceEquals(_node.DataModelPath, value))
return;
_node.DataModelPath.Dispose();
_node.DataModelPath = value; _node.DataModelPath = value;
if (_node.DataModelPath != null)
{
_node.DataModelPath.Save(); _node.DataModelPath.Save();
_node.Storage = _node.DataModelPath.Entity;
}
else
{
_node.Storage = null;
}
_node.UpdateOutputPin(); _node.Storage = _node.DataModelPath.Entity;
_node.UpdateOutputPin(false);
} }
} }

View File

@ -3,11 +3,12 @@ using System.Linq;
using Artemis.Core; using Artemis.Core;
using Artemis.Storage.Entities.Profile; using Artemis.Storage.Entities.Profile;
using Artemis.VisualScripting.Nodes.CustomViewModels; using Artemis.VisualScripting.Nodes.CustomViewModels;
using Stylet;
namespace Artemis.VisualScripting.Nodes namespace Artemis.VisualScripting.Nodes
{ {
[Node("Data Model-Value", "Outputs a selectable data model value.")] [Node("Data Model-Value", "Outputs a selectable data model value.")]
public class DataModelNode : Node<DataModelNodeCustomViewModel> public class DataModelNode : Node<DataModelNodeCustomViewModel>, IDisposable
{ {
private DataModelPath _dataModelPath; private DataModelPath _dataModelPath;
@ -21,7 +22,7 @@ namespace Artemis.VisualScripting.Nodes
public DataModelPath DataModelPath public DataModelPath DataModelPath
{ {
get => _dataModelPath; get => _dataModelPath;
set => SetAndNotify(ref _dataModelPath , value); set => SetAndNotify(ref _dataModelPath, value);
} }
public override void Initialize(INodeScript script) public override void Initialize(INodeScript script)
@ -32,21 +33,30 @@ namespace Artemis.VisualScripting.Nodes
return; return;
DataModelPath = new DataModelPath(null, pathEntity); DataModelPath = new DataModelPath(null, pathEntity);
UpdateOutputPin(); DataModelPath.PathValidated += DataModelPathOnPathValidated;
UpdateOutputPin(false);
} }
public override void Evaluate() public override void Evaluate()
{ {
if (DataModelPath.IsValid && Output != null) if (DataModelPath.IsValid)
Output.Value = DataModelPath.GetValue()!; {
if (Output == null)
UpdateOutputPin(false);
Output.Value = DataModelPath.GetValue() ?? Output.Type.GetDefault();
}
} }
public void UpdateOutputPin() public void UpdateOutputPin(bool loadConnections)
{
Execute.OnUIThread(() =>
{ {
if (Output != null && Output.Type == DataModelPath?.GetPropertyType()) if (Output != null && Output.Type == DataModelPath?.GetPropertyType())
return; return;
if (Output != null && Pins.Contains(Output)) if (Output != null)
{ {
RemovePin(Output); RemovePin(Output);
Output = null; Output = null;
@ -55,6 +65,26 @@ namespace Artemis.VisualScripting.Nodes
Type type = DataModelPath?.GetPropertyType(); Type type = DataModelPath?.GetPropertyType();
if (type != null) if (type != null)
Output = CreateOutputPin(type); Output = CreateOutputPin(type);
if (loadConnections && Script is NodeScript nodeScript)
nodeScript.LoadConnections();
});
} }
private void DataModelPathOnPathValidated(object sender, EventArgs e)
{
UpdateOutputPin(true);
}
#region IDisposable
/// <inheritdoc />
public void Dispose()
{
DataModelPath.Dispose();
}
#endregion
} }
} }