Files
2026-07-13 13:15:05 +08:00

368 lines
12 KiB
C#

#nullable enable
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using T3.Core.Logging;
using T3.Core.Model;
using T3.Core.Operator.Slots;
using T3.Core.Resource;
using T3.Core.Settings;
namespace T3.Core.Operator;
public abstract partial class Instance : IGuidPathContainer, IResourceConsumer
{
public abstract Type Type { get; }
public Guid SymbolChildId => SymbolChild.Id;
public InstanceChildren Children { get; private set; }
public event Action<IResourceConsumer>? Disposing;
public abstract Symbol Symbol { get; }
public Symbol.Child SymbolChild { get; private set; }
public readonly List<ISlot> Outputs;
public readonly List<IInputSlot> Inputs;
public IReadOnlyList<Guid> InstancePath { get; private set; }
public IReadOnlyList<IResourcePackage> AvailableResourcePackages
{
get
{
GatherAvailableResourcePackages(this, ref _availableResourcePackages);
return _availableResourcePackages;
}
}
public IReadOnlyList<IResourcePackage> LocalResourcePackages
{
get
{
GatherLocalResourcePackages(this, ref _availableResourcePackages);
return _availableResourcePackages;
}
}
private Guid[]? _parentPath;
private Symbol.Child? _parentSymbolChild;
private int? _pathHash;
private List<SymbolPackage> _availableResourcePackages = [];
private readonly List<ISlot> _outputs = [];
private readonly List<IInputSlot> _inputs = [];
protected Instance()
{
Outputs = _outputs;
Inputs = _inputs;
Children = null!;
SymbolChild = null!;
InstancePath = [];
}
internal void SetSymbolInfo(Symbol.Child child, Symbol.Child? parent, IReadOnlyList<Guid> instancePath, int pathHash)
{
if(SymbolChild != null)
throw new InvalidOperationException("Instance already has a symbol child");
if (instancePath.Count > 1)
{
_parentPath = new Guid[instancePath.Count - 1];
for (var i = 0; i < _parentPath.Length; i++)
{
_parentPath[i] = instancePath[i];
}
}
InstancePath = instancePath;
_pathHash = pathHash;
SymbolChild = child;
_parentSymbolChild = parent;
Children = new InstanceChildren(instancePath, child);
}
internal void Initialize(Instance? parentInstance)
{
SortInputSlotsByDefinitionOrder(this);
Debug.Assert(_status == default);
parentInstance ??= Parent;
if (parentInstance is { Initialized: true, IsReconnecting: false })
{
// mark parent as needing reconnections if it's already initialized
parentInstance.MarkNeedsConnections();
}
_status |= InstanceStatus.ResourceFoldersDirty; // we need to mark resource folders as dirty, so they are loaded by ops that need them
foreach (var childInstance in Children.PreExistingValues)
{
childInstance.MarkResourceFoldersDirty();
}
ReconnectChildren();
_status |= InstanceStatus.Initialized;
_status |= InstanceStatus.Active;
}
private void MarkResourceFoldersDirty()
{
_status |= InstanceStatus.ResourceFoldersDirty;
foreach(var child in Children.PreExistingValues)
{
child.MarkResourceFoldersDirty();
}
}
public Instance? Parent
{
get
{
// a procedural lookup from the parent,
// since we cannot count on it existing at all times (particularly during loading or unloading)
if (_parentSymbolChild == null)
{
return null;
}
if (_parentPath == null)
throw new InvalidOperationException($"Parent path is not initialized.");
if(_parentSymbolChild.TryGetOrCreateInstance(_parentPath, out var parentInstance, out _))
{
return parentInstance;
}
Log.Error($"Could not find parent instance for {_parentSymbolChild} with path {_parentPath}");
return null;
}
}
internal bool TryGetParentInstance([NotNullWhen(true)] out Instance? instance, bool allowCreate)
{
if (_parentSymbolChild == null || _parentPath == null)
{
instance = null;
return false;
}
if(_parentSymbolChild!.TryGetOrCreateInstance(_parentPath, out var parentInstance, out _, allowCreate))
{
instance = parentInstance;
return true;
}
instance = null;
return false;
}
SymbolPackage IResourceConsumer.Package => Symbol.SymbolPackage;
/// <summary>
/// get input without GC allocations
/// </summary>
public IInputSlot? GetInput(Guid guid)
{
//return Inputs.SingleOrDefault(input => input.Id == guid);
var inputCount = Inputs.Count;
for (var index = 0; index < inputCount; index++)
{
var i = Inputs[index];
if (i.Id == guid)
return i;
}
return null;
}
~Instance()
{
if ((_status & InstanceStatus.Disposed) == 0)
{
Log.Error($"Instance {GetType()} was not disposed properly");
// todo : do we want to invoke disposal here? could this prevent memory leaks or other shutdown/reload issues?
// would it cause thread safety issues? probably..
// but currently this is using a disposal pattern designed around finalizers (disposing = true), etc
}
}
internal void DisposePackage(SymbolPackage? instancePackage)
{
if ((_status & InstanceStatus.Disposed) != 0)
{
throw new Exception($"{this} has already been disposed\n" + Environment.StackTrace);
}
_status &= ~InstanceStatus.Active; // remove active status
Children.DisposePackageInChildren(instancePackage);
// clear connections - they need to be repopulated with valid connections next time
// we don't clear outputs as they are solely connected to by internal instances
var disconnectCount = DisconnectInputs();
// we should let the parent know it's missing someone
if (disconnectCount != 0 && TryGetParentInstance(out var parentInstance, false))
{
parentInstance.MarkNeedsConnections();
}
// if a package was specified that is not our own, we can live to see another day
if (instancePackage != null && Symbol.SymbolPackage != instancePackage)
{
return;
}
_status |= InstanceStatus.Disposed;
try
{
Dispose(true);
}
catch (Exception e)
{
Log.Error($"Error disposing instance {this}: {e}");
}
// and remove ourselves from the symbol child
SymbolChild.RemoveDisposedInstance(this, _pathHash!.Value);
// any resources we have should be disposed of by the child class
try
{
Disposing?.Invoke(this);
}
catch (Exception e)
{
Log.Error($"Error on dispose event for {this}: {e}");
}
// warning CA1816: Dispose methods should call SuppressFinalize on themselves
// it doesn't recognize this as a dispose method due to its argument type
#pragma warning disable CA1816
GC.SuppressFinalize(this);
#pragma warning restore CA1816
}
/// <summary>
/// Collect the available resource packages for this instance. This refers to resource paths
/// that can be picked (e.g. in asset selectors) without injecting dependencies.
///
/// For Resources that could be loaded (e.g.
/// </summary>
/// <remarks>
/// An instance may use resources from:
/// - all resources it depends on AND its parent
///
/// Some examples for a complex nesting structure:
///
/// [SomeProject.SomeProject] > [SomeProject.SomeScene] > [Examples.SomeDemo] > [Lib.SomeEffect] > [Lib.SomeOp]
/// (proj+examples+lib) (proj+examples+lib) (proj+examples+lib) (examples+lib) (lib)
///
/// So "lib" should not use Examples.
/// So "Examples" should not use "Project".
/// </remarks>
private static void GatherLocalResourcePackages(Instance instance, ref List<SymbolPackage> resourcePackages)
{
if((instance!._status & InstanceStatus.ResourceFoldersDirty) == 0)
return;
instance._status &= ~InstanceStatus.ResourceFoldersDirty;
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (resourcePackages != null)
{
resourcePackages.Clear();
}
else
{
resourcePackages = [];
}
// Add self
HashSet<SymbolPackage> packageSet = [ instance.Symbol.SymbolPackage ];
// Add parent's resources if different
if(instance.Parent?.Symbol.SymbolPackage != null && instance.Parent.Symbol.SymbolPackage != instance.Symbol.SymbolPackage)
packageSet.Add(instance.Parent.Symbol.SymbolPackage);
var isParentLib = instance.Parent != null && instance.Parent.Symbol.SymbolPackage.DoNotIncludedSharedPackages;
// Add shared resources if allowed
if (!instance.Symbol.SymbolPackage.DoNotIncludedSharedPackages
&& !isParentLib)
{
foreach (var somePackage in SymbolPackage.AllPackages)
{
if (!somePackage.IsSharingResources)
continue;
packageSet.Add(somePackage);
}
}
resourcePackages.AddRange(packageSet);
// Walk up parents.
// while (instance != null)
// {
// var package = instance.Symbol.SymbolPackage;
// if(!resourcePackages.Contains(package))
// resourcePackages.Add(package);
//
// instance = instance.Parent;
// }
}
/// <summary>
/// Collect all resources that could be loaded by the instance.
/// (This could include resources through parents.)
/// </summary>
private static void GatherAvailableResourcePackages(Instance instance, ref List<SymbolPackage> resourcePackages)
{
if((instance!._status & InstanceStatus.ResourceFoldersDirty) == 0)
return;
instance._status &= ~InstanceStatus.ResourceFoldersDirty;
// ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract
if (resourcePackages != null)
{
resourcePackages.Clear();
}
else
{
resourcePackages = [];
}
HashSet<SymbolPackage> packageSet = [];
var structureItem = instance;
// Walk up parents.
while (structureItem != null)
{
packageSet.Add(structureItem.Symbol.SymbolPackage);
structureItem = structureItem.Parent;
}
// Add shared resources if allowed
{
foreach (var somePackage in SymbolPackage.AllPackages)
{
if (!somePackage.IsSharingResources)
continue;
packageSet.Add(somePackage);
}
}
resourcePackages.AddRange(packageSet);
}
private string? _asString;
}