Files
tooll3--t3/Editor/UiModel/ProjectHandling/ProjectView.InstanceView.cs
T
2026-07-13 13:13:17 +08:00

177 lines
6.4 KiB
C#

#nullable enable
using System.Runtime.CompilerServices;
using T3.Core.Operator;
namespace T3.Editor.UiModel.ProjectHandling;
internal sealed partial class ProjectView
{
/// <summary>
/// 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.
/// </summary>
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<Guid> _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;
/// <summary>
/// The symbol Ui of the instance this composition refers to
/// </summary>
public SymbolUi SymbolUi
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => Symbol.GetSymbolUi();
}
/// <summary>
/// The symbol of the instance this composition refers to
/// </summary>
/// <exception cref="Exception"></exception>
public Symbol Symbol
{
get
{
if (!SymbolRegistry.TryGetSymbol(_symbolId, out var symbol))
{
throw new Exception($"Could not find symbol with id {_symbolId}");
}
return symbol;
}
}
/// <summary>
/// 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.
/// </summary>
public bool IsValid
{
get
{
try
{
return Instance != null!;
}
catch
{
return false;
}
}
}
/// <summary>
/// Returns the instance this composition refers to
/// </summary>
/// <exception cref="Exception"></exception>
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);
}
}
/// <summary>
/// A key for looking up the relevant <see cref="InstanceView"/> 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
/// </summary>
/// <param name="SymbolChildId">The <see cref="Symbol.Child.Id"/> of this instance reference</param>
/// <param name="ParentSymbolId">The symbol id of the parent, if it exists</param>
private readonly record struct InstanceViewKey(Guid SymbolChildId, Guid? ParentSymbolId);
private static readonly Dictionary<InstanceViewKey, InstanceView> _viewsBySymbolChildId = new();
}
}