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.
dev-new-models
krzys-h 2016-07-10 14:56:34 +02:00
parent 9e545d0d39
commit 29f0631a2c
12 changed files with 289 additions and 118 deletions

View File

@ -151,6 +151,8 @@ set(BASE_SOURCES
common/thread/resource_owning_thread.h common/thread/resource_owning_thread.h
common/thread/sdl_cond_wrapper.h common/thread/sdl_cond_wrapper.h
common/thread/sdl_mutex_wrapper.h common/thread/sdl_mutex_wrapper.h
common/thread/thread.h
common/thread/worker_thread.h
graphics/core/color.cpp graphics/core/color.cpp
graphics/core/color.h graphics/core/color.h
graphics/core/device.h graphics/core/device.h

View File

@ -34,6 +34,8 @@
#include "common/resources/resourcemanager.h" #include "common/resources/resourcemanager.h"
#include "common/thread/thread.h"
#include "graphics/core/nulldevice.h" #include "graphics/core/nulldevice.h"
#include "graphics/opengl/glutil.h" #include "graphics/opengl/glutil.h"
@ -514,8 +516,6 @@ bool CApplication::Create()
#endif #endif
m_sound->Create(); m_sound->Create();
m_sound->CacheAll();
m_sound->CacheCommonMusic();
GetLogger()->Info("CApplication created successfully\n"); GetLogger()->Info("CApplication created successfully\n");
@ -685,6 +685,20 @@ bool CApplication::Create()
// Create the robot application. // Create the robot application.
m_controller = MakeUnique<CController>(); m_controller = MakeUnique<CController>();
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) if (m_runSceneCategory == LevelCategory::Max)
m_controller->StartApp(); m_controller->StartApp();
else else

View File

