Move soundmanager into its own thread

Fixes sound queues running empty on client step hiccups.
This commit is contained in:
Desour 2023-06-24 20:37:31 +02:00 committed by DS
parent 591e45657f
commit 8fa2ea71ef
3 changed files with 384 additions and 12 deletions

@ -38,5 +38,5 @@ std::shared_ptr<SoundManagerSingleton> createSoundManagerSingleton()
std::unique_ptr<ISoundManager> createOpenALSoundManager(SoundManagerSingleton *smg,
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider)
{
return std::make_unique<OpenALSoundManager>(smg, std::move(fallback_path_provider));
return std::make_unique<ProxySoundManager>(smg, std::move(fallback_path_provider));
};

@ -25,7 +25,6 @@ with this program; ifnot, write to the Free Software Foundation, Inc.,
#include "sound_openal_internal.h"
#include "util/numeric.h" // myrand()
#include "../sound.h"
#include "filesys.h"
#include "settings.h"
#include <algorithm>
@ -806,7 +805,7 @@ std::string OpenALSoundManager::getLoadedSoundNameFromGroup(const std::string &g
auto it_groups = m_sound_groups.find(group_name);
if (it_groups == m_sound_groups.end())
return chosen_sound_name;
return "";
std::vector<std::string> &group_sounds = it_groups->second;
while (!group_sounds.empty()) {
@ -817,7 +816,7 @@ std::string OpenALSoundManager::getLoadedSoundNameFromGroup(const std::string &g
// find chosen one
std::shared_ptr<ISoundDataOpen> snd = openSingleSound(chosen_sound_name);
if (snd)
break;
return chosen_sound_name;
// it doesn't exist
// remove it from the group and try again
@ -825,7 +824,7 @@ std::string OpenALSoundManager::getLoadedSoundNameFromGroup(const std::string &g
group_sounds.pop_back();
}
return chosen_sound_name;
return "";
}
std::string OpenALSoundManager::getOrLoadLoadedSoundNameFromGroup(const std::string &group_name)
@ -887,8 +886,7 @@ void OpenALSoundManager::playSoundGeneric(sound_handle_t id, const std::string &
bool loop, f32 volume, f32 fade, f32 pitch, bool use_local_fallback,
f32 start_time, const std::optional<std::pair<v3f, v3f>> &pos_vel_opt)
{
if (id == 0)
id = allocateId(1);
assert(id != 0);
if (group_name.empty()) {
reportRemovedSound(id);
@ -963,6 +961,7 @@ int OpenALSoundManager::removeDeadSounds()
OpenALSoundManager::OpenALSoundManager(SoundManagerSingleton *smg,
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider) :
Thread("OpenALSoundManager"),
m_fallback_path_provider(std::move(fallback_path_provider)),
m_device(smg->m_device.get()),
m_context(smg->m_context.get())
@ -1053,8 +1052,7 @@ bool OpenALSoundManager::loadSoundFile(const std::string &name, const std::strin
if (!fs::IsFile(filepath))
return false;
// remember for lazy loading
m_sound_datas_unopen.emplace(name, std::make_unique<SoundDataUnopenFile>(filepath));
loadSoundFileNoCheck(name, filepath);
return true;
}
@ -1064,9 +1062,20 @@ bool OpenALSoundManager::loadSoundData(const std::string &name, std::string &&fi
if (m_sound_datas_open.count(name) != 0 || m_sound_datas_unopen.count(name) != 0)
return false;
loadSoundDataNoCheck(name, std::move(filedata));
return true;
}
void OpenALSoundManager::loadSoundFileNoCheck(const std::string &name, const std::string &filepath)
{
// remember for lazy loading
m_sound_datas_unopen.emplace(name, std::make_unique<SoundDataUnopenFile>(filepath));
}
void OpenALSoundManager::loadSoundDataNoCheck(const std::string &name, std::string &&filedata)
{
// remember for lazy loading
m_sound_datas_unopen.emplace(name, std::make_unique<SoundDataUnopenBuffer>(std::move(filedata)));
return true;
}
void OpenALSoundManager::addSoundToGroup(const std::string &sound_name, const std::string &group_name)
@ -1126,3 +1135,229 @@ void OpenALSoundManager::updateSoundPosVel(sound_handle_t id, const v3f &pos_,
return;
i->second->updatePosVel(pos, vel);
}
void *OpenALSoundManager::run()
{
using namespace sound_manager_messages_to_mgr;
struct MsgVisitor {
enum class Result { Ok, Empty, StopRequested };
OpenALSoundManager &mgr;
Result operator()(std::monostate &&) {
return Result::Empty; }
Result operator()(PauseAll &&) {
mgr.pauseAll(); return Result::Ok; }
Result operator()(ResumeAll &&) {
mgr.resumeAll(); return Result::Ok; }
Result operator()(UpdateListener &&msg) {
mgr.updateListener(msg.pos_, msg.vel_, msg.at_, msg.up_); return Result::Ok; }
Result operator()(SetListenerGain &&msg) {
mgr.setListenerGain(msg.gain); return Result::Ok; }
Result operator()(LoadSoundFile &&msg) {
mgr.loadSoundFileNoCheck(msg.name, msg.filepath); return Result::Ok; }
Result operator()(LoadSoundData &&msg) {
mgr.loadSoundDataNoCheck(msg.name, std::move(msg.filedata)); return Result::Ok; }
Result operator()(AddSoundToGroup &&msg) {
mgr.addSoundToGroup(msg.sound_name, msg.group_name); return Result::Ok; }
Result operator()(PlaySound &&msg) {
mgr.playSound(msg.id, msg.spec); return Result::Ok; }
Result operator()(PlaySoundAt &&msg) {
mgr.playSoundAt(msg.id, msg.spec, msg.pos_, msg.vel_); return Result::Ok; }
Result operator()(StopSound &&msg) {
mgr.stopSound(msg.sound); return Result::Ok; }
Result operator()(FadeSound &&msg) {
mgr.fadeSound(msg.soundid, msg.step, msg.target_gain); return Result::Ok; }
Result operator()(UpdateSoundPosVel &&msg) {
mgr.updateSoundPosVel(msg.sound, msg.pos_, msg.vel_); return Result::Ok; }
Result operator()(PleaseStop &&msg) {
return Result::StopRequested; }
};
u64 t_step_start = porting::getTimeMs();
while (true) {
auto get_time_since_last_step = [&] {
return (f32)(porting::getTimeMs() - t_step_start);
};
auto get_remaining_timeout = [&] {
return (s32)((1.0e3f * PROXYSOUNDMGR_DTIME) - get_time_since_last_step());
};
bool stop_requested = false;
while (true) {
SoundManagerMsgToMgr msg =
m_queue_to_mgr.pop_frontNoEx(std::max(get_remaining_timeout(), 0));
MsgVisitor::Result res = std::visit(MsgVisitor{*this}, std::move(msg));
if (res == MsgVisitor::Result::Empty && get_remaining_timeout() <= 0) {
break; // finished sleeping
} else if (res == MsgVisitor::Result::StopRequested) {
stop_requested = true;
break;
}
}
if (stop_requested)
break;
f32 dtime = get_time_since_last_step();
t_step_start = porting::getTimeMs();
step(dtime);
}
send(sound_manager_messages_to_proxy::Stopped{});
return nullptr;
}
/*
* ProxySoundManager class
*/
ProxySoundManager::MsgResult ProxySoundManager::handleMsg(SoundManagerMsgToProxy &&msg)
{
using namespace sound_manager_messages_to_proxy;
return std::visit([&](auto &&msg) {
using T = std::decay_t<decltype(msg)>;
if constexpr (std::is_same_v<T, std::monostate>)
return MsgResult::Empty;
else if constexpr (std::is_same_v<T, ReportRemovedSound>)
reportRemovedSound(msg.id);
else if constexpr (std::is_same_v<T, Stopped>)
return MsgResult::Stopped;
return MsgResult::Ok;
},
std::move(msg));
}
ProxySoundManager::~ProxySoundManager()
{
if (m_sound_manager.isRunning()) {
send(sound_manager_messages_to_mgr::PleaseStop{});
// recv until it stopped
auto recv = [&] {
return m_sound_manager.m_queue_to_proxy.pop_frontNoEx();
};
while (true) {
if (handleMsg(recv()) == MsgResult::Stopped)
break;
}
// join
m_sound_manager.stop();
SANITY_CHECK(m_sound_manager.wait());
}
}
void ProxySoundManager::step(f32 dtime)
{
auto recv = [&] {
return m_sound_manager.m_queue_to_proxy.pop_frontNoEx(0);
};
while (true) {
MsgResult res = handleMsg(recv());
if (res == MsgResult::Empty)
break;
else if (res == MsgResult::Stopped)
throw std::runtime_error("OpenALSoundManager stopped unexpectedly");
}
}
void ProxySoundManager::pauseAll()
{
send(sound_manager_messages_to_mgr::PauseAll{});
}
void ProxySoundManager::resumeAll()
{
send(sound_manager_messages_to_mgr::ResumeAll{});
}
void ProxySoundManager::updateListener(const v3f &pos_, const v3f &vel_,
const v3f &at_, const v3f &up_)
{
send(sound_manager_messages_to_mgr::UpdateListener{pos_, vel_, at_, up_});
}
void ProxySoundManager::setListenerGain(f32 gain)
{
send(sound_manager_messages_to_mgr::SetListenerGain{gain});
}
bool ProxySoundManager::loadSoundFile(const std::string &name,
const std::string &filepath)
{
// do not add twice
if (m_known_sound_names.count(name) != 0)
return false;
// coarse check
if (!fs::IsFile(filepath))
return false;
send(sound_manager_messages_to_mgr::LoadSoundFile{name, filepath});
m_known_sound_names.insert(name);
return true;
}
bool ProxySoundManager::loadSoundData(const std::string &name, std::string &&filedata)
{
// do not add twice
if (m_known_sound_names.count(name) != 0)
return false;
send(sound_manager_messages_to_mgr::LoadSoundData{name, std::move(filedata)});
m_known_sound_names.insert(name);
return true;
}
void ProxySoundManager::addSoundToGroup(const std::string &sound_name,
const std::string &group_name)
{
send(sound_manager_messages_to_mgr::AddSoundToGroup{sound_name, group_name});
}
void ProxySoundManager::playSound(sound_handle_t id, const SoundSpec &spec)
{
if (id == 0)
id = allocateId(1);
send(sound_manager_messages_to_mgr::PlaySound{id, spec});
}
void ProxySoundManager::playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos_,
const v3f &vel_)
{
if (id == 0)
id = allocateId(1);
send(sound_manager_messages_to_mgr::PlaySoundAt{id, spec, pos_, vel_});
}
void ProxySoundManager::stopSound(sound_handle_t sound)
{
send(sound_manager_messages_to_mgr::StopSound{sound});
}
void ProxySoundManager::fadeSound(sound_handle_t soundid, f32 step, f32 target_gain)
{
send(sound_manager_messages_to_mgr::FadeSound{soundid, step, target_gain});
}
void ProxySoundManager::updateSoundPosVel(sound_handle_t sound, const v3f &pos_, const v3f &vel_)
{
send(sound_manager_messages_to_mgr::UpdateSoundPosVel{sound, pos_, vel_});
}

@ -27,7 +27,10 @@ with this program; ifnot, write to the Free Software Foundation, Inc.,
#include "log.h"
#include "porting.h"
#include "sound_openal.h"
#include "../sound.h"
#include "threading/thread.h"
#include "util/basic_macros.h"
#include "util/container.h"
#if defined(_WIN32)
#include <al.h>
@ -48,6 +51,7 @@ with this program; ifnot, write to the Free Software Foundation, Inc.,
#include <optional>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>
@ -141,6 +145,8 @@ constexpr f32 SOUND_DURATION_MAX_SINGLE = 3.0f;
constexpr f32 MIN_STREAM_BUFFER_LENGTH = 1.0f;
// duration in seconds of one bigstep
constexpr f32 STREAM_BIGSTEP_TIME = 0.3f;
// step duration for the ProxySoundManager, in seconds
constexpr f32 PROXYSOUNDMGR_DTIME = 0.016f;
static_assert(MIN_STREAM_BUFFER_LENGTH > STREAM_BIGSTEP_TIME * 2.0f,
"See [Streaming of sounds].");
@ -506,10 +512,67 @@ public:
/*
* The public ISoundManager interface
* The SoundManager thread
*/
class OpenALSoundManager final : public ISoundManager
namespace sound_manager_messages_to_mgr {
struct PauseAll {};
struct ResumeAll {};
struct UpdateListener { v3f pos_; v3f vel_; v3f at_; v3f up_; };
struct SetListenerGain { f32 gain; };
struct LoadSoundFile { std::string name; std::string filepath; };
struct LoadSoundData { std::string name; std::string filedata; };
struct AddSoundToGroup { std::string sound_name; std::string group_name; };
struct PlaySound { sound_handle_t id; SoundSpec spec; };
struct PlaySoundAt { sound_handle_t id; SoundSpec spec; v3f pos_; v3f vel_; };
struct StopSound { sound_handle_t sound; };
struct FadeSound { sound_handle_t soundid; f32 step; f32 target_gain; };
struct UpdateSoundPosVel { sound_handle_t sound; v3f pos_; v3f vel_; };
struct PleaseStop {};
}
using SoundManagerMsgToMgr = std::variant<
std::monostate,
sound_manager_messages_to_mgr::PauseAll,
sound_manager_messages_to_mgr::ResumeAll,
sound_manager_messages_to_mgr::UpdateListener,
sound_manager_messages_to_mgr::SetListenerGain,
sound_manager_messages_to_mgr::LoadSoundFile,
sound_manager_messages_to_mgr::LoadSoundData,
sound_manager_messages_to_mgr::AddSoundToGroup,
sound_manager_messages_to_mgr::PlaySound,
sound_manager_messages_to_mgr::PlaySoundAt,
sound_manager_messages_to_mgr::StopSound,
sound_manager_messages_to_mgr::FadeSound,
sound_manager_messages_to_mgr::UpdateSoundPosVel,
sound_manager_messages_to_mgr::PleaseStop
>;
namespace sound_manager_messages_to_proxy {
struct ReportRemovedSound { sound_handle_t id; };
struct Stopped {};
}
using SoundManagerMsgToProxy = std::variant<
std::monostate,
sound_manager_messages_to_proxy::ReportRemovedSound,
sound_manager_messages_to_proxy::Stopped
>;
// not an ISoundManager. doesn't allocate ids, and doesn't accept id 0
class OpenALSoundManager final : public Thread
{
private:
std::unique_ptr<SoundFallbackPathProvider> m_fallback_path_provider;
@ -540,6 +603,11 @@ private:
// if true, all sounds will be directly paused after creation
bool m_is_paused = false;
public:
// used for communication with ProxySoundManager
MutexedQueue<SoundManagerMsgToMgr> m_queue_to_mgr;
MutexedQueue<SoundManagerMsgToProxy> m_queue_to_proxy;
private:
void stepStreams(f32 dtime);
void doFades(f32 dtime);
@ -591,6 +659,75 @@ public:
DISABLE_CLASS_COPY(OpenALSoundManager)
private:
/* Similar to ISoundManager */
void step(f32 dtime);
void pauseAll();
void resumeAll();
void updateListener(const v3f &pos_, const v3f &vel_, const v3f &at_, const v3f &up_);
void setListenerGain(f32 gain);
bool loadSoundFile(const std::string &name, const std::string &filepath);
bool loadSoundData(const std::string &name, std::string &&filedata);
void loadSoundFileNoCheck(const std::string &name, const std::string &filepath);
void loadSoundDataNoCheck(const std::string &name, std::string &&filedata);
void addSoundToGroup(const std::string &sound_name, const std::string &group_name);
void playSound(sound_handle_t id, const SoundSpec &spec);
void playSoundAt(sound_handle_t id, const SoundSpec &spec, const v3f &pos_,
const v3f &vel_);
void stopSound(sound_handle_t sound);
void fadeSound(sound_handle_t soundid, f32 step, f32 target_gain);
void updateSoundPosVel(sound_handle_t sound, const v3f &pos_, const v3f &vel_);
protected:
/* Thread stuff */
void *run() override;
private:
void send(SoundManagerMsgToProxy msg)
{
m_queue_to_proxy.push_back(std::move(msg));
}
void reportRemovedSound(sound_handle_t id)
{
send(sound_manager_messages_to_proxy::ReportRemovedSound{id});
}
};
/*
* The public ISoundManager interface
*/
class ProxySoundManager final : public ISoundManager
{
OpenALSoundManager m_sound_manager;
// sound names from loadSoundData and loadSoundFile
std::unordered_set<std::string> m_known_sound_names;
void send(SoundManagerMsgToMgr msg)
{
m_sound_manager.m_queue_to_mgr.push_back(std::move(msg));
}
enum class MsgResult { Ok, Empty, Stopped};
MsgResult handleMsg(SoundManagerMsgToProxy &&msg);
public:
ProxySoundManager(SoundManagerSingleton *smg,
std::unique_ptr<SoundFallbackPathProvider> fallback_path_provider) :
m_sound_manager(smg, std::move(fallback_path_provider))
{
m_sound_manager.start();
}
~ProxySoundManager() override;
/* Interface */
void step(f32 dtime) override;