From faf0f4ce5511c087935d9d1627366b277b9935b8 Mon Sep 17 00:00:00 2001 From: Causeless Date: Sat, 10 Oct 2026 13:56:00 +0100 Subject: [PATCH 1/2] Fixed a crash when quitting while sounds are still being loaded FMOD loads sounds asynchronously, on a thread of its own, and it takes a while after launching for it to get through all of them. Quitting before then had the audio system get released with sounds still loading (around 980 of them one sim update into an Activity launched straight into), which every so often crashed on that thread. AudioMan now waits for the loading to finish before it releases the audio system. That only takes any time when quitting that soon after launching, and then a second or two. Co-Authored-By: Claude Opus 5.5 --- Source/Managers/AudioMan.cpp | 2 ++ Source/System/ContentFile.cpp | 10 ++++++++++ Source/System/ContentFile.h | 3 +++ 3 files changed, 15 insertions(+) diff --git a/Source/Managers/AudioMan.cpp b/Source/Managers/AudioMan.cpp index 4839b0af5f..980065971c 100644 --- a/Source/Managers/AudioMan.cpp +++ b/Source/Managers/AudioMan.cpp @@ -130,6 +130,8 @@ bool AudioMan::Initialize() { void AudioMan::Destroy() { if (m_AudioEnabled) { StopAll(); + // FMOD loads sounds on a thread of its own, which crashes if the audio system gets released while it's still at it. That's the case when quitting shortly after launching. + ContentFile::WaitForSoundsToFinishLoading(); m_AudioSystem->release(); Clear(); } diff --git a/Source/System/ContentFile.cpp b/Source/System/ContentFile.cpp index 3d7b9b4b8f..017baa23b2 100644 --- a/Source/System/ContentFile.cpp +++ b/Source/System/ContentFile.cpp @@ -14,6 +14,7 @@ #include #include +#include using namespace RTE; @@ -435,6 +436,15 @@ FMOD::Sound* ContentFile::LoadAndReleaseSound(bool abortGameForInvalidSound, boo return returnSample; } +void ContentFile::WaitForSoundsToFinishLoading() { + for (const auto& [soundPath, sound]: s_LoadedSamples) { + FMOD_OPENSTATE openState = FMOD_OPENSTATE_READY; + while (sound && sound->getOpenState(&openState, nullptr, nullptr, nullptr) == FMOD_OK && openState == FMOD_OPENSTATE_LOADING) { + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } +} + void ContentFile::ReloadBitmap(const std::string& filePath, int conversionMode) { const int bitDepth = (conversionMode == COLORCONV_8_TO_32) ? BitDepths::ThirtyTwo : BitDepths::Eight; diff --git a/Source/System/ContentFile.h b/Source/System/ContentFile.h index 454511d13b..8777df3585 100644 --- a/Source/System/ContentFile.h +++ b/Source/System/ContentFile.h @@ -154,6 +154,9 @@ namespace RTE { /// @param asyncLoading Whether to enable FMOD asynchronous loading or not. Should be disabled for loading audio files with Lua AddSound. /// @return Pointer to the FSOUND_SAMPLE loaded from disk. FMOD::Sound* GetAsSound(bool abortGameForInvalidSound = true, bool asyncLoading = true); + + /// Waits for FMOD to be done with all the sounds it is still loading asynchronously. + static void WaitForSoundsToFinishLoading(); #pragma endregion /// Copies the default palette to an sdl palette. From be0f68130643a180288726ca66cf87e4b83560db Mon Sep 17 00:00:00 2001 From: Causeless Date: Sun, 11 Oct 2026 20:52:41 +0100 Subject: [PATCH 2/2] Made the sounds get released before the audio system instead of waited on Waiting for FMOD to be done loading sounds before releasing the audio system was done by checking each sound's state in a loop, sleeping in between. FMOD does that waiting itself when a sound that is still being loaded gets released, and it wants all sounds released before the audio system anyway, which was never done. So the sounds get released now, and the loop is gone. Co-Authored-By: Claude Opus 5.5 --- Source/Managers/AudioMan.cpp | 3 ++- Source/System/ContentFile.cpp | 19 +++++++++---------- Source/System/ContentFile.h | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Source/Managers/AudioMan.cpp b/Source/Managers/AudioMan.cpp index 980065971c..2182c08279 100644 --- a/Source/Managers/AudioMan.cpp +++ b/Source/Managers/AudioMan.cpp @@ -131,7 +131,8 @@ void AudioMan::Destroy() { if (m_AudioEnabled) { StopAll(); // FMOD loads sounds on a thread of its own, which crashes if the audio system gets released while it's still at it. That's the case when quitting shortly after launching. - ContentFile::WaitForSoundsToFinishLoading(); + // Releasing a sound waits for it to be done loading, and FMOD wants them all released before the audio system anyway. + ContentFile::FreeAllLoadedSounds(); m_AudioSystem->release(); Clear(); } diff --git a/Source/System/ContentFile.cpp b/Source/System/ContentFile.cpp index 017baa23b2..f57c07b34e 100644 --- a/Source/System/ContentFile.cpp +++ b/Source/System/ContentFile.cpp @@ -14,7 +14,6 @@ #include #include -#include using namespace RTE; @@ -61,6 +60,15 @@ void ContentFile::FreeAllLoaded() { } } +void ContentFile::FreeAllLoadedSounds() { + for (const auto& [soundPath, sound]: s_LoadedSamples) { + if (sound) { + sound->release(); + } + } + s_LoadedSamples.clear(); +} + int ContentFile::ReadProperty(const std::string_view& propName, Reader& reader) { StartPropertyList(return Serializable::ReadProperty(propName, reader)); @@ -436,15 +444,6 @@ FMOD::Sound* ContentFile::LoadAndReleaseSound(bool abortGameForInvalidSound, boo return returnSample; } -void ContentFile::WaitForSoundsToFinishLoading() { - for (const auto& [soundPath, sound]: s_LoadedSamples) { - FMOD_OPENSTATE openState = FMOD_OPENSTATE_READY; - while (sound && sound->getOpenState(&openState, nullptr, nullptr, nullptr) == FMOD_OK && openState == FMOD_OPENSTATE_LOADING) { - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - } - } -} - void ContentFile::ReloadBitmap(const std::string& filePath, int conversionMode) { const int bitDepth = (conversionMode == COLORCONV_8_TO_32) ? BitDepths::ThirtyTwo : BitDepths::Eight; diff --git a/Source/System/ContentFile.h b/Source/System/ContentFile.h index 8777df3585..0b451cb604 100644 --- a/Source/System/ContentFile.h +++ b/Source/System/ContentFile.h @@ -61,6 +61,9 @@ namespace RTE { /// Frees all loaded data used by all ContentFile instances. This should ONLY be done when quitting the app, or after everything else is completely destroyed. static void FreeAllLoaded(); + + /// Releases all the sounds loaded by all ContentFile instances, waiting for the ones FMOD is still loading asynchronously. This has to be done before the audio system gets released. + static void FreeAllLoadedSounds(); #pragma endregion #pragma region Getters and Setters @@ -154,9 +157,6 @@ namespace RTE { /// @param asyncLoading Whether to enable FMOD asynchronous loading or not. Should be disabled for loading audio files with Lua AddSound. /// @return Pointer to the FSOUND_SAMPLE loaded from disk. FMOD::Sound* GetAsSound(bool abortGameForInvalidSound = true, bool asyncLoading = true); - - /// Waits for FMOD to be done with all the sounds it is still loading asynchronously. - static void WaitForSoundsToFinishLoading(); #pragma endregion /// Copies the default palette to an sdl palette.