From 29f0631a2c322d5e86ec2e64b7940d966d7b00f0 Mon Sep 17 00:00:00 2001 From: krzys-h Date: Sun, 10 Jul 2016 14:56:34 +0200 Subject: [PATCH] Asynchronous sound/music loading This improves the loading time a lot. Time from starting the app to opening game window decreased by almost 5 seconds (it's almost instant now). Mission loading times are significantly better too. As a bonus, the playmusic() CBot function doesn't hang the game if you don't preload the files with CacheAudio in scene file. --- src/CMakeLists.txt | 2 + src/app/app.cpp | 18 +++- src/common/thread/resource_owning_thread.h | 15 ++- src/common/thread/sdl_cond_wrapper.h | 12 +++ src/common/thread/sdl_mutex_wrapper.h | 10 ++ src/common/thread/thread.h | 77 ++++++++++++++ src/common/thread/worker_thread.h | 92 +++++++++++++++++ src/sound/oalsound/alsound.cpp | 113 +++++++++------------ src/sound/oalsound/alsound.h | 11 +- src/sound/sound.cpp | 28 +---- src/sound/sound.h | 26 ++--- src/ui/mainui.cpp | 3 +- 12 files changed, 289 insertions(+), 118 deletions(-) create mode 100644 src/common/thread/thread.h create mode 100644 src/common/thread/worker_thread.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index bc6f12ad..4d5a6409 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -151,6 +151,8 @@ set(BASE_SOURCES common/thread/resource_owning_thread.h common/thread/sdl_cond_wrapper.h common/thread/sdl_mutex_wrapper.h + common/thread/thread.h + common/thread/worker_thread.h graphics/core/color.cpp graphics/core/color.h graphics/core/device.h diff --git a/src/app/app.cpp b/src/app/app.cpp index d34532f7..6241ef08 100644 --- a/src/app/app.cpp +++ b/src/app/app.cpp @@ -34,6 +34,8 @@ #include "common/resources/resourcemanager.h" +#include "common/thread/thread.h" + #include "graphics/core/nulldevice.h" #include "graphics/opengl/glutil.h" @@ -514,8 +516,6 @@ bool CApplication::Create() #endif m_sound->Create(); - m_sound->CacheAll(); - m_sound->CacheCommonMusic(); GetLogger()->Info("CApplication created successfully\n"); @@ -685,6 +685,20 @@ bool CApplication::Create() // Create the robot application. m_controller = MakeUnique(); + CThread musicLoadThread([this]() { + GetLogger()->Debug("Cache sounds...\n"); + SystemTimeStamp* musicLoadStart = m_systemUtils->CreateTimeStamp(); + m_systemUtils->GetCurrentTimeStamp(musicLoadStart); + + m_sound->CacheAll(); + + SystemTimeStamp* musicLoadEnd = m_systemUtils->CreateTimeStamp(); + m_systemUtils->GetCurrentTimeStamp(musicLoadEnd); + float musicLoadTime = m_systemUtils->TimeStampDiff(musicLoadStart, musicLoadEnd, STU_MSEC); + GetLogger()->Debug("Sound loading took %.2f ms\n", musicLoadTime); + }, "Sound loading thread"); + musicLoadThread.Start(); + if (m_runSceneCategory == LevelCategory::Max) m_controller->StartApp(); else diff --git a/src/common/thread/resource_owning_thread.h b/src/common/thread/resource_owning_thread.h index 0e4c2006..04237961 100644 --- a/src/common/thread/resource_owning_thread.h +++ b/src/common/thread/resource_owning_thread.h @@ -59,6 +59,11 @@ public: m_name(name) {} + ~CResourceOwningThread() + { + SDL_DetachThread(m_thread); + } + void Start() { CSDLMutexWrapper mutex; @@ -74,7 +79,7 @@ public: SDL_LockMutex(*mutex); - SDL_CreateThread(Run, !m_name.empty() ? m_name.c_str() : nullptr, reinterpret_cast(&data)); + m_thread = SDL_CreateThread(Run, !m_name.empty() ? m_name.c_str() : nullptr, reinterpret_cast(&data)); while (!condition) { @@ -84,6 +89,13 @@ public: SDL_UnlockMutex(*mutex); } + void Join() + { + if (m_thread == nullptr) return; + SDL_WaitThread(m_thread, nullptr); + m_thread = nullptr; + } + private: static int Run(void* data) { @@ -117,4 +129,5 @@ private: ThreadFunctionPtr m_threadFunction; ResourceUPtr m_resource; std::string m_name; + SDL_Thread* m_thread = nullptr; }; diff --git a/src/common/thread/sdl_cond_wrapper.h b/src/common/thread/sdl_cond_wrapper.h index 5b09d2f3..8c8be5ee 100644 --- a/src/common/thread/sdl_cond_wrapper.h +++ b/src/common/thread/sdl_cond_wrapper.h @@ -19,6 +19,8 @@ #pragma once +#include "common/thread/sdl_mutex_wrapper.h" + #include /** @@ -45,6 +47,16 @@ public: return m_cond; } + void Signal() + { + SDL_CondSignal(m_cond); + } + + void Wait(SDL_mutex* mutex) + { + SDL_CondWait(m_cond, mutex); + } + private: SDL_cond* m_cond; }; diff --git a/src/common/thread/sdl_mutex_wrapper.h b/src/common/thread/sdl_mutex_wrapper.h index bd7d9075..59afc0aa 100644 --- a/src/common/thread/sdl_mutex_wrapper.h +++ b/src/common/thread/sdl_mutex_wrapper.h @@ -45,6 +45,16 @@ public: return m_mutex; } + void Lock() + { + SDL_LockMutex(m_mutex); + } + + void Unlock() + { + SDL_UnlockMutex(m_mutex); + } + private: SDL_mutex* m_mutex; }; diff --git a/src/common/thread/thread.h b/src/common/thread/thread.h new file mode 100644 index 00000000..d0b6b873 --- /dev/null +++ b/src/common/thread/thread.h @@ -0,0 +1,77 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2016, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#pragma once + +#include "common/make_unique.h" + +#include "common/thread/resource_owning_thread.h" + +#include +#include +#include + +/** + * \class CThread + * \brief Wrapper for using SDL_thread with std::function + */ +class CThread +{ +public: + using ThreadFunctionPtr = std::function; + +private: + struct ThreadData + { + ThreadFunctionPtr func; + }; + +public: + CThread(ThreadFunctionPtr func, std::string name = "") + : m_func(std::move(func)) + , m_name(name) + {} + + void Start() + { + std::unique_ptr data = MakeUnique(); + data->func = m_func; + m_thread = MakeUnique>(Run, std::move(data), m_name); + m_thread->Start(); + } + + void Join() + { + if (!m_thread) return; + m_thread->Join(); + } + + CThread(const CThread&) = delete; + CThread& operator=(const CThread&) = delete; + +private: + static void Run(std::unique_ptr data) + { + data->func(); + } + + std::unique_ptr> m_thread; + ThreadFunctionPtr m_func; + std::string m_name; +}; diff --git a/src/common/thread/worker_thread.h b/src/common/thread/worker_thread.h new file mode 100644 index 00000000..aa2ce3e4 --- /dev/null +++ b/src/common/thread/worker_thread.h @@ -0,0 +1,92 @@ +/* + * This file is part of the Colobot: Gold Edition source code + * Copyright (C) 2001-2016, Daniel Roux, EPSITEC SA & TerranovaTeam + * http://epsitec.ch; http://colobot.info; http://github.com/colobot + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://gnu.org/licenses + */ + +#pragma once + +#include "common/make_unique.h" + +#include "common/thread/sdl_cond_wrapper.h" +#include "common/thread/sdl_mutex_wrapper.h" +#include "common/thread/thread.h" + +#include +#include +#include + +/** + * \class CWorkerThread + * \brief Thread that runs functions, one at a time + */ +class CWorkerThread +{ +public: + using ThreadFunctionPtr = std::function; + +public: + CWorkerThread(std::string name = "") + : m_thread(std::bind(&CWorkerThread::Run, this), name) + { + m_thread.Start(); + } + + ~CWorkerThread() + { + m_mutex.Lock(); + m_running = false; + m_cond.Signal(); + m_mutex.Unlock(); + m_thread.Join(); + } + + void Start(ThreadFunctionPtr func) + { + m_mutex.Lock(); + m_queue.push(func); + m_cond.Signal(); + m_mutex.Unlock(); + } + + CWorkerThread(const CWorkerThread&) = delete; + CWorkerThread& operator=(const CWorkerThread&) = delete; + +private: + void Run() + { + m_mutex.Lock(); + while (true) + { + while (m_queue.empty() && m_running) + { + m_cond.Wait(*m_mutex); + } + if (!m_running) break; + + ThreadFunctionPtr func = m_queue.front(); + m_queue.pop(); + func(); + } + m_mutex.Unlock(); + } + + CThread m_thread; + CSDLMutexWrapper m_mutex; + CSDLCondWrapper m_cond; + bool m_running = true; + std::queue m_queue; +}; diff --git a/src/sound/oalsound/alsound.cpp b/src/sound/oalsound/alsound.cpp index cfa335d1..f51887b1 100644 --- a/src/sound/oalsound/alsound.cpp +++ b/src/sound/oalsound/alsound.cpp @@ -32,7 +32,8 @@ CALSound::CALSound() m_musicVolume(1.0f), m_channelsLimit(2048), m_device{}, - m_context{} + m_context{}, + m_thread("Music loading thread") { } @@ -144,18 +145,19 @@ bool CALSound::Cache(SoundType sound, const std::string &filename) return false; } -bool CALSound::CacheMusic(const std::string &filename) +void CALSound::CacheMusic(const std::string &filename) { - if (m_music.find(filename) == m_music.end()) + m_thread.Start([this, filename]() { - auto buffer = MakeUnique(); - if (buffer->LoadFromFile(filename, static_cast(-1))) + if (m_music.find(filename) == m_music.end()) { - m_music[filename] = std::move(buffer); - return true; + auto buffer = MakeUnique(); + if (buffer->LoadFromFile(filename, static_cast(-1))) + { + m_music[filename] = std::move(buffer); + } } - } - return false; + }); } bool CALSound::IsCached(SoundType sound) @@ -572,53 +574,54 @@ void CALSound::SetListener(const Math::Vector &eye, const Math::Vector &lookat) alListenerfv(AL_ORIENTATION, orientation); } -bool CALSound::PlayMusic(const std::string &filename, bool repeat, float fadeTime) +void CALSound::PlayMusic(const std::string &filename, bool repeat, float fadeTime) { if (!m_enabled) { - return false; + return; } - CBuffer *buffer = nullptr; - - // check if we have music in cache - if (m_music.find(filename) == m_music.end()) + m_thread.Start([this, filename, repeat, fadeTime]() { - GetLogger()->Debug("Music %s was not cached!\n", filename.c_str()); + CBuffer* buffer = nullptr; - auto newBuffer = MakeUnique(); - buffer = newBuffer.get(); - if (!newBuffer->LoadFromFile(filename, static_cast(-1))) + // check if we have music in cache + if (m_music.find(filename) == m_music.end()) { - return false; + GetLogger()->Debug("Music %s was not cached!\n", filename.c_str()); + + auto newBuffer = MakeUnique(); + buffer = newBuffer.get(); + if (!newBuffer->LoadFromFile(filename, static_cast(-1))) + { + return; + } + m_music[filename] = std::move(newBuffer); + } + else + { + GetLogger()->Debug("Music loaded from cache\n"); + buffer = m_music[filename].get(); } - m_music[filename] = std::move(newBuffer); - } - else - { - GetLogger()->Debug("Music loaded from cache\n"); - buffer = m_music[filename].get(); - } - if (m_currentMusic) - { - OldMusic old; - old.music = std::move(m_currentMusic); - old.fadeTime = fadeTime; - old.currentTime = 0.0f; - m_oldMusic.push_back(std::move(old)); - } + if (m_currentMusic) + { + OldMusic old; + old.music = std::move(m_currentMusic); + old.fadeTime = fadeTime; + old.currentTime = 0.0f; + m_oldMusic.push_back(std::move(old)); + } - m_currentMusic = MakeUnique(); - m_currentMusic->SetBuffer(buffer); - m_currentMusic->SetVolume(m_musicVolume); - m_currentMusic->SetLoop(repeat); - m_currentMusic->Play(); - - return true; + m_currentMusic = MakeUnique(); + m_currentMusic->SetBuffer(buffer); + m_currentMusic->SetVolume(m_musicVolume); + m_currentMusic->SetLoop(repeat); + m_currentMusic->Play(); + }); } -bool CALSound::PlayPauseMusic(const std::string &filename, bool repeat) +void CALSound::PlayPauseMusic(const std::string &filename, bool repeat) { if (m_previousMusic.fadeTime > 0.0f) { @@ -640,7 +643,7 @@ bool CALSound::PlayPauseMusic(const std::string &filename, bool repeat) m_previousMusic.currentTime = 0.0f; } } - return PlayMusic(filename, repeat); + PlayMusic(filename, repeat); } void CALSound::StopPauseMusic() @@ -662,18 +665,6 @@ void CALSound::StopPauseMusic() } } -bool CALSound::RestartMusic() -{ - if (!m_enabled || m_currentMusic == nullptr) - { - return false; - } - - m_currentMusic->Stop(); - m_currentMusic->Play(); - return true; -} - void CALSound::StopMusic(float fadeTime) { if (!m_enabled || m_currentMusic == nullptr) @@ -698,16 +689,6 @@ bool CALSound::IsPlayingMusic() return m_currentMusic->IsPlaying(); } -void CALSound::SuspendMusic() -{ - if (!m_enabled || m_currentMusic == nullptr) - { - return; - } - - m_currentMusic->Stop(); -} - bool CALSound::CheckChannel(int &channel) { int id = (channel >> 16) & 0xffff; diff --git a/src/sound/oalsound/alsound.h b/src/sound/oalsound/alsound.h index e4656e11..03d5bd49 100644 --- a/src/sound/oalsound/alsound.h +++ b/src/sound/oalsound/alsound.h @@ -26,6 +26,8 @@ #include "sound/sound.h" +#include "common/thread/worker_thread.h" + #include "sound/oalsound/buffer.h" #include "sound/oalsound/channel.h" #include "sound/oalsound/check.h" @@ -83,7 +85,7 @@ public: bool Create() override; bool Cache(SoundType, const std::string &) override; - bool CacheMusic(const std::string &) override; + void CacheMusic(const std::string &) override; bool IsCached(SoundType) override; bool IsCachedMusic(const std::string &) override; @@ -106,12 +108,10 @@ public: bool StopAll() override; bool MuteAll(bool mute) override; - bool PlayMusic(const std::string &filename, bool repeat, float fadeTime=2.0f) override; - bool RestartMusic() override; - void SuspendMusic() override; + void PlayMusic(const std::string &filename, bool repeat, float fadeTime = 2.0f) override; void StopMusic(float fadeTime=2.0f) override; bool IsPlayingMusic() override; - bool PlayPauseMusic(const std::string &filename, bool repeat) override; + void PlayPauseMusic(const std::string &filename, bool repeat) override; void StopPauseMusic() override; private: @@ -134,4 +134,5 @@ private: OldMusic m_previousMusic; Math::Vector m_eye; Math::Vector m_lookat; + CWorkerThread m_thread; }; diff --git a/src/sound/sound.cpp b/src/sound/sound.cpp index 3640c585..e5b29291 100644 --- a/src/sound/sound.cpp +++ b/src/sound/sound.cpp @@ -52,22 +52,13 @@ void CSoundInterface::CacheAll() } } -void CSoundInterface::CacheCommonMusic() -{ - CacheMusic("music/Intro1.ogg"); - CacheMusic("music/Intro2.ogg"); - CacheMusic("music/music010.ogg"); - CacheMusic("music/music011.ogg"); -} - bool CSoundInterface::Cache(SoundType sound, const std::string &file) { return true; } -bool CSoundInterface::CacheMusic(const std::string &file) +void CSoundInterface::CacheMusic(const std::string &file) { - return true; } bool CSoundInterface::IsCached(SoundType sound) @@ -156,17 +147,7 @@ bool CSoundInterface::MuteAll(bool mute) return true; } -bool CSoundInterface::PlayMusic(const std::string &filename, bool repeat, float fadeTime) -{ - return true; -} - -bool CSoundInterface::RestartMusic() -{ - return true; -} - -void CSoundInterface::SuspendMusic() +void CSoundInterface::PlayMusic(const std::string &filename, bool repeat, float fadeTime) { } @@ -176,12 +157,11 @@ void CSoundInterface::StopMusic(float fadeTime) bool CSoundInterface::IsPlayingMusic() { - return true; + return false; } -bool CSoundInterface::PlayPauseMusic(const std::string &filename, bool repeat) +void CSoundInterface::PlayPauseMusic(const std::string &filename, bool repeat) { - return true; } void CSoundInterface::StopPauseMusic() diff --git a/src/sound/sound.h b/src/sound/sound.h index 3799d6f9..a2dce312 100644 --- a/src/sound/sound.h +++ b/src/sound/sound.h @@ -72,9 +72,6 @@ public: */ void CacheAll(); - /** Function called to add all music files to list */ - void CacheCommonMusic(); - /** Function called to cache sound effect file. * This function is called by plugin interface for each file. * \param sound - id of a file, will be used to identify sound files @@ -85,10 +82,10 @@ public: /** Function called to cache music file. * This function is called by CRobotMain for each file used in the mission. + * This function is executed asynchronously * \param file - file to load - * \return return true on success */ - virtual bool CacheMusic(const std::string &file); + virtual void CacheMusic(const std::string &file); /** Function to check if sound effect file was cached. * \param sound - id of a sound effect file @@ -205,22 +202,12 @@ public: virtual bool MuteAll(bool mute); /** Start playing music + * This function is executed asynchronously * \param filename - name of file to play * \param repeat - repeat playing - * \param fadeTime - time of transition between music - * \return return true on success + * \param fadeTime - time of transition between music, 0 to disable */ - virtual bool PlayMusic(const std::string &filename, bool repeat, float fadeTime=2.0f); - - /** Restart music - * \return return true on success - */ - virtual bool RestartMusic(); - - /** Susspend playing music - * \return nothing - */ - virtual void SuspendMusic(); + virtual void PlayMusic(const std::string &filename, bool repeat, float fadeTime = 2.0f); /** Stop playing music * \return nothing @@ -233,11 +220,12 @@ public: virtual bool IsPlayingMusic(); /** Start playing pause music + * This function is executed asynchronously * \param filename - name of file to play * \param repeat - repeat playing * \return return true on success */ - virtual bool PlayPauseMusic(const std::string &filename, bool repeat); + virtual void PlayPauseMusic(const std::string &filename, bool repeat); /** Stop playing pause music and return to the mission music * \return nothing diff --git a/src/ui/mainui.cpp b/src/ui/mainui.cpp index f373d692..503cc49f 100644 --- a/src/ui/mainui.cpp +++ b/src/ui/mainui.cpp @@ -213,9 +213,10 @@ void CMainUserInterface::ChangePhase(Phase phase) if ( IsMainMenuPhase(m_phase) ) { - if (!m_sound->IsPlayingMusic() && m_sound->IsCachedMusic("music/Intro1.ogg")) + if (!m_sound->IsPlayingMusic()) { m_sound->PlayMusic("music/Intro1.ogg", false); + m_sound->CacheMusic("music/Intro2.ogg"); } }