#nullable enable using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using T3.Core.Logging; using T3.Core.Model; namespace T3.Core.Operator; // TODO: Making this a IReadOnlyDictionary and causes excessive GC allocations. It's cleaned but causes frame drops on larger graphs. public sealed class InstanceChildren : IReadOnlyDictionary { /** The symbol child of the instance this collection belongs to */ private readonly Symbol.Child _asChild; /** A cached search path to avoid creating new arrays for each child lookup */ private readonly Guid[] _childSearchPath; public int Count => _asChild.Symbol.Children.Count; internal InstanceChildren(IReadOnlyList instancePath, Symbol.Child symbolChild) { _asChild = symbolChild; _childSearchPath = new Guid[instancePath.Count + 1]; for (var i = 0; i < instancePath.Count; i++) { _childSearchPath[i] = instancePath[i]; } _childSearchPath[^1] = Guid.Empty; } /* Dictionary accessor overload */ public Instance this[Guid sourceParentOrChildId] { get { if (TryGetChildInstance(sourceParentOrChildId, out var sourceInstance)) return sourceInstance; throw new KeyNotFoundException($"Child with id {sourceParentOrChildId} not found in {this}"); } } IEnumerable IReadOnlyDictionary.Keys { get { foreach (var child in _asChild.Symbol.Children) { if (TryGetChildInstance(child.Key, out var instance)) { yield return child.Key; } else { Log.Error($"Child {child.Key} not found in {this}"); } } } } public IEnumerable Values { get { if(_asChild.Symbol.Children.Count == 0) yield break; foreach (var child in _asChild.Symbol.Children.Values) { if (TryGetChildInstance(child.Id, out var instance)) { yield return instance; } else { Log.Error($"Child {child.Id} not found in {this}"); } } } } internal IEnumerable PreExistingValues { get { if (_asChild.Symbol.Children.Count == 0) yield break; foreach (var child in _asChild.Symbol.Children.Values) { if (TryGetChildInstance(child.Id, out var instance, allowCreate: false)) { yield return instance; } } } } public void GetExistingCounts(out int count, out int existingCount, out Instance.InstanceStatus childFlags, out int nestedCount, out int nestedExistingCount, out Instance.InstanceStatus nestedFlags) { count = Count; existingCount = 0; nestedCount = count; nestedExistingCount = 0; childFlags = default; nestedFlags = default; foreach (var child in _asChild.Symbol.Children.Values) { if (!TryGetChildInstance(child.Id, out var instance, allowCreate: false)) { continue; } childFlags |= instance.Status; existingCount++; nestedExistingCount++; instance.Children.GetExistingCounts(out _, out _, out var childFlag, out var childTotalCount, out var childTotalExistingCounts, out var flags); nestedCount += childTotalCount; nestedExistingCount += childTotalExistingCounts; nestedFlags |= flags | childFlag; } } bool IReadOnlyDictionary.ContainsKey(Guid key) { return _asChild.Symbol.Children.ContainsKey(key); } bool IReadOnlyDictionary.TryGetValue(Guid key, [NotNullWhen(true)] out Instance? value) { if (TryGetChildInstance(key, out value)) return true; value = null; return false; } public bool TryGetChildInstance(Guid childId, [NotNullWhen(true)] out Instance? instance, bool allowCreate = true) { var symbol = _asChild.Symbol; if (!symbol.Children.TryGetValue(childId, out var sourceChild)) { // This happens frequently after deleting a child and some views like console or output are trying to access it. //Log.Error($"{_asChild} failed to find child : {childId}"); instance = null; return false; } lock (_childSearchPath) // prevent concurrent modifications to the search path { _childSearchPath[^1] = childId; if (sourceChild.TryGetOrCreateInstance(_childSearchPath, out instance, out _, allowCreate)) { return true; } } if (allowCreate) { Log.Error($"{_asChild} Failed to find or create child instance : {childId}"); } return false; } internal void DisposePackageInChildren(SymbolPackage? packageToDispose) { lock (_childSearchPath) { foreach (var child in _asChild.Symbol.Children.Values) { var childId = child.Id; _childSearchPath[^1] = childId; if (child.TryGetOrCreateInstance(_childSearchPath, out var inst, out _, false)) { inst.DisposePackage(packageToDispose); } } } } public IEnumerator> GetEnumerator() { foreach (var child in _asChild.Symbol.Children) { if (TryGetChildInstance(child.Key, out var instance)) { yield return new KeyValuePair(child.Key, instance); } else { Log.Error($"Child {child.Key} not found in {this}"); } } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } }