perf(streaming): quiesce retired generations and budget teardown

Publish the retail blocking-for-cells edge before deferred recenter work, freeze old-world presentation/simulation/audio, and advance full-window retirement from exact metered entity and owner cursors. This removes synchronous portal teardown without allowing retained owners to remain observable.
This commit is contained in:
Erik 2026-07-24 18:29:52 +02:00
parent b8f6317fe1
commit bb16f74fd4
38 changed files with 1691 additions and 170 deletions

View file

@ -122,7 +122,14 @@ public sealed class AudioHookSink : IAnimationHookSink
if (waveId == 0) return;
WaveData? wave = _cache.GetWave(waveId);
if (wave is null) return;
_engine.Play3DWave(waveId, wave, worldPos, volume, priority, pitch);
_engine.Play3DWave(
entityId,
waveId,
wave,
worldPos,
volume,
priority,
pitch);
}
}

View file

@ -54,7 +54,13 @@ namespace AcDream.App.Audio;
/// unaffected.
/// </para>
/// </summary>
public sealed unsafe class OpenAlAudioEngine : IAudioEngine
internal interface IWorldAudioQuiescence
{
void SuspendWorldAudio();
void ResumeWorldAudio();
}
public sealed unsafe class OpenAlAudioEngine : IAudioEngine, IWorldAudioQuiescence
{
// ── Backends ─────────────────────────────────────────────────────────────
private AL? _al;
@ -71,12 +77,14 @@ public sealed unsafe class OpenAlAudioEngine : IAudioEngine
private sealed class Slot3D
{
public uint SourceId;
public uint OwnerId;
public float PlayingGain; // gain at play time (for eviction compare)
public bool InUse;
public uint PriorityBase; // raw priority from SoundEntry.Priority
}
private readonly Slot3D[] _pool3D = new Slot3D[PoolSize3D];
private int _pool3DCursor; // round-robin start
private bool _worldAudioSuspended;
private readonly uint[] _poolUi = new uint[PoolSizeUi];
@ -238,6 +246,7 @@ public sealed unsafe class OpenAlAudioEngine : IAudioEngine
/// Returns true on success, false if the buffer was rejected.
/// </summary>
public bool Play3DWave(
uint ownerId,
uint waveId,
WaveData wave,
Vector3 position,
@ -245,7 +254,7 @@ public sealed unsafe class OpenAlAudioEngine : IAudioEngine
float priority,
float pitch = 1.0f)
{
if (!_available || _al is null) return false;
if (_worldAudioSuspended || !_available || _al is null) return false;
float effectiveGain = volume * SfxVolume;
if (effectiveGain < 0.001f) return false; // silent; skip
@ -284,11 +293,39 @@ public sealed unsafe class OpenAlAudioEngine : IAudioEngine
slot.PlayingGain = effectiveGain;
slot.InUse = true;
slot.OwnerId = ownerId;
slot.PriorityBase = (uint)Math.Clamp((int)priority, 0, 7);
_pool3DCursor = (slotIdx + 1) & (PoolSize3D - 1);
return true;
}
/// <summary>
/// Stops every world-space voice while preserving the independent UI
/// source pool. Retail suppresses ambient/object audio while cell loading
/// blocks world maintenance; stopped voices are not resumed afterward.
/// </summary>
public void SuspendWorldAudio()
{
_worldAudioSuspended = true;
for (int i = 0; i < _pool3D.Length; i++)
StopWorldSlot(_pool3D[i]);
}
public void ResumeWorldAudio() => _worldAudioSuspended = false;
internal void StopAllForOwner(uint ownerId)
{
if (ownerId == 0)
return;
for (int i = 0; i < _pool3D.Length; i++)
{
Slot3D slot = _pool3D[i];
if (slot.InUse && slot.OwnerId == ownerId)
StopWorldSlot(slot);
}
}
/// <summary>
/// Play a raw WaveData blob as a 2D UI sound (no falloff, ignores
/// listener position).
@ -469,4 +506,18 @@ public sealed unsafe class OpenAlAudioEngine : IAudioEngine
_al.GetSourceProperty(sourceId, GetSourceInteger.SourceState, out int state);
return state == (int)SourceState.Playing;
}
private void StopWorldSlot(Slot3D slot)
{
if (_available && _al is not null && slot.SourceId != 0)
{
_al.SourceStop(slot.SourceId);
_al.SetSourceProperty(slot.SourceId, SourceInteger.Buffer, 0);
}
slot.OwnerId = 0;
slot.PlayingGain = 0f;
slot.PriorityBase = 0;
slot.InUse = false;
}
}