From 308241402da7517dcb6ba92b4e996390b3ff3393 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 3 Aug 2023 12:23:22 -0700 Subject: [PATCH] v1.7.25 --- .efrocachemap | 62 +++--- CHANGELOG.md | 7 +- Makefile | 2 +- config/toolconfigsrc/dir-locals.el | 13 +- src/assets/ba_data/python/baenv.py | 117 +++++++---- src/ballistica/base/app/app.cc | 8 +- src/ballistica/base/base.cc | 11 +- src/ballistica/core/core.cc | 7 +- src/ballistica/core/platform/core_platform.cc | 2 - src/ballistica/shared/ballistica.cc | 19 +- .../shared/foundation/event_loop.cc | 188 +++++++++--------- src/ballistica/shared/foundation/event_loop.h | 85 ++++---- src/ballistica/shared/python/python.cc | 5 + src/ballistica/shared/python/python.h | 6 +- tools/efrotools/toolconfig.py | 5 +- tools/pcommand | 1 - 16 files changed, 308 insertions(+), 230 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index d7b1e128..e5fe3c92 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -421,10 +421,10 @@ "build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55", - "build/assets/ba_data/data/langdata.json": "85819425f358eafcd41e91fb999612ba", + "build/assets/ba_data/data/langdata.json": "9929c8aa90669322bc0548cdb035fb1b", "build/assets/ba_data/data/languages/arabic.json": "db961f7fe0541a31880929e1c17ea957", "build/assets/ba_data/data/languages/belarussian.json": "5e373ddcfa6e1f771b74c02298a6599a", - "build/assets/ba_data/data/languages/chinese.json": "b600924583b899165757f412eef0dd01", + "build/assets/ba_data/data/languages/chinese.json": "6ca44804339926f5ad21584555aa22cf", "build/assets/ba_data/data/languages/chinesetraditional.json": "3fe960a8f0ca529aa57b4f9cb7385abc", "build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa", "build/assets/ba_data/data/languages/czech.json": "f3ce219840946cb8f9aa6d3e25927ab3", @@ -450,7 +450,7 @@ "build/assets/ba_data/data/languages/russian.json": "aa99f9f597787fe4e09c8ab53fe2e081", "build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef", - "build/assets/ba_data/data/languages/spanish.json": "8776884442afd08b31364dc6a8b0cc16", + "build/assets/ba_data/data/languages/spanish.json": "f9047a2d5e9f453981ca5f4cb53006fa", "build/assets/ba_data/data/languages/swedish.json": "77d671f10613291ebf9c71da66f18a18", "build/assets/ba_data/data/languages/tamil.json": "b9d4b4e107456ea6420ee0f9d9d7a03e", "build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc", @@ -4068,26 +4068,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": "884277e559d840df55f7e0dd1c186949", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "38240f93c1ae9065cf671be2e7620684", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "0480c97db92676754cf9552ee462dccb", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "052b4f4804e30da165744893e84d4a64", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "32d4c7a4bdac7a8cb12d4b42868a5979", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "42649b501212bea15f4b2f2741583339", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "03cbedec6c8f294d7fe57a6dc64803e8", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "37b2c536cd841b40319b0efae40471ce", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1f67054d55541b13ca912b4d9b57f499", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "5399d3f4924909ca1c0878f347e20a10", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "18c01fc3b664bb30bd2e5b6ad635b5f3", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "58559f7512a300586b8141591a21564e", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "cec05c9cf3882d00ba27b81696d95d1e", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "8e98e2bb1d7d9fa34f873fd2b7d82138", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "c215407232dbf0166d9980c1dae780b6", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "49107effdf5c1ee5a0533251a99f9f6a", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "8731458bc07c5a9ecea1c2ac46b5b96c", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "e51c409b2f1ca6433f96e3b10615a914", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "b3d961223dc84f6805613790d1e2c267", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "1cadf9af171902a4984658c624a42142", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "5c33841bddb06db422bc7b610d54e850", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "7c68be74dc87cc8bea603351d4edc19e", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "4aee26e9c9399db7036d63c05b415146", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "0c863960576179584121bfa00b0c0176", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "5c7ce41451ea6fd44f3106ad4d333def", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "189837b829a2ea704fad9fb163e50490", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "ddc816b66933c7e5b811c9c7c2dd16b8", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "98e206e6e92514f7be59096a135bed3c", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f29ba9eeddf9a55febff06fca7db52a9", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "c63f95f2f7e813351dfe18f51e6deeb4", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c303ae3176d11b2b134068878e786833", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "123098bb0bb01419fa2acdad01be34a0", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "0d416da46426a9969ed3b7a91f5497b5", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "9e924485487aaabbc137c02f895247f2", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "61af0496d9b62e671fc55a38df776d99", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "62d2eaf952658ef8fe0d9910c9f8d964", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "6e35a1c8093de8886134d2f1dab4a56a", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "803800bebf963742fd26cae8e793245b", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "6bb44a0e36fa87f384aa4a29f56c9dcc", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "f72615c3eeea0a5f8789faa20d053f85", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "85ba4e81a1f7ae2cff4b1355eb49904f", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "498921f7eb2afd327d4b900cb70e31f9", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "85ba4e81a1f7ae2cff4b1355eb49904f", @@ -4104,14 +4104,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "0ab638b6602610bdaf432e3cc2464080", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "92394eb19387c363471ce134ac9e6a1b", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "0ab638b6602610bdaf432e3cc2464080", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "aa2d3f27999da77b4ad584df0707d8c0", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "2d134738e7ea8d7751ebae3df834806e", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "451c1d7295846fcad90b649fc574ee0e", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "3e910b8da4a13dbcdde7955bbde17abb", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "987b68e1e4b4e89bd73d5014735318d6", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "520f2b3e9c7ae15954589ce148dc1506", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "d02da070911bac71d7dbb4afc7a93e6a", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "629d285dfe041d4237e14d140ac70482", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "ae1fcb25dead5a8b306d1a3c8d02887e", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "56b419764d2ea9811fbca8bef99d032e", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "787b4c6337d367c2ad0cdf1cdc980b5e", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "7d4b36b9cc305832430210d7db55f13a", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ae9083ecbbcf81f54ab3aafbf5a58273", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "1220228d0a0036c898483ef73b41056c", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "14d5968a2cb52b1eb9c33704f90a3e4d", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "a85e95a06c5a3f696334c3da807c0ada", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318", "src/ballistica/base/mgen/pyembed/binding_base.inc": "eeddad968b176000e31c65be6206a2bc", diff --git a/CHANGELOG.md b/CHANGELOG.md index 335b2ef7..7521401c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ -### 1.7.25 (build 21208, api 8, 2023-07-31) +### 1.7.25 (build 21211, api 8, 2023-08-03) +- Fixed an issue where the main thread was holding the Python GIL by default in + monolithic builds with environment-managed event loops. This theoretically + could have lead to stuttery performanace in the Android or Mac builds. +- Did a bit of cleanup on `baenv.py` in preparation for some additional setup it + will soon be doing to give users more control over logging. - `getconfig` and `setconfig` in `efrotools` are now `getprojectconfig` and `setprojectconfig` (to reflect the file name changes that happened in 1.7.20). - The efrocache system (how assets and prebuilt binaries are downloaded during diff --git a/Makefile b/Makefile index 262f21ae..f5ea4548 100644 --- a/Makefile +++ b/Makefile @@ -1167,7 +1167,7 @@ CHECK_CLEAN_SAFETY = tools/pcommand check_clean_safety TOOL_CFG_INST = tools/pcommand tool_config_install # Anything that affects tool-config generation. -TOOL_CFG_SRC = tools/efrotools/pcommand.py config/projectconfig.json +TOOL_CFG_SRC = tools/efrotools/toolconfig.py config/projectconfig.json # Anything that should trigger an environment-check when changed. ENV_SRC = tools/pcommand tools/batools/build.py diff --git a/config/toolconfigsrc/dir-locals.el b/config/toolconfigsrc/dir-locals.el index 82acc8ef..389432c7 100644 --- a/config/toolconfigsrc/dir-locals.el +++ b/config/toolconfigsrc/dir-locals.el @@ -1,10 +1,15 @@ ;; -*- lexical-binding: t; -*- ( - - ;; Specify some extra paths that project.el searches and whatnot should ignore. - ;; Note that gitignored stuff is ignored implicitly. - (nil . ((project-vc-ignores . ("docs" + + ;; Stuff that applies everywhere. + (nil . ( + ;; Short project name to save some space in mode-lines/messages/etc. + (project-vc-name . "bainternal") + + ;; Extra paths that searches and whatnot should ignore. Note that + ;; gitignored stuff is ignored implicitly. + (project-vc-ignores . ("docs" "submodules" "src/external" "src/assets/ba_data/python-site-packages" diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 037a7dfd..6460f821 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -24,8 +24,9 @@ from dataclasses import dataclass from typing import TYPE_CHECKING import __main__ - if TYPE_CHECKING: + from typing import Any + from efro.log import LogHandler # IMPORTANT - It is likely (and in some cases expected) that this @@ -51,27 +52,59 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21208 +TARGET_BALLISTICA_BUILD = 21211 TARGET_BALLISTICA_VERSION = '1.7.25' @dataclass class EnvConfig: - """Environment values put together by the configure call.""" + """Final config values we provide to the engine.""" + # Where app config/state data lives. config_dir: str + + # Directory containing ba_data and any other platform-specific data. data_dir: str - user_python_dir: str | None + + # Where the app's built-in Python stuff lives. app_python_dir: str | None + + # Where the app's built-in Python stuff lives in the default case. standard_app_python_dir: str + + # Where the app's bundled third party Python stuff lives. site_python_dir: str | None - log_handler: LogHandler | None + + # Custom Python provided by the user (mods). + user_python_dir: str | None + + # We have a mechanism allowing app scripts to be overridden by + # placing a specially named directory in a user-scripts dir. + # This is true if that is enabled. is_user_app_python_dir: bool + # Our fancy app log handler. This handles feeding logs, stdout, and + # stderr into the engine so they show up on in-app consoles, etc. + log_handler: LogHandler | None + + # Initial data from the ballisticakit-config.json file. This is + # passed mostly as an optimization to avoid reading the same config + # file twice, since config data is first needed in baenv and next in + # the engine. It will be cleared after passing it to the app's + # config management subsystem and should not be accessed by any + # other code. + initial_app_config: Any + @dataclass class _EnvGlobals: - """Our globals we store in the main module.""" + """Globals related to baenv's operation. + + We store this in __main__ instead of in our own module because it + is likely that multiple versions of our module will be spun up + and we want a single set of globals (see notes at top of our module + code). + """ config: EnvConfig | None = None called_configure: bool = False @@ -90,7 +123,7 @@ class _EnvGlobals: def did_paths_set_fail() -> bool: - """Did we try to set paths and failed?""" + """Did we try to set paths and fail?""" return _EnvGlobals.get().paths_set_failed @@ -104,8 +137,14 @@ def get_config() -> EnvConfig: """Return the active config, creating a default if none exists.""" envglobals = _EnvGlobals.get() + # If configure() has not been explicitly called, set up a + # minimally-intrusive default config. We want Ballistica to default + # to being a good citizen when imported into alien environments and + # not blow away logging or otherwise muck with stuff. All official + # paths to run Ballistica apps should be explicitly calling + # configure() first to get a full featured setup. if not envglobals.called_configure: - configure() + configure(setup_logging=False) config = envglobals.config if config is None: @@ -123,17 +162,20 @@ def configure( app_python_dir: str | None = None, site_python_dir: str | None = None, contains_python_dist: bool = False, + setup_logging: bool = True, ) -> None: - """Set up the Python environment for running a Ballistica app. + """Set up the environment for running a Ballistica app. This includes things such as Python path wrangling and app directory - creation. This should be called before any other ballistica modules - are imported since it may make changes to sys.path which can affect - where those modules get loaded from. + creation. This must be called before any actual Ballistica modules + are imported; the environment is locked in as soon as that happens. """ envglobals = _EnvGlobals.get() + # Keep track of whether we've been *called*, not whether a config + # has been created. Otherwise its possible to get multiple + # overlapping configure calls going. if envglobals.called_configure: raise RuntimeError( 'baenv.configure() has already been called;' @@ -141,21 +183,12 @@ def configure( ) envglobals.called_configure = True - # The very first thing we do is set up our logging system and pipe - # Python's stdout/stderr into it. Then we can at least debug - # problems on systems where native stdout/stderr is not easily - # accessible such as Android. - log_handler = _setup_logging() - - # We want to always be run in UTF-8 mode; complain if we're not. - if sys.flags.utf8_mode != 1: - logging.warning( - "Python's UTF-8 mode is not set. Running ballistica without" - ' it may lead to errors.' - ) - - # Attempt to set up Python paths and our own data paths so that - # engine modules, mods, etc. are pulled from predictable places. + # The very first thing we do is setup Python paths (while also + # calculating some engine paths). This code needs to be bulletproof + # since we have no logging yet at this point. We used to set up + # logging first, but this way logging stuff will get loaded from its + # proper final path (otherwise we might wind up using two different + # versions of efro.logging in a single engine run). ( user_python_dir, app_python_dir, @@ -172,6 +205,19 @@ def configure( config_dir, ) + # The second thing we do is set up our logging system and pipe + # Python's stdout/stderr into it. At this point we can at least + # debug problems on systems where native stdout/stderr is not easily + # accessible such as Android. + log_handler = _setup_logging() if setup_logging else None + + # We want to always be run in UTF-8 mode; complain if we're not. + if sys.flags.utf8_mode != 1: + logging.warning( + "Python's UTF-8 mode is not set. Running Ballistica without" + ' it may lead to errors.' + ) + # Attempt to create dirs that we'll write stuff to. _setup_dirs(config_dir, user_python_dir) @@ -188,6 +234,7 @@ def configure( site_python_dir=site_python_dir, log_handler=log_handler, is_user_app_python_dir=is_user_app_python_dir, + initial_app_config=None, ) @@ -198,11 +245,13 @@ def _calc_data_dir(data_dir: str | None) -> str: assert Path(__file__).parts[-3:-1] == ('ba_data', 'python') data_dir_path = Path(__file__).parents[2] - # Prefer tidy relative paths like '.' if possible so that things - # like stack traces are easier to read. - - # NOTE: Perhaps we should have an option to disable this - # behavior for cases where the user might be doing chdir stuff. + # Prefer tidy relative paths like './ba_data' if possible so + # that things like stack traces are easier to read. For best + # results, platforms where CWD doesn't matter can chdir to where + # ba_data lives before calling configure(). + # + # NOTE: If there's ever a case where the user is chdir'ing at + # runtime we might want an option to use only abs paths here. cwd_path = Path.cwd() data_dir = str( data_dir_path.relative_to(cwd_path) @@ -226,7 +275,7 @@ def _setup_logging() -> LogHandler: def _setup_certs(contains_python_dist: bool) -> None: - # In situations where we're bringing our own Python let's also + # In situations where we're bringing our own Python, let's also # provide our own root certs so ssl works. We can consider # overriding this in particular embedded cases if we can verify that # system certs are working. We also allow forcing this via an env @@ -279,7 +328,7 @@ def _setup_paths( envglobals.paths_set_failed = True else: - # Ok; _babase hasn't been imported yet so we can muck with + # Ok; _babase hasn't been imported yet, so we can muck with # Python paths. if app_python_dir is None: diff --git a/src/ballistica/base/app/app.cc b/src/ballistica/base/app/app.cc index 7a495860..e76631fd 100644 --- a/src/ballistica/base/app/app.cc +++ b/src/ballistica/base/app/app.cc @@ -87,7 +87,7 @@ void App::RunRenderUpkeepCycle() { // Pump thread messages (we're being driven by frame-draw callbacks // so this is the only place that it gets done at). - event_loop()->RunEventLoop(true); // Single pass only. + event_loop()->RunSingleCycle(); // Now do the general app event cycle for whoever needs to process things. RunEvents(); @@ -301,10 +301,14 @@ void App::DidFinishRenderingFrame(FrameDef* frame) {} void App::PrimeMainThreadEventPump() { assert(!ManagesEventLoop()); + // Need to release the GIL while we're doing this so other thread + // can do their Python-y stuff. + Python::ScopedInterpreterLockRelease release; + // Pump events manually until a screen gets created. // At that point we use frame-draws to drive our event loop. while (!g_base->graphics_server->initial_screen_created()) { - event_loop()->RunEventLoop(true); + event_loop()->RunSingleCycle(); core::CorePlatform::SleepMillisecs(1); } } diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index 3192c6a1..42da0575 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -84,8 +84,7 @@ void BaseFeatureSet::OnModuleExec(PyObject* module) { g_core->LifecycleLog("_babase exec begin"); - // Want to run this at the last possible moment before spinning up our - // BaseFeatureSet. This locks in baenv customizations. + // This locks in a baenv configuration. g_core->ApplyBaEnvConfig(); // Create our feature-set's C++ front-end. @@ -280,18 +279,14 @@ void BaseFeatureSet::RunAppToCompletion() { BA_PRECONDITION(!called_run_app_to_completion_); called_run_app_to_completion_ = true; - // Start things moving if not done yet. if (!called_start_app_) { StartApp(); } - // Let go of the GIL while we're running. The logic thread or other things - // will grab it when needed. + // Let go of the GIL while we're running. Python::ScopedInterpreterLockRelease gil_release; - // On our event-loop-managing platforms we now simply sit in our event - // loop until the app is quit. - g_core->main_event_loop()->RunEventLoop(false); + g_core->main_event_loop()->RunToCompletion(); } void BaseFeatureSet::PrimeAppMainThreadEventPump() { diff --git a/src/ballistica/core/core.cc b/src/ballistica/core/core.cc index c2d1461b..45ee45cb 100644 --- a/src/ballistica/core/core.cc +++ b/src/ballistica/core/core.cc @@ -16,7 +16,8 @@ BaseSoftInterface* g_base_soft{}; auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* { // In monolithic builds we can accept an explicit core-config the first - // time we're imported. + // time we're imported. In this case, Python is not even spun up yet so + // it can influence even that. if (g_buildconfig.monolithic_build()) { if (config != nullptr) { if (g_core != nullptr) { @@ -36,7 +37,9 @@ auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* { } else { // In modular builds we autogenerate a CoreConfig that takes into // account only env-vars (or env-vars plus Python args if we're being - // run via the baenv script). + // run via the baenv script). In this case, Python is already spun up + // and baenv already handled any Python environment stuff so we have + // less to do. if (config != nullptr) { FatalError("CoreConfig can't be explicitly passed in modular builds."); } diff --git a/src/ballistica/core/platform/core_platform.cc b/src/ballistica/core/platform/core_platform.cc index daa92f0d..13fedcba 100644 --- a/src/ballistica/core/platform/core_platform.cc +++ b/src/ballistica/core/platform/core_platform.cc @@ -464,8 +464,6 @@ auto CorePlatform::IsRunningOnDesktop() -> bool { } void CorePlatform::SleepMillisecs(millisecs_t ms) { - // If we're holding the Python GIL, release it while we sleep. - Python::ScopedInterpreterLockRelease release; std::this_thread::sleep_for(std::chrono::milliseconds(ms)); } diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 013cc28c..9ccfb191 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 = 21208; +const int kEngineBuildNumber = 21211; const char* kEngineVersion = "1.7.25"; #if BA_MONOLITHIC_BUILD @@ -116,13 +116,18 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { // In environments where we control the event loop... do that. l_base->RunAppToCompletion(); } else { - // Under managed environments we now simply return and let the - // environment feed us events until the app exits. However, we may - // need to first 'prime the pump' here for our main thread event loop. - // For instance, if our event loop is driven by frame draws, we may - // need to manually pump events until we receive the 'create-screen' - // message from the logic thread which gets our frame draws going. + // If the environment is managing events, we now simply return and let + // it feed us those events. However, we may first need to 'prime the + // pump'. For instance, if the work we do here in the main thread is + // driven by frame draws, we may need to manually pump events until we + // receive a 'create-screen' message from the logic thread which + // gets those frame draws going. l_base->PrimeAppMainThreadEventPump(); + + // IMPORTANT - We're still holding the GIL at this point, so we need + // to permanently release it to avoid starving the app. Any of our + // callback code that needs it will need to acquire it. + Python::PermanentlyReleaseGIL(); } } catch (const std::exception& exc) { std::string error_msg = diff --git a/src/ballistica/shared/foundation/event_loop.cc b/src/ballistica/shared/foundation/event_loop.cc index c5c6f9ef..91dd0f7a 100644 --- a/src/ballistica/shared/foundation/event_loop.cc +++ b/src/ballistica/shared/foundation/event_loop.cc @@ -33,31 +33,31 @@ EventLoop::EventLoop(EventLoopID identifier_in, ThreadSource source) void* (*funcp)(void*); switch (identifier_) { case EventLoopID::kLogic: - func = ThreadMainLogic; - funcp = ThreadMainLogicP; + func = ThreadMainLogic_; + funcp = ThreadMainLogicP_; break; case EventLoopID::kAssets: - func = ThreadMainAssets; - funcp = ThreadMainAssetsP; + func = ThreadMainAssets_; + funcp = ThreadMainAssetsP_; break; case EventLoopID::kMain: // Shouldn't happen; this thread gets wrapped; not launched. throw Exception(); case EventLoopID::kAudio: - func = ThreadMainAudio; - funcp = ThreadMainAudioP; + func = ThreadMainAudio_; + funcp = ThreadMainAudioP_; break; case EventLoopID::kBGDynamics: - func = ThreadMainBGDynamics; - funcp = ThreadMainBGDynamicsP; + func = ThreadMainBGDynamics_; + funcp = ThreadMainBGDynamicsP_; break; case EventLoopID::kNetworkWrite: - func = ThreadMainNetworkWrite; - funcp = ThreadMainNetworkWriteP; + func = ThreadMainNetworkWrite_; + funcp = ThreadMainNetworkWriteP_; break; case EventLoopID::kStdin: - func = ThreadMainStdInput; - funcp = ThreadMainStdInputP; + func = ThreadMainStdInput_; + funcp = ThreadMainStdInputP_; break; default: throw Exception(); @@ -105,7 +105,7 @@ EventLoop::EventLoop(EventLoopID identifier_in, ThreadSource source) thread_id_ = std::this_thread::get_id(); // Set our own thread-id-to-name mapping. - SetInternalThreadName("main"); + SetInternalThreadName_("main"); // Hmmm we might want to set our OS thread name here, // as we do for other threads? (SetCurrentThreadName). @@ -116,7 +116,7 @@ EventLoop::EventLoop(EventLoopID identifier_in, ThreadSource source) } } -void EventLoop::SetInternalThreadName(const std::string& name) { +void EventLoop::SetInternalThreadName_(const std::string& name) { assert(g_core); std::scoped_lock lock(g_core->thread_name_map_mutex); g_core->thread_name_map[std::this_thread::get_id()] = name; @@ -135,57 +135,57 @@ void EventLoop::ClearCurrentThreadName() { // in stack traces which thread is running in case it is not otherwise // evident. -auto EventLoop::ThreadMainLogic(void* data) -> int { - return static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainLogic_(void* data) -> int { + return static_cast(data)->ThreadMain_(); } -auto EventLoop::ThreadMainLogicP(void* data) -> void* { - static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainLogicP_(void* data) -> void* { + static_cast(data)->ThreadMain_(); return nullptr; } -auto EventLoop::ThreadMainAudio(void* data) -> int { - return static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainAudio_(void* data) -> int { + return static_cast(data)->ThreadMain_(); } -auto EventLoop::ThreadMainAudioP(void* data) -> void* { - static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainAudioP_(void* data) -> void* { + static_cast(data)->ThreadMain_(); return nullptr; } -auto EventLoop::ThreadMainBGDynamics(void* data) -> int { - return static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainBGDynamics_(void* data) -> int { + return static_cast(data)->ThreadMain_(); } -auto EventLoop::ThreadMainBGDynamicsP(void* data) -> void* { - static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainBGDynamicsP_(void* data) -> void* { + static_cast(data)->ThreadMain_(); return nullptr; } -auto EventLoop::ThreadMainNetworkWrite(void* data) -> int { - return static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainNetworkWrite_(void* data) -> int { + return static_cast(data)->ThreadMain_(); } -auto EventLoop::ThreadMainNetworkWriteP(void* data) -> void* { - static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainNetworkWriteP_(void* data) -> void* { + static_cast(data)->ThreadMain_(); return nullptr; } -auto EventLoop::ThreadMainStdInput(void* data) -> int { - return static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainStdInput_(void* data) -> int { + return static_cast(data)->ThreadMain_(); } -auto EventLoop::ThreadMainStdInputP(void* data) -> void* { - static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainStdInputP_(void* data) -> void* { + static_cast(data)->ThreadMain_(); return nullptr; } -auto EventLoop::ThreadMainAssets(void* data) -> int { - return static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainAssets_(void* data) -> int { + return static_cast(data)->ThreadMain_(); } -auto EventLoop::ThreadMainAssetsP(void* data) -> void* { - static_cast(data)->ThreadMain(); +auto EventLoop::ThreadMainAssetsP_(void* data) -> void* { + static_cast(data)->ThreadMain_(); return nullptr; } @@ -193,11 +193,11 @@ void EventLoop::PushSetPaused(bool paused) { assert(g_core); // Can be toggled from the main thread only. assert(std::this_thread::get_id() == g_core->main_thread_id); - PushThreadMessage(ThreadMessage(paused ? ThreadMessage::Type::kPause - : ThreadMessage::Type::kResume)); + PushThreadMessage_(ThreadMessage_(paused ? ThreadMessage_::Type::kPause + : ThreadMessage_::Type::kResume)); } -void EventLoop::WaitForNextEvent(bool single_cycle) { +void EventLoop::WaitForNextEvent_(bool single_cycle) { assert(g_core); // If we're running a single cycle we never stop to wait. @@ -225,7 +225,7 @@ void EventLoop::WaitForNextEvent(bool single_cycle) { // While we're waiting, allow other python threads to run. if (acquires_python_gil_) { - ReleaseGIL(); + ReleaseGIL_(); } // If we've got active timers, wait for messages with a timeout so we can @@ -258,11 +258,11 @@ void EventLoop::WaitForNextEvent(bool single_cycle) { } if (acquires_python_gil_) { - AcquireGIL(); + AcquireGIL_(); } } -void EventLoop::LoopUpkeep(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 @@ -281,38 +281,41 @@ void EventLoop::LoopUpkeep(bool single_cycle) { #endif } -auto EventLoop::RunEventLoop(bool single_cycle) -> int { +void EventLoop::RunToCompletion() { Run_(false); } +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); + WaitForNextEvent_(single_cycle); // Process all queued thread messages. - std::list thread_messages; - GetThreadMessages(&thread_messages); + std::list thread_messages; + GetThreadMessages_(&thread_messages); for (auto& thread_message : thread_messages) { switch (thread_message.type) { - case ThreadMessage::Type::kRunnable: { - PushLocalRunnable(thread_message.runnable, - thread_message.completion_flag); + case ThreadMessage_::Type::kRunnable: { + PushLocalRunnable_(thread_message.runnable, + thread_message.completion_flag); break; } - case ThreadMessage::Type::kShutdown: { + case ThreadMessage_::Type::kShutdown: { done_ = true; break; } - case ThreadMessage::Type::kPause: { + case ThreadMessage_::Type::kPause: { assert(!paused_); - RunPauseCallbacks(); + RunPauseCallbacks_(); paused_ = true; last_pause_time_ = g_core->GetAppTimeMillisecs(); messages_since_paused_ = 0; break; } - case ThreadMessage::Type::kResume: { + case ThreadMessage_::Type::kResume: { assert(paused_); - RunResumeCallbacks(); + RunResumeCallbacks_(); paused_ = false; break; } @@ -328,17 +331,16 @@ auto EventLoop::RunEventLoop(bool single_cycle) -> int { if (!paused_) { timers_.Run(g_core->GetAppTimeMillisecs()); - RunPendingRunnables(); + RunPendingRunnables_(); } if (done_ || single_cycle) { break; } } - return 0; } -void EventLoop::GetThreadMessages(std::list* messages) { +void EventLoop::GetThreadMessages_(std::list* messages) { assert(messages); assert(std::this_thread::get_id() == thread_id()); @@ -350,7 +352,7 @@ void EventLoop::GetThreadMessages(std::list* messages) { } } -auto EventLoop::ThreadMain() -> int { +auto EventLoop::ThreadMain_() -> int { assert(g_core); try { assert(source_ == ThreadSource::kCreate); @@ -395,7 +397,7 @@ auto EventLoop::ThreadMain() -> int { throw Exception(); } assert(name && id_string); - SetInternalThreadName(name); + SetInternalThreadName_(name); g_core->platform->SetCurrentThreadName(id_string); // Mark ourself as bootstrapped and signal listeners so @@ -403,19 +405,19 @@ auto EventLoop::ThreadMain() -> int { 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. + // Momentarily grab this lock. This pauses if need be until whoever + // launched us releases their lock, which means they're now actively + // waiting for our notification. If we skipped this, it would be + // possible to zip through and send the notification before they + // start listening for it which would lead to a hang. std::unique_lock lock(client_listener_mutex_); } client_listener_cv_.notify_all(); - // Now just run our loop until we die. - int result = RunEventLoop(); + RunToCompletion(); ClearCurrentThreadName(); - return result; + return 0; } catch (const std::exception& e) { auto error_msg = std::string("Unhandled exception in ") + CurrentThreadName() + " thread:\n" + e.what(); @@ -452,7 +454,7 @@ void EventLoop::SetAcquiresPythonGIL() { assert(!acquires_python_gil_); // This should be called exactly once. assert(ThreadIsCurrent()); acquires_python_gil_ = true; - AcquireGIL(); + AcquireGIL_(); } // Explicitly kill the main thread. @@ -465,7 +467,7 @@ void EventLoop::Quit() { EventLoop::~EventLoop() = default; -void EventLoop::LogThreadMessageTally( +void EventLoop::LogThreadMessageTally_( std::vector>* log_entries) { assert(g_core); // Prevent recursion. @@ -480,23 +482,23 @@ void EventLoop::LogThreadMessageTally( for (auto&& m : thread_messages_) { std::string s; switch (m.type) { - case ThreadMessage::Type::kShutdown: + case ThreadMessage_::Type::kShutdown: s += "kShutdown"; break; - case ThreadMessage::Type::kRunnable: + case ThreadMessage_::Type::kRunnable: s += "kRunnable"; break; - case ThreadMessage::Type::kPause: + case ThreadMessage_::Type::kPause: s += "kPause"; break; - case ThreadMessage::Type::kResume: + case ThreadMessage_::Type::kResume: s += "kResume"; break; default: s += "UNKNOWN(" + std::to_string(static_cast(m.type)) + ")"; break; } - if (m.type == ThreadMessage::Type::kRunnable) { + if (m.type == ThreadMessage_::Type::kRunnable) { std::string m_name = g_core->platform->DemangleCXXSymbol(typeid(*(m.runnable)).name()); s += std::string(": ") + m_name; @@ -518,7 +520,7 @@ void EventLoop::LogThreadMessageTally( } } -void EventLoop::PushThreadMessage(const ThreadMessage& t) { +void EventLoop::PushThreadMessage_(const ThreadMessage_& t) { assert(g_core); // We don't want to make log calls while holding this mutex; // log calls acquire the GIL and if the GIL-holder (generally @@ -562,7 +564,7 @@ void EventLoop::PushThreadMessage(const ThreadMessage& t) { LogLevel::kError, "ThreadMessage list > 1000 in thread: " + CurrentThreadName()); - LogThreadMessageTally(&log_entries); + LogThreadMessageTally_(&log_entries); } } @@ -661,7 +663,7 @@ auto EventLoop::CurrentThreadName() -> std::string { #endif } -void EventLoop::RunPendingRunnables() { +void EventLoop::RunPendingRunnables_() { // Pull all runnables off the list first (its possible for one of these // runnables to add more) and then process them. assert(std::this_thread::get_id() == thread_id()); @@ -691,27 +693,27 @@ void EventLoop::RunPendingRunnables() { } } -void EventLoop::RunPauseCallbacks() { +void EventLoop::RunPauseCallbacks_() { for (Runnable* i : pause_callbacks_) { i->Run(); } } -void EventLoop::RunResumeCallbacks() { +void EventLoop::RunResumeCallbacks_() { for (Runnable* i : resume_callbacks_) { i->Run(); } } -void EventLoop::PushLocalRunnable(Runnable* runnable, bool* completion_flag) { +void EventLoop::PushLocalRunnable_(Runnable* runnable, bool* completion_flag) { assert(std::this_thread::get_id() == thread_id()); runnables_.emplace_back(runnable, completion_flag); } -void EventLoop::PushCrossThreadRunnable(Runnable* runnable, - bool* completion_flag) { - PushThreadMessage(EventLoop::ThreadMessage( - EventLoop::ThreadMessage::Type::kRunnable, runnable, completion_flag)); +void EventLoop::PushCrossThreadRunnable_(Runnable* runnable, + bool* completion_flag) { + PushThreadMessage_(EventLoop::ThreadMessage_( + EventLoop::ThreadMessage_::Type::kRunnable, runnable, completion_flag)); } void EventLoop::AddPauseCallback(Runnable* runnable) { @@ -729,9 +731,9 @@ void EventLoop::PushRunnable(Runnable* runnable) { // If we're being called from withing our thread, just drop it in the list. // otherwise send it as a message to the other thread. if (std::this_thread::get_id() == thread_id()) { - PushLocalRunnable(runnable, nullptr); + PushLocalRunnable_(runnable, nullptr); } else { - PushCrossThreadRunnable(runnable, nullptr); + PushCrossThreadRunnable_(runnable, nullptr); } } @@ -753,7 +755,7 @@ void EventLoop::PushRunnableSynchronous(Runnable* runnable) { "PushRunnableSynchronous called from target thread;" " would deadlock."); } else { - PushCrossThreadRunnable(runnable, &complete); + PushCrossThreadRunnable_(runnable, &complete); } if (identifier_ == EventLoopID::kLogic) { @@ -777,15 +779,15 @@ auto EventLoop::CheckPushSafety() -> bool { // behave the same as the thread-message safety check. return (runnables_.size() < kThreadMessageSafetyThreshold); } else { - return CheckPushRunnableSafety(); + return CheckPushRunnableSafety_(); } } -auto EventLoop::CheckPushRunnableSafety() -> bool { +auto EventLoop::CheckPushRunnableSafety_() -> bool { std::unique_lock lock(thread_message_mutex_); return thread_messages_.size() < kThreadMessageSafetyThreshold; } -void EventLoop::AcquireGIL() { +void EventLoop::AcquireGIL_() { assert(g_base_soft && g_base_soft->InLogicThread()); auto debug_timing{g_core->core_config().debug_timing}; millisecs_t startms{debug_timing ? core::CorePlatform::GetCurrentMillisecs() @@ -805,7 +807,7 @@ void EventLoop::AcquireGIL() { } } -void EventLoop::ReleaseGIL() { +void EventLoop::ReleaseGIL_() { assert(g_base_soft && g_base_soft->InLogicThread()); assert(py_thread_state_ == nullptr); py_thread_state_ = PyEval_SaveThread(); diff --git a/src/ballistica/shared/foundation/event_loop.h b/src/ballistica/shared/foundation/event_loop.h index 0a30544d..a45f5c37 100644 --- a/src/ballistica/shared/foundation/event_loop.h +++ b/src/ballistica/shared/foundation/event_loop.h @@ -19,7 +19,6 @@ namespace ballistica { const int kThreadMessageSafetyThreshold{500}; -// A thread with a built-in event loop. class EventLoop { public: explicit EventLoop(EventLoopID id, @@ -51,7 +50,9 @@ class EventLoop { // rendering contexts are recreated in new threads/etc.) void set_thread_id(std::thread::id id) { thread_id_ = id; } - auto RunEventLoop(bool single_cycle = false) -> int; + void RunToCompletion(); + void RunSingleCycle(); + auto identifier() const -> EventLoopID { return identifier_; } // Register a timer to run on the thread. @@ -101,26 +102,55 @@ class EventLoop { auto paused() { return paused_; } private: - struct ThreadMessage { + struct ThreadMessage_ { enum class Type { kShutdown = 999, kRunnable, kPause, kResume }; Type type; union { Runnable* runnable{}; }; bool* completion_flag{}; - explicit ThreadMessage(Type type_in) : type(type_in) {} - explicit ThreadMessage(Type type, Runnable* runnable, bool* completion_flag) + explicit ThreadMessage_(Type type_in) : type(type_in) {} + explicit ThreadMessage_(Type type, Runnable* runnable, + bool* completion_flag) : type(type), runnable(runnable), completion_flag{completion_flag} {} }; - auto CheckPushRunnableSafety() -> bool; - void SetInternalThreadName(const std::string& name); - void WaitForNextEvent(bool single_cycle); - void LoopUpkeep(bool once); - void LogThreadMessageTally( + auto CheckPushRunnableSafety_() -> bool; + void SetInternalThreadName_(const std::string& name); + void WaitForNextEvent_(bool single_cycle); + void LoopUpkeep_(bool once); + void LogThreadMessageTally_( std::vector>* log_entries); - void PushLocalRunnable(Runnable* runnable, bool* completion_flag); - void PushCrossThreadRunnable(Runnable* runnable, bool* completion_flag); - void NotifyClientListeners(); + void PushLocalRunnable_(Runnable* runnable, bool* completion_flag); + void PushCrossThreadRunnable_(Runnable* runnable, bool* completion_flag); + void NotifyClientListeners_(); + void Run_(bool single_cycle); + + // These are all exactly the same, but running different ones for + // different threads can help identify threads in profilers, backtraces, + // etc. + static auto ThreadMainLogic_(void* data) -> int; + static auto ThreadMainLogicP_(void* data) -> void*; + static auto ThreadMainAudio_(void* data) -> int; + static auto ThreadMainAudioP_(void* data) -> void*; + static auto ThreadMainBGDynamics_(void* data) -> int; + static auto ThreadMainBGDynamicsP_(void* data) -> void*; + static auto ThreadMainNetworkWrite_(void* data) -> int; + static auto ThreadMainNetworkWriteP_(void* data) -> void*; + static auto ThreadMainStdInput_(void* data) -> int; + static auto ThreadMainStdInputP_(void* data) -> void*; + static auto ThreadMainAssets_(void* data) -> int; + static auto ThreadMainAssetsP_(void* data) -> void*; + + auto ThreadMain_() -> int; + void GetThreadMessages_(std::list* messages); + void PushThreadMessage_(const ThreadMessage_& t); + + void RunPendingRunnables_(); + void RunPauseCallbacks_(); + void RunResumeCallbacks_(); + + void AcquireGIL_(); + void ReleaseGIL_(); bool writing_tally_{}; bool paused_{}; @@ -140,40 +170,13 @@ class EventLoop { void* auto_release_pool_{}; #endif - // These are all exactly the same, but running different ones for - // different threads can help identify threads in profilers, backtraces, - // etc. - static auto ThreadMainLogic(void* data) -> int; - static auto ThreadMainLogicP(void* data) -> void*; - static auto ThreadMainAudio(void* data) -> int; - static auto ThreadMainAudioP(void* data) -> void*; - static auto ThreadMainBGDynamics(void* data) -> int; - static auto ThreadMainBGDynamicsP(void* data) -> void*; - static auto ThreadMainNetworkWrite(void* data) -> int; - static auto ThreadMainNetworkWriteP(void* data) -> void*; - static auto ThreadMainStdInput(void* data) -> int; - static auto ThreadMainStdInputP(void* data) -> void*; - static auto ThreadMainAssets(void* data) -> int; - static auto ThreadMainAssetsP(void* data) -> void*; - - auto ThreadMain() -> int; - void GetThreadMessages(std::list* messages); - void PushThreadMessage(const ThreadMessage& t); - - void RunPendingRunnables(); - void RunPauseCallbacks(); - void RunResumeCallbacks(); - - void AcquireGIL(); - void ReleaseGIL(); - bool bootstrapped_{}; std::list> runnables_; std::list pause_callbacks_; std::list resume_callbacks_; std::condition_variable thread_message_cv_; std::mutex thread_message_mutex_; - std::list thread_messages_; + std::list thread_messages_; std::condition_variable client_listener_cv_; std::mutex client_listener_mutex_; std::list> data_to_client_; diff --git a/src/ballistica/shared/python/python.cc b/src/ballistica/shared/python/python.cc index c1f30067..58a24262 100644 --- a/src/ballistica/shared/python/python.cc +++ b/src/ballistica/shared/python/python.cc @@ -77,6 +77,11 @@ const char* Python::ScopedCallLabel::current_label_ = nullptr; auto Python::HaveGIL() -> bool { return static_cast(PyGILState_Check()); } +void Python::PermanentlyReleaseGIL() { + assert(HaveGIL()); + PyEval_SaveThread(); +} + void Python::PrintStackTrace() { bool available{}; if (g_base_soft) { diff --git a/src/ballistica/shared/python/python.h b/src/ballistica/shared/python/python.h index de3c04a9..f8968209 100644 --- a/src/ballistica/shared/python/python.h +++ b/src/ballistica/shared/python/python.h @@ -76,7 +76,11 @@ class Python { /// sanity checking that. static auto HaveGIL() -> bool; - /// Attempt to print the python stack trace. + /// For use in specific cases when a thread exits our control. In most + /// cases Scoped Locks/Unlocks should be used. + static void PermanentlyReleaseGIL(); + + /// Attempt to print the Python stack trace. static void PrintStackTrace(); /// Pass any PyObject* (including nullptr) to get a readable string diff --git a/tools/efrotools/toolconfig.py b/tools/efrotools/toolconfig.py index bf9bd5db..deb43613 100644 --- a/tools/efrotools/toolconfig.py +++ b/tools/efrotools/toolconfig.py @@ -111,8 +111,9 @@ def _filter_tool_config(projroot: Path, cfg: str) -> str: ' :jedi (:extra_paths' ' [__EFRO_PYTHON_PATHS_Q_REL_STR__])\n' ' :pylsp_mypy (:enabled t\n' - ' :live_mode nil\n' - ' :dmypy t))))))))\n', + ' :live_mode :json-false\n' + ' :report_progress t\n' + ' :dmypy :json-false))))))))\n', ) # Stick project-root wherever they want. diff --git a/tools/pcommand b/tools/pcommand index ca90021e..afc42b70 100755 --- a/tools/pcommand +++ b/tools/pcommand @@ -15,7 +15,6 @@ from __future__ import annotations from typing import TYPE_CHECKING - # Pull in commands we want to expose. Its more efficient to define them in # modules rather than inline here because we'll be able to load them via pyc. # pylint: disable=unused-import