diff --git a/.efrocachemap b/.efrocachemap index 69a613c4..665bb33f 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4056,26 +4056,26 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "af600306cf8085f909e40a7d2129a73b", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "54f7cdbdbc16601ff1f841e48cecf05c", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cc6387a5d3a8f36b7bf667f9b7e719df", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "0de15cf59051ba17ee91e3d3ac25be93", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "345c4646377145cf88b30fcba122f42b", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "4dae703893e19bed1e8a16029a646104", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "573adb2913309a6c375a6d3b92a20208", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "3db3c7e41182fd05f5f9731fee3002d0", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1e3e5ee34645928ec3bdc3c52de98839", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "41354efa8cdbb83a3b2e4cd7fce6b308", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "a304c1a0d2d2f5bdbc31a27ec899a95b", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "6b236e3246ded0460fcc7ea50fc51b36", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "e26b4e3b5e2bca5606d2ac674fcc7496", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "ae954a12775213c64d45b535b289ebff", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "960c3e2fd9c3e19de75fd92546447890", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "e0c05531b48ec5e36da10783280957fe", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "e1c137fdefcf34a642c0962999973eff", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "bdf75b68e6b0e1c8dcfa76ac04306fbb", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "5f01ab596c3d389c95761db531b1766b", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "4826ca9c757dbf38749710bc94844aca", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "8ea626f6dd70d998ee77c58fffc51545", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "d7ad10904fe7c4d4555366ccb1feedcb", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "a9eea92d521e97b1772b5e44b402ce8a", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "57043f40971d800d27ee6d646aae8d61", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "3e4009d7fa4b90abc526f56361ecab05", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "334cdc0688e66a1bc75cd05bae1729c7", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "95278d80378be5b61026253492cbfa70", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "0a3da8e5264a7b733960e83a0e8c4bba", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "a089d4aaa39e18553cf0a70a77b4cfcd", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "e66c4eceb79710b8fda2bfea781e241a", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "6b3021b9a7584da86bbb95324e81e851", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "6fe90d50f905a0da9fa52c39a458d1e3", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "afc7ed826486aec82613832865177570", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "ae8b6dad770793188aad8e2966189bb3", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "7622dde1a021152cac42e7db3e803392", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8a4b1e521bf668cc6ec6a65519defb12", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "9a69c0d4c9ae319595843b16f14795fc", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "89c02260fb4781f5e293658cecbb363f", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "3165d4230069c22300abfff8abf1d714", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "41e46dfdbb542e3e823f6aee87e93ac9", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "20cc128ff9d44f9d74e4301c6d49f48f", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "f93bc8f98ee31f39b54ab46264eccb22", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "20cc128ff9d44f9d74e4301c6d49f48f", @@ -4092,14 +4092,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "c8715c85010ea431d7346f40f5421819", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "49775819d4ba9af15061080d17377a18", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "c8715c85010ea431d7346f40f5421819", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "99e44969ef0f9421f21cb32554464bc7", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "a14441d4ff9adae6e015a9f0d107a61a", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "90b5c353d5296be7e3b06e8c823564bb", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "84409ce44260a641295c1459e4d3af8f", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "35c58dd0a7482bb5f1ef5d6edb647c46", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "a7c2895f59f75b767258277c766e9ed4", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "e0430eeeb324ccfca8ace41d743e069c", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "d594057005fc56c8576f555f892b3dc4", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "750c2964308cd3f3e5986fcda9a25706", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "772af7da6a115f53b0b3f6a4afd3baec", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "178c1a53a7ad50297aed68d0ca3a1476", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "b011bc2b6437995f2d33f5215b4ffa36", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "a758a4f98336208381b093aacb735878", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "6c86545fab2327105114676a20ca5e68", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "f75525fdc9f7db4a81ca9bae6a79add5", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "2e297069baec404d43ccdb18abeef658", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/ballistica/base/mgen/pyembed/binding_base.inc": "ba8ce3ca3858b4c2d20db68f99b788b2", diff --git a/CHANGELOG.md b/CHANGELOG.md index fa184489..21aade8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.28 (build 21453, api 8, 2023-10-13) +### 1.7.28 (build 21465, api 8, 2023-10-14) - Massively cleaned up code related to rendering and window systems (OpenGL, SDL, etc). This code had been growing into a nasty tangle for 15 years @@ -144,6 +144,8 @@ that you will be cutting off support for older clients if you use 35. - Fixed a bug with screen-messages animating off screen too fast when frame rates are high. +- Added a proper graceful shutdown process for the audio server. This should + result in fewer ugly pops and warning messages when the app is quit. ### 1.7.27 (build 21282, api 8, 2023-08-30) diff --git a/config/projectconfig.json b/config/projectconfig.json index a60bf91d..6dbb5ae0 100644 --- a/config/projectconfig.json +++ b/config/projectconfig.json @@ -14,7 +14,6 @@ "src/ballistica/core/platform/android/utf8/checked.h", "src/ballistica/core/platform/android/utf8/unchecked.h", "src/ballistica/core/platform/android/utf8/core.h", - "src/ballistica/base/platform/apple/sdl_main_mac.h", "src/ballistica/base/platform/oculus/main_rift.cc", "src/ballistica/core/platform/android/android_gl3.c" ], diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index 363daa00..84147e5d 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -210,7 +210,8 @@ class App: self._shutdown_task: asyncio.Task[None] | None = None self._shutdown_tasks: list[Coroutine[None, None, None]] = [ self._wait_for_shutdown_suppressions(), - self._fade_for_shutdown(), + self._fade_and_shutdown_graphics(), + self._fade_and_shutdown_audio(), ] self._pool_thread_count = 0 @@ -798,6 +799,7 @@ class App: async def _shutdown(self) -> None: import asyncio + _babase.lock_all_input() try: async with asyncio.TaskGroup() as task_group: for task_coro in self._shutdown_tasks: @@ -898,18 +900,26 @@ class App: await asyncio.sleep(0.001) _babase.lifecyclelog('shutdown-suppress wait end') - async def _fade_for_shutdown(self) -> None: + async def _fade_and_shutdown_graphics(self) -> None: import asyncio - # Kick off a fade, block input, and wait for a short bit. - # Ideally most shutdown activity completes during the fade so - # there's no tangible wait. - _babase.lifecyclelog('fade-for-shutdown begin') + # Kick off a short fade and give it time to complete. + _babase.lifecyclelog('fade-and-shutdown-graphics begin') _babase.fade_screen(False, time=0.15) - _babase.lock_all_input() - # _babase.getsimplesound('swish2').play() await asyncio.sleep(0.15) - _babase.lifecyclelog('fade-for-shutdown end') + _babase.lifecyclelog('fade-and-shutdown-graphics end') + + async def _fade_and_shutdown_audio(self) -> None: + import asyncio + + # Tell the audio system to go down and give it a bit of + # time to do so gracefully. + _babase.lifecyclelog('fade-and-shutdown-audio begin') + _babase.audio_shutdown_begin() + await asyncio.sleep(0.15) + while not _babase.audio_shutdown_is_complete(): + await asyncio.sleep(0.01) + _babase.lifecyclelog('fade-and-shutdown-audio end') def _threadpool_no_wait_done(self, fut: Future) -> None: try: diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index d9117788..b832f68b 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21453 +TARGET_BALLISTICA_BUILD = 21465 TARGET_BALLISTICA_VERSION = '1.7.28' diff --git a/src/ballistica/base/app_adapter/app_adapter_apple.cc b/src/ballistica/base/app_adapter/app_adapter_apple.cc index c5c55b03..13ebe5a8 100644 --- a/src/ballistica/base/app_adapter/app_adapter_apple.cc +++ b/src/ballistica/base/app_adapter/app_adapter_apple.cc @@ -190,13 +190,13 @@ void AppAdapterApple::SetHardwareCursorVisible(bool visible) { assert(g_core->InMainThread()); #if BA_OSTYPE_MACOS - BallisticaKit::CocoaSupportSetCursorVisible(visible); + BallisticaKit::CocoaFromCppSetCursorVisible(visible); #endif } void AppAdapterApple::TerminateApp() { #if BA_OSTYPE_MACOS - BallisticaKit::CocoaSupportTerminateApp(); + BallisticaKit::CocoaFromCppTerminateApp(); #else AppAdapter::TerminateApp(); #endif diff --git a/src/ballistica/base/audio/audio.cc b/src/ballistica/base/audio/audio.cc index 8971b449..f7daba43 100644 --- a/src/ballistica/base/audio/audio.cc +++ b/src/ballistica/base/audio/audio.cc @@ -101,7 +101,7 @@ auto Audio::SourceBeginNew() -> AudioSource* { #pragma clang diagnostic pop auto Audio::IsSoundPlaying(uint32_t play_id) -> bool { - uint32_t source_id = AudioServer::source_id_from_play_id(play_id); + uint32_t source_id = AudioServer::SourceIdFromPlayId(play_id); assert(client_sources_.size() > source_id); client_sources_[source_id]->Lock(2); bool result = (client_sources_[source_id]->play_id() == play_id); @@ -112,7 +112,7 @@ auto Audio::IsSoundPlaying(uint32_t play_id) -> bool { auto Audio::SourceBeginExisting(uint32_t play_id, int debug_id) -> AudioSource* { BA_DEBUG_FUNCTION_TIMER_BEGIN(); - uint32_t source_id = AudioServer::source_id_from_play_id(play_id); + uint32_t source_id = AudioServer::SourceIdFromPlayId(play_id); // Ok, the audio thread fills in this source list, // so theoretically a client could call this before the audio thread diff --git a/src/ballistica/base/audio/audio_server.cc b/src/ballistica/base/audio/audio_server.cc index 5ba5a304..1e873026 100644 --- a/src/ballistica/base/audio/audio_server.cc +++ b/src/ballistica/base/audio/audio_server.cc @@ -2,6 +2,8 @@ #include "ballistica/base/audio/audio_server.h" +#include + #include "ballistica/base/assets/assets.h" #include "ballistica/base/assets/sound_asset.h" #include "ballistica/base/audio/al_sys.h" @@ -33,15 +35,14 @@ LPALCDEVICERESUMESOFT alcDeviceResumeSOFT; const int kAudioProcessIntervalNormal{500}; const int kAudioProcessIntervalFade{50}; const int kAudioProcessIntervalPendingLoad{1}; -#if (BA_DEBUG_BUILD || BA_TEST_BUILD) + +#if BA_DEBUG_BUILD || BA_TEST_BUILD const bool kShowInUseSounds{}; #endif -int AudioServer::al_source_count_ = 0; - -struct AudioServer::Impl { - Impl() = default; - ~Impl() = default; +struct AudioServer::Impl_ { + Impl_() = default; + ~Impl_() = default; #if BA_ENABLE_AUDIO ALCcontext* alc_context{}; @@ -49,14 +50,14 @@ struct AudioServer::Impl { }; /// Location for sound emission (server version). -class AudioServer::ThreadSource : public Object { +class AudioServer::ThreadSource_ : public Object { public: // The id is returned as the lo-word of the identifier // returned by "play". If valid is returned as false, there are no // hardware channels available (or another error) and the source should // not be used. - ThreadSource(AudioServer* audio_thread, int id, bool* valid); - ~ThreadSource() override; + ThreadSource_(AudioServer* audio_thread, int id, bool* valid); + ~ThreadSource_() override; void Reset() { SetIsMusic(false); SetPositional(true); @@ -101,6 +102,10 @@ class AudioServer::ThreadSource : public Object { void ExecPlay(); void Update(); + void CreateClientSource(int id) { + client_source_ = std::make_unique(id); + } + private: bool looping_{}; std::unique_ptr client_source_; @@ -127,11 +132,11 @@ class AudioServer::ThreadSource : public Object { #if BA_ENABLE_AUDIO Object::Ref streamer_; #endif - - friend class AudioServer; }; // ThreadSource -AudioServer::AudioServer() : impl_{new AudioServer::Impl()} {} +AudioServer::AudioServer() : impl_{std::make_unique()} {} + +AudioServer::~AudioServer() = default; void AudioServer::OnMainThreadStartApp() { // Spin up our thread. @@ -142,21 +147,21 @@ void AudioServer::OnMainThreadStartApp() { event_loop_->PushCall([this] { // We want to be informed when our event-loop is pausing and unpausing. event_loop()->AddSuspendCallback( - NewLambdaRunnableUnmanaged([this] { OnThreadPause(); })); + NewLambdaRunnableUnmanaged([this] { OnThreadSuspend_(); })); event_loop()->AddUnsuspendCallback( - NewLambdaRunnableUnmanaged([this] { OnThreadResume(); })); + NewLambdaRunnableUnmanaged([this] { OnThreadUnsuspend_(); })); }); - event_loop_->PushCallSynchronous([this] { OnAppStartInThread(); }); + event_loop_->PushCallSynchronous([this] { OnAppStartInThread_(); }); } -void AudioServer::OnAppStartInThread() { +void AudioServer::OnAppStartInThread_() { assert(g_base->InAudioThread()); // Get our thread to give us periodic processing time. process_timer_ = event_loop()->NewTimer(kAudioProcessIntervalNormal, true, - NewLambdaRunnable([this] { Process(); })); + NewLambdaRunnable([this] { Process_(); })); #if BA_ENABLE_AUDIO @@ -235,10 +240,10 @@ void AudioServer::OnAppStartInThread() { int target_source_count = 30; for (int i = 0; i < target_source_count; i++) { bool valid = false; - auto s(Object::New(this, i, &valid)); + auto s(Object::New(this, i, &valid)); if (valid) { - s->client_source_ = std::make_unique(i); - g_base->audio->AddClientSource(&(*s->client_source_)); + s->CreateClientSource(i); + g_base->audio->AddClientSource(&(*s->client_source())); sound_source_refs_.push_back(s); sources_.push_back(&(*s)); } else { @@ -250,47 +255,74 @@ void AudioServer::OnAppStartInThread() { CHECK_AL_ERROR; // Now make available any stopped sources (should be all of them). - UpdateAvailableSources(); + UpdateAvailableSources_(); #endif // BA_ENABLE_AUDIO } -AudioServer::~AudioServer() { -#if BA_ENABLE_AUDIO - sound_source_refs_.clear(); - - // Take down AL stuff. - { - ALCdevice* device; - BA_PRECONDITION_LOG(alcMakeContextCurrent(nullptr)); - device = alcGetContextsDevice(impl_->alc_context); - alcDestroyContext(impl_->alc_context); - assert(alcGetError(device) == ALC_NO_ERROR); - alcCloseDevice(device); +void AudioServer::Shutdown() { + BA_PRECONDITION(g_base->InAudioThread()); + if (shutting_down_) { + return; } - assert(streaming_sources_.empty()); - assert(al_source_count_ == 0); + shutting_down_ = true; + shutdown_start_time_ = g_core->GetAppTimeSeconds(); -#endif // BA_ENABLE_AUDIO - delete impl_; + // Stop all playing sounds and note the time. We'll then give everything a + // moment to come to a halt before we tear down the audio context to + // hopefully minimize errors/pops/etc. + for (auto&& i : sources_) { + i->Stop(); + } + UpdateTimerInterval_(); } -struct AudioServer::SoundFadeNode { +void AudioServer::CompleteShutdown_() { + assert(g_base->InAudioThread()); + assert(shutting_down_); + assert(!shutdown_completed_); + +#if BA_ENABLE_AUDIO + ALCboolean check = alcMakeContextCurrent(nullptr); + if (!check) { + Log(LogLevel::kWarning, "Error on alcMakeContextCurrent at shutdown."); + } + auto* device = alcGetContextsDevice(impl_->alc_context); + if (!device) { + Log(LogLevel::kWarning, "Unable to get ALCdevice at shutdown."); + } else { + alcDestroyContext(impl_->alc_context); + ALenum err = alcGetError(device); + if (err != ALC_NO_ERROR) { + Log(LogLevel::kWarning, "Error on AL shutdown."); + } + check = alcCloseDevice(device); + if (!check) { + Log(LogLevel::kWarning, "Error on alcCloseDevice at shutdown."); + } + } +#endif + + shutdown_completed_ = true; +} + +struct AudioServer::SoundFadeNode_ { uint32_t play_id; millisecs_t starttime; millisecs_t endtime; bool out; - SoundFadeNode(uint32_t play_id_in, millisecs_t duration_in, bool out_in) + SoundFadeNode_(uint32_t play_id_in, millisecs_t duration_in, bool out_in) : play_id(play_id_in), starttime(g_core->GetAppTimeMillisecs()), endtime(g_core->GetAppTimeMillisecs() + duration_in), out(out_in) {} }; -void AudioServer::SetPaused(bool pause) { - if (!paused_) { - if (!pause) { - Log(LogLevel::kError, "Got audio unpause request when already unpaused."); +void AudioServer::SetSuspended_(bool suspend) { + if (!suspended_) { + if (!suspend) { + Log(LogLevel::kError, + "Got audio unsuspend request when already unsuspended."); } else { #if BA_OSTYPE_IOS_TVOS // apple recommends this during audio-interruptions.. @@ -302,14 +334,15 @@ void AudioServer::SetPaused(bool pause) { // On android lets tell open-sl to stop its processing. #if BA_OSTYPE_ANDROID alcDevicePauseSOFT(alcGetContextsDevice(impl_->alc_context)); -#endif // BA_OSTYPE_ANDROID +#endif - paused_ = true; + suspended_ = true; } } else { - // unpause if requested.. - if (pause) { - Log(LogLevel::kError, "Got audio pause request when already paused."); + // unsuspend if requested.. + if (suspend) { + Log(LogLevel::kError, + "Got audio suspend request when already suspended."); } else { #if BA_OSTYPE_IOS_TVOS // apple recommends this during audio-interruptions.. @@ -320,18 +353,18 @@ void AudioServer::SetPaused(bool pause) { alcMakeContextCurrent(impl_->alc_context); // hmm is this necessary?.. #endif #endif + // On android lets tell openal-soft to stop processing. #if BA_OSTYPE_ANDROID alcDeviceResumeSOFT(alcGetContextsDevice(impl_->alc_context)); -#endif // BA_OSTYPE_ANDROID - - paused_ = false; +#endif + suspended_ = false; #if BA_ENABLE_AUDIO CHECK_AL_ERROR; -#endif // BA_ENABLE_AUDIO +#endif - // Go through all of our sources and stop any we've wanted to stop while - // paused. + // Go through all of our sources and stop any we've wanted to stop + // while we were suspended. for (auto&& i : sources_) { if ((!i->want_to_play()) && (i->is_actually_playing())) { i->ExecStop(); @@ -343,7 +376,7 @@ void AudioServer::SetPaused(bool pause) { void AudioServer::PushSourceSetIsMusicCall(uint32_t play_id, bool val) { event_loop()->PushCall([this, play_id, val] { - ThreadSource* s = GetPlayingSound(play_id); + ThreadSource_* s = GetPlayingSound_(play_id); if (s) { s->SetIsMusic(val); } @@ -352,7 +385,7 @@ void AudioServer::PushSourceSetIsMusicCall(uint32_t play_id, bool val) { void AudioServer::PushSourceSetPositionalCall(uint32_t play_id, bool val) { event_loop()->PushCall([this, play_id, val] { - ThreadSource* s = GetPlayingSound(play_id); + ThreadSource_* s = GetPlayingSound_(play_id); if (s) { s->SetPositional(val); } @@ -362,7 +395,7 @@ void AudioServer::PushSourceSetPositionalCall(uint32_t play_id, bool val) { void AudioServer::PushSourceSetPositionCall(uint32_t play_id, const Vector3f& p) { event_loop()->PushCall([this, play_id, p] { - ThreadSource* s = GetPlayingSound(play_id); + ThreadSource_* s = GetPlayingSound_(play_id); if (s) { s->SetPosition(p.x, p.y, p.z); } @@ -371,7 +404,7 @@ void AudioServer::PushSourceSetPositionCall(uint32_t play_id, void AudioServer::PushSourceSetGainCall(uint32_t play_id, float val) { event_loop()->PushCall([this, play_id, val] { - ThreadSource* s = GetPlayingSound(play_id); + ThreadSource_* s = GetPlayingSound_(play_id); if (s) { s->SetGain(val); } @@ -380,7 +413,7 @@ void AudioServer::PushSourceSetGainCall(uint32_t play_id, float val) { void AudioServer::PushSourceSetFadeCall(uint32_t play_id, float val) { event_loop()->PushCall([this, play_id, val] { - ThreadSource* s = GetPlayingSound(play_id); + ThreadSource_* s = GetPlayingSound_(play_id); if (s) { s->SetFade(val); } @@ -389,7 +422,7 @@ void AudioServer::PushSourceSetFadeCall(uint32_t play_id, float val) { void AudioServer::PushSourceSetLoopingCall(uint32_t play_id, bool val) { event_loop()->PushCall([this, play_id, val] { - ThreadSource* s = GetPlayingSound(play_id); + ThreadSource_* s = GetPlayingSound_(play_id); if (s) { s->SetLooping(val); } @@ -399,7 +432,7 @@ void AudioServer::PushSourceSetLoopingCall(uint32_t play_id, bool val) { void AudioServer::PushSourcePlayCall(uint32_t play_id, Object::Ref* sound) { event_loop()->PushCall([this, play_id, sound] { - ThreadSource* s = GetPlayingSound(play_id); + ThreadSource_* s = GetPlayingSound_(play_id); // If this play command is valid, pass it along. // Otherwise, return it immediately for deletion. @@ -413,13 +446,13 @@ void AudioServer::PushSourcePlayCall(uint32_t play_id, // This way the more things clients are playing, the more // tight our source availability checking gets (instead of solely relying on // our periodic process() calls). - UpdateAvailableSources(); + UpdateAvailableSources_(); }); } void AudioServer::PushSourceStopCall(uint32_t play_id) { event_loop()->PushCall([this, play_id] { - ThreadSource* s = GetPlayingSound(play_id); + ThreadSource_* s = GetPlayingSound_(play_id); if (s) { s->Stop(); } @@ -428,7 +461,7 @@ void AudioServer::PushSourceStopCall(uint32_t play_id) { void AudioServer::PushSourceEndCall(uint32_t play_id) { event_loop()->PushCall([this, play_id] { - ThreadSource* s = GetPlayingSound(play_id); + ThreadSource_* s = GetPlayingSound_(play_id); assert(s); s->client_source()->Lock(5); s->client_source()->set_client_queue_size( @@ -439,13 +472,13 @@ void AudioServer::PushSourceEndCall(uint32_t play_id) { } void AudioServer::PushResetCall() { - event_loop()->PushCall([this] { Reset(); }); + event_loop()->PushCall([this] { Reset_(); }); } void AudioServer::PushSetListenerPositionCall(const Vector3f& p) { event_loop()->PushCall([this, p] { #if BA_ENABLE_AUDIO - if (!paused_) { + if (!suspended_ && !shutting_down_) { ALfloat lpos[3] = {p.x, p.y, p.z}; alListenerfv(AL_POSITION, lpos); CHECK_AL_ERROR; @@ -458,7 +491,7 @@ void AudioServer::PushSetListenerOrientationCall(const Vector3f& forward, const Vector3f& up) { event_loop()->PushCall([this, forward, up] { #if BA_ENABLE_AUDIO - if (!paused_) { + if (!suspended_ && !shutting_down_) { ALfloat lorient[6] = {forward.x, forward.y, forward.z, up.x, up.y, up.z}; alListenerfv(AL_ORIENTATION, lorient); CHECK_AL_ERROR; @@ -467,7 +500,7 @@ void AudioServer::PushSetListenerOrientationCall(const Vector3f& forward, }); } -void AudioServer::UpdateAvailableSources() { +void AudioServer::UpdateAvailableSources_() { for (auto&& i : sources_) { i->UpdateAvailability(); } @@ -525,17 +558,19 @@ void AudioServer::UpdateAvailableSources() { } void AudioServer::StopSound(uint32_t play_id) { - uint32_t source = source_id_from_play_id(play_id); - uint32_t count = play_count_from_play_id(play_id); + uint32_t source = SourceIdFromPlayId(play_id); + uint32_t count = PlayCountFromPlayId(play_id); if (source < sources_.size()) { - if (count == sources_[source]->play_count()) sources_[source]->Stop(); + if (count == sources_[source]->play_count()) { + sources_[source]->Stop(); + } } } -auto AudioServer::GetPlayingSound(uint32_t play_id) - -> AudioServer::ThreadSource* { - uint32_t source = source_id_from_play_id(play_id); - uint32_t count = play_count_from_play_id(play_id); +auto AudioServer::GetPlayingSound_(uint32_t play_id) + -> AudioServer::ThreadSource_* { + uint32_t source = SourceIdFromPlayId(play_id); + uint32_t count = PlayCountFromPlayId(play_id); assert(source < sources_.size()); if (source < sources_.size()) { // If the sound has finished playing or whatnot, we @@ -551,9 +586,10 @@ auto AudioServer::GetPlayingSound(uint32_t play_id) return nullptr; } -void AudioServer::UpdateTimerInterval() { - // If we've got pending loads, go into uber-hyperactive mode. - if (have_pending_loads_) { +void AudioServer::UpdateTimerInterval_() { + // If we've got pending loads or are shutting down, go into + // uber-hyperactive mode. + if (have_pending_loads_ || shutting_down_) { assert(process_timer_); process_timer_->SetLength(kAudioProcessIntervalPendingLoad); } else { @@ -563,48 +599,40 @@ void AudioServer::UpdateTimerInterval() { assert(process_timer_); process_timer_->SetLength(kAudioProcessIntervalFade); } else { - // Nothing but normal activity; just run enough to keep - // buffers filled and whatnot. + // Nothing but normal activity; just run often enough to keep buffers + // filled and whatnot. assert(process_timer_); process_timer_->SetLength(kAudioProcessIntervalNormal); } } } -void AudioServer::SetSoundPitch(float pitch) { - sound_pitch_ = pitch; - if (sound_pitch_ < 0.01f) sound_pitch_ = 0.01f; +void AudioServer::SetSoundPitch_(float pitch) { + sound_pitch_ = std::clamp(pitch, 0.1f, 10.0f); for (auto&& i : sources_) { i->UpdatePitch(); } } -void AudioServer::SetSoundVolume(float volume) { - sound_volume_ = volume; - if (sound_volume_ > 3.0f) { - sound_volume_ = 3.0f; - } - if (sound_volume_ < 0) { - sound_volume_ = 0; - } +void AudioServer::SetSoundVolume_(float volume) { + sound_volume_ = std::clamp(volume, 0.0f, 3.0f); for (auto&& i : sources_) { i->UpdateVolume(); } } -void AudioServer::SetMusicVolume(float volume) { - music_volume_ = volume; - if (music_volume_ > 3.0f) music_volume_ = 3.0f; - if (music_volume_ < 0) music_volume_ = 0; - UpdateMusicPlayState(); +void AudioServer::SetMusicVolume_(float volume) { + music_volume_ = std::clamp(volume, 0.0f, 3.0f); + UpdateMusicPlayState_(); for (auto&& i : sources_) { i->UpdateVolume(); } } -// Start or stop music playback based on volume/pause-state/etc. -void AudioServer::UpdateMusicPlayState() { - bool should_be_playing = ((music_volume_ > 0.000001f) && !paused_); +// Start or stop music playback based on volume/suspend-state/etc. +void AudioServer::UpdateMusicPlayState_() { + bool should_be_playing = + (music_volume_ > 0.000001f && !suspended_ && !shutting_down_); // Flip any playing music off. if (!should_be_playing) { @@ -624,21 +652,21 @@ void AudioServer::UpdateMusicPlayState() { } } -void AudioServer::Process() { +void AudioServer::Process_() { assert(g_base->InAudioThread()); millisecs_t real_time = g_core->GetAppTimeMillisecs(); - // If we're paused we don't do nothin'. - if (!paused_) { + // If we're suspended we don't do nothin'. + if (!suspended_ && !shutting_down_) { // Do some loading... have_pending_loads_ = g_base->assets->RunPendingAudioLoads(); // Keep that available-sources list filled. - UpdateAvailableSources(); + UpdateAvailableSources_(); // Update our fading sound volumes. if (real_time - last_sound_fade_process_time_ > 50) { - ProcessSoundFades(); + ProcessSoundFades_(); last_sound_fade_process_time_ = real_time; } @@ -649,14 +677,25 @@ void AudioServer::Process() { i->Update(); } } + #if BA_ENABLE_AUDIO CHECK_AL_ERROR; #endif } - UpdateTimerInterval(); + UpdateTimerInterval_(); + + // In my brief unscientific testing with my airpods, a 0.2 second delay + // between stopping sounds and killing the sound-system seems to be enough + // for the mixer to spit out some silence so we don't hear sudden cut-offs + // in one or both ears. + if (shutting_down_ && !shutdown_completed_) { + if (g_core->GetAppTimeSeconds() - shutdown_start_time_ > 0.2) { + CompleteShutdown_(); + } + } } -void AudioServer::Reset() { +void AudioServer::Reset_() { // Note: up until version 1.7.20, the audio server would stop all playing // sounds when reset. This would prevent against long sounds playing at // the end of a game session 'bleeding' into the main menu/etc. However, @@ -665,10 +704,11 @@ void AudioServer::Reset() { // growing. In particular, a 'power down' sound at launch when a plugin is // no longer found is being cut off by the initial app-mode switch. - // So disabling the stop behavior for now and hoping that doesn't bite us. - // Ideally we should have sounds contexts so that we can stop sounds for - // a particular scene when that scene ends/etc. This would also fix our - // current problem where epic mode screws up the pitch on our UI sounds. + // So I'm disabling the stop behavior for now and hoping that doesn't bite + // us. Ideally we should have sounds contexts so that we can stop sounds + // for a particular scene when that scene ends/etc. This could also + // address our current problem where epic mode screws up the pitch on our + // UI sounds. if (explicit_bool(false)) { // Stop all playing sounds. @@ -677,17 +717,17 @@ void AudioServer::Reset() { } } // Still need to reset this though or epic-mode will screw us up. - SetSoundPitch(1.0f); + SetSoundPitch_(1.0f); } -void AudioServer::ProcessSoundFades() { +void AudioServer::ProcessSoundFades_() { auto i = sound_fade_nodes_.begin(); decltype(i) i_next; while (i != sound_fade_nodes_.end()) { i_next = i; i_next++; - AudioServer::ThreadSource* s = GetPlayingSound(i->second.play_id); + AudioServer::ThreadSource_* s = GetPlayingSound_(i->second.play_id); if (s) { if (g_core->GetAppTimeMillisecs() > i->second.endtime) { StopSound(i->second.play_id); @@ -710,17 +750,17 @@ void AudioServer::ProcessSoundFades() { void AudioServer::FadeSoundOut(uint32_t play_id, uint32_t time) { // Pop a new node on the list (this won't overwrite the old if there is one). sound_fade_nodes_.insert( - std::make_pair(play_id, SoundFadeNode(play_id, time, true))); + std::make_pair(play_id, SoundFadeNode_(play_id, time, true))); } -void AudioServer::DeleteAssetComponent(Asset* c) { - assert(g_base->InAudioThread()); - c->Unload(); - delete c; -} +// void AudioServer::DeleteAssetComponent_(Asset* c) { +// assert(g_base->InAudioThread()); +// c->Unload(); +// delete c; +// } -AudioServer::ThreadSource::ThreadSource(AudioServer* audio_thread_in, int id_in, - bool* valid_out) +AudioServer::ThreadSource_::ThreadSource_(AudioServer* audio_thread_in, + int id_in, bool* valid_out) : id_(id_in), audio_thread_(audio_thread_in) { #if BA_ENABLE_AUDIO assert(g_core); @@ -751,13 +791,13 @@ AudioServer::ThreadSource::ThreadSource(AudioServer* audio_thread_in, int id_in, } *valid_out = valid_; if (valid_) { - al_source_count_++; + g_base->audio_server->al_source_count_++; } #endif // BA_ENABLE_AUDIO } -AudioServer::ThreadSource::~ThreadSource() { +AudioServer::ThreadSource_::~ThreadSource_() { #if BA_ENABLE_AUDIO if (!valid_) { @@ -779,16 +819,16 @@ AudioServer::ThreadSource::~ThreadSource() { alDeleteSources(1, &source_); CHECK_AL_ERROR; - al_source_count_--; + g_base->audio_server->al_source_count_--; #endif // BA_ENABLE_AUDIO } -auto AudioServer::ThreadSource::GetDefaultOwnerThread() const -> EventLoopID { +auto AudioServer::ThreadSource_::GetDefaultOwnerThread() const -> EventLoopID { return EventLoopID::kAudio; } -void AudioServer::ThreadSource::UpdateAvailability() { +void AudioServer::ThreadSource_::UpdateAvailability() { #if BA_ENABLE_AUDIO assert(g_base->InAudioThread()); @@ -814,9 +854,10 @@ void AudioServer::ThreadSource::UpdateAvailability() { if (looping_ || (is_streamed_ && streamer_.Exists() && streamer_->loops())) { busy = want_to_play_; } else { - // If our context is paused, we know nothing is playing + // If our context is suspended, we know nothing is playing // (and we can't ask AL cuz we have no context). - if (g_base->audio_server->paused()) { + if (g_base->audio_server->suspended_ + || g_base->audio_server->shutting_down_) { busy = false; } else { ALint state; @@ -827,8 +868,9 @@ void AudioServer::ThreadSource::UpdateAvailability() { } // Ok, now if we can get a lock on the availability list, go ahead and - // make this guy available; give him a new play id and reset his state. - // If we can't get a lock it's no biggie... we'll come back to this guy later. + // make this guy available; give him a new play id and reset his state. If + // we can't get a lock it's no biggie... we'll come back to this guy + // later. if (!busy) { if (g_base->audio->available_sources_mutex().try_lock()) { @@ -850,86 +892,144 @@ void AudioServer::ThreadSource::UpdateAvailability() { #endif // BA_ENABLE_AUDIO } -void AudioServer::ThreadSource::Update() { +void AudioServer::ThreadSource_::Update() { #if BA_ENABLE_AUDIO assert(is_streamed_ && is_actually_playing_); streamer_->Update(); #endif } -void AudioServer::ThreadSource::SetIsMusic(bool m) { is_music_ = m; } +void AudioServer::ThreadSource_::SetIsMusic(bool m) { is_music_ = m; } -void AudioServer::ThreadSource::SetGain(float g) { +void AudioServer::ThreadSource_::SetGain(float g) { gain_ = g; UpdateVolume(); } -void AudioServer::ThreadSource::SetFade(float f) { +void AudioServer::ThreadSource_::SetFade(float f) { fade_ = f; UpdateVolume(); } -void AudioServer::ThreadSource::SetLooping(bool loop) { +void AudioServer::ThreadSource_::SetLooping(bool loop) { looping_ = loop; - if (!g_base->audio_server->paused()) { #if BA_ENABLE_AUDIO - alSourcei(source_, AL_LOOPING, loop); - CHECK_AL_ERROR; -#endif - } -} - -void AudioServer::ThreadSource::SetPositional(bool p) { -#if BA_ENABLE_AUDIO - if (!g_base->audio_server->paused()) { - // TODO(ericf): Don't allow setting of positional - // on stereo sounds - we check this at initial play() - // but should do it here too. - alSourcei(source_, AL_SOURCE_RELATIVE, !p); - CHECK_AL_ERROR; + if (g_base->audio_server->suspended_ + || g_base->audio_server->shutting_down_) { + return; } + alSourcei(source_, AL_LOOPING, loop); + CHECK_AL_ERROR; #endif } -void AudioServer::ThreadSource::SetPosition(float x, float y, float z) { +void AudioServer::ThreadSource_::SetPositional(bool p) { #if BA_ENABLE_AUDIO - if (!g_base->audio_server->paused()) { - bool oob = false; - if (x < -500) { - oob = true; - x = -500; - } else if (x > 500) { - oob = true; - x = 500; - } - if (y < -500) { - oob = true; - y = -500; - } else if (y > 500) { - oob = true; - y = 500; - } - if (z < -500) { - oob = true; - z = -500; - } else if (z > 500) { - oob = true; - z = 500; - } - if (oob) { - BA_LOG_ONCE(LogLevel::kError, - "AudioServer::ThreadSource::SetPosition" - " got out-of-bounds value."); - } - ALfloat source_pos[] = {x, y, z}; - alSourcefv(source_, AL_POSITION, source_pos); - CHECK_AL_ERROR; + if (g_base->audio_server->suspended_ + || g_base->audio_server->shutting_down_) { + return; } + // TODO(ericf): Don't allow setting of positional + // on stereo sounds - we check this at initial play() + // but should do it here too. + alSourcei(source_, AL_SOURCE_RELATIVE, !p); + CHECK_AL_ERROR; + +#endif +} + +void AudioServer::ThreadSource_::SetPosition(float x, float y, float z) { +#if BA_ENABLE_AUDIO + if (g_base->audio_server->suspended_ + || g_base->audio_server->shutting_down_) { + return; + } + bool oob = false; + if (x < -500) { + oob = true; + x = -500; + } else if (x > 500) { + oob = true; + x = 500; + } + if (y < -500) { + oob = true; + y = -500; + } else if (y > 500) { + oob = true; + y = 500; + } + if (z < -500) { + oob = true; + z = -500; + } else if (z > 500) { + oob = true; + z = 500; + } + if (oob) { + BA_LOG_ONCE(LogLevel::kError, + "AudioServer::ThreadSource::SetPosition" + " got out-of-bounds value."); + } + ALfloat source_pos[] = {x, y, z}; + alSourcefv(source_, AL_POSITION, source_pos); + CHECK_AL_ERROR; + #endif // BA_ENABLE_AUDIO } +auto AudioServer::ThreadSource_::Play(const Object::Ref* sound) + -> uint32_t { +#if BA_ENABLE_AUDIO + + assert(g_base->InAudioThread()); + assert(sound->Exists()); + + // Stop whatever we were doing. + Stop(); + + assert(source_sound_ == nullptr); + source_sound_ = sound; + + if (!g_base->audio_server->suspended_ + && !g_base->audio_server->shutting_down_) { + // Ok, here's where we might start needing to access our media... can't + // hold off any longer... + (**source_sound_).Load(); + + is_streamed_ = (**source_sound_).is_streamed(); + current_is_music_ = is_music_; + + if (is_streamed_) { + streamer_ = Object::New( + (**source_sound_).file_name_full().c_str(), source_, looping_); + } else { + alSourcei(source_, AL_BUFFER, + static_cast((**source_sound_).buffer())); + } + CHECK_AL_ERROR; + + // Always update our volume and pitch here (we may be changing from + // music to nonMusic, etc.) + UpdateVolume(); + UpdatePitch(); + + bool music_should_play = ((g_base->audio_server->music_volume_ > 0.000001f) + && !g_base->audio_server->suspended_ + && !g_base->audio_server->shutting_down_); + if ((!current_is_music_) || music_should_play) { + ExecPlay(); + } + } + want_to_play_ = true; + +#endif // BA_ENABLE_AUDIO + + return play_id(); +} + // Actually begin playback. -void AudioServer::ThreadSource::ExecPlay() { +void AudioServer::ThreadSource_::ExecPlay() { #if BA_ENABLE_AUDIO assert(g_core); @@ -992,60 +1092,39 @@ void AudioServer::ThreadSource::ExecPlay() { #endif // BA_ENABLE_AUDIO } -auto AudioServer::ThreadSource::Play(const Object::Ref* sound) - -> uint32_t { +// Do a complete stop... take us off the music list, detach our source, etc. +void AudioServer::ThreadSource_::Stop() { #if BA_ENABLE_AUDIO + assert(g_base->audio_server); - // FatalError("Testing other thread."); - - assert(g_base->InAudioThread()); - assert(sound->Exists()); - - // Stop whatever we were doing. - Stop(); - - assert(source_sound_ == nullptr); - source_sound_ = sound; - - if (!g_base->audio_server->paused()) { - // Ok, here's where we might start needing to access our media... can't hold - // off any longer... - (**source_sound_).Load(); - - is_streamed_ = (**source_sound_).is_streamed(); - current_is_music_ = is_music_; - - if (is_streamed_) { - streamer_ = Object::New( - (**source_sound_).file_name_full().c_str(), source_, looping_); - } else { - alSourcei(source_, AL_BUFFER, - static_cast((**source_sound_).buffer())); + // If our context is suspended we can't actually stop now; just record our + // intent. + if (g_base->audio_server->suspended_) { + want_to_play_ = false; + } else { + if (is_actually_playing_) { + ExecStop(); } - CHECK_AL_ERROR; - - // Always update our volume and pitch here (we may be changing from music to - // nonMusic, etc.) - UpdateVolume(); - UpdatePitch(); - - bool music_should_play = ((g_base->audio_server->music_volume_ > 0.000001f) - && !g_base->audio_server->paused()); - if ((!current_is_music_) || music_should_play) { - ExecPlay(); + if (streamer_.Exists()) { + streamer_.Clear(); } + // If we've got an attached sound, toss it back to the main thread + // to free up... + // (we can't kill media-refs outside the main thread) + if (source_sound_) { + assert(g_base->assets); + g_base->audio_server->AddSoundRefDelete(source_sound_); + source_sound_ = nullptr; + } + want_to_play_ = false; } - want_to_play_ = true; - #endif // BA_ENABLE_AUDIO - - return play_id(); } -void AudioServer::ThreadSource::ExecStop() { +void AudioServer::ThreadSource_::ExecStop() { #if BA_ENABLE_AUDIO assert(g_base->InAudioThread()); - assert(!g_base->audio_server->paused()); + assert(!g_base->audio_server->suspended_); assert(is_actually_playing_); if (streamer_.Exists()) { assert(is_streamed_); @@ -1067,83 +1146,63 @@ void AudioServer::ThreadSource::ExecStop() { #endif // BA_ENABLE_AUDIO } -// Do a complete stop... take us off the music list, detach our source, etc. -void AudioServer::ThreadSource::Stop() { +void AudioServer::ThreadSource_::UpdateVolume() { #if BA_ENABLE_AUDIO - assert(g_base->audio_server); - - // If our context is paused we can't actually stop now; just record our - // intent. - if (g_base->audio_server->paused()) { - want_to_play_ = false; + assert(g_base->InAudioThread()); + if (g_base->audio_server->suspended_ + || g_base->audio_server->shutting_down_) { + return; + } + float val = gain_ * fade_; + if (current_is_music()) { + val *= audio_thread_->music_volume_ / 7.0f; } else { - if (is_actually_playing_) ExecStop(); - if (streamer_.Exists()) { - streamer_.Clear(); - } - // If we've got an attached sound, toss it back to the main thread - // to free up... - // (we can't kill media-refs outside the main thread) - if (source_sound_) { - assert(g_base->assets); - g_base->audio_server->AddSoundRefDelete(source_sound_); - source_sound_ = nullptr; - } - want_to_play_ = false; + val *= audio_thread_->sound_volume_; } + alSourcef(source_, AL_GAIN, std::max(0.0f, val)); + CHECK_AL_ERROR; + #endif // BA_ENABLE_AUDIO } -void AudioServer::ThreadSource::UpdateVolume() { +void AudioServer::ThreadSource_::UpdatePitch() { #if BA_ENABLE_AUDIO assert(g_base->InAudioThread()); - if (!g_base->audio_server->paused()) { - float val = gain_ * fade_; - if (current_is_music()) { - val *= audio_thread_->music_volume() / 7.0f; - } else { - val *= audio_thread_->sound_volume(); - } - alSourcef(source_, AL_GAIN, std::max(0.0f, val)); - CHECK_AL_ERROR; + if (g_base->audio_server->suspended_ + || g_base->audio_server->shutting_down_) { + return; } -#endif // BA_ENABLE_AUDIO -} + float val = 1.0f; + if (current_is_music()) { + } else { + val *= audio_thread_->sound_pitch_; + } + alSourcef(source_, AL_PITCH, val); + CHECK_AL_ERROR; -void AudioServer::ThreadSource::UpdatePitch() { -#if BA_ENABLE_AUDIO - assert(g_base->InAudioThread()); - if (!g_base->audio_server->paused()) { - float val = 1.0f; - if (current_is_music()) { - } else { - val *= audio_thread_->sound_pitch(); - } - alSourcef(source_, AL_PITCH, val); - CHECK_AL_ERROR; - } #endif // BA_ENABLE_AUDIO } void AudioServer::PushSetVolumesCall(float music_volume, float sound_volume) { event_loop()->PushCall([this, music_volume, sound_volume] { - SetSoundVolume(sound_volume); - SetMusicVolume(music_volume); + SetSoundVolume_(sound_volume); + SetMusicVolume_(music_volume); }); } void AudioServer::PushSetSoundPitchCall(float val) { - event_loop()->PushCall([this, val] { SetSoundPitch(val); }); + event_loop()->PushCall([this, val] { SetSoundPitch_(val); }); } -void AudioServer::PushSetSuspendedCall(bool pause) { - event_loop()->PushCall([this, pause] { - if (g_buildconfig.ostype_android()) { - Log(LogLevel::kError, "Shouldn't be getting SetPausedCall on android."); - } - SetPaused(pause); - }); -} +// void AudioServer::PushSetSuspendedCall(bool suspend) { +// event_loop()->PushCall([this, suspend] { +// if (g_buildconfig.ostype_android()) { +// Log(LogLevel::kError, "Shouldn't be getting SetSuspendedCall on +// android."); +// } +// SetSuspended_(suspend); +// }); +// } void AudioServer::PushComponentUnloadCall( const std::vector*>& components) { @@ -1164,7 +1223,7 @@ void AudioServer::PushComponentUnloadCall( void AudioServer::PushHavePendingLoadsCall() { event_loop()->PushCall([this] { have_pending_loads_ = true; - UpdateTimerInterval(); + UpdateTimerInterval_(); }); } @@ -1187,44 +1246,44 @@ void AudioServer::ClearSoundRefDeleteList() { sound_ref_delete_list_.clear(); } -void AudioServer::BeginInterruption() { - assert(!g_base->InAudioThread()); - g_base->audio_server->PushSetSuspendedCall(true); +// void AudioServer::BeginInterruption() { +// assert(!g_base->InAudioThread()); +// g_base->audio_server->PushSetSuspendedCall(true); - // Wait a reasonable amount of time for the thread to act on it. - millisecs_t t = g_core->GetAppTimeMillisecs(); - while (true) { - if (g_base->audio_server->paused()) { - break; - } - if (g_core->GetAppTimeMillisecs() - t > 1000) { - Log(LogLevel::kError, "Timed out waiting for audio pause."); - break; - } - core::CorePlatform::SleepMillisecs(2); - } -} +// // Wait a reasonable amount of time for the thread to act on it. +// millisecs_t t = g_core->GetAppTimeMillisecs(); +// while (true) { +// if (g_base->audio_server->suspended()) { +// break; +// } +// if (g_core->GetAppTimeMillisecs() - t > 1000) { +// Log(LogLevel::kError, "Timed out waiting for audio suspend."); +// break; +// } +// core::CorePlatform::SleepMillisecs(2); +// } +// } -void AudioServer::OnThreadPause() { SetPaused(true); } +// void AudioServer::EndInterruption() { +// assert(!g_base->InAudioThread()); +// g_base->audio_server->PushSetSuspendedCall(false); -void AudioServer::OnThreadResume() { SetPaused(false); } +// // Wait a reasonable amount of time for the thread to act on it. +// millisecs_t t = g_core->GetAppTimeMillisecs(); +// while (true) { +// if (!g_base->audio_server->suspended()) { +// break; +// } +// if (g_core->GetAppTimeMillisecs() - t > 1000) { +// Log(LogLevel::kError, "Timed out waiting for audio unsuspend."); +// break; +// } +// core::CorePlatform::SleepMillisecs(2); +// } +// } -void AudioServer::EndInterruption() { - assert(!g_base->InAudioThread()); - g_base->audio_server->PushSetSuspendedCall(false); +void AudioServer::OnThreadSuspend_() { SetSuspended_(true); } - // Wait a reasonable amount of time for the thread to act on it. - millisecs_t t = g_core->GetAppTimeMillisecs(); - while (true) { - if (!g_base->audio_server->paused()) { - break; - } - if (g_core->GetAppTimeMillisecs() - t > 1000) { - Log(LogLevel::kError, "Timed out waiting for audio unpause."); - break; - } - core::CorePlatform::SleepMillisecs(2); - } -} +void AudioServer::OnThreadUnsuspend_() { SetSuspended_(false); } } // namespace ballistica::base diff --git a/src/ballistica/base/audio/audio_server.h b/src/ballistica/base/audio/audio_server.h index 5c5202ec..a47bf68a 100644 --- a/src/ballistica/base/audio/audio_server.h +++ b/src/ballistica/base/audio/audio_server.h @@ -12,14 +12,14 @@ namespace ballistica::base { -/// A module that handles audio processing. +/// Wrangles audio off in its own thread. class AudioServer { public: - static auto source_id_from_play_id(uint32_t play_id) -> uint32_t { + static auto SourceIdFromPlayId(uint32_t play_id) -> uint32_t { return play_id & 0xFFFFu; } - static auto play_count_from_play_id(uint32_t play_id) -> uint32_t { + static auto PlayCountFromPlayId(uint32_t play_id) -> uint32_t { return play_id >> 16u; } @@ -28,10 +28,9 @@ class AudioServer { void PushSetVolumesCall(float music_volume, float sound_volume); void PushSetSoundPitchCall(float val); - void PushSetSuspendedCall(bool pause); - static void BeginInterruption(); - static void EndInterruption(); + // static void BeginInterruption(); + // static void EndInterruption(); void PushSetListenerPositionCall(const Vector3f& p); void PushSetListenerOrientationCall(const Vector3f& forward, @@ -41,10 +40,12 @@ class AudioServer { void PushComponentUnloadCall( const std::vector*>& components); - /// For use by g_logic_module(). void ClearSoundRefDeleteList(); - auto paused() const -> bool { return paused_; } + auto paused() const -> bool { return suspended_; } + + void Shutdown(); + auto shutdown_completed() const { return shutdown_completed_; } // Client sources use these to pass settings to the server. void PushSourceSetIsMusicCall(uint32_t play_id, bool val); @@ -67,37 +68,36 @@ class AudioServer { auto event_loop() const -> EventLoop* { return event_loop_; } private: - class ThreadSource; - struct Impl; + class ThreadSource_; + struct Impl_; - void OnAppStartInThread(); + void OnAppStartInThread_(); ~AudioServer(); - void OnThreadPause(); - void OnThreadResume(); + void OnThreadSuspend_(); + void OnThreadUnsuspend_(); - void SetPaused(bool paused); + void SetSuspended_(bool suspended); - void SetMusicVolume(float volume); - void SetSoundVolume(float volume); - void SetSoundPitch(float pitch); - auto music_volume() -> float { return music_volume_; } - auto sound_volume() -> float { return sound_volume_; } - auto sound_pitch() -> float { return sound_pitch_; } + void SetMusicVolume_(float volume); + void SetSoundVolume_(float volume); + void SetSoundPitch_(float pitch); + + void CompleteShutdown_(); /// If a sound play id is currently playing, return the sound. - auto GetPlayingSound(uint32_t play_id) -> ThreadSource*; + auto GetPlayingSound_(uint32_t play_id) -> ThreadSource_*; - void Reset(); - void Process(); + void Reset_(); + void Process_(); /// Send a component to the audio thread to delete. - void DeleteAssetComponent(Asset* c); + // void DeleteAssetComponent_(Asset* c); - void UpdateTimerInterval(); - void UpdateAvailableSources(); - void UpdateMusicPlayState(); - void ProcessSoundFades(); + void UpdateTimerInterval_(); + void UpdateAvailableSources_(); + void UpdateMusicPlayState_(); + void ProcessSoundFades_(); // Some threads such as audio hold onto allocated Media-Component-Refs to keep // media components alive that they need. Media-Component-Refs, however, must @@ -107,32 +107,36 @@ class AudioServer { // Note: should use unique_ptr for this, but build fails on raspberry pi // (gcc 8.3.0). Works on Ubuntu 9.3 so should try again later. - // std::unique_ptr impl_{}; - Impl* impl_{}; + std::unique_ptr impl_{}; + // Impl* impl_{}; EventLoop* event_loop_{}; Timer* process_timer_{}; - bool have_pending_loads_{}; - bool paused_{}; - millisecs_t last_sound_fade_process_time_{}; - float sound_volume_{1.0f}; float sound_pitch_{1.0f}; float music_volume_{1.0f}; + bool have_pending_loads_ : 1 {}; + bool suspended_ : 1 {}; + bool shutdown_completed_ : 1 {}; + bool shutting_down_ : 1 {}; + seconds_t shutdown_start_time_{}; + millisecs_t last_sound_fade_process_time_{}; + /// Indexed list of sources. - std::vector sources_; - std::vector streaming_sources_; + std::vector sources_; + std::vector streaming_sources_; millisecs_t last_stream_process_time_{}; + millisecs_t last_sanity_check_time_{}; // Holds refs to all sources. // Use sources, not this, for faster iterating. - std::vector > sound_source_refs_; - struct SoundFadeNode; + std::vector> sound_source_refs_; + struct SoundFadeNode_; // NOTE: would use unordered_map here but gcc doesn't seem to allow // forward-declared template params with them. - std::map sound_fade_nodes_; + std::map sound_fade_nodes_; // This mutex controls access to our list of media component shared ptrs to // delete in the main thread. @@ -141,9 +145,7 @@ class AudioServer { // Our list of sound media components to delete via the main thread. std::vector*> sound_ref_delete_list_; - millisecs_t last_sanity_check_time_{}; - - static int al_source_count_; + int al_source_count_{}; }; } // namespace ballistica::base diff --git a/src/ballistica/base/audio/audio_source.cc b/src/ballistica/base/audio/audio_source.cc index 541d6d89..07d0bbdf 100644 --- a/src/ballistica/base/audio/audio_source.cc +++ b/src/ballistica/base/audio/audio_source.cc @@ -15,7 +15,7 @@ AudioSource::AudioSource(int id_in) : id_(id_in) {} AudioSource::~AudioSource() { assert(client_queue_size_ == 0); } void AudioSource::MakeAvailable(uint32_t play_id_new) { - assert(AudioServer::source_id_from_play_id(play_id_new) == id_); + assert(AudioServer::SourceIdFromPlayId(play_id_new) == id_); assert(client_queue_size_ == 0); assert(locked()); play_id_ = play_id_new; diff --git a/src/ballistica/base/audio/audio_streamer.cc b/src/ballistica/base/audio/audio_streamer.cc index 60e03fa5..bc6f1488 100644 --- a/src/ballistica/base/audio/audio_streamer.cc +++ b/src/ballistica/base/audio/audio_streamer.cc @@ -68,7 +68,9 @@ void AudioStreamer::Stop() { } void AudioStreamer::Update() { - if (eof_) return; + if (eof_) { + return; + } CHECK_AL_ERROR; diff --git a/src/ballistica/base/audio/audio_streamer.h b/src/ballistica/base/audio/audio_streamer.h index e4104fa6..934a5ee6 100644 --- a/src/ballistica/base/audio/audio_streamer.h +++ b/src/ballistica/base/audio/audio_streamer.h @@ -23,17 +23,18 @@ class AudioStreamer : public Object { auto Play() -> bool; void Stop(); void Update(); - enum Format { INVALID_FORMAT, MONO16_FORMAT, STEREO16_FORMAT }; + enum class Format : uint8_t { kInvalid, kMono16, kStereo16 }; auto al_format() const -> ALenum { switch (format_) { - case MONO16_FORMAT: + case Format::kMono16: return AL_FORMAT_MONO16; - case STEREO16_FORMAT: + case Format::kStereo16: return AL_FORMAT_STEREO16; default: break; } - return INVALID_FORMAT; + FatalError("Invalid AL format."); + return AL_FORMAT_MONO16; } auto loops() const -> bool { return loops_; } auto file_name() const -> const std::string& { return file_name_; } @@ -46,13 +47,13 @@ class AudioStreamer : public Object { void set_format(Format format) { format_ = format; } private: - Format format_ = INVALID_FORMAT; - bool playing_ = false; + Format format_{Format::kInvalid}; + bool playing_ : 1 {}; + bool loops_ : 1 {}; + bool eof_ : 1 {}; ALuint buffers_[kAudioStreamBufferCount]{}; - ALuint source_ = 0; + ALuint source_{}; std::string file_name_; - bool loops_ = false; - bool eof_ = false; }; #endif // BA_ENABLE_AUDIO diff --git a/src/ballistica/base/audio/ogg_stream.cc b/src/ballistica/base/audio/ogg_stream.cc index abe45317..5b51d427 100644 --- a/src/ballistica/base/audio/ogg_stream.cc +++ b/src/ballistica/base/audio/ogg_stream.cc @@ -53,9 +53,9 @@ OggStream::OggStream(const char* file_name, ALuint source, bool loop) vorbis_info_ = ov_info(&ogg_file_, -1); if (vorbis_info_->channels == 1) { - set_format(MONO16_FORMAT); + set_format(Format::kMono16); } else { - set_format(STEREO16_FORMAT); + set_format(Format::kStereo16); } } diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index 4b583e29..94792ad4 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -4,6 +4,7 @@ #include "ballistica/base/app_adapter/app_adapter.h" #include "ballistica/base/app_mode/app_mode_empty.h" +#include "ballistica/base/audio/audio_server.h" #include "ballistica/base/graphics/graphics_server.h" #include "ballistica/base/logic/logic.h" #include "ballistica/base/python/base_python.h" @@ -729,11 +730,8 @@ static auto PyEnv(PyObject* self) -> PyObject* { "ss" // ui_scale "sO" // on_tv "sO" // vr_mode - // "sO" // toolbar_test "sO" // demo_mode "sO" // arcade_mode - // "sO" // iircade_mode - // "si" // protocol_version "sO" // headless_mode "sO" // python_directory_app_site "ss" // device_name @@ -757,8 +755,6 @@ static auto PyEnv(PyObject* self) -> PyObject* { "vr_mode", g_core->IsVRMode() ? Py_True : Py_False, "demo_mode", g_buildconfig.demo_build() ? Py_True : Py_False, "arcade_mode", g_buildconfig.arcade_build() ? Py_True : Py_False, - // "iircade_mode", g_buildconfig.iircade_build() ? Py_True: Py_False, - // "protocol_version", kProtocolVersion, "headless_mode", g_core->HeadlessMode() ? Py_True : Py_False, "python_directory_app_site", site_py_dir ? *PythonRef::FromString(*site_py_dir) : Py_None, @@ -1603,6 +1599,52 @@ static PyMethodDef PyDevConsoleInputAdapterFinishDef = { "(internal)\n", }; +// -------------------------- audio_shutdown_begin ----------------------------- + +static auto PyAudioShutdownBegin(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + + auto* audio_event_loop = g_base->audio_server->event_loop(); + BA_PRECONDITION(audio_event_loop); + audio_event_loop->PushCall([] { g_base->audio_server->Shutdown(); }); + Py_RETURN_NONE; + + BA_PYTHON_CATCH; +} + +static PyMethodDef PyAudioShutdownBeginDef = { + "audio_shutdown_begin", // name + (PyCFunction)PyAudioShutdownBegin, // method + METH_NOARGS, // flags + + "audio_shutdown_begin() -> None\n" + "\n" + "(internal)\n", +}; + +// ----------------------- audio_shutdown_is_complete -------------------------- + +static auto PyAudioShutdownIsComplete(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + + if (g_base->audio_server->shutdown_completed()) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; + + BA_PYTHON_CATCH; +} + +static PyMethodDef PyAudioShutdownIsCompleteDef = { + "audio_shutdown_is_complete", // name + (PyCFunction)PyAudioShutdownIsComplete, // method + METH_NOARGS, // flags + + "audio_shutdown_is_complete() -> bool\n" + "\n" + "(internal)\n", +}; + // ----------------------------------------------------------------------------- auto PythonMethodsApp::GetMethods() -> std::vector { @@ -1658,6 +1700,8 @@ auto PythonMethodsApp::GetMethods() -> std::vector { PyGetDevConsoleInputTextDef, PySetDevConsoleInputTextDef, PyDevConsoleInputAdapterFinishDef, + PyAudioShutdownBeginDef, + PyAudioShutdownIsCompleteDef, }; } diff --git a/src/ballistica/base/ui/ui.cc b/src/ballistica/base/ui/ui.cc index 0b774b9c..34ce797d 100644 --- a/src/ballistica/base/ui/ui.cc +++ b/src/ballistica/base/ui/ui.cc @@ -42,7 +42,7 @@ UI::UI() { // VR and TV modes always use medium. scale_ = UIScale::kMedium; } else { - scale_ = g_core->platform->GetUIScale(); + scale_ = g_core->platform->GetDefaultUIScale(); } } } diff --git a/src/ballistica/core/platform/apple/core_platform_apple.cc b/src/ballistica/core/platform/apple/core_platform_apple.cc index 5320a8e5..b227b817 100644 --- a/src/ballistica/core/platform/apple/core_platform_apple.cc +++ b/src/ballistica/core/platform/apple/core_platform_apple.cc @@ -4,8 +4,10 @@ #include "ballistica/core/platform/apple/core_platform_apple.h" #if BA_XCODE_BUILD +#include #include #endif + #include #if BA_XCODE_BUILD @@ -102,7 +104,7 @@ auto CorePlatformApple::DoGetConfigDirectoryMonolithicDefault() auto CorePlatformApple::GetLocale() -> std::string { #if BA_XCODE_BUILD - return base::AppleUtils::GetLocaleString(); + return BallisticaKit::FromCppGetLocaleString(); #else return CorePlatform::GetLocale(); #endif @@ -124,7 +126,7 @@ auto CorePlatformApple::DoHasTouchScreen() -> bool { #endif } -auto CorePlatformApple::GetUIScale() -> UIScale { +auto CorePlatformApple::GetDefaultUIScale() -> UIScale { #if BA_OSTYPE_IOS if (base::AppleUtils::IsTablet()) { return UIScale::kMedium; @@ -132,8 +134,8 @@ auto CorePlatformApple::GetUIScale() -> UIScale { return UIScale::kSmall; } #else - // default case handles mac/tvos - return CorePlatform::GetUIScale(); + // Default case handles mac & tvos. + return CorePlatform::GetDefaultUIScale(); #endif } @@ -160,9 +162,8 @@ void CorePlatformApple::DisplayLog(const std::string& name, LogLevel level, } auto CorePlatformApple::DoGetDataDirectoryMonolithicDefault() -> std::string { -#if BA_XCODE_BUILD && !BA_HEADLESS_BUILD - // On Apple package-y builds use our resources dir. - return base::AppleUtils::GetResourcesPath(); +#if BA_XCODE_BUILD + return BallisticaKit::FromCppGetResourcesPath(); #else // Fall back to default. return CorePlatform::DoGetDataDirectoryMonolithicDefault(); @@ -292,13 +293,8 @@ void CorePlatformApple::OpenFileExternally(const std::string& path) { } void CorePlatformApple::OpenDirExternally(const std::string& path) { -#if BA_OSTYPE_MACOS - std::string cmd = std::string("open \"") + path + "\""; - int result = system(cmd.c_str()); - if (result != 0) { - Log(LogLevel::kError, "Got return value " + std::to_string(result) - + " on open cmd '" + cmd + "'"); - } +#if BA_OSTYPE_MACOS && BA_XCODE_BUILD + BallisticaKit::CocoaFromCppOpenDirExternally(path); #else CorePlatform::OpenDirExternally(path); #endif @@ -380,7 +376,7 @@ auto CorePlatformApple::DoClipboardIsSupported() -> bool { return base::AppleUtils::ClipboardIsSupported(); #else return CorePlatform::DoClipboardIsSupported(); -#endif // BA_XCODE_BUILD +#endif } auto CorePlatformApple::DoClipboardHasText() -> bool { @@ -388,7 +384,7 @@ auto CorePlatformApple::DoClipboardHasText() -> bool { return base::AppleUtils::ClipboardHasText(); #else return CorePlatform::DoClipboardHasText(); -#endif // BA_XCODE_BUILD +#endif } void CorePlatformApple::DoClipboardSetText(const std::string& text) { @@ -396,7 +392,7 @@ void CorePlatformApple::DoClipboardSetText(const std::string& text) { base::AppleUtils::ClipboardSetText(text); #else CorePlatform::DoClipboardSetText(text); -#endif // BA_XCODE_BUILD +#endif } auto CorePlatformApple::DoClipboardGetText() -> std::string { @@ -404,7 +400,7 @@ auto CorePlatformApple::DoClipboardGetText() -> std::string { return base::AppleUtils::ClipboardGetText(); #else return CorePlatform::DoClipboardGetText(); -#endif // BA_XCODE_BUILD +#endif } } // namespace ballistica::core diff --git a/src/ballistica/core/platform/apple/core_platform_apple.h b/src/ballistica/core/platform/apple/core_platform_apple.h index 6e8d337f..4198f7e8 100644 --- a/src/ballistica/core/platform/apple/core_platform_apple.h +++ b/src/ballistica/core/platform/apple/core_platform_apple.h @@ -24,7 +24,7 @@ class CorePlatformApple : public CorePlatform { auto GetLocale() -> std::string override; auto DoGetDeviceName() -> std::string override; auto DoHasTouchScreen() -> bool override; - auto GetUIScale() -> UIScale override; + auto GetDefaultUIScale() -> UIScale override; auto IsRunningOnDesktop() -> bool override; void DisplayLog(const std::string& name, LogLevel level, const std::string& msg) override; diff --git a/src/ballistica/core/platform/core_platform.cc b/src/ballistica/core/platform/core_platform.cc index b28976d3..338d451d 100644 --- a/src/ballistica/core/platform/core_platform.cc +++ b/src/ballistica/core/platform/core_platform.cc @@ -476,7 +476,7 @@ void CorePlatform::SleepMicrosecs(millisecs_t ms) { #pragma clang diagnostic push #pragma ide diagnostic ignored "NullDereferences" -auto CorePlatform::GetUIScale() -> UIScale { +auto CorePlatform::GetDefaultUIScale() -> UIScale { // Handles mac/pc/linux cases. return UIScale::kLarge; } diff --git a/src/ballistica/core/platform/core_platform.h b/src/ballistica/core/platform/core_platform.h index f01e9940..df05291a 100644 --- a/src/ballistica/core/platform/core_platform.h +++ b/src/ballistica/core/platform/core_platform.h @@ -97,26 +97,22 @@ class CorePlatform { #pragma mark PRINTING/LOGGING -------------------------------------------------- /// Display a message to any default log for the platform (android log, - /// etc.) Note that this can be called from any thread. + /// etc.) Note that this can be called from any thread. Default + /// implementation does nothing. virtual void DisplayLog(const std::string& name, LogLevel level, const std::string& msg); #pragma mark ENVIRONMENT ------------------------------------------------------- - // Return a simple name for the platform: 'mac', 'windows', 'linux', etc. + /// Return a simple name for the platform: 'mac', 'windows', 'linux', etc. virtual auto GetPlatformName() -> std::string; - // Return a simple name for the subplatform: 'amazon', 'google', etc. + /// Return a simple name for the subplatform: 'amazon', 'google', etc. virtual auto GetSubplatformName() -> std::string; - // Are we running in event-push-mode? With this on, we return from Main() - // and the system handles the event loop. With it off, we loop in Main() - // ourself. - // virtual auto IsEventPushMode() -> bool; - /// Return the interface type based on the environment (phone, tablet, /// etc). - virtual auto GetUIScale() -> UIScale; + virtual auto GetDefaultUIScale() -> UIScale; /// Return default DataDirectory value for monolithic builds. auto GetDataDirectoryMonolithicDefault() -> std::string; @@ -387,7 +383,7 @@ class CorePlatform { static void SleepMillisecs(millisecs_t ms); - static void SleepMicrosecs(millisecs_t ms); + static void SleepMicrosecs(microsecs_t ms); /// Given a C++ symbol, attempt to return a pretty one. virtual auto DemangleCXXSymbol(const std::string& s) -> std::string; diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 6c182345..c67f264a 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21453; +const int kEngineBuildNumber = 21465; const char* kEngineVersion = "1.7.28"; const int kEngineApiVersion = 8; diff --git a/src/ballistica/shared/foundation/event_loop.cc b/src/ballistica/shared/foundation/event_loop.cc index cb2051c5..9133557b 100644 --- a/src/ballistica/shared/foundation/event_loop.cc +++ b/src/ballistica/shared/foundation/event_loop.cc @@ -248,24 +248,27 @@ void EventLoop::WaitForNextEvent_(bool single_cycle) { } } -// void EventLoop::LoopUpkeep_(bool single_cycle) { -// assert(g_core); -// // Keep our autorelease pool clean on mac/ios -// // FIXME: Should define a CorePlatform::ThreadHelper or something -// // so we don't have platform-specific code here. -// #if BA_XCODE_BUILD -// // Let's not do autorelease pools when being called ad-hoc, -// // since in that case we're part of another run loop -// // (and its crashing on drain for some reason) -// if (!single_cycle) { -// if (auto_release_pool_) { -// g_core->platform->DrainAutoReleasePool(auto_release_pool_); -// auto_release_pool_ = nullptr; -// } -// auto_release_pool_ = g_core->platform->NewAutoReleasePool(); -// } -// #endif -// } +// Note to self (Oct '23): can probably kill this at some point, +// but am still using some non-ARC objc stuff from logic thread +// so should keep it around just a bit longer just in case. +void EventLoop::LoopUpkeep_(bool single_cycle) { + assert(g_core); + // Keep our autorelease pool clean on mac/ios + // FIXME: Should define a CorePlatform::ThreadHelper or something + // so we don't have platform-specific code here. +#if BA_XCODE_BUILD + // Let's not do autorelease pools when being called ad-hoc, + // since in that case we're part of another run loop + // (and its crashing on drain for some reason) + if (!single_cycle) { + if (auto_release_pool_) { + g_core->platform->DrainAutoReleasePool(auto_release_pool_); + auto_release_pool_ = nullptr; + } + auto_release_pool_ = g_core->platform->NewAutoReleasePool(); + } +#endif +} void EventLoop::RunToCompletion() { Run_(false); } void EventLoop::RunSingleCycle() { Run_(true); } @@ -273,7 +276,7 @@ void EventLoop::RunSingleCycle() { Run_(true); } void EventLoop::Run_(bool single_cycle) { assert(g_core); while (true) { - // LoopUpkeep_(single_cycle); + LoopUpkeep_(single_cycle); WaitForNextEvent_(single_cycle); diff --git a/src/ballistica/shared/foundation/event_loop.h b/src/ballistica/shared/foundation/event_loop.h index 9c6c6457..40f03631 100644 --- a/src/ballistica/shared/foundation/event_loop.h +++ b/src/ballistica/shared/foundation/event_loop.h @@ -118,7 +118,6 @@ class EventLoop { auto CheckPushRunnableSafety_() -> bool; void SetInternalThreadName_(const std::string& name); void WaitForNextEvent_(bool single_cycle); - // void LoopUpkeep_(bool single_cycle); void LogThreadMessageTally_( std::vector>* log_entries); void PushLocalRunnable_(Runnable* runnable, bool* completion_flag); @@ -155,6 +154,13 @@ class EventLoop { void BootstrapThread_(); + void LoopUpkeep_(bool single_cycle); + + // FIXME: Should generalize this to some sort of PlatformThreadData class. +#if BA_XCODE_BUILD + void* auto_release_pool_{}; +#endif + bool bootstrapped_{}; bool writing_tally_{}; bool suspended_{}; diff --git a/src/ballistica/shared/foundation/inline.h b/src/ballistica/shared/foundation/inline.h index db9d8e1d..5376eeb3 100644 --- a/src/ballistica/shared/foundation/inline.h +++ b/src/ballistica/shared/foundation/inline.h @@ -88,8 +88,7 @@ template auto static_cast_check_type(IN_TYPE in) -> OUT_TYPE { auto out_static = static_cast(in); if (g_buildconfig.debug_build()) { - auto out_dynamic = dynamic_cast(in); - assert(out_static == out_dynamic); + assert(out_static == dynamic_cast(in)); } return out_static; } diff --git a/src/ballistica/shared/foundation/types.h b/src/ballistica/shared/foundation/types.h index eed5c225..fd35d729 100644 --- a/src/ballistica/shared/foundation/types.h +++ b/src/ballistica/shared/foundation/types.h @@ -30,6 +30,7 @@ typedef struct _SDL_Joystick SDL_Joystick; namespace ballistica { // Used internally for time values. +typedef double seconds_t; typedef int64_t millisecs_t; typedef int64_t microsecs_t; diff --git a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc index ac51c315..f03b6841 100644 --- a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc +++ b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc @@ -23,10 +23,6 @@ #include "ballistica/ui_v1/widget/row_widget.h" #include "ballistica/ui_v1/widget/scroll_widget.h" -// #if !BA_HEADLESS_BUILD && !BA_XCODE_NEW_PROJECT -// extern "C" void SDL_ericf_focus(void); -// #endif - namespace ballistica::ui_v1 { // Ignore signed bitwise stuff; python macros do it quite a bit.