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;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using Artemis.Core.Internal; using Artemis.Core.Internal;
using Artemis.Storage.Entities.Profile.Abstract; using Artemis.Storage.Entities.Profile.Abstract;
@ -13,12 +14,12 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
{ {
private readonly string _displayName; private readonly string _displayName;
private readonly EventConditionEntity _entity; private readonly EventConditionEntity _entity;
private NodeScript<bool> _script;
private DefaultNode _startNode;
private DataModelPath? _eventPath; private DataModelPath? _eventPath;
private DateTime _lastProcessedTrigger; private DateTime _lastProcessedTrigger;
private object? _lastProcessedValue; private object? _lastProcessedValue;
private EventOverlapMode _overlapMode; private EventOverlapMode _overlapMode;
private NodeScript<bool> _script;
private IEventConditionNode _startNode;
private EventToggleOffMode _toggleOffMode; private EventToggleOffMode _toggleOffMode;
private EventTriggerMode _triggerMode; private EventTriggerMode _triggerMode;
private bool _wasMet; private bool _wasMet;
@ -33,7 +34,7 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
_entity = new EventConditionEntity(); _entity = new EventConditionEntity();
_displayName = profileElement.GetType().Name; _displayName = profileElement.GetType().Name;
_startNode = new EventConditionEventStartNode {X = -300}; _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) internal EventCondition(EventConditionEntity entity, RenderProfileElement profileElement)
@ -95,109 +96,6 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
set => SetAndNotify(ref _toggleOffMode, value); 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 /> /// <inheritdoc />
public IConditionEntity Entity => _entity; public IConditionEntity Entity => _entity;
@ -263,6 +161,116 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
Script?.Dispose(); Script?.Dispose();
EventPath?.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 #region Storage
@ -275,11 +283,13 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
if (_entity.EventPath != null) if (_entity.EventPath != null)
EventPath = new DataModelPath(_entity.EventPath); 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 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>(name, description, _entity.Script, ProfileElement.Profile, new List<DefaultNode> {_startNode})
: new NodeScript<bool>($"Activate {_displayName}", $"Whether or not the event should activate the {_displayName}", ProfileElement.Profile); : new NodeScript<bool>(name, description, ProfileElement.Profile, new List<DefaultNode> {_startNode});
UpdateEventNode();
} }
/// <inheritdoc /> /// <inheritdoc />
@ -310,71 +320,9 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
/// <inheritdoc /> /// <inheritdoc />
public void LoadNodeScript() public void LoadNodeScript()
{ {
UpdateEventNode(true);
Script.Load(); 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 #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> /// </summary>
public abstract class InputProvider : IDisposable 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> /// <summary>
/// Called when the input service requests a <see cref="KeyboardToggleStatusReceived" /> event /// Called when the input service requests a <see cref="KeyboardToggleStatusReceived" /> event
/// </summary> /// </summary>

View File

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

View File

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

View File

@ -2,7 +2,7 @@
namespace Artemis.Core.Internal; namespace Artemis.Core.Internal;
internal class EventConditionValueChangedStartNode : DefaultNode, IEventConditionNode internal class EventConditionValueChangedStartNode : DefaultNode
{ {
internal static readonly Guid NodeId = new("F9A270DB-A231-4800-BAB3-DC1F96856756"); internal static readonly Guid NodeId = new("F9A270DB-A231-4800-BAB3-DC1F96856756");
private object? _newValue; 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> /// <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> /// </summary>
/// <param name="script">The script the node is contained in</param> /// <param name="script">The script the node is contained in</param>
public virtual void Initialize(INodeScript script) 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 /// The context of the node script, usually a <see cref="Profile" /> or
/// <see cref="ProfileConfiguration" /> /// <see cref="ProfileConfiguration" />
/// </param> /// </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; Name = name;
Description = description; Description = description;
@ -90,9 +91,15 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded; NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved; 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; Name = name;
Description = description; Description = description;
@ -102,6 +109,12 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded; NodeTypeStore.NodeTypeAdded += NodeTypeStoreOnNodeTypeAdded;
NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved; NodeTypeStore.NodeTypeRemoved += NodeTypeStoreOnNodeTypeRemoved;
if (defaultNodes != null)
{
foreach (DefaultNode defaultNode in defaultNodes)
AddNode(defaultNode);
}
} }
#endregion #endregion
@ -414,8 +427,8 @@ public class NodeScript<T> : NodeScript, INodeScript<T>
#region Constructors #region Constructors
/// <inheritdoc /> /// <inheritdoc />
public NodeScript(string name, string description, NodeScriptEntity entity, object? context = null) public NodeScript(string name, string description, NodeScriptEntity entity, object? context = null, List<DefaultNode>? defaultNodes = null)
: base(name, description, entity, context) : base(name, description, entity, context, defaultNodes)
{ {
ExitNode = new ExitNode<T>(name, description); ExitNode = new ExitNode<T>(name, description);
AddNode(ExitNode); AddNode(ExitNode);
@ -424,8 +437,8 @@ public class NodeScript<T> : NodeScript, INodeScript<T>
} }
/// <inheritdoc /> /// <inheritdoc />
public NodeScript(string name, string description, object? context = null) public NodeScript(string name, string description, object? context = null, List<DefaultNode>? defaultNodes = null)
: base(name, description, context) : base(name, description, context, defaultNodes)
{ {
ExitNode = new ExitNode<T>(name, description); ExitNode = new ExitNode<T>(name, description);
AddNode(ExitNode); AddNode(ExitNode);

View File

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

View File

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

View File

@ -9,10 +9,11 @@ namespace Artemis.VisualScripting.Nodes.List;
public class ListOperatorPredicateNode : Node<ListOperatorEntity, ListOperatorPredicateNodeCustomViewModel>, IDisposable public class ListOperatorPredicateNode : Node<ListOperatorEntity, ListOperatorPredicateNodeCustomViewModel>, IDisposable
{ {
private readonly object _scriptLock = new(); 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") 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>(); InputList = CreateInputPin<IList>();
Output = CreateOutputPin<bool>(); Output = CreateOutputPin<bool>();
@ -31,21 +32,8 @@ public class ListOperatorPredicateNode : Node<ListOperatorEntity, ListOperatorPr
lock (_scriptLock) lock (_scriptLock)
{ {
Script = Storage?.Script != null 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", 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 NodeScript<bool>("Is match", "Determines whether the current list item is a match", script.Context, new List<DefaultNode> {_startNode});
// 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();
} }
} }
@ -101,6 +89,7 @@ public class ListOperatorPredicateNode : Node<ListOperatorEntity, ListOperatorPr
lock (_scriptLock) lock (_scriptLock)
{ {
UpdateStartNode(); UpdateStartNode();
Script?.LoadConnections();
} }
} }

View File

@ -1,5 +1,7 @@
using System.Reactive; using System.Reactive;
using System.Reactive.Disposables;
using Artemis.Core; using Artemis.Core;
using Artemis.Core.Events;
using Artemis.UI.Shared.Services; using Artemis.UI.Shared.Services;
using Artemis.UI.Shared.Services.NodeEditor; using Artemis.UI.Shared.Services.NodeEditor;
using Artemis.UI.Shared.VisualScripting; using Artemis.UI.Shared.VisualScripting;
@ -12,17 +14,37 @@ public class ListOperatorPredicateNodeCustomViewModel : CustomNodeViewModel
private readonly ListOperatorPredicateNode _node; private readonly ListOperatorPredicateNode _node;
private readonly IWindowService _windowService; private readonly IWindowService _windowService;
private ListOperator _operator; private ListOperator _operator;
private bool _canOpenEditor;
public ListOperatorPredicateNodeCustomViewModel(ListOperatorPredicateNode node, INodeScript script, IWindowService windowService) : base(node, script) public ListOperatorPredicateNodeCustomViewModel(ListOperatorPredicateNode node, INodeScript script, IWindowService windowService) : base(node, script)
{ {
_node = node; _node = node;
_windowService = windowService; _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; } public ReactiveCommand<Unit, Unit> OpenEditor { get; }
private bool CanOpenEditor
{
get => _canOpenEditor;
set => this.RaiseAndSetIfChanged(ref _canOpenEditor, value);
}
public ListOperator Operator public ListOperator Operator
{ {
get => _operator; get => _operator;
@ -40,4 +62,14 @@ public class ListOperatorPredicateNodeCustomViewModel : CustomNodeViewModel
_node.Storage ??= new ListOperatorEntity(); _node.Storage ??= new ListOperatorEntity();
_node.Storage.Script = _node.Script.Entity; _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;
}
} }