#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; /// /// 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.) /// internal sealed class Structure { private readonly Func _getRootAction; public Structure(Func getRootAction) { _getRootAction = getRootAction; } public Instance? GetRootInstance() { return GetInstanceFromIdPath([_getRootAction().Id]); } public Instance? GetInstanceFromIdPath(IReadOnlyList? 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 GetReadableInstancePath(IReadOnlyList? 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(); 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()) { return clipProvider.TimeClip; } return null; } /// /// This is slow and should be refactored into something else /// public static IEnumerable 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 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 CollectConnectedChildren(Symbol.Child child, Symbol compositionSymbol, HashSet? 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 CollectConnectedChildrenOut(Symbol.Child child, Symbol compositionSymbol, HashSet? 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 selectedChildren, HashSet? 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; } } /// /// Scan all slots required for updating a Slot. /// This can be used for invalidation and cycle checking. /// private static HashSet CollectSlotDependencies(ISlot slot, HashSet? all = null) { all ??= []; var stack = new Stack(); 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(); 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(); 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 CollectParentInstances(Instance compositionOp) { var parents = new List(); var op = compositionOp; while (op.Parent != null) { op = op.Parent; parents.Insert(0, op); } return parents; } public static void PopulateInstancePath(Instance instance, IList 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 entrySourceIdPath, out Instance? hoveredSourceInstance, out List 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; } }