diff --git a/.efrocachemap b/.efrocachemap index 52843119..f007fca3 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4068,26 +4068,26 @@ "build/assets/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/2d/ef/5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/b0/8a/55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/86/5b/2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/af/b7/62892190ebe1627f6754cf9b8f81", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/5c/1a/ca899e8d4ed6bc687beb0e38dcd6", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/41/83/dd4dca131a728b0895132fed9460", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/76/c3/1390ce3a6f8cc8c8f6fb964acfa6", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/7a/08/7bbb34a806cb2496c09dfde676e4", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/9a/ec/b1ceae80bb7883223fb452357fb7", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/9f/1c/143dea8c4fe1e3e2d85a8c840e72", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/81/b0/e1b2ca529888b4404141ed85fead", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/03/9d/1a6c8386f0d400337c4603041b35", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/2e/99/6a8ca74892e6592dbc76a3c7b131", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/1c/04/b71e12f876970e293a0e080db9ea", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/56/26/881bfb1a0f3c0ffa9be27d879943", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/99/f9/f12a44c19406317c097ae87e0778", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/5d/ee/d9d65c36efb1caca59f4a8911824", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/32/4e/ecd23e2010e07d78f99b82d78b0f", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/08/36/45f7f8d005b350aba84b813a83e8", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/ea/8b/dcbc7facc3517832cd087cde8392", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/4c/2b/3d925eca850cad90a85110f7363d", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/b4/fb/5e7980c7a8af2e54283f3cba8432", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/b4/56/d1a2a14e4010e9684d966c723383", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/cc/c6/e6492f9eeb1c6904ad745abe6eee", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/08/19/389fd56c41b7ecaa5a8ce0030038", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/2e/36/d7de8b609ed2ac99d1e0d5de0ec3", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/68/0d/768d03448e5a5d4531dd1a11ebd9", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/ba/6f/5c4982d5026eb61c97cdd2029b69", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/5f/b0/3fce5a36c251f4090efa8d5c17f6", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/41/76/508f80aa2cad4248be0f644ac577", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/e2/8c/71a04c5829575d085aef4e36a77f", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/50/ea/3425b68d9cbd018b591620ea21c8", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/ab/c8/e638cf97e0f18c247c87c453d017", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/3a/16/39bbdf1fd716309eb9bc00a3914c", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/60/ff/8ad1713b4fbd20d43b3a791095b3", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/81/38/d5beff6112f26a82271e6f8430ba", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/c2/ed/95e885a5bc785a1e595c74ff76dc", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/e2/1a/a14431e08d923eb01f6a06132b30", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/6d/ab/bb8b72e8764292d17803fbecfcb0", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/3f/7a/37310338d005ba12cab72fdd990d", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/de/09/c0e6a848c89c20279a8d562821f7", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/59/3a/97b3e97889a8111b1d4fa54e28e9", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/9e/e5/ec1673fdc92998ee8952225b46ec", "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/be/19/b5458933dfc7371d91ecfcd2e06f", "build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/4e/48/123b806cbe6ddb3d9a8368bbb4f8", "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/be/19/b5458933dfc7371d91ecfcd2e06f", @@ -4104,14 +4104,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/7e/fa/291fd7e935502ced7e99b8c8f7f0", "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/e1/cb/7e8440699e59e8646da25aa5782b", "build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/7e/fa/291fd7e935502ced7e99b8c8f7f0", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/66/17/448c77d6413f25555e40055d154b", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/56/b0/7ce3384cfb63d3a224e317c69fa8", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/b6/73/847160ecfbbbea758f130c03abb8", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/dc/e6/e052a9115ca83686c6634d2c26ff", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/2e/56/390279b063b7f8455cf7f1656608", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/3a/d7/a0329b38db9b594c36ff88a884c9", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/a5/9b/0802a9d6604fffc0136ac95a5be3", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/e9/74/5d6a32e3a161a2f5597aa9336b79", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/5d/1c/956723a220dbd69a6fccbdec29df", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/e5/5c/c7737071206b53292cdc954bfde5", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/50/78/552f6c71715a4939b0ca67468efc", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/71/5f/bcbdc8e71a29515ed95ec3c33fae", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/de/64/60396bbaba483ff09a421998372a", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/8b/92/725858ebddf7f1d35562ce516e59", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/15/08/f62cc35c4746f7767059e0e358b5", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/e4/39/8964fd8d1781fe1d5b09bce43d98", "src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/f8/85/fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/48/4b/e6974f0a4d14be8213dc00d971c3", "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/3e/7a/203e2a5d2b5bb42cfe3fd2fe16c2", diff --git a/CHANGELOG.md b/CHANGELOG.md index 9830fc63..65cf739f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,9 @@ +### 1.7.22 (build 21154, api 8, 2023-06-28) + +- Fixed a very rare race condition when launching threads or sending synchronous + cross-thread messages. This was manifesting as one out of several thousand + server launches hanging. + ### 1.7.21 (build 21152, api 8, 2023-06-27) - Fixed an issue where server builds would not always include collision meshes. @@ -102,7 +108,7 @@ to the local device. - The `ba.Context` class has been reworked a bit and is now `ba.ContextRef` to more accurately describe what it actually is. The default constructor - (`ba.ContextRef()`) will grab a reference to the current context. To get a + (`ba.ContextRef()`) will grab a reference to the current context. To get a context-ref pointing to *no* context, do `ba.ContextRef.empty()`. UI stuff will now insist on being run with no context set. To get references to Activity/Session contexts, use the context() methods they provide. @@ -127,7 +133,7 @@ conversions' may want to keep an eye on this. - There is no longer a standalone `ba.playsound()` function. Both ui-sounds ( acquired via `bauiv1.getsound()` and scene-sounds (acquired via - `bascenev1.getsound()`) now have a play() method on them for this purpose. So + `bascenev1.getsound()`) now have a play() method on them for this purpose. So just search for any instances of 'playsound' in your code and change stuff like `ba.playsound(ba.getsound('error'))` to `bs.getsound('error').play()`. Playing sounds in timers is now especially nicer looking; instead of @@ -303,8 +309,10 @@ instances of `bastd.ui` with `bauiv1lib` and all other instances of `bastd` with `bascenev1lib`. That should mostly do it. Random tip: check out the `tools/pcommand mypy_files` as a handy tool to help get your mods updated. -- (build 21057) Fixed an issue with news items erroring on the main menu (thanks for the heads up Rikko) -- (build 21059) Fixed an issue where trying to add a new playlist would error (thanks for the heads up SEBASTIAN2059) +- (build 21057) Fixed an issue with news items erroring on the main menu (thanks + for the heads up Rikko) +- (build 21059) Fixed an issue where trying to add a new playlist would error ( + thanks for the heads up SEBASTIAN2059) - (build 21059) Fixed meta scanning which was coming up empty. Note that games must now tag themselves via `ba_meta export bascenev1.GameActivity` instead of `ba_meta export game` to be discovered. Warnings will be issued if the old tag @@ -350,7 +358,8 @@ Android would not show correctly under the player. - (build 21084) Plugin UI now has a categories dropdown for showing only enabled or disabled plugins (Thanks vishal332008!) -- (build 21095) Fixed an issue where certain buttons such as map selection buttons +- (build 21095) Fixed an issue where certain buttons such as map selection + buttons would draw incorrectly. - (build 21106) Fixed an issue where in-game ping would always display green no matter how bad the ping was. @@ -1235,7 +1244,7 @@ cause a crash. - Fixed a case where an early fatal error could lead to a hung app and no error dialog. -- Added environment variables which can override UI scale for testing. Set +- Added environment variables which can override UI scale for testing. Set `BA_FORCE_UI_SCALE` to small, medium or large. - Added a ba.UIScale enum. The value at ba.app.uiscale replaces the old `ba.app.interface_type`, `ba.app.small_ui`, and `ba.app.med_ui` values. diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 3160bd5c..b3059284 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -28,8 +28,8 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21152 -TARGET_BALLISTICA_VERSION = '1.7.21' +TARGET_BALLISTICA_BUILD = 21154 +TARGET_BALLISTICA_VERSION = '1.7.22' _g_env_config: EnvConfig | None = None g_paths_set_failed = False # pylint: disable=invalid-name diff --git a/src/ballistica/base/dynamics/bg/bg_dynamics.cc b/src/ballistica/base/dynamics/bg/bg_dynamics.cc index 5def66d4..e92604e6 100644 --- a/src/ballistica/base/dynamics/bg/bg_dynamics.cc +++ b/src/ballistica/base/dynamics/bg/bg_dynamics.cc @@ -49,9 +49,15 @@ void BGDynamics::Step(const Vector3f& cam_pos, int step_millisecs) { TooSlow(); } - // If we're slightly behind, just don't send this step; - // the bg dynamics will slow down a bit but nothing will disappear this way. - if (step_count > 1) return; + // If we're slightly behind, just don't send this step; the bg dynamics + // will slow down a bit but nothing will disappear this way, which should + // be less jarring. + // + // HMMM; wondering if this should be limited in some way; it might lead to + // oddly slow feeling bg sims if things are consistently slow. + if (step_count > 1) { + return; + } // Pass a newly allocated raw pointer to the bg-dynamics thread; it takes care // of disposing it when done. diff --git a/src/ballistica/base/dynamics/bg/bg_dynamics_server.cc b/src/ballistica/base/dynamics/bg/bg_dynamics_server.cc index b2f88e50..f10a8203 100644 --- a/src/ballistica/base/dynamics/bg/bg_dynamics_server.cc +++ b/src/ballistica/base/dynamics/bg/bg_dynamics_server.cc @@ -2281,7 +2281,7 @@ void BGDynamicsServer::Step(StepData* step_data) { // data. auto ref(Object::CompleteDeferred(step_data)); - // Keep this in sync with the game thread's. + // Keep our quality in sync with the graphics thread's. graphics_quality_ = g_base->graphics_server->graphics_quality(); cam_pos_ = step_data->cam_pos; @@ -2352,6 +2352,13 @@ void BGDynamicsServer::Step(StepData* step_data) { step_count_--; } assert(step_count_ >= 0); + + // Math sanity check. + if (step_count_ < 0) { + BA_LOG_ONCE(LogLevel::kWarning, "BGDynamics step_count too low (" + + std::to_string(step_count_) + + "); should not happen."); + } } void BGDynamicsServer::PushStep(StepData* data) { @@ -2361,6 +2368,13 @@ void BGDynamicsServer::PushStep(StepData* data) { step_count_++; } + // Client thread should stop feeding us if we get clogged up. + if (step_count_ > 5) { + BA_LOG_ONCE(LogLevel::kWarning, "BGDynamics step_count too high (" + + std::to_string(step_count_) + + "); should not happen."); + } + event_loop()->PushCall([this, data] { Step(data); }); } diff --git a/src/ballistica/base/logic/logic.h b/src/ballistica/base/logic/logic.h index ae0a15e7..6f2d539e 100644 --- a/src/ballistica/base/logic/logic.h +++ b/src/ballistica/base/logic/logic.h @@ -72,10 +72,10 @@ class Logic { auto display_time_microsecs() { return display_time_microsecs_; } /// Return current display-time increment in seconds. - auto display_time_increment() -> double { return display_time_increment_; } + auto display_time_increment() { return display_time_increment_; } /// Return current display-time increment in microseconds. - auto display_time_increment_microsecs() -> double { + auto display_time_increment_microsecs() { return display_time_increment_microsecs_; } diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 72523332..08f9cbe0 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,8 +39,8 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21152; -const char* kEngineVersion = "1.7.21"; +const int kEngineBuildNumber = 21154; +const char* kEngineVersion = "1.7.22"; auto MonolithicMain(const core::CoreConfig& core_config) -> int { // This code is meant to be run standalone so won't inherit any diff --git a/src/ballistica/shared/foundation/event_loop.cc b/src/ballistica/shared/foundation/event_loop.cc index 301956f7..0d0e3c05 100644 --- a/src/ballistica/shared/foundation/event_loop.cc +++ b/src/ballistica/shared/foundation/event_loop.cc @@ -19,6 +19,15 @@ EventLoop::EventLoop(EventLoopID identifier_in, ThreadSource source) : source_(source), identifier_(identifier_in) { switch (source_) { case ThreadSource::kCreate: { + // IMPORTANT: We grab this lock *before* kicking off our thread, and + // we hold it until we're actively listening for the completion + // notification. The new thread also grabs the lock before notifying + // us, which ensures that we've reached the waiting state before the + // notification happens. Otherwise it is possible for them to push out + // a notification before we start waiting for it, which means we hang + // when we do start listening and nothing comes in. + std::unique_lock lock(client_listener_mutex_); + int (*func)(void*); void* (*funcp)(void*); switch (identifier_) { @@ -80,7 +89,6 @@ EventLoop::EventLoop(EventLoopID identifier_in, ThreadSource source) g_core->LifecycleLog("logic thread bootstrap wait begin"); } - std::unique_lock lock(client_listener_mutex_); client_listener_cv_.wait(lock, [this] { return bootstrapped_; }); if (identifier_ == EventLoopID::kLogic) { @@ -392,6 +400,14 @@ auto EventLoop::ThreadMain() -> int { // Mark ourself as bootstrapped and signal listeners so // anyone waiting for us to spin up can move along. bootstrapped_ = true; + + { + // Momentarily grab this lock. This ensures that whoever launched us + // is now actively waiting for this notification. If we skipped this + // it would be possible to notify before they start listening which + // leads to a hang. + std::unique_lock lock(client_listener_mutex_); + } client_listener_cv_.notify_all(); // Now just run our loop until we die. @@ -663,6 +679,13 @@ void EventLoop::RunPendingRunnables() { } } if (do_notify_listeners) { + { + // Momentarily grab this lock. This ensures that whoever pushed us is + // now actively waiting for completion notification. If we skipped + // this it would be possible to notify before they start listening + // which leads to a hang. + std::unique_lock lock(client_listener_mutex_); + } client_listener_cv_.notify_all(); } } @@ -714,6 +737,16 @@ void EventLoop::PushRunnable(Runnable* runnable) { void EventLoop::PushRunnableSynchronous(Runnable* runnable) { bool complete{}; bool* complete_ptr{&complete}; + + // IMPORTANT: We grab this lock *before* pushing our runnable, and we hold + // it until we're actively listening for the completion notification. The + // receiver also grabs the lock before notifying us, which ensures that + // we've reached the waiting state before the notification happens. + // Otherwise it is possible for them to push out a notification before we + // start waiting for it, which means we hang when we do start listening + // and nothing comes in. + std::unique_lock lock(client_listener_mutex_); + if (std::this_thread::get_id() == thread_id()) { FatalError( "PushRunnableSynchronous called from target thread;" @@ -727,7 +760,6 @@ void EventLoop::PushRunnableSynchronous(Runnable* runnable) { } // Now listen until our completion flag gets set. - std::unique_lock lock(client_listener_mutex_); client_listener_cv_.wait(lock, [complete_ptr] { // Go back to sleep on spurious wakeups // (if we're not actually complete yet). diff --git a/src/ballistica/shared/foundation/macros.h b/src/ballistica/shared/foundation/macros.h index 0859ab43..1a445200 100644 --- a/src/ballistica/shared/foundation/macros.h +++ b/src/ballistica/shared/foundation/macros.h @@ -90,14 +90,14 @@ } \ ((void)0) // (see 'Trailing-semicolon note' at top) -#define BA_LOG_ONCE(lvl, msg) \ - { \ - static bool did_log_here = false; \ - if (!did_log_here) { \ - ::ballistica::Log(lvl, msg); \ - did_log_here = true; \ - } \ - } \ +#define BA_LOG_ONCE(lvl, msg) \ + { \ + static bool did_log_here{}; \ + if (!did_log_here) { \ + ::ballistica::Log(lvl, msg); \ + did_log_here = true; \ + } \ + } \ ((void)0) // (see 'Trailing-semicolon note' at top) #define BA_LOG_PYTHON_TRACE(msg) ::ballistica::MacroLogPythonTrace(g_core, msg)