diff --git a/src/Audio/AudioBuffer.cs b/src/Audio/AudioBuffer.cs index 6c79055c..78fe19d0 100644 --- a/src/Audio/AudioBuffer.cs +++ b/src/Audio/AudioBuffer.cs @@ -64,7 +64,7 @@ namespace MoonWorks.Audio }; } - protected override unsafe void Destroy() + protected override unsafe void DisposeUnmanagedState() { if (OwnsBufferData) { diff --git a/src/Audio/AudioDataOgg.cs b/src/Audio/AudioDataOgg.cs index 3728cc8a..565ce49d 100644 --- a/src/Audio/AudioDataOgg.cs +++ b/src/Audio/AudioDataOgg.cs @@ -90,22 +90,15 @@ namespace MoonWorks.Audio /// /// Unloads the Ogg data, freeing resources. /// - public override unsafe void Unload() + public override void Unload() { - if (Loaded) - { - FAudio.stb_vorbis_close(VorbisHandle); - NativeMemory.Free((void*) FileDataPtr); - - VorbisHandle = IntPtr.Zero; - FileDataPtr = IntPtr.Zero; - } + DisposeUnmanagedState(); } /// /// Loads an entire ogg file into an AudioBuffer. Useful for static audio. /// - 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); @@ -143,5 +136,17 @@ namespace MoonWorks.Audio (uint) lengthInBytes, true); } + + protected override unsafe void DisposeUnmanagedState() + { + if (Loaded) + { + FAudio.stb_vorbis_close(VorbisHandle); + NativeMemory.Free((void*) FileDataPtr); + + VorbisHandle = IntPtr.Zero; + FileDataPtr = IntPtr.Zero; + } + } } } diff --git a/src/Audio/AudioDataQoa.cs b/src/Audio/AudioDataQoa.cs index 2ee075fa..0adbe5ab 100644 --- a/src/Audio/AudioDataQoa.cs +++ b/src/Audio/AudioDataQoa.cs @@ -99,16 +99,9 @@ namespace MoonWorks.Audio /// /// Unloads the qoa data, freeing resources. /// - public override unsafe void Unload() + public override void Unload() { - if (Loaded) - { - FAudio.qoa_close(QoaHandle); - NativeMemory.Free((void*) FileDataPtr); - - QoaHandle = IntPtr.Zero; - FileDataPtr = IntPtr.Zero; - } + DisposeUnmanagedState(); } /// @@ -160,5 +153,17 @@ namespace MoonWorks.Audio ((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) | ((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; + } + } } } diff --git a/src/Audio/AudioDataStreamable.cs b/src/Audio/AudioDataStreamable.cs index a0b9d605..7559bec3 100644 --- a/src/Audio/AudioDataStreamable.cs +++ b/src/Audio/AudioDataStreamable.cs @@ -36,10 +36,5 @@ namespace MoonWorks.Audio /// How much data was actually filled in by the decode. /// Whether the end of the data was reached on this decode. public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd); - - protected override void Destroy() - { - Unload(); - } } } diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs index 30763a8f..dea4edc7 100644 --- a/src/Audio/AudioDevice.cs +++ b/src/Audio/AudioDevice.cs @@ -26,7 +26,7 @@ namespace MoonWorks.Audio public float SpeedOfSound = 343.5f; private readonly HashSet resources = new HashSet(); - private readonly HashSet activeSourceVoices = new HashSet(); + private readonly HashSet updatingSourceVoices = new HashSet(); private AudioTweenManager AudioTweenManager; @@ -42,7 +42,7 @@ namespace MoonWorks.Audio internal readonly object StateLock = new object(); private bool Running; - private bool IsDisposed; + public bool IsDisposed { get; private set; } internal unsafe AudioDevice() { @@ -164,15 +164,19 @@ namespace MoonWorks.Audio AudioTweenManager.Update(elapsedSeconds); - foreach (var voice in activeSourceVoices) + foreach (var voice in updatingSourceVoices) { voice.Update(); } foreach (var voice in VoicesToReturn) { + if (voice is UpdatingSourceVoice updatingSourceVoice) + { + updatingSourceVoices.Remove(updatingSourceVoice); + } + voice.Reset(); - activeSourceVoices.Remove(voice); VoicePool.Return(voice); } @@ -197,7 +201,12 @@ namespace MoonWorks.Audio lock (StateLock) { var voice = VoicePool.Obtain(format); - activeSourceVoices.Add(voice); + + if (voice is UpdatingSourceVoice updatingSourceVoice) + { + updatingSourceVoices.Add(updatingSourceVoice); + } + return voice; } } @@ -258,9 +267,9 @@ namespace MoonWorks.Audio { resources.Add(resourceReference); - if (resourceReference.Target is SourceVoice voice) + if (resourceReference.Target is UpdatingSourceVoice updatableVoice) { - activeSourceVoices.Add(voice); + updatingSourceVoices.Add(updatableVoice); } } } diff --git a/src/Audio/AudioEmitter.cs b/src/Audio/AudioEmitter.cs index 1a9c9833..8b3fbf5e 100644 --- a/src/Audio/AudioEmitter.cs +++ b/src/Audio/AudioEmitter.cs @@ -132,7 +132,5 @@ namespace MoonWorks.Audio emitterData.pReverbCurve = IntPtr.Zero; emitterData.CurveDistanceScaler = 1.0f; } - - protected override void Destroy() { } } } diff --git a/src/Audio/AudioListener.cs b/src/Audio/AudioListener.cs index e1217909..7557485e 100644 --- a/src/Audio/AudioListener.cs +++ b/src/Audio/AudioListener.cs @@ -94,7 +94,5 @@ namespace MoonWorks.Audio /* Unexposed variables, defaults based on XNA behavior */ listenerData.pCone = IntPtr.Zero; } - - protected override void Destroy() { } } } diff --git a/src/Audio/AudioResource.cs b/src/Audio/AudioResource.cs index 4c8d4e92..3f6b4d0e 100644 --- a/src/Audio/AudioResource.cs +++ b/src/Audio/AudioResource.cs @@ -19,16 +19,22 @@ namespace MoonWorks.Audio Device.AddResourceReference(SelfReference); } - protected abstract void Destroy(); + protected virtual void DisposeManagedState() { } + protected virtual void DisposeUnmanagedState() { } protected void Dispose(bool disposing) { if (!IsDisposed) { - Destroy(); + if (disposing) + { + DisposeManagedState(); - Device.RemoveResourceReference(SelfReference); - SelfReference.Free(); + Device.RemoveResourceReference(SelfReference); + SelfReference.Free(); + } + + DisposeUnmanagedState(); IsDisposed = true; } @@ -36,6 +42,17 @@ namespace MoonWorks.Audio ~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 Dispose(disposing: false); } diff --git a/src/Audio/SoundSequence.cs b/src/Audio/SoundSequence.cs index 9d5b1631..dacacedf 100644 --- a/src/Audio/SoundSequence.cs +++ b/src/Audio/SoundSequence.cs @@ -3,7 +3,7 @@ namespace MoonWorks.Audio /// /// Plays back a series of AudioBuffers in sequence. Set the OnSoundNeeded callback to add AudioBuffers dynamically. /// - public class SoundSequence : SourceVoice, IPoolable + public class SoundSequence : UpdatingSourceVoice, IPoolable { public int NeedSoundThreshold = 0; public delegate void OnSoundNeededFunc(); diff --git a/src/Audio/SourceVoice.cs b/src/Audio/SourceVoice.cs index 5e4efeca..d1a86268 100644 --- a/src/Audio/SourceVoice.cs +++ b/src/Audio/SourceVoice.cs @@ -191,12 +191,6 @@ namespace MoonWorks.Audio Device.Return(this); } - /// - /// Called automatically by AudioDevice in the audio thread. - /// Don't call this yourself! You might regret it! - /// - public virtual void Update() { } - /// /// 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. @@ -221,10 +215,9 @@ namespace MoonWorks.Audio base.Reset(); } - protected override unsafe void Destroy() + protected override void DisposeManagedState() { Stop(); - base.Destroy(); } } } diff --git a/src/Audio/StreamingVoice.cs b/src/Audio/StreamingVoice.cs index 24623a1e..2e8b061b 100644 --- a/src/Audio/StreamingVoice.cs +++ b/src/Audio/StreamingVoice.cs @@ -6,7 +6,7 @@ namespace MoonWorks.Audio /// /// Use in conjunction with an AudioDataStreamable object to play back streaming audio data. /// - public class StreamingVoice : SourceVoice, IPoolable + public class StreamingVoice : UpdatingSourceVoice, IPoolable { private const int BUFFER_COUNT = 3; private readonly IntPtr[] buffers; diff --git a/src/Audio/TransientVoice.cs b/src/Audio/TransientVoice.cs index 0b6e499c..550ce1b8 100644 --- a/src/Audio/TransientVoice.cs +++ b/src/Audio/TransientVoice.cs @@ -4,7 +4,7 @@ namespace MoonWorks.Audio /// TransientVoice is intended for playing one-off sound effects that don't have a long term reference.
/// It will be automatically returned to the AudioDevice SourceVoice pool once it is done playing back. ///
- public class TransientVoice : SourceVoice, IPoolable + public class TransientVoice : UpdatingSourceVoice, IPoolable { static TransientVoice IPoolable.Create(AudioDevice device, Format format) { diff --git a/src/Audio/UpdatingSourceVoice.cs b/src/Audio/UpdatingSourceVoice.cs new file mode 100644 index 00000000..4c68737f --- /dev/null +++ b/src/Audio/UpdatingSourceVoice.cs @@ -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(); + } +} diff --git a/src/Audio/Voice.cs b/src/Audio/Voice.cs index d982f1bc..d1739c92 100644 --- a/src/Audio/Voice.cs +++ b/src/Audio/Voice.cs @@ -565,7 +565,7 @@ namespace MoonWorks.Audio ); } - protected unsafe override void Destroy() + protected override void DisposeUnmanagedState() { NativeMemory.Free(pMatrixCoefficients); FAudio.FAudioVoice_DestroyVoice(Handle);