@ -59,6 +59,11 @@ public:
m_name(name) m_name(name)
{} {}
~CResourceOwningThread()
{
SDL_DetachThread(m_thread);
}
void Start() void Start()
{ {
CSDLMutexWrapper mutex; CSDLMutexWrapper mutex;
@ -74,7 +79,7 @@ public:
SDL_LockMutex(*mutex); SDL_LockMutex(*mutex);
SDL_CreateThread(Run, !m_name.empty() ? m_name.c_str() : nullptr, reinterpret_cast<void*>(&data)); m_thread = SDL_CreateThread(Run, !m_name.empty() ? m_name.c_str() : nullptr, reinterpret_cast<void*>(&data));
while (!condition) while (!condition)
{ {
@ -84,6 +89,13 @@ public:
SDL_UnlockMutex(*mutex); SDL_UnlockMutex(*mutex);
} }
void Join()
{
if (m_thread == nullptr) return;
SDL_WaitThread(m_thread, nullptr);
m_thread = nullptr;
}
private: private:
static int Run(void* data) static int Run(void* data)
{ {
@ -117,4 +129,5 @@ private:
ThreadFunctionPtr m_threadFunction; ThreadFunctionPtr m_threadFunction;
ResourceUPtr m_resource; ResourceUPtr m_resource;
std::string m_name; std::string m_name;
SDL_Thread* m_thread = nullptr;
}; };

View File

@ -19,6 +19,8 @@
#pragma once #pragma once
#include "common/thread/sdl_mutex_wrapper.h"
#include <SDL_thread.h> #include <SDL_thread.h>
/** /**
@ -45,6 +47,16 @@ public:
return m_cond; return m_cond;
} }
void Signal()
{
SDL_CondSignal(m_cond);
}
void Wait(SDL_mutex* mutex)
{
SDL_CondWait(m_cond, mutex);
}
private: private:
SDL_cond* m_cond; SDL_cond* m_cond;
}; };

View File

@ -45,6 +45,16 @@ public:
return m_mutex; return m_mutex;
} }
void Lock()
{
SDL_LockMutex(m_mutex);
}
void Unlock()
{
SDL_UnlockMutex(m_mutex);
}
private: private:
SDL_mutex* m_mutex; SDL_mutex* m_mutex;
}; };

View File

@ -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 <functional>
#include <string>
#include <memory>
/**
* \class CThread
* \brief Wrapper for using SDL_thread with std::function
*/
class CThread
{
public:
using ThreadFunctionPtr = std::function<void()>;
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<ThreadData> data = MakeUnique<ThreadData>();
data->func = m_func;
m_thread = MakeUnique<CResourceOwningThread<ThreadData>>(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<ThreadData> data)
{
data->func();
}
std::unique_ptr<CResourceOwningThread<ThreadData>> m_thread;
ThreadFunctionPtr m_func;
std::string m_name;
};

View File

@ -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 <functional>
#include <string>
#include <queue>
/**
* \class CWorkerThread
* \brief Thread that runs functions, one at a time
*/
class CWorkerThread
{
public:
using ThreadFunctionPtr = std::function<void()>;
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<ThreadFunctionPtr> m_queue;
};

View File

@ -32,7 +32,8 @@ CALSound::CALSound()
m_musicVolume(1.0f), m_musicVolume(1.0f),
m_channelsLimit(2048), m_channelsLimit(2048),
m_device{}, 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; 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<CBuffer>(); if (m_music.find(filename) == m_music.end())
if (buffer->LoadFromFile(filename, static_cast<SoundType>(-1)))
{ {
m_music[filename] = std::move(buffer); auto buffer = MakeUnique<CBuffer>();
return true; if (buffer->LoadFromFile(filename, static_cast<SoundType>(-1)))
{
m_music[filename] = std::move(buffer);
}
} }
} });
return false;
} }
bool CALSound::IsCached(SoundType sound) bool CALSound::IsCached(SoundType sound)
@ -572,53 +574,54 @@ void CALSound::SetListener(const Math::Vector &eye, const Math::Vector &lookat)
alListenerfv(AL_ORIENTATION, orientation); 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) if (!m_enabled)
{ {
return false; return;
} }
CBuffer *buffer = nullptr; m_thread.Start([this, filename, repeat, fadeTime]()
// check if we have music in cache
if (m_music.find(filename) == m_music.end())
{ {
GetLogger()->Debug("Music %s was not cached!\n", filename.c_str()); CBuffer* buffer = nullptr;
auto newBuffer = MakeUnique<CBuffer>(); // check if we have music in cache
buffer = newBuffer.get(); if (m_music.find(filename) == m_music.end())
if (!newBuffer->LoadFromFile(filename, static_cast<SoundType>(-1)))
{ {
return false; GetLogger()->Debug("Music %s was not cached!\n", filename.c_str());
auto newBuffer = MakeUnique<CBuffer>();
buffer = newBuffer.get();
if (!newBuffer->LoadFromFile(filename, static_cast<SoundType>(-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) if (m_currentMusic)
{ {
OldMusic old; OldMusic old;
old.music = std::move(m_currentMusic); old.music = std::move(m_currentMusic);
old.fadeTime = fadeTime; old.fadeTime = fadeTime;
old.currentTime = 0.0f; old.currentTime = 0.0f;
m_oldMusic.push_back(std::move(old)); m_oldMusic.push_back(std::move(old));
} }
m_currentMusic = MakeUnique<CChannel>(); m_currentMusic = MakeUnique<CChannel>();
m_currentMusic->SetBuffer(buffer); m_currentMusic->SetBuffer(buffer);
m_currentMusic->SetVolume(m_musicVolume); m_currentMusic->SetVolume(m_musicVolume);
m_currentMusic->SetLoop(repeat); m_currentMusic->SetLoop(repeat);
m_currentMusic->Play(); m_currentMusic->Play();
});
return true;
} }
bool CALSound::PlayPauseMusic(const std::string &filename, bool repeat) void CALSound::PlayPauseMusic(const std::string &filename, bool repeat)
{ {
if (m_previousMusic.fadeTime > 0.0f) if (m_previousMusic.fadeTime > 0.0f)
{ {
@ -640,7 +643,7 @@ bool CALSound::PlayPauseMusic(const std::string &filename, bool repeat)
m_previousMusic.currentTime = 0.0f; m_previousMusic.currentTime = 0.0f;
} }
} }
return PlayMusic(filename, repeat); PlayMusic(filename, repeat);
} }
void CALSound::StopPauseMusic() 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) void CALSound::StopMusic(float fadeTime)
{ {
if (!m_enabled || m_currentMusic == nullptr) if (!m_enabled || m_currentMusic == nullptr)
@ -698,16 +689,6 @@ bool CALSound::IsPlayingMusic()
return m_currentMusic->IsPlaying(); return m_currentMusic->IsPlaying();
} }
void CALSound::SuspendMusic()
{
if (!m_enabled || m_currentMusic == nullptr)
{
return;
}
m_currentMusic->Stop();
}
bool CALSound::CheckChannel(int &channel) bool CALSound::CheckChannel(int &channel)
{ {
int id = (channel >> 16) & 0xffff; int id = (channel >> 16) & 0xffff;

View File

@ -26,6 +26,8 @@
#include "sound/sound.h" #include "sound/sound.h"
#include "common/thread/worker_thread.h"
#include "sound/oalsound/buffer.h" #include "sound/oalsound/buffer.h"
#include "sound/oalsound/channel.h" #include "sound/oalsound/channel.h"
#include "sound/oalsound/check.h" #include "sound/oalsound/check.h"
@ -83,7 +85,7 @@ public:
bool Create() override; bool Create() override;
bool Cache(SoundType, const std::string &) 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 IsCached(SoundType) override;
bool IsCachedMusic(const std::string &) override; bool IsCachedMusic(const std::string &) override;
@ -106,12 +108,10 @@ public:
bool StopAll() override; bool StopAll() override;
bool MuteAll(bool mute) override; bool MuteAll(bool mute) override;
bool PlayMusic(const std::string &filename, bool repeat, float fadeTime=2.0f) override; void PlayMusic(const std::string &filename, bool repeat, float fadeTime = 2.0f) override;
bool RestartMusic() override;
void SuspendMusic() override;
void StopMusic(float fadeTime=2.0f) override; void StopMusic(float fadeTime=2.0f) override;
bool IsPlayingMusic() 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; void StopPauseMusic() override;
private: private:
@ -134,4 +134,5 @@ private:
OldMusic m_previousMusic; OldMusic m_previousMusic;
Math::Vector m_eye; Math::Vector m_eye;
Math::Vector m_lookat; Math::Vector m_lookat;
CWorkerThread m_thread;
}; };

