mirror of
https://github.com/Artemis-RGB/Artemis
synced 2026-11-12 03:48:56 +00:00
Nodes - Added object output pins and a new list predicate node
This commit is contained in:
1 parent
9106359515
commit
b19854ee47
21 files changed
+492
-143
No files matched your search
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Artemis.Core.Internal;
|
||||
using Artemis.Core.VisualScripting.Internal;
|
||||
using Artemis.Storage.Entities.Profile.Abstract;
|
||||
using Artemis.Storage.Entities.Profile.Conditions;
|
||||
|
||||
|
||||
+2
-11
@@ -1,18 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Artemis.Core.Internal;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a kind of node that cannot be deleted inside a <see cref="INode" />.
|
||||
/// </summary>
|
||||
public interface IDefaultNode : INode
|
||||
{
|
||||
}
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a kind of node that cannot be deleted inside a <see cref="NodeScript" />.
|
||||
/// </summary>
|
||||
public abstract class DefaultNode : Node, IDefaultNode
|
||||
public abstract class DefaultNode : Node
|
||||
{
|
||||
#region Constructors
|
||||
|
||||
@@ -20,8 +13,6 @@ public abstract class DefaultNode : Node, IDefaultNode
|
||||
protected DefaultNode(Guid id, string name, string description = "") : base(name, description)
|
||||
{
|
||||
Id = id;
|
||||
Name = name;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1,23 +1,16 @@
|
||||
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;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.Core.Internal;
|
||||
|
||||
internal class EventConditionEventStartNode : DefaultNode, IEventConditionNode
|
||||
{
|
||||
internal static readonly Guid NodeId = new("278735FE-69E9-4A73-A6B8-59E83EE19305");
|
||||
private readonly Dictionary<Func<DataModelEventArgs, object>, OutputPin> _propertyPins;
|
||||
private readonly ObjectOutputPins _objectOutputPins;
|
||||
private IDataModelEvent? _dataModelEvent;
|
||||
|
||||
public EventConditionEventStartNode() : base(NodeId, "Event Arguments", "Contains the event arguments that triggered the evaluation")
|
||||
{
|
||||
_propertyPins = new Dictionary<Func<DataModelEventArgs, object>, OutputPin>();
|
||||
_objectOutputPins = new ObjectOutputPins(this);
|
||||
}
|
||||
|
||||
public void CreatePins(IDataModelEvent? dataModelEvent)
|
||||
@@ -25,30 +18,8 @@ internal class EventConditionEventStartNode : DefaultNode, IEventConditionNode
|
||||
if (_dataModelEvent == dataModelEvent)
|
||||
return;
|
||||
|
||||
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()));
|
||||
}
|
||||
_objectOutputPins.ChangeType(dataModelEvent?.ArgumentsType);
|
||||
}
|
||||
|
||||
public override void Evaluate()
|
||||
@@ -56,12 +27,6 @@ internal class EventConditionEventStartNode : DefaultNode, IEventConditionNode
|
||||
if (_dataModelEvent?.LastEventArgumentsUntyped == null)
|
||||
return;
|
||||
|
||||
foreach ((Func<DataModelEventArgs, object> propertyAccessor, OutputPin outputPin) in _propertyPins)
|
||||
{
|
||||
if (!outputPin.ConnectedTo.Any())
|
||||
continue;
|
||||
object value = _dataModelEvent.LastEventArgumentsUntyped != null ? propertyAccessor(_dataModelEvent.LastEventArgumentsUntyped) : outputPin.Type.GetDefault()!;
|
||||
outputPin.Value = outputPin.IsNumeric ? new Numeric(value) : value;
|
||||
}
|
||||
_objectOutputPins.SetCurrentValue(_dataModelEvent.LastEventArgumentsUntyped);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Artemis.Core.VisualScripting.Internal;
|
||||
|
||||
namespace Artemis.Core.Internal;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Artemis.Core.VisualScripting.Internal;
|
||||
namespace Artemis.Core.Internal;
|
||||
|
||||
internal interface IEventConditionNode : INode
|
||||
{
|
||||
|
||||
@@ -132,7 +132,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// <param name="name">The name of the pin</param>
|
||||
/// <typeparam name="T">The type of value the pin will hold</typeparam>
|
||||
/// <returns>The newly created pin</returns>
|
||||
protected InputPin<T> CreateInputPin<T>(string name = "")
|
||||
public InputPin<T> CreateInputPin<T>(string name = "")
|
||||
{
|
||||
InputPin<T> pin = new(this, name);
|
||||
_pins.Add(pin);
|
||||
@@ -146,7 +146,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// <param name="type">The type of value the pin will hold</param>
|
||||
/// <param name="name">The name of the pin</param>
|
||||
/// <returns>The newly created pin</returns>
|
||||
protected InputPin CreateInputPin(Type type, string name = "")
|
||||
public InputPin CreateInputPin(Type type, string name = "")
|
||||
{
|
||||
InputPin pin = new(this, type, name);
|
||||
_pins.Add(pin);
|
||||
@@ -160,7 +160,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// <param name="name">The name of the pin</param>
|
||||
/// <typeparam name="T">The type of value the pin will hold</typeparam>
|
||||
/// <returns>The newly created pin</returns>
|
||||
protected OutputPin<T> CreateOutputPin<T>(string name = "")
|
||||
public OutputPin<T> CreateOutputPin<T>(string name = "")
|
||||
{
|
||||
OutputPin<T> pin = new(this, name);
|
||||
_pins.Add(pin);
|
||||
@@ -174,7 +174,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// <param name="type">The type of value the pin will hold</param>
|
||||
/// <param name="name">The name of the pin</param>
|
||||
/// <returns>The newly created pin</returns>
|
||||
protected OutputPin CreateOutputPin(Type type, string name = "")
|
||||
public OutputPin CreateOutputPin(Type type, string name = "")
|
||||
{
|
||||
OutputPin pin = new(this, type, name);
|
||||
_pins.Add(pin);
|
||||
@@ -187,7 +187,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// 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)
|
||||
public 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));
|
||||
@@ -217,7 +217,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// 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)
|
||||
public 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));
|
||||
@@ -247,7 +247,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin to remove</param>
|
||||
/// <returns><see langword="true" /> if the pin was removed; otherwise <see langword="false" />.</returns>
|
||||
protected bool RemovePin(Pin pin)
|
||||
public bool RemovePin(Pin pin)
|
||||
{
|
||||
bool isRemoved = _pins.Remove(pin);
|
||||
if (isRemoved)
|
||||
@@ -263,7 +263,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// Adds an existing <paramref name="pin" /> to the <see cref="Pins" /> collection.
|
||||
/// </summary>
|
||||
/// <param name="pin">The pin to add</param>
|
||||
protected void AddPin(Pin pin)
|
||||
public void AddPin(Pin pin)
|
||||
{
|
||||
if (pin.Node != this)
|
||||
throw new ArtemisCoreException("Can't add a pin to a node that belongs to a different node than the one it's being added to.");
|
||||
@@ -281,7 +281,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// <param name="name">The name of the pin collection</param>
|
||||
/// <param name="initialCount">The amount of pins to initially add to the collection</param>
|
||||
/// <returns>The resulting input pin collection</returns>
|
||||
protected InputPinCollection<T> CreateInputPinCollection<T>(string name = "", int initialCount = 1)
|
||||
public InputPinCollection<T> CreateInputPinCollection<T>(string name = "", int initialCount = 1)
|
||||
{
|
||||
InputPinCollection<T> pin = new(this, name, initialCount);
|
||||
_pinCollections.Add(pin);
|
||||
@@ -296,7 +296,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// <param name="name">The name of the pin collection</param>
|
||||
/// <param name="initialCount">The amount of pins to initially add to the collection</param>
|
||||
/// <returns>The resulting input pin collection</returns>
|
||||
protected InputPinCollection CreateInputPinCollection(Type type, string name = "", int initialCount = 1)
|
||||
public InputPinCollection CreateInputPinCollection(Type type, string name = "", int initialCount = 1)
|
||||
{
|
||||
InputPinCollection pin = new(this, type, name, initialCount);
|
||||
_pinCollections.Add(pin);
|
||||
@@ -311,7 +311,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// <param name="name">The name of the pin collection</param>
|
||||
/// <param name="initialCount">The amount of pins to initially add to the collection</param>
|
||||
/// <returns>The resulting output pin collection</returns>
|
||||
protected OutputPinCollection<T> CreateOutputPinCollection<T>(string name = "", int initialCount = 1)
|
||||
public OutputPinCollection<T> CreateOutputPinCollection<T>(string name = "", int initialCount = 1)
|
||||
{
|
||||
OutputPinCollection<T> pin = new(this, name, initialCount);
|
||||
_pinCollections.Add(pin);
|
||||
@@ -325,7 +325,7 @@ public abstract class Node : BreakableModel, INode
|
||||
/// </summary>
|
||||
/// <param name="pinCollection">The pin collection to remove</param>
|
||||
/// <returns><see langword="true" /> if the pin collection was removed; otherwise <see langword="false" />.</returns>
|
||||
protected bool RemovePinCollection(PinCollection pinCollection)
|
||||
public bool RemovePinCollection(PinCollection pinCollection)
|
||||
{
|
||||
bool isRemoved = _pinCollections.Remove(pinCollection);
|
||||
if (isRemoved)
|
||||
|
||||
@@ -35,7 +35,10 @@ public abstract class NodeScript : CorePropertyChanged, INodeScript
|
||||
|
||||
#region Properties & Fields
|
||||
|
||||
internal NodeScriptEntity Entity { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the entity used to store this script.
|
||||
/// </summary>
|
||||
public NodeScriptEntity Entity { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Name { get; }
|
||||
@@ -410,7 +413,8 @@ public class NodeScript<T> : NodeScript, INodeScript<T>
|
||||
|
||||
#region Constructors
|
||||
|
||||
internal NodeScript(string name, string description, NodeScriptEntity entity, object? context = null)
|
||||
/// <inheritdoc />
|
||||
public NodeScript(string name, string description, NodeScriptEntity entity, object? context = null)
|
||||
: base(name, description, entity, context)
|
||||
{
|
||||
ExitNode = new ExitNode<T>(name, description);
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
using Artemis.Core.Modules;
|
||||
using Humanizer;
|
||||
|
||||
namespace Artemis.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of output pins for a node capable of outputting the properties of an object or value type.
|
||||
/// </summary>
|
||||
public class ObjectOutputPins
|
||||
{
|
||||
private readonly Dictionary<Func<object, object>, OutputPin> _propertyPins;
|
||||
private OutputPin? _valueTypePin;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the <see cref="ObjectOutputPins" /> class.
|
||||
/// </summary>
|
||||
/// <param name="node">The node the object output was created for.</param>
|
||||
public ObjectOutputPins(Node node)
|
||||
{
|
||||
Node = node;
|
||||
_propertyPins = new Dictionary<Func<object, object>, OutputPin>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the node the object output was created for.
|
||||
/// </summary>
|
||||
public Node Node { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current type the node's pins are set up for.
|
||||
/// </summary>
|
||||
public Type? CurrentType { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only collection of the pins outputting the object of this object node.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<OutputPin> Pins => _valueTypePin != null ? new ReadOnlyCollection<OutputPin>(new List<OutputPin> {_valueTypePin}) : _propertyPins.Values.ToList().AsReadOnly();
|
||||
|
||||
/// <summary>
|
||||
/// Change the current type and create pins on the node to reflect this.
|
||||
/// </summary>
|
||||
/// <param name="type">The type to change the collection to.</param>
|
||||
public void ChangeType(Type? type)
|
||||
{
|
||||
if (type == CurrentType)
|
||||
return;
|
||||
CurrentType = type;
|
||||
|
||||
// Remove current pins
|
||||
foreach ((Func<object, object>? _, OutputPin? pin) in _propertyPins)
|
||||
Node.RemovePin(pin);
|
||||
_propertyPins.Clear();
|
||||
if (_valueTypePin != null)
|
||||
{
|
||||
Node.RemovePin(_valueTypePin);
|
||||
_valueTypePin = null;
|
||||
}
|
||||
|
||||
if (type == null)
|
||||
return;
|
||||
|
||||
// Create new pins
|
||||
List<TypeColorRegistration> nodeTypeColors = NodeTypeStore.GetColors();
|
||||
if (type.IsClass && type != typeof(string))
|
||||
foreach (PropertyInfo propertyInfo in type.GetProperties(BindingFlags.Instance | BindingFlags.Public))
|
||||
{
|
||||
Type propertyType = propertyInfo.PropertyType;
|
||||
bool toNumeric = Numeric.IsTypeCompatible(propertyType);
|
||||
|
||||
// Skip ignored properties
|
||||
if (propertyInfo.CustomAttributes.Any(a => a.AttributeType == typeof(DataModelIgnoreAttribute)))
|
||||
continue;
|
||||
// Skip incompatible properties
|
||||
if (!toNumeric && !nodeTypeColors.Any(c => c.Type.IsAssignableFrom(propertyType)))
|
||||
continue;
|
||||
|
||||
// Expect an object
|
||||
ParameterExpression itemParameter = Expression.Parameter(typeof(object), "item");
|
||||
// Cast it to the actual item type
|
||||
UnaryExpression itemCast = Expression.Convert(itemParameter, propertyInfo.DeclaringType!);
|
||||
// Access the property
|
||||
MemberExpression accessor = Expression.Property(itemCast, propertyInfo);
|
||||
|
||||
// Turn into a numeric if needed or access directly
|
||||
UnaryExpression objectExpression;
|
||||
if (toNumeric)
|
||||
{
|
||||
propertyType = typeof(Numeric);
|
||||
ConstructorInfo constructor = typeof(Numeric).GetConstructors().First(c => c.GetParameters().First().ParameterType == propertyInfo.PropertyType);
|
||||
// Cast the property to an object (sadly boxing)
|
||||
objectExpression = Expression.Convert(Expression.New(constructor, accessor), typeof(object));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cast the property to an object (sadly boxing)
|
||||
objectExpression = Expression.Convert(accessor, typeof(object));
|
||||
}
|
||||
|
||||
// Compile the resulting expression
|
||||
Func<object, object> expression = Expression.Lambda<Func<object, object>>(objectExpression, itemParameter).Compile();
|
||||
_propertyPins.Add(expression, Node.CreateOrAddOutputPin(propertyType, propertyInfo.Name.Humanize()));
|
||||
}
|
||||
else
|
||||
// Value types are applied directly to a single pin, however if the type is compatible with Numeric, we use a Numeric pin instead
|
||||
// the value will then be turned into a numeric in SetCurrentValue
|
||||
_valueTypePin = Node.CreateOrAddOutputPin(Numeric.IsTypeCompatible(type) ? typeof(Numeric) : type, "Item");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the current value to be output onto connected pins.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to output onto the connected pins.</param>
|
||||
/// <exception cref="ArtemisCoreException"></exception>
|
||||
public void SetCurrentValue(object? value)
|
||||
{
|
||||
if (CurrentType == null)
|
||||
throw new ArtemisCoreException("Cannot apply a value to an object output pins not yet configured for a type.");
|
||||
if (value != null && CurrentType != value.GetType())
|
||||
throw new ArtemisCoreException($"Cannot apply a value of type {value.GetType().FullName} to an object output pins configured for type {CurrentType.FullName}");
|
||||
|
||||
// Apply the object to the pin, it must be connected if SetCurrentValue got called
|
||||
if (_valueTypePin != null)
|
||||
{
|
||||
value ??= _valueTypePin.Type.GetDefault();
|
||||
_valueTypePin.Value = _valueTypePin.Type == typeof(Numeric) ? new Numeric(value) : value;
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply the properties of the object to each connected pin
|
||||
foreach ((Func<object, object>? propertyAccessor, OutputPin? outputPin) in _propertyPins)
|
||||
{
|
||||
if (outputPin.ConnectedTo.Any())
|
||||
outputPin.Value = value != null ? propertyAccessor(value) : outputPin.Type.GetDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user