refactor(app): compose content effects and audio startup

Move Phase-2 startup behind typed composition/publication boundaries, replace the GameWindow-capturing PhysicsScript gate with a focused deferred source, and own animation-hook registrations reversibly. Make OpenAL construction and teardown transactional so every device, context, source, and buffer prefix is retryable without replay.
This commit is contained in:
Erik 2026-07-22 15:36:05 +02:00
parent 1d51e35c14
commit 60a1698ce7
12 changed files with 1901 additions and 152 deletions

View file

@ -0,0 +1,63 @@
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 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;
}
}
}

View file

@ -22,7 +22,8 @@ namespace AcDream.App.Rendering.Vfx;
/// (<c>0x00513260</c>) and both <c>play_default_script</c> overloads
/// (<c>0x005132B0</c>, <c>0x00513300</c>).
/// </remarks>
public sealed class EntityEffectController : IAnimationHookSink
public sealed class EntityEffectController : IAnimationHookSink,
IEntityEffectAdvanceSource
{
private readonly LiveEntityRuntime _liveEntities;
private readonly PhysicsScriptRunner _runner;