#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 : 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 multiInput) { multiInput = _thisAsMultiInputSlot; return _isMultiInput; } internal virtual bool TrySetBypassToInput(Slot 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 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 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 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)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)sourceSlot, index); } private Action? _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[] 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? UpdateAction { get; set; } protected Action? _keepOriginalUpdateAction; private DirtyFlagTrigger _keepDirtyFlagTrigger; protected Slot? _targetInputForBypass; private bool _isInputSlot; private bool _isMultiInput; public bool IsMultiInput => _isMultiInput; private MultiInputSlot _thisAsMultiInputSlot = null!; protected MultiInputSlot 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"; } }