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)
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<TProperty> property = new(getter, setter, displayName);
_properties.Add(property);
@ -110,6 +110,8 @@ namespace Artemis.Core
if (disposing)
{
_disposed = true;
_isEnabled = false;
Script.Dispose();
}
}

View File

@ -365,6 +365,31 @@ namespace Artemis.Core
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
}
}

View File

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

View File

@ -83,7 +83,7 @@ namespace Artemis.Core.Services
// ignored
}
}
node.Initialize(script);
return node;
}

View File

@ -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<IPin> Pins { get; }
public IReadOnlyCollection<IPinCollection> PinCollections { get; }
event EventHandler Resetting;

View File

@ -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<INode>? NodeAdded;
public event EventHandler<INode>? NodeRemoved;
#region Properties & Fields
internal NodeScriptEntity Entity { get; }
@ -28,21 +36,14 @@ namespace Artemis.Core
#endregion
#region Events
public event EventHandler<INode>? NodeAdded;
public event EventHandler<INode>? 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
/// <inheritdoc />
public void Load()
{
_nodes.Clear();
// Create nodes
Dictionary<int, INode> 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<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)
{
// 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<T> : NodeScript, INodeScript<T>

View File

@ -44,7 +44,10 @@ namespace Artemis.UI.Shared.Controls
/// <summary>
/// Gets or sets the brush to use when drawing the button
/// </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>
/// 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)

View File

@ -11,10 +11,12 @@ namespace Artemis.VisualScripting.Nodes.CustomViewModels
private readonly DataModelNode _node;
private BindableCollection<Module> _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<Module>();
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);
}
}

View File

@ -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<DataModelNodeCustomViewModel>
public class DataModelNode : Node<DataModelNodeCustomViewModel>, 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
/// <inheritdoc />
public void Dispose()
{
DataModelPath.Dispose();
}
#endregion
}
}