diff --git a/.efrocachemap b/.efrocachemap
index ebd14c62..82e6f6f8 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -3995,50 +3995,51 @@
"assets/src/ba_data/python/ba/_generated/__init__.py": "https://files.ballistica.net/cache/ba1/ee/e8/cad05aa531c7faf7ff7b96db7f6e",
"assets/src/ba_data/python/ba/_generated/enums.py": "https://files.ballistica.net/cache/ba1/b2/e5/0ee0561e16257a32830645239f34",
"ballisticacore-windows/Generic/BallisticaCore.ico": "https://files.ballistica.net/cache/ba1/89/c0/e32c7d2a35dc9aef57cc73b0911a",
- "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/4c/c8/30631d3154a8c450b105da4fba36",
- "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/81/c8/19c1a99a50a074027e85485b48ef",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5f/5f/93f8a60c6f6d58352a83540792ba",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b5/1c/c0fe6d5570d238872bfc839b46ac",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/b6/92/91c7675daecf9f2c01672f45b420",
- "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f5/49/7fff46b30c288ffc7e94c9fd4182",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e9/f3/99c96b36694274aab5f44f0a8c78",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/30/19/031ab0138f689d0fc7698c55f514",
- "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/78/9b/6613bcdbd99cba2abd8994da47e9",
- "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/85/83/8be76939bf30ad8d43d189099b4c",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a5/bc/374eaeb09819570ba9b9c30707e0",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/37/40/306b6050cc2ab7ce40b8a1935989",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/03/6b/3bdea294966f3d6b5f86ab3f15f1",
- "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/e9/8f/86486943a8e2de808deb812cfdc6",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/33/72/b886fb6fdb0c36066da3a6d72f40",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/bb/e5/425b5f16ffaae0ade584872e6b1c",
- "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a3/c9/44b771f906edf3d63943acd535a8",
- "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/17/b5/95fc75d8b8ee7dc24e2897309ff1",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/93/06/3ff8db73b443613e342010b8d26f",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/60/09/d6e216de302ff8dfbb5966b08f90",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/81/60/37044e17e52ca125c1d48af83d03",
- "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fb/47/d505c3feea40888985dd335dc8c4",
- "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4f/3d/20c78e744ac26a13b7418166d675",
- "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e3/77/67a1fafff2c1918b1661b71ecaa0",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/9b/00/f11d8f7f79c9b692b84f6dbd54dc",
- "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f8/3a/0cd5030cbe13f99bf23d05ee6cfb",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5b/2a/3be2a55cbd63f9c42618c4013c87",
- "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/09/ef/8fbfc6032e6a6089215c3dc365a1",
- "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f8/79/31ecc50de3e52c55a1569b8b9438",
- "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8a/a0/3ef54e3c6c3c777f8a221da16c14",
- "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ee/a8/07c5e78d027bfc1acc88a25932b4",
- "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ec/f5/a161f6c194a64f371e35e31bbfca",
- "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/56/45/b6b9259f447f19ef0643b972bc64",
- "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3b/79/592c41691e94807d23a783723861",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/13/d9/36f4cf8527494f419db74ae6c78f",
- "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ee/e5/7f0677e6fb8d7cfa169930579712",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/69/c1/1694e8c7f55ac1c07277a2b5d7c2",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/73/5d/e1960323b7229bf34dd88a4b7a72",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/2c/13/30518d52dfa93a303ba6033f13d4",
- "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/dc/41/911fb9a80bbe2e8234d8610b0cf3",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/be/e0/9c48a3fc8c3a0caf8a5c2c88ddf4",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/02/bd/6e08bef172191198f82e61f714f6",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/f2/3e/5fbbe38fa5047ac11b10f214ecd9",
- "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/4f/cf/09c154037d8f597fcac94fcf5723",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ca/c4/9784ffa7a202da7e851c447dc69a",
+ "build/prefab/full/linux_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/f0/15/1ffcc9804e84eaa6a652be53cbbd",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ac/45/ff0681bcf7d1c5ac2c191d288434",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/7f/27/4a4158050c3d77a7021e12bb9d20",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/f1/c9/048c9c9267b456ff7e5610624fbd",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/ad/fe/6afe3275a46a1384c348ee3917bc",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/de/e3/69d0a6f6cbfba5a6e0d74c0442ba",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/06/b6/824ee73b255e99093aeae719e2e4",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/c7/0e/3dd207020a393e2fff196918e986",
+ "build/prefab/full/mac_arm64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/7b/d3/3698352ea276344dd12e969da09e",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ff/b3/5e9acbef286f6738de3023011556",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ab/ab/50ea4865929b418bf25dac3f475d",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e2/9f/aae9e3204175fce33a75d486008a",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticacore": "https://files.ballistica.net/cache/ba1/98/89/63eec19c696e364e349b0d200e91",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/5b/75/bc745b50536aade78db0150d9d9e",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/94/06/ba7c0d3e3d3e9d02594871687ec9",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/a2/ea/87883b1f4063980a1a40a112f9ce",
+ "build/prefab/full/windows_x86_gui/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/da/0b/16a433cafa1e2a7f87caf83409bf",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/3a/4c/8aa877e66c3a7ad25dbd0e075ef8",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaCoreHeadless.exe": "https://files.ballistica.net/cache/ba1/83/07/b36ff25c01d4790b4758e0c86e68",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/4b/02/e1d6afd343ce32d53c134c5fc4e7",
+ "build/prefab/lib/linux_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ed/18/45e61446f5f7e02624fb87fb8c97",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/cf/46/393131611fa1395fd9c173d8426f",
+ "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2d/2e/d4a8ba6f7c46da387394f4542d02",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/df/6c/cafe5794323f445392e63114f14b",
+ "build/prefab/lib/linux_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/00/ba/5c85c98957ac9f0832a01ea83168",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/35/0c/c85a8d0a8b43dac24a94417297fb",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f1/ca/c78a414c9d6ec0ffb647585c3e33",
+ "build/prefab/lib/mac_arm64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0f/6a/83518a680d4917e3356d21d3650d",
+ "build/prefab/lib/mac_arm64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6a/39/2e1794bf48cbc4c67c63b9527a68",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/3b/64/48be465941f4f1aa3332a1d32f6a",
+ "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/63/03/d9a332d36a337086b639c4240674",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ea/ca/533d54af50f2b5750f7c407dcbd5",
+ "build/prefab/lib/mac_x86_64_gui/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/17/d6/e9af602218f3dd14a5bedb6c7eb2",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a6/cc/2ee87bb2ead38a3bf7d8b2588f39",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/e5/ef/91bb1dfff04d62899cbe363b0d23",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/a2/7a/faf4c2744e2762e58d0119dafa72",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/7d/66/d3aafe52f5d44a23aa426bc8e602",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/63/5b/a3d3b19e7583a72cbdeefbd00f7e",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/60/14/3a28abc1b1b87b14f05d86a2e900",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.lib": "https://files.ballistica.net/cache/ba1/6c/a3/ded8df37d31c54c4ab66e224015a",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreGenericInternal.pdb": "https://files.ballistica.net/cache/ba1/2b/c6/a8701e03471178983957c33026ff",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.lib": "https://files.ballistica.net/cache/ba1/7e/16/307bb68c0561181ba701d0bcd350",
+ "build/prefab/lib/windows/Release_Win32/BallisticaCoreHeadlessInternal.pdb": "https://files.ballistica.net/cache/ba1/09/b3/2fd1934cb6f0d2454e222c60ef3b",
"src/ballistica/generated/python_embedded/binding.inc": "https://files.ballistica.net/cache/ba1/7d/3e/229a581cb2454ed856f1d8b564a7",
- "src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/98/12/571b2160d69d42580e8f31fa6a8d"
+ "src/ballistica/generated/python_embedded/bootstrap.inc": "https://files.ballistica.net/cache/ba1/2d/4f/f4fe67827f36cd59cd5193333a02",
+ "src/ballistica/generated/python_embedded/bootstrap_monolithic.inc": "https://files.ballistica.net/cache/ba1/ef/c1/aa5f1aa10af89f5c0b1e616355fd"
}
\ No newline at end of file
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 06bafff1..6ae45394 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -234,6 +234,7 @@
benning
bfiledir
bfiles
+ bgdynamics
bgmodel
bgrn
bgterrain
@@ -415,6 +416,7 @@
cleanlist
cleanupcheck
cleanupchecks
+ clearsign
clientid
clientlist
clienttobasn
@@ -1648,6 +1650,7 @@
nettest
nettesting
netutils
+ networkwrite
nevermind
newactivity
newdamage
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c0642d1..bf01925f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-### 1.7.7 (build 20837, api 7, 2022-09-12)
+### 1.7.7 (build 20840, api 7, 2022-09-13)
- Added `ba.app.meta.load_exported_classes()` for loading classes discovered by the meta subsystem cleanly in a background thread.
- Improved logging of missing playlist game types.
- Some ba.Lstr functionality can now be used in background threads.
@@ -24,7 +24,9 @@
- Renamed C++ Media to Assets.
- Removed 'scores to beat' list in coop which was only ever functional in limited cases on the Mac version. Perhaps that feature can reappear in a cross-platform way sometime.
- Simplified C++ bootstrapping to allocate all globals in one place.
-- Renamed C++ Game class to Logic.
+- Renamed C++ Game classes to Logic.
+- The app now bootstraps Python in the main thread instead of the logic thread. This will keep things more consistent later when we are able to run under an already-existing Python interpreter.
+- Python and _ba are now completely initialized in public source code. Now we just need to enable the app to survive without _bainternal and it'll be possible to build a 100% open source app.
### 1.7.6 (build 20687, api 7, 2022-08-11)
- Cleaned up da MetaSubsystem code.
diff --git a/assets/src/ba_data/python/._bainternal_sources_hash b/assets/src/ba_data/python/._bainternal_sources_hash
index 0321a786..6e9f924c 100644
--- a/assets/src/ba_data/python/._bainternal_sources_hash
+++ b/assets/src/ba_data/python/._bainternal_sources_hash
@@ -1 +1 @@
-304827393730630459319623179535089986346
\ No newline at end of file
+222094078620857897443553282652634355523
\ No newline at end of file
diff --git a/assets/src/ba_data/python/ba/_bootstrap.py b/assets/src/ba_data/python/ba/_bootstrap.py
index f25876fd..8bbf492e 100644
--- a/assets/src/ba_data/python/ba/_bootstrap.py
+++ b/assets/src/ba_data/python/ba/_bootstrap.py
@@ -5,7 +5,6 @@ from __future__ import annotations
import os
import sys
-import signal
import threading
from typing import TYPE_CHECKING
@@ -38,7 +37,7 @@ def bootstrap() -> None:
# Give a soft warning if we're being used with a different binary
# version than we expect.
- expected_build = 20837
+ expected_build = 20840
running_build: int = env['build_number']
if running_build != expected_build:
print(
@@ -48,16 +47,11 @@ def bootstrap() -> None:
f' This might cause the app to error or misbehave.',
file=sys.stderr)
- # Tell Python to not handle SIGINT itself (it normally generates
- # KeyboardInterrupts which make a mess; we want to intercept them
- # for simple clean exit). We capture interrupts per-platform in
- # the C++ layer.
- # Note: I tried creating a handler in Python but it seemed to often have
- # a delay of up to a second before getting called. (not a huge deal
- # but I'm picky).
- signal.signal(signal.SIGINT, signal.SIG_DFL) # Do default handling.
+ # In bootstrap_monolithic.py we told Python not to handle SIGINT itself
+ # (because that must be done in the main thread). Now we finish the
+ # job by adding our own handler to replace it.
- # ..though it turns out we need to set up our C signal handling AFTER
+ # Note: I've found we need to set up our C signal handling AFTER
# we've told Python to disable its own; otherwise (on Mac at least) it
# wipes out our existing C handler.
_ba.setup_sigint()
diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
index 597eda1d..16d6b197 100644
--- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
@@ -119,6 +119,7 @@
bdea
benning
bezanson
+ bgdynamics
bgra
bigendian
bilinear
@@ -226,6 +227,7 @@
classdict
classline
cleanupcheck
+ clearsign
clientid
clientinfo
clienttobasn
@@ -849,6 +851,7 @@
netcode
netplay
nettest
+ networkwrite
newactivity
newchild
newimg
diff --git a/ballisticacore-cmake/.idea/inspectionProfiles/Project_Default.xml b/ballisticacore-cmake/.idea/inspectionProfiles/Project_Default.xml
index ffa57a65..bccdbc35 100644
--- a/ballisticacore-cmake/.idea/inspectionProfiles/Project_Default.xml
+++ b/ballisticacore-cmake/.idea/inspectionProfiles/Project_Default.xml
@@ -58,6 +58,7 @@
+
diff --git a/src/ballistica/audio/audio_server.cc b/src/ballistica/audio/audio_server.cc
index 86d02fb8..a63451d7 100644
--- a/src/ballistica/audio/audio_server.cc
+++ b/src/ballistica/audio/audio_server.cc
@@ -86,7 +86,7 @@ class AudioServer::ThreadSource : public Object {
auto play_id() const -> uint32_t {
return (play_count_ << 16u) | (static_cast(id_) & 0xFFFFu);
}
- void UpdateAvailability();
+ auto UpdateAvailability() -> void;
auto GetDefaultOwnerThread() const -> ThreadTag override;
auto client_source() const -> AudioSource* { return client_source_.get(); }
auto source_sound() const -> SoundData* {
diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc
index fd3c28b4..5a4c81db 100644
--- a/src/ballistica/ballistica.cc
+++ b/src/ballistica/ballistica.cc
@@ -32,7 +32,7 @@
namespace ballistica {
// These are set automatically via script; don't modify them here.
-const int kAppBuildNumber = 20837;
+const int kAppBuildNumber = 20840;
const char* kAppVersion = "1.7.7";
// Our standalone globals.
@@ -88,9 +88,10 @@ auto BallisticaMain(int argc, char** argv) -> int {
g_platform = Platform::Create();
g_app = new App(argc, argv);
+ g_app_internal = CreateAppInternal();
g_main_thread = new Thread(ThreadTag::kMain, ThreadSource::kWrapMain);
- g_python = new Python();
g_app_flavor = g_platform->CreateAppFlavor();
+ g_python = Python::Create();
g_graphics = g_platform->CreateGraphics();
g_graphics_server = new GraphicsServer();
g_audio = new Audio();
@@ -107,7 +108,6 @@ auto BallisticaMain(int argc, char** argv) -> int {
g_network_reader = new NetworkReader();
g_network_writer = new NetworkWriter();
g_input = new Input();
- g_app_internal = CreateAppInternal();
g_logic = new Logic();
g_scene_v1 = new SceneV1();
if (!HeadlessMode()) {
@@ -220,7 +220,7 @@ auto FatalError(const std::string& message) -> void {
FatalError::ReportFatalError(message, false);
bool exit_cleanly = !IsUnmodifiedBlessedBuild();
bool handled = FatalError::HandleFatalError(exit_cleanly, false);
- assert(handled);
+ BA_PRECONDITION(handled);
}
auto GetAppInstanceUUID() -> const std::string& {
diff --git a/src/ballistica/internal/app_internal.h b/src/ballistica/internal/app_internal.h
index 3c26ee9b..076c246f 100644
--- a/src/ballistica/internal/app_internal.h
+++ b/src/ballistica/internal/app_internal.h
@@ -16,7 +16,7 @@ class AppInternal {
public:
virtual ~AppInternal() {}
- virtual auto PyInitialize(void* pyconfig) -> void = 0;
+ virtual auto DefineInternalModule() -> void = 0;
virtual auto PythonPostInit() -> void = 0;
virtual auto HasBlessingHash() -> bool = 0;
virtual auto PutLog(bool fatal) -> bool = 0;
diff --git a/src/ballistica/logic/logic.cc b/src/ballistica/logic/logic.cc
index 90a31040..795951ac 100644
--- a/src/ballistica/logic/logic.cc
+++ b/src/ballistica/logic/logic.cc
@@ -83,10 +83,20 @@ auto Logic::OnAppStart() -> void {
auto Logic::OnAppStartInThread() -> void {
try {
- // Our thread should hold the Python GIL any time it is running.
- // TODO(ericf): It could be better to have each individual Python call
- // we make acquire the GIL. Then we're not holding it during long
- // bits of C++ logic.
+ // Our thread should not be holding the GIL here at the start (and
+ // probably not have any Python state at all). So here we set both
+ // of those up.
+ assert(!PyGILState_Check());
+ PyGILState_Ensure();
+
+ // Tell our thread that it should grab the Python GIL any time it
+ // is running something and release it when done.
+ // TODO(ericf): It could be good to explore the idea of having
+ // individual runnables grab the GIL instead of doing it here at the
+ // thread level. Though its a bit freeing to know that we can run
+ // Python code at any time in the logic thread without worry. We can
+ // also consider explicitly releasing the GIL in the logic thread
+ // during long operations where we know no Python will occur.
thread_->SetAcquiresPythonGIL();
// We want to be informed when our thread is pausing.
@@ -98,7 +108,7 @@ auto Logic::OnAppStartInThread() -> void {
// Init python and apply our settings immediately.
// This way we can get started loading stuff in the background
// and it'll come in with the correct texture quality etc.
- g_python->Reset(true);
+ g_python->InitBallisticaPython();
} catch (const std::exception& e) {
// If anything went wrong, trigger a deferred error.
// This way it is more likely we can show a fatal error dialog
diff --git a/src/ballistica/python/methods/python_methods_app.cc b/src/ballistica/python/methods/python_methods_app.cc
index 54a10daf..d8a7fc6f 100644
--- a/src/ballistica/python/methods/python_methods_app.cc
+++ b/src/ballistica/python/methods/python_methods_app.cc
@@ -701,6 +701,7 @@ auto PyCommitConfig(PyObject* self, PyObject* args, PyObject* keywds)
auto PyEnv(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
+ assert(g_app->is_bootstrapped);
static PyObject* env_obj = nullptr;
diff --git a/src/ballistica/python/python.cc b/src/ballistica/python/python.cc
index 169564bd..ae8d285f 100644
--- a/src/ballistica/python/python.cc
+++ b/src/ballistica/python/python.cc
@@ -853,6 +853,19 @@ auto Python::GetPyVector3f(PyObject* o) -> Vector3f {
Python::Python() = default;
+auto Python::Create() -> Python* {
+ assert(InMainThread());
+
+ Python* python = new Python();
+ python->InitCorePython();
+
+ // After we bootstrap Python here in the main thread we release the GIL.
+ // We'll explicitly reacquire it anytime we need it (mainly in the logic
+ // thread once that comes up later).
+ PyEval_SaveThread();
+ return python;
+}
+
static struct PyModuleDef ba_module_def = {PyModuleDef_HEAD_INIT};
static auto ba_exec(PyObject* module) -> int {
@@ -885,161 +898,183 @@ static auto PyInit__ba() -> PyObject* {
return module;
}
-void Python::Reset(bool do_init) {
+auto Python::InitCorePython() -> void {
+ assert(!inited_);
+ assert(InMainThread());
+ // Flip on some extra runtime debugging options in debug builds.
+ // https://docs.python.org/3/library/devmode.html#devmode
+ int dev_mode{g_buildconfig.debug_build()};
+
+ // Pre-config as isolated if we include our own Python and as standard
+ // otherwise.
+ PyPreConfig preconfig;
+ if (g_platform->ContainsPythonDist()) {
+ PyPreConfig_InitIsolatedConfig(&preconfig);
+ } else {
+ PyPreConfig_InitPythonConfig(&preconfig);
+ }
+ preconfig.dev_mode = dev_mode;
+
+ // We want consistent utf-8 everywhere (Python used to default to
+ // windows-specific file encodings, etc.)
+ preconfig.utf8_mode = 1;
+
+ PyStatus status = Py_PreInitialize(&preconfig);
+ BA_PRECONDITION(!PyStatus_Exception(status));
+
+ // Configure as isolated if we include our own Python and as standard
+ // otherwise.
+ PyConfig config;
+ if (g_platform->ContainsPythonDist()) {
+ PyConfig_InitIsolatedConfig(&config);
+ } else {
+ PyConfig_InitPythonConfig(&config);
+ }
+ config.dev_mode = dev_mode;
+ if (!g_buildconfig.debug_build()) {
+ config.optimization_level = 1;
+ }
+
+ // In cases where we bundle Python, set up all paths explicitly.
+ // https://docs.python.org/3/c-api/init_config.html#path-configuration
+ if (g_platform->ContainsPythonDist()) {
+ PyConfig_SetBytesString(&config, &config.base_exec_prefix, "");
+ PyConfig_SetBytesString(&config, &config.base_executable, "");
+ PyConfig_SetBytesString(&config, &config.base_prefix, "");
+ PyConfig_SetBytesString(&config, &config.exec_prefix, "");
+ PyConfig_SetBytesString(&config, &config.executable, "");
+ PyConfig_SetBytesString(&config, &config.prefix, "");
+
+ // Interesting note: it seems we can pass relative paths here but
+ // they wind up in sys.path as absolute paths (unlike entries we add
+ // to sys.path after things are up and running).
+ if (g_buildconfig.ostype_windows()) {
+ // Windows Python looks for Lib and DLLs dirs by default, along with
+ // some others, but we want to be more explicit in limiting to these. It
+ // also seems that windows Python's paths can be incorrect if we're in
+ // strange dirs such as \\wsl$\Ubuntu-18.04\ that we get with WSL build
+ // setups.
+
+ // NOTE: Python for windows actually comes with 'Lib', not 'lib', but
+ // it seems the interpreter defaults point to ./lib (as of 3.8.5).
+ // Normally this doesn't matter since windows is case-insensitive but
+ // under WSL it does.
+ // So we currently bundle the dir as 'lib' and use that in our path so
+ // that everything is happy (both with us and with python.exe).
+ PyWideStringList_Append(&config.module_search_paths,
+ Py_DecodeLocale("lib", nullptr));
+ PyWideStringList_Append(&config.module_search_paths,
+ Py_DecodeLocale("DLLs", nullptr));
+ } else {
+ PyWideStringList_Append(&config.module_search_paths,
+ Py_DecodeLocale("pylib", nullptr));
+ }
+ config.module_search_paths_set = 1;
+ }
+
+ // Let Python know how to spin up our _ba module.
+ PyImport_AppendInittab("_ba", &PyInit__ba);
+
+ // Let Python know how to spin up the _bainternal module.
+ g_app_internal->DefineInternalModule();
+
+ // Init Python.
+ status = Py_InitializeFromConfig(&config);
+ BA_PRECONDITION(!PyStatus_Exception(status));
+
+ // Create a dict for execing just this bootstrap code in so
+ // we don't pollute the __main__ namespace.
+ auto bootstrap_context{PythonRef(PyDict_New(), PythonRef::kSteal)};
+
+ // Do a bit of basic bootstrapping here in the main thread.
+#include "ballistica/generated/python_embedded/bootstrap_monolithic.inc"
+ PyObject* result =
+ PyRun_String(bootstrap_monolithic_code, Py_file_input,
+ bootstrap_context.get(), bootstrap_context.get());
+ if (result == nullptr) {
+ PyErr_PrintEx(0);
+
+ // Throw a simple exception so we don't get a stack trace.
+ throw std::logic_error(
+ "Error in ba Python bootstrapping. See log for details.");
+ }
+ Py_DECREF(result);
+
+ // Grab __main__ in case we need to use it later.
+ // FIXME: put this in objs_ with everything else.
+ PyObject* m;
+ BA_PRECONDITION(m = PyImport_AddModule("__main__"));
+ BA_PRECONDITION(main_dict_ = PyModule_GetDict(m));
+
+ // Make sure we're running the Python version we require.
+ const char* ver = Py_GetVersion();
+ if (strncmp(ver, "3.10", 4) != 0) {
+ FatalError("We require Python 3.10.x; instead found " + std::string(ver));
+ }
+}
+
+auto Python::InitBallisticaPython() -> void {
+ assert(InLogicThread());
+
+ // Create a dict for execing just this bootstrap code in so
+ // we don't pollute the __main__ namespace.
+ auto bootstrap_context{PythonRef(PyDict_New(), PythonRef::kSteal)};
+
+ // Get the app up and running.
+ // Run a few core bootstrappy things first:
+ // - get stdout/stderr redirection up so we can intercept python output
+ // - add our user and system script dirs to python path
+ // - create the ba.app instance.
+
+#include "ballistica/generated/python_embedded/bootstrap.inc"
+ PyObject* result =
+ PyRun_String(bootstrap_code, Py_file_input, bootstrap_context.get(),
+ bootstrap_context.get());
+ if (result == nullptr) {
+ PyErr_PrintEx(0);
+
+ // Throw a simple exception so we don't get a stack trace.
+ throw std::logic_error(
+ "Error in ba Python bootstrapping. See log for details.");
+ }
+ Py_DECREF(result);
+
+ // Import and grab all the Python stuff we use from C++.
+#include "ballistica/generated/python_embedded/binding.inc"
+
+ g_app_internal->PythonPostInit();
+
+ // Alright I guess let's pull ba in to main, since pretty
+ // much all interactive commands will be using it.
+ // If we ever build the game as a pure python module we should
+ // of course not do this.
+ BA_PRECONDITION(PyRun_SimpleString("import ba") == 0);
+
+ // Read the config file and store the config dict for easy access.
+ obj(ObjID::kReadConfigCall).Call();
+ StoreObj(ObjID::kConfig, obj(ObjID::kApp).GetAttr("config").get());
+ assert(PyDict_Check(obj(ObjID::kConfig).get()));
+
+ // Turn off fancy-pants cyclic garbage-collection.
+ // We run it only at explicit times to avoid random hitches and keep
+ // things more deterministic.
+ // Non-reference-looped objects will still get cleaned up
+ // immediately, so we should try to structure things to avoid
+ // reference loops (just like Swift, ObjC, etc).
+ // FIXME - move this to Python code.
+ g_python->obj(Python::ObjID::kGCDisableCall).Call();
+ inited_ = true;
+}
+
+auto Python::Reset() -> void {
assert(InLogicThread());
assert(g_python);
assert(g_platform);
+ assert(inited_);
- bool was_inited = inited_;
-
- if (inited_) {
- ReleaseGamePadInput();
- ReleaseKeyboardInput();
- g_graphics->ReleaseFadeEndCommand();
- inited_ = false;
- }
-
- if (!was_inited && do_init) {
- // Flip on some extra runtime debugging options in debug builds.
- // https://docs.python.org/3.10/library/devmode.html#devmode
- int dev_mode{g_buildconfig.debug_build()};
-
- // Pre-config as isolated if we include our own Python and as standard
- // otherwise.
- PyPreConfig preconfig;
- if (g_platform->ContainsPythonDist()) {
- PyPreConfig_InitIsolatedConfig(&preconfig);
- } else {
- PyPreConfig_InitPythonConfig(&preconfig);
- }
- preconfig.dev_mode = dev_mode;
-
- // We want consistent utf-8 everywhere (Python used to default to
- // windows-specific file encodings, etc.)
- preconfig.utf8_mode = 1;
-
- PyStatus status = Py_PreInitialize(&preconfig);
- BA_PRECONDITION(!PyStatus_Exception(status));
-
- // Configure as isolated if we include our own Python and as standard
- // otherwise.
- PyConfig config;
- if (g_platform->ContainsPythonDist()) {
- PyConfig_InitIsolatedConfig(&config);
- } else {
- PyConfig_InitPythonConfig(&config);
- }
- config.dev_mode = dev_mode;
- if (!g_buildconfig.debug_build()) {
- config.optimization_level = 1;
- }
-
- // In cases where we bundle Python, set up all paths explicitly.
- // see https://docs.python.org/3.8/
- // c-api/init_config.html#path-configuration
- if (g_platform->ContainsPythonDist()) {
- PyConfig_SetBytesString(&config, &config.base_exec_prefix, "");
- PyConfig_SetBytesString(&config, &config.base_executable, "");
- PyConfig_SetBytesString(&config, &config.base_prefix, "");
- PyConfig_SetBytesString(&config, &config.exec_prefix, "");
- PyConfig_SetBytesString(&config, &config.executable, "");
- PyConfig_SetBytesString(&config, &config.prefix, "");
-
- // Interesting note: it seems we can pass relative paths here but
- // they wind up in sys.path as absolute paths (unlike entries we add
- // to sys.path after things are up and running).
- if (g_buildconfig.ostype_windows()) {
- // Windows Python looks for Lib and DLLs dirs by default, along with
- // some others, but we want to be more explicit in limiting to these. It
- // also seems that windows Python's paths can be incorrect if we're in
- // strange dirs such as \\wsl$\Ubuntu-18.04\ that we get with WSL build
- // setups.
-
- // NOTE: Python for windows actually comes with 'Lib', not 'lib', but
- // it seems the interpreter defaults point to ./lib (as of 3.8.5).
- // Normally this doesn't matter since windows is case-insensitive but
- // under WSL it does.
- // So we currently bundle the dir as 'lib' and use that in our path so
- // that everything is happy (both with us and with python.exe).
- PyWideStringList_Append(&config.module_search_paths,
- Py_DecodeLocale("lib", nullptr));
- PyWideStringList_Append(&config.module_search_paths,
- Py_DecodeLocale("DLLs", nullptr));
- } else {
- PyWideStringList_Append(&config.module_search_paths,
- Py_DecodeLocale("pylib", nullptr));
- }
- config.module_search_paths_set = 1;
- }
-
- // Let Python know how to spin up our _ba module.
- PyImport_AppendInittab("_ba", &PyInit__ba);
-
- // Inits our _ba module and runs Py_Initialize().
- g_app_internal->PyInitialize(&config);
-
- // Grab __main__ in case we need to use it later.
- PyObject* m;
- BA_PRECONDITION(m = PyImport_AddModule("__main__"));
- BA_PRECONDITION(main_dict_ = PyModule_GetDict(m));
-
- // Make sure we're running the Python version we require.
- const char* ver = Py_GetVersion();
- if (strncmp(ver, "3.10", 4) != 0) {
- throw Exception("We require Python 3.10.x; instead found "
- + std::string(ver));
- }
-
- // Create a dict for execing our bootstrap code in so
- // we don't pollute the __main__ namespace.
- auto bootstrap_context{PythonRef(PyDict_New(), PythonRef::kSteal)};
-
- // Get the app up and running.
- // Run a few core bootstrappy things first:
- // - get stdout/stderr redirection up so we can intercept python output
- // - add our user and system script dirs to python path
- // - create the ba.app instance.
-
-#include "ballistica/generated/python_embedded/bootstrap.inc"
- PyObject* result =
- PyRun_String(bootstrap_code, Py_file_input, bootstrap_context.get(),
- bootstrap_context.get());
- if (result == nullptr) {
- PyErr_PrintEx(0);
-
- // Throw a simple exception so we don't get a stack trace.
- throw std::logic_error(
- "Error in ba Python bootstrapping. See log for details.");
- }
- Py_DECREF(result);
-
- // Import and grab all the Python stuff we use from C++.
-#include "ballistica/generated/python_embedded/binding.inc"
-
- g_app_internal->PythonPostInit();
-
- // Alright I guess let's pull ba in to main, since pretty
- // much all interactive commands will be using it.
- // If we ever build the game as a pure python module we should
- // of course not do this.
- BA_PRECONDITION(PyRun_SimpleString("import ba") == 0);
-
- // Read the config file and store the config dict for easy access.
- obj(ObjID::kReadConfigCall).Call();
- StoreObj(ObjID::kConfig, obj(ObjID::kApp).GetAttr("config").get());
- assert(PyDict_Check(obj(ObjID::kConfig).get()));
-
- // Turn off fancy-pants cyclic garbage-collection.
- // We run it only at explicit times to avoid random hitches and keep
- // things more deterministic.
- // Non-reference-looped objects will still get cleaned up
- // immediately, so we should try to structure things to avoid
- // reference loops (just like Swift, ObjC, etc).
- g_python->obj(Python::ObjID::kGCDisableCall).Call();
- }
- if (do_init) {
- inited_ = true;
- }
+ ReleaseGamePadInput();
+ ReleaseKeyboardInput();
+ g_graphics->ReleaseFadeEndCommand();
}
auto Python::GetModuleMethods() -> std::vector {
@@ -1114,8 +1149,6 @@ void Python::PushObjCall(ObjID obj_id, const std::string& arg) {
});
}
-Python::~Python() { Reset(false); }
-
auto Python::GetResource(const char* key, const char* fallback_resource,
const char* fallback_value) -> std::string {
assert(HaveGIL());
@@ -2259,12 +2292,13 @@ void Python::LogContextAuto() {
}
void Python::AcquireGIL() {
+ assert(InLogicThread());
auto debug_timing{g_app->debug_timing};
millisecs_t startms{debug_timing ? Platform::GetCurrentMilliseconds() : 0};
- if (thread_state_) {
- PyEval_RestoreThread(thread_state_);
- thread_state_ = nullptr;
+ if (logic_thread_state_) {
+ PyEval_RestoreThread(logic_thread_state_);
+ logic_thread_state_ = nullptr;
}
if (debug_timing) {
@@ -2275,9 +2309,11 @@ void Python::AcquireGIL() {
}
}
}
+
void Python::ReleaseGIL() {
- assert(thread_state_ == nullptr);
- thread_state_ = PyEval_SaveThread();
+ assert(InLogicThread());
+ assert(logic_thread_state_ == nullptr);
+ logic_thread_state_ = PyEval_SaveThread();
}
void Python::AddCleanFrameCommand(const Object::Ref& c) {
diff --git a/src/ballistica/python/python.h b/src/ballistica/python/python.h
index e1094129..bf451032 100644
--- a/src/ballistica/python/python.h
+++ b/src/ballistica/python/python.h
@@ -80,9 +80,11 @@ class Python {
static auto LogContextAuto() -> void;
static auto LogContextNonLogicThread() -> void;
Python();
- ~Python();
+ static auto Create() -> Python*;
- auto Reset(bool init = true) -> void;
+ auto InitCorePython() -> void;
+ auto InitBallisticaPython() -> void;
+ auto Reset() -> void;
/// Add classes to the newly created ba module.
static auto InitModuleClasses(PyObject* module) -> void;
@@ -424,7 +426,7 @@ class Python {
PyObject* empty_dict_object_{};
PyObject* main_dict_{};
PyObject* env_{};
- PyThreadState* thread_state_{};
+ PyThreadState* logic_thread_state_{};
};
} // namespace ballistica
diff --git a/src/ballistica/python/python_ref.cc b/src/ballistica/python/python_ref.cc
index a91b6ada..008213d9 100644
--- a/src/ballistica/python/python_ref.cc
+++ b/src/ballistica/python/python_ref.cc
@@ -13,7 +13,6 @@ namespace ballistica {
#pragma ide diagnostic ignored "RedundantCast"
PythonRef::PythonRef(PyObject* obj_in, ReferenceBehavior b) {
- assert(g_python);
assert(Python::HaveGIL());
switch (b) {
case kSteal:
@@ -54,7 +53,6 @@ void PythonRef::Acquire(PyObject* obj_in) {
void PythonRef::Steal(PyObject* obj_in) {
BA_PRECONDITION(obj_in);
- assert(g_python);
assert(Python::HaveGIL());
// Assign before decrementing the old
@@ -67,7 +65,6 @@ void PythonRef::Steal(PyObject* obj_in) {
}
void PythonRef::Release() {
- assert(g_python);
assert(Python::HaveGIL());
// Py_CLEAR uses a temp variable and assigns o to nullptr first
@@ -113,7 +110,6 @@ auto PythonRef::ValueAsInt() const -> int64_t {
}
auto PythonRef::GetAttr(const char* name) const -> PythonRef {
- assert(g_python);
assert(Python::HaveGIL());
BA_PRECONDITION(obj_);
PyObject* val = PyObject_GetAttrString(get(), name);
@@ -149,7 +145,6 @@ auto PythonRef::UnicodeCheck() const -> bool {
auto PythonRef::Call(PyObject* args, PyObject* keywds, bool print_errors) const
-> PythonRef {
assert(obj_);
- assert(g_python);
assert(Python::HaveGIL());
assert(CallableCheck());
assert(args);
diff --git a/src/meta/.meta_manifest_public.json b/src/meta/.meta_manifest_public.json
index dd1d3882..a5a5e757 100644
--- a/src/meta/.meta_manifest_public.json
+++ b/src/meta/.meta_manifest_public.json
@@ -2,5 +2,6 @@
"assets/src/ba_data/python/ba/_generated/__init__.py",
"assets/src/ba_data/python/ba/_generated/enums.py",
"src/ballistica/generated/python_embedded/binding.inc",
- "src/ballistica/generated/python_embedded/bootstrap.inc"
+ "src/ballistica/generated/python_embedded/bootstrap.inc",
+ "src/ballistica/generated/python_embedded/bootstrap_monolithic.inc"
]
\ No newline at end of file
diff --git a/src/meta/Makefile b/src/meta/Makefile
index 073a4d54..ce68dfee 100644
--- a/src/meta/Makefile
+++ b/src/meta/Makefile
@@ -17,7 +17,8 @@ sources: \
../../assets/src/ba_data/python/ba/_generated/__init__.py \
../../assets/src/ba_data/python/ba/_generated/enums.py \
../ballistica/generated/python_embedded/binding.inc \
- ../ballistica/generated/python_embedded/bootstrap.inc
+ ../ballistica/generated/python_embedded/bootstrap.inc \
+ ../ballistica/generated/python_embedded/bootstrap_monolithic.inc
../ballistica/generated/python_embedded/binding.inc : bameta/python_embedded/binding.py
@$(PCOMMAND) gen_binding_code $< $@
@@ -25,6 +26,9 @@ sources: \
../ballistica/generated/python_embedded/bootstrap.inc : bameta/python_embedded/bootstrap.py
@$(PCOMMAND) gen_flat_data_code $< $@ bootstrap_code
+../ballistica/generated/python_embedded/bootstrap_monolithic.inc : bameta/python_embedded/bootstrap_monolithic.py
+ @$(PCOMMAND) gen_flat_data_code $< $@ bootstrap_monolithic_code
+
../../assets/src/ba_data/python/ba/_generated/__init__.py : ../../tools/batools/pcommand.py
@$(PCOMMAND) gen_python_init_module $@
@@ -45,7 +49,8 @@ efrocache-list:
@echo "../../assets/src/ba_data/python/ba/_generated/__init__.py" \
"../../assets/src/ba_data/python/ba/_generated/enums.py" \
"../ballistica/generated/python_embedded/binding.inc" \
- "../ballistica/generated/python_embedded/bootstrap.inc"
+ "../ballistica/generated/python_embedded/bootstrap.inc" \
+ "../ballistica/generated/python_embedded/bootstrap_monolithic.inc"
efrocache-build: sources
diff --git a/src/meta/bameta/python_embedded/bootstrap.py b/src/meta/bameta/python_embedded/bootstrap.py
index d84cd45d..85d06b15 100644
--- a/src/meta/bameta/python_embedded/bootstrap.py
+++ b/src/meta/bameta/python_embedded/bootstrap.py
@@ -1,6 +1,8 @@
# Released under the MIT License. See LICENSE for details.
#
-"""Initial ballistica bootstrapping."""
+"""Ballistica bootstrapping."""
+
+# This code runs in the logic thread to bootstrap ballistica.
from __future__ import annotations
diff --git a/src/meta/bameta/python_embedded/bootstrap_monolithic.py b/src/meta/bameta/python_embedded/bootstrap_monolithic.py
new file mode 100644
index 00000000..d6534a1a
--- /dev/null
+++ b/src/meta/bameta/python_embedded/bootstrap_monolithic.py
@@ -0,0 +1,22 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""Main thread bootstrapping for Python monolithic builds."""
+
+# This code runs in the main thread just after the interpreter comes up.
+# It should *ONLY* do things that must be done in the main thread and
+# should not import any ballistica stuff.
+
+from __future__ import annotations
+
+import signal
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+ pass
+
+# Tell Python to not handle SIGINT itself (it normally generates
+# KeyboardInterrupts which make a mess; we want to intercept them
+# for simple clean exit). We have to do this part here because it must
+# run in the main thread. We add our own handler later in the logic thread
+# alongside our other ba bootstrapping.
+signal.signal(signal.SIGINT, signal.SIG_DFL) # Do default handling.