View File

@ -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) bool CSoundInterface::Cache(SoundType sound, const std::string &file)
{ {
return true; return true;
} }
bool CSoundInterface::CacheMusic(const std::string &file) void CSoundInterface::CacheMusic(const std::string &file)
{ {
return true;
} }
bool CSoundInterface::IsCached(SoundType sound) bool CSoundInterface::IsCached(SoundType sound)
@ -156,17 +147,7 @@ bool CSoundInterface::MuteAll(bool mute)
return true; return true;
} }
bool CSoundInterface::PlayMusic(const std::string &filename, bool repeat, float fadeTime) void CSoundInterface::PlayMusic(const std::string &filename, bool repeat, float fadeTime)
{
return true;
}
bool CSoundInterface::RestartMusic()
{
return true;
}
void CSoundInterface::SuspendMusic()
{ {
} }
@ -176,12 +157,11 @@ void CSoundInterface::StopMusic(float fadeTime)
bool CSoundInterface::IsPlayingMusic() 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() void CSoundInterface::StopPauseMusic()

View File

@ -72,9 +72,6 @@ public:
*/ */
void CacheAll(); void CacheAll();
/** Function called to add all music files to list */
void CacheCommonMusic();
/** Function called to cache sound effect file. /** Function called to cache sound effect file.
* This function is called by plugin interface for each file. * This function is called by plugin interface for each file.
* \param sound - id of a file, will be used to identify sound files * \param sound - id of a file, will be used to identify sound files
@ -85,10 +82,10 @@ public:
/** Function called to cache music file. /** Function called to cache music file.
* This function is called by CRobotMain for each file used in the mission. * This function is called by CRobotMain for each file used in the mission.
* This function is executed asynchronously
* \param file - file to load * \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. /** Function to check if sound effect file was cached.
* \param sound - id of a sound effect file * \param sound - id of a sound effect file
@ -205,22 +202,12 @@ public:
virtual bool MuteAll(bool mute); virtual bool MuteAll(bool mute);
/** Start playing music /** Start playing music
* This function is executed asynchronously
* \param filename - name of file to play * \param filename - name of file to play
* \param repeat - repeat playing * \param repeat - repeat playing
* \param fadeTime - time of transition between music * \param fadeTime - time of transition between music, 0 to disable
* \return return true on success
*/ */
virtual bool PlayMusic(const std::string &filename, bool repeat, float fadeTime=2.0f); virtual void 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();
/** Stop playing music /** Stop playing music
* \return nothing * \return nothing
@ -233,11 +220,12 @@ public:
virtual bool IsPlayingMusic(); virtual bool IsPlayingMusic();
/** Start playing pause music /** Start playing pause music
* This function is executed asynchronously
* \param filename - name of file to play * \param filename - name of file to play
* \param repeat - repeat playing * \param repeat - repeat playing
* \return return true on success * \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 /** Stop playing pause music and return to the mission music
* \return nothing * \return nothing

View File

@ -213,9 +213,10 @@ void CMainUserInterface::ChangePhase(Phase phase)
if ( IsMainMenuPhase(m_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->PlayMusic("music/Intro1.ogg", false);
m_sound->CacheMusic("music/Intro2.ogg");
} }
} }