diff --git a/src/client/sound_openal.cpp b/src/client/sound_openal.cpp index 9baa0d152..e8bb6290e 100644 --- a/src/client/sound_openal.cpp +++ b/src/client/sound_openal.cpp @@ -38,5 +38,5 @@ std::shared_ptr createSoundManagerSingleton() std::unique_ptr createOpenALSoundManager(SoundManagerSingleton *smg, std::unique_ptr fallback_path_provider) { - return std::make_unique(smg, std::move(fallback_path_provider)); + return std::make_unique(smg, std::move(fallback_path_provider)); }; diff --git a/src/client/sound_openal_internal.cpp b/src/client/sound_openal_internal.cpp index 5ab9e4c7c..208c33e45 100644 --- a/src/client/sound_openal_internal.cpp +++ b/src/client/sound_openal_internal.cpp @@ -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 @@ -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 &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 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> &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 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(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(filepath)); +} + +void OpenALSoundManager::loadSoundDataNoCheck(const std::string &name, std::string &&filedata) +{ // remember for lazy loading m_sound_datas_unopen.emplace(name, std::make_unique(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; + + if constexpr (std::is_same_v) + return MsgResult::Empty; + else if constexpr (std::is_same_v) + reportRemovedSound(msg.id); + else if constexpr (std::is_same_v) + 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_}); +} diff --git a/src/client/sound_openal_internal.h b/src/client/sound_openal_internal.h index 7fcb73a34..65e9622f2 100644 --- a/src/client/sound_openal_internal.h +++ b/src/client/sound_openal_internal.h @@ -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 @@ -48,6 +51,7 @@ with this program; ifnot, write to the Free Software Foundation, Inc., #include #include #include +#include #include @@ -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 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 m_queue_to_mgr; + MutexedQueue 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 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 fallback_path_provider) : + m_sound_manager(smg, std::move(fallback_path_provider)) + { + m_sound_manager.start(); + } + + ~ProxySoundManager() override; + /* Interface */ void step(f32 dtime) override;