1
0
mirror of https://github.com/Artemis-RGB/Artemis synced 2025-12-13 05:48:35 +00:00
RobertBeekman e5a5f10286
Profiles - Added IPluginFeatureDependent interface and implement throughout profiles (#842)
* Profiles - Added IPluginFeatureDependent interface and implement througout profiles
* Workshop - Include dependencies in profile upload request
2024-03-03 20:19:36 +01:00

338 lines
10 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Internal;
using Artemis.Storage.Entities.Profile.Abstract;
using Artemis.Storage.Entities.Profile.Conditions;
namespace Artemis.Core;
/// <summary>
/// Represents a condition that is based on a <see cref="DataModelEvent" />
/// </summary>
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 EventToggleOffMode _toggleOffMode;
private EventTriggerMode _triggerMode;
private bool _wasMet;
/// <summary>
/// Creates a new instance of the <see cref="EventCondition" /> class
/// </summary>
public EventCondition(RenderProfileElement profileElement)
{
ProfileElement = profileElement;
_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, new List<DefaultNode> {_startNode});
}
internal EventCondition(EventConditionEntity entity, RenderProfileElement profileElement)
{
ProfileElement = profileElement;
_entity = entity;
_displayName = profileElement.GetType().Name;
_startNode = new EventConditionEventStartNode();
_script = null!;
Load();
}
/// <summary>
/// Gets the script that drives the event condition
/// </summary>
public NodeScript<bool> Script
{
get => _script;
private set => SetAndNotify(ref _script, value);
}
/// <summary>
/// Gets or sets the path to the event that drives this event condition
/// </summary>
public DataModelPath? EventPath
{
get => _eventPath;
set => SetAndNotify(ref _eventPath, value);
}
/// <summary>
/// Gets or sets how the condition behaves when the event fires.
/// </summary>
public EventTriggerMode TriggerMode
{
get => _triggerMode;
set => SetAndNotify(ref _triggerMode, value);
}
/// <summary>
/// Gets or sets how the condition behaves when events trigger before the timeline finishes in the
/// <see cref="EventTriggerMode.Play" /> event trigger mode.
/// </summary>
public EventOverlapMode OverlapMode
{
get => _overlapMode;
set => SetAndNotify(ref _overlapMode, value);
}
/// <summary>
/// Gets or sets the mode for render elements when toggling off the event when using
/// <see cref="EventTriggerMode.Toggle" />.
/// </summary>
public EventToggleOffMode ToggleOffMode
{
get => _toggleOffMode;
set => SetAndNotify(ref _toggleOffMode, value);
}
/// <inheritdoc />
public IConditionEntity Entity => _entity;
/// <inheritdoc />
public RenderProfileElement ProfileElement { get; }
/// <inheritdoc />
public bool IsMet { get; private set; }
/// <inheritdoc />
public void Update()
{
_wasMet = IsMet;
if (TriggerMode == EventTriggerMode.Toggle)
{
if (Evaluate())
IsMet = !IsMet;
}
else
{
IsMet = Evaluate();
}
}
/// <inheritdoc />
public void UpdateTimeline(double deltaTime)
{
if (TriggerMode == EventTriggerMode.Toggle)
{
if (IsMet && !_wasMet)
ProfileElement.Timeline.JumpToStart();
if (!IsMet && _wasMet && ToggleOffMode == EventToggleOffMode.SkipToEnd)
ProfileElement.Timeline.JumpToEndSegment();
}
else
{
if (IsMet && ProfileElement.Timeline.IsFinished)
{
ProfileElement.Timeline.JumpToStart();
}
else if (IsMet)
{
if (OverlapMode == EventOverlapMode.Restart)
ProfileElement.Timeline.JumpToStart();
else if (OverlapMode == EventOverlapMode.Copy && ProfileElement is Layer layer && layer.Parent is not Layer)
layer.CreateRenderCopy(10);
}
}
// Stick to mean segment in toggle mode for as long as the condition is met
ProfileElement.Timeline.Update(TimeSpan.FromSeconds(deltaTime), TriggerMode == EventTriggerMode.Toggle && IsMet);
}
/// <inheritdoc />
public void OverrideTimeline(TimeSpan position)
{
ProfileElement.Timeline.Override(position, TriggerMode == EventTriggerMode.Toggle && position > ProfileElement.Timeline.Length);
}
/// <inheritdoc />
public void Dispose()
{
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
/// <inheritdoc />
public void Load()
{
TriggerMode = (EventTriggerMode) _entity.TriggerMode;
OverlapMode = (EventOverlapMode) _entity.OverlapMode;
ToggleOffMode = (EventToggleOffMode) _entity.ToggleOffMode;
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>(name, description, _entity.Script, ProfileElement.Profile, new List<DefaultNode> {_startNode})
: new NodeScript<bool>(name, description, ProfileElement.Profile, new List<DefaultNode> {_startNode});
}
/// <inheritdoc />
public void Save()
{
_entity.TriggerMode = (int) TriggerMode;
_entity.OverlapMode = (int) OverlapMode;
_entity.ToggleOffMode = (int) ToggleOffMode;
// If the exit node isn't connected and there are only the start- and exit node, don't save the script
if (!Script.ExitNodeConnected && Script.Nodes.Count() <= 2)
{
_entity.Script = null;
}
else
{
Script.Save();
_entity.Script = Script.Entity;
}
EventPath?.Save();
_entity.EventPath = EventPath?.Entity;
}
/// <inheritdoc />
public INodeScript NodeScript => Script;
/// <inheritdoc />
public void LoadNodeScript()
{
UpdateEventNode(true);
Script.Load();
}
#endregion
#region Implementation of IPluginFeatureDependent
/// <inheritdoc />
public IEnumerable<PluginFeature> GetFeatureDependencies()
{
return Script.GetFeatureDependencies().Concat(EventPath?.GetFeatureDependencies() ?? []);
}
#endregion
}