#nullable enable using System.Runtime.CompilerServices; using T3.Core.Operator; namespace T3.Editor.UiModel.ProjectHandling; internal sealed partial class ProjectView { /// /// This class contains logic for the sake of repairing read-only symbols after user modification. It keeps a pool of these objects to avoid garbage, /// and a reference count per object, without caching the instance itself for compatibility with runtime compilation. /// /// In effect, this class only really stores the instance child ID, and its parent symbol Id if it exists. /// public sealed class InstanceView : IDisposable { // runtime-cached fields taken from the instance public readonly Guid SymbolChildId; // we don't expose these publicly so the Symbol must be fetched, ensuring correctness of any retrieved symbol Id private readonly Guid _symbolId; private readonly Symbol? _parentSymbol; private readonly IReadOnlyList _instancePath; // true if the symbol's package is read-only (i.e. not an editable project) public bool IsReadOnly { get; } public int CheckoutCount { get; private set; } public bool HasBeenModified => SymbolUi.HasBeenModified; // runtime-computed fields for tracking references of this composition private bool _disposed; /// /// The symbol Ui of the instance this composition refers to /// public SymbolUi SymbolUi { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => Symbol.GetSymbolUi(); } /// /// The symbol of the instance this composition refers to /// /// public Symbol Symbol { get { if (!SymbolRegistry.TryGetSymbol(_symbolId, out var symbol)) { throw new Exception($"Could not find symbol with id {_symbolId}"); } return symbol; } } /// /// After recompilation (e.g. after adding a new parameter) the Instance can't be /// inferred from the parent because it has been discarded and the parent has no /// more instances of self. This would lead to an exception in the Instance property. /// /// Because we want the Instance to be not-null, testing this with the try catch is /// somewhat difficult. This is a work-around, but there are probably better ways to /// do that. /// public bool IsValid { get { try { return Instance != null!; } catch { return false; } } } /// /// Returns the instance this composition refers to /// /// public Instance Instance { get { if(Symbol.TryGetOrCreateInstance(_instancePath, _parentSymbol, out var instance)) { return instance; } throw new Exception($"Could not find instance with id {_symbolId} and child id {SymbolChildId}. Symbol type: \"{Symbol.InstanceType}\""); } } private InstanceView(Instance instance) { _instancePath = instance.InstancePath; var parent = instance.Parent; if (parent != null) { _parentSymbol = parent.Symbol; } SymbolChildId = instance.SymbolChildId; var symbol = instance.Symbol; IsReadOnly = symbol.SymbolPackage.IsReadOnly; _symbolId = symbol.Id; } internal static InstanceView GetForInstance(Instance instance) { if (instance.IsDisposed) { throw new InvalidOperationException("Cannot get composition for disposed instance."); } InstanceView? composition; var childId = instance.SymbolChildId; var key = new InstanceViewKey(childId, instance.Parent?.Symbol.Id); lock (_viewsBySymbolChildId) { if (!_viewsBySymbolChildId.TryGetValue(key, out composition)) { composition = new InstanceView(instance); _viewsBySymbolChildId[key] = composition; } composition.CheckoutCount++; } return composition; } public bool Is(Instance newCompositionOp) { var othersKey = new InstanceViewKey(newCompositionOp.SymbolChildId, newCompositionOp.Parent?.Symbol.Id); var myKey = new InstanceViewKey(SymbolChildId, _parentSymbol?.Id); return othersKey == myKey; } public void Dispose() { CheckoutCount--; if (CheckoutCount > 0) return; if (_disposed) throw new Exception("Composition already disposed."); _disposed = true; lock (_viewsBySymbolChildId) { var key = new InstanceViewKey(SymbolChildId, _parentSymbol?.Id); _viewsBySymbolChildId.Remove(key); } } /// /// A key for looking up the relevant object /// Requires the parent symbol ID as a symbol can have multiple live instances with the same SymbolChildId if they belong to multiple /// instances of the same parent /// /// The of this instance reference /// The symbol id of the parent, if it exists private readonly record struct InstanceViewKey(Guid SymbolChildId, Guid? ParentSymbolId); private static readonly Dictionary _viewsBySymbolChildId = new(); } }