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

Event conditions - Fix start-node disconnecting

Input - Performance optimizations
This commit is contained in:
Robert 2022-09-16 19:40:06 +02:00 committed by RobertBeekman
parent b19854ee47
commit b779a86d13
15 changed files with 298 additions and 251 deletions

View File

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Internal;
using Artemis.Storage.Entities.Profile.Abstract;
@ -13,12 +14,12 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{
private readonly string _displayName;
private readonly EventConditionEntity _entity;
private NodeScript<bool> _script;
private DefaultNode _startNode;
private DataModelPath? _eventPath;
private DateTime _lastProcessedTrigger;
private object? _lastProcessedValue;
private EventOverlapMode _overlapMode;
private NodeScript<bool> _script;
private IEventConditionNode _startNode;
private EventToggleOffMode _toggleOffMode;
private EventTriggerMode _triggerMode;
private bool _wasMet;
@ -33,7 +34,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
_entity = new EventConditionEntity();
_displayName = profileElement.GetType().Name;
_startNode = new EventConditionEventStartNode {X = -300};
_script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
_script = new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile, new List<DefaultNode> {_startNode});
}
internal EventCondition(EventConditionEntity entity, RenderProfileElement profileElement)
@ -95,109 +96,6 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
set => SetAndNotify(ref _toggleOffMode, value);
}
/// <summary>
/// Updates the event node, applying the selected event
/// </summary>
public void UpdateEventNode()
{
if (EventPath == null)
return;
Type? pathType = EventPath.GetPropertyType();
if (pathType == null)
return;
// Create an event node if the path type is a data model event
if (pathType.IsAssignableTo(typeof(IDataModelEvent)))
{
EventConditionEventStartNode eventNode;
// Ensure the start node is an event node
if (_startNode is not EventConditionEventStartNode node)
{
eventNode = new EventConditionEventStartNode();
ReplaceStartNode(eventNode);
_startNode = eventNode;
}
else
{
eventNode = node;
}
IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent;
eventNode.CreatePins(dataModelEvent);
}
// Create a value changed node if the path type is a regular value
else
{
// Ensure the start nod is a value changed node
EventConditionValueChangedStartNode valueChangedNode;
// Ensure the start node is an event node
if (_startNode is not EventConditionValueChangedStartNode node)
{
valueChangedNode = new EventConditionValueChangedStartNode();
ReplaceStartNode(valueChangedNode);
}
else
{
valueChangedNode = node;
}
valueChangedNode.UpdateOutputPins(EventPath);
}
if (!Script.Nodes.Contains(_startNode))
Script.AddNode(_startNode);
Script.Save();
}
/// <summary>
/// Gets the start node of the event script, if any
/// </summary>
/// <returns>The start node of the event script, if any.</returns>
public INode GetStartNode()
{
return _startNode;
}
private void ReplaceStartNode(IEventConditionNode newStartNode)
{
if (Script.Nodes.Contains(_startNode))
Script.RemoveNode(_startNode);
_startNode = newStartNode;
if (!Script.Nodes.Contains(_startNode))
Script.AddNode(_startNode);
}
private bool Evaluate()
{
if (EventPath == null)
return false;
object? value = EventPath.GetValue();
if (_startNode is EventConditionEventStartNode)
{
if (value is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger)
return false;
_lastProcessedTrigger = dataModelEvent.LastTrigger;
}
else if (_startNode is EventConditionValueChangedStartNode valueChangedNode)
{
if (Equals(value, _lastProcessedValue))
return false;
valueChangedNode.UpdateValues(value, _lastProcessedValue);
_lastProcessedValue = value;
}
if (!Script.ExitNodeConnected)
return true;
Script.Run();
return Script.Result;
}
/// <inheritdoc />
public IConditionEntity Entity => _entity;
@ -263,6 +161,116 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
Script?.Dispose();
EventPath?.Dispose();
}
/// <summary>
/// Updates the event node, applying the selected event
/// </summary>
public void UpdateEventNode(bool updateScript)
{
if (EventPath == null)
return;
Type? pathType = EventPath.GetPropertyType();
if (pathType == null)
return;
// Create an event node if the path type is a data model event
if (pathType.IsAssignableTo(typeof(IDataModelEvent)))
{
EventConditionEventStartNode eventNode;
// Ensure the start node is an event node
if (_startNode is not EventConditionEventStartNode node)
{
eventNode = new EventConditionEventStartNode();
if (updateScript)
ReplaceStartNode(eventNode);
_startNode = eventNode;
}
else
{
eventNode = node;
}
IDataModelEvent? dataModelEvent = EventPath?.GetValue() as IDataModelEvent;
eventNode.CreatePins(dataModelEvent);
}
// Create a value changed node if the path type is a regular value
else
{
// Ensure the start nod is a value changed node
EventConditionValueChangedStartNode valueChangedNode;
// Ensure the start node is an event node
if (_startNode is not EventConditionValueChangedStartNode node)
{
valueChangedNode = new EventConditionValueChangedStartNode();
if (updateScript)
ReplaceStartNode(valueChangedNode);
_startNode = valueChangedNode;
}
else
{
valueChangedNode = node;
}
valueChangedNode.UpdateOutputPins(EventPath);
}
// Script can be null if called before load
if (!updateScript)
return;
if (!Script.Nodes.Contains(_startNode))
{
Script.AddNode(_startNode);
Script.LoadConnections();
}
Script.Save();
}
/// <summary>
/// Gets the start node of the event script, if any
/// </summary>
/// <returns>The start node of the event script, if any.</returns>
public INode GetStartNode()
{
return _startNode;
}
private void ReplaceStartNode(DefaultNode newStartNode)
{
if (Script.Nodes.Contains(_startNode))
Script.RemoveNode(_startNode);
if (!Script.Nodes.Contains(newStartNode))
Script.AddNode(newStartNode);
}
private bool Evaluate()
{
if (EventPath == null)
return false;
object? value = EventPath.GetValue();
if (_startNode is EventConditionEventStartNode)
{
if (value is not IDataModelEvent dataModelEvent || dataModelEvent.LastTrigger <= _lastProcessedTrigger)
return false;
_lastProcessedTrigger = dataModelEvent.LastTrigger;
}
else if (_startNode is EventConditionValueChangedStartNode valueChangedNode)
{
if (Equals(value, _lastProcessedValue))
return false;
valueChangedNode.UpdateValues(value, _lastProcessedValue);
_lastProcessedValue = value;
}
if (!Script.ExitNodeConnected)
return true;
Script.Run();
return Script.Result;
}
#region Storage
@ -275,11 +283,13 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
if (_entity.EventPath != null)
EventPath = new DataModelPath(_entity.EventPath);
UpdateEventNode(false);
string name = $"Activate {_displayName}";
string description = $"Whether or not the event should activate the {_displayName}";
Script = _entity.Script != null
? new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", _entity.Script, ProfileElement.Profile)
: new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile);
UpdateEventNode();
? new NodeScript<bool>(name, description, _entity.Script, ProfileElement.Profile, new List<DefaultNode> {_startNode})
: new NodeScript<bool>(name, description, ProfileElement.Profile, new List<DefaultNode> {_startNode});
}
/// <inheritdoc />
@ -310,71 +320,9 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
/// <inheritdoc />
public void LoadNodeScript()
{
UpdateEventNode(true);
Script.Load();
// The load action may have created an event node, use that one over the one we have here
INode? existingEventNode = Script.Nodes.FirstOrDefault(n => n.Id == EventConditionEventStartNode.NodeId || n.Id == EventConditionValueChangedStartNode.NodeId);
if (existingEventNode != null)
_startNode = (IEventConditionNode) existingEventNode;
UpdateEventNode();
Script.LoadConnections();
}
#endregion
}
/// <summary>
/// Represents a mode for render elements to start their timeline when display conditions events are fired.
/// </summary>
public enum EventTriggerMode
{
/// <summary>
/// Play the timeline once.
/// </summary>
Play,
/// <summary>
/// Toggle repeating the timeline.
/// </summary>
Toggle
}
/// <summary>
/// Represents a mode for render elements to configure the behaviour of events that overlap i.e. trigger again before
/// the timeline finishes.
/// </summary>
public enum EventOverlapMode
{
/// <summary>
/// Stop the current run and restart the timeline
/// </summary>
Restart,
/// <summary>
/// Play another copy of the timeline on top of the current run
/// </summary>
Copy,
/// <summary>
/// Ignore subsequent event fires until the timeline finishes
/// </summary>
Ignore
}
/// <summary>
/// Represents a mode for render elements when toggling off the event when using <see cref="EventTriggerMode.Toggle" />
/// .
/// </summary>
public enum EventToggleOffMode
{
/// <summary>
/// When the event toggles the condition off, finish the the current run of the main timeline
/// </summary>
Finish,
/// <summary>
/// When the event toggles the condition off, skip to the end segment of the timeline
/// </summary>
SkipToEnd
}

