diff --git a/src/music/dmusic.cpp b/src/music/dmusic.cpp index 68de8484f8..e22de5152f 100644 --- a/src/music/dmusic.cpp +++ b/src/music/dmusic.cpp @@ -121,7 +121,7 @@ struct PlaybackSegment { bool loop; }; -static struct { +struct DMusicPlayback { bool shutdown; ///< flag to indicate playback thread shutdown bool playing; ///< flag indicating that playback is active bool do_start; ///< flag for starting playback of next_file at next opportunity @@ -132,14 +132,29 @@ static struct { MidiFile next_file; ///< upcoming file to play PlaybackSegment next_segment; ///< segment info for upcoming file -} _playback; -/** Handle to our worker thread. */ -static std::thread _dmusic_thread; -/** Event to signal the thread that it should look at a state change. */ -static HANDLE _thread_event = nullptr; -/** Lock access to playback data that is not thread-safe. */ -static std::mutex _thread_mutex; + /** Handle to our worker thread. */ + std::thread dmusic_thread; + /** Event to signal the thread that it should look at a state change. */ + HANDLE thread_event = nullptr; + /** Lock access to playback data that is not thread-safe. */ + std::mutex thread_mutex; + + void StopThread() + { + if (this->dmusic_thread.joinable()) { + this->shutdown = true; + SetEvent(this->thread_event); + this->dmusic_thread.join(); + } + } + + ~DMusicPlayback() + { + this->StopThread(); + } +}; +static DMusicPlayback _playback; /** The direct music object manages buffers and ports. */ static IDirectMusic *_music = nullptr; @@ -610,7 +625,7 @@ static void MidiThreadProc() DWORD next_timeout = 1000; while (true) { /* Wait for a signal from the GUI thread or until the time for the next event has come. */ - DWORD wfso = WaitForSingleObject(_thread_event, next_timeout); + DWORD wfso = WaitForSingleObject(_playback.thread_event, next_timeout); if (_playback.shutdown) { _playback.playing = false; @@ -636,7 +651,7 @@ static void MidiThreadProc() DEBUG(driver, 2, "DMusic thread: Starting playback"); { /* New scope to limit the time the mutex is locked. */ - std::lock_guard lock(_thread_mutex); + std::lock_guard lock(_playback.thread_mutex); current_file.MoveFrom(_playback.next_file); std::swap(_playback.next_segment, current_segment); @@ -1148,10 +1163,10 @@ const char *MusicDriver_DMusic::Start(const StringList &parm) if (dls != nullptr) return dls; /* Create playback thread and synchronization primitives. */ - _thread_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); - if (_thread_event == nullptr) return "Can't create thread shutdown event"; + _playback.thread_event = CreateEvent(nullptr, FALSE, FALSE, nullptr); + if (_playback.thread_event == nullptr) return "Can't create thread shutdown event"; - if (!StartNewThread(&_dmusic_thread, "ottd:dmusic", &MidiThreadProc)) return "Can't create MIDI output thread"; + if (!StartNewThread(&_playback.dmusic_thread, "ottd:dmusic", &MidiThreadProc)) return "Can't create MIDI output thread"; return nullptr; } @@ -1165,11 +1180,7 @@ MusicDriver_DMusic::~MusicDriver_DMusic() void MusicDriver_DMusic::Stop() { - if (_dmusic_thread.joinable()) { - _playback.shutdown = true; - SetEvent(_thread_event); - _dmusic_thread.join(); - } + _playback.StopThread(); /* Unloaded any instruments we loaded. */ if (!_dls_downloads.empty()) { @@ -1203,7 +1214,7 @@ void MusicDriver_DMusic::Stop() _music = nullptr; } - CloseHandle(_thread_event); + CloseHandle(_playback.thread_event); CoUninitialize(); } @@ -1211,7 +1222,7 @@ void MusicDriver_DMusic::Stop() void MusicDriver_DMusic::PlaySong(const MusicSongInfo &song) { - std::lock_guard lock(_thread_mutex); + std::lock_guard lock(_playback.thread_mutex); if (!_playback.next_file.LoadSong(song)) return; @@ -1220,14 +1231,14 @@ void MusicDriver_DMusic::PlaySong(const MusicSongInfo &song) _playback.next_segment.loop = song.loop; _playback.do_start = true; - SetEvent(_thread_event); + SetEvent(_playback.thread_event); } void MusicDriver_DMusic::StopSong() { _playback.do_stop = true; - SetEvent(_thread_event); + SetEvent(_playback.thread_event); } diff --git a/src/sl/saveload.cpp b/src/sl/saveload.cpp index e6df6f2130..9082be1a35 100644 --- a/src/sl/saveload.cpp +++ b/src/sl/saveload.cpp @@ -479,8 +479,56 @@ void NORETURN CDECL SlErrorCorruptFmt(const char *format, ...) } typedef void (*AsyncSaveFinishProc)(); ///< Callback for when the savegame loading is finished. -static std::atomic _async_save_finish; ///< Callback to call when the savegame loading is finished. -static std::thread _save_thread; ///< The thread we're using to compress and write a savegame + +struct AsyncSaveThread { + std::atomic exit_thread; ///< Signal that the thread should exit early + std::atomic finish_proc; ///< Callback to call when the savegame saving is finished. + std::thread save_thread; ///< The thread we're using to compress and write a savegame + + void SetAsyncSaveFinish(AsyncSaveFinishProc proc) + { + if (_exit_game || this->exit_thread.load(std::memory_order_relaxed)) return; + + while (this->finish_proc.load(std::memory_order_acquire) != nullptr) { + CSleep(10); + if (_exit_game || this->exit_thread.load(std::memory_order_relaxed)) return; + } + + this->finish_proc.store(proc, std::memory_order_release); + } + + void ProcessAsyncSaveFinish() + { + AsyncSaveFinishProc proc = this->finish_proc.exchange(nullptr, std::memory_order_acq_rel); + if (proc == nullptr) return; + + proc(); + + if (this->save_thread.joinable()) { + this->save_thread.join(); + } + } + + void WaitTillSaved() + { + if (!this->save_thread.joinable()) return; + + this->save_thread.join(); + + /* Make sure every other state is handled properly as well. */ + this->ProcessAsyncSaveFinish(); + } + + ~AsyncSaveThread() + { + this->exit_thread.store(true, std::memory_order_relaxed); + + if (this->save_thread.joinable()) { + this->save_thread.join(); + } + } +}; +static AsyncSaveThread _async_save_thread; /** * Called by save thread to tell we finished saving. @@ -488,10 +536,7 @@ static std::thread _save_thread; ///< The thread we'r */ static void SetAsyncSaveFinish(AsyncSaveFinishProc proc) { - if (_exit_game) return; - while (_async_save_finish.load(std::memory_order_acquire) != nullptr) CSleep(10); - - _async_save_finish.store(proc, std::memory_order_release); + _async_save_thread.SetAsyncSaveFinish(proc); } /** @@ -499,14 +544,7 @@ static void SetAsyncSaveFinish(AsyncSaveFinishProc proc) */ void ProcessAsyncSaveFinish() { - AsyncSaveFinishProc proc = _async_save_finish.exchange(nullptr, std::memory_order_acq_rel); - if (proc == nullptr) return; - - proc(); - - if (_save_thread.joinable()) { - _save_thread.join(); - } + _async_save_thread.ProcessAsyncSaveFinish(); } /** @@ -3492,12 +3530,7 @@ static SaveOrLoadResult SaveFileToDisk(bool threaded) void WaitTillSaved() { - if (!_save_thread.joinable()) return; - - _save_thread.join(); - - /* Make sure every other state is handled properly as well. */ - ProcessAsyncSaveFinish(); + _async_save_thread.WaitTillSaved(); } /** @@ -3523,7 +3556,7 @@ static SaveOrLoadResult DoSave(SaveFilter *writer, bool threaded) SaveFileStart(); - if (!threaded || !StartNewThread(&_save_thread, "ottd:savegame", &SaveFileToDisk, true)) { + if (!threaded || !StartNewThread(&_async_save_thread.save_thread, "ottd:savegame", &SaveFileToDisk, true)) { if (threaded) DEBUG(sl, 1, "Cannot create savegame thread, reverting to single-threaded mode..."); SaveOrLoadResult result = SaveFileToDisk(false);