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/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",

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
`setprojectconfig` (to reflect the file name changes that happened in 1.7.20).
- 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
# 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

View File

@ -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"

View File

@ -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:

View File

@ -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);
}
}

View File

@ -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() {

View File

@ -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.");
}

View File

@ -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));
}

View File

@ -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 =

View File

@ -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<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainLogic_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain_();
}
auto EventLoop::ThreadMainLogicP(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainLogicP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr;
}
auto EventLoop::ThreadMainAudio(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainAudio_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain_();
}
auto EventLoop::ThreadMainAudioP(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainAudioP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr;
}
auto EventLoop::ThreadMainBGDynamics(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainBGDynamics_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain_();
}
auto EventLoop::ThreadMainBGDynamicsP(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainBGDynamicsP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr;
}
auto EventLoop::ThreadMainNetworkWrite(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainNetworkWrite_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain_();
}
auto EventLoop::ThreadMainNetworkWriteP(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainNetworkWriteP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr;
}
auto EventLoop::ThreadMainStdInput(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainStdInput_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain_();
}
auto EventLoop::ThreadMainStdInputP(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainStdInputP_(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain_();
return nullptr;
}
auto EventLoop::ThreadMainAssets(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainAssets_(void* data) -> int {
return static_cast<EventLoop*>(data)->ThreadMain_();
}
auto EventLoop::ThreadMainAssetsP(void* data) -> void* {
static_cast<EventLoop*>(data)->ThreadMain();
auto EventLoop::ThreadMainAssetsP_(void* data) -> void* {
static_cast<EventLoop*>(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<ThreadMessage> thread_messages;
GetThreadMessages(&thread_messages);
std::list<ThreadMessage_> 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<ThreadMessage>* messages) {
void EventLoop::GetThreadMessages_(std::list<ThreadMessage_>* messages) {
assert(messages);
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);
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<std::pair<LogLevel, std::string>>* 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<int>(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();

View File

@ -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<std::pair<LogLevel, std::string>>* 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<ThreadMessage_>* 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<ThreadMessage>* messages);
void PushThreadMessage(const ThreadMessage& t);
void RunPendingRunnables();
void RunPauseCallbacks();
void RunResumeCallbacks();
void AcquireGIL();
void ReleaseGIL();
bool bootstrapped_{};
std::list<std::pair<Runnable*, bool*>> runnables_;
std::list<Runnable*> pause_callbacks_;
std::list<Runnable*> resume_callbacks_;
std::condition_variable thread_message_cv_;
std::mutex thread_message_mutex_;
std::list<ThreadMessage> thread_messages_;
std::list<ThreadMessage_> thread_messages_;
std::condition_variable client_listener_cv_;
std::mutex client_listener_mutex_;
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()); }
void Python::PermanentlyReleaseGIL() {
assert(HaveGIL());
PyEval_SaveThread();
}
void Python::PrintStackTrace() {
bool available{};
if (g_base_soft) {

View File

@ -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

View File

@ -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.

View File

@ -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