Files
tooll3--t3/Core/Operator/Instance.Connections.cs
2026-07-13 13:13:17 +08:00

435 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using T3.Core.DataTypes;
using T3.Core.Logging;
using T3.Core.Operator.Slots;
using T3.Core.Utils;
namespace T3.Core.Operator;
public abstract partial class Instance
{
internal void ReconnectChildren()
{
if (!NeedsInternalReconnections)
return;
if (TryGetParentInstance(out var parent, false) && parent is { NeedsInternalReconnections: true })
{
// if the parent instance also needs reconnections, we return early
// as the parent will trigger this method in its children as well
parent.ReconnectChildren();
return;
}
// prevent recursion by setting the connection status prematurely
_status |= InstanceStatus.ConnectedInternally;
_status |= InstanceStatus.IsReconnecting;
// first removes all connections from direct children
// we do this so we can blindly re-apply all of our connections
foreach (var childInst in Children.PreExistingValues)
{
childInst.DisconnectInputs();
}
// ensure all of our children have their own internall connections connected (recursive)
foreach (var child in SymbolChild.Symbol.Children.Values)
{
if (Children.TryGetChildInstance(child.Id, out var childInst) )
{
if(childInst.NeedsInternalReconnections)
childInst.ReconnectChildren();
}
else
{
Log.Error($"Failed to create/locate child instance {child.Id} in {SymbolChild}");
}
}
// actually create the connections for this instance and its children
CreateConnectionsForInstance(this);
_status &= ~InstanceStatus.IsReconnecting; // clear reconnecting status
return;
static void CreateConnectionsForInstance(Instance instance)
{
// create connections between child instances populated with CreateAndAddNewChildInstance
var child = instance.SymbolChild;
var symbol = instance.Symbol;
var connections = symbol.Connections;
// if connections already exist for the symbol, remove any that shouldn't exist anymore
if (connections.Count != 0)
{
var conHashToCount = new Dictionary<ulong, int>(connections.Count);
for (var index = 0; index < connections.Count; index++) // warning: the order in which these are processed matters
{
var connection = connections[index];
ulong highPart = 0xFFFFFFFF & (ulong)connection.TargetSlotId.GetHashCode();
ulong lowPart = 0xFFFFFFFF & (ulong)connection.TargetParentOrChildId.GetHashCode();
ulong hash = (highPart << 32) | lowPart;
conHashToCount.TryGetValue(hash, out int count);
if (!instance.TryAddConnection(connection, count, true))
{
Log.Warning($"Removing obsolete connecting in {symbol}...");
// todo: this removal should be moved into the Symbol class
connections.RemoveAt(index);
index--;
continue;
}
conHashToCount[hash] = count + 1;
}
}
// connect animations if available
// note: by accessing the Values property, all children are guaranteed to be created, even if they are not connected to anything
symbol.Animator.CreateUpdateActionsForExistingCurves(instance.Children.Values);
if (child.IsBypassed)
{
SetBypassFor(instance, true, invalidate: false);
}
}
}
// disconnects all inputs of this instance - all considered "external" connections
private int DisconnectInputs()
{
// clear our connections - we may be reassigned to another parent
int disconnectCount = 0;
for (var index = 0; index < _inputs.Count; index++)
{
var input = _inputs[index];
while (input.HasInputConnections)
{
input.RemoveConnection();
++disconnectCount;
}
}
return disconnectCount;
}
internal bool TryGetTargetSlot(Symbol.Connection connection, [NotNullWhen(true)] out ISlot targetSlot, bool allowCreate)
{
// Get target Instance
var targetParentOrChildId = connection.TargetParentOrChildId;
IEnumerable<ISlot> targetSlotList;
if (targetParentOrChildId == Guid.Empty)
{
targetSlotList = Outputs;
}
else
{
if (!Children.TryGetChildInstance(targetParentOrChildId, out var targetInstance, allowCreate))
{
targetSlot = null;
return false;
}
targetSlotList = targetInstance.Inputs;
}
foreach (var slot in targetSlotList)
{
if (slot.Id != connection.TargetSlotId)
continue;
targetSlot = slot;
return true;
}
targetSlot = null;
return false;
}
internal static void SortInputSlotsByDefinitionOrder(Instance instance)
{
// order the inputs by the given input definitions. original order is coming from code, but input def order is the relevant one
var inputs = instance._inputs;
var inputDefinitions = instance.Symbol.InputDefinitions;
int numInputs = inputs.Count;
var lastIndex = numInputs - 1;
for (int i = 0; i < lastIndex; i++)
{
Guid inputId = inputDefinitions[i].Id;
if (inputs[i].Id != inputId)
{
int index = inputs.FindIndex(i + 1, input => input.Id == inputId);
if (index == -1)
continue;
//Debug.Assert(index >= 0);
inputs.Swap(i, index);
Debug.Assert(inputId == inputs[i].Id);
}
}
#if DEBUG
if (numInputs > 0)
{
#if SKIP_ASSERTS
Debug.Assert(inputs.Count == inputDefinitions.Count);
#endif
}
#endif
}
private bool TryGetSourceSlot(Symbol.Connection connection, [NotNullWhen(true)] out ISlot sourceSlot, bool allowCreate)
{
// Get source Instance
IEnumerable<ISlot> sourceSlotList;
var sourceParentOrChildId = connection.SourceParentOrChildId;
if (sourceParentOrChildId == Guid.Empty)
{
sourceSlotList = Inputs;
}
else
{
if (!Children.TryGetChildInstance(sourceParentOrChildId, out var sourceInstance, allowCreate))
{
sourceSlot = null;
return false;
}
sourceSlotList = sourceInstance.Outputs;
}
// Get source Slot
sourceSlot = null;
var gotSourceSlot = false;
foreach (var slot in sourceSlotList)
{
if (slot.Id != connection.SourceSlotId)
continue;
sourceSlot = slot;
gotSourceSlot = true;
break;
}
return gotSourceSlot;
}
internal bool TryAddConnection(Symbol.Connection connection, int multiInputIndex, bool allowCreate)
{
if (!TryGetSourceSlot(connection, out var sourceSlot, allowCreate) ||
!TryGetTargetSlot(connection, out var targetSlot, allowCreate))
return false;
targetSlot.AddConnection(sourceSlot, multiInputIndex);
targetSlot.InvalidateGraph();
return true;
}
private protected void SetupInputAndOutputsFromType()
{
var symbol = Symbol;
var assemblyInfo = symbol.SymbolPackage.AssemblyInformation;
if (!assemblyInfo.OperatorTypeInfo.TryGetValue(symbol.Id, out var operatorTypeInfo))
{
Log.Error($"Can't find operatorTypeInfo for id {symbol} {symbol.Id} in {assemblyInfo}");
Debug.Assert(false);
}
//var operatorTypeInfo = assemblyInfo.OperatorTypeInfo[symbol.Id];
foreach (var input in operatorTypeInfo.Inputs)
{
var attribute = input.Attribute;
var inputSlot = input.GetSlotObject(this);
inputSlot.Parent = this;
inputSlot.Id = attribute.Id;
inputSlot.MappedType = attribute.MappedType;
_inputs.Add(inputSlot);
}
// outputs identified by attribute
foreach (var output in operatorTypeInfo.Outputs)
{
var slot = output.GetSlotObject(this);
slot.Parent = this;
slot.Id = output.Attribute.Id;
_outputs.Add(slot);
}
}
internal static bool SetBypassFor(Instance instance, bool shouldBypass, bool invalidate = true)
{
var mainInputSlot = instance.Inputs[0];
var mainOutputSlot = instance.Outputs[0];
var wasByPassed = false;
// note - can this be made more flexible by not having a "main" input/output requirement and instead
// matching any one-to-one input/output type pairs?
switch (mainOutputSlot)
{
case Slot<Command> commandOutput when mainInputSlot is Slot<Command> commandInput:
if (shouldBypass)
{
wasByPassed = commandOutput.TrySetBypassToInput(commandInput);
}
else
{
commandOutput.RestoreUpdateAction();
}
if (invalidate)
InvalidateConnected(commandInput);
break;
case Slot<BufferWithViews> bufferOutput when mainInputSlot is Slot<BufferWithViews> bufferInput:
if (shouldBypass)
{
wasByPassed = bufferOutput.TrySetBypassToInput(bufferInput);
}
else
{
bufferOutput.RestoreUpdateAction();
}
if (invalidate)
InvalidateConnected(bufferInput);
break;
case Slot<MeshBuffers> bufferOutput when mainInputSlot is Slot<MeshBuffers> bufferInput:
if (shouldBypass)
{
wasByPassed = bufferOutput.TrySetBypassToInput(bufferInput);
}
else
{
bufferOutput.RestoreUpdateAction();
}
if (invalidate)
InvalidateConnected(bufferInput);
break;
case Slot<Texture2D> texture2dOutput when mainInputSlot is Slot<Texture2D> texture2dInput:
if (shouldBypass)
{
wasByPassed = texture2dOutput.TrySetBypassToInput(texture2dInput);
}
else
{
texture2dOutput.RestoreUpdateAction();
}
if (invalidate)
InvalidateConnected(texture2dInput);
break;
case Slot<float> floatOutput when mainInputSlot is Slot<float> floatInput:
if (shouldBypass)
{
wasByPassed = floatOutput.TrySetBypassToInput(floatInput);
}
else
{
floatOutput.RestoreUpdateAction();
}
if (invalidate)
InvalidateConnected(floatInput);
break;
case Slot<Vector2> vec2Output when mainInputSlot is Slot<Vector2> vec2Input:
if (shouldBypass)
{
wasByPassed = vec2Output.TrySetBypassToInput(vec2Input);
}
else
{
vec2Output.RestoreUpdateAction();
}
if (invalidate)
InvalidateConnected(vec2Input);
break;
case Slot<Vector3> vec3Output when mainInputSlot is Slot<Vector3> vec3Input:
if (shouldBypass)
{
wasByPassed = vec3Output.TrySetBypassToInput(vec3Input);
}
else
{
vec3Output.RestoreUpdateAction();
}
if (invalidate)
InvalidateConnected(vec3Input);
break;
case Slot<string> stringOutput when mainInputSlot is Slot<string> stringInput:
if (shouldBypass)
{
wasByPassed = stringOutput.TrySetBypassToInput(stringInput);
}
else
{
stringOutput.RestoreUpdateAction();
}
if (invalidate)
InvalidateConnected(stringInput);
break;
}
if(wasByPassed)
instance._status |= InstanceStatus.Bypassed;
else
instance._status &= ~InstanceStatus.Bypassed;
return wasByPassed;
static void InvalidateConnected<T>(Slot<T> bufferInput)
{
if (bufferInput.TryGetAsMultiInputTyped(out var multiInput))
{
foreach (var connection in multiInput.CollectedInputs)
{
InvalidateParentInputs(connection);
}
}
else
{
var connection = bufferInput.FirstConnection;
InvalidateParentInputs(connection);
}
return;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void InvalidateParentInputs(ISlot connection)
{
if (connection.ValueType == typeof(string)) // TODO: clarify and comment why whe need this check
return;
connection.DirtyFlag.Invalidate();
}
}
}
private void MarkNeedsConnections()
{
_status &= ~InstanceStatus.ConnectedInternally;
if (TryGetParentInstance(out var parent, false))
{
parent.MarkNeedsConnections();
}
}
}