Resolve Audio Clips From Level Files

This commit is contained in:
copecone committed 2026-10-13 02:00:18 +09:00
1 parent 35606d6e58
commit 7ea2d8fe3f
5 files changed
+149 -7

No files matched your search

+18 -7
View File
@@ -26,6 +26,9 @@ internal sealed class AudioClipStore : IDisposable
/// <summary>Longest wait of a consumer between checks when no chunk is loaded, in milliseconds.</summary>
private const int ConsumerWaitMs = 50;
/// <summary>Longest wait for the decoder thread to stop in <see cref="Dispose"/>, in milliseconds.</summary>
private const int DecoderStopTimeoutMs = 3000;
private readonly Dictionary<string, AudioClipSource?> _sources = new();
private readonly List<AudioClipSource> _sourceList = new();
private readonly Stack<float[]> _chunkPool = new();
@@ -128,10 +131,10 @@ internal sealed class AudioClipStore : IDisposable
/// <summary>
/// Returns the source registered under <paramref name="key"/>, creating it with <paramref name="createReader"/> on first use.
/// A key whose reader could not be created is remembered as null and not retried.
/// For readers that can seek, <paramref name="createReader"/> is kept and called again on the decoder thread
/// to open additional decode streams (see <see cref="AudioClipSource.MaxDecodeStreams"/>).
/// For readers that can seek, additional decode streams (see <see cref="AudioClipSource.MaxDecodeStreams"/>) are opened on the
/// decoder thread with <paramref name="openAdditionalReader"/>, or with <paramref name="createReader"/> when it is null.
/// </summary>
public AudioClipSource? GetOrAdd(string key, Func<IPCMReader?> createReader)
public AudioClipSource? GetOrAdd(string key, Func<IPCMReader?> createReader, Func<IPCMReader?>? openAdditionalReader = null)
{
lock (_sources)
{
@@ -147,7 +150,7 @@ internal sealed class AudioClipStore : IDisposable
{
try
{
source = new AudioClipSource(this, key, reader, reader.IsForwardOnly ? null : createReader, OutputRate);
source = new AudioClipSource(this, key, reader, reader.IsForwardOnly ? null : openAdditionalReader ?? createReader, OutputRate);
}
catch
{
@@ -315,7 +318,12 @@ internal sealed class AudioClipStore : IDisposable
_stopRequested = true;
_serviceRequested.Set();
NotifyChunkLoaded();
_decoderThread?.Join();
// A decoder thread blocked inside a read (for example a file on an unresponsive drive) is left running:
// it is a background thread, and its sources keep their readers so the blocked call does not touch freed FFmpeg contexts.
var decoderStopped = _decoderThread == null || _decoderThread.Join(DecoderStopTimeoutMs);
if (!decoderStopped)
Melon<RenderMod>.Logger.Warning($"[AudioClipStore] Decoder thread did not stop within {DecoderStopTimeoutMs} ms; its readers are left open.");
LogMemoryUsage();
@@ -325,8 +333,11 @@ internal sealed class AudioClipStore : IDisposable
_sources.Clear();
}
foreach (var source in sources)
source.DisposeInternal();
if (decoderStopped)
{
foreach (var source in sources)
source.DisposeInternal();
}
lock (_poolLock)
{