This commit is contained in:
Eric 2023-08-03 12:23:22 -07:00
parent 40b603a17f
commit 308241402d
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
16 changed files with 308 additions and 230 deletions

62
.efrocachemap generated
View File

@ -421,10 +421,10 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55", "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/arabic.json": "db961f7fe0541a31880929e1c17ea957",
"build/assets/ba_data/data/languages/belarussian.json": "5e373ddcfa6e1f771b74c02298a6599a", "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/chinesetraditional.json": "3fe960a8f0ca529aa57b4f9cb7385abc",
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa", "build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
"build/assets/ba_data/data/languages/czech.json": "f3ce219840946cb8f9aa6d3e25927ab3", "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/russian.json": "aa99f9f597787fe4e09c8ab53fe2e081",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef", "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/swedish.json": "77d671f10613291ebf9c71da66f18a18",
"build/assets/ba_data/data/languages/tamil.json": "b9d4b4e107456ea6420ee0f9d9d7a03e", "build/assets/ba_data/data/languages/tamil.json": "b9d4b4e107456ea6420ee0f9d9d7a03e",
"build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc", "build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc",
@ -4068,26 +4068,26 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "884277e559d840df55f7e0dd1c186949", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "5c33841bddb06db422bc7b610d54e850",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "38240f93c1ae9065cf671be2e7620684", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "7c68be74dc87cc8bea603351d4edc19e",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "0480c97db92676754cf9552ee462dccb", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "4aee26e9c9399db7036d63c05b415146",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "052b4f4804e30da165744893e84d4a64", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "0c863960576179584121bfa00b0c0176",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "32d4c7a4bdac7a8cb12d4b42868a5979", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "5c7ce41451ea6fd44f3106ad4d333def",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "42649b501212bea15f4b2f2741583339", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "189837b829a2ea704fad9fb163e50490",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "03cbedec6c8f294d7fe57a6dc64803e8", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "ddc816b66933c7e5b811c9c7c2dd16b8",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "37b2c536cd841b40319b0efae40471ce", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "98e206e6e92514f7be59096a135bed3c",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1f67054d55541b13ca912b4d9b57f499", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f29ba9eeddf9a55febff06fca7db52a9",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "5399d3f4924909ca1c0878f347e20a10", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "c63f95f2f7e813351dfe18f51e6deeb4",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "18c01fc3b664bb30bd2e5b6ad635b5f3", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c303ae3176d11b2b134068878e786833",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "58559f7512a300586b8141591a21564e", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "123098bb0bb01419fa2acdad01be34a0",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "cec05c9cf3882d00ba27b81696d95d1e", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "0d416da46426a9969ed3b7a91f5497b5",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "8e98e2bb1d7d9fa34f873fd2b7d82138", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "9e924485487aaabbc137c02f895247f2",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "c215407232dbf0166d9980c1dae780b6", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "61af0496d9b62e671fc55a38df776d99",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "49107effdf5c1ee5a0533251a99f9f6a", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "62d2eaf952658ef8fe0d9910c9f8d964",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "8731458bc07c5a9ecea1c2ac46b5b96c", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "6e35a1c8093de8886134d2f1dab4a56a",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "e51c409b2f1ca6433f96e3b10615a914", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "803800bebf963742fd26cae8e793245b",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "b3d961223dc84f6805613790d1e2c267", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "6bb44a0e36fa87f384aa4a29f56c9dcc",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "1cadf9af171902a4984658c624a42142", "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/debug/libballisticaplus.a": "85ba4e81a1f7ae2cff4b1355eb49904f",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "498921f7eb2afd327d4b900cb70e31f9", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "498921f7eb2afd327d4b900cb70e31f9",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "85ba4e81a1f7ae2cff4b1355eb49904f", "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_gui/release/libballisticaplus.a": "0ab638b6602610bdaf432e3cc2464080",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "92394eb19387c363471ce134ac9e6a1b", "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/mac_x86_64_server/release/libballisticaplus.a": "0ab638b6602610bdaf432e3cc2464080",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "aa2d3f27999da77b4ad584df0707d8c0", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "ae1fcb25dead5a8b306d1a3c8d02887e",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "2d134738e7ea8d7751ebae3df834806e", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "56b419764d2ea9811fbca8bef99d032e",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "451c1d7295846fcad90b649fc574ee0e", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "787b4c6337d367c2ad0cdf1cdc980b5e",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "3e910b8da4a13dbcdde7955bbde17abb", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "7d4b36b9cc305832430210d7db55f13a",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "987b68e1e4b4e89bd73d5014735318d6", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ae9083ecbbcf81f54ab3aafbf5a58273",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "520f2b3e9c7ae15954589ce148dc1506", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "1220228d0a0036c898483ef73b41056c",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "d02da070911bac71d7dbb4afc7a93e6a", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "14d5968a2cb52b1eb9c33704f90a3e4d",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "629d285dfe041d4237e14d140ac70482", "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/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318", "src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "eeddad968b176000e31c65be6206a2bc", "src/ballistica/base/mgen/pyembed/binding_base.inc": "eeddad968b176000e31c65be6206a2bc",

