acdream/src/AcDream.App/Rendering/Vfx/EntityEffectAdvanceSource.cs

86 lines
2.3 KiB
C#

namespace AcDream.App.Rendering.Vfx;
internal interface IEntityEffectAdvanceSource
{
bool CanAdvanceOwner(uint ownerLocalId);
}
/// <summary>
/// Focused late-binding edge between the Phase-2 PhysicsScript runner and the
/// Phase-6 live effect authority. Before live presentation exists, scripts are
/// unconstrained exactly as the former null-safe callback was; after binding,
/// the canonical effect owner decides whether its current pose can advance.
/// </summary>
internal sealed class DeferredEntityEffectAdvanceSource : IEntityEffectAdvanceSource
{
private readonly object _gate = new();
private IEntityEffectAdvanceSource? _target;
private bool _deactivated;
public void Bind(IEntityEffectAdvanceSource target)
{
ArgumentNullException.ThrowIfNull(target);
lock (_gate)
{
ObjectDisposedException.ThrowIf(_deactivated, this);
if (_target is not null && !ReferenceEquals(_target, target))
{
throw new InvalidOperationException(
"Entity-effect script advancement is already bound.");
}
_target = target;
}
}
public IDisposable BindOwned(IEntityEffectAdvanceSource target)
{
Bind(target);
return new Binding(this, target);
}
public void Unbind(IEntityEffectAdvanceSource target)
{
ArgumentNullException.ThrowIfNull(target);
lock (_gate)
{
if (ReferenceEquals(_target, target))
_target = null;
}
}
public void Deactivate()
{
lock (_gate)
{
_deactivated = true;
_target = null;
}
}
public bool CanAdvanceOwner(uint ownerLocalId)
{
lock (_gate)
{
return _deactivated
|| _target?.CanAdvanceOwner(ownerLocalId) != false;
}
}
private sealed class Binding : IDisposable
{
private DeferredEntityEffectAdvanceSource? _owner;
private readonly IEntityEffectAdvanceSource _target;
public Binding(
DeferredEntityEffectAdvanceSource owner,
IEntityEffectAdvanceSource target)
{
_owner = owner;
_target = target;
}
public void Dispose() =>
Interlocked.Exchange(ref _owner, null)?.Unbind(_target);
}
}