mirror of
https://github.com/Artemis-RGB/Artemis
synced 2025-12-13 05:48:35 +00:00
Nodes - Fixed string format node not outputting numerics
Data model event node - Renamed to Data Model-Event Value Cycle Data model event node - Added new data model event node that outputs the latest event data Event conditions - Performance improvements
This commit is contained in:
parent
3efb97eedd
commit
d2e0607622
@ -146,6 +146,8 @@ public class EventCondition : CorePropertyChanged, INodeScriptCondition
|
||||
valueChangedNode.UpdateOutputPins(EventPath);
|
||||
}
|
||||
|
||||
if (!Script.Nodes.Contains(_startNode))
|
||||
Script.AddNode(_startNode);
|
||||
Script.Save();
|
||||
}
|
||||
|
||||
|
||||
@ -48,6 +48,14 @@ public readonly struct Numeric : IComparable<Numeric>, IConvertible
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="Numeric" /> from a <see cref="long" />
|
||||
/// </summary>
|
||||
public Numeric(long value)
|
||||
{
|
||||
_value = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="Numeric" /> from an <see cref="object" />
|
||||
@ -60,6 +68,7 @@ public readonly struct Numeric : IComparable<Numeric>, IConvertible
|
||||
int value => value,
|
||||
double value => (float) value,
|
||||
byte value => value,
|
||||
long value => value,
|
||||
Numeric value => value,
|
||||
_ => ParseFloatOrDefault(pathValue?.ToString())
|
||||
};
|
||||
@ -150,6 +159,11 @@ public readonly struct Numeric : IComparable<Numeric>, IConvertible
|
||||
{
|
||||
return (byte) Math.Clamp(p._value, 0, 255);
|
||||
}
|
||||
|
||||
public static implicit operator long(Numeric p)
|
||||
{
|
||||
return (long) p._value;
|
||||
}
|
||||
|
||||
public static bool operator >(Numeric a, Numeric b)
|
||||
{
|
||||
@ -264,6 +278,7 @@ public readonly struct Numeric : IComparable<Numeric>, IConvertible
|
||||
type == typeof(float) ||
|
||||
type == typeof(double) ||
|
||||
type == typeof(int) ||
|
||||
type == typeof(long) ||
|
||||
type == typeof(byte);
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Core.VisualScripting.Internal;
|
||||
@ -11,13 +12,12 @@ namespace Artemis.Core.Internal;
|
||||
internal class EventConditionEventStartNode : DefaultNode, IEventConditionNode
|
||||
{
|
||||
internal static readonly Guid NodeId = new("278735FE-69E9-4A73-A6B8-59E83EE19305");
|
||||
private readonly List<OutputPin> _pinBucket = new();
|
||||
private readonly Dictionary<PropertyInfo, OutputPin> _propertyPins;
|
||||
private readonly Dictionary<Func<DataModelEventArgs, object>, OutputPin> _propertyPins;
|
||||
private IDataModelEvent? _dataModelEvent;
|
||||
|
||||
public EventConditionEventStartNode() : base(NodeId, "Event Arguments", "Contains the event arguments that triggered the evaluation")
|
||||
{
|
||||
_propertyPins = new Dictionary<PropertyInfo, OutputPin>();
|
||||
_propertyPins = new Dictionary<Func<DataModelEventArgs, object>, OutputPin>();
|
||||
}
|
||||
|
||||
public void CreatePins(IDataModelEvent? dataModelEvent)
|
||||
@ -33,40 +33,22 @@ internal class EventConditionEventStartNode : DefaultNode, IEventConditionNode
|
||||
if (dataModelEvent == null)
|
||||
return;
|
||||
|
||||
foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType
|
||||
.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof(DataModelIgnoreAttribute))))
|
||||
_propertyPins.Add(propertyInfo, CreateOrAddOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize()));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or adds an input pin to the node using a bucket.
|
||||
/// The bucket might grow a bit over time as the user edits the node but pins won't get lost, enabling undo/redo in the
|
||||
/// editor.
|
||||
/// </summary>
|
||||
private OutputPin CreateOrAddOutputPin(Type valueType, string displayName)
|
||||
{
|
||||
// Grab the first pin from the bucket that isn't on the node yet
|
||||
OutputPin? pin = _pinBucket.FirstOrDefault(p => !Pins.Contains(p));
|
||||
|
||||
if (Numeric.IsTypeCompatible(valueType))
|
||||
valueType = typeof(Numeric);
|
||||
|
||||
// If there is none, create a new one and add it to the bucket
|
||||
if (pin == null)
|
||||
{
|
||||
pin = CreateOutputPin(valueType, displayName);
|
||||
_pinBucket.Add(pin);
|
||||
}
|
||||
// If there was a pin in the bucket, update it's type and display name and reuse it
|
||||
else
|
||||
{
|
||||
pin.ChangeType(valueType);
|
||||
pin.Name = displayName;
|
||||
AddPin(pin);
|
||||
}
|
||||
// Expect an IDataModelEvent
|
||||
ParameterExpression eventParameter = Expression.Parameter(typeof(DataModelEventArgs), "event");
|
||||
// Cast it to the actual event type
|
||||
UnaryExpression eventCast = Expression.Convert(eventParameter, propertyInfo.DeclaringType!);
|
||||
// Access the property
|
||||
MemberExpression accessor = Expression.Property(eventCast, propertyInfo);
|
||||
// Cast the property to an object (sadly boxing)
|
||||
UnaryExpression objectCast = Expression.Convert(accessor, typeof(object));
|
||||
// Compile the resulting expression
|
||||
Func<DataModelEventArgs, object> expression = Expression.Lambda<Func<DataModelEventArgs, object>>(objectCast, eventParameter).Compile();
|
||||
|
||||
return pin;
|
||||
_propertyPins.Add(expression, CreateOrAddOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize()));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Evaluate()
|
||||
@ -74,13 +56,12 @@ internal class EventConditionEventStartNode : DefaultNode, IEventConditionNode
|
||||
if (_dataModelEvent?.LastEventArgumentsUntyped == null)
|
||||
return;
|
||||
|
||||
foreach ((PropertyInfo propertyInfo, OutputPin outputPin) in _propertyPins)
|
||||
foreach ((Func<DataModelEventArgs, object> propertyAccessor, OutputPin outputPin) in _propertyPins)
|
||||
{
|
||||
if (outputPin.ConnectedTo.Any())
|
||||
{
|
||||
object value = propertyInfo.GetValue(_dataModelEvent.LastEventArgumentsUntyped) ?? outputPin.Type.GetDefault()!;
|
||||
outputPin.Value = outputPin.IsNumeric ? new Numeric(value) : value;
|
||||
}
|
||||
if (!outputPin.ConnectedTo.Any())
|
||||
continue;
|
||||
object value = _dataModelEvent.LastEventArgumentsUntyped != null ? propertyAccessor(_dataModelEvent.LastEventArgumentsUntyped) : outputPin.Type.GetDefault()!;
|
||||
outputPin.Value = outputPin.IsNumeric ? new Numeric(value) : value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Ninject;
|
||||
using Ninject.Parameters;
|
||||
|
||||
@ -15,6 +16,9 @@ public abstract class Node : CorePropertyChanged, INode
|
||||
public event EventHandler? Resetting;
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
private readonly List<OutputPin> _outputPinBucket = new();
|
||||
private readonly List<InputPin> _inputPinBucket = new();
|
||||
|
||||
private Guid _id;
|
||||
|
||||
@ -160,6 +164,66 @@ public abstract class Node : CorePropertyChanged, INode
|
||||
OnPropertyChanged(nameof(Pins));
|
||||
return pin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or adds an output pin to the node using a bucket.
|
||||
/// The bucket might grow a bit over time as the user edits the node but pins won't get lost, enabling undo/redo in the
|
||||
/// editor.
|
||||
/// </summary>
|
||||
protected OutputPin CreateOrAddOutputPin(Type valueType, string displayName)
|
||||
{
|
||||
// Grab the first pin from the bucket that isn't on the node yet
|
||||
OutputPin? pin = _outputPinBucket.FirstOrDefault(p => !Pins.Contains(p));
|
||||
|
||||
if (Numeric.IsTypeCompatible(valueType))
|
||||
valueType = typeof(Numeric);
|
||||
|
||||
// If there is none, create a new one and add it to the bucket
|
||||
if (pin == null)
|
||||
{
|
||||
pin = CreateOutputPin(valueType, displayName);
|
||||
_outputPinBucket.Add(pin);
|
||||
}
|
||||
// If there was a pin in the bucket, update it's type and display name and reuse it
|
||||
else
|
||||
{
|
||||
pin.ChangeType(valueType);
|
||||
pin.Name = displayName;
|
||||
AddPin(pin);
|
||||
}
|
||||
|
||||
return pin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates or adds an input pin to the node using a bucket.
|
||||
/// The bucket might grow a bit over time as the user edits the node but pins won't get lost, enabling undo/redo in the
|
||||
/// editor.
|
||||
/// </summary>
|
||||
protected InputPin CreateOrAddInputPin(Type valueType, string displayName)
|
||||
{
|
||||
// Grab the first pin from the bucket that isn't on the node yet
|
||||
InputPin? pin = _inputPinBucket.FirstOrDefault(p => !Pins.Contains(p));
|
||||
|
||||
if (Numeric.IsTypeCompatible(valueType))
|
||||
valueType = typeof(Numeric);
|
||||
|
||||
// If there is none, create a new one and add it to the bucket
|
||||
if (pin == null)
|
||||
{
|
||||
pin = CreateInputPin(valueType, displayName);
|
||||
_inputPinBucket.Add(pin);
|
||||
}
|
||||
// If there was a pin in the bucket, update it's type and display name and reuse it
|
||||
else
|
||||
{
|
||||
pin.ChangeType(valueType);
|
||||
pin.Name = displayName;
|
||||
AddPin(pin);
|
||||
}
|
||||
|
||||
return pin;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the provided <paramref name="pin" /> from the node and it's <see cref="Pins" /> collection
|
||||
|
||||
@ -14,7 +14,7 @@ namespace Artemis.UI.Services;
|
||||
|
||||
public class UpdateService : IUpdateService
|
||||
{
|
||||
private const double UPDATE_CHECK_INTERVAL = 10000; // once per hour
|
||||
private const double UPDATE_CHECK_INTERVAL = 3_600_000; // once per hour
|
||||
|
||||
private readonly PluginSetting<bool> _autoUpdate;
|
||||
private readonly PluginSetting<bool> _checkForUpdates;
|
||||
|
||||
@ -33,6 +33,10 @@
|
||||
<Compile Update="Nodes\DataModel\Screens\DataModelNodeCustomView.axaml.cs">
|
||||
<DependentUpon>DataModelNodeCustomView.axaml</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Update="Nodes\DataModel\Screens\DataModelEventNodeCustomView.axaml.cs">
|
||||
<DependentUpon>DataModelEventNodeCustomView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -0,0 +1,167 @@
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.VisualScripting.Nodes.DataModel.Screens;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes.DataModel;
|
||||
|
||||
[Node("Data Model-Event Value Cycle", "Cycles through provided values each time the select event fires.", "Data Model", OutputType = typeof(object))]
|
||||
public class DataModelEventCycleNode : Node<DataModelPathEntity, DataModelEventCycleNodeCustomViewModel>, IDisposable
|
||||
{
|
||||
private int _currentIndex;
|
||||
private Type _currentType;
|
||||
private DataModelPath? _dataModelPath;
|
||||
private object? _lastPathValue;
|
||||
private DateTime _lastTrigger;
|
||||
private bool _updating;
|
||||
|
||||
public DataModelEventCycleNode() : base("Data Model-Event Value Cycle", "Cycles through provided values each time the select event fires.")
|
||||
{
|
||||
_currentType = typeof(object);
|
||||
|
||||
CycleValues = CreateInputPinCollection(typeof(object), "", 0);
|
||||
Output = CreateOutputPin(typeof(object));
|
||||
|
||||
CycleValues.PinAdded += CycleValuesOnPinAdded;
|
||||
CycleValues.PinRemoved += CycleValuesOnPinRemoved;
|
||||
CycleValues.Add(CycleValues.CreatePin());
|
||||
|
||||
// Monitor storage for changes
|
||||
StorageModified += (_, _) => UpdateDataModelPath();
|
||||
}
|
||||
|
||||
public INodeScript? Script { get; set; }
|
||||
|
||||
public InputPinCollection CycleValues { get; }
|
||||
public OutputPin Output { get; }
|
||||
|
||||
public override void Initialize(INodeScript script)
|
||||
{
|
||||
Script = script;
|
||||
|
||||
if (Storage == null)
|
||||
return;
|
||||
|
||||
UpdateDataModelPath();
|
||||
}
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
object? pathValue = _dataModelPath?.GetValue();
|
||||
bool hasTriggered = pathValue is IDataModelEvent dataModelEvent
|
||||
? EvaluateEvent(dataModelEvent)
|
||||
: EvaluateValue(pathValue);
|
||||
|
||||
if (hasTriggered)
|
||||
{
|
||||
_currentIndex++;
|
||||
|
||||
if (_currentIndex >= CycleValues.Count())
|
||||
_currentIndex = 0;
|
||||
}
|
||||
|
||||
object? outputValue = CycleValues.ElementAt(_currentIndex).PinValue;
|
||||
if (Output.Type.IsInstanceOfType(outputValue))
|
||||
Output.Value = outputValue;
|
||||
else if (Output.Type.IsValueType)
|
||||
Output.Value = Output.Type.GetDefault()!;
|
||||
}
|
||||
|
||||
private bool EvaluateEvent(IDataModelEvent dataModelEvent)
|
||||
{
|
||||
if (dataModelEvent.LastTrigger <= _lastTrigger)
|
||||
return false;
|
||||
|
||||
_lastTrigger = dataModelEvent.LastTrigger;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EvaluateValue(object? pathValue)
|
||||
{
|
||||
if (Equals(pathValue, _lastPathValue))
|
||||
return false;
|
||||
|
||||
_lastPathValue = pathValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CycleValuesOnPinAdded(object? sender, SingleValueEventArgs<IPin> e)
|
||||
{
|
||||
e.Value.PinConnected += OnPinConnected;
|
||||
e.Value.PinDisconnected += OnPinDisconnected;
|
||||
}
|
||||
|
||||
private void CycleValuesOnPinRemoved(object? sender, SingleValueEventArgs<IPin> e)
|
||||
{
|
||||
e.Value.PinConnected -= OnPinConnected;
|
||||
e.Value.PinDisconnected -= OnPinDisconnected;
|
||||
}
|
||||
|
||||
private void OnPinDisconnected(object? sender, SingleValueEventArgs<IPin> e)
|
||||
{
|
||||
ProcessPinDisconnected();
|
||||
}
|
||||
|
||||
private void OnPinConnected(object? sender, SingleValueEventArgs<IPin> e)
|
||||
{
|
||||
ProcessPinConnected(e.Value);
|
||||
}
|
||||
|
||||
private void ProcessPinConnected(IPin source)
|
||||
{
|
||||
if (_updating)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_updating = true;
|
||||
|
||||
// No need to change anything if the types haven't changed
|
||||
if (_currentType != source.Type)
|
||||
ChangeCurrentType(source.Type);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateDataModelPath()
|
||||
{
|
||||
DataModelPath? old = _dataModelPath;
|
||||
_dataModelPath = Storage != null ? new DataModelPath(Storage) : null;
|
||||
old?.Dispose();
|
||||
}
|
||||
|
||||
private void ChangeCurrentType(Type type)
|
||||
{
|
||||
CycleValues.ChangeType(type);
|
||||
Output.ChangeType(type);
|
||||
|
||||
_currentType = type;
|
||||
}
|
||||
|
||||
private void ProcessPinDisconnected()
|
||||
{
|
||||
if (_updating)
|
||||
return;
|
||||
try
|
||||
{
|
||||
// If there's still a connected pin, stick to the current type
|
||||
if (CycleValues.Any(v => v.ConnectedTo.Any()))
|
||||
return;
|
||||
|
||||
ChangeCurrentType(typeof(object));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose()
|
||||
{
|
||||
_dataModelPath?.Dispose();
|
||||
}
|
||||
}
|
||||
@ -1,39 +1,35 @@
|
||||
using Artemis.Core;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Events;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.VisualScripting.Nodes.DataModel.Screens;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes.DataModel;
|
||||
|
||||
[Node("Data Model-Event", "Responds to a data model event trigger", "Data Model", OutputType = typeof(object))]
|
||||
[Node("Data Model-Event", "Outputs the latest values of a data model event.", "Data Model", OutputType = typeof(object))]
|
||||
public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCustomViewModel>, IDisposable
|
||||
{
|
||||
private int _currentIndex;
|
||||
private Type _currentType;
|
||||
private readonly Dictionary<Func<DataModelEventArgs, object>, OutputPin> _propertyPins;
|
||||
private DataModelPath? _dataModelPath;
|
||||
private object? _lastPathValue;
|
||||
private DateTime _lastTrigger;
|
||||
private bool _updating;
|
||||
|
||||
public DataModelEventNode() : base("Data Model-Event", "Responds to a data model event trigger")
|
||||
private IDataModelEvent? _dataModelEvent;
|
||||
|
||||
public DataModelEventNode() : base("Data Model-Event", "Outputs the latest values of a data model event.")
|
||||
{
|
||||
_currentType = typeof(object);
|
||||
|
||||
CycleValues = CreateInputPinCollection(typeof(object), "", 0);
|
||||
Output = CreateOutputPin(typeof(object));
|
||||
|
||||
CycleValues.PinAdded += CycleValuesOnPinAdded;
|
||||
CycleValues.PinRemoved += CycleValuesOnPinRemoved;
|
||||
CycleValues.Add(CycleValues.CreatePin());
|
||||
_propertyPins = new Dictionary<Func<DataModelEventArgs, object>, OutputPin>();
|
||||
|
||||
TimeSinceLastTrigger = CreateOutputPin<Numeric>("Time since trigger");
|
||||
TriggerCount = CreateOutputPin<Numeric>("Trigger count");
|
||||
|
||||
// Monitor storage for changes
|
||||
StorageModified += (_, _) => UpdateDataModelPath();
|
||||
}
|
||||
|
||||
public INodeScript? Script { get; set; }
|
||||
|
||||
public InputPinCollection CycleValues { get; }
|
||||
public OutputPin Output { get; }
|
||||
public OutputPin<Numeric> TimeSinceLastTrigger { get; }
|
||||
public OutputPin<Numeric> TriggerCount { get; }
|
||||
|
||||
public override void Initialize(INodeScript script)
|
||||
{
|
||||
@ -45,84 +41,53 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
|
||||
UpdateDataModelPath();
|
||||
}
|
||||
|
||||
private void CreatePins(IDataModelEvent? dataModelEvent)
|
||||
{
|
||||
if (_dataModelEvent == dataModelEvent)
|
||||
return;
|
||||
|
||||
List<IPin> pins = Pins.Skip(2).ToList();
|
||||
while (pins.Any())
|
||||
RemovePin((Pin) pins.First());
|
||||
_propertyPins.Clear();
|
||||
|
||||
_dataModelEvent = dataModelEvent;
|
||||
if (dataModelEvent == null)
|
||||
return;
|
||||
|
||||
foreach (PropertyInfo propertyInfo in dataModelEvent.ArgumentsType.GetProperties(BindingFlags.Instance | BindingFlags.Public)
|
||||
.Where(p => p.CustomAttributes.All(a => a.AttributeType != typeof(DataModelIgnoreAttribute))))
|
||||
{
|
||||
// Expect an IDataModelEvent
|
||||
ParameterExpression eventParameter = Expression.Parameter(typeof(DataModelEventArgs), "event");
|
||||
// Cast it to the actual event type
|
||||
UnaryExpression eventCast = Expression.Convert(eventParameter, propertyInfo.DeclaringType!);
|
||||
// Access the property
|
||||
MemberExpression accessor = Expression.Property(eventCast, propertyInfo);
|
||||
// Cast the property to an object (sadly boxing)
|
||||
UnaryExpression objectCast = Expression.Convert(accessor, typeof(object));
|
||||
// Compile the resulting expression
|
||||
Func<DataModelEventArgs, object> expression = Expression.Lambda<Func<DataModelEventArgs, object>>(objectCast, eventParameter).Compile();
|
||||
|
||||
_propertyPins.Add(expression, CreateOrAddOutputPin(propertyInfo.PropertyType, propertyInfo.Name.Humanize()));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
object? pathValue = _dataModelPath?.GetValue();
|
||||
bool hasTriggered = pathValue is IDataModelEvent dataModelEvent
|
||||
? EvaluateEvent(dataModelEvent)
|
||||
: EvaluateValue(pathValue);
|
||||
|
||||
if (hasTriggered)
|
||||
{
|
||||
_currentIndex++;
|
||||
|
||||
if (_currentIndex >= CycleValues.Count())
|
||||
_currentIndex = 0;
|
||||
}
|
||||
|
||||
object? outputValue = CycleValues.ElementAt(_currentIndex).PinValue;
|
||||
if (Output.Type.IsInstanceOfType(outputValue))
|
||||
Output.Value = outputValue;
|
||||
else if (Output.Type.IsValueType)
|
||||
Output.Value = Output.Type.GetDefault()!;
|
||||
}
|
||||
|
||||
private bool EvaluateEvent(IDataModelEvent dataModelEvent)
|
||||
{
|
||||
if (dataModelEvent.LastTrigger <= _lastTrigger)
|
||||
return false;
|
||||
|
||||
_lastTrigger = dataModelEvent.LastTrigger;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool EvaluateValue(object? pathValue)
|
||||
{
|
||||
if (Equals(pathValue, _lastPathValue))
|
||||
return false;
|
||||
|
||||
_lastPathValue = pathValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CycleValuesOnPinAdded(object? sender, SingleValueEventArgs<IPin> e)
|
||||
{
|
||||
e.Value.PinConnected += OnPinConnected;
|
||||
e.Value.PinDisconnected += OnPinDisconnected;
|
||||
}
|
||||
|
||||
private void CycleValuesOnPinRemoved(object? sender, SingleValueEventArgs<IPin> e)
|
||||
{
|
||||
e.Value.PinConnected -= OnPinConnected;
|
||||
e.Value.PinDisconnected -= OnPinDisconnected;
|
||||
}
|
||||
|
||||
private void OnPinDisconnected(object? sender, SingleValueEventArgs<IPin> e)
|
||||
{
|
||||
ProcessPinDisconnected();
|
||||
}
|
||||
|
||||
private void OnPinConnected(object? sender, SingleValueEventArgs<IPin> e)
|
||||
{
|
||||
ProcessPinConnected(e.Value);
|
||||
}
|
||||
|
||||
private void ProcessPinConnected(IPin source)
|
||||
{
|
||||
if (_updating)
|
||||
if (pathValue is not IDataModelEvent dataModelEvent)
|
||||
return;
|
||||
|
||||
try
|
||||
TimeSinceLastTrigger.Value = new Numeric(dataModelEvent.TimeSinceLastTrigger.TotalMilliseconds);
|
||||
TriggerCount.Value = new Numeric(dataModelEvent.TriggerCount);
|
||||
|
||||
foreach ((Func<DataModelEventArgs, object> propertyAccessor, OutputPin outputPin) in _propertyPins)
|
||||
{
|
||||
_updating = true;
|
||||
|
||||
// No need to change anything if the types haven't changed
|
||||
if (_currentType != source.Type)
|
||||
ChangeCurrentType(source.Type);
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
if (!outputPin.ConnectedTo.Any())
|
||||
continue;
|
||||
object value = dataModelEvent.LastEventArgumentsUntyped != null ? propertyAccessor(dataModelEvent.LastEventArgumentsUntyped) : outputPin.Type.GetDefault()!;
|
||||
outputPin.Value = outputPin.IsNumeric ? new Numeric(value) : value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,32 +96,10 @@ public class DataModelEventNode : Node<DataModelPathEntity, DataModelEventNodeCu
|
||||
DataModelPath? old = _dataModelPath;
|
||||
_dataModelPath = Storage != null ? new DataModelPath(Storage) : null;
|
||||
old?.Dispose();
|
||||
}
|
||||
|
||||
private void ChangeCurrentType(Type type)
|
||||
{
|
||||
CycleValues.ChangeType(type);
|
||||
Output.ChangeType(type);
|
||||
|
||||
_currentType = type;
|
||||
}
|
||||
|
||||
private void ProcessPinDisconnected()
|
||||
{
|
||||
if (_updating)
|
||||
return;
|
||||
try
|
||||
{
|
||||
// If there's still a connected pin, stick to the current type
|
||||
if (CycleValues.Any(v => v.ConnectedTo.Any()))
|
||||
return;
|
||||
|
||||
ChangeCurrentType(typeof(object));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
}
|
||||
object? pathValue = _dataModelPath?.GetValue();
|
||||
if (pathValue is IDataModelEvent dataModelEvent)
|
||||
CreatePins(dataModelEvent);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@ -0,0 +1,18 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:screens="clr-namespace:Artemis.VisualScripting.Nodes.DataModel.Screens"
|
||||
xmlns:dataModelPicker="clr-namespace:Artemis.UI.Shared.DataModelPicker;assembly=Artemis.UI.Shared"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Artemis.VisualScripting.Nodes.DataModel.Screens.DataModelEventCycleNodeCustomView"
|
||||
x:DataType="screens:DataModelEventCycleNodeCustomViewModel">
|
||||
<dataModelPicker:DataModelPickerButton Classes="condensed"
|
||||
DataModelPath="{CompiledBinding DataModelPath}"
|
||||
Modules="{CompiledBinding Modules}"
|
||||
ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}"
|
||||
ShowFullPath="{CompiledBinding ShowFullPaths.Value}"
|
||||
IsEventPicker="True"
|
||||
VerticalAlignment="Top"
|
||||
MaxWidth="300"/>
|
||||
</UserControl>
|
||||
@ -0,0 +1,17 @@
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes.DataModel.Screens;
|
||||
|
||||
public class DataModelEventCycleNodeCustomView : ReactiveUserControl<DataModelEventCycleNodeCustomViewModel>
|
||||
{
|
||||
public DataModelEventCycleNodeCustomView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,104 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reactive.Disposables;
|
||||
using Artemis.Core;
|
||||
using Artemis.Core.Modules;
|
||||
using Artemis.Core.Services;
|
||||
using Artemis.Storage.Entities.Profile;
|
||||
using Artemis.UI.Shared.Services.NodeEditor;
|
||||
using Artemis.UI.Shared.Services.NodeEditor.Commands;
|
||||
using Artemis.UI.Shared.VisualScripting;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Artemis.VisualScripting.Nodes.DataModel.Screens;
|
||||
|
||||
public class DataModelEventCycleNodeCustomViewModel : CustomNodeViewModel
|
||||
{
|
||||
private readonly DataModelEventCycleNode _cycleNode;
|
||||
private readonly INodeEditorService _nodeEditorService;
|
||||
private DataModelPath? _dataModelPath;
|
||||
private ObservableCollection<Module>? _modules;
|
||||
private bool _updating;
|
||||
|
||||
public DataModelEventCycleNodeCustomViewModel(DataModelEventCycleNode cycleNode, INodeScript script, ISettingsService settingsService, INodeEditorService nodeEditorService) : base(cycleNode, script)
|
||||
{
|
||||
_cycleNode = cycleNode;
|
||||
_nodeEditorService = nodeEditorService;
|
||||
|
||||
ShowFullPaths = settingsService.GetSetting("ProfileEditor.ShowFullPaths", true);
|
||||
ShowDataModelValues = settingsService.GetSetting("ProfileEditor.ShowDataModelValues", false);
|
||||
Modules = new ObservableCollection<Module>();
|
||||
|
||||
this.WhenActivated(d =>
|
||||
{
|
||||
// Set up extra modules
|
||||
if (_cycleNode.Script?.Context is Profile scriptProfile && scriptProfile.Configuration.Module != null)
|
||||
Modules = new ObservableCollection<Module> {scriptProfile.Configuration.Module};
|
||||
else if (_cycleNode.Script?.Context is ProfileConfiguration profileConfiguration && profileConfiguration.Module != null)
|
||||
Modules = new ObservableCollection<Module> {profileConfiguration.Module};
|
||||
|
||||
// Subscribe to node changes
|
||||
_cycleNode.WhenAnyValue(n => n.Storage).Subscribe(UpdateDataModelPath).DisposeWith(d);
|
||||
this.WhenAnyValue(vm => vm.DataModelPath).Subscribe(ApplyDataModelPath).DisposeWith(d);
|
||||
|
||||
Disposable.Create(() =>
|
||||
{
|
||||
_dataModelPath?.Dispose();
|
||||
_dataModelPath = null;
|
||||
}).DisposeWith(d);
|
||||
});
|
||||
}
|
||||
|
||||
public PluginSetting<bool> ShowFullPaths { get; }
|
||||
public PluginSetting<bool> ShowDataModelValues { get; }
|
||||
|
||||
public ObservableCollection<Module>? Modules
|
||||
{
|
||||
get => _modules;
|
||||
set => this.RaiseAndSetIfChanged(ref _modules, value);
|
||||
}
|
||||
|
||||
public DataModelPath? DataModelPath
|
||||
{
|
||||
get => _dataModelPath;
|
||||
set => this.RaiseAndSetIfChanged(ref _dataModelPath, value);
|
||||
}
|
||||
|
||||
private void UpdateDataModelPath(DataModelPathEntity? entity)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_updating)
|
||||
return;
|
||||
|
||||
_updating = true;
|
||||
|
||||
DataModelPath? old = DataModelPath;
|
||||
DataModelPath = entity != null ? new DataModelPath(entity) : null;
|
||||
old?.Dispose();
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyDataModelPath(DataModelPath? path)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_updating)
|
||||
return;
|
||||
if (path?.Path == _cycleNode.Storage?.Path)
|
||||
return;
|
||||
|
||||
_updating = true;
|
||||
|
||||
path?.Save();
|
||||
_nodeEditorService.ExecuteCommand(Script, new UpdateStorage<DataModelPathEntity>(_cycleNode, path?.Entity, "event"));
|
||||
}
|
||||
finally
|
||||
{
|
||||
_updating = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -12,6 +12,7 @@
|
||||
Modules="{CompiledBinding Modules}"
|
||||
ShowDataModelValues="{CompiledBinding ShowDataModelValues.Value}"
|
||||
ShowFullPath="{CompiledBinding ShowFullPaths.Value}"
|
||||
FilterTypes="{CompiledBinding FilterTypes}"
|
||||
IsEventPicker="True"
|
||||
VerticalAlignment="Top"
|
||||
MaxWidth="300"/>
|
||||
|
||||
@ -63,6 +63,8 @@ public class DataModelEventNodeCustomViewModel : CustomNodeViewModel
|
||||
set => this.RaiseAndSetIfChanged(ref _dataModelPath, value);
|
||||
}
|
||||
|
||||
public List<Type> FilterTypes => new() {typeof(IDataModelEvent)};
|
||||
|
||||
private void UpdateDataModelPath(DataModelPathEntity? entity)
|
||||
{
|
||||
try
|
||||
|
||||
@ -32,6 +32,14 @@ public class DisplayValueNodeCustomViewModel : CustomNodeViewModel
|
||||
|
||||
private void Update(object? sender, EventArgs e)
|
||||
{
|
||||
CurrentValue = _node.Input.Value;
|
||||
try
|
||||
{
|
||||
CurrentValue = _node.Input.Value;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Don't crash the timer on exceptions and display the messages as a bit of a nice to have
|
||||
CurrentValue = ex.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,9 @@ public class StringFormatNode : Node
|
||||
|
||||
public override void Evaluate()
|
||||
{
|
||||
Output.Value = string.Format(Format.Value ?? string.Empty, Values.Values.ToArray());
|
||||
// Convert numerics to floats beforehand to allow string.Format to format them
|
||||
object[] values = Values.Values.Select(v => v is Numeric n ? (float) n : v).ToArray();
|
||||
Output.Value = string.Format(Format.Value ?? string.Empty, values);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user