Files
2026-07-13 13:13:17 +08:00

412 lines
14 KiB
C#

#nullable enable
using System.Diagnostics.CodeAnalysis;
using T3.Core.Animation;
using T3.Core.Operator;
using T3.Core.Operator.Slots;
// ReSharper disable ForCanBeConvertedToForeach
namespace T3.Editor.UiModel.ProjectHandling;
/// <summary>
/// each GraphWindow class should have its own Structure object, and some of this logic should
/// be moved out where applicable (i.e. dealing with packages, selecting root instances, etc.)
/// </summary>
internal sealed class Structure
{
private readonly Func<Symbol.Child> _getRootAction;
public Structure(Func<Symbol.Child> getRootAction)
{
_getRootAction = getRootAction;
}
public Instance? GetRootInstance()
{
return GetInstanceFromIdPath([_getRootAction().Id]);
}
public Instance? GetInstanceFromIdPath(IReadOnlyList<Guid>? childPath)
{
if (childPath == null || childPath.Count == 0)
{
return null;
}
Symbol.Child? rootSymbolChild = _getRootAction();
if (rootSymbolChild == null)
{
Log.Error("Root does not exist? \n" + Environment.StackTrace);
return null;
}
if (childPath[0] != rootSymbolChild.Id)
{
//Log.Warning("Can't access instance after root changed.\n" + Environment.StackTrace);
return null;
}
var current = rootSymbolChild;
for (int i = 1; i < childPath.Count; i++)
{
if (!current.Symbol.Children.TryGetValue(childPath[i], out current))
{
// Commented out, because this frequently happens after deleting pinned Instance
//Log.Error("Did not find child in path provided.\n" + Environment.StackTrace);
return null;
}
}
_ = current.TryGetOrCreateInstance(childPath, out var instance, out _);
return instance;
}
public List<string> GetReadableInstancePath(IReadOnlyList<Guid>? path, bool includeLeave = true)
{
if (path == null || (includeLeave && path.Count == 0) || (!includeLeave && path.Count == 1))
return ["Path empty"];
var instance = GetInstanceFromIdPath(path);
if (instance == null)
return ["Path invalid"];
var newList = new List<string>();
var isFirst = true;
while (true)
{
var parent = instance.Parent;
if (parent == null)
{
break;
}
if (!includeLeave && isFirst)
{
isFirst = false;
instance = parent;
continue;
}
isFirst = false;
var parentSymbolUi = parent.GetSymbolUi();
var childUisWithThatType = parentSymbolUi.ChildUis.Values
.Where(c => c.SymbolChild.Symbol == instance.Symbol)
.ToList();
var indexLabel = "";
var symbolUiChild = childUisWithThatType.Single(c => c.Id == instance.SymbolChildId);
if (childUisWithThatType.Count > 1)
{
var index = childUisWithThatType.IndexOf(symbolUiChild);
indexLabel = $"#{index}";
}
var readableNameSuffice = !string.IsNullOrEmpty(symbolUiChild.SymbolChild.Name)
? $" ({symbolUiChild.SymbolChild.Name})"
: "";
newList.Insert(0, instance.Symbol.Name + indexLabel + readableNameSuffice);
instance = parent;
}
return newList;
}
public static TimeClip? GetCompositionTimeClip(Instance? compositionOp)
{
if (compositionOp == null)
{
Log.Error("Can't get time clip from null composition op");
return null;
}
foreach (var clipProvider in compositionOp.Outputs.OfType<ITimeClipProvider>())
{
return clipProvider.TimeClip;
}
return null;
}
/// <summary>
/// This is slow and should be refactored into something else
/// </summary>
public static IEnumerable<TimeClip> GetAllTimeClips(Instance compositionOp)
{
foreach (var child in compositionOp.Children.Values)
{
var outputs = child.Outputs;
for (var i = 0; i < outputs.Count; i++)
{
if (outputs[i] is ITimeClipProvider clipProvider)
{
yield return clipProvider.TimeClip;
}
}
}
}
public static bool TryGetUiAndInstanceInComposition(Guid id,
Instance compositionOp,
[NotNullWhen(true)] out SymbolUi.Child? childUi,
[NotNullWhen(true)] out Instance? instance)
{
if (!compositionOp.Children.TryGetChildInstance(id, out instance))
{
Log.Warning($"Failed to get instance for {id} in {compositionOp.Symbol.Name}");
childUi = null;
return false;
}
childUi = instance.SymbolChild.GetChildUi();
return true;
}
public static IEnumerable<Symbol> CollectDependingSymbols(Symbol symbol)
{
var symbolId = symbol.Id;
foreach (var s in EditorSymbolPackage.AllSymbols)
{
foreach (var child in s.Children.Values)
{
if (child.Symbol.Id != symbolId)
continue;
yield return s;
break;
}
}
}
internal static HashSet<Guid> CollectConnectedChildren(Symbol.Child child, Symbol compositionSymbol, HashSet<Guid>? set = null)
{
set ??= [];
set.Add(child.Id);
//var compositionSymbol = composition.Symbol;
var connectedChildren = (from con in compositionSymbol.Connections
where !con.IsConnectedToSymbolInput && !con.IsConnectedToSymbolOutput
from sourceChild in compositionSymbol.Children.Values
where con.SourceParentOrChildId == sourceChild.Id
&& con.TargetParentOrChildId == child.Id
select sourceChild).Distinct().ToArray();
foreach (var connectedChild in connectedChildren)
{
set.Add(connectedChild.Id);
CollectConnectedChildren(connectedChild, compositionSymbol, set);
}
return set;
}
internal static HashSet<Guid> CollectConnectedChildrenOut(Symbol.Child child, Symbol compositionSymbol, HashSet<Guid>? set = null)
{
set ??= [];
set.Add(child.Id);
var connectedChildren = (from con in compositionSymbol.Connections
where !con.IsConnectedToSymbolInput && !con.IsConnectedToSymbolOutput
from targetChild in compositionSymbol.Children.Values
where (con.SourceParentOrChildId == child.Id
&& con.TargetParentOrChildId ==targetChild.Id )
select targetChild).Distinct();
foreach (var connectedChild in connectedChildren)
{
set.Add(connectedChild.Id);
CollectConnectedChildrenOut(connectedChild, compositionSymbol, set);
}
return set;
}
internal static void CollectConnectedChildIds(Symbol compositionSymbol, List<Symbol.Child> selectedChildren, HashSet<Guid>? connectedIds)
{
connectedIds ??= [];
foreach (var selected in selectedChildren)
{
connectedIds.Add(selected.Id);
}
while(true)
{
var foundNew = false;
foreach (var c in compositionSymbol.Connections)
{
var connectedInput = !c.IsConnectedToSymbolInput && connectedIds.Contains(c.TargetParentOrChildId);
var connectedOutput = !c.IsConnectedToSymbolOutput && connectedIds.Contains(c.SourceParentOrChildId);
if ((connectedInput || connectedOutput) && (connectedInput != connectedOutput))
{
foundNew |= connectedIds.Add(!connectedInput ? c.TargetParentOrChildId : c.SourceParentOrChildId);
}
}
if (!foundNew)
break;
}
}
/// <summary>
/// Scan all slots required for updating a Slot.
/// This can be used for invalidation and cycle checking.
/// </summary>
private static HashSet<ISlot> CollectSlotDependencies(ISlot slot, HashSet<ISlot>? all = null)
{
all ??= [];
var stack = new Stack<ISlot>();
stack.Push(slot);
while (stack.Count > 0)
{
var currentSlot = stack.Pop();
if (!all.Add(currentSlot))
continue;
if (currentSlot.TryGetFirstConnection(out var firstConnection))
{
stack.Push(firstConnection);
}
else
{
var op = currentSlot.Parent;
var opInputs = op.Inputs;
for (int i = 0; i < opInputs.Count; i++)
{
var input = opInputs[i];
// Skip if not connected
if (!input.TryGetFirstConnection(out var connectedCompInputSlot))
continue;
if (input.TryGetAsMultiInput(out var multiInput))
{
var collectedInputs = multiInput.GetCollectedInputs();
for (var j = 0; j < collectedInputs.Count; j++)
{
stack.Push(collectedInputs[j]);
}
}
else
{
stack.Push(connectedCompInputSlot);
}
}
}
}
return all;
}
/** Returns true if connecting the outputSlot to an input of the op with a symbolChildId would result in a cycle */
internal static bool CheckForCycle(ISlot outputSlot, Guid targetOpId)
{
var linkedSlots = CollectSlotDependencies(outputSlot);
foreach (var linkedSlot in linkedSlots)
{
if (linkedSlot.Parent.SymbolChildId != targetOpId)
continue;
return true;
}
return false;
}
/** Returns true if connecting the outputSlot to an input of the op with a symbolChildId would result in a cycle */
internal static bool CheckForCycle(Instance sourceInstance, Guid targetOpId)
{
var linkedSlots = new HashSet<ISlot>();
foreach (var inputSlot in sourceInstance.Inputs)
{
CollectSlotDependencies(inputSlot, linkedSlots);
}
//var linkedSlots = CollectSlotDependencies(outputSlot);
foreach (var linkedSlot in linkedSlots)
{
if (linkedSlot.Parent.SymbolChildId != targetOpId)
continue;
return true;
}
return false;
}
internal static bool CheckForCycle(Symbol compositionSymbol, Symbol.Connection connection)
{
var dependingSourceItemIds = new HashSet<Guid>();
CollectDependentChildren(connection.SourceParentOrChildId);
return dependingSourceItemIds.Contains(connection.TargetParentOrChildId);
void CollectDependentChildren(Guid sourceChildId)
{
if (sourceChildId == Guid.Empty || !dependingSourceItemIds.Add(sourceChildId))
return;
// find all connections into child...
foreach (var c in compositionSymbol.Connections)
{
if (sourceChildId == Guid.Empty || c.TargetParentOrChildId != sourceChildId)
continue;
CollectDependentChildren(c.SourceParentOrChildId);
}
}
}
public static IEnumerable<Instance> CollectParentInstances(Instance compositionOp)
{
var parents = new List<Instance>();
var op = compositionOp;
while (op.Parent != null)
{
op = op.Parent;
parents.Insert(0, op);
}
return parents;
}
public static void PopulateInstancePath(Instance instance, IList<Guid> path)
{
path.Insert(0, instance.SymbolChildId);
var parent = instance.Parent;
while (parent != null)
{
path.Insert(0, parent.SymbolChildId);
parent = parent.Parent;
}
}
public static bool TryGetInstanceFromPath(IReadOnlyList<Guid> entrySourceIdPath, out Instance? hoveredSourceInstance,
out List<string> readableInstancePath)
{
var hasInstancePath = entrySourceIdPath.Count > 0;
var focusedView = ProjectView.Focused;
var childIdPath = entrySourceIdPath.ToList();
hoveredSourceInstance = hasInstancePath && focusedView != null
? focusedView.Structure.GetInstanceFromIdPath(childIdPath)
: null;
readableInstancePath = hoveredSourceInstance != null ? focusedView!.Structure.GetReadableInstancePath(childIdPath) : [];
return hasInstancePath;
}
}