UpdatingSourceVoice + warn on audio leak

pull/53/head
cosmonaut 2023-11-20 18:56:22 -08:00
parent 772a0378bb
commit e961a18a83
14 changed files with 83 additions and 52 deletions

View File

@ -64,7 +64,7 @@ namespace MoonWorks.Audio
}; };
} }
protected override unsafe void Destroy() protected override unsafe void DisposeUnmanagedState()
{ {
if (OwnsBufferData) if (OwnsBufferData)
{ {

View File

@ -90,22 +90,15 @@ namespace MoonWorks.Audio
/// <summary> /// <summary>
/// Unloads the Ogg data, freeing resources. /// Unloads the Ogg data, freeing resources.
/// </summary> /// </summary>
public override unsafe void Unload() public override void Unload()
{ {
if (Loaded) DisposeUnmanagedState();
{
FAudio.stb_vorbis_close(VorbisHandle);
NativeMemory.Free((void*) FileDataPtr);
VorbisHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
} }
/// <summary> /// <summary>
/// Loads an entire ogg file into an AudioBuffer. Useful for static audio. /// Loads an entire ogg file into an AudioBuffer. Useful for static audio.
/// </summary> /// </summary>
public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath) public static unsafe AudioBuffer CreateBuffer(AudioDevice device, string filePath)
{ {
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero); var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
@ -143,5 +136,17 @@ namespace MoonWorks.Audio
(uint) lengthInBytes, (uint) lengthInBytes,
true); true);
} }
protected override unsafe void DisposeUnmanagedState()
{
if (Loaded)
{
FAudio.stb_vorbis_close(VorbisHandle);
NativeMemory.Free((void*) FileDataPtr);
VorbisHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
}
} }
} }

View File

@ -99,16 +99,9 @@ namespace MoonWorks.Audio
/// <summary> /// <summary>
/// Unloads the qoa data, freeing resources. /// Unloads the qoa data, freeing resources.
/// </summary> /// </summary>
public override unsafe void Unload() public override void Unload()
{ {
if (Loaded) DisposeUnmanagedState();
{
FAudio.qoa_close(QoaHandle);
NativeMemory.Free((void*) FileDataPtr);
QoaHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
} }
/// <summary> /// <summary>
@ -160,5 +153,17 @@ namespace MoonWorks.Audio
((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) | ((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) |
((UInt64)(bytes[6]) << 8) | ((UInt64)(bytes[7]) << 0); ((UInt64)(bytes[6]) << 8) | ((UInt64)(bytes[7]) << 0);
} }
protected override unsafe void DisposeUnmanagedState()
{
if (Loaded)
{
FAudio.qoa_close(QoaHandle);
NativeMemory.Free((void*) FileDataPtr);
QoaHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
}
} }
} }

View File

@ -36,10 +36,5 @@ namespace MoonWorks.Audio
/// <param name="filledLengthInBytes">How much data was actually filled in by the decode.</param> /// <param name="filledLengthInBytes">How much data was actually filled in by the decode.</param>
/// <param name="reachedEnd">Whether the end of the data was reached on this decode.</param> /// <param name="reachedEnd">Whether the end of the data was reached on this decode.</param>
public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd); public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd);
protected override void Destroy()
{
Unload();
}
} }
} }

View File

@ -26,7 +26,7 @@ namespace MoonWorks.Audio
public float SpeedOfSound = 343.5f; public float SpeedOfSound = 343.5f;
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>(); private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
private readonly HashSet<SourceVoice> activeSourceVoices = new HashSet<SourceVoice>(); private readonly HashSet<UpdatingSourceVoice> updatingSourceVoices = new HashSet<UpdatingSourceVoice>();
private AudioTweenManager AudioTweenManager; private AudioTweenManager AudioTweenManager;
@ -42,7 +42,7 @@ namespace MoonWorks.Audio
internal readonly object StateLock = new object(); internal readonly object StateLock = new object();
private bool Running; private bool Running;
private bool IsDisposed; public bool IsDisposed { get; private set; }
internal unsafe AudioDevice() internal unsafe AudioDevice()
{ {
@ -164,15 +164,19 @@ namespace MoonWorks.Audio
AudioTweenManager.Update(elapsedSeconds); AudioTweenManager.Update(elapsedSeconds);
foreach (var voice in activeSourceVoices) foreach (var voice in updatingSourceVoices)
{ {
voice.Update(); voice.Update();
} }
foreach (var voice in VoicesToReturn) foreach (var voice in VoicesToReturn)
{ {
if (voice is UpdatingSourceVoice updatingSourceVoice)
{
updatingSourceVoices.Remove(updatingSourceVoice);
}
voice.Reset(); voice.Reset();
activeSourceVoices.Remove(voice);
VoicePool.Return(voice); VoicePool.Return(voice);
} }
@ -197,7 +201,12 @@ namespace MoonWorks.Audio
lock (StateLock) lock (StateLock)
{ {
var voice = VoicePool.Obtain<T>(format); var voice = VoicePool.Obtain<T>(format);
activeSourceVoices.Add(voice);
if (voice is UpdatingSourceVoice updatingSourceVoice)
{
updatingSourceVoices.Add(updatingSourceVoice);
}
return voice; return voice;
} }
} }
@ -258,9 +267,9 @@ namespace MoonWorks.Audio
{ {
resources.Add(resourceReference); resources.Add(resourceReference);
if (resourceReference.Target is SourceVoice voice) if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
{ {
activeSourceVoices.Add(voice); updatingSourceVoices.Add(updatableVoice);
} }
} }
} }

View File

