Index: source/soundmanager/ISoundManager.h =================================================================== --- source/soundmanager/ISoundManager.h +++ source/soundmanager/ISoundManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -50,6 +50,7 @@ virtual void PlayAsGroup(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source, bool ownedSound) = 0; virtual bool InDistress() = 0; + virtual void Hotloader() = 0; }; extern ISoundManager* g_SoundManager; Index: source/soundmanager/SoundManager.h =================================================================== --- source/soundmanager/SoundManager.h +++ source/soundmanager/SoundManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -49,6 +49,7 @@ typedef std::vector ItemsList; typedef std::map ItemsMap; typedef std::map SoundGroupMap; +typedef std::vector ReloadList; class CSoundManagerWorker; @@ -66,8 +67,11 @@ ISoundItem* m_CurrentEnvirons; CSoundManagerWorker* m_Worker; std::mutex m_DistressMutex; + std::mutex m_HotloadingMutex; PlayList* m_PlayListItems; SoundGroupMap m_SoundGroups; + ReloadList m_ReloadList; + float m_Gain; float m_MusicGain; @@ -90,6 +94,7 @@ long m_PlaylistGap; long m_DistressErrCount; long m_DistressTime; + bool m_Hotloading; public: @@ -101,8 +106,13 @@ ISoundItem* LoadItem(const VfsPath& itemPath); ISoundItem* ItemForData(CSoundData* itemData); ISoundItem* ItemForEntity(entity_id_t source, CSoundData* sndData); + CSoundGroup* LoadGroup(const VfsPath& groupPath, CSoundGroup* group); + void ReloadGroup(const VfsPath& groupPath); Status ReloadChangedFiles(const VfsPath& path); + void HotloadSoundGroup(const VfsPath& path); + void HotloadOggFile(const VfsPath& path); + void Hotloader(); void ClearPlayListItems(); void StartPlayList(bool doLoop); @@ -128,6 +138,8 @@ ISoundItem* ItemFromOgg(VfsPath& fname); ISoundItem* GetSoundItem(unsigned long itemRow); + ALSourceHolder* GetSourceHolder(const VfsPath& itemPath); + ALSourceHolder* GetSourceHolder(ISoundItem* theItem); unsigned long Count(); void IdleTask(); @@ -143,8 +155,10 @@ void PlayGroupItem(ISoundItem* anItem, ALfloat groupGain); bool InDistress(); + bool InHotloading(); void SetDistressThroughShortage(); void SetDistressThroughError(); + void SetHotloading(bool hotloading); void Pause(bool pauseIt); void PauseMusic(bool pauseIt); @@ -158,6 +172,9 @@ void SetActionGain(float gain); void SetUIGain(float gain); + bool IsCurrentAmbient(ISoundItem* anItem); + bool IsCurrentMusic(ISoundItem* anItem); + protected: void InitListener(); Status AlcInit(); Index: source/soundmanager/SoundManager.cpp =================================================================== --- source/soundmanager/SoundManager.cpp +++ source/soundmanager/SoundManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 Wildfire Games. +/* Copyright (C) 2020 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -50,6 +50,7 @@ m_Items = new ItemsList; m_DeadItems = new ItemsList; m_Shutdown = false; + m_Hotloading = false; m_WorkerThread = std::thread(RunThread, this); } @@ -120,6 +121,8 @@ if (GetShutdown()) return; + g_SoundManager->Hotloader(); //Check if there're any files pending + int pauseTime = 500; if (g_SoundManager->InDistress()) pauseTime = 50; @@ -177,6 +180,7 @@ ItemsList* m_DeadItems; bool m_Shutdown; + bool m_Hotloading; CSoundManagerWorker(ISoundManager* UNUSED(other)){}; }; @@ -215,12 +219,96 @@ al_ReportError(err, caller, line); } -Status CSoundManager::ReloadChangedFiles(const VfsPath& UNUSED(path)) +Status CSoundManager::ReloadChangedFiles(const VfsPath& path) { - // TODO implement sound file hotloading + CStrW sPrefix = L"audio/"; + std::vector accExt = {Path(".xml").Extension(), Path{".ogg"}.Extension()}; + Path extension = path.Extension(); + + //Only try to reload .ogg or .xml files in the audio/ directory. + if ( (path.string().find(sPrefix) != std::string::npos) || + (std::find(accExt.begin(), accExt.end(), extension) != accExt.end()) ) + { + while (InHotloading()){}; // Wait for current hotloading to finish + SetHotloading(true); // Block worker thread from accessing the list at this time + ReloadList::iterator it = std::find(m_ReloadList.begin(), m_ReloadList.end(), path); + if (it == m_ReloadList.end()) + { + m_ReloadList.push_back(path); + } + SetHotloading(false); + } return INFO::OK; } +void CSoundManager::HotloadSoundGroup(const VfsPath& path) +{ + if (path.Extension() != L".xml") + return; + + // Need to strip leading 'audio/' in path as that is not stored in m_SoundGroups + CStrW sPrefix = L"audio/"; + CStrW sPath = path.string(); + VfsPath vfsPath = sPath.erase(0, sPrefix.size()); + if (m_SoundGroups.find(vfsPath.string()) != m_SoundGroups.end()) + ReloadGroup(vfsPath); + return; +} + +void CSoundManager::HotloadOggFile(const VfsPath& path) +{ + if (path.Extension() != L".ogg") + return; + + ALSourceHolder* thisSourceHolder = GetSourceHolder(path); + if (!thisSourceHolder) + { + // As there is no current sourceHolder for this file it probably + // belongs to one or more(?) soundGroup(s) so reload all. + for (SoundGroupMap::iterator it=m_SoundGroups.begin(); it!=m_SoundGroups.end(); ++it) + { + ReloadGroup(it->first); + } + return; + } + else + { + ISoundItem* newItem = LoadItem(path); // A new ALSource will be allocated + ALSourceHolder* newSourceHolder = GetSourceHolder(newItem); + + ISoundItem* replacedItem = thisSourceHolder->SourceItem; //Store the old soundItem + thisSourceHolder = newSourceHolder; + + // Check if the file changed is currently playing and replace in that case + if (IsCurrentMusic(replacedItem) && replacedItem->IsPlaying()) + { + replacedItem->StopAndDelete(); + SetMusicItem(newItem); + } + + else if (IsCurrentAmbient(replacedItem) && replacedItem->IsPlaying()) + { + replacedItem->StopAndDelete(); + SetAmbientItem(newItem); + } + } +} + +void CSoundManager::Hotloader() +{ + if (InHotloading()) + return; + + SetHotloading(true); + for (const VfsPath& path : m_ReloadList) + { + HotloadSoundGroup(path); + HotloadOggFile(path); + } + m_ReloadList.clear(); + SetHotloading(false); +} + /*static*/ Status CSoundManager::ReloadChangedFileCB(void* param, const VfsPath& path) { return static_cast(param)->ReloadChangedFiles(path); @@ -229,13 +317,14 @@ CSoundManager::CSoundManager() : m_Context(nullptr), m_Device(nullptr), m_ALSourceBuffer(nullptr), m_CurrentTune(nullptr), m_CurrentEnvirons(nullptr), - m_Worker(nullptr), m_DistressMutex(), m_PlayListItems(nullptr), m_SoundGroups(), + m_Worker(nullptr), m_DistressMutex(), m_HotloadingMutex(), m_PlayListItems(nullptr), + m_SoundGroups(), m_ReloadList(), m_Gain(.5f), m_MusicGain(.5f), m_AmbientGain(.5f), m_ActionGain(.5f), m_UIGain(.5f), m_Enabled(false), m_BufferSize(98304), m_BufferCount(50), m_SoundEnabled(true), m_MusicEnabled(true), m_MusicPaused(false), m_AmbientPaused(false), m_ActionPaused(false), m_RunningPlaylist(false), m_PlayingPlaylist(false), m_LoopingPlaylist(false), - m_PlaylistGap(0), m_DistressErrCount(0), m_DistressTime(0) + m_PlaylistGap(0), m_DistressErrCount(0), m_DistressTime(0), m_Hotloading(false) { CFG_GET_VAL("sound.mastergain", m_Gain); CFG_GET_VAL("sound.musicgain", m_MusicGain); @@ -374,6 +463,12 @@ return true; } +bool CSoundManager::InHotloading() +{ + std::lock_guard lock(m_HotloadingMutex); + return m_Hotloading; +} + void CSoundManager::SetDistressThroughShortage() { std::lock_guard lock(m_DistressMutex); @@ -393,6 +488,11 @@ m_DistressErrCount++; } +void CSoundManager::SetHotloading(bool hotloading) +{ + std::lock_guard lock(m_HotloadingMutex); + m_Hotloading = hotloading; +} ALuint CSoundManager::GetALSource(ISoundItem* anItem) @@ -421,6 +521,30 @@ } } +ALSourceHolder* CSoundManager::GetSourceHolder(const VfsPath& itemPath) +{ + for (int x = 0; x < SOURCE_NUM; ++x) + { + if (m_ALSourceBuffer[x].SourceItem != NULL && m_ALSourceBuffer[x].SourceItem->GetName() == itemPath) + { + return &m_ALSourceBuffer[x]; + } + } + return 0; +} + +ALSourceHolder* CSoundManager::GetSourceHolder(ISoundItem* theItem) +{ + for (int x = 0; x < SOURCE_NUM; ++x) + { + if (m_ALSourceBuffer[x].SourceItem != NULL && m_ALSourceBuffer[x].SourceItem == theItem) + { + return &m_ALSourceBuffer[x]; + } + } + return 0; +} + long CSoundManager::GetBufferCount() { return m_BufferCount; @@ -517,6 +641,36 @@ return NULL; } + +CSoundGroup* CSoundManager::LoadGroup(const VfsPath& groupPath, CSoundGroup* group) +{ + if (group == NULL) + { + group = new CSoundGroup(); + } + + if (!group->LoadSoundGroup(L"audio/" + groupPath.string())) + { + LOGERROR("Failed to load sound group '%s'", groupPath.string8()); + delete group; + group = NULL; + } + // Cache the sound group (or the null, if it failed) + m_SoundGroups[groupPath.string()] = group; + return group; +} + +void CSoundManager::ReloadGroup(const VfsPath& groupPath) +{ + CStrW sPath = groupPath.string(); + SoundGroupMap::iterator it = m_SoundGroups.find(sPath); + if (it != m_SoundGroups.end()) + { + delete it->second; + LoadGroup(groupPath, NULL); + } +} + ISoundItem* CSoundManager::ItemForData(CSoundData* itemData) { AL_CHECK; @@ -641,23 +795,11 @@ void CSoundManager::PlayAsGroup(const VfsPath& groupPath, const CVector3D& sourcePos, entity_id_t source, bool ownedSound) { // Make sure the sound group is loaded - CSoundGroup* group; + CSoundGroup* group = NULL; if (m_SoundGroups.find(groupPath.string()) == m_SoundGroups.end()) - { - group = new CSoundGroup(); - if (!group->LoadSoundGroup(L"audio/" + groupPath.string())) - { - LOGERROR("Failed to load sound group '%s'", groupPath.string8()); - delete group; - group = NULL; - } - // Cache the sound group (or the null, if it failed) - m_SoundGroups[groupPath.string()] = group; - } + group = LoadGroup(groupPath, group); else - { group = m_SoundGroups[groupPath.string()]; - } // Failed to load group -> do nothing if (group && (ownedSound || !group->TestFlag(eOwnerOnly))) @@ -808,6 +950,16 @@ AL_CHECK; } } + +bool CSoundManager::IsCurrentAmbient(ISoundItem* anItem) +{ + return m_CurrentEnvirons == anItem; +} + +bool CSoundManager::IsCurrentMusic(ISoundItem* anItem) +{ + return m_CurrentTune == anItem; +} #else // CONFIG2_AUDIO void ISoundManager::CreateSoundManager(){}