365 lines
11 KiB
C#
365 lines
11 KiB
C#
#nullable enable
|
|
using System;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using T3.Core.DataTypes;
|
|
using T3.Core.Logging;
|
|
using T3.Core.Operator.Interfaces;
|
|
using T3.Core.Stats;
|
|
using T3.Core.Utils;
|
|
// ReSharper disable FieldCanBeMadeReadOnly.Local
|
|
// ReSharper disable InconsistentNaming
|
|
|
|
// ReSharper disable ConvertToAutoPropertyWhenPossible
|
|
// ReSharper disable ForCanBeConvertedToForeach
|
|
// ReSharper disable InlineTemporaryVariable
|
|
|
|
namespace T3.Core.Operator.Slots;
|
|
|
|
public class Slot<T> : ISlot
|
|
{
|
|
public Guid Id;
|
|
private readonly Type _valueType;
|
|
Type ISlot.ValueType => _valueType;
|
|
|
|
public Instance Parent
|
|
{
|
|
get => _parent;
|
|
set
|
|
{
|
|
_parent = value;
|
|
_parentIsICompoundWithUpdate = _parent is ICompoundWithUpdate;
|
|
}
|
|
}
|
|
|
|
public DirtyFlag DirtyFlag => _dirtyFlag;
|
|
// ReSharper disable once FieldCanBeMadeReadOnly.Local
|
|
private protected DirtyFlag _dirtyFlag = new();
|
|
|
|
public T Value;
|
|
|
|
protected bool _isDisabled;
|
|
|
|
protected virtual void SetDisabled(bool shouldBeDisabled)
|
|
{
|
|
if (shouldBeDisabled == _isDisabled)
|
|
return;
|
|
|
|
if (shouldBeDisabled)
|
|
{
|
|
if (_keepOriginalUpdateAction != null)
|
|
{
|
|
Log.Warning("Is already bypassed or disabled");
|
|
return;
|
|
}
|
|
|
|
_keepOriginalUpdateAction = UpdateAction;
|
|
_keepDirtyFlagTrigger = _dirtyFlag.Trigger;
|
|
UpdateAction = EmptyAction;
|
|
_dirtyFlag.Invalidate();
|
|
}
|
|
else
|
|
{
|
|
RestoreUpdateAction();
|
|
}
|
|
|
|
_isDisabled = shouldBeDisabled;
|
|
}
|
|
|
|
internal bool TryGetAsMultiInputTyped(out MultiInputSlot<T> multiInput)
|
|
{
|
|
multiInput = _thisAsMultiInputSlot;
|
|
return _isMultiInput;
|
|
}
|
|
|
|
internal virtual bool TrySetBypassToInput(Slot<T> targetSlot)
|
|
{
|
|
if (_keepOriginalUpdateAction != null)
|
|
{
|
|
//Log.Warning("Already disabled or bypassed");
|
|
return false;
|
|
}
|
|
|
|
_keepOriginalUpdateAction = UpdateAction;
|
|
_keepDirtyFlagTrigger = _dirtyFlag.Trigger;
|
|
UpdateAction = ByPassUpdate;
|
|
_dirtyFlag.Invalidate();
|
|
_targetInputForBypass = targetSlot;
|
|
return true;
|
|
}
|
|
|
|
internal void OverrideWithAnimationAction(Action<EvaluationContext> newAction)
|
|
{
|
|
// Animation actions are updated regardless if operator was already animated
|
|
if (_keepOriginalUpdateAction == null)
|
|
{
|
|
_keepOriginalUpdateAction = UpdateAction;
|
|
_keepDirtyFlagTrigger = _dirtyFlag.Trigger;
|
|
}
|
|
|
|
UpdateAction = newAction;
|
|
_dirtyFlag.Invalidate();
|
|
}
|
|
|
|
public virtual void RestoreUpdateAction()
|
|
{
|
|
// This will happen when operators are recompiled and output slots are InputConnections[0]
|
|
if (_keepOriginalUpdateAction == null)
|
|
{
|
|
UpdateAction = null;
|
|
return;
|
|
}
|
|
|
|
UpdateAction = _keepOriginalUpdateAction;
|
|
_keepOriginalUpdateAction = null;
|
|
_dirtyFlag.Trigger = _keepDirtyFlagTrigger;
|
|
_dirtyFlag.Invalidate();
|
|
}
|
|
|
|
public bool IsDisabled
|
|
{
|
|
get => _isDisabled;
|
|
set => SetDisabled(value);
|
|
}
|
|
|
|
// ReSharper disable once StaticMemberInGenericType
|
|
protected static readonly Action<EvaluationContext> EmptyAction = _ => { };
|
|
|
|
public Slot()
|
|
{
|
|
Value = default!; // tells the compiler it's intentionally initialized
|
|
|
|
// UpdateAction = Update;
|
|
_valueType = typeof(T);
|
|
_valueIsCommand = _valueType == typeof(Command);
|
|
|
|
if (this is IInputSlot)
|
|
{
|
|
_isInputSlot = true;
|
|
}
|
|
}
|
|
|
|
public Slot(T defaultValue) : this()
|
|
{
|
|
Value = defaultValue;
|
|
}
|
|
|
|
// dummy constructor to initialize input slot values
|
|
// ReSharper disable once UnusedParameter.Local
|
|
protected Slot(bool _) : this()
|
|
{
|
|
_isInputSlot = true;
|
|
if (this is MultiInputSlot<T> multiInputSlot)
|
|
{
|
|
_isMultiInput = true;
|
|
_thisAsMultiInputSlot = multiInputSlot;
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
public void Update(EvaluationContext context)
|
|
{
|
|
if (_dirtyFlag.IsDirty || _valueIsCommand)
|
|
{
|
|
OpUpdateCounter.CountUp();
|
|
UpdateAction?.Invoke(context);
|
|
_dirtyFlag.Clear();
|
|
_dirtyFlag.SetUpdated();
|
|
}
|
|
}
|
|
|
|
public void ConnectedUpdate(EvaluationContext context)
|
|
{
|
|
Value = InputConnections[0].GetValue(context)!;
|
|
}
|
|
|
|
protected void ByPassUpdate(EvaluationContext context)
|
|
{
|
|
Value = _targetInputForBypass!.GetValue(context)!;
|
|
}
|
|
|
|
public T? GetValue(EvaluationContext context)
|
|
{
|
|
Update(context);
|
|
|
|
return Value;
|
|
}
|
|
|
|
public void AddConnection(ISlot sourceSlot, int index = 0)
|
|
{
|
|
if (!HasInputConnections)
|
|
{
|
|
if (UpdateAction != null)
|
|
{
|
|
_actionBeforeAddingConnecting = UpdateAction;
|
|
if (_parentIsICompoundWithUpdate && !_isInputSlot && _parent.Children.Count > 0)
|
|
{
|
|
ArrayUtils.InsertAtIndexOrEnd(ref InputConnections, (Slot<T>)sourceSlot, index);
|
|
_dirtyFlag.SourceVersion = sourceSlot.DirtyFlag.SourceVersion;
|
|
_dirtyFlag.ValueVersion = - 1;
|
|
return;
|
|
}
|
|
}
|
|
UpdateAction = ConnectedUpdate;
|
|
_dirtyFlag.SourceVersion = sourceSlot.DirtyFlag.SourceVersion;
|
|
_dirtyFlag.ValueVersion = -1;
|
|
}
|
|
|
|
if (sourceSlot.ValueType != _valueType)
|
|
{
|
|
Log.Warning("Type mismatch during connection");
|
|
return;
|
|
}
|
|
|
|
ArrayUtils.InsertAtIndexOrEnd(ref InputConnections, (Slot<T>)sourceSlot, index);
|
|
}
|
|
|
|
private Action<EvaluationContext>? _actionBeforeAddingConnecting;
|
|
|
|
public void RemoveConnection(int index = 0)
|
|
{
|
|
if (HasInputConnections)
|
|
{
|
|
if (index < InputConnections.Length)
|
|
{
|
|
ArrayUtils.RemoveAt(ref InputConnections, index);
|
|
}
|
|
else
|
|
{
|
|
Log.Error($"{Parent} trying to delete connection at index {index}, but {GetType()} only has {InputConnections.Length} connections");
|
|
}
|
|
}
|
|
|
|
if (!HasInputConnections)
|
|
{
|
|
if (_actionBeforeAddingConnecting != null)
|
|
{
|
|
UpdateAction = _actionBeforeAddingConnecting;
|
|
}
|
|
else
|
|
{
|
|
// if no connection is set anymore restore the default update action
|
|
RestoreUpdateAction();
|
|
}
|
|
_dirtyFlag.ForceInvalidate();
|
|
}
|
|
}
|
|
|
|
public bool HasInputConnections => InputConnections.Length > 0;
|
|
|
|
public ISlot? FirstConnection => InputConnections.Length > 0 ? InputConnections[0] : null;
|
|
|
|
public bool TryGetFirstConnection(out ISlot? connectedSlot)
|
|
{
|
|
if(InputConnections.Length > 0)
|
|
{
|
|
connectedSlot = InputConnections[0];
|
|
return true;
|
|
}
|
|
|
|
connectedSlot = null;
|
|
return false;
|
|
}
|
|
|
|
protected Slot<T>[] InputConnections = [];
|
|
|
|
public int InvalidateGraph()
|
|
{
|
|
var globalTick = DirtyFlag.GlobalInvalidationTick;
|
|
if (globalTick == _dirtyFlag.InvalidationTick)
|
|
{
|
|
// do nothing
|
|
return _dirtyFlag.SourceVersion;
|
|
}
|
|
|
|
// MultiInputSlot, TimeClipSlot, TransformCallbackSlot, etc
|
|
if (HasInvalidationOverride)
|
|
{
|
|
var target = InvalidationOverride();
|
|
_dirtyFlag.SourceVersion = target;
|
|
_dirtyFlag.InvalidationTick = globalTick;
|
|
return target;
|
|
}
|
|
|
|
// connected
|
|
if (InputConnections.Length > 0)
|
|
{
|
|
var target = InputConnections[0].InvalidateGraph();
|
|
_dirtyFlag.SourceVersion = target;
|
|
_dirtyFlag.InvalidationTick = globalTick;
|
|
return target;
|
|
}
|
|
|
|
// unconnected input slots
|
|
if (_isInputSlot)
|
|
{
|
|
if(_dirtyFlag.TriggerIsEnabled)
|
|
{
|
|
return _dirtyFlag.Invalidate();
|
|
}
|
|
_dirtyFlag.InvalidationTick = globalTick;
|
|
return _dirtyFlag.SourceVersion;
|
|
}
|
|
|
|
// unconnected output slots
|
|
var parentInputs = _parent.Inputs;
|
|
var parentInputCount = parentInputs.Count;
|
|
|
|
bool outputDirty = _dirtyFlag.IsDirty;
|
|
for (var i = 0; i < parentInputCount; i++)
|
|
{
|
|
var input = parentInputs[i];
|
|
input.InvalidateGraph();
|
|
outputDirty |= input.IsDirty;
|
|
}
|
|
|
|
if (outputDirty)
|
|
{
|
|
return _dirtyFlag.Invalidate();
|
|
}
|
|
|
|
_dirtyFlag.InvalidationTick = globalTick;
|
|
return _dirtyFlag.SourceVersion;
|
|
}
|
|
|
|
protected void SetVisited() => _dirtyFlag.InvalidationTick = DirtyFlag.GlobalInvalidationTick;
|
|
|
|
protected virtual int InvalidationOverride() => 0;
|
|
|
|
Guid ISlot.Id { get => Id; set => Id = value; }
|
|
DirtyFlag ISlot.DirtyFlag => DirtyFlag;
|
|
|
|
// todo - this should be an action list or event? ordered execution can be important
|
|
public virtual Action<EvaluationContext>? UpdateAction { get; set; }
|
|
|
|
protected Action<EvaluationContext>? _keepOriginalUpdateAction;
|
|
private DirtyFlagTrigger _keepDirtyFlagTrigger;
|
|
protected Slot<T>? _targetInputForBypass;
|
|
|
|
private bool _isInputSlot;
|
|
private bool _isMultiInput;
|
|
public bool IsMultiInput => _isMultiInput;
|
|
private MultiInputSlot<T> _thisAsMultiInputSlot = null!;
|
|
protected MultiInputSlot<T> ThisAsMultiInputSlot => _thisAsMultiInputSlot;
|
|
private Instance _parent = null!;
|
|
private bool _valueIsCommand;
|
|
private protected bool HasInvalidationOverride;
|
|
private bool _parentIsICompoundWithUpdate;
|
|
|
|
public override string ToString()
|
|
{
|
|
if (_isInputSlot)
|
|
{
|
|
var i = Parent.Inputs.FirstOrDefault(i => i.Id == Id);
|
|
return i != null
|
|
? $"{i.Parent.Symbol.Name}.{i.Input.Name}"
|
|
: "ISlot";
|
|
}
|
|
|
|
var symbol = Parent.Symbol;
|
|
var outputDef = Parent.Symbol.OutputDefinitions.FirstOrDefault(o => o.Id == Id);
|
|
return outputDef != null
|
|
? $"{symbol.Name}.{outputDef.Name} (Output)"
|
|
: "IOutputSlot";
|
|
}
|
|
} |