View File

@ -0,0 +1,23 @@
namespace Artemis.Core;
/// <summary>
/// Represents a mode for render elements to configure the behaviour of events that overlap i.e. trigger again before
/// the timeline finishes.
/// </summary>
public enum EventOverlapMode
{
/// <summary>
/// Stop the current run and restart the timeline
/// </summary>
Restart,
/// <summary>
/// Play another copy of the timeline on top of the current run
/// </summary>
Copy,
/// <summary>
/// Ignore subsequent event fires until the timeline finishes
/// </summary>
Ignore
}

View File

@ -0,0 +1,18 @@
namespace Artemis.Core;
/// <summary>
/// Represents a mode for render elements when toggling off the event when using <see cref="EventTriggerMode.Toggle" />
/// .
/// </summary>
public enum EventToggleOffMode
{
/// <summary>
/// When the event toggles the condition off, finish the the current run of the main timeline
/// </summary>
Finish,
/// <summary>
/// When the event toggles the condition off, skip to the end segment of the timeline
/// </summary>
SkipToEnd
}

View File

@ -0,0 +1,17 @@
namespace Artemis.Core;
/// <summary>
/// Represents a mode for render elements to start their timeline when display conditions events are fired.
/// </summary>
public enum EventTriggerMode
{
/// <summary>
/// Play the timeline once.
/// </summary>
Play,
/// <summary>
/// Toggle repeating the timeline.
/// </summary>
Toggle
}