View File

@ -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 - `getconfig` and `setconfig` in `efrotools` are now `getprojectconfig` and
`setprojectconfig` (to reflect the file name changes that happened in 1.7.20). `setprojectconfig` (to reflect the file name changes that happened in 1.7.20).
- The efrocache system (how assets and prebuilt binaries are downloaded during - The efrocache system (how assets and prebuilt binaries are downloaded during

View File

@ -1167,7 +1167,7 @@ CHECK_CLEAN_SAFETY = tools/pcommand check_clean_safety
TOOL_CFG_INST = tools/pcommand tool_config_install TOOL_CFG_INST = tools/pcommand tool_config_install
# Anything that affects tool-config generation. # 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. # Anything that should trigger an environment-check when changed.
ENV_SRC = tools/pcommand tools/batools/build.py ENV_SRC = tools/pcommand tools/batools/build.py

View File

@ -2,9 +2,14 @@
( (
;; Specify some extra paths that project.el searches and whatnot should ignore. ;; Stuff that applies everywhere.
;; Note that gitignored stuff is ignored implicitly. (nil . (
(nil . ((project-vc-ignores . ("docs" ;; 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" "submodules"
"src/external" "src/external"
"src/assets/ba_data/python-site-packages" "src/assets/ba_data/python-site-packages"

View File

@ -24,8 +24,9 @@ from dataclasses import dataclass
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import __main__ import __main__
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any
from efro.log import LogHandler from efro.log import LogHandler
# IMPORTANT - It is likely (and in some cases expected) that this # 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 # Build number and version of the ballistica binary we expect to be
# using. # using.
TARGET_BALLISTICA_BUILD = 21208 TARGET_BALLISTICA_BUILD = 21211
TARGET_BALLISTICA_VERSION = '1.7.25' TARGET_BALLISTICA_VERSION = '1.7.25'
@dataclass @dataclass
class EnvConfig: 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 config_dir: str
# Directory containing ba_data and any other platform-specific data.
data_dir: str data_dir: str
user_python_dir: str | None
# Where the app's built-in Python stuff lives.
app_python_dir: str | None app_python_dir: str | None
# Where the app's built-in Python stuff lives in the default case.
standard_app_python_dir: str standard_app_python_dir: str
# Where the app's bundled third party Python stuff lives.
site_python_dir: str | None 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 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 @dataclass
class _EnvGlobals: 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 config: EnvConfig | None = None
called_configure: bool = False called_configure: bool = False
@ -90,7 +123,7 @@ class _EnvGlobals:
def did_paths_set_fail() -> bool: 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 return _EnvGlobals.get().paths_set_failed
@ -104,8 +137,14 @@ def get_config() -> EnvConfig:
"""Return the active config, creating a default if none exists.""" """Return the active config, creating a default if none exists."""
envglobals = _EnvGlobals.get() 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: if not envglobals.called_configure:
configure() configure(setup_logging=False)
config = envglobals.config config = envglobals.config
if config is None: if config is None:
@ -123,17 +162,20 @@ def configure(
app_python_dir: str | None = None, app_python_dir: str | None = None,
site_python_dir: str | None = None, site_python_dir: str | None = None,
contains_python_dist: bool = False, contains_python_dist: bool = False,
setup_logging: bool = True,
) -> None: ) -> 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 This includes things such as Python path wrangling and app directory
creation. This should be called before any other ballistica modules creation. This must be called before any actual Ballistica modules
are imported since it may make changes to sys.path which can affect are imported; the environment is locked in as soon as that happens.
where those modules get loaded from.
""" """
envglobals = _EnvGlobals.get() 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: if envglobals.called_configure:
raise RuntimeError( raise RuntimeError(
'baenv.configure() has already been called;' 'baenv.configure() has already been called;'
@ -141,21 +183,12 @@ def configure(
) )
envglobals.called_configure = True envglobals.called_configure = True
# The very first thing we do is set up our logging system and pipe # The very first thing we do is setup Python paths (while also
# Python's stdout/stderr into it. Then we can at least debug # calculating some engine paths). This code needs to be bulletproof
# problems on systems where native stdout/stderr is not easily # since we have no logging yet at this point. We used to set up
# accessible such as Android. # logging first, but this way logging stuff will get loaded from its
log_handler = _setup_logging() # proper final path (otherwise we might wind up using two different
# versions of efro.logging in a single engine run).
# 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.
( (
user_python_dir, user_python_dir,
app_python_dir, app_python_dir,
@ -172,6 +205,19 @@ def configure(
config_dir, 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. # Attempt to create dirs that we'll write stuff to.
_setup_dirs(config_dir, user_python_dir) _setup_dirs(config_dir, user_python_dir)
@ -188,6 +234,7 @@ def configure(
site_python_dir=site_python_dir, site_python_dir=site_python_dir,
log_handler=log_handler, log_handler=log_handler,
is_user_app_python_dir=is_user_app_python_dir, 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') assert Path(__file__).parts[-3:-1] == ('ba_data', 'python')
data_dir_path = Path(__file__).parents[2] data_dir_path = Path(__file__).parents[2]
# Prefer tidy relative paths like '.' if possible so that things # Prefer tidy relative paths like './ba_data' if possible so
# like stack traces are easier to read. # that things like stack traces are easier to read. For best
# results, platforms where CWD doesn't matter can chdir to where
# NOTE: Perhaps we should have an option to disable this # ba_data lives before calling configure().
# behavior for cases where the user might be doing chdir stuff. #
# 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() cwd_path = Path.cwd()
data_dir = str( data_dir = str(
data_dir_path.relative_to(cwd_path) data_dir_path.relative_to(cwd_path)
@ -226,7 +275,7 @@ def _setup_logging() -> LogHandler:
def _setup_certs(contains_python_dist: bool) -> None: 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 # provide our own root certs so ssl works. We can consider
# overriding this in particular embedded cases if we can verify that # overriding this in particular embedded cases if we can verify that
# system certs are working. We also allow forcing this via an env # system certs are working. We also allow forcing this via an env
@ -279,7 +328,7 @@ def _setup_paths(
envglobals.paths_set_failed = True envglobals.paths_set_failed = True
else: 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. # Python paths.
if app_python_dir is None: if app_python_dir is None:

View File

@ -87,7 +87,7 @@ void App::RunRenderUpkeepCycle() {
// Pump thread messages (we're being driven by frame-draw callbacks // Pump thread messages (we're being driven by frame-draw callbacks
// so this is the only place that it gets done at). // 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. // Now do the general app event cycle for whoever needs to process things.
RunEvents(); RunEvents();
@ -301,10 +301,14 @@ void App::DidFinishRenderingFrame(FrameDef* frame) {}
void App::PrimeMainThreadEventPump() { void App::PrimeMainThreadEventPump() {
assert(!ManagesEventLoop()); 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. // Pump events manually until a screen gets created.
// At that point we use frame-draws to drive our event loop. // At that point we use frame-draws to drive our event loop.
while (!g_base->graphics_server->initial_screen_created()) { while (!g_base->graphics_server->initial_screen_created()) {
event_loop()->RunEventLoop(true); event_loop()->RunSingleCycle();
core::CorePlatform::SleepMillisecs(1); core::CorePlatform::SleepMillisecs(1);
} }
} }

View File

@ -84,8 +84,7 @@ void BaseFeatureSet::OnModuleExec(PyObject* module) {
g_core->LifecycleLog("_babase exec begin"); g_core->LifecycleLog("_babase exec begin");
// Want to run this at the last possible moment before spinning up our // This locks in a baenv configuration.
// BaseFeatureSet. This locks in baenv customizations.
g_core->ApplyBaEnvConfig(); g_core->ApplyBaEnvConfig();
// Create our feature-set's C++ front-end. // Create our feature-set's C++ front-end.
@ -280,18 +279,14 @@ void BaseFeatureSet::RunAppToCompletion() {
BA_PRECONDITION(!called_run_app_to_completion_); BA_PRECONDITION(!called_run_app_to_completion_);
called_run_app_to_completion_ = true; called_run_app_to_completion_ = true;
// Start things moving if not done yet.
if (!called_start_app_) { if (!called_start_app_) {
StartApp(); StartApp();
} }
// Let go of the GIL while we're running. The logic thread or other things // Let go of the GIL while we're running.
// will grab it when needed.
Python::ScopedInterpreterLockRelease gil_release; Python::ScopedInterpreterLockRelease gil_release;
// On our event-loop-managing platforms we now simply sit in our event g_core->main_event_loop()->RunToCompletion();
// loop until the app is quit.
g_core->main_event_loop()->RunEventLoop(false);
} }
void BaseFeatureSet::PrimeAppMainThreadEventPump() { void BaseFeatureSet::PrimeAppMainThreadEventPump() {

View File

@ -16,7 +16,8 @@ BaseSoftInterface* g_base_soft{};
auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* { auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* {
// In monolithic builds we can accept an explicit core-config the first // 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 (g_buildconfig.monolithic_build()) {
if (config != nullptr) { if (config != nullptr) {
if (g_core != nullptr) { if (g_core != nullptr) {
@ -36,7 +37,9 @@ auto CoreFeatureSet::Import(const CoreConfig* config) -> CoreFeatureSet* {
} else { } else {
// In modular builds we autogenerate a CoreConfig that takes into // In modular builds we autogenerate a CoreConfig that takes into
// account only env-vars (or env-vars plus Python args if we're being // 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) { if (config != nullptr) {
FatalError("CoreConfig can't be explicitly passed in modular builds."); FatalError("CoreConfig can't be explicitly passed in modular builds.");
} }

View File

@ -464,8 +464,6 @@ auto CorePlatform::IsRunningOnDesktop() -> bool {
} }
void CorePlatform::SleepMillisecs(millisecs_t ms) { 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)); std::this_thread::sleep_for(std::chrono::milliseconds(ms));
} }

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // 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"; const char* kEngineVersion = "1.7.25";
#if BA_MONOLITHIC_BUILD #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. // In environments where we control the event loop... do that.
l_base->RunAppToCompletion(); l_base->RunAppToCompletion();
} else { } else {
// Under managed environments we now simply return and let the // If the environment is managing events, we now simply return and let
// environment feed us events until the app exits. However, we may // it feed us those events. However, we may first need to 'prime the
// need to first 'prime the pump' here for our main thread event loop. // pump'. For instance, if the work we do here in the main thread is
// For instance, if our event loop is driven by frame draws, we may // driven by frame draws, we may need to manually pump events until we
// need to manually pump events until we receive the 'create-screen' // receive a 'create-screen' message from the logic thread which
// message from the logic thread which gets our frame draws going. // gets those frame draws going.
l_base->PrimeAppMainThreadEventPump(); 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) { } catch (const std::exception& exc) {
std::string error_msg = std::string error_msg =

View File

@ -33,31 +33,31 @@ EventLoop::EventLoop(EventLoopID identifier_in, ThreadSource source)
void* (*funcp)(void*); void* (*funcp)(void*);
switch (identifier_) { switch (identifier_) {
case EventLoopID::kLogic: case EventLoopID::kLogic:
func = ThreadMainLogic; func = ThreadMainLogic_;
funcp = ThreadMainLogicP; funcp = ThreadMainLogicP_;
break; break;
case EventLoopID::kAssets: case EventLoopID::kAssets:
func = ThreadMainAssets; func = ThreadMainAssets_;
funcp = ThreadMainAssetsP; funcp = ThreadMainAssetsP_;
break; break;
case EventLoopID::kMain: case EventLoopID::kMain:
// Shouldn't happen; this thread gets wrapped; not launched. // Shouldn't happen; this thread gets wrapped; not launched.
throw Exception(); throw Exception();
case EventLoopID::kAudio: case EventLoopID::kAudio:
func = ThreadMainAudio; func = ThreadMainAudio_;
funcp = ThreadMainAudioP; funcp = ThreadMainAudioP_;
break; break;
case EventLoopID::kBGDynamics: case EventLoopID::kBGDynamics:
func = ThreadMainBGDynamics; func = ThreadMainBGDynamics_;
funcp = ThreadMainBGDynamicsP; funcp = ThreadMainBGDynamicsP_;
break; break;
case EventLoopID::kNetworkWrite: case EventLoopID::kNetworkWrite:
func = ThreadMainNetworkWrite; func = ThreadMainNetworkWrite_;
funcp = ThreadMainNetworkWriteP; funcp = ThreadMainNetworkWriteP_;
break; break;
case EventLoopID::kStdin: case EventLoopID::kStdin:
func = ThreadMainStdInput; func = ThreadMainStdInput_;
funcp = ThreadMainStdInputP; funcp = ThreadMainStdInputP_;
break; break;
default: default:
throw Exception(); throw Exception();
@ -105,7 +105,7 @@ EventLoop::EventLoop(EventLoopID identifier_in, ThreadSource source)
thread_id_ = std::this_thread::get_id(); thread_id_ = std::this_thread::get_id();
// Set our own thread-id-to-name mapping. // Set our own thread-id-to-name mapping.
SetInternalThreadName("main"); SetInternalThreadName_("main");
// Hmmm we might want to set our OS thread name here, // Hmmm we might want to set our OS thread name here,
// as we do for other threads? (SetCurrentThreadName). // 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); assert(g_core);
std::scoped_lock lock(g_core->thread_name_map_mutex); std::scoped_lock lock(g_core->thread_name_map_mutex);
g_core->thread_name_map[std::this_thread::get_id()] = name; 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 // in stack traces which thread is running in case it is not otherwise
// evident. // evident.
auto EventLoop::ThreadMainLogic(void* data) -> int { auto EventLoop::ThreadMainLogic_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain(); return static_cast<EventLoop*>(data)->ThreadMain_();
} }
auto EventLoop::ThreadMainLogicP(void* data) -> void* { auto EventLoop::ThreadMainLogicP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain(); static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr; return nullptr;
} }
auto EventLoop::ThreadMainAudio(void* data) -> int { auto EventLoop::ThreadMainAudio_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain(); return static_cast<EventLoop*>(data)->ThreadMain_();
} }
auto EventLoop::ThreadMainAudioP(void* data) -> void* { auto EventLoop::ThreadMainAudioP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain(); static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr; return nullptr;
} }
auto EventLoop::ThreadMainBGDynamics(void* data) -> int { auto EventLoop::ThreadMainBGDynamics_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain(); return static_cast<EventLoop*>(data)->ThreadMain_();
} }
auto EventLoop::ThreadMainBGDynamicsP(void* data) -> void* { auto EventLoop::ThreadMainBGDynamicsP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain(); static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr; return nullptr;
} }
auto EventLoop::ThreadMainNetworkWrite(void* data) -> int { auto EventLoop::ThreadMainNetworkWrite_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain(); return static_cast<EventLoop*>(data)->ThreadMain_();
} }
auto EventLoop::ThreadMainNetworkWriteP(void* data) -> void* { auto EventLoop::ThreadMainNetworkWriteP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain(); static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr; return nullptr;
} }
auto EventLoop::ThreadMainStdInput(void* data) -> int { auto EventLoop::ThreadMainStdInput_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain(); return static_cast<EventLoop*>(data)->ThreadMain_();
} }
auto EventLoop::ThreadMainStdInputP(void* data) -> void* { auto EventLoop::ThreadMainStdInputP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain(); static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr; return nullptr;
} }
auto EventLoop::ThreadMainAssets(void* data) -> int { auto EventLoop::ThreadMainAssets_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain(); return static_cast<EventLoop*>(data)->ThreadMain_();
} }
auto EventLoop::ThreadMainAssetsP(void* data) -> void* { auto EventLoop::ThreadMainAssetsP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain(); static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr; return nullptr;
} }
@ -193,11 +193,11 @@ void EventLoop::PushSetPaused(bool paused) {
assert(g_core); assert(g_core);
// Can be toggled from the main thread only. // Can be toggled from the main thread only.
assert(std::this_thread::get_id() == g_core->main_thread_id); assert(std::this_thread::get_id() == g_core->main_thread_id);
PushThreadMessage(ThreadMessage(paused ? ThreadMessage::Type::kPause PushThreadMessage_(ThreadMessage_(paused ? ThreadMessage_::Type::kPause
: ThreadMessage::Type::kResume)); : ThreadMessage_::Type::kResume));
} }
void EventLoop::WaitForNextEvent(bool single_cycle) { void EventLoop::WaitForNextEvent_(bool single_cycle) {
assert(g_core); assert(g_core);
// If we're running a single cycle we never stop to wait. // 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. // While we're waiting, allow other python threads to run.
if (acquires_python_gil_) { if (acquires_python_gil_) {
ReleaseGIL(); ReleaseGIL_();
} }
// If we've got active timers, wait for messages with a timeout so we can // 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_) { if (acquires_python_gil_) {
AcquireGIL(); AcquireGIL_();
} }
} }
void EventLoop::LoopUpkeep(bool single_cycle) { void EventLoop::LoopUpkeep_(bool single_cycle) {
assert(g_core); assert(g_core);
// Keep our autorelease pool clean on mac/ios // Keep our autorelease pool clean on mac/ios
// FIXME: Should define a CorePlatform::ThreadHelper or something // FIXME: Should define a CorePlatform::ThreadHelper or something
@ -281,38 +281,41 @@ void EventLoop::LoopUpkeep(bool single_cycle) {
#endif #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); assert(g_core);
while (true) { while (true) {
LoopUpkeep(single_cycle); LoopUpkeep_(single_cycle);
WaitForNextEvent(single_cycle); WaitForNextEvent_(single_cycle);
// Process all queued thread messages. // Process all queued thread messages.
std::list<ThreadMessage> thread_messages; std::list<ThreadMessage_> thread_messages;
GetThreadMessages(&thread_messages); GetThreadMessages_(&thread_messages);
for (auto& thread_message : thread_messages) { for (auto& thread_message : thread_messages) {
switch (thread_message.type) { switch (thread_message.type) {
case ThreadMessage::Type::kRunnable: { case ThreadMessage_::Type::kRunnable: {
PushLocalRunnable(thread_message.runnable, PushLocalRunnable_(thread_message.runnable,
thread_message.completion_flag); thread_message.completion_flag);
break; break;
} }
case ThreadMessage::Type::kShutdown: { case ThreadMessage_::Type::kShutdown: {
done_ = true; done_ = true;
break; break;
} }
case ThreadMessage::Type::kPause: { case ThreadMessage_::Type::kPause: {
assert(!paused_); assert(!paused_);
RunPauseCallbacks(); RunPauseCallbacks_();
paused_ = true; paused_ = true;
last_pause_time_ = g_core->GetAppTimeMillisecs(); last_pause_time_ = g_core->GetAppTimeMillisecs();
messages_since_paused_ = 0; messages_since_paused_ = 0;
break; break;
} }
case ThreadMessage::Type::kResume: { case ThreadMessage_::Type::kResume: {
assert(paused_); assert(paused_);
RunResumeCallbacks(); RunResumeCallbacks_();
paused_ = false; paused_ = false;
break; break;
} }
@ -328,17 +331,16 @@ auto EventLoop::RunEventLoop(bool single_cycle) -> int {
if (!paused_) { if (!paused_) {
timers_.Run(g_core->GetAppTimeMillisecs()); timers_.Run(g_core->GetAppTimeMillisecs());
RunPendingRunnables(); RunPendingRunnables_();
} }
if (done_ || single_cycle) { if (done_ || single_cycle) {
break; break;
} }
} }
return 0;
} }
void EventLoop::GetThreadMessages(std::list<ThreadMessage>* messages) { void EventLoop::GetThreadMessages_(std::list<ThreadMessage_>* messages) {
assert(messages); assert(messages);
assert(std::this_thread::get_id() == thread_id()); assert(std::this_thread::get_id() == thread_id());
@ -350,7 +352,7 @@ void EventLoop::GetThreadMessages(std::list<ThreadMessage>* messages) {
} }
} }
auto EventLoop::ThreadMain() -> int { auto EventLoop::ThreadMain_() -> int {
assert(g_core); assert(g_core);
try { try {
assert(source_ == ThreadSource::kCreate); assert(source_ == ThreadSource::kCreate);
@ -395,7 +397,7 @@ auto EventLoop::ThreadMain() -> int {
throw Exception(); throw Exception();
} }
assert(name && id_string); assert(name && id_string);
SetInternalThreadName(name); SetInternalThreadName_(name);
g_core->platform->SetCurrentThreadName(id_string); g_core->platform->SetCurrentThreadName(id_string);
// Mark ourself as bootstrapped and signal listeners so // Mark ourself as bootstrapped and signal listeners so
@ -403,19 +405,19 @@ auto EventLoop::ThreadMain() -> int {
bootstrapped_ = true; bootstrapped_ = true;
{ {
// Momentarily grab this lock. This ensures that whoever launched us // Momentarily grab this lock. This pauses if need be until whoever
// is now actively waiting for this notification. If we skipped this // launched us releases their lock, which means they're now actively
// it would be possible to notify before they start listening which // waiting for our notification. If we skipped this, it would be
// leads to a hang. // 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_); std::unique_lock lock(client_listener_mutex_);
} }
client_listener_cv_.notify_all(); client_listener_cv_.notify_all();
// Now just run our loop until we die. RunToCompletion();
int result = RunEventLoop();
ClearCurrentThreadName(); ClearCurrentThreadName();
return result; return 0;
} catch (const std::exception& e) { } catch (const std::exception& e) {
auto error_msg = std::string("Unhandled exception in ") auto error_msg = std::string("Unhandled exception in ")
+ CurrentThreadName() + " thread:\n" + e.what(); + CurrentThreadName() + " thread:\n" + e.what();
@ -452,7 +454,7 @@ void EventLoop::SetAcquiresPythonGIL() {
assert(!acquires_python_gil_); // This should be called exactly once. assert(!acquires_python_gil_); // This should be called exactly once.
assert(ThreadIsCurrent()); assert(ThreadIsCurrent());
acquires_python_gil_ = true; acquires_python_gil_ = true;
AcquireGIL(); AcquireGIL_();
} }
// Explicitly kill the main thread. // Explicitly kill the main thread.
@ -465,7 +467,7 @@ void EventLoop::Quit() {
EventLoop::~EventLoop() = default; EventLoop::~EventLoop() = default;
void EventLoop::LogThreadMessageTally( void EventLoop::LogThreadMessageTally_(
std::vector<std::pair<LogLevel, std::string>>* log_entries) { std::vector<std::pair<LogLevel, std::string>>* log_entries) {
assert(g_core); assert(g_core);
// Prevent recursion. // Prevent recursion.
@ -480,23 +482,23 @@ void EventLoop::LogThreadMessageTally(
for (auto&& m : thread_messages_) { for (auto&& m : thread_messages_) {
std::string s; std::string s;
switch (m.type) { switch (m.type) {
case ThreadMessage::Type::kShutdown: case ThreadMessage_::Type::kShutdown:
s += "kShutdown"; s += "kShutdown";
break; break;
case ThreadMessage::Type::kRunnable: case ThreadMessage_::Type::kRunnable:
s += "kRunnable"; s += "kRunnable";
break; break;
case ThreadMessage::Type::kPause: case ThreadMessage_::Type::kPause:
s += "kPause"; s += "kPause";
break; break;
case ThreadMessage::Type::kResume: case ThreadMessage_::Type::kResume:
s += "kResume"; s += "kResume";
break; break;
default: default:
s += "UNKNOWN(" + std::to_string(static_cast<int>(m.type)) + ")"; s += "UNKNOWN(" + std::to_string(static_cast<int>(m.type)) + ")";
break; break;
} }
if (m.type == ThreadMessage::Type::kRunnable) { if (m.type == ThreadMessage_::Type::kRunnable) {
std::string m_name = std::string m_name =
g_core->platform->DemangleCXXSymbol(typeid(*(m.runnable)).name()); g_core->platform->DemangleCXXSymbol(typeid(*(m.runnable)).name());
s += std::string(": ") + m_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); assert(g_core);
// We don't want to make log calls while holding this mutex; // We don't want to make log calls while holding this mutex;
// log calls acquire the GIL and if the GIL-holder (generally // log calls acquire the GIL and if the GIL-holder (generally
@ -562,7 +564,7 @@ void EventLoop::PushThreadMessage(const ThreadMessage& t) {
LogLevel::kError, LogLevel::kError,
"ThreadMessage list > 1000 in thread: " + CurrentThreadName()); "ThreadMessage list > 1000 in thread: " + CurrentThreadName());
LogThreadMessageTally(&log_entries); LogThreadMessageTally_(&log_entries);
} }
} }
@ -661,7 +663,7 @@ auto EventLoop::CurrentThreadName() -> std::string {
#endif #endif
} }
void EventLoop::RunPendingRunnables() { void EventLoop::RunPendingRunnables_() {
// Pull all runnables off the list first (its possible for one of these // Pull all runnables off the list first (its possible for one of these
// runnables to add more) and then process them. // runnables to add more) and then process them.
assert(std::this_thread::get_id() == thread_id()); 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_) { for (Runnable* i : pause_callbacks_) {
i->Run(); i->Run();
} }
} }
void EventLoop::RunResumeCallbacks() { void EventLoop::RunResumeCallbacks_() {
for (Runnable* i : resume_callbacks_) { for (Runnable* i : resume_callbacks_) {
i->Run(); 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()); assert(std::this_thread::get_id() == thread_id());
runnables_.emplace_back(runnable, completion_flag); runnables_.emplace_back(runnable, completion_flag);
} }
void EventLoop::PushCrossThreadRunnable(Runnable* runnable, void EventLoop::PushCrossThreadRunnable_(Runnable* runnable,
bool* completion_flag) { bool* completion_flag) {
PushThreadMessage(EventLoop::ThreadMessage( PushThreadMessage_(EventLoop::ThreadMessage_(
EventLoop::ThreadMessage::Type::kRunnable, runnable, completion_flag)); EventLoop::ThreadMessage_::Type::kRunnable, runnable, completion_flag));
} }
void EventLoop::AddPauseCallback(Runnable* runnable) { 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. // 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. // otherwise send it as a message to the other thread.
if (std::this_thread::get_id() == thread_id()) { if (std::this_thread::get_id() == thread_id()) {
PushLocalRunnable(runnable, nullptr); PushLocalRunnable_(runnable, nullptr);
} else { } else {
PushCrossThreadRunnable(runnable, nullptr); PushCrossThreadRunnable_(runnable, nullptr);
} }
} }
@ -753,7 +755,7 @@ void EventLoop::PushRunnableSynchronous(Runnable* runnable) {
"PushRunnableSynchronous called from target thread;" "PushRunnableSynchronous called from target thread;"
" would deadlock."); " would deadlock.");
} else { } else {
PushCrossThreadRunnable(runnable, &complete); PushCrossThreadRunnable_(runnable, &complete);
} }
if (identifier_ == EventLoopID::kLogic) { if (identifier_ == EventLoopID::kLogic) {
@ -777,15 +779,15 @@ auto EventLoop::CheckPushSafety() -> bool {
// behave the same as the thread-message safety check. // behave the same as the thread-message safety check.
return (runnables_.size() < kThreadMessageSafetyThreshold); return (runnables_.size() < kThreadMessageSafetyThreshold);
} else { } else {
return CheckPushRunnableSafety(); return CheckPushRunnableSafety_();
} }
} }
auto EventLoop::CheckPushRunnableSafety() -> bool { auto EventLoop::CheckPushRunnableSafety_() -> bool {
std::unique_lock lock(thread_message_mutex_); std::unique_lock lock(thread_message_mutex_);
return thread_messages_.size() < kThreadMessageSafetyThreshold; return thread_messages_.size() < kThreadMessageSafetyThreshold;
} }
void EventLoop::AcquireGIL() { void EventLoop::AcquireGIL_() {
assert(g_base_soft && g_base_soft->InLogicThread()); assert(g_base_soft && g_base_soft->InLogicThread());
auto debug_timing{g_core->core_config().debug_timing}; auto debug_timing{g_core->core_config().debug_timing};
millisecs_t startms{debug_timing ? core::CorePlatform::GetCurrentMillisecs() 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(g_base_soft && g_base_soft->InLogicThread());
assert(py_thread_state_ == nullptr); assert(py_thread_state_ == nullptr);
py_thread_state_ = PyEval_SaveThread(); py_thread_state_ = PyEval_SaveThread();

View File

@ -19,7 +19,6 @@ namespace ballistica {
const int kThreadMessageSafetyThreshold{500}; const int kThreadMessageSafetyThreshold{500};
// A thread with a built-in event loop.
class EventLoop { class EventLoop {
public: public:
explicit EventLoop(EventLoopID id, explicit EventLoop(EventLoopID id,
@ -51,7 +50,9 @@ class EventLoop {
// rendering contexts are recreated in new threads/etc.) // rendering contexts are recreated in new threads/etc.)
void set_thread_id(std::thread::id id) { thread_id_ = id; } 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_; } auto identifier() const -> EventLoopID { return identifier_; }
// Register a timer to run on the thread. // Register a timer to run on the thread.
@ -101,26 +102,55 @@ class EventLoop {
auto paused() { return paused_; } auto paused() { return paused_; }
private: private:
struct ThreadMessage { struct ThreadMessage_ {
enum class Type { kShutdown = 999, kRunnable, kPause, kResume }; enum class Type { kShutdown = 999, kRunnable, kPause, kResume };
Type type; Type type;
union { union {
Runnable* runnable{}; Runnable* runnable{};
}; };
bool* completion_flag{}; bool* completion_flag{};
explicit ThreadMessage(Type type_in) : type(type_in) {} explicit ThreadMessage_(Type type_in) : type(type_in) {}
explicit ThreadMessage(Type type, Runnable* runnable, bool* completion_flag) explicit ThreadMessage_(Type type, Runnable* runnable,
bool* completion_flag)
: type(type), runnable(runnable), completion_flag{completion_flag} {} : type(type), runnable(runnable), completion_flag{completion_flag} {}
}; };
auto CheckPushRunnableSafety() -> bool; auto CheckPushRunnableSafety_() -> bool;
void SetInternalThreadName(const std::string& name); void SetInternalThreadName_(const std::string& name);
void WaitForNextEvent(bool single_cycle); void WaitForNextEvent_(bool single_cycle);
void LoopUpkeep(bool once); void LoopUpkeep_(bool once);
void LogThreadMessageTally( void LogThreadMessageTally_(
std::vector<std::pair<LogLevel, std::string>>* log_entries); std::vector<std::pair<LogLevel, std::string>>* log_entries);
void PushLocalRunnable(Runnable* runnable, bool* completion_flag); void PushLocalRunnable_(Runnable* runnable, bool* completion_flag);
void PushCrossThreadRunnable(Runnable* runnable, bool* completion_flag); void PushCrossThreadRunnable_(Runnable* runnable, bool* completion_flag);
void NotifyClientListeners(); 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<ThreadMessage_>* messages);
void PushThreadMessage_(const ThreadMessage_& t);
void RunPendingRunnables_();
void RunPauseCallbacks_();
void RunResumeCallbacks_();
void AcquireGIL_();
void ReleaseGIL_();
bool writing_tally_{}; bool writing_tally_{};
bool paused_{}; bool paused_{};
@ -140,40 +170,13 @@ class EventLoop {
void* auto_release_pool_{}; void* auto_release_pool_{};
#endif #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<ThreadMessage>* messages);
void PushThreadMessage(const ThreadMessage& t);
void RunPendingRunnables();
void RunPauseCallbacks();
void RunResumeCallbacks();
void AcquireGIL();
void ReleaseGIL();
bool bootstrapped_{}; bool bootstrapped_{};
std::list<std::pair<Runnable*, bool*>> runnables_; std::list<std::pair<Runnable*, bool*>> runnables_;
std::list<Runnable*> pause_callbacks_; std::list<Runnable*> pause_callbacks_;
std::list<Runnable*> resume_callbacks_; std::list<Runnable*> resume_callbacks_;
std::condition_variable thread_message_cv_; std::condition_variable thread_message_cv_;
std::mutex thread_message_mutex_; std::mutex thread_message_mutex_;
std::list<ThreadMessage> thread_messages_; std::list<ThreadMessage_> thread_messages_;
std::condition_variable client_listener_cv_; std::condition_variable client_listener_cv_;
std::mutex client_listener_mutex_; std::mutex client_listener_mutex_;
std::list<std::vector<char>> data_to_client_; std::list<std::vector<char>> data_to_client_;

View File

@ -77,6 +77,11 @@ const char* Python::ScopedCallLabel::current_label_ = nullptr;
auto Python::HaveGIL() -> bool { return static_cast<bool>(PyGILState_Check()); } auto Python::HaveGIL() -> bool { return static_cast<bool>(PyGILState_Check()); }
void Python::PermanentlyReleaseGIL() {
assert(HaveGIL());
PyEval_SaveThread();
}
void Python::PrintStackTrace() { void Python::PrintStackTrace() {
bool available{}; bool available{};
if (g_base_soft) { if (g_base_soft) {

View File

@ -76,7 +76,11 @@ class Python {
/// sanity checking that. /// sanity checking that.
static auto HaveGIL() -> bool; 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(); static void PrintStackTrace();
/// Pass any PyObject* (including nullptr) to get a readable string /// Pass any PyObject* (including nullptr) to get a readable string

View File

@ -111,8 +111,9 @@ def _filter_tool_config(projroot: Path, cfg: str) -> str:
' :jedi (:extra_paths' ' :jedi (:extra_paths'
' [__EFRO_PYTHON_PATHS_Q_REL_STR__])\n' ' [__EFRO_PYTHON_PATHS_Q_REL_STR__])\n'
' :pylsp_mypy (:enabled t\n' ' :pylsp_mypy (:enabled t\n'
' :live_mode nil\n' ' :live_mode :json-false\n'
' :dmypy t))))))))\n', ' :report_progress t\n'
' :dmypy :json-false))))))))\n',
) )
# Stick project-root wherever they want. # Stick project-root wherever they want.

View File

@ -15,7 +15,6 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
# Pull in commands we want to expose. Its more efficient to define them in # 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. # modules rather than inline here because we'll be able to load them via pyc.
# pylint: disable=unused-import # pylint: disable=unused-import