@ -132,7 +132,5 @@ namespace MoonWorks.Audio
emitterData.pReverbCurve = IntPtr.Zero; emitterData.pReverbCurve = IntPtr.Zero;
emitterData.CurveDistanceScaler = 1.0f; emitterData.CurveDistanceScaler = 1.0f;
} }
protected override void Destroy() { }
} }
} }

View File

@ -94,7 +94,5 @@ namespace MoonWorks.Audio
/* Unexposed variables, defaults based on XNA behavior */ /* Unexposed variables, defaults based on XNA behavior */
listenerData.pCone = IntPtr.Zero; listenerData.pCone = IntPtr.Zero;
} }
protected override void Destroy() { }
} }
} }

View File

@ -19,16 +19,22 @@ namespace MoonWorks.Audio
Device.AddResourceReference(SelfReference); Device.AddResourceReference(SelfReference);
} }
protected abstract void Destroy(); protected virtual void DisposeManagedState() { }
protected virtual void DisposeUnmanagedState() { }
protected void Dispose(bool disposing) protected void Dispose(bool disposing)
{ {
if (!IsDisposed) if (!IsDisposed)
{ {
Destroy(); if (disposing)
{
DisposeManagedState();
Device.RemoveResourceReference(SelfReference); Device.RemoveResourceReference(SelfReference);
SelfReference.Free(); SelfReference.Free();
}
DisposeUnmanagedState();
IsDisposed = true; IsDisposed = true;
} }
@ -36,6 +42,17 @@ namespace MoonWorks.Audio
~AudioResource() ~AudioResource()
{ {
#if DEBUG
// If the graphics device associated with this resource was already disposed, we assume
// that your game is in the middle of shutting down.
if (!IsDisposed && Device != null && !Device.IsDisposed)
{
// If you see this log message, you leaked a graphics resource without disposing it!
// This means your game may eventually run out of native memory for mysterious reasons.
Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
}
#endif
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false); Dispose(disposing: false);
} }

View File

@ -3,7 +3,7 @@ namespace MoonWorks.Audio
/// <summary> /// <summary>
/// Plays back a series of AudioBuffers in sequence. Set the OnSoundNeeded callback to add AudioBuffers dynamically. /// Plays back a series of AudioBuffers in sequence. Set the OnSoundNeeded callback to add AudioBuffers dynamically.
/// </summary> /// </summary>
public class SoundSequence : SourceVoice, IPoolable<SoundSequence> public class SoundSequence : UpdatingSourceVoice, IPoolable<SoundSequence>
{ {
public int NeedSoundThreshold = 0; public int NeedSoundThreshold = 0;
public delegate void OnSoundNeededFunc(); public delegate void OnSoundNeededFunc();

View File

@ -191,12 +191,6 @@ namespace MoonWorks.Audio
Device.Return(this); Device.Return(this);
} }
/// <summary>
/// Called automatically by AudioDevice in the audio thread.
/// Don't call this yourself! You might regret it!
/// </summary>
public virtual void Update() { }
/// <summary> /// <summary>
/// Adds an FAudio buffer to the voice queue. /// Adds an FAudio buffer to the voice queue.
/// The voice processes and plays back the buffers in its queue in the order that they were submitted. /// The voice processes and plays back the buffers in its queue in the order that they were submitted.
@ -221,10 +215,9 @@ namespace MoonWorks.Audio
base.Reset(); base.Reset();
} }
protected override unsafe void Destroy() protected override void DisposeManagedState()
{ {
Stop(); Stop();
base.Destroy();
} }
} }
} }

View File

@ -6,7 +6,7 @@ namespace MoonWorks.Audio
/// <summary> /// <summary>
/// Use in conjunction with an AudioDataStreamable object to play back streaming audio data. /// Use in conjunction with an AudioDataStreamable object to play back streaming audio data.
/// </summary> /// </summary>
public class StreamingVoice : SourceVoice, IPoolable<StreamingVoice> public class StreamingVoice : UpdatingSourceVoice, IPoolable<StreamingVoice>
{ {
private const int BUFFER_COUNT = 3; private const int BUFFER_COUNT = 3;
private readonly IntPtr[] buffers; private readonly IntPtr[] buffers;

View File

@ -4,7 +4,7 @@ namespace MoonWorks.Audio
/// TransientVoice is intended for playing one-off sound effects that don't have a long term reference. <br/> /// TransientVoice is intended for playing one-off sound effects that don't have a long term reference. <br/>
/// It will be automatically returned to the AudioDevice SourceVoice pool once it is done playing back. /// It will be automatically returned to the AudioDevice SourceVoice pool once it is done playing back.
/// </summary> /// </summary>
public class TransientVoice : SourceVoice, IPoolable<TransientVoice> public class TransientVoice : UpdatingSourceVoice, IPoolable<TransientVoice>
{ {
static TransientVoice IPoolable<TransientVoice>.Create(AudioDevice device, Format format) static TransientVoice IPoolable<TransientVoice>.Create(AudioDevice device, Format format)
{ {

View File

@ -0,0 +1,11 @@
namespace MoonWorks.Audio
{
public abstract class UpdatingSourceVoice : SourceVoice
{
protected UpdatingSourceVoice(AudioDevice device, Format format) : base(device, format)
{
}
public abstract void Update();
}
}

View File

@ -565,7 +565,7 @@ namespace MoonWorks.Audio
); );
} }
protected unsafe override void Destroy() protected override void DisposeUnmanagedState()
{ {
NativeMemory.Free(pMatrixCoefficients); NativeMemory.Free(pMatrixCoefficients);
FAudio.FAudioVoice_DestroyVoice(Handle); FAudio.FAudioVoice_DestroyVoice(Handle);