View File

@ -8,6 +8,13 @@ namespace Artemis.Core.Services;
/// </summary>
public abstract class InputProvider : IDisposable
{
public InputProvider()
{
ProviderName = GetType().FullName ?? throw new InvalidOperationException("Input provider must have a type with a name");
}
internal string ProviderName { get; set; }
/// <summary>
/// Called when the input service requests a <see cref="KeyboardToggleStatusReceived" /> event
/// </summary>

View File

@ -10,12 +10,18 @@ internal class InputService : IInputService
{
private readonly ILogger _logger;
private readonly IRgbService _rgbService;
private ArtemisDevice? _firstKeyboard;
private ArtemisDevice? _firstMouse;
private int _keyboardCount;
private int _mouseCount;
public InputService(ILogger logger, IRgbService rgbService)
{
_logger = logger;
_rgbService = rgbService;
_rgbService.DeviceAdded += RgbServiceOnDevicesModified;
_rgbService.DeviceRemoved += RgbServiceOnDevicesModified;
BustIdentifierCache();
}
@ -132,8 +138,6 @@ internal class InputService : IInputService
private readonly Dictionary<Tuple<InputProvider, object>, ArtemisDevice> _deviceCache = new();
private List<ArtemisDevice> _devices = new();
private ArtemisDevice? _cachedFallbackKeyboard;
private ArtemisDevice? _cachedFallbackMouse;
private ArtemisDevice? _identifyingDevice;
public void IdentifyDevice(ArtemisDevice device)
@ -164,13 +168,29 @@ internal class InputService : IInputService
if (provider == null) throw new ArgumentNullException(nameof(provider));
if (identifier == null) throw new ArgumentNullException(nameof(identifier));
// We will almost always only have zero or one of each
if (type == InputDeviceType.Keyboard)
{
if (_keyboardCount == 0)
return null;
if (_keyboardCount == 1)
return _firstKeyboard;
}
if (type == InputDeviceType.Mouse)
{
if (_mouseCount == 0)
return null;
if (_mouseCount == 1)
return _firstMouse;
}
// Try cache first
ArtemisDevice? cacheMatch = GetDeviceFromCache(provider, identifier);
if (cacheMatch != null)
return cacheMatch;
string providerName = provider.GetType().FullName!;
ArtemisDevice? match = _devices.FirstOrDefault(m => m.InputIdentifiers.Any(i => Equals(i.InputProvider, providerName) && Equals(i.Identifier, identifier)));
ArtemisDevice? match = _devices.FirstOrDefault(m => m.InputIdentifiers.Any(i => Equals(i.InputProvider, provider.ProviderName) && Equals(i.Identifier, identifier)));
// If a match was found cache it to speed up the next event and return the match
if (match != null)
@ -179,34 +199,16 @@ internal class InputService : IInputService
return match;
}
// If there is no match, apply our fallback type
if (type == InputDeviceType.None)
return null;
if (type == InputDeviceType.Keyboard)
{
if (_cachedFallbackKeyboard != null)
return _cachedFallbackKeyboard;
_cachedFallbackKeyboard = _rgbService.EnabledDevices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard);
return _cachedFallbackKeyboard;
}
return _firstKeyboard;
if (type == InputDeviceType.Mouse)
{
if (_cachedFallbackMouse != null)
return _cachedFallbackMouse;
_cachedFallbackMouse = _rgbService.EnabledDevices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Mouse);
return _cachedFallbackMouse;
}
return _firstMouse;
return null;
}
public void BustIdentifierCache()
{
_deviceCache.Clear();
_cachedFallbackKeyboard = null;
_cachedFallbackMouse = null;
_devices = _rgbService.EnabledDevices.Where(d => d.InputIdentifiers.Any()).ToList();
}
@ -220,12 +222,7 @@ internal class InputService : IInputService
_deviceCache.TryGetValue(new Tuple<InputProvider, object>(provider, identifier), out ArtemisDevice? device);
return device;
}
private void SurfaceConfigurationChanged(object? sender, SurfaceConfigurationEventArgs e)
{
BustIdentifierCache();
}
private void InputProviderOnIdentifierReceived(object? sender, InputProviderIdentifierEventArgs e)
{
// Don't match if there is no device or if the device type differs from the event device type
@ -236,16 +233,24 @@ internal class InputService : IInputService
if (!(sender is InputProvider inputProvider))
return;
string providerName = inputProvider.GetType().FullName!;
// Remove existing identification
_identifyingDevice.InputIdentifiers.RemoveAll(i => i.InputProvider == providerName);
_identifyingDevice.InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(providerName, e.Identifier));
_identifyingDevice.InputIdentifiers.RemoveAll(i => i.InputProvider == inputProvider.ProviderName);
_identifyingDevice.InputIdentifiers.Add(new ArtemisDeviceInputIdentifier(inputProvider.ProviderName, e.Identifier));
StopIdentify();
OnDeviceIdentified();
}
private void RgbServiceOnDevicesModified(object? sender, DeviceEventArgs args)
{
_firstKeyboard = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Keyboard);
_firstMouse = _rgbService.Devices.FirstOrDefault(d => d.DeviceType == RGBDeviceType.Mouse);
_keyboardCount = _rgbService.Devices.Count(d => d.DeviceType == RGBDeviceType.Keyboard);
_mouseCount = _rgbService.Devices.Count(d => d.DeviceType == RGBDeviceType.Mouse);
BustIdentifierCache();
}
#endregion
#region Keyboard
@ -379,6 +384,7 @@ internal class InputService : IInputService
private readonly HashSet<MouseButton> _pressedButtons = new();
private void InputProviderOnMouseButtonDataReceived(object? sender, InputProviderMouseButtonEventArgs e)
{
bool foundLedId = InputKeyUtilities.MouseButtonLedIdMap.TryGetValue(e.Button, out LedId ledId);

View File

@ -2,7 +2,7 @@
namespace Artemis.Core.Internal;
internal class EventConditionEventStartNode : DefaultNode, IEventConditionNode
internal class EventConditionEventStartNode : DefaultNode
{
internal static readonly Guid NodeId = new("278735FE-69E9-4A73-A6B8-59E83EE19305");
private readonly ObjectOutputPins _objectOutputPins;
@ -13,6 +13,11 @@ internal class EventConditionEventStartNode : DefaultNode, IEventConditionNode
_objectOutputPins = new ObjectOutputPins(this);
}
public void SetDataModelEvent(IDataModelEvent? dataModelEvent)
{
}
public void CreatePins(IDataModelEvent? dataModelEvent)
{
if (_dataModelEvent == dataModelEvent)

View File

@ -2,7 +2,7 @@
namespace Artemis.Core.Internal;
internal class EventConditionValueChangedStartNode : DefaultNode, IEventConditionNode
internal class EventConditionValueChangedStartNode : DefaultNode
{
internal static readonly Guid NodeId = new("F9A270DB-A231-4800-BAB3-DC1F96856756");
private object? _newValue;

View File

@ -1,5 +0,0 @@
namespace Artemis.Core.Internal;
internal interface IEventConditionNode : INode
{
}

View File

@ -339,7 +339,7 @@ public abstract class Node : BreakableModel, INode
}
/// <summary>
/// Called when the node was loaded from storage or newly created
/// Called when the node was loaded from storage or newly created, at this point pin connections aren't reestablished yet.
/// </summary>
/// <param name="script">The script the node is contained in</param>
public virtual void Initialize(INodeScript script)

View File

@ -80,7 +80,8 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
/// The context of the node script, usually a <see cref="Profile" /> or
/// <see cref="ProfileConfiguration" />
/// </param>
protected NodeScript(string name, string description, object? context = null)
/// <param name="defaultNodes">A list of default nodes to add to the node script.</param>
protected NodeScript(string name, string description, object? context = null, List<DefaultNode>? defaultNodes = null)
{
Name = name;
Description = description;
@ -90,9 +91,15 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved;
if (defaultNodes != null)
{
foreach (DefaultNode defaultNode in defaultNodes)
AddNode(defaultNode);
}
}
internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null)
internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null, List<DefaultNode>? defaultNodes = null)
{
Name = name;
Description = description;
@ -102,6 +109,12 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved;
if (defaultNodes != null)
{
foreach (DefaultNode defaultNode in defaultNodes)
AddNode(defaultNode);
}
}
#endregion
@ -414,8 +427,8 @@ public class NodeScript<T> : NodeScript, INodeScript<T>
#region Constructors
/// <inheritdoc />
public NodeScript(string name, string description, NodeScriptEntity entity, object? context = null)
: base(name, description, entity, context)
public NodeScript(string name, string description, NodeScriptEntity entity, object? context = null, List<DefaultNode>? defaultNodes = null)
: base(name, description, entity, context, defaultNodes)
{
ExitNode = new ExitNode<T>(name, description);
AddNode(ExitNode);
@ -424,8 +437,8 @@ public class NodeScript<T> : NodeScript, INodeScript<T>
}
/// <inheritdoc />
public NodeScript(string name, string description, object? context = null)
: base(name, description, context)
public NodeScript(string name, string description, object? context = null, List<DefaultNode>? defaultNodes = null)
: base(name, description, context, defaultNodes)
{
ExitNode = new ExitNode<T>(name, description);
AddNode(ExitNode);

View File

@ -49,7 +49,7 @@ public class UpdateEventConditionPath : IProfileEditorCommand, IDisposable
// Change the end node
_eventCondition.EventPath = _value;
_eventCondition.UpdateEventNode();
_eventCondition.UpdateEventNode(true);
_executed = true;
}
@ -59,7 +59,7 @@ public class UpdateEventConditionPath : IProfileEditorCommand, IDisposable
{
// Change the end node
_eventCondition.EventPath = _oldValue;
_eventCondition.UpdateEventNode();
_eventCondition.UpdateEventNode(true);
// Restore old connections
_store?.Restore();

View File

@ -19,7 +19,6 @@ public class WindowsInputProvider : InputProvider
private readonly ILogger _logger;
private readonly SpongeWindow _sponge;
private readonly Timer _taskManagerTimer;
private DateTime _lastMouseUpdate;
private int _lastProcessId;
public WindowsInputProvider(ILogger logger, IInputService inputService)
@ -160,8 +159,8 @@ public class WindowsInputProvider : InputProvider
#region Mouse
private int _mouseDeltaX;
private int _mouseDeltaY;
private int _previousMouseX;
private int _previousMouseY;
private void HandleMouseData(RawInputData data, RawInputMouseData mouseData)
{
@ -169,10 +168,8 @@ public class WindowsInputProvider : InputProvider
// This can create a small inaccuracy of course, but Artemis is not a shooter :')
if (mouseData.Mouse.Buttons == RawMouseButtonFlags.None)
{
_mouseDeltaX += mouseData.Mouse.LastX;
_mouseDeltaY += mouseData.Mouse.LastY;
if (DateTime.Now - _lastMouseUpdate < TimeSpan.FromMilliseconds(40))
return;
_previousMouseX += mouseData.Mouse.LastX;
_previousMouseY += mouseData.Mouse.LastY;
}
ArtemisDevice? device = null;
@ -193,10 +190,7 @@ public class WindowsInputProvider : InputProvider
if (mouseData.Mouse.Buttons == RawMouseButtonFlags.None)
{
Win32Point cursorPosition = GetCursorPosition();
OnMouseMoveDataReceived(device, cursorPosition.X, cursorPosition.Y, _mouseDeltaX, _mouseDeltaY);
_mouseDeltaX = 0;
_mouseDeltaY = 0;
_lastMouseUpdate = DateTime.Now;
OnMouseMoveDataReceived(device, cursorPosition.X, cursorPosition.Y, cursorPosition.X - _previousMouseX, cursorPosition.Y - _previousMouseY);
return;
}

View File

@ -9,10 +9,11 @@ namespace Artemis.VisualScripting.Nodes.List;
public class ListOperatorPredicateNode : Node<ListOperatorEntity, ListOperatorPredicateNodeCustomViewModel>, IDisposable
{
private readonly object _scriptLock = new();
private ListOperatorPredicateStartNode? _startNode;
private ListOperatorPredicateStartNode _startNode;
public ListOperatorPredicateNode() : base("List Operator (Advanced)", "Checks if any/all/no values in the input list match a condition")
{
_startNode = new ListOperatorPredicateStartNode {X = -200};
InputList = CreateInputPin<IList>();
Output = CreateOutputPin<bool>();
@ -31,21 +32,8 @@ public class ListOperatorPredicateNode : Node<ListOperatorEntity, ListOperatorPr
lock (_scriptLock)
{
Script = Storage?.Script != null
? new NodeScript<bool>("Is match", "Determines whether the current list item is a match", Storage.Script, script.Context)
: new NodeScript<bool>("Is match", "Determines whether the current list item is a match", script.Context);
// The load action may have created an event node, use that one over the one we have here
INode? existingEventNode = Script.Nodes.FirstOrDefault(n => n.Id == ListOperatorPredicateStartNode.NodeId);
if (existingEventNode != null)
_startNode = (ListOperatorPredicateStartNode) existingEventNode;
else
{
_startNode = new ListOperatorPredicateStartNode {X = -200};
Script.AddNode(_startNode);
}
UpdateStartNode();
Script.LoadConnections();
? new NodeScript<bool>("Is match", "Determines whether the current list item is a match", Storage.Script, script.Context, new List<DefaultNode> {_startNode})
: new NodeScript<bool>("Is match", "Determines whether the current list item is a match", script.Context, new List<DefaultNode> {_startNode});
}
}
@ -101,6 +89,7 @@ public class ListOperatorPredicateNode : Node<ListOperatorEntity, ListOperatorPr
lock (_scriptLock)
{
UpdateStartNode();
Script?.LoadConnections();
}
}

View File

@ -1,5 +1,7 @@
using System.Reactive;
using System.Reactive.Disposables;
using Artemis.Core;
using Artemis.Core.Events;
using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.VisualScripting;
@ -12,17 +14,37 @@ public class ListOperatorPredicateNodeCustomViewModel : CustomNodeViewModel
private readonly ListOperatorPredicateNode _node;
private readonly IWindowService _windowService;
private ListOperator _operator;
private bool _canOpenEditor;
public ListOperatorPredicateNodeCustomViewModel(ListOperatorPredicateNode node, INodeScript script, IWindowService windowService) : base(node, script)
{
_node = node;
_windowService = windowService;
OpenEditor = ReactiveCommand.CreateFromTask(ExecuteOpenEditor);
OpenEditor = ReactiveCommand.CreateFromTask(ExecuteOpenEditor, this.WhenAnyValue(vm => vm.CanOpenEditor));
CanOpenEditor = node.InputList.ConnectedTo.Any();
this.WhenActivated(d =>
{
node.InputList.PinConnected += InputListOnPinConnected;
node.InputList.PinDisconnected += InputListOnPinDisconnected;
Disposable.Create(() =>
{
node.InputList.PinConnected -= InputListOnPinConnected;
node.InputList.PinDisconnected -= InputListOnPinDisconnected;
}).DisposeWith(d);
});
}
public ReactiveCommand<Unit, Unit> OpenEditor { get; }
private bool CanOpenEditor
{
get => _canOpenEditor;
set => this.RaiseAndSetIfChanged(ref _canOpenEditor, value);
}
public ListOperator Operator
{
get => _operator;
@ -40,4 +62,14 @@ public class ListOperatorPredicateNodeCustomViewModel : CustomNodeViewModel
_node.Storage ??= new ListOperatorEntity();
_node.Storage.Script = _node.Script.Entity;
}
private void InputListOnPinDisconnected(object? sender, SingleValueEventArgs<IPin> e)
{
CanOpenEditor = false;
}
private void InputListOnPinConnected(object? sender, SingleValueEventArgs<IPin> e)
{
CanOpenEditor = true;
}
}