mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-04 06:23:19 +08:00
1.7.27 work in progress
This commit is contained in:
parent
82aa76b29b
commit
5ea2344e1d
88
.efrocachemap
generated
88
.efrocachemap
generated
@ -4064,50 +4064,50 @@
|
|||||||
"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": "bb5e0df17efe96476c3c2b8c41c7979b",
|
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "a039bab674a99b559440323b965d2274",
|
||||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "056115be35ac1afc1f62b58dcc8f217a",
|
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "fb15d3a3e792163f18af727447564192",
|
||||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cf2b052caaa06d512ef379687b3aff86",
|
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "246782dc8e1f2114c62980f8addbc4f4",
|
||||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "97b8d5f261f84b8047d43df1ca81777a",
|
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "8e59c9779e54f22b66ddfe2cd7c21528",
|
||||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "f304ee46675e7552c31e174d81199307",
|
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "0c241652d1669e3bbaf8df19c3ae756c",
|
||||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "70b54361b557f5e8d4271a0c976b28b6",
|
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "40ba4e0316c063238ab8e8b94f98351c",
|
||||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "06c778dc4c2772acf6dbaf67eb7321c9",
|
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "9d80b87c57556a0877f260305f571c78",
|
||||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "212a5c7206c1aa9a8427b61471e9e89b",
|
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "0e7d5147a5b1b9a7ecb8e6fc4cfc1174",
|
||||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f3a1028602c7bbd3f8d62bd843af628d",
|
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "943297ef2d247451140c08816fa0b46d",
|
||||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "9c4c6d1c50e225dc61cfbab4a82a37a6",
|
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "af9ef217e000fb8e2d7fff8770b7bf44",
|
||||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "4db4b600478f1767fdd0462a137912de",
|
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c0e09234f16c75313eab30d576783549",
|
||||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "628bc102cf6ef17b38804c4c9baa5265",
|
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "3f265457324e3a07a676b4db52a5f111",
|
||||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "fb9d165ab24084e8beaa6d7c51c81a77",
|
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "6482c468d8e798e081310c294553e4da",
|
||||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "ae88283e96a9238aab7561d2afcd9a5f",
|
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c6354818c9abd243e9b9af03f1f075f7",
|
||||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "e5fe958377b8dcf5d5203dbd17aaab72",
|
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "43908d43f107baa521cee51af01a9583",
|
||||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f22e8af184b19b4929162e901a056454",
|
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "c91f0c62c989a33caa7b4b4769754f1a",
|
||||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5fa2cb24b9e78bddb1bf9efb197d0c51",
|
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "79a0eb8f637e295447150a2c1e03357d",
|
||||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "d36f11acfa5e68f303d347c3895979d0",
|
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "5900beafe0de9b11ce4d00e9163c2d15",
|
||||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "570a7e325c15ecebcc419d48a046dd24",
|
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "7e7a5d0cc2f6fdd8fd9affbc05c5195c",
|
||||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "3155f665993e5f850db5b87c9296abe7",
|
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "91a1f57c0f4e9ef6fb5eb590f883e167",
|
||||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "d1bfae5e75824ba89338892bc0f84c6b",
|
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "56ebc8c31e020e515395d3a81d2bb766",
|
||||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b1466048e319c0d60139c46751f3eb79",
|
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b9aabf060ca52f9957e5c0c68975dd0d",
|
||||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "d1bfae5e75824ba89338892bc0f84c6b",
|
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "56ebc8c31e020e515395d3a81d2bb766",
|
||||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "b1466048e319c0d60139c46751f3eb79",
|
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "b9aabf060ca52f9957e5c0c68975dd0d",
|
||||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "656176631037184b6e22b0b68c3cd1fa",
|
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "21b01b868f38182cbe30dead5e6f6688",
|
||||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "12b633db4dad37bbb5a5c398db0c10dd",
|
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "5bafa4627b87a3cfc6558d51c2760560",
|
||||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "656176631037184b6e22b0b68c3cd1fa",
|
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "21b01b868f38182cbe30dead5e6f6688",
|
||||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "12b633db4dad37bbb5a5c398db0c10dd",
|
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "5bafa4627b87a3cfc6558d51c2760560",
|
||||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "7e014214c6cb9ddaa0e95f5186ba9df6",
|
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "9ba592d991ebb8724de8cab368bd1ac7",
|
||||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "45ddc559dd5ef563d2df5c5db9c9fbc0",
|
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "79630364e1f71cedf87140c40b913785",
|
||||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "7e014214c6cb9ddaa0e95f5186ba9df6",
|
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "9ba592d991ebb8724de8cab368bd1ac7",
|
||||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "45ddc559dd5ef563d2df5c5db9c9fbc0",
|
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "79630364e1f71cedf87140c40b913785",
|
||||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "1a15bf7d809addab4992827da9d89895",
|
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "7e3d1a1c0bdb8a814f7227f71862fa1d",
|
||||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "a1e03b7d13482beab8852691b5698974",
|
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "5bfe717b5f30a67822130299b7342bcf",
|
||||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "807e3629d9d4611cd93feb87face4e51",
|
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "6c7d4caaad12d39c61b291fe33eef2af",
|
||||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "a1e03b7d13482beab8852691b5698974",
|
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "5bfe717b5f30a67822130299b7342bcf",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "7f7bc04993982b164f6e86ad6ce350ef",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "ac0e239be82c9f04383eb4400313ad90",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "d24102dd35c29b6a77cdf3d9921695da",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "866ae37140298f2bd1ed9913afa773fb",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "8edef08a22594617d2b4273e0e4cba40",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "ac8e60b59a767546d1bdb86d68c8e83d",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "cb6c0c6efad034b53fe1a8f930f2ce81",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "8e4b7a2ae0cd444e818789ac919413d1",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "3ec4aadf128132116fc5479a46bd1f71",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "2ea511bd7f4bf34ec1651cee263f3d11",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "14c2cf0812e3022391caffd9409d0650",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "d9dd043cc3518ef0d02ceb302dfa71e1",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "67c207425afc5023cea9740e3bd459c3",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "fbfab4ba2a24a212d4f3b22a259ae3f8",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "7219b9034f14c5b769818b80135ea61b",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "bb47df20836a1f0466f785b1458d7f48",
|
||||||
"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": "ad347097a38e0d7ede9eb6dec6a80ee9",
|
"src/ballistica/base/mgen/pyembed/binding_base.inc": "ad347097a38e0d7ede9eb6dec6a80ee9",
|
||||||
|
|||||||
3
.idea/dictionaries/ericf.xml
generated
3
.idea/dictionaries/ericf.xml
generated
@ -1622,6 +1622,7 @@
|
|||||||
<w>linearstep</w>
|
<w>linearstep</w>
|
||||||
<w>linebegin</w>
|
<w>linebegin</w>
|
||||||
<w>linebits</w>
|
<w>linebits</w>
|
||||||
|
<w>lineend</w>
|
||||||
<w>lineheight</w>
|
<w>lineheight</w>
|
||||||
<w>linemax</w>
|
<w>linemax</w>
|
||||||
<w>lineno</w>
|
<w>lineno</w>
|
||||||
@ -2777,6 +2778,7 @@
|
|||||||
<w>sslcontext</w>
|
<w>sslcontext</w>
|
||||||
<w>sslproto</w>
|
<w>sslproto</w>
|
||||||
<w>ssval</w>
|
<w>ssval</w>
|
||||||
|
<w>stacklevel</w>
|
||||||
<w>stackstr</w>
|
<w>stackstr</w>
|
||||||
<w>stager</w>
|
<w>stager</w>
|
||||||
<w>standin</w>
|
<w>standin</w>
|
||||||
@ -2857,6 +2859,7 @@
|
|||||||
<w>successmsg</w>
|
<w>successmsg</w>
|
||||||
<w>suiciding</w>
|
<w>suiciding</w>
|
||||||
<w>sunau</w>
|
<w>sunau</w>
|
||||||
|
<w>suppressions</w>
|
||||||
<w>suter</w>
|
<w>suter</w>
|
||||||
<w>sval</w>
|
<w>sval</w>
|
||||||
<w>svalin</w>
|
<w>svalin</w>
|
||||||
|
|||||||
34
CHANGELOG.md
34
CHANGELOG.md
@ -1,4 +1,32 @@
|
|||||||
### 1.7.26 (build 21256, api 8, 2023-08-25)
|
### 1.7.27 (build 21269, api 8, 2023-08-30)
|
||||||
|
|
||||||
|
- Fixed a rare crash that could occur if the app shuts down while a background
|
||||||
|
thread is making a web request. The app will now try to wait for any such
|
||||||
|
attempts to complete.
|
||||||
|
- Added `babase.app.env` which is a type-friendly object containing various
|
||||||
|
environment/runtime values. Values directly under `app` such as
|
||||||
|
`babase.app.debug_build` will either be consolidated here or moved to classic
|
||||||
|
if they are considered deprecated.
|
||||||
|
- Started using Python's `warnings` module to announce deprecations, and turned
|
||||||
|
on deprecation warnings for the release build (by default in Python they are
|
||||||
|
mostly only on for debug builds). This way, when making minor changes, I can
|
||||||
|
keep old code paths intact for a few versions and warn modders that they
|
||||||
|
should transition to new code paths before the old ones disappear. I'd prefer
|
||||||
|
to avoid incrementing api-version again if at all possible since that is such
|
||||||
|
a dramatic event, so this alternative will hopefully allow gently evolving
|
||||||
|
some things without too much breakage.
|
||||||
|
- Following up on the above two entries, several attributes under `babase.app`
|
||||||
|
have been relocated to `babase.app.env` and the originals have been given
|
||||||
|
deprecation warnings and will disappear sometime soon. This includes
|
||||||
|
`build_number`, `device_name`, `config_file_path`, `version`, `debug_build`,
|
||||||
|
`test_build`, `data_directory`, `python_directory_user`,
|
||||||
|
`python_directory_app`, `python_directory_app_site`, `api_version`.
|
||||||
|
- Reverting the Android keyboard changes from 1.7.26, as I've received a few
|
||||||
|
reports of bluetooth game controllers now thinking they are keyboards. I'm
|
||||||
|
thinking I'll have to bite the bullet and implement something that asks the
|
||||||
|
user what the thing is to solve cases like that.
|
||||||
|
|
||||||
|
### 1.7.26 (build 21259, api 8, 2023-08-29)
|
||||||
|
|
||||||
- Android should now be better at detecting hardware keyboards (you will see
|
- Android should now be better at detecting hardware keyboards (you will see
|
||||||
'Configure Keyboard' and 'Configure Keyboard P2' buttons under
|
'Configure Keyboard' and 'Configure Keyboard P2' buttons under
|
||||||
@ -30,9 +58,9 @@
|
|||||||
should be more consistent use of the 'Quit?' confirm window. Please holler if
|
should be more consistent use of the 'Quit?' confirm window. Please holler if
|
||||||
you see any odd behavior when trying to quit the app.
|
you see any odd behavior when trying to quit the app.
|
||||||
- Unix TERM signal now triggers graceful app shutdown.
|
- Unix TERM signal now triggers graceful app shutdown.
|
||||||
- Added `ba.app.add_shutdown_task()` to register coroutines to be run as part of
|
- Added `app.add_shutdown_task()` to register coroutines to be run as part of
|
||||||
shutdown.
|
shutdown.
|
||||||
- Removed `babase.app.iircade_mode`. RIP iiRcade :(.
|
- Removed `app.iircade_mode`. RIP iiRcade :(.
|
||||||
- Changed `AppState.INITIAL` to `AppState.NOT_RUNNING`, added a
|
- Changed `AppState.INITIAL` to `AppState.NOT_RUNNING`, added a
|
||||||
`AppState.NATIVE_BOOTSTRAPPING`, and changed `AppState.LAUNCHING` to
|
`AppState.NATIVE_BOOTSTRAPPING`, and changed `AppState.LAUNCHING` to
|
||||||
`AppState.INITING`. These better describe what the app is actually doing while
|
`AppState.INITING`. These better describe what the app is actually doing while
|
||||||
|
|||||||
3
ballisticakit-cmake/.idea/dictionaries/ericf.xml
generated
3
ballisticakit-cmake/.idea/dictionaries/ericf.xml
generated
@ -960,6 +960,7 @@
|
|||||||
<w>linearsize</w>
|
<w>linearsize</w>
|
||||||
<w>linearstep</w>
|
<w>linearstep</w>
|
||||||
<w>linebegin</w>
|
<w>linebegin</w>
|
||||||
|
<w>lineend</w>
|
||||||
<w>linemax</w>
|
<w>linemax</w>
|
||||||
<w>linestart</w>
|
<w>linestart</w>
|
||||||
<w>linestripped</w>
|
<w>linestripped</w>
|
||||||
@ -1643,6 +1644,7 @@
|
|||||||
<w>sssssssi</w>
|
<w>sssssssi</w>
|
||||||
<w>ssssssssssss</w>
|
<w>ssssssssssss</w>
|
||||||
<w>ssval</w>
|
<w>ssval</w>
|
||||||
|
<w>stacklevel</w>
|
||||||
<w>stager</w>
|
<w>stager</w>
|
||||||
<w>standin</w>
|
<w>standin</w>
|
||||||
<w>startedptr</w>
|
<w>startedptr</w>
|
||||||
@ -1690,6 +1692,7 @@
|
|||||||
<w>subsys</w>
|
<w>subsys</w>
|
||||||
<w>subtypestr</w>
|
<w>subtypestr</w>
|
||||||
<w>successmsg</w>
|
<w>successmsg</w>
|
||||||
|
<w>suppressions</w>
|
||||||
<w>sval</w>
|
<w>sval</w>
|
||||||
<w>swidth</w>
|
<w>swidth</w>
|
||||||
<w>swiftc</w>
|
<w>swiftc</w>
|
||||||
|
|||||||
@ -393,6 +393,8 @@ set(BALLISTICA_SOURCES
|
|||||||
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_context_ref.h
|
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_context_ref.h
|
||||||
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_display_timer.cc
|
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_display_timer.cc
|
||||||
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_display_timer.h
|
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_display_timer.h
|
||||||
|
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_env.cc
|
||||||
|
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_env.h
|
||||||
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_feature_set_data.cc
|
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_feature_set_data.cc
|
||||||
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_feature_set_data.h
|
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_feature_set_data.h
|
||||||
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_simple_sound.cc
|
${BA_SRC_ROOT}/ballistica/base/python/class/python_class_simple_sound.cc
|
||||||
|
|||||||
@ -379,6 +379,8 @@
|
|||||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_context_ref.h" />
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_context_ref.h" />
|
||||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_display_timer.cc" />
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_display_timer.cc" />
|
||||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h" />
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h" />
|
||||||
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_env.cc" />
|
||||||
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_env.h" />
|
||||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.cc" />
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.cc" />
|
||||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.h" />
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.h" />
|
||||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_simple_sound.cc" />
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_simple_sound.cc" />
|
||||||
|
|||||||
@ -571,6 +571,12 @@
|
|||||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h">
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h">
|
||||||
<Filter>ballistica\base\python\class</Filter>
|
<Filter>ballistica\base\python\class</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_env.cc">
|
||||||
|
<Filter>ballistica\base\python\class</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_env.h">
|
||||||
|
<Filter>ballistica\base\python\class</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.cc">
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.cc">
|
||||||
<Filter>ballistica\base\python\class</Filter>
|
<Filter>ballistica\base\python\class</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
@ -374,6 +374,8 @@
|
|||||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_context_ref.h" />
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_context_ref.h" />
|
||||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_display_timer.cc" />
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_display_timer.cc" />
|
||||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h" />
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h" />
|
||||||
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_env.cc" />
|
||||||
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_env.h" />
|
||||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.cc" />
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.cc" />
|
||||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.h" />
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.h" />
|
||||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_simple_sound.cc" />
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_simple_sound.cc" />
|
||||||
|
|||||||
@ -571,6 +571,12 @@
|
|||||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h">
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h">
|
||||||
<Filter>ballistica\base\python\class</Filter>
|
<Filter>ballistica\base\python\class</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_env.cc">
|
||||||
|
<Filter>ballistica\base\python\class</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_env.h">
|
||||||
|
<Filter>ballistica\base\python\class</Filter>
|
||||||
|
</ClInclude>
|
||||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.cc">
|
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_feature_set_data.cc">
|
||||||
<Filter>ballistica\base\python\class</Filter>
|
<Filter>ballistica\base\python\class</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
@ -39,6 +39,7 @@ from _babase import (
|
|||||||
DisplayTimer,
|
DisplayTimer,
|
||||||
do_once,
|
do_once,
|
||||||
env,
|
env,
|
||||||
|
Env,
|
||||||
fade_screen,
|
fade_screen,
|
||||||
fatal_error,
|
fatal_error,
|
||||||
get_display_resolution,
|
get_display_resolution,
|
||||||
@ -48,8 +49,10 @@ from _babase import (
|
|||||||
get_replays_dir,
|
get_replays_dir,
|
||||||
get_string_height,
|
get_string_height,
|
||||||
get_string_width,
|
get_string_width,
|
||||||
|
get_v1_cloud_log_file_path,
|
||||||
getsimplesound,
|
getsimplesound,
|
||||||
has_gamma_control,
|
has_gamma_control,
|
||||||
|
has_user_run_commands,
|
||||||
have_chars,
|
have_chars,
|
||||||
have_permission,
|
have_permission,
|
||||||
in_logic_thread,
|
in_logic_thread,
|
||||||
@ -83,6 +86,9 @@ from _babase import (
|
|||||||
set_thread_name,
|
set_thread_name,
|
||||||
set_ui_input_device,
|
set_ui_input_device,
|
||||||
show_progress_bar,
|
show_progress_bar,
|
||||||
|
shutdown_suppress_begin,
|
||||||
|
shutdown_suppress_end,
|
||||||
|
shutdown_suppress_count,
|
||||||
SimpleSound,
|
SimpleSound,
|
||||||
unlock_all_input,
|
unlock_all_input,
|
||||||
user_agent_string,
|
user_agent_string,
|
||||||
@ -96,6 +102,7 @@ from babase._appconfig import commit_app_config
|
|||||||
from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec
|
from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec
|
||||||
from babase._appmode import AppMode
|
from babase._appmode import AppMode
|
||||||
from babase._appsubsystem import AppSubsystem
|
from babase._appsubsystem import AppSubsystem
|
||||||
|
from babase._appmodeselector import AppModeSelector
|
||||||
from babase._appconfig import AppConfig
|
from babase._appconfig import AppConfig
|
||||||
from babase._apputils import (
|
from babase._apputils import (
|
||||||
handle_leftover_v1_cloud_log_file,
|
handle_leftover_v1_cloud_log_file,
|
||||||
@ -175,6 +182,7 @@ __all__ = [
|
|||||||
'AppMode',
|
'AppMode',
|
||||||
'appname',
|
'appname',
|
||||||
'appnameupper',
|
'appnameupper',
|
||||||
|
'AppModeSelector',
|
||||||
'AppSubsystem',
|
'AppSubsystem',
|
||||||
'apptime',
|
'apptime',
|
||||||
'AppTime',
|
'AppTime',
|
||||||
@ -200,6 +208,7 @@ __all__ = [
|
|||||||
'do_once',
|
'do_once',
|
||||||
'EmptyAppMode',
|
'EmptyAppMode',
|
||||||
'env',
|
'env',
|
||||||
|
'Env',
|
||||||
'Existable',
|
'Existable',
|
||||||
'existing',
|
'existing',
|
||||||
'fade_screen',
|
'fade_screen',
|
||||||
@ -214,11 +223,13 @@ __all__ = [
|
|||||||
'get_replays_dir',
|
'get_replays_dir',
|
||||||
'get_string_height',
|
'get_string_height',
|
||||||
'get_string_width',
|
'get_string_width',
|
||||||
|
'get_v1_cloud_log_file_path',
|
||||||
'get_type_name',
|
'get_type_name',
|
||||||
'getclass',
|
'getclass',
|
||||||
'getsimplesound',
|
'getsimplesound',
|
||||||
'handle_leftover_v1_cloud_log_file',
|
'handle_leftover_v1_cloud_log_file',
|
||||||
'has_gamma_control',
|
'has_gamma_control',
|
||||||
|
'has_user_run_commands',
|
||||||
'have_chars',
|
'have_chars',
|
||||||
'have_permission',
|
'have_permission',
|
||||||
'in_logic_thread',
|
'in_logic_thread',
|
||||||
@ -277,6 +288,9 @@ __all__ = [
|
|||||||
'set_thread_name',
|
'set_thread_name',
|
||||||
'set_ui_input_device',
|
'set_ui_input_device',
|
||||||
'show_progress_bar',
|
'show_progress_bar',
|
||||||
|
'shutdown_suppress_begin',
|
||||||
|
'shutdown_suppress_end',
|
||||||
|
'shutdown_suppress_count',
|
||||||
'SimpleSound',
|
'SimpleSound',
|
||||||
'SpecialChar',
|
'SpecialChar',
|
||||||
'storagename',
|
'storagename',
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
# Released under the MIT License. See LICENSE for details.
|
# Released under the MIT License. See LICENSE for details.
|
||||||
#
|
#
|
||||||
"""Functionality related to the high level state of the app."""
|
"""Functionality related to the high level state of the app."""
|
||||||
|
# pylint: disable=too-many-lines
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
|
import warnings
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from functools import cached_property
|
from functools import cached_property
|
||||||
@ -55,6 +58,7 @@ class App:
|
|||||||
|
|
||||||
plugins: PluginSubsystem
|
plugins: PluginSubsystem
|
||||||
lang: LanguageSubsystem
|
lang: LanguageSubsystem
|
||||||
|
env: babase.Env
|
||||||
|
|
||||||
health_monitor: AppHealthMonitor
|
health_monitor: AppHealthMonitor
|
||||||
|
|
||||||
@ -68,173 +72,72 @@ class App:
|
|||||||
|
|
||||||
# The app has not yet begun starting and should not be used in
|
# The app has not yet begun starting and should not be used in
|
||||||
# any way.
|
# any way.
|
||||||
NOT_RUNNING = 'not_running'
|
NOT_RUNNING = 0
|
||||||
|
|
||||||
# The native layer is spinning up its machinery (screens,
|
# The native layer is spinning up its machinery (screens,
|
||||||
# renderers, etc.). Nothing should happen in the Python layer
|
# renderers, etc.). Nothing should happen in the Python layer
|
||||||
# until this completes.
|
# until this completes.
|
||||||
NATIVE_BOOTSTRAPPING = 'native_bootstrapping'
|
NATIVE_BOOTSTRAPPING = 1
|
||||||
|
|
||||||
# Python app subsystems are being inited but should not yet
|
# Python app subsystems are being inited but should not yet
|
||||||
# interact or do any work.
|
# interact or do any work.
|
||||||
INITING = 'initing'
|
INITING = 2
|
||||||
|
|
||||||
# Python app subsystems are inited and interacting, but the app
|
# Python app subsystems are inited and interacting, but the app
|
||||||
# has not yet embarked on a high level course of action. It is
|
# has not yet embarked on a high level course of action. It is
|
||||||
# doing initial account logins, workspace & asset downloads,
|
# doing initial account logins, workspace & asset downloads,
|
||||||
# etc.
|
# etc.
|
||||||
LOADING = 'loading'
|
LOADING = 3
|
||||||
|
|
||||||
# All pieces are in place and the app is now doing its thing.
|
# All pieces are in place and the app is now doing its thing.
|
||||||
RUNNING = 'running'
|
RUNNING = 4
|
||||||
|
|
||||||
# The app is backgrounded or otherwise suspended.
|
# The app is backgrounded or otherwise suspended.
|
||||||
PAUSED = 'paused'
|
PAUSED = 5
|
||||||
|
|
||||||
# The app is shutting down.
|
# The app is shutting down.
|
||||||
SHUTTING_DOWN = 'shutting_down'
|
SHUTTING_DOWN = 6
|
||||||
|
|
||||||
@property
|
class DefaultAppModeSelector(AppModeSelector):
|
||||||
def aioloop(self) -> asyncio.AbstractEventLoop:
|
"""Decides which app modes to use to handle intents.
|
||||||
"""The logic thread's asyncio event loop.
|
|
||||||
|
|
||||||
This allow async tasks to be run in the logic thread.
|
The behavior here is generated by the project updater based on
|
||||||
Note that, at this time, the asyncio loop is encapsulated
|
the feature-sets present in the project. Spinoff projects can
|
||||||
and explicitly stepped by the engine's logic thread loop and
|
also inject their own behavior by replacing the text
|
||||||
thus things like asyncio.get_running_loop() will not return this
|
'__GOOD_PLACE_FOR_CUSTOM_SPINOFF_LOGIC__' with their own code
|
||||||
loop from most places in the logic thread; only from within a
|
through spinoff filtering.
|
||||||
task explicitly created in this loop.
|
|
||||||
|
It is also possible to modify mode selection behavior by writing
|
||||||
|
a custom AppModeSelector class and replacing app.mode_selector
|
||||||
|
with an instance of it. This is a good way to go if you are
|
||||||
|
modifying app behavior with a plugin instead of in a spinoff
|
||||||
|
project.
|
||||||
"""
|
"""
|
||||||
assert self._aioloop is not None
|
|
||||||
return self._aioloop
|
|
||||||
|
|
||||||
@property
|
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode]:
|
||||||
def build_number(self) -> int:
|
# pylint: disable=cyclic-import
|
||||||
"""Integer build number.
|
|
||||||
|
|
||||||
This value increases by at least 1 with each release of the game.
|
# __GOOD_PLACE_FOR_CUSTOM_SPINOFF_LOGIC__
|
||||||
It is independent of the human readable babase.App.version string.
|
|
||||||
"""
|
|
||||||
assert isinstance(self._env['build_number'], int)
|
|
||||||
return self._env['build_number']
|
|
||||||
|
|
||||||
@property
|
# __DEFAULT_APP_MODE_SELECTION_BEGIN__
|
||||||
def device_name(self) -> str:
|
# This section generated by batools.appmodule; do not edit.
|
||||||
"""Name of the device running the game."""
|
|
||||||
assert isinstance(self._env['device_name'], str)
|
|
||||||
return self._env['device_name']
|
|
||||||
|
|
||||||
@property
|
# Hmm; need to think about how we auto-construct this; how
|
||||||
def config_file_path(self) -> str:
|
# should we determine which app modes to check and in what
|
||||||
"""Where the game's config file is stored on disk."""
|
# order?
|
||||||
assert isinstance(self._env['config_file_path'], str)
|
import bascenev1
|
||||||
return self._env['config_file_path']
|
|
||||||
|
|
||||||
@property
|
import babase
|
||||||
def version(self) -> str:
|
|
||||||
"""Human-readable version string; something like '1.3.24'.
|
|
||||||
|
|
||||||
This should not be interpreted as a number; it may contain
|
if bascenev1.SceneV1AppMode.supports_intent(intent):
|
||||||
string elements such as 'alpha', 'beta', 'test', etc.
|
return bascenev1.SceneV1AppMode
|
||||||
If a numeric version is needed, use 'babase.App.build_number'.
|
|
||||||
"""
|
|
||||||
assert isinstance(self._env['version'], str)
|
|
||||||
return self._env['version']
|
|
||||||
|
|
||||||
@property
|
if babase.EmptyAppMode.supports_intent(intent):
|
||||||
def debug_build(self) -> bool:
|
return babase.EmptyAppMode
|
||||||
"""Whether the app was compiled in debug mode.
|
|
||||||
|
|
||||||
Debug builds generally run substantially slower than non-debug
|
raise RuntimeError(f'No handler found for intent {type(intent)}.')
|
||||||
builds due to compiler optimizations being disabled and extra
|
|
||||||
checks being run.
|
|
||||||
"""
|
|
||||||
assert isinstance(self._env['debug_build'], bool)
|
|
||||||
return self._env['debug_build']
|
|
||||||
|
|
||||||
@property
|
# __DEFAULT_APP_MODE_SELECTION_END__
|
||||||
def test_build(self) -> bool:
|
|
||||||
"""Whether the game was compiled in test mode.
|
|
||||||
|
|
||||||
Test mode enables extra checks and features that are useful for
|
|
||||||
release testing but which do not slow the game down significantly.
|
|
||||||
"""
|
|
||||||
assert isinstance(self._env['test_build'], bool)
|
|
||||||
return self._env['test_build']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def data_directory(self) -> str:
|
|
||||||
"""Path where static app data lives."""
|
|
||||||
assert isinstance(self._env['data_directory'], str)
|
|
||||||
return self._env['data_directory']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def python_directory_user(self) -> str | None:
|
|
||||||
"""Path where ballistica expects its user scripts (mods) to live.
|
|
||||||
|
|
||||||
Be aware that this value may be None if ballistica is running in
|
|
||||||
a non-standard environment, and that python-path modifications may
|
|
||||||
cause modules to be loaded from other locations.
|
|
||||||
"""
|
|
||||||
assert isinstance(self._env['python_directory_user'], (str, type(None)))
|
|
||||||
return self._env['python_directory_user']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def python_directory_app(self) -> str | None:
|
|
||||||
"""Path where ballistica expects its bundled modules to live.
|
|
||||||
|
|
||||||
Be aware that this value may be None if ballistica is running in
|
|
||||||
a non-standard environment, and that python-path modifications may
|
|
||||||
cause modules to be loaded from other locations.
|
|
||||||
"""
|
|
||||||
assert isinstance(self._env['python_directory_app'], (str, type(None)))
|
|
||||||
return self._env['python_directory_app']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def python_directory_app_site(self) -> str | None:
|
|
||||||
"""Path where ballistica expects its bundled pip modules to live.
|
|
||||||
|
|
||||||
Be aware that this value may be None if ballistica is running in
|
|
||||||
a non-standard environment, and that python-path modifications may
|
|
||||||
cause modules to be loaded from other locations.
|
|
||||||
"""
|
|
||||||
assert isinstance(
|
|
||||||
self._env['python_directory_app_site'], (str, type(None))
|
|
||||||
)
|
|
||||||
return self._env['python_directory_app_site']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def config(self) -> babase.AppConfig:
|
|
||||||
"""The babase.AppConfig instance representing the app's config state."""
|
|
||||||
assert self._config is not None
|
|
||||||
return self._config
|
|
||||||
|
|
||||||
@property
|
|
||||||
def api_version(self) -> int:
|
|
||||||
"""The app's api version.
|
|
||||||
|
|
||||||
Only Python modules and packages associated with the current API
|
|
||||||
version number will be detected by the game (see the ba_meta tag).
|
|
||||||
This value will change whenever substantial backward-incompatible
|
|
||||||
changes are introduced to ballistica APIs. When that happens,
|
|
||||||
modules/packages should be updated accordingly and set to target
|
|
||||||
the newer API version number.
|
|
||||||
"""
|
|
||||||
from babase._meta import CURRENT_API_VERSION
|
|
||||||
|
|
||||||
return CURRENT_API_VERSION
|
|
||||||
|
|
||||||
@property
|
|
||||||
def on_tv(self) -> bool:
|
|
||||||
"""Whether the game is currently running on a TV."""
|
|
||||||
assert isinstance(self._env['on_tv'], bool)
|
|
||||||
return self._env['on_tv']
|
|
||||||
|
|
||||||
@property
|
|
||||||
def vr_mode(self) -> bool:
|
|
||||||
"""Whether the game is currently running in VR."""
|
|
||||||
assert isinstance(self._env['vr_mode'], bool)
|
|
||||||
return self._env['vr_mode']
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
"""(internal)
|
"""(internal)
|
||||||
@ -248,6 +151,8 @@ class App:
|
|||||||
|
|
||||||
self.state = self.State.NOT_RUNNING
|
self.state = self.State.NOT_RUNNING
|
||||||
|
|
||||||
|
self.env: babase.Env = _babase.Env()
|
||||||
|
|
||||||
self._subsystems: list[AppSubsystem] = []
|
self._subsystems: list[AppSubsystem] = []
|
||||||
|
|
||||||
self._native_bootstrapping_completed = False
|
self._native_bootstrapping_completed = False
|
||||||
@ -263,12 +168,11 @@ class App:
|
|||||||
self._subsystem_registration_ended = False
|
self._subsystem_registration_ended = False
|
||||||
self._pending_apply_app_config = False
|
self._pending_apply_app_config = False
|
||||||
|
|
||||||
# Config.
|
self.config_file_healthy: bool = False
|
||||||
self.config_file_healthy = False
|
|
||||||
|
|
||||||
# This is incremented any time the app is
|
# This is incremented any time the app is backgrounded or
|
||||||
# backgrounded/foregrounded; can be a simple way to determine if
|
# foregrounded; can be a simple way to determine if network data
|
||||||
# network data should be refreshed/etc.
|
# should be refreshed/etc.
|
||||||
self.fg_state = 0
|
self.fg_state = 0
|
||||||
|
|
||||||
self._aioloop: asyncio.AbstractEventLoop | None = None
|
self._aioloop: asyncio.AbstractEventLoop | None = None
|
||||||
@ -294,20 +198,25 @@ class App:
|
|||||||
self._config: babase.AppConfig | None = None
|
self._config: babase.AppConfig | None = None
|
||||||
|
|
||||||
self.components = AppComponentSubsystem()
|
self.components = AppComponentSubsystem()
|
||||||
|
|
||||||
|
# Testing this.
|
||||||
self.meta = MetadataSubsystem()
|
self.meta = MetadataSubsystem()
|
||||||
|
|
||||||
self.net = NetworkSubsystem()
|
self.net = NetworkSubsystem()
|
||||||
self.workspaces = WorkspaceSubsystem()
|
self.workspaces = WorkspaceSubsystem()
|
||||||
self._pending_intent: AppIntent | None = None
|
self._pending_intent: AppIntent | None = None
|
||||||
self._intent: AppIntent | None = None
|
self._intent: AppIntent | None = None
|
||||||
self._mode: AppMode | None = None
|
self._mode: AppMode | None = None
|
||||||
self._shutdown_task: asyncio.Task[None] | None = None
|
self._shutdown_task: asyncio.Task[None] | None = None
|
||||||
self._shutdown_tasks: list[Coroutine[None, None, None]] = []
|
self._shutdown_tasks: list[Coroutine[None, None, None]] = [
|
||||||
|
self._wait_for_shutdown_suppressions()
|
||||||
|
]
|
||||||
|
|
||||||
# Controls which app-modes we use for handling given
|
# Controls which app-modes we use for handling given
|
||||||
# app-intents. Plugins can override this to change high level
|
# app-intents. Plugins can override this to change high level
|
||||||
# app behavior and spinoff projects can change the default
|
# app behavior and spinoff projects can change the default
|
||||||
# implementation for the same effect.
|
# implementation for the same effect.
|
||||||
self.mode_selector: AppModeSelector | None = None
|
self.mode_selector: babase.AppModeSelector | None = None
|
||||||
|
|
||||||
self._asyncio_timer: babase.AppTimer | None = None
|
self._asyncio_timer: babase.AppTimer | None = None
|
||||||
|
|
||||||
@ -324,35 +233,25 @@ class App:
|
|||||||
self.lang = LanguageSubsystem()
|
self.lang = LanguageSubsystem()
|
||||||
self.plugins = PluginSubsystem()
|
self.plugins = PluginSubsystem()
|
||||||
|
|
||||||
def register_subsystem(self, subsystem: AppSubsystem) -> None:
|
@property
|
||||||
"""Called by the AppSubsystem class. Do not use directly."""
|
def aioloop(self) -> asyncio.AbstractEventLoop:
|
||||||
|
"""The logic thread's asyncio event loop.
|
||||||
|
|
||||||
# We only allow registering new subsystems if we've not yet
|
This allow async tasks to be run in the logic thread.
|
||||||
# reached the 'running' state. This ensures that all subsystems
|
Note that, at this time, the asyncio loop is encapsulated
|
||||||
# receive a consistent set of callbacks starting with
|
and explicitly stepped by the engine's logic thread loop and
|
||||||
# on_app_running().
|
thus things like asyncio.get_running_loop() will not return this
|
||||||
if self._subsystem_registration_ended:
|
loop from most places in the logic thread; only from within a
|
||||||
raise RuntimeError(
|
task explicitly created in this loop.
|
||||||
'Subsystems can no longer be registered at this point.'
|
|
||||||
)
|
|
||||||
self._subsystems.append(subsystem)
|
|
||||||
|
|
||||||
def _threadpool_no_wait_done(self, fut: Future) -> None:
|
|
||||||
try:
|
|
||||||
fut.result()
|
|
||||||
except Exception:
|
|
||||||
logging.exception(
|
|
||||||
'Error in work submitted via threadpool_submit_no_wait()'
|
|
||||||
)
|
|
||||||
|
|
||||||
def threadpool_submit_no_wait(self, call: Callable[[], Any]) -> None:
|
|
||||||
"""Submit work to our threadpool and log any errors.
|
|
||||||
|
|
||||||
Use this when you want to run something asynchronously but don't
|
|
||||||
intend to call result() on it to handle errors/etc.
|
|
||||||
"""
|
"""
|
||||||
fut = self.threadpool.submit(call)
|
assert self._aioloop is not None
|
||||||
fut.add_done_callback(self._threadpool_no_wait_done)
|
return self._aioloop
|
||||||
|
|
||||||
|
@property
|
||||||
|
def config(self) -> babase.AppConfig:
|
||||||
|
"""The babase.AppConfig instance representing the app's config state."""
|
||||||
|
assert self._config is not None
|
||||||
|
return self._config
|
||||||
|
|
||||||
# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__
|
# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__
|
||||||
# This section generated by batools.appmodule; do not edit.
|
# This section generated by batools.appmodule; do not edit.
|
||||||
@ -398,6 +297,48 @@ class App:
|
|||||||
|
|
||||||
# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_END__
|
# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_END__
|
||||||
|
|
||||||
|
def register_subsystem(self, subsystem: AppSubsystem) -> None:
|
||||||
|
"""Called by the AppSubsystem class. Do not use directly."""
|
||||||
|
|
||||||
|
# We only allow registering new subsystems if we've not yet
|
||||||
|
# reached the 'running' state. This ensures that all subsystems
|
||||||
|
# receive a consistent set of callbacks starting with
|
||||||
|
# on_app_running().
|
||||||
|
if self._subsystem_registration_ended:
|
||||||
|
raise RuntimeError(
|
||||||
|
'Subsystems can no longer be registered at this point.'
|
||||||
|
)
|
||||||
|
self._subsystems.append(subsystem)
|
||||||
|
|
||||||
|
def add_shutdown_task(self, coro: Coroutine[None, None, None]) -> None:
|
||||||
|
"""Add a task to be run on app shutdown.
|
||||||
|
|
||||||
|
Note that tasks will be killed after
|
||||||
|
App.SHUTDOWN_TASK_TIMEOUT_SECONDS if they are still running.
|
||||||
|
"""
|
||||||
|
if self.state is self.State.SHUTTING_DOWN:
|
||||||
|
raise RuntimeError(
|
||||||
|
'Cannot add shutdown tasks with state SHUTTING_DOWN.'
|
||||||
|
)
|
||||||
|
self._shutdown_tasks.append(coro)
|
||||||
|
|
||||||
|
def run(self) -> None:
|
||||||
|
"""Run the app to completion.
|
||||||
|
|
||||||
|
Note that this only works on platforms where Ballistica
|
||||||
|
manages its own event loop.
|
||||||
|
"""
|
||||||
|
_babase.run_app()
|
||||||
|
|
||||||
|
def threadpool_submit_no_wait(self, call: Callable[[], Any]) -> None:
|
||||||
|
"""Submit work to our threadpool and log any errors.
|
||||||
|
|
||||||
|
Use this when you want to run something asynchronously but don't
|
||||||
|
intend to call result() on it to handle errors/etc.
|
||||||
|
"""
|
||||||
|
fut = self.threadpool.submit(call)
|
||||||
|
fut.add_done_callback(self._threadpool_no_wait_done)
|
||||||
|
|
||||||
def set_intent(self, intent: AppIntent) -> None:
|
def set_intent(self, intent: AppIntent) -> None:
|
||||||
"""Set the intent for the app.
|
"""Set the intent for the app.
|
||||||
|
|
||||||
@ -417,6 +358,92 @@ class App:
|
|||||||
# since it may block for a moment to load modules/etc.
|
# since it may block for a moment to load modules/etc.
|
||||||
self.threadpool_submit_no_wait(tpartial(self._set_intent, intent))
|
self.threadpool_submit_no_wait(tpartial(self._set_intent, intent))
|
||||||
|
|
||||||
|
def push_apply_app_config(self) -> None:
|
||||||
|
"""Internal. Use app.config.apply() to apply app config changes."""
|
||||||
|
# To be safe, let's run this by itself in the event loop.
|
||||||
|
# This avoids potential trouble if this gets called mid-draw or
|
||||||
|
# something like that.
|
||||||
|
self._pending_apply_app_config = True
|
||||||
|
_babase.pushcall(self._apply_app_config, raw=True)
|
||||||
|
|
||||||
|
def on_native_start(self) -> None:
|
||||||
|
"""Called by the native layer when the app is being started."""
|
||||||
|
assert _babase.in_logic_thread()
|
||||||
|
assert not self._native_start_called
|
||||||
|
self._native_start_called = True
|
||||||
|
self._update_state()
|
||||||
|
|
||||||
|
def on_native_bootstrapping_complete(self) -> None:
|
||||||
|
"""Called by the native layer once its ready to rock."""
|
||||||
|
assert _babase.in_logic_thread()
|
||||||
|
assert not self._native_bootstrapping_completed
|
||||||
|
self._native_bootstrapping_completed = True
|
||||||
|
self._update_state()
|
||||||
|
|
||||||
|
def on_native_pause(self) -> None:
|
||||||
|
"""Called by the native layer when the app pauses."""
|
||||||
|
assert _babase.in_logic_thread()
|
||||||
|
assert not self._native_paused # Should avoid redundant calls.
|
||||||
|
self._native_paused = True
|
||||||
|
self._update_state()
|
||||||
|
|
||||||
|
def on_native_resume(self) -> None:
|
||||||
|
"""Called by the native layer when the app resumes."""
|
||||||
|
assert _babase.in_logic_thread()
|
||||||
|
assert self._native_paused # Should avoid redundant calls.
|
||||||
|
self._native_paused = False
|
||||||
|
self._update_state()
|
||||||
|
|
||||||
|
def on_native_shutdown(self) -> None:
|
||||||
|
"""Called by the native layer when the app starts shutting down."""
|
||||||
|
assert _babase.in_logic_thread()
|
||||||
|
self._native_shutdown_called = True
|
||||||
|
self._update_state()
|
||||||
|
|
||||||
|
def read_config(self) -> None:
|
||||||
|
"""(internal)"""
|
||||||
|
from babase._appconfig import read_app_config
|
||||||
|
|
||||||
|
self._config, self.config_file_healthy = read_app_config()
|
||||||
|
|
||||||
|
def handle_deep_link(self, url: str) -> None:
|
||||||
|
"""Handle a deep link URL."""
|
||||||
|
from babase._language import Lstr
|
||||||
|
|
||||||
|
assert _babase.in_logic_thread()
|
||||||
|
|
||||||
|
appname = _babase.appname()
|
||||||
|
if url.startswith(f'{appname}://code/'):
|
||||||
|
code = url.replace(f'{appname}://code/', '')
|
||||||
|
if self.classic is not None:
|
||||||
|
self.classic.accounts.add_pending_promo_code(code)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
_babase.screenmessage(
|
||||||
|
Lstr(resource='errorText'), color=(1, 0, 0)
|
||||||
|
)
|
||||||
|
_babase.getsimplesound('error').play()
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def on_initial_sign_in_complete(self) -> None:
|
||||||
|
"""Called when initial sign-in (or lack thereof) completes.
|
||||||
|
|
||||||
|
This normally gets called by the plus subsystem. The
|
||||||
|
initial-sign-in process may include tasks such as syncing
|
||||||
|
account workspaces or other data so it may take a substantial
|
||||||
|
amount of time.
|
||||||
|
"""
|
||||||
|
assert _babase.in_logic_thread()
|
||||||
|
assert not self._initial_sign_in_completed
|
||||||
|
|
||||||
|
# Tell meta it can start scanning extra stuff that just showed
|
||||||
|
# up (namely account workspaces).
|
||||||
|
self.meta.start_extra_scan()
|
||||||
|
|
||||||
|
self._initial_sign_in_completed = True
|
||||||
|
self._update_state()
|
||||||
|
|
||||||
def _set_intent(self, intent: AppIntent) -> None:
|
def _set_intent(self, intent: AppIntent) -> None:
|
||||||
# This should be running in a bg thread.
|
# This should be running in a bg thread.
|
||||||
assert not _babase.in_logic_thread()
|
assert not _babase.in_logic_thread()
|
||||||
@ -492,14 +519,6 @@ class App:
|
|||||||
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||||
_babase.getsimplesound('error').play()
|
_babase.getsimplesound('error').play()
|
||||||
|
|
||||||
def run(self) -> None:
|
|
||||||
"""Run the app to completion.
|
|
||||||
|
|
||||||
Note that this only works on platforms where Ballistica
|
|
||||||
manages its own event loop.
|
|
||||||
"""
|
|
||||||
_babase.run_app()
|
|
||||||
|
|
||||||
def _on_initing(self) -> None:
|
def _on_initing(self) -> None:
|
||||||
"""Called when the app enters the initing state.
|
"""Called when the app enters the initing state.
|
||||||
|
|
||||||
@ -633,14 +652,6 @@ class App:
|
|||||||
# plugin hasn't already told it to do something.
|
# plugin hasn't already told it to do something.
|
||||||
self.set_intent(AppIntentDefault())
|
self.set_intent(AppIntentDefault())
|
||||||
|
|
||||||
def push_apply_app_config(self) -> None:
|
|
||||||
"""Internal. Use app.config.apply() to apply app config changes."""
|
|
||||||
# To be safe, let's run this by itself in the event loop.
|
|
||||||
# This avoids potential trouble if this gets called mid-draw or
|
|
||||||
# something like that.
|
|
||||||
self._pending_apply_app_config = True
|
|
||||||
_babase.pushcall(self._apply_app_config, raw=True)
|
|
||||||
|
|
||||||
def _apply_app_config(self) -> None:
|
def _apply_app_config(self) -> None:
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
|
|
||||||
@ -667,47 +678,6 @@ class App:
|
|||||||
# Let the native layer do its thing.
|
# Let the native layer do its thing.
|
||||||
_babase.do_apply_app_config()
|
_babase.do_apply_app_config()
|
||||||
|
|
||||||
class DefaultAppModeSelector(AppModeSelector):
|
|
||||||
"""Decides which app modes to use to handle intents.
|
|
||||||
|
|
||||||
The behavior here is generated by the project updater based on
|
|
||||||
the feature-sets present in the project. Spinoff projects can
|
|
||||||
also inject their own behavior by replacing the text
|
|
||||||
'__GOOD_PLACE_FOR_CUSTOM_SPINOFF_LOGIC__' with their own code
|
|
||||||
through spinoff filtering.
|
|
||||||
|
|
||||||
It is also possible to modify mode selection behavior by writing
|
|
||||||
a custom AppModeSelector class and replacing app.mode_selector
|
|
||||||
with an instance of it. This is a good way to go if you are
|
|
||||||
modifying app behavior with a plugin instead of in a spinoff
|
|
||||||
project.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode]:
|
|
||||||
# pylint: disable=cyclic-import
|
|
||||||
|
|
||||||
# __GOOD_PLACE_FOR_CUSTOM_SPINOFF_LOGIC__
|
|
||||||
|
|
||||||
# __DEFAULT_APP_MODE_SELECTION_BEGIN__
|
|
||||||
# This section generated by batools.appmodule; do not edit.
|
|
||||||
|
|
||||||
# Hmm; need to think about how we auto-construct this; how
|
|
||||||
# should we determine which app modes to check and in what
|
|
||||||
# order?
|
|
||||||
import bascenev1
|
|
||||||
|
|
||||||
import babase
|
|
||||||
|
|
||||||
if bascenev1.SceneV1AppMode.supports_intent(intent):
|
|
||||||
return bascenev1.SceneV1AppMode
|
|
||||||
|
|
||||||
if babase.EmptyAppMode.supports_intent(intent):
|
|
||||||
return babase.EmptyAppMode
|
|
||||||
|
|
||||||
raise RuntimeError(f'No handler found for intent {type(intent)}.')
|
|
||||||
|
|
||||||
# __DEFAULT_APP_MODE_SELECTION_END__
|
|
||||||
|
|
||||||
def _update_state(self) -> None:
|
def _update_state(self) -> None:
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
@ -761,18 +731,6 @@ class App:
|
|||||||
if bool(True):
|
if bool(True):
|
||||||
self.state = self.State.NATIVE_BOOTSTRAPPING
|
self.state = self.State.NATIVE_BOOTSTRAPPING
|
||||||
|
|
||||||
def add_shutdown_task(self, coro: Coroutine[None, None, None]) -> None:
|
|
||||||
"""Add a task to be run on app shutdown.
|
|
||||||
|
|
||||||
Note that tasks will be killed after
|
|
||||||
App.SHUTDOWN_TASK_TIMEOUT_SECONDS if they are still running.
|
|
||||||
"""
|
|
||||||
if self.state is self.State.SHUTTING_DOWN:
|
|
||||||
raise RuntimeError(
|
|
||||||
'Cannot add shutdown tasks with state SHUTTING_DOWN.'
|
|
||||||
)
|
|
||||||
self._shutdown_tasks.append(coro)
|
|
||||||
|
|
||||||
async def _shutdown(self) -> None:
|
async def _shutdown(self) -> None:
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
@ -802,40 +760,6 @@ class App:
|
|||||||
except Exception:
|
except Exception:
|
||||||
logging.exception('Error in shutdown task.')
|
logging.exception('Error in shutdown task.')
|
||||||
|
|
||||||
def on_native_start(self) -> None:
|
|
||||||
"""Called by the native layer when the app is being started."""
|
|
||||||
assert _babase.in_logic_thread()
|
|
||||||
assert not self._native_start_called
|
|
||||||
self._native_start_called = True
|
|
||||||
self._update_state()
|
|
||||||
|
|
||||||
def on_native_bootstrapping_complete(self) -> None:
|
|
||||||
"""Called by the native layer once its ready to rock."""
|
|
||||||
assert _babase.in_logic_thread()
|
|
||||||
assert not self._native_bootstrapping_completed
|
|
||||||
self._native_bootstrapping_completed = True
|
|
||||||
self._update_state()
|
|
||||||
|
|
||||||
def on_native_pause(self) -> None:
|
|
||||||
"""Called by the native layer when the app pauses."""
|
|
||||||
assert _babase.in_logic_thread()
|
|
||||||
assert not self._native_paused # Should avoid redundant calls.
|
|
||||||
self._native_paused = True
|
|
||||||
self._update_state()
|
|
||||||
|
|
||||||
def on_native_resume(self) -> None:
|
|
||||||
"""Called by the native layer when the app resumes."""
|
|
||||||
assert _babase.in_logic_thread()
|
|
||||||
assert self._native_paused # Should avoid redundant calls.
|
|
||||||
self._native_paused = False
|
|
||||||
self._update_state()
|
|
||||||
|
|
||||||
def on_native_shutdown(self) -> None:
|
|
||||||
"""Called by the native layer when the app starts shutting down."""
|
|
||||||
assert _babase.in_logic_thread()
|
|
||||||
self._native_shutdown_called = True
|
|
||||||
self._update_state()
|
|
||||||
|
|
||||||
def _on_pause(self) -> None:
|
def _on_pause(self) -> None:
|
||||||
"""Called when the app goes to a paused state."""
|
"""Called when the app goes to a paused state."""
|
||||||
assert _babase.in_logic_thread()
|
assert _babase.in_logic_thread()
|
||||||
@ -881,46 +805,198 @@ class App:
|
|||||||
assert self._aioloop is not None
|
assert self._aioloop is not None
|
||||||
self._shutdown_task = self._aioloop.create_task(self._shutdown())
|
self._shutdown_task = self._aioloop.create_task(self._shutdown())
|
||||||
|
|
||||||
def read_config(self) -> None:
|
async def _wait_for_shutdown_suppressions(self) -> None:
|
||||||
"""(internal)"""
|
import asyncio
|
||||||
from babase._appconfig import read_app_config
|
|
||||||
|
|
||||||
self._config, self.config_file_healthy = read_app_config()
|
# Spin and wait for anything blocking shutdown to complete.
|
||||||
|
_babase.lifecyclelog('shutdown-suppress wait begin')
|
||||||
|
while _babase.shutdown_suppress_count() > 0:
|
||||||
|
await asyncio.sleep(0.001)
|
||||||
|
_babase.lifecyclelog('shutdown-suppress wait end')
|
||||||
|
|
||||||
def handle_deep_link(self, url: str) -> None:
|
def _threadpool_no_wait_done(self, fut: Future) -> None:
|
||||||
"""Handle a deep link URL."""
|
try:
|
||||||
from babase._language import Lstr
|
fut.result()
|
||||||
|
except Exception:
|
||||||
|
logging.exception(
|
||||||
|
'Error in work submitted via threadpool_submit_no_wait()'
|
||||||
|
)
|
||||||
|
|
||||||
assert _babase.in_logic_thread()
|
# --------------------------------------------------------------------
|
||||||
|
# THE FOLLOWING ARE DEPRECATED AND WILL BE REMOVED IN A FUTURE UPDATE.
|
||||||
|
# --------------------------------------------------------------------
|
||||||
|
|
||||||
appname = _babase.appname()
|
@property
|
||||||
if url.startswith(f'{appname}://code/'):
|
def build_number(self) -> int:
|
||||||
code = url.replace(f'{appname}://code/', '')
|
"""Integer build number.
|
||||||
if self.classic is not None:
|
|
||||||
self.classic.accounts.add_pending_promo_code(code)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
_babase.screenmessage(
|
|
||||||
Lstr(resource='errorText'), color=(1, 0, 0)
|
|
||||||
)
|
|
||||||
_babase.getsimplesound('error').play()
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def on_initial_sign_in_complete(self) -> None:
|
This value increases by at least 1 with each release of the engine.
|
||||||
"""Called when initial sign-in (or lack thereof) completes.
|
It is independent of the human readable babase.App.version string.
|
||||||
|
|
||||||
This normally gets called by the plus subsystem. The
|
|
||||||
initial-sign-in process may include tasks such as syncing
|
|
||||||
account workspaces or other data so it may take a substantial
|
|
||||||
amount of time.
|
|
||||||
"""
|
"""
|
||||||
assert _babase.in_logic_thread()
|
warnings.warn(
|
||||||
assert not self._initial_sign_in_completed
|
'app.build_number is deprecated; use app.env.build_number',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.build_number
|
||||||
|
|
||||||
# Tell meta it can start scanning extra stuff that just showed
|
@property
|
||||||
# up (namely account workspaces).
|
def device_name(self) -> str:
|
||||||
self.meta.start_extra_scan()
|
"""Name of the device running the app."""
|
||||||
|
warnings.warn(
|
||||||
|
'app.device_name is deprecated; use app.env.device_name',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.device_name
|
||||||
|
|
||||||
self._initial_sign_in_completed = True
|
@property
|
||||||
self._update_state()
|
def config_file_path(self) -> str:
|
||||||
|
"""Where the app's config file is stored on disk."""
|
||||||
|
warnings.warn(
|
||||||
|
'app.config_file_path is deprecated;'
|
||||||
|
' use app.env.config_file_path',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.config_file_path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def version(self) -> str:
|
||||||
|
"""Human-readable engine version string; something like '1.3.24'.
|
||||||
|
|
||||||
|
This should not be interpreted as a number; it may contain
|
||||||
|
string elements such as 'alpha', 'beta', 'test', etc.
|
||||||
|
If a numeric version is needed, use `build_number`.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
'app.version is deprecated; use app.env.version',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.version
|
||||||
|
|
||||||
|
@property
|
||||||
|
def debug_build(self) -> bool:
|
||||||
|
"""Whether the app was compiled in debug mode.
|
||||||
|
|
||||||
|
Debug builds generally run substantially slower than non-debug
|
||||||
|
builds due to compiler optimizations being disabled and extra
|
||||||
|
checks being run.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
'app.debug_build is deprecated; use app.env.debug',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.debug
|
||||||
|
|
||||||
|
@property
|
||||||
|
def test_build(self) -> bool:
|
||||||
|
"""Whether the app was compiled in test mode.
|
||||||
|
|
||||||
|
Test mode enables extra checks and features that are useful for
|
||||||
|
release testing but which do not slow the game down significantly.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
'app.test_build is deprecated; use app.env.test',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.test
|
||||||
|
|
||||||
|
@property
|
||||||
|
def data_directory(self) -> str:
|
||||||
|
"""Path where static app data lives."""
|
||||||
|
warnings.warn(
|
||||||
|
'app.data_directory is deprecated; use app.env.data_directory',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.data_directory
|
||||||
|
|
||||||
|
@property
|
||||||
|
def python_directory_user(self) -> str | None:
|
||||||
|
"""Path where the app expects its user scripts (mods) to live.
|
||||||
|
|
||||||
|
Be aware that this value may be None if ballistica is running in
|
||||||
|
a non-standard environment, and that python-path modifications may
|
||||||
|
cause modules to be loaded from other locations.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
'app.python_directory_user is deprecated;'
|
||||||
|
' use app.env.python_directory_user',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.python_directory_user
|
||||||
|
|
||||||
|
@property
|
||||||
|
def python_directory_app(self) -> str | None:
|
||||||
|
"""Path where the app expects its bundled modules to live.
|
||||||
|
|
||||||
|
Be aware that this value may be None if Ballistica is running in
|
||||||
|
a non-standard environment, and that python-path modifications may
|
||||||
|
cause modules to be loaded from other locations.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
'app.python_directory_app is deprecated;'
|
||||||
|
' use app.env.python_directory_app',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.python_directory_app
|
||||||
|
|
||||||
|
@property
|
||||||
|
def python_directory_app_site(self) -> str | None:
|
||||||
|
"""Path where the app expects its bundled pip modules to live.
|
||||||
|
|
||||||
|
Be aware that this value may be None if Ballistica is running in
|
||||||
|
a non-standard environment, and that python-path modifications may
|
||||||
|
cause modules to be loaded from other locations.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
'app.python_directory_app_site is deprecated;'
|
||||||
|
' use app.env.python_directory_app_site',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.python_directory_app_site
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_version(self) -> int:
|
||||||
|
"""The app's api version.
|
||||||
|
|
||||||
|
Only Python modules and packages associated with the current API
|
||||||
|
version number will be detected by the game (see the ba_meta tag).
|
||||||
|
This value will change whenever substantial backward-incompatible
|
||||||
|
changes are introduced to ballistica APIs. When that happens,
|
||||||
|
modules/packages should be updated accordingly and set to target
|
||||||
|
the newer API version number.
|
||||||
|
"""
|
||||||
|
warnings.warn(
|
||||||
|
'app.api_version is deprecated; use app.env.api_version',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.api_version
|
||||||
|
|
||||||
|
@property
|
||||||
|
def on_tv(self) -> bool:
|
||||||
|
"""Whether the app is currently running on a TV."""
|
||||||
|
warnings.warn(
|
||||||
|
'app.on_tv is deprecated; use app.env.tv',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.tv
|
||||||
|
|
||||||
|
@property
|
||||||
|
def vr_mode(self) -> bool:
|
||||||
|
"""Whether the app is currently running in VR."""
|
||||||
|
warnings.warn(
|
||||||
|
'app.vr_mode is deprecated; use app.env.vr',
|
||||||
|
DeprecationWarning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return self.env.vr
|
||||||
|
|||||||
@ -109,7 +109,7 @@ def read_app_config() -> tuple[AppConfig, bool]:
|
|||||||
|
|
||||||
# NOTE: it is assumed that this only gets called once and the
|
# NOTE: it is assumed that this only gets called once and the
|
||||||
# config object will not change from here on out
|
# config object will not change from here on out
|
||||||
config_file_path = _babase.app.config_file_path
|
config_file_path = _babase.app.env.config_file_path
|
||||||
config_contents = ''
|
config_contents = ''
|
||||||
try:
|
try:
|
||||||
if os.path.exists(config_file_path):
|
if os.path.exists(config_file_path):
|
||||||
|
|||||||
@ -48,7 +48,7 @@ def is_browser_likely_available() -> bool:
|
|||||||
# assume no browser.
|
# assume no browser.
|
||||||
# FIXME: Might not be the case anymore; should make this definable
|
# FIXME: Might not be the case anymore; should make this definable
|
||||||
# at the platform level.
|
# at the platform level.
|
||||||
if app.vr_mode or (platform == 'android' and not hastouchscreen):
|
if app.env.vr or (platform == 'android' and not hastouchscreen):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Anywhere else assume we've got one.
|
# Anywhere else assume we've got one.
|
||||||
@ -103,8 +103,8 @@ def handle_v1_cloud_log() -> None:
|
|||||||
|
|
||||||
info = {
|
info = {
|
||||||
'log': _babase.get_v1_cloud_log(),
|
'log': _babase.get_v1_cloud_log(),
|
||||||
'version': app.version,
|
'version': app.env.version,
|
||||||
'build': app.build_number,
|
'build': app.env.build_number,
|
||||||
'userAgentString': classic.legacy_user_agent_string,
|
'userAgentString': classic.legacy_user_agent_string,
|
||||||
'session': sessionname,
|
'session': sessionname,
|
||||||
'activity': activityname,
|
'activity': activityname,
|
||||||
@ -279,7 +279,8 @@ def dump_app_state(
|
|||||||
# the dump in that case.
|
# the dump in that case.
|
||||||
try:
|
try:
|
||||||
mdpath = os.path.join(
|
mdpath = os.path.join(
|
||||||
os.path.dirname(_babase.app.config_file_path), '_appstate_dump_md'
|
os.path.dirname(_babase.app.env.config_file_path),
|
||||||
|
'_appstate_dump_md',
|
||||||
)
|
)
|
||||||
with open(mdpath, 'w', encoding='utf-8') as outfile:
|
with open(mdpath, 'w', encoding='utf-8') as outfile:
|
||||||
outfile.write(
|
outfile.write(
|
||||||
@ -297,7 +298,7 @@ def dump_app_state(
|
|||||||
return
|
return
|
||||||
|
|
||||||
tbpath = os.path.join(
|
tbpath = os.path.join(
|
||||||
os.path.dirname(_babase.app.config_file_path), '_appstate_dump_tb'
|
os.path.dirname(_babase.app.env.config_file_path), '_appstate_dump_tb'
|
||||||
)
|
)
|
||||||
|
|
||||||
tbfile = open(tbpath, 'w', encoding='utf-8')
|
tbfile = open(tbpath, 'w', encoding='utf-8')
|
||||||
@ -329,7 +330,8 @@ def log_dumped_app_state() -> None:
|
|||||||
try:
|
try:
|
||||||
out = ''
|
out = ''
|
||||||
mdpath = os.path.join(
|
mdpath = os.path.join(
|
||||||
os.path.dirname(_babase.app.config_file_path), '_appstate_dump_md'
|
os.path.dirname(_babase.app.env.config_file_path),
|
||||||
|
'_appstate_dump_md',
|
||||||
)
|
)
|
||||||
if os.path.exists(mdpath):
|
if os.path.exists(mdpath):
|
||||||
# We may be hanging on to open file descriptors for use by
|
# We may be hanging on to open file descriptors for use by
|
||||||
@ -354,7 +356,7 @@ def log_dumped_app_state() -> None:
|
|||||||
f'Time: {metadata.app_time:.2f}'
|
f'Time: {metadata.app_time:.2f}'
|
||||||
)
|
)
|
||||||
tbpath = os.path.join(
|
tbpath = os.path.join(
|
||||||
os.path.dirname(_babase.app.config_file_path),
|
os.path.dirname(_babase.app.env.config_file_path),
|
||||||
'_appstate_dump_tb',
|
'_appstate_dump_tb',
|
||||||
)
|
)
|
||||||
if os.path.exists(tbpath):
|
if os.path.exists(tbpath):
|
||||||
|
|||||||
@ -6,6 +6,7 @@ from __future__ import annotations
|
|||||||
import sys
|
import sys
|
||||||
import signal
|
import signal
|
||||||
import logging
|
import logging
|
||||||
|
import warnings
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from efro.log import LogLevel
|
from efro.log import LogLevel
|
||||||
@ -103,6 +104,12 @@ def on_main_thread_start_app() -> None:
|
|||||||
signal.signal(signal.SIGINT, signal.SIG_DFL) # Do default handling.
|
signal.signal(signal.SIGINT, signal.SIG_DFL) # Do default handling.
|
||||||
_babase.setup_sigint()
|
_babase.setup_sigint()
|
||||||
|
|
||||||
|
# Turn on deprecation warnings. By default these are off for release
|
||||||
|
# builds except for in __main__. However this is a key way to
|
||||||
|
# communicate api changes to modders and most modders are running
|
||||||
|
# release builds so its good to have this on everywhere.
|
||||||
|
warnings.simplefilter('default', DeprecationWarning)
|
||||||
|
|
||||||
# Turn off fancy-pants cyclic garbage-collection. We run it only at
|
# Turn off fancy-pants cyclic garbage-collection. We run it only at
|
||||||
# explicit times to avoid random hitches and keep things more
|
# explicit times to avoid random hitches and keep things more
|
||||||
# deterministic. Non-reference-looped objects will still get cleaned
|
# deterministic. Non-reference-looped objects will still get cleaned
|
||||||
|
|||||||
@ -354,7 +354,7 @@ def show_client_too_old_error() -> None:
|
|||||||
# a newer build.
|
# a newer build.
|
||||||
if (
|
if (
|
||||||
_babase.app.config.get('SuppressClientTooOldErrorForBuild')
|
_babase.app.config.get('SuppressClientTooOldErrorForBuild')
|
||||||
== _babase.app.build_number
|
== _babase.app.env.build_number
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@ -68,7 +68,10 @@ class LanguageSubsystem(AppSubsystem):
|
|||||||
try:
|
try:
|
||||||
names = os.listdir(
|
names = os.listdir(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
_babase.app.data_directory, 'ba_data', 'data', 'languages'
|
_babase.app.env.data_directory,
|
||||||
|
'ba_data',
|
||||||
|
'data',
|
||||||
|
'languages',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
names = [n.replace('.json', '').capitalize() for n in names]
|
names = [n.replace('.json', '').capitalize() for n in names]
|
||||||
@ -121,7 +124,7 @@ class LanguageSubsystem(AppSubsystem):
|
|||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
_babase.app.data_directory,
|
_babase.app.env.data_directory,
|
||||||
'ba_data',
|
'ba_data',
|
||||||
'data',
|
'data',
|
||||||
'languages',
|
'languages',
|
||||||
@ -139,7 +142,7 @@ class LanguageSubsystem(AppSubsystem):
|
|||||||
lmodvalues = None
|
lmodvalues = None
|
||||||
else:
|
else:
|
||||||
lmodfile = os.path.join(
|
lmodfile = os.path.join(
|
||||||
_babase.app.data_directory,
|
_babase.app.env.data_directory,
|
||||||
'ba_data',
|
'ba_data',
|
||||||
'data',
|
'data',
|
||||||
'languages',
|
'languages',
|
||||||
|
|||||||
@ -18,11 +18,6 @@ import _babase
|
|||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
|
|
||||||
# The meta api version of this build of the game.
|
|
||||||
# Only packages and modules requiring this exact api version
|
|
||||||
# will be considered when scanning directories.
|
|
||||||
# See: https://ballistica.net/wiki/Meta-Tag-System
|
|
||||||
CURRENT_API_VERSION = 8
|
|
||||||
|
|
||||||
# Meta export lines can use these names to represent these classes.
|
# Meta export lines can use these names to represent these classes.
|
||||||
# This is purely a convenience; it is possible to use full class paths
|
# This is purely a convenience; it is possible to use full class paths
|
||||||
@ -76,14 +71,15 @@ class MetadataSubsystem:
|
|||||||
"""
|
"""
|
||||||
assert self._scan_complete_cb is None
|
assert self._scan_complete_cb is None
|
||||||
assert self._scan is None
|
assert self._scan is None
|
||||||
|
env = _babase.app.env
|
||||||
|
|
||||||
self._scan_complete_cb = scan_complete_cb
|
self._scan_complete_cb = scan_complete_cb
|
||||||
self._scan = DirectoryScan(
|
self._scan = DirectoryScan(
|
||||||
[
|
[
|
||||||
path
|
path
|
||||||
for path in [
|
for path in [
|
||||||
_babase.app.python_directory_app,
|
env.python_directory_app,
|
||||||
_babase.app.python_directory_user,
|
env.python_directory_user,
|
||||||
]
|
]
|
||||||
if path is not None
|
if path is not None
|
||||||
]
|
]
|
||||||
@ -212,7 +208,7 @@ class MetadataSubsystem:
|
|||||||
'${NUM}',
|
'${NUM}',
|
||||||
str(len(results.incorrect_api_modules) - 1),
|
str(len(results.incorrect_api_modules) - 1),
|
||||||
),
|
),
|
||||||
('${API}', str(CURRENT_API_VERSION)),
|
('${API}', str(_babase.app.env.api_version)),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@ -220,7 +216,7 @@ class MetadataSubsystem:
|
|||||||
resource='scanScriptsSingleModuleNeedsUpdatesText',
|
resource='scanScriptsSingleModuleNeedsUpdatesText',
|
||||||
subs=[
|
subs=[
|
||||||
('${PATH}', results.incorrect_api_modules[0]),
|
('${PATH}', results.incorrect_api_modules[0]),
|
||||||
('${API}', str(CURRENT_API_VERSION)),
|
('${API}', str(_babase.app.env.api_version)),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
_babase.screenmessage(msg, color=(1, 0, 0))
|
_babase.screenmessage(msg, color=(1, 0, 0))
|
||||||
@ -344,13 +340,16 @@ class DirectoryScan:
|
|||||||
|
|
||||||
# If we find a module requiring a different api version, warn
|
# If we find a module requiring a different api version, warn
|
||||||
# and ignore.
|
# and ignore.
|
||||||
if required_api is not None and required_api != CURRENT_API_VERSION:
|
if (
|
||||||
|
required_api is not None
|
||||||
|
and required_api != _babase.app.env.api_version
|
||||||
|
):
|
||||||
logging.warning(
|
logging.warning(
|
||||||
'metascan: %s requires api %s but we are running'
|
'metascan: %s requires api %s but we are running'
|
||||||
' %s. Ignoring module.',
|
' %s. Ignoring module.',
|
||||||
subpath,
|
subpath,
|
||||||
required_api,
|
required_api,
|
||||||
CURRENT_API_VERSION,
|
_babase.app.env.api_version,
|
||||||
)
|
)
|
||||||
self.results.incorrect_api_modules.append(
|
self.results.incorrect_api_modules.append(
|
||||||
self._module_name_for_subpath(subpath)
|
self._module_name_for_subpath(subpath)
|
||||||
|
|||||||
@ -18,7 +18,7 @@ def get_human_readable_user_scripts_path() -> str:
|
|||||||
This is NOT a valid filesystem path; may be something like "(SD Card)".
|
This is NOT a valid filesystem path; may be something like "(SD Card)".
|
||||||
"""
|
"""
|
||||||
app = _babase.app
|
app = _babase.app
|
||||||
path: str | None = app.python_directory_user
|
path: str | None = app.env.python_directory_user
|
||||||
if path is None:
|
if path is None:
|
||||||
return '<Not Available>'
|
return '<Not Available>'
|
||||||
|
|
||||||
@ -66,19 +66,20 @@ def _request_storage_permission() -> bool:
|
|||||||
def show_user_scripts() -> None:
|
def show_user_scripts() -> None:
|
||||||
"""Open or nicely print the location of the user-scripts directory."""
|
"""Open or nicely print the location of the user-scripts directory."""
|
||||||
app = _babase.app
|
app = _babase.app
|
||||||
|
env = app.env
|
||||||
|
|
||||||
# First off, if we need permission for this, ask for it.
|
# First off, if we need permission for this, ask for it.
|
||||||
if _request_storage_permission():
|
if _request_storage_permission():
|
||||||
return
|
return
|
||||||
|
|
||||||
# If we're running in a nonstandard environment its possible this is unset.
|
# If we're running in a nonstandard environment its possible this is unset.
|
||||||
if app.python_directory_user is None:
|
if env.python_directory_user is None:
|
||||||
_babase.screenmessage('<unset>')
|
_babase.screenmessage('<unset>')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Secondly, if the dir doesn't exist, attempt to make it.
|
# Secondly, if the dir doesn't exist, attempt to make it.
|
||||||
if not os.path.exists(app.python_directory_user):
|
if not os.path.exists(env.python_directory_user):
|
||||||
os.makedirs(app.python_directory_user)
|
os.makedirs(env.python_directory_user)
|
||||||
|
|
||||||
# On android, attempt to write a file in their user-scripts dir telling
|
# On android, attempt to write a file in their user-scripts dir telling
|
||||||
# them about modding. This also has the side-effect of allowing us to
|
# them about modding. This also has the side-effect of allowing us to
|
||||||
@ -88,7 +89,7 @@ def show_user_scripts() -> None:
|
|||||||
# they can see it.
|
# they can see it.
|
||||||
if app.classic is not None and app.classic.platform == 'android':
|
if app.classic is not None and app.classic.platform == 'android':
|
||||||
try:
|
try:
|
||||||
usd: str | None = app.python_directory_user
|
usd: str | None = env.python_directory_user
|
||||||
if usd is not None and os.path.isdir(usd):
|
if usd is not None and os.path.isdir(usd):
|
||||||
file_name = usd + '/about_this_folder.txt'
|
file_name = usd + '/about_this_folder.txt'
|
||||||
with open(file_name, 'w', encoding='utf-8') as outfile:
|
with open(file_name, 'w', encoding='utf-8') as outfile:
|
||||||
@ -105,7 +106,7 @@ def show_user_scripts() -> None:
|
|||||||
|
|
||||||
# On a few platforms we try to open the dir in the UI.
|
# On a few platforms we try to open the dir in the UI.
|
||||||
if app.classic is not None and app.classic.platform in ['mac', 'windows']:
|
if app.classic is not None and app.classic.platform in ['mac', 'windows']:
|
||||||
_babase.open_dir_externally(app.python_directory_user)
|
_babase.open_dir_externally(env.python_directory_user)
|
||||||
|
|
||||||
# Otherwise we just print a pretty version of it.
|
# Otherwise we just print a pretty version of it.
|
||||||
else:
|
else:
|
||||||
@ -120,18 +121,19 @@ def create_user_system_scripts() -> None:
|
|||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
app = _babase.app
|
app = _babase.app
|
||||||
|
env = app.env
|
||||||
|
|
||||||
# First off, if we need permission for this, ask for it.
|
# First off, if we need permission for this, ask for it.
|
||||||
if _request_storage_permission():
|
if _request_storage_permission():
|
||||||
return
|
return
|
||||||
|
|
||||||
# Its possible these are unset in non-standard environments.
|
# Its possible these are unset in non-standard environments.
|
||||||
if app.python_directory_user is None:
|
if env.python_directory_user is None:
|
||||||
raise RuntimeError('user python dir unset')
|
raise RuntimeError('user python dir unset')
|
||||||
if app.python_directory_app is None:
|
if env.python_directory_app is None:
|
||||||
raise RuntimeError('app python dir unset')
|
raise RuntimeError('app python dir unset')
|
||||||
|
|
||||||
path = app.python_directory_user + '/sys/' + app.version
|
path = f'{env.python_directory_user}/sys/{env.version}'
|
||||||
pathtmp = path + '_tmp'
|
pathtmp = path + '_tmp'
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
shutil.rmtree(path)
|
shutil.rmtree(path)
|
||||||
@ -147,8 +149,8 @@ def create_user_system_scripts() -> None:
|
|||||||
# /Knowledge-Nuggets#python-cache-files-gotcha
|
# /Knowledge-Nuggets#python-cache-files-gotcha
|
||||||
return ('__pycache__',)
|
return ('__pycache__',)
|
||||||
|
|
||||||
print(f'COPYING "{app.python_directory_app}" -> "{pathtmp}".')
|
print(f'COPYING "{env.python_directory_app}" -> "{pathtmp}".')
|
||||||
shutil.copytree(app.python_directory_app, pathtmp, ignore=_ignore_filter)
|
shutil.copytree(env.python_directory_app, pathtmp, ignore=_ignore_filter)
|
||||||
|
|
||||||
print(f'MOVING "{pathtmp}" -> "{path}".')
|
print(f'MOVING "{pathtmp}" -> "{path}".')
|
||||||
shutil.move(pathtmp, path)
|
shutil.move(pathtmp, path)
|
||||||
@ -168,12 +170,12 @@ def delete_user_system_scripts() -> None:
|
|||||||
"""Clean out the scripts created by create_user_system_scripts()."""
|
"""Clean out the scripts created by create_user_system_scripts()."""
|
||||||
import shutil
|
import shutil
|
||||||
|
|
||||||
app = _babase.app
|
env = _babase.app.env
|
||||||
|
|
||||||
if app.python_directory_user is None:
|
if env.python_directory_user is None:
|
||||||
raise RuntimeError('user python dir unset')
|
raise RuntimeError('user python dir unset')
|
||||||
|
|
||||||
path = app.python_directory_user + '/sys/' + app.version
|
path = f'{env.python_directory_user}/sys/{env.version}'
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
shutil.rmtree(path)
|
shutil.rmtree(path)
|
||||||
print(
|
print(
|
||||||
@ -185,6 +187,6 @@ def delete_user_system_scripts() -> None:
|
|||||||
print(f"User system scripts not found at '{path}'.")
|
print(f"User system scripts not found at '{path}'.")
|
||||||
|
|
||||||
# If the sys path is empty, kill it.
|
# If the sys path is empty, kill it.
|
||||||
dpath = app.python_directory_user + '/sys'
|
dpath = env.python_directory_user + '/sys'
|
||||||
if os.path.isdir(dpath) and not os.listdir(dpath):
|
if os.path.isdir(dpath) and not os.listdir(dpath):
|
||||||
os.rmdir(dpath)
|
os.rmdir(dpath)
|
||||||
|
|||||||
@ -4,14 +4,12 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
import logging
|
|
||||||
import weakref
|
import weakref
|
||||||
import threading
|
import threading
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import babase
|
import babase
|
||||||
from babase import DEFAULT_REQUEST_TIMEOUT_SECONDS
|
|
||||||
import bascenev1
|
import bascenev1
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -72,6 +70,7 @@ class MasterServerV1CallThread(threading.Thread):
|
|||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
# pylint: disable=consider-using-with
|
# pylint: disable=consider-using-with
|
||||||
|
# pylint: disable=too-many-branches
|
||||||
import urllib.request
|
import urllib.request
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import urllib.error
|
import urllib.error
|
||||||
@ -79,20 +78,15 @@ class MasterServerV1CallThread(threading.Thread):
|
|||||||
|
|
||||||
from efro.error import is_urllib_communication_error
|
from efro.error import is_urllib_communication_error
|
||||||
|
|
||||||
# If the app is going down, this is a no-op. Trying to avoid the
|
|
||||||
# rare odd crash I see from (presumably) SSL stuff getting used
|
|
||||||
# while the app is being torn down.
|
|
||||||
if babase.app.state is babase.app.State.SHUTTING_DOWN:
|
|
||||||
logging.warning(
|
|
||||||
'MasterServerV1CallThread.run() during app'
|
|
||||||
' shutdown is a no-op.'
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
plus = babase.app.plus
|
plus = babase.app.plus
|
||||||
assert plus is not None
|
assert plus is not None
|
||||||
response_data: Any = None
|
response_data: Any = None
|
||||||
url: str | None = None
|
url: str | None = None
|
||||||
|
|
||||||
|
# Tearing the app down while this is running can lead to
|
||||||
|
# rare crashes in LibSSL, so avoid that if at all possible.
|
||||||
|
babase.shutdown_suppress_begin()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
classic = babase.app.classic
|
classic = babase.app.classic
|
||||||
assert classic is not None
|
assert classic is not None
|
||||||
@ -114,7 +108,7 @@ class MasterServerV1CallThread(threading.Thread):
|
|||||||
{'User-Agent': classic.legacy_user_agent_string},
|
{'User-Agent': classic.legacy_user_agent_string},
|
||||||
),
|
),
|
||||||
context=babase.app.net.sslcontext,
|
context=babase.app.net.sslcontext,
|
||||||
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
timeout=babase.DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||||
)
|
)
|
||||||
elif self._request_type == 'post':
|
elif self._request_type == 'post':
|
||||||
url = plus.get_master_server_address() + '/' + self._request
|
url = plus.get_master_server_address() + '/' + self._request
|
||||||
@ -126,7 +120,7 @@ class MasterServerV1CallThread(threading.Thread):
|
|||||||
{'User-Agent': classic.legacy_user_agent_string},
|
{'User-Agent': classic.legacy_user_agent_string},
|
||||||
),
|
),
|
||||||
context=babase.app.net.sslcontext,
|
context=babase.app.net.sslcontext,
|
||||||
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
timeout=babase.DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
raise TypeError('Invalid request_type: ' + self._request_type)
|
raise TypeError('Invalid request_type: ' + self._request_type)
|
||||||
@ -160,6 +154,9 @@ class MasterServerV1CallThread(threading.Thread):
|
|||||||
|
|
||||||
response_data = None
|
response_data = None
|
||||||
|
|
||||||
|
finally:
|
||||||
|
babase.shutdown_suppress_end()
|
||||||
|
|
||||||
if self._callback is not None:
|
if self._callback is not None:
|
||||||
babase.pushcall(
|
babase.pushcall(
|
||||||
babase.Call(self._run_callback, response_data),
|
babase.Call(self._run_callback, response_data),
|
||||||
|
|||||||
@ -214,7 +214,10 @@ class ServerController:
|
|||||||
|
|
||||||
babase.app.classic.master_server_v1_get(
|
babase.app.classic.master_server_v1_get(
|
||||||
'bsAccessCheck',
|
'bsAccessCheck',
|
||||||
{'port': bascenev1.get_game_port(), 'b': babase.app.build_number},
|
{
|
||||||
|
'port': bascenev1.get_game_port(),
|
||||||
|
'b': babase.app.env.build_number,
|
||||||
|
},
|
||||||
callback=self._access_check_response,
|
callback=self._access_check_response,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -379,8 +382,8 @@ class ServerController:
|
|||||||
if self._first_run:
|
if self._first_run:
|
||||||
curtimestr = time.strftime('%c')
|
curtimestr = time.strftime('%c')
|
||||||
startupmsg = (
|
startupmsg = (
|
||||||
f'{Clr.BLD}{Clr.BLU}{babase.appnameupper()} {app.version}'
|
f'{Clr.BLD}{Clr.BLU}{babase.appnameupper()} {app.env.version}'
|
||||||
f' ({app.build_number})'
|
f' ({app.env.build_number})'
|
||||||
f' entering server-mode {curtimestr}{Clr.RST}'
|
f' entering server-mode {curtimestr}{Clr.RST}'
|
||||||
)
|
)
|
||||||
logging.info(startupmsg)
|
logging.info(startupmsg)
|
||||||
|
|||||||
@ -153,6 +153,7 @@ class ClassicSubsystem(babase.AppSubsystem):
|
|||||||
plus = babase.app.plus
|
plus = babase.app.plus
|
||||||
assert plus is not None
|
assert plus is not None
|
||||||
|
|
||||||
|
env = babase.app.env
|
||||||
cfg = babase.app.config
|
cfg = babase.app.config
|
||||||
|
|
||||||
self.music.on_app_loading()
|
self.music.on_app_loading()
|
||||||
@ -161,11 +162,7 @@ class ClassicSubsystem(babase.AppSubsystem):
|
|||||||
|
|
||||||
# Non-test, non-debug builds should generally be blessed; warn if not.
|
# Non-test, non-debug builds should generally be blessed; warn if not.
|
||||||
# (so I don't accidentally release a build that can't play tourneys)
|
# (so I don't accidentally release a build that can't play tourneys)
|
||||||
if (
|
if not env.debug and not env.test and not plus.is_blessed():
|
||||||
not babase.app.debug_build
|
|
||||||
and not babase.app.test_build
|
|
||||||
and not plus.is_blessed()
|
|
||||||
):
|
|
||||||
babase.screenmessage('WARNING: NON-BLESSED BUILD', color=(1, 0, 0))
|
babase.screenmessage('WARNING: NON-BLESSED BUILD', color=(1, 0, 0))
|
||||||
|
|
||||||
# FIXME: This should not be hard-coded.
|
# FIXME: This should not be hard-coded.
|
||||||
|
|||||||
@ -113,7 +113,7 @@ def get_all_tips() -> list[str]:
|
|||||||
if (
|
if (
|
||||||
app.classic is not None
|
app.classic is not None
|
||||||
and app.classic.platform in ('android', 'ios')
|
and app.classic.platform in ('android', 'ios')
|
||||||
and not app.on_tv
|
and not app.env.tv
|
||||||
):
|
):
|
||||||
tips += [
|
tips += [
|
||||||
(
|
(
|
||||||
|
|||||||
@ -52,8 +52,8 @@ 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 = 21256
|
TARGET_BALLISTICA_BUILD = 21269
|
||||||
TARGET_BALLISTICA_VERSION = '1.7.26'
|
TARGET_BALLISTICA_VERSION = '1.7.27'
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@ -108,7 +108,7 @@ class CoopGameActivity(GameActivity[PlayerT, TeamT]):
|
|||||||
)
|
)
|
||||||
if not a.complete
|
if not a.complete
|
||||||
]
|
]
|
||||||
vrmode = babase.app.vr_mode
|
vrmode = babase.app.env.vr
|
||||||
if achievements:
|
if achievements:
|
||||||
Text(
|
Text(
|
||||||
babase.Lstr(resource='achievementsRemainingText'),
|
babase.Lstr(resource='achievementsRemainingText'),
|
||||||
|
|||||||
@ -600,7 +600,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
|||||||
translate=('gameDescriptions', sb_desc_l[0]), subs=subs
|
translate=('gameDescriptions', sb_desc_l[0]), subs=subs
|
||||||
)
|
)
|
||||||
sb_desc = translation
|
sb_desc = translation
|
||||||
vrmode = babase.app.vr_mode
|
vrmode = babase.app.env.vr
|
||||||
yval = -34 if is_empty else -20
|
yval = -34 if is_empty else -20
|
||||||
yval -= 16
|
yval -= 16
|
||||||
sbpos = (
|
sbpos = (
|
||||||
@ -706,7 +706,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
|||||||
resource='epicDescriptionFilterText',
|
resource='epicDescriptionFilterText',
|
||||||
subs=[('${DESCRIPTION}', translation)],
|
subs=[('${DESCRIPTION}', translation)],
|
||||||
)
|
)
|
||||||
vrmode = babase.app.vr_mode
|
vrmode = babase.app.env.vr
|
||||||
dnode = _bascenev1.newnode(
|
dnode = _bascenev1.newnode(
|
||||||
'text',
|
'text',
|
||||||
attrs={
|
attrs={
|
||||||
@ -761,7 +761,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
|||||||
base_position = (75, 50)
|
base_position = (75, 50)
|
||||||
tip_scale = 0.8
|
tip_scale = 0.8
|
||||||
tip_title_scale = 1.2
|
tip_title_scale = 1.2
|
||||||
vrmode = babase.app.vr_mode
|
vrmode = babase.app.env.vr
|
||||||
|
|
||||||
t_offs = -350.0
|
t_offs = -350.0
|
||||||
tnode = _bascenev1.newnode(
|
tnode = _bascenev1.newnode(
|
||||||
|
|||||||
@ -185,7 +185,7 @@ def show_damage_count(
|
|||||||
# (connected clients may have differing configs so they won't
|
# (connected clients may have differing configs so they won't
|
||||||
# get the intended results).
|
# get the intended results).
|
||||||
assert app.classic is not None
|
assert app.classic is not None
|
||||||
do_big = app.ui_v1.uiscale is babase.UIScale.SMALL or app.vr_mode
|
do_big = app.ui_v1.uiscale is babase.UIScale.SMALL or app.env.vr
|
||||||
txtnode = _bascenev1.newnode(
|
txtnode = _bascenev1.newnode(
|
||||||
'text',
|
'text',
|
||||||
attrs={
|
attrs={
|
||||||
|
|||||||
@ -49,7 +49,7 @@ class JoinInfo:
|
|||||||
if keyboard is not None:
|
if keyboard is not None:
|
||||||
self._update_for_keyboard(keyboard)
|
self._update_for_keyboard(keyboard)
|
||||||
|
|
||||||
flatness = 1.0 if babase.app.vr_mode else 0.0
|
flatness = 1.0 if babase.app.env.vr else 0.0
|
||||||
self._text = NodeActor(
|
self._text = NodeActor(
|
||||||
_bascenev1.newnode(
|
_bascenev1.newnode(
|
||||||
'text',
|
'text',
|
||||||
|
|||||||
@ -76,7 +76,7 @@ class CoopJoinActivity(bs.JoinActivity):
|
|||||||
]
|
]
|
||||||
have_achievements = bool(achievements)
|
have_achievements = bool(achievements)
|
||||||
achievements = [a for a in achievements if not a.complete]
|
achievements = [a for a in achievements if not a.complete]
|
||||||
vrmode = bs.app.vr_mode
|
vrmode = bs.app.env.vr
|
||||||
if have_achievements:
|
if have_achievements:
|
||||||
Text(
|
Text(
|
||||||
bs.Lstr(resource='achievementsRemainingText'),
|
bs.Lstr(resource='achievementsRemainingText'),
|
||||||
|
|||||||
@ -74,7 +74,7 @@ class Background(bs.Actor):
|
|||||||
self.node.connectattr('opacity', self.logo, 'opacity')
|
self.node.connectattr('opacity', self.logo, 'opacity')
|
||||||
# add jitter/pulse for a stop-motion-y look unless we're in VR
|
# add jitter/pulse for a stop-motion-y look unless we're in VR
|
||||||
# in which case stillness is better
|
# in which case stillness is better
|
||||||
if not bs.app.vr_mode:
|
if not bs.app.env.vr:
|
||||||
self.cmb = bs.newnode(
|
self.cmb = bs.newnode(
|
||||||
'combine', owner=self.node, attrs={'size': 2}
|
'combine', owner=self.node, attrs={'size': 2}
|
||||||
)
|
)
|
||||||
|
|||||||
@ -208,13 +208,13 @@ class ControlsGuide(bs.Actor):
|
|||||||
clr = (0.9, 0.9, 2.0, 1.0) if bright else (0.8, 0.8, 2.0, 1.0)
|
clr = (0.9, 0.9, 2.0, 1.0) if bright else (0.8, 0.8, 2.0, 1.0)
|
||||||
self._run_text_pos_top = (position[0], position[1] - 135.0 * scale)
|
self._run_text_pos_top = (position[0], position[1] - 135.0 * scale)
|
||||||
self._run_text_pos_bottom = (position[0], position[1] - 172.0 * scale)
|
self._run_text_pos_bottom = (position[0], position[1] - 172.0 * scale)
|
||||||
sval = 1.0 * scale if bs.app.vr_mode else 0.8 * scale
|
sval = 1.0 * scale if bs.app.env.vr else 0.8 * scale
|
||||||
self._run_text = bs.newnode(
|
self._run_text = bs.newnode(
|
||||||
'text',
|
'text',
|
||||||
attrs={
|
attrs={
|
||||||
'scale': sval,
|
'scale': sval,
|
||||||
'host_only': True,
|
'host_only': True,
|
||||||
'shadow': 1.0 if bs.app.vr_mode else 0.5,
|
'shadow': 1.0 if bs.app.env.vr else 0.5,
|
||||||
'flatness': 1.0,
|
'flatness': 1.0,
|
||||||
'maxwidth': 380,
|
'maxwidth': 380,
|
||||||
'v_align': 'top',
|
'v_align': 'top',
|
||||||
|
|||||||
@ -45,7 +45,7 @@ class _Entry:
|
|||||||
|
|
||||||
# FIXME: Should not do things conditionally for vr-mode, as there may
|
# FIXME: Should not do things conditionally for vr-mode, as there may
|
||||||
# be non-vr clients connected which will also get these value.
|
# be non-vr clients connected which will also get these value.
|
||||||
vrmode = bs.app.vr_mode
|
vrmode = bs.app.env.vr
|
||||||
|
|
||||||
if self._do_cover:
|
if self._do_cover:
|
||||||
if vrmode:
|
if vrmode:
|
||||||
|
|||||||
@ -69,7 +69,7 @@ class ZoomText(bs.Actor):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# we never jitter in vr mode..
|
# we never jitter in vr mode..
|
||||||
if bs.app.vr_mode:
|
if bs.app.env.vr:
|
||||||
jitter = 0.0
|
jitter = 0.0
|
||||||
|
|
||||||
# if they want jitter, animate its position slightly...
|
# if they want jitter, animate its position slightly...
|
||||||
|
|||||||
@ -478,7 +478,7 @@ class RunaroundGame(bs.CoopGameActivity[Player, Team]):
|
|||||||
)
|
)
|
||||||
# FIXME; should not set things based on vr mode.
|
# FIXME; should not set things based on vr mode.
|
||||||
# (won't look right to non-vr connected clients, etc)
|
# (won't look right to non-vr connected clients, etc)
|
||||||
vrmode = bs.app.vr_mode
|
vrmode = bs.app.env.vr
|
||||||
self._lives_text = bs.NodeActor(
|
self._lives_text = bs.NodeActor(
|
||||||
bs.newnode(
|
bs.newnode(
|
||||||
'text',
|
'text',
|
||||||
|
|||||||
@ -50,6 +50,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||||||
super().on_transition_in()
|
super().on_transition_in()
|
||||||
random.seed(123)
|
random.seed(123)
|
||||||
app = bs.app
|
app = bs.app
|
||||||
|
env = app.env
|
||||||
assert app.classic is not None
|
assert app.classic is not None
|
||||||
|
|
||||||
plus = bui.app.plus
|
plus = bui.app.plus
|
||||||
@ -59,7 +60,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||||||
# the host is VR mode or not (clients may differ in that regard).
|
# the host is VR mode or not (clients may differ in that regard).
|
||||||
# Any differences need to happen at the engine level so everyone
|
# Any differences need to happen at the engine level so everyone
|
||||||
# sees things in their own optimal way.
|
# sees things in their own optimal way.
|
||||||
vr_mode = bs.app.vr_mode
|
vr_mode = bs.app.env.vr
|
||||||
|
|
||||||
if not bs.app.toolbar_test:
|
if not bs.app.toolbar_test:
|
||||||
color = (1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6)
|
color = (1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6)
|
||||||
@ -117,7 +118,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||||||
# the host is vr mode or not (clients may not be or vice versa).
|
# the host is vr mode or not (clients may not be or vice versa).
|
||||||
# Any differences need to happen at the engine level so everyone sees
|
# Any differences need to happen at the engine level so everyone sees
|
||||||
# things in their own optimal way.
|
# things in their own optimal way.
|
||||||
vr_mode = app.vr_mode
|
vr_mode = app.env.vr
|
||||||
uiscale = app.ui_v1.uiscale
|
uiscale = app.ui_v1.uiscale
|
||||||
|
|
||||||
# In cases where we're doing lots of dev work lets always show the
|
# In cases where we're doing lots of dev work lets always show the
|
||||||
@ -125,13 +126,13 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||||||
force_show_build_number = False
|
force_show_build_number = False
|
||||||
|
|
||||||
if not bs.app.toolbar_test:
|
if not bs.app.toolbar_test:
|
||||||
if app.debug_build or app.test_build or force_show_build_number:
|
if env.debug or env.test or force_show_build_number:
|
||||||
if app.debug_build:
|
if env.debug:
|
||||||
text = bs.Lstr(
|
text = bs.Lstr(
|
||||||
value='${V} (${B}) (${D})',
|
value='${V} (${B}) (${D})',
|
||||||
subs=[
|
subs=[
|
||||||
('${V}', app.version),
|
('${V}', app.env.version),
|
||||||
('${B}', str(app.build_number)),
|
('${B}', str(app.env.build_number)),
|
||||||
('${D}', bs.Lstr(resource='debugText')),
|
('${D}', bs.Lstr(resource='debugText')),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
@ -139,12 +140,12 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||||||
text = bs.Lstr(
|
text = bs.Lstr(
|
||||||
value='${V} (${B})',
|
value='${V} (${B})',
|
||||||
subs=[
|
subs=[
|
||||||
('${V}', app.version),
|
('${V}', app.env.version),
|
||||||
('${B}', str(app.build_number)),
|
('${B}', str(app.env.build_number)),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
text = bs.Lstr(value='${V}', subs=[('${V}', app.version)])
|
text = bs.Lstr(value='${V}', subs=[('${V}', app.env.version)])
|
||||||
scale = 0.9 if (uiscale is bs.UIScale.SMALL or vr_mode) else 0.7
|
scale = 0.9 if (uiscale is bs.UIScale.SMALL or vr_mode) else 0.7
|
||||||
color = (1, 1, 1, 1) if vr_mode else (0.5, 0.6, 0.5, 0.7)
|
color = (1, 1, 1, 1) if vr_mode else (0.5, 0.6, 0.5, 0.7)
|
||||||
self.version = bs.NodeActor(
|
self.version = bs.NodeActor(
|
||||||
@ -170,7 +171,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||||||
|
|
||||||
# Throw in test build info.
|
# Throw in test build info.
|
||||||
self.beta_info = self.beta_info_2 = None
|
self.beta_info = self.beta_info_2 = None
|
||||||
if app.test_build and not (app.demo_mode or app.arcade_mode):
|
if env.test and not (app.demo_mode or app.arcade_mode):
|
||||||
pos = (230, 35)
|
pos = (230, 35)
|
||||||
self.beta_info = bs.NodeActor(
|
self.beta_info = bs.NodeActor(
|
||||||
bs.newnode(
|
bs.newnode(
|
||||||
@ -655,7 +656,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||||||
# Add a bit of stop-motion-y jitter to the logo
|
# Add a bit of stop-motion-y jitter to the logo
|
||||||
# (unless we're in VR mode in which case its best to
|
# (unless we're in VR mode in which case its best to
|
||||||
# leave things still).
|
# leave things still).
|
||||||
if not bs.app.vr_mode:
|
if not bs.app.env.vr:
|
||||||
cmb: bs.Node | None
|
cmb: bs.Node | None
|
||||||
cmb2: bs.Node | None
|
cmb2: bs.Node | None
|
||||||
if not shadow:
|
if not shadow:
|
||||||
@ -774,7 +775,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
|||||||
# (unless we're in VR mode in which case its best to
|
# (unless we're in VR mode in which case its best to
|
||||||
# leave things still).
|
# leave things still).
|
||||||
assert logo.node
|
assert logo.node
|
||||||
if not bs.app.vr_mode:
|
if not bs.app.env.vr:
|
||||||
cmb = bs.newnode('combine', owner=logo.node, attrs={'size': 2})
|
cmb = bs.newnode('combine', owner=logo.node, attrs={'size': 2})
|
||||||
cmb.connectattr('output', logo.node, 'position')
|
cmb.connectattr('output', logo.node, 'position')
|
||||||
keys = {}
|
keys = {}
|
||||||
@ -882,7 +883,7 @@ class NewsDisplay:
|
|||||||
self._phrases.insert(0, phr)
|
self._phrases.insert(0, phr)
|
||||||
val = self._phrases.pop()
|
val = self._phrases.pop()
|
||||||
if val == '__ACH__':
|
if val == '__ACH__':
|
||||||
vrmode = app.vr_mode
|
vrmode = app.env.vr
|
||||||
Text(
|
Text(
|
||||||
bs.Lstr(resource='nextAchievementsText'),
|
bs.Lstr(resource='nextAchievementsText'),
|
||||||
color=((1, 1, 1, 1) if vrmode else (0.95, 0.9, 1, 0.4)),
|
color=((1, 1, 1, 1) if vrmode else (0.95, 0.9, 1, 0.4)),
|
||||||
@ -948,7 +949,7 @@ class NewsDisplay:
|
|||||||
|
|
||||||
# Show upcoming achievements in non-vr versions
|
# Show upcoming achievements in non-vr versions
|
||||||
# (currently too hard to read in vr).
|
# (currently too hard to read in vr).
|
||||||
self._used_phrases = (['__ACH__'] if not bs.app.vr_mode else []) + [
|
self._used_phrases = (['__ACH__'] if not bs.app.env.vr else []) + [
|
||||||
s for s in news.split('<br>\n') if s != ''
|
s for s in news.split('<br>\n') if s != ''
|
||||||
]
|
]
|
||||||
self._phrase_change_timer = bs.Timer(
|
self._phrase_change_timer = bs.Timer(
|
||||||
@ -960,12 +961,12 @@ class NewsDisplay:
|
|||||||
assert bs.app.classic is not None
|
assert bs.app.classic is not None
|
||||||
scl = (
|
scl = (
|
||||||
1.2
|
1.2
|
||||||
if (bs.app.ui_v1.uiscale is bs.UIScale.SMALL or bs.app.vr_mode)
|
if (bs.app.ui_v1.uiscale is bs.UIScale.SMALL or bs.app.env.vr)
|
||||||
else 0.8
|
else 0.8
|
||||||
)
|
)
|
||||||
|
|
||||||
color2 = (1, 1, 1, 1) if bs.app.vr_mode else (0.7, 0.65, 0.75, 1.0)
|
color2 = (1, 1, 1, 1) if bs.app.env.vr else (0.7, 0.65, 0.75, 1.0)
|
||||||
shadow = 1.0 if bs.app.vr_mode else 0.4
|
shadow = 1.0 if bs.app.env.vr else 0.4
|
||||||
self._text = bs.NodeActor(
|
self._text = bs.NodeActor(
|
||||||
bs.newnode(
|
bs.newnode(
|
||||||
'text',
|
'text',
|
||||||
|
|||||||
@ -141,7 +141,7 @@ class AccountViewerWindow(PopupWindow):
|
|||||||
bui.app.classic.master_server_v1_get(
|
bui.app.classic.master_server_v1_get(
|
||||||
'bsAccountInfo',
|
'bsAccountInfo',
|
||||||
{
|
{
|
||||||
'buildNumber': bui.app.build_number,
|
'buildNumber': bui.app.env.build_number,
|
||||||
'accountID': self._account_id,
|
'accountID': self._account_id,
|
||||||
'profileID': self._profile_id,
|
'profileID': self._profile_id,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -11,7 +11,7 @@ class ConfigErrorWindow(bui.Window):
|
|||||||
"""Window for dealing with a broken config."""
|
"""Window for dealing with a broken config."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._config_file_path = bui.app.config_file_path
|
self._config_file_path = bui.app.env.config_file_path
|
||||||
width = 800
|
width = 800
|
||||||
super().__init__(
|
super().__init__(
|
||||||
bui.containerwidget(size=(width, 400), transition='in_right')
|
bui.containerwidget(size=(width, 400), transition='in_right')
|
||||||
|
|||||||
@ -197,13 +197,19 @@ class QuitWindow:
|
|||||||
time=0.2,
|
time=0.2,
|
||||||
endcall=lambda: bui.quit(soft=True, back=self._back),
|
endcall=lambda: bui.quit(soft=True, back=self._back),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Prevent the user from doing anything else while we're on our
|
||||||
|
# way out.
|
||||||
bui.lock_all_input()
|
bui.lock_all_input()
|
||||||
|
|
||||||
# Unlock and fade back in shortly. Just in case something goes
|
# On systems supporting soft-quit, unlock and fade back in shortly
|
||||||
# wrong (or on Android where quit just backs out of our activity
|
# (soft-quit basically just backgrounds/hides the app).
|
||||||
# and we may come back after).
|
if bui.app.env.supports_soft_quit:
|
||||||
def _come_back() -> None:
|
# Unlock and fade back in shortly. Just in case something goes
|
||||||
bui.unlock_all_input()
|
# wrong (or on Android where quit just backs out of our activity
|
||||||
bui.fade_screen(True, time=0.1)
|
# and we may come back after).
|
||||||
|
def _come_back() -> None:
|
||||||
|
bui.unlock_all_input()
|
||||||
|
bui.fade_screen(True)
|
||||||
|
|
||||||
bui.apptimer(0.3, _come_back)
|
bui.apptimer(0.5, _come_back)
|
||||||
|
|||||||
@ -212,7 +212,10 @@ class CreditsListWindow(bui.Window):
|
|||||||
try:
|
try:
|
||||||
with open(
|
with open(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
bui.app.data_directory, 'ba_data', 'data', 'langdata.json'
|
bui.app.env.data_directory,
|
||||||
|
'ba_data',
|
||||||
|
'data',
|
||||||
|
'langdata.json',
|
||||||
),
|
),
|
||||||
encoding='utf-8',
|
encoding='utf-8',
|
||||||
) as infile:
|
) as infile:
|
||||||
|
|||||||
@ -15,7 +15,7 @@ def ask_for_rating() -> bui.Widget | None:
|
|||||||
subplatform = app.classic.subplatform
|
subplatform = app.classic.subplatform
|
||||||
|
|
||||||
# FIXME: should whitelist platforms we *do* want this for.
|
# FIXME: should whitelist platforms we *do* want this for.
|
||||||
if bui.app.test_build:
|
if bui.app.env.test:
|
||||||
return None
|
return None
|
||||||
if not (
|
if not (
|
||||||
platform == 'mac'
|
platform == 'mac'
|
||||||
|
|||||||
@ -43,7 +43,7 @@ class AboutGatherTab(GatherTab):
|
|||||||
|
|
||||||
# Let's not talk about sharing in vr-mode; its tricky to fit more
|
# Let's not talk about sharing in vr-mode; its tricky to fit more
|
||||||
# than one head in a VR-headset ;-)
|
# than one head in a VR-headset ;-)
|
||||||
if not bui.app.vr_mode:
|
if not bui.app.env.vr:
|
||||||
message = bui.Lstr(
|
message = bui.Lstr(
|
||||||
value='${A}\n\n${B}',
|
value='${A}\n\n${B}',
|
||||||
subs=[
|
subs=[
|
||||||
|
|||||||
@ -1010,7 +1010,7 @@ class ManualGatherTab(GatherTab):
|
|||||||
self._t_accessible_extra = t_accessible_extra
|
self._t_accessible_extra = t_accessible_extra
|
||||||
bui.app.classic.master_server_v1_get(
|
bui.app.classic.master_server_v1_get(
|
||||||
'bsAccessCheck',
|
'bsAccessCheck',
|
||||||
{'b': bui.app.build_number},
|
{'b': bui.app.env.build_number},
|
||||||
callback=bui.WeakCall(self._on_accessible_response),
|
callback=bui.WeakCall(self._on_accessible_response),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -1327,7 +1327,7 @@ class PublicGatherTab(GatherTab):
|
|||||||
)
|
)
|
||||||
bui.app.classic.master_server_v1_get(
|
bui.app.classic.master_server_v1_get(
|
||||||
'bsAccessCheck',
|
'bsAccessCheck',
|
||||||
{'b': bui.app.build_number},
|
{'b': bui.app.env.build_number},
|
||||||
callback=bui.WeakCall(self._on_public_party_accessible_response),
|
callback=bui.WeakCall(self._on_public_party_accessible_response),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@ -621,7 +621,7 @@ class GetCurrencyWindow(bui.Window):
|
|||||||
app = bui.app
|
app = bui.app
|
||||||
assert app.classic is not None
|
assert app.classic is not None
|
||||||
if (
|
if (
|
||||||
app.test_build
|
app.env.test
|
||||||
or (
|
or (
|
||||||
app.classic.platform == 'android'
|
app.classic.platform == 'android'
|
||||||
and app.classic.subplatform in ['oculus', 'cardboard']
|
and app.classic.subplatform in ['oculus', 'cardboard']
|
||||||
@ -664,8 +664,8 @@ class GetCurrencyWindow(bui.Window):
|
|||||||
'item': item,
|
'item': item,
|
||||||
'platform': app.classic.platform,
|
'platform': app.classic.platform,
|
||||||
'subplatform': app.classic.subplatform,
|
'subplatform': app.classic.subplatform,
|
||||||
'version': app.version,
|
'version': app.env.version,
|
||||||
'buildNumber': app.build_number,
|
'buildNumber': app.env.build_number,
|
||||||
},
|
},
|
||||||
callback=bui.WeakCall(self._purchase_check_result, item),
|
callback=bui.WeakCall(self._purchase_check_result, item),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -353,7 +353,7 @@ class HelpWindow(bui.Window):
|
|||||||
v -= spacing * 45.0
|
v -= spacing * 45.0
|
||||||
txt = (
|
txt = (
|
||||||
bui.Lstr(resource=self._r + '.devicesText').evaluate()
|
bui.Lstr(resource=self._r + '.devicesText').evaluate()
|
||||||
if app.vr_mode
|
if app.env.vr
|
||||||
else bui.Lstr(resource=self._r + '.controllersText').evaluate()
|
else bui.Lstr(resource=self._r + '.controllersText').evaluate()
|
||||||
)
|
)
|
||||||
txt_scale = 0.74
|
txt_scale = 0.74
|
||||||
@ -372,7 +372,7 @@ class HelpWindow(bui.Window):
|
|||||||
)
|
)
|
||||||
|
|
||||||
txt_scale = 0.7
|
txt_scale = 0.7
|
||||||
if not app.vr_mode:
|
if not app.env.vr:
|
||||||
infotxt = '.controllersInfoText'
|
infotxt = '.controllersInfoText'
|
||||||
txt = bui.Lstr(
|
txt = bui.Lstr(
|
||||||
resource=self._r + infotxt,
|
resource=self._r + infotxt,
|
||||||
|
|||||||
@ -117,7 +117,7 @@ class MainMenuWindow(bui.Window):
|
|||||||
force_test = False
|
force_test = False
|
||||||
bs.get_local_active_input_devices_count()
|
bs.get_local_active_input_devices_count()
|
||||||
if (
|
if (
|
||||||
(app.on_tv or app.classic.platform == 'mac')
|
(app.env.tv or app.classic.platform == 'mac')
|
||||||
and bui.app.config.get('launchCount', 0) <= 1
|
and bui.app.config.get('launchCount', 0) <= 1
|
||||||
) or force_test:
|
) or force_test:
|
||||||
|
|
||||||
|
|||||||
@ -34,7 +34,7 @@ class PopupWindow:
|
|||||||
focus_size = size
|
focus_size = size
|
||||||
|
|
||||||
# In vr mode we can't have windows going outside the screen.
|
# In vr mode we can't have windows going outside the screen.
|
||||||
if bui.app.vr_mode:
|
if bui.app.env.vr:
|
||||||
focus_size = size
|
focus_size = size
|
||||||
focus_position = (0, 0)
|
focus_position = (0, 0)
|
||||||
|
|
||||||
|
|||||||
@ -155,7 +155,7 @@ class ProfileUpgradeWindow(bui.Window):
|
|||||||
|
|
||||||
bui.app.classic.master_server_v1_get(
|
bui.app.classic.master_server_v1_get(
|
||||||
'bsGlobalProfileCheck',
|
'bsGlobalProfileCheck',
|
||||||
{'name': self._name, 'b': bui.app.build_number},
|
{'name': self._name, 'b': bui.app.env.build_number},
|
||||||
callback=bui.WeakCall(self._profile_check_result),
|
callback=bui.WeakCall(self._profile_check_result),
|
||||||
)
|
)
|
||||||
self._cost = plus.get_v1_account_misc_read_val(
|
self._cost = plus.get_v1_account_misc_read_val(
|
||||||
|
|||||||
@ -88,7 +88,7 @@ class AdvancedSettingsWindow(bui.Window):
|
|||||||
|
|
||||||
# In vr-mode, the internal keyboard is currently the *only* option,
|
# In vr-mode, the internal keyboard is currently the *only* option,
|
||||||
# so no need to show this.
|
# so no need to show this.
|
||||||
self._show_always_use_internal_keyboard = not app.vr_mode
|
self._show_always_use_internal_keyboard = not app.env.vr
|
||||||
|
|
||||||
self._scroll_width = self._width - (100 + 2 * x_inset)
|
self._scroll_width = self._width - (100 + 2 * x_inset)
|
||||||
self._scroll_height = self._height - 115.0
|
self._scroll_height = self._height - 115.0
|
||||||
@ -102,7 +102,7 @@ class AdvancedSettingsWindow(bui.Window):
|
|||||||
if self._show_disable_gyro:
|
if self._show_disable_gyro:
|
||||||
self._sub_height += 42
|
self._sub_height += 42
|
||||||
|
|
||||||
self._do_vr_test_button = app.vr_mode
|
self._do_vr_test_button = app.env.vr
|
||||||
self._do_net_test_button = True
|
self._do_net_test_button = True
|
||||||
self._extra_button_spacing = self._spacing * 2.5
|
self._extra_button_spacing = self._spacing * 2.5
|
||||||
|
|
||||||
@ -178,7 +178,7 @@ class AdvancedSettingsWindow(bui.Window):
|
|||||||
# Fetch the list of completed languages.
|
# Fetch the list of completed languages.
|
||||||
bui.app.classic.master_server_v1_get(
|
bui.app.classic.master_server_v1_get(
|
||||||
'bsLangGetCompleted',
|
'bsLangGetCompleted',
|
||||||
{'b': app.build_number},
|
{'b': app.env.build_number},
|
||||||
callback=bui.WeakCall(self._completed_langs_cb),
|
callback=bui.WeakCall(self._completed_langs_cb),
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -322,7 +322,10 @@ class AdvancedSettingsWindow(bui.Window):
|
|||||||
|
|
||||||
with open(
|
with open(
|
||||||
os.path.join(
|
os.path.join(
|
||||||
bui.app.data_directory, 'ba_data', 'data', 'langdata.json'
|
bui.app.env.data_directory,
|
||||||
|
'ba_data',
|
||||||
|
'data',
|
||||||
|
'langdata.json',
|
||||||
),
|
),
|
||||||
encoding='utf-8',
|
encoding='utf-8',
|
||||||
) as infile:
|
) as infile:
|
||||||
|
|||||||
@ -47,14 +47,14 @@ class ControlsSettingsWindow(bui.Window):
|
|||||||
|
|
||||||
space_height = spacing * 0.3
|
space_height = spacing * 0.3
|
||||||
|
|
||||||
# FIXME: should create vis settings in platform for these,
|
# FIXME: should create vis settings under platform or app-adapter
|
||||||
# not hard code them here.
|
# to determine whether to show this stuff; not hard code it.
|
||||||
|
|
||||||
show_gamepads = False
|
show_gamepads = False
|
||||||
platform = app.classic.platform
|
platform = app.classic.platform
|
||||||
subplatform = app.classic.subplatform
|
subplatform = app.classic.subplatform
|
||||||
non_vr_windows = platform == 'windows' and (
|
non_vr_windows = platform == 'windows' and (
|
||||||
subplatform != 'oculus' or not app.vr_mode
|
subplatform != 'oculus' or not app.env.vr
|
||||||
)
|
)
|
||||||
if platform in ('linux', 'android', 'mac') or non_vr_windows:
|
if platform in ('linux', 'android', 'mac') or non_vr_windows:
|
||||||
show_gamepads = True
|
show_gamepads = True
|
||||||
@ -70,11 +70,12 @@ class ControlsSettingsWindow(bui.Window):
|
|||||||
show_space_1 = True
|
show_space_1 = True
|
||||||
height += space_height
|
height += space_height
|
||||||
|
|
||||||
|
print('hello')
|
||||||
show_keyboard = False
|
show_keyboard = False
|
||||||
if bs.getinputdevice('Keyboard', '#1', doraise=False) is not None:
|
if bs.getinputdevice('Keyboard', '#1', doraise=False) is not None:
|
||||||
show_keyboard = True
|
show_keyboard = True
|
||||||
height += spacing
|
height += spacing
|
||||||
show_keyboard_p2 = False if app.vr_mode else show_keyboard
|
show_keyboard_p2 = False if app.env.vr else show_keyboard
|
||||||
if show_keyboard_p2:
|
if show_keyboard_p2:
|
||||||
height += spacing
|
height += spacing
|
||||||
|
|
||||||
@ -91,7 +92,7 @@ class ControlsSettingsWindow(bui.Window):
|
|||||||
|
|
||||||
# On windows (outside of oculus/vr), show an option to disable xinput.
|
# On windows (outside of oculus/vr), show an option to disable xinput.
|
||||||
show_xinput_toggle = False
|
show_xinput_toggle = False
|
||||||
if platform == 'windows' and not app.vr_mode:
|
if platform == 'windows' and not app.env.vr:
|
||||||
show_xinput_toggle = True
|
show_xinput_toggle = True
|
||||||
|
|
||||||
# On mac builds, show an option to switch between generic and
|
# On mac builds, show an option to switch between generic and
|
||||||
@ -352,6 +353,7 @@ class ControlsSettingsWindow(bui.Window):
|
|||||||
maxwidth=width * 0.8,
|
maxwidth=width * 0.8,
|
||||||
)
|
)
|
||||||
v -= spacing * 1.5
|
v -= spacing * 1.5
|
||||||
|
|
||||||
self._restore_state()
|
self._restore_state()
|
||||||
|
|
||||||
def _set_mac_controller_subsystem(self, val: str) -> None:
|
def _set_mac_controller_subsystem(self, val: str) -> None:
|
||||||
|
|||||||
@ -829,7 +829,7 @@ class GamepadSettingsWindow(bui.Window):
|
|||||||
'controllerConfig',
|
'controllerConfig',
|
||||||
{
|
{
|
||||||
'ua': classic.legacy_user_agent_string,
|
'ua': classic.legacy_user_agent_string,
|
||||||
'b': bui.app.build_number,
|
'b': bui.app.env.build_number,
|
||||||
'name': self._name,
|
'name': self._name,
|
||||||
'inputMapHash': inputhash,
|
'inputMapHash': inputhash,
|
||||||
'config': dst2,
|
'config': dst2,
|
||||||
|
|||||||
@ -91,7 +91,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||||||
self._sub_height = (
|
self._sub_height = (
|
||||||
940 if self._parent_window.get_is_secondary() else 1040
|
940 if self._parent_window.get_is_secondary() else 1040
|
||||||
)
|
)
|
||||||
if app.vr_mode:
|
if app.env.vr:
|
||||||
self._sub_height += 50
|
self._sub_height += 50
|
||||||
self._scrollwidget = bui.scrollwidget(
|
self._scrollwidget = bui.scrollwidget(
|
||||||
parent=self._root_widget,
|
parent=self._root_widget,
|
||||||
@ -183,7 +183,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# in vr mode, allow assigning a reset-view button
|
# in vr mode, allow assigning a reset-view button
|
||||||
if app.vr_mode:
|
if app.env.vr:
|
||||||
v -= 50
|
v -= 50
|
||||||
self._capture_button(
|
self._capture_button(
|
||||||
pos=(h2, v),
|
pos=(h2, v),
|
||||||
|
|||||||
@ -61,7 +61,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||||||
show_vsync = True
|
show_vsync = True
|
||||||
|
|
||||||
show_resolution = True
|
show_resolution = True
|
||||||
if app.vr_mode:
|
if app.env.vr:
|
||||||
show_resolution = (
|
show_resolution = (
|
||||||
app.classic.platform == 'android'
|
app.classic.platform == 'android'
|
||||||
and app.classic.subplatform == 'cardboard'
|
and app.classic.subplatform == 'cardboard'
|
||||||
@ -400,7 +400,7 @@ class GraphicsSettingsWindow(bui.Window):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# (tv mode doesnt apply to vr)
|
# (tv mode doesnt apply to vr)
|
||||||
if not bui.app.vr_mode:
|
if not bui.app.env.vr:
|
||||||
tvc = ConfigCheckBox(
|
tvc = ConfigCheckBox(
|
||||||
parent=self._root_widget,
|
parent=self._root_widget,
|
||||||
position=(240, v - 6),
|
position=(240, v - 6),
|
||||||
|
|||||||
@ -301,7 +301,7 @@ class ConfigKeyboardWindow(bui.Window):
|
|||||||
{
|
{
|
||||||
'ua': bui.app.classic.legacy_user_agent_string,
|
'ua': bui.app.classic.legacy_user_agent_string,
|
||||||
'name': self._name,
|
'name': self._name,
|
||||||
'b': bui.app.build_number,
|
'b': bui.app.env.build_number,
|
||||||
'config': dst2,
|
'config': dst2,
|
||||||
'v': 2,
|
'v': 2,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -44,14 +44,14 @@ class SpecialOfferWindow(bui.Window):
|
|||||||
real_price = plus.get_price(
|
real_price = plus.get_price(
|
||||||
'pro' if offer['item'] == 'pro_fullprice' else 'pro_sale'
|
'pro' if offer['item'] == 'pro_fullprice' else 'pro_sale'
|
||||||
)
|
)
|
||||||
if real_price is None and bui.app.debug_build:
|
if real_price is None and bui.app.env.debug:
|
||||||
print('NOTE: Faking prices for debug build.')
|
print('NOTE: Faking prices for debug build.')
|
||||||
real_price = '$1.23'
|
real_price = '$1.23'
|
||||||
zombie = real_price is None
|
zombie = real_price is None
|
||||||
elif isinstance(offer['price'], str):
|
elif isinstance(offer['price'], str):
|
||||||
# (a string price implies IAP id)
|
# (a string price implies IAP id)
|
||||||
real_price = plus.get_price(offer['price'])
|
real_price = plus.get_price(offer['price'])
|
||||||
if real_price is None and bui.app.debug_build:
|
if real_price is None and bui.app.env.debug:
|
||||||
print('NOTE: Faking price for debug build.')
|
print('NOTE: Faking price for debug build.')
|
||||||
real_price = '$1.23'
|
real_price = '$1.23'
|
||||||
zombie = real_price is None
|
zombie = real_price is None
|
||||||
|
|||||||
@ -566,8 +566,8 @@ class StoreBrowserWindow(bui.Window):
|
|||||||
'item': item,
|
'item': item,
|
||||||
'platform': app.classic.platform,
|
'platform': app.classic.platform,
|
||||||
'subplatform': app.classic.subplatform,
|
'subplatform': app.classic.subplatform,
|
||||||
'version': app.version,
|
'version': app.env.version,
|
||||||
'buildNumber': app.build_number,
|
'buildNumber': app.env.build_number,
|
||||||
'purchaseType': 'ticket' if is_ticket_purchase else 'real',
|
'purchaseType': 'ticket' if is_ticket_purchase else 'real',
|
||||||
},
|
},
|
||||||
callback=bui.WeakCall(
|
callback=bui.WeakCall(
|
||||||
|
|||||||
@ -715,5 +715,10 @@ void BaseFeatureSet::DoPushObjCall(const PythonObjectSetBase* objset, int id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto BaseFeatureSet::IsAppStarted() const -> bool { return app_started_; }
|
auto BaseFeatureSet::IsAppStarted() const -> bool { return app_started_; }
|
||||||
|
void BaseFeatureSet::ShutdownSuppressBegin() { shutdown_suppress_count_++; }
|
||||||
|
void BaseFeatureSet::ShutdownSuppressEnd() {
|
||||||
|
shutdown_suppress_count_--;
|
||||||
|
assert(shutdown_suppress_count_ >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace ballistica::base
|
} // namespace ballistica::base
|
||||||
|
|||||||
@ -690,6 +690,9 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
|||||||
void DoPushObjCall(const PythonObjectSetBase* objset, int id,
|
void DoPushObjCall(const PythonObjectSetBase* objset, int id,
|
||||||
const std::string& arg) override;
|
const std::string& arg) override;
|
||||||
void OnReachedEndOfBaBaseImport();
|
void OnReachedEndOfBaBaseImport();
|
||||||
|
void ShutdownSuppressBegin();
|
||||||
|
void ShutdownSuppressEnd();
|
||||||
|
auto shutdown_suppress_count() const { return shutdown_suppress_count_; }
|
||||||
|
|
||||||
/// Called in the logic thread once our screen is up and assets are
|
/// Called in the logic thread once our screen is up and assets are
|
||||||
/// loading.
|
/// loading.
|
||||||
@ -748,6 +751,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
|||||||
StressTest* stress_test_;
|
StressTest* stress_test_;
|
||||||
|
|
||||||
std::string console_startup_messages_;
|
std::string console_startup_messages_;
|
||||||
|
int shutdown_suppress_count_{};
|
||||||
bool tried_importing_plus_{};
|
bool tried_importing_plus_{};
|
||||||
bool tried_importing_classic_{};
|
bool tried_importing_classic_{};
|
||||||
bool tried_importing_ui_v1_{};
|
bool tried_importing_ui_v1_{};
|
||||||
|
|||||||
@ -1931,8 +1931,10 @@ auto Graphics::ScreenMessageEntry::GetText() -> TextGroup& {
|
|||||||
return *s_mesh_;
|
return *s_mesh_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Graphics::OnScreenSizeChange(float virtual_width, float virtual_height,
|
void Graphics::OnScreenSizeChange() {}
|
||||||
float pixel_width, float pixel_height) {
|
|
||||||
|
void Graphics::SetScreenSize(float virtual_width, float virtual_height,
|
||||||
|
float pixel_width, float pixel_height) {
|
||||||
assert(g_base->InLogicThread());
|
assert(g_base->InLogicThread());
|
||||||
res_x_virtual_ = virtual_width;
|
res_x_virtual_ = virtual_width;
|
||||||
res_y_virtual_ = virtual_height;
|
res_y_virtual_ = virtual_height;
|
||||||
|
|||||||
@ -54,9 +54,11 @@ class Graphics {
|
|||||||
void OnAppPause();
|
void OnAppPause();
|
||||||
void OnAppResume();
|
void OnAppResume();
|
||||||
void OnAppShutdown();
|
void OnAppShutdown();
|
||||||
|
void OnScreenSizeChange();
|
||||||
void DoApplyAppConfig();
|
void DoApplyAppConfig();
|
||||||
void OnScreenSizeChange(float virtual_width, float virtual_height,
|
|
||||||
float physical_width, float physical_height);
|
void SetScreenSize(float virtual_width, float virtual_height,
|
||||||
|
float physical_width, float physical_height);
|
||||||
void StepDisplayTime();
|
void StepDisplayTime();
|
||||||
|
|
||||||
static auto IsShaderTransparent(ShadingType c) -> bool;
|
static auto IsShaderTransparent(ShadingType c) -> bool;
|
||||||
|
|||||||
@ -419,9 +419,10 @@ void GraphicsServer::HandleFullContextScreenRebuild(
|
|||||||
|
|
||||||
UpdateVirtualScreenRes();
|
UpdateVirtualScreenRes();
|
||||||
|
|
||||||
// Inform the logic thread of the latest values.
|
// Inform graphics client and logic thread subsystems of the change.
|
||||||
g_base->logic->event_loop()->PushCall(
|
g_base->logic->event_loop()->PushCall(
|
||||||
[vx = res_x_virtual_, vy = res_y_virtual_, x = res_x_, y = res_y_] {
|
[vx = res_x_virtual_, vy = res_y_virtual_, x = res_x_, y = res_y_] {
|
||||||
|
g_base->graphics->SetScreenSize(vx, vy, x, y);
|
||||||
g_base->logic->OnScreenSizeChange(vx, vy, x, y);
|
g_base->logic->OnScreenSizeChange(vx, vy, x, y);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -568,9 +569,10 @@ void GraphicsServer::SetScreenResolution(float h, float v) {
|
|||||||
renderer_->ScreenSizeChanged();
|
renderer_->ScreenSizeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inform logic thread of the change.
|
// Inform graphics client and logic thread subsystems of the change.
|
||||||
g_base->logic->event_loop()->PushCall(
|
g_base->logic->event_loop()->PushCall(
|
||||||
[vx = res_x_virtual_, vy = res_y_virtual_, x = res_x_, y = res_y_] {
|
[vx = res_x_virtual_, vy = res_y_virtual_, x = res_x_, y = res_y_] {
|
||||||
|
g_base->graphics->SetScreenSize(vx, vy, x, y);
|
||||||
g_base->logic->OnScreenSizeChange(vx, vy, x, y);
|
g_base->logic->OnScreenSizeChange(vx, vy, x, y);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "ballistica/base/audio/audio.h"
|
#include "ballistica/base/audio/audio.h"
|
||||||
#include "ballistica/base/input/input.h"
|
#include "ballistica/base/input/input.h"
|
||||||
#include "ballistica/base/networking/networking.h"
|
#include "ballistica/base/networking/networking.h"
|
||||||
|
#include "ballistica/base/platform/base_platform.h"
|
||||||
#include "ballistica/base/python/base_python.h"
|
#include "ballistica/base/python/base_python.h"
|
||||||
#include "ballistica/base/support/plus_soft.h"
|
#include "ballistica/base/support/plus_soft.h"
|
||||||
#include "ballistica/base/support/stdio_console.h"
|
#include "ballistica/base/support/stdio_console.h"
|
||||||
@ -58,6 +59,7 @@ void Logic::OnAppStart() {
|
|||||||
// it will be the most variable; that way it will interact with other
|
// it will be the most variable; that way it will interact with other
|
||||||
// subsystems in their normal states which is less likely to lead to
|
// subsystems in their normal states which is less likely to lead to
|
||||||
// problems.
|
// problems.
|
||||||
|
g_base->platform->OnAppStart();
|
||||||
g_base->graphics->OnAppStart();
|
g_base->graphics->OnAppStart();
|
||||||
g_base->audio->OnAppStart();
|
g_base->audio->OnAppStart();
|
||||||
g_base->input->OnAppStart();
|
g_base->input->OnAppStart();
|
||||||
@ -184,6 +186,7 @@ void Logic::OnAppPause() {
|
|||||||
g_base->input->OnAppPause();
|
g_base->input->OnAppPause();
|
||||||
g_base->audio->OnAppPause();
|
g_base->audio->OnAppPause();
|
||||||
g_base->graphics->OnAppPause();
|
g_base->graphics->OnAppPause();
|
||||||
|
g_base->platform->OnAppPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Logic::OnAppResume() {
|
void Logic::OnAppResume() {
|
||||||
@ -191,6 +194,7 @@ void Logic::OnAppResume() {
|
|||||||
assert(g_base->CurrentContext().IsEmpty());
|
assert(g_base->CurrentContext().IsEmpty());
|
||||||
|
|
||||||
// Note: keep these in the same order as OnAppStart.
|
// Note: keep these in the same order as OnAppStart.
|
||||||
|
g_base->platform->OnAppResume();
|
||||||
g_base->graphics->OnAppResume();
|
g_base->graphics->OnAppResume();
|
||||||
g_base->audio->OnAppResume();
|
g_base->audio->OnAppResume();
|
||||||
g_base->input->OnAppResume();
|
g_base->input->OnAppResume();
|
||||||
@ -235,6 +239,7 @@ void Logic::OnAppShutdown() {
|
|||||||
g_base->input->OnAppShutdown();
|
g_base->input->OnAppShutdown();
|
||||||
g_base->audio->OnAppShutdown();
|
g_base->audio->OnAppShutdown();
|
||||||
g_base->graphics->OnAppShutdown();
|
g_base->graphics->OnAppShutdown();
|
||||||
|
g_base->platform->OnAppShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Logic::CompleteShutdown() {
|
void Logic::CompleteShutdown() {
|
||||||
@ -283,12 +288,10 @@ void Logic::OnScreenSizeChange(float virtual_width, float virtual_height,
|
|||||||
float pixel_width, float pixel_height) {
|
float pixel_width, float pixel_height) {
|
||||||
assert(g_base->InLogicThread());
|
assert(g_base->InLogicThread());
|
||||||
|
|
||||||
// First, pass the new values to the graphics subsystem. Then inform
|
// Inform all subsystems.
|
||||||
// everyone else simply that they changed; they can ask g_graphics for
|
// Note: keep these in the same order as OnAppStart.
|
||||||
// whatever specific values they need. Note: keep these in the same order
|
g_base->platform->OnScreenSizeChange();
|
||||||
// as OnAppStart.
|
g_base->graphics->OnScreenSizeChange();
|
||||||
g_base->graphics->OnScreenSizeChange(virtual_width, virtual_height,
|
|
||||||
pixel_width, pixel_height);
|
|
||||||
g_base->audio->OnScreenSizeChange();
|
g_base->audio->OnScreenSizeChange();
|
||||||
g_base->input->OnScreenSizeChange();
|
g_base->input->OnScreenSizeChange();
|
||||||
g_base->ui->OnScreenSizeChange();
|
g_base->ui->OnScreenSizeChange();
|
||||||
|
|||||||
@ -314,4 +314,11 @@ void BasePlatform::GetCursorPosition(float* x, float* y) {
|
|||||||
|
|
||||||
void BasePlatform::OnMainThreadStartAppComplete() {}
|
void BasePlatform::OnMainThreadStartAppComplete() {}
|
||||||
|
|
||||||
|
void BasePlatform::OnAppStart() { assert(g_base->InLogicThread()); }
|
||||||
|
void BasePlatform::OnAppPause() { assert(g_base->InLogicThread()); }
|
||||||
|
void BasePlatform::OnAppResume() { assert(g_base->InLogicThread()); }
|
||||||
|
void BasePlatform::OnAppShutdown() { assert(g_base->InLogicThread()); }
|
||||||
|
void BasePlatform::OnScreenSizeChange() { assert(g_base->InLogicThread()); }
|
||||||
|
void BasePlatform::DoApplyAppConfig() { assert(g_base->InLogicThread()); }
|
||||||
|
|
||||||
} // namespace ballistica::base
|
} // namespace ballistica::base
|
||||||
|
|||||||
@ -26,6 +26,13 @@ class BasePlatform {
|
|||||||
/// start talking to them.
|
/// start talking to them.
|
||||||
virtual void OnMainThreadStartAppComplete();
|
virtual void OnMainThreadStartAppComplete();
|
||||||
|
|
||||||
|
virtual void OnAppStart();
|
||||||
|
virtual void OnAppPause();
|
||||||
|
virtual void OnAppResume();
|
||||||
|
virtual void OnAppShutdown();
|
||||||
|
virtual void OnScreenSizeChange();
|
||||||
|
virtual void DoApplyAppConfig();
|
||||||
|
|
||||||
#pragma mark IN APP PURCHASES --------------------------------------------------
|
#pragma mark IN APP PURCHASES --------------------------------------------------
|
||||||
|
|
||||||
void Purchase(const std::string& item);
|
void Purchase(const std::string& item);
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
#include "ballistica/base/python/class/python_class_context_call.h"
|
#include "ballistica/base/python/class/python_class_context_call.h"
|
||||||
#include "ballistica/base/python/class/python_class_context_ref.h"
|
#include "ballistica/base/python/class/python_class_context_ref.h"
|
||||||
#include "ballistica/base/python/class/python_class_display_timer.h"
|
#include "ballistica/base/python/class/python_class_display_timer.h"
|
||||||
|
#include "ballistica/base/python/class/python_class_env.h"
|
||||||
#include "ballistica/base/python/class/python_class_feature_set_data.h"
|
#include "ballistica/base/python/class/python_class_feature_set_data.h"
|
||||||
#include "ballistica/base/python/class/python_class_simple_sound.h"
|
#include "ballistica/base/python/class/python_class_simple_sound.h"
|
||||||
#include "ballistica/base/python/class/python_class_vec3.h"
|
#include "ballistica/base/python/class/python_class_vec3.h"
|
||||||
@ -45,6 +46,7 @@ void BasePython::AddPythonClasses(PyObject* module) {
|
|||||||
PythonModuleBuilder::AddClass<PythonClassContextRef>(module);
|
PythonModuleBuilder::AddClass<PythonClassContextRef>(module);
|
||||||
PythonModuleBuilder::AddClass<PythonClassAppTimer>(module);
|
PythonModuleBuilder::AddClass<PythonClassAppTimer>(module);
|
||||||
PythonModuleBuilder::AddClass<PythonClassDisplayTimer>(module);
|
PythonModuleBuilder::AddClass<PythonClassDisplayTimer>(module);
|
||||||
|
PythonModuleBuilder::AddClass<PythonClassEnv>(module);
|
||||||
PythonModuleBuilder::AddClass<PythonClassSimpleSound>(module);
|
PythonModuleBuilder::AddClass<PythonClassSimpleSound>(module);
|
||||||
PythonModuleBuilder::AddClass<PythonClassContextCall>(module);
|
PythonModuleBuilder::AddClass<PythonClassContextCall>(module);
|
||||||
PyObject* vec3 = PythonModuleBuilder::AddClass<PythonClassVec3>(module);
|
PyObject* vec3 = PythonModuleBuilder::AddClass<PythonClassVec3>(module);
|
||||||
|
|||||||
223
src/ballistica/base/python/class/python_class_env.cc
Normal file
223
src/ballistica/base/python/class/python_class_env.cc
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
// Released under the MIT License. See LICENSE for details.
|
||||||
|
|
||||||
|
#include "ballistica/base/python/class/python_class_env.h"
|
||||||
|
|
||||||
|
#include "ballistica/base/base.h"
|
||||||
|
#include "ballistica/core/platform/core_platform.h"
|
||||||
|
namespace ballistica::base {
|
||||||
|
|
||||||
|
struct EnvEntry_ {
|
||||||
|
PyObject* obj;
|
||||||
|
const char* typestr;
|
||||||
|
const char* docs;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::map<std::string, EnvEntry_>* g_entries_{};
|
||||||
|
|
||||||
|
auto PythonClassEnv::type_name() -> const char* { return "Env"; }
|
||||||
|
|
||||||
|
static auto BoolEntry_(bool val, const char* docs) -> EnvEntry_ {
|
||||||
|
PyObject* pyval = val ? Py_True : Py_False;
|
||||||
|
Py_INCREF(pyval);
|
||||||
|
return {pyval, "bool", docs};
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto StrEntry_(const char* val, const char* docs) -> EnvEntry_ {
|
||||||
|
return {PyUnicode_FromString(val), "str", docs};
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto OptionalStrEntry_(const char* val, const char* docs) -> EnvEntry_ {
|
||||||
|
if (val) {
|
||||||
|
return {PyUnicode_FromString(val), "str | None", docs};
|
||||||
|
} else {
|
||||||
|
Py_INCREF(Py_None);
|
||||||
|
return {Py_None, "str | None", docs};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static auto IntEntry_(int val, const char* docs) -> EnvEntry_ {
|
||||||
|
return {PyLong_FromLong(val), "int", docs};
|
||||||
|
}
|
||||||
|
|
||||||
|
void PythonClassEnv::SetupType(PyTypeObject* cls) {
|
||||||
|
// Dynamically allocate this since Python needs to keep it around.
|
||||||
|
auto* docsptr = new std::string(
|
||||||
|
"Unchanging values for the current running app instance.\n"
|
||||||
|
"Access the single shared instance of this class at `babase.app.env`.\n"
|
||||||
|
"\n"
|
||||||
|
"Attributes:\n");
|
||||||
|
auto& docs{*docsptr};
|
||||||
|
|
||||||
|
// Populate our static entries dict. We'll generate Python class docs
|
||||||
|
// from that so we don't have to manually keep doc strings in sync.
|
||||||
|
assert(!g_entries_);
|
||||||
|
assert(Python::HaveGIL());
|
||||||
|
g_entries_ = new std::map<std::string, EnvEntry_>();
|
||||||
|
auto& envs{*g_entries_};
|
||||||
|
|
||||||
|
envs["android"] = BoolEntry_(g_buildconfig.ostype_android(),
|
||||||
|
"Is this build targeting an Android based OS?");
|
||||||
|
|
||||||
|
envs["build_number"] = IntEntry_(
|
||||||
|
kEngineBuildNumber,
|
||||||
|
"Integer build number for the engine.\n"
|
||||||
|
"\n"
|
||||||
|
"This value increases by at least 1 with each release of the engine.\n"
|
||||||
|
"It is independent of the human readable `version` string.");
|
||||||
|
|
||||||
|
envs["version"] = StrEntry_(
|
||||||
|
kEngineVersion,
|
||||||
|
"Human-readable version string for the engine; something like '1.3.24'.\n"
|
||||||
|
"\n"
|
||||||
|
"This should not be interpreted as a number; it may contain\n"
|
||||||
|
"string elements such as 'alpha', 'beta', 'test', etc.\n"
|
||||||
|
"If a numeric version is needed, use `build_number`.");
|
||||||
|
|
||||||
|
envs["device_name"] =
|
||||||
|
StrEntry_(g_core->platform->GetDeviceName().c_str(),
|
||||||
|
"Human readable name of the device running this app.");
|
||||||
|
|
||||||
|
envs["supports_soft_quit"] = BoolEntry_(
|
||||||
|
g_buildconfig.ostype_android() || g_buildconfig.ostype_ios_tvos(),
|
||||||
|
"Whether the running app supports 'soft' quit options.\n"
|
||||||
|
"\n"
|
||||||
|
"This generally applies to mobile derived OSs, where an act of\n"
|
||||||
|
"'quitting' may leave the app running in the background waiting\n"
|
||||||
|
"in case it is used again.");
|
||||||
|
|
||||||
|
envs["debug"] = BoolEntry_(
|
||||||
|
g_buildconfig.debug_build(),
|
||||||
|
"Whether the app is running in debug mode.\n"
|
||||||
|
"\n"
|
||||||
|
"Debug builds generally run substantially slower than non-debug\n"
|
||||||
|
"builds due to compiler optimizations being disabled and extra\n"
|
||||||
|
"checks being run.");
|
||||||
|
|
||||||
|
envs["test"] = BoolEntry_(
|
||||||
|
g_buildconfig.test_build(),
|
||||||
|
"Whether the app is running in test mode.\n"
|
||||||
|
"\n"
|
||||||
|
"Test mode enables extra checks and features that are useful for\n"
|
||||||
|
"release testing but which do not slow the game down significantly.");
|
||||||
|
|
||||||
|
envs["config_file_path"] =
|
||||||
|
StrEntry_(g_core->platform->GetConfigFilePath().c_str(),
|
||||||
|
"Where the app's config file is stored on disk.");
|
||||||
|
|
||||||
|
envs["data_directory"] = StrEntry_(g_core->GetDataDirectory().c_str(),
|
||||||
|
"Where bundled static app data lives.");
|
||||||
|
|
||||||
|
envs["api_version"] = IntEntry_(
|
||||||
|
kEngineApiVersion,
|
||||||
|
"The app's api version.\n"
|
||||||
|
"\n"
|
||||||
|
"Only Python modules and packages associated with the current API\n"
|
||||||
|
"version number will be detected by the game (see the ba_meta tag).\n"
|
||||||
|
"This value will change whenever substantial backward-incompatible\n"
|
||||||
|
"changes are introduced to Ballistica APIs. When that happens,\n"
|
||||||
|
"modules/packages should be updated accordingly and set to target\n"
|
||||||
|
"the newer API version number.");
|
||||||
|
|
||||||
|
std::optional<std::string> user_py_dir = g_core->GetUserPythonDirectory();
|
||||||
|
envs["python_directory_user"] = OptionalStrEntry_(
|
||||||
|
user_py_dir ? user_py_dir->c_str() : nullptr,
|
||||||
|
"Path where the app expects its user scripts (mods) to live.\n"
|
||||||
|
"\n"
|
||||||
|
"Be aware that this value may be None if Ballistica is running in\n"
|
||||||
|
"a non-standard environment, and that python-path modifications may\n"
|
||||||
|
"cause modules to be loaded from other locations.");
|
||||||
|
|
||||||
|
std::optional<std::string> app_py_dir = g_core->GetAppPythonDirectory();
|
||||||
|
envs["python_directory_app"] = OptionalStrEntry_(
|
||||||
|
app_py_dir ? app_py_dir->c_str() : nullptr,
|
||||||
|
"Path where the app expects its bundled modules to live.\n"
|
||||||
|
"\n"
|
||||||
|
"Be aware that this value may be None if Ballistica is running in\n"
|
||||||
|
"a non-standard environment, and that python-path modifications may\n"
|
||||||
|
"cause modules to be loaded from other locations.");
|
||||||
|
|
||||||
|
std::optional<std::string> site_py_dir = g_core->GetSitePythonDirectory();
|
||||||
|
envs["python_directory_app_site"] = OptionalStrEntry_(
|
||||||
|
site_py_dir ? site_py_dir->c_str() : nullptr,
|
||||||
|
"Path where the app expects its bundled pip modules to live.\n"
|
||||||
|
"\n"
|
||||||
|
"Be aware that this value may be None if Ballistica is running in\n"
|
||||||
|
"a non-standard environment, and that python-path modifications may\n"
|
||||||
|
"cause modules to be loaded from other locations.");
|
||||||
|
|
||||||
|
envs["tv"] = BoolEntry_(g_core->platform->IsRunningOnTV(),
|
||||||
|
"Whether the app is currently running on a TV.");
|
||||||
|
|
||||||
|
envs["vr"] = BoolEntry_(g_core->IsVRMode(),
|
||||||
|
"Whether the app is currently running in VR.");
|
||||||
|
bool first = true;
|
||||||
|
for (auto&& entry : envs) {
|
||||||
|
if (!first) {
|
||||||
|
docs += "\n";
|
||||||
|
}
|
||||||
|
docs += " " + entry.first + " (" + entry.second.typestr + "):\n "
|
||||||
|
+ entry.second.docs + "\n";
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PythonClass::SetupType(cls);
|
||||||
|
// Fully qualified type path we will be exposed as:
|
||||||
|
cls->tp_name = "babase.Env";
|
||||||
|
cls->tp_basicsize = sizeof(PythonClassEnv);
|
||||||
|
cls->tp_doc = docs.c_str();
|
||||||
|
cls->tp_new = tp_new;
|
||||||
|
cls->tp_dealloc = (destructor)tp_dealloc;
|
||||||
|
cls->tp_getattro = (getattrofunc)tp_getattro;
|
||||||
|
cls->tp_methods = tp_methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PythonClassEnv::tp_new(PyTypeObject* type, PyObject* args,
|
||||||
|
PyObject* keywds) -> PyObject* {
|
||||||
|
auto* self = type->tp_alloc(type, 0);
|
||||||
|
if (!self) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
// Using placement new here. Remember that this means we can be destructed
|
||||||
|
// in any thread. If that's a problem we need to move to manual
|
||||||
|
// allocation/deallocation so we can push deallocation to a specific
|
||||||
|
// thread.
|
||||||
|
new (self) PythonClassEnv();
|
||||||
|
return self;
|
||||||
|
BA_PYTHON_NEW_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PythonClassEnv::tp_dealloc(PythonClassEnv* self) {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
self->~PythonClassEnv();
|
||||||
|
BA_PYTHON_DEALLOC_CATCH;
|
||||||
|
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto PythonClassEnv::tp_getattro(PythonClassEnv* self, PyObject* attr)
|
||||||
|
-> PyObject* {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
|
||||||
|
// Do we need to support other attr types?
|
||||||
|
assert(PyUnicode_Check(attr));
|
||||||
|
|
||||||
|
auto&& entry = (*g_entries_).find(PyUnicode_AsUTF8(attr));
|
||||||
|
if (entry != g_entries_->end()) {
|
||||||
|
Py_INCREF(entry->second.obj);
|
||||||
|
return entry->second.obj;
|
||||||
|
} else {
|
||||||
|
return PyObject_GenericGetAttr(reinterpret_cast<PyObject*>(self), attr);
|
||||||
|
}
|
||||||
|
BA_PYTHON_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
PythonClassEnv::PythonClassEnv() = default;
|
||||||
|
|
||||||
|
PythonClassEnv::~PythonClassEnv() = default;
|
||||||
|
|
||||||
|
PyTypeObject PythonClassEnv::type_obj;
|
||||||
|
|
||||||
|
// Any methods for our class go here.
|
||||||
|
PyMethodDef PythonClassEnv::tp_methods[] = {{nullptr}};
|
||||||
|
|
||||||
|
} // namespace ballistica::base
|
||||||
44
src/ballistica/base/python/class/python_class_env.h
Normal file
44
src/ballistica/base/python/class/python_class_env.h
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Released under the MIT License. See LICENSE for details.
|
||||||
|
|
||||||
|
#ifndef BALLISTICA_BASE_PYTHON_CLASS_PYTHON_CLASS_ENV_H_
|
||||||
|
#define BALLISTICA_BASE_PYTHON_CLASS_PYTHON_CLASS_ENV_H_
|
||||||
|
|
||||||
|
#include "ballistica/shared/python/python.h"
|
||||||
|
#include "ballistica/shared/python/python_class.h"
|
||||||
|
|
||||||
|
namespace ballistica::base {
|
||||||
|
|
||||||
|
/// A simple example native class.
|
||||||
|
class PythonClassEnv : public PythonClass {
|
||||||
|
public:
|
||||||
|
static void SetupType(PyTypeObject* cls);
|
||||||
|
static auto type_name() -> const char*;
|
||||||
|
static auto tp_getattro(PythonClassEnv* self, PyObject* attr) -> PyObject*;
|
||||||
|
static auto Check(PyObject* o) -> bool {
|
||||||
|
return PyObject_TypeCheck(o, &type_obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cast raw Python pointer to our type; throws an exception on wrong types.
|
||||||
|
static auto FromPyObj(PyObject* o) -> PythonClassEnv& {
|
||||||
|
if (Check(o)) {
|
||||||
|
return *reinterpret_cast<PythonClassEnv*>(o);
|
||||||
|
}
|
||||||
|
throw Exception(std::string("Expected a ") + type_name() + "; got a "
|
||||||
|
+ Python::ObjTypeToString(o),
|
||||||
|
PyExcType::kType);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyTypeObject type_obj;
|
||||||
|
|
||||||
|
private:
|
||||||
|
PythonClassEnv();
|
||||||
|
~PythonClassEnv();
|
||||||
|
static PyMethodDef tp_methods[];
|
||||||
|
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||||
|
-> PyObject*;
|
||||||
|
static void tp_dealloc(PythonClassEnv* self);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ballistica::base
|
||||||
|
|
||||||
|
#endif // BALLISTICA_BASE_PYTHON_CLASS_PYTHON_CLASS_ENV_H_
|
||||||
@ -1265,7 +1265,7 @@ static PyMethodDef PyIsOSPlayingMusicDef = {
|
|||||||
"\n"
|
"\n"
|
||||||
"Tells whether the OS is currently playing music of some sort.\n"
|
"Tells whether the OS is currently playing music of some sort.\n"
|
||||||
"\n"
|
"\n"
|
||||||
"(Used to determine whether the game should avoid playing its own)",
|
"(Used to determine whether the app should avoid playing its own)",
|
||||||
};
|
};
|
||||||
|
|
||||||
// -------------------------------- exec_arg -----------------------------------
|
// -------------------------------- exec_arg -----------------------------------
|
||||||
@ -1491,6 +1491,67 @@ static PyMethodDef PyGetImmediateReturnCodeDef = {
|
|||||||
"(internal)\n",
|
"(internal)\n",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ----------------------- shutdown_suppress_begin -----------------------------
|
||||||
|
|
||||||
|
static auto PyShutdownSuppressBegin(PyObject* self) -> PyObject* {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
assert(g_base);
|
||||||
|
g_base->ShutdownSuppressBegin();
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
BA_PYTHON_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef PyShutdownSuppressBeginDef = {
|
||||||
|
"shutdown_suppress_begin", // name
|
||||||
|
(PyCFunction)PyShutdownSuppressBegin, // method
|
||||||
|
METH_NOARGS, // flags
|
||||||
|
|
||||||
|
"shutdown_suppress_begin() -> None\n"
|
||||||
|
"\n"
|
||||||
|
"(internal)\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
// ------------------------ shutdown_suppress_end ------------------------------
|
||||||
|
|
||||||
|
static auto PyShutdownSuppressEnd(PyObject* self) -> PyObject* {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
assert(g_base);
|
||||||
|
g_base->ShutdownSuppressEnd();
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
BA_PYTHON_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef PyShutdownSuppressEndDef = {
|
||||||
|
"shutdown_suppress_end", // name
|
||||||
|
(PyCFunction)PyShutdownSuppressEnd, // method
|
||||||
|
METH_NOARGS, // flags
|
||||||
|
|
||||||
|
"shutdown_suppress_end() -> None\n"
|
||||||
|
"\n"
|
||||||
|
"(internal)\n",
|
||||||
|
};
|
||||||
|
|
||||||
|
// ------------------------ shutdown_suppress_count
|
||||||
|
// ------------------------------
|
||||||
|
|
||||||
|
static auto PyShutdownSuppressCount(PyObject* self) -> PyObject* {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
assert(g_base);
|
||||||
|
return PyLong_FromLong(g_base->shutdown_suppress_count());
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
BA_PYTHON_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyMethodDef PyShutdownSuppressCountDef = {
|
||||||
|
"shutdown_suppress_count", // name
|
||||||
|
(PyCFunction)PyShutdownSuppressCount, // method
|
||||||
|
METH_NOARGS, // flags
|
||||||
|
|
||||||
|
"shutdown_suppress_count() -> int\n"
|
||||||
|
"\n"
|
||||||
|
"(internal)\n",
|
||||||
|
};
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
||||||
@ -1540,6 +1601,9 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
|||||||
PyEmptyAppModeHandleIntentExecDef,
|
PyEmptyAppModeHandleIntentExecDef,
|
||||||
PyGetImmediateReturnCodeDef,
|
PyGetImmediateReturnCodeDef,
|
||||||
PyCompleteShutdownDef,
|
PyCompleteShutdownDef,
|
||||||
|
PyShutdownSuppressBeginDef,
|
||||||
|
PyShutdownSuppressEndDef,
|
||||||
|
PyShutdownSuppressCountDef,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -134,7 +134,7 @@ auto CoreFeatureSet::core_config() const -> const CoreConfig& {
|
|||||||
// we don't interfere with low-level stuff like FatalError handling that
|
// we don't interfere with low-level stuff like FatalError handling that
|
||||||
// might need core_config access at any time.
|
// might need core_config access at any time.
|
||||||
if (!g_buildconfig.monolithic_build()) {
|
if (!g_buildconfig.monolithic_build()) {
|
||||||
if (!HaveBaEnvVals()) {
|
if (!have_ba_env_vals()) {
|
||||||
static bool did_warn = false;
|
static bool did_warn = false;
|
||||||
if (!did_warn) {
|
if (!did_warn) {
|
||||||
did_warn = true;
|
did_warn = true;
|
||||||
|
|||||||
@ -115,7 +115,7 @@ class CoreFeatureSet {
|
|||||||
/// Return true if baenv values have been locked in: python paths, log
|
/// Return true if baenv values have been locked in: python paths, log
|
||||||
/// handling, etc. Early-running code may wish to explicitly avoid making log
|
/// handling, etc. Early-running code may wish to explicitly avoid making log
|
||||||
/// calls until this condition is met to ensure predictable behavior.
|
/// calls until this condition is met to ensure predictable behavior.
|
||||||
auto HaveBaEnvVals() const { return have_ba_env_vals_; }
|
auto have_ba_env_vals() const { return have_ba_env_vals_; }
|
||||||
|
|
||||||
/// Return the directory where the app expects to find its bundled Python
|
/// Return the directory where the app expects to find its bundled Python
|
||||||
/// files.
|
/// files.
|
||||||
|
|||||||
@ -396,7 +396,7 @@ auto CorePython::FetchPythonArgs(std::vector<std::string>* buffer)
|
|||||||
// argv pointers to it.
|
// argv pointers to it.
|
||||||
std::vector<char*> out;
|
std::vector<char*> out;
|
||||||
out.reserve(buffer->size());
|
out.reserve(buffer->size());
|
||||||
for (int i = 0; i < buffer->size(); ++i) {
|
for (size_t i = 0; i < buffer->size(); ++i) {
|
||||||
out.push_back(const_cast<char*>((*buffer)[i].c_str()));
|
out.push_back(const_cast<char*>((*buffer)[i].c_str()));
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|||||||
@ -39,8 +39,9 @@ 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 = 21256;
|
const int kEngineBuildNumber = 21269;
|
||||||
const char* kEngineVersion = "1.7.26";
|
const char* kEngineVersion = "1.7.27";
|
||||||
|
const int kEngineApiVersion = 8;
|
||||||
|
|
||||||
#if BA_MONOLITHIC_BUILD
|
#if BA_MONOLITHIC_BUILD
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,7 @@ namespace ballistica {
|
|||||||
|
|
||||||
extern const int kEngineBuildNumber;
|
extern const int kEngineBuildNumber;
|
||||||
extern const char* kEngineVersion;
|
extern const char* kEngineVersion;
|
||||||
|
extern const int kEngineApiVersion;
|
||||||
|
|
||||||
// Protocol version we host games with and write replays to.
|
// Protocol version we host games with and write replays to.
|
||||||
// This should be incremented whenever there are changes made to the
|
// This should be incremented whenever there are changes made to the
|
||||||
|
|||||||
@ -35,16 +35,15 @@ auto PythonClassHello::tp_new(PyTypeObject* type, PyObject* args,
|
|||||||
void PythonClassHello::tp_dealloc(PythonClassHello* self) {
|
void PythonClassHello::tp_dealloc(PythonClassHello* self) {
|
||||||
BA_PYTHON_TRY;
|
BA_PYTHON_TRY;
|
||||||
|
|
||||||
// Because we used placement-new we need to manually run the equivalent
|
// Because we used placement-new, we need to manually run the equivalent
|
||||||
// destructor to balance things. Note that if anything goes wrong here it'll
|
// destructor to clean ourself up. Note that if anything goes wrong here
|
||||||
// simply print an error; we don't set any Python error state. Not sure if
|
// it'll simply print an error; we don't set any Python error state. Not
|
||||||
// that is ever even allowed from destructors anyway.
|
// sure if that is ever even allowed from destructors anyway.
|
||||||
|
|
||||||
// IMPORTANT: With Python objects we can't guarantee that this destructor runs
|
// IMPORTANT: With Python objects we can't guarantee that this destructor
|
||||||
// in a particular thread; if our object contains anything that must be
|
// runs in a particular thread, so if that is something we need then we
|
||||||
// destructed in a particular thread then we should manually allocate &
|
// should manually allocate stuff in tp_new and then ship a pointer off
|
||||||
// deallocate things so we can ship it off to the proper thread for cleanup as
|
// from here to whatever thread needs to clean it up.
|
||||||
// needed.
|
|
||||||
self->~PythonClassHello();
|
self->~PythonClassHello();
|
||||||
BA_PYTHON_DEALLOC_CATCH;
|
BA_PYTHON_DEALLOC_CATCH;
|
||||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||||
@ -58,7 +57,32 @@ PythonClassHello::~PythonClassHello() {
|
|||||||
Log(LogLevel::kInfo, "Goodbye from PythonClassHello destructor!!!");
|
Log(LogLevel::kInfo, "Goodbye from PythonClassHello destructor!!!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto PythonClassHello::TestMethod(PythonClassHello* self, PyObject* args,
|
||||||
|
PyObject* keywds) -> PyObject* {
|
||||||
|
BA_PYTHON_TRY;
|
||||||
|
int val{};
|
||||||
|
static const char* kwlist[] = {"val", nullptr};
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|i",
|
||||||
|
const_cast<char**>(kwlist), &val)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
Log(LogLevel::kInfo, "Hello from PythonClassHello.test_method!!! (val="
|
||||||
|
+ std::to_string(val) + ")");
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
BA_PYTHON_CATCH;
|
||||||
|
}
|
||||||
|
|
||||||
PyTypeObject PythonClassHello::type_obj;
|
PyTypeObject PythonClassHello::type_obj;
|
||||||
PyMethodDef PythonClassHello::tp_methods[] = {{nullptr}};
|
|
||||||
|
// Any methods for our class go here.
|
||||||
|
PyMethodDef PythonClassHello::tp_methods[] = {
|
||||||
|
{"testmethod", (PyCFunction)PythonClassHello::TestMethod,
|
||||||
|
METH_VARARGS | METH_KEYWORDS,
|
||||||
|
"testmethod(val: int = 0) -> None\n"
|
||||||
|
"\n"
|
||||||
|
"Just testing.\n"
|
||||||
|
""},
|
||||||
|
|
||||||
|
{nullptr}};
|
||||||
|
|
||||||
} // namespace ballistica::template_fs
|
} // namespace ballistica::template_fs
|
||||||
|
|||||||
@ -36,8 +36,8 @@ class PythonClassHello : public PythonClass {
|
|||||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||||
-> PyObject*;
|
-> PyObject*;
|
||||||
static void tp_dealloc(PythonClassHello* self);
|
static void tp_dealloc(PythonClassHello* self);
|
||||||
static auto Play(PythonClassHello* self, PyObject* args, PyObject* keywds)
|
static auto TestMethod(PythonClassHello* self, PyObject* args,
|
||||||
-> PyObject*;
|
PyObject* keywds) -> PyObject*;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ballistica::template_fs
|
} // namespace ballistica::template_fs
|
||||||
|
|||||||
@ -71,13 +71,19 @@ def get_current_version() -> tuple[str, int]:
|
|||||||
def get_current_api_version() -> int:
|
def get_current_api_version() -> int:
|
||||||
"""Pull current api version from the project."""
|
"""Pull current api version from the project."""
|
||||||
with open(
|
with open(
|
||||||
'src/assets/ba_data/python/babase/_meta.py', encoding='utf-8'
|
'src/ballistica/shared/ballistica.cc', encoding='utf-8'
|
||||||
) as infile:
|
) as infile:
|
||||||
lines = infile.readlines()
|
lines = infile.readlines()
|
||||||
linestart = 'CURRENT_API_VERSION = '
|
linestart = 'const int kEngineApiVersion = '
|
||||||
|
lineend = ';'
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if line.startswith(linestart):
|
if line.startswith(linestart):
|
||||||
return int(line.strip().removeprefix(linestart).strip())
|
return int(
|
||||||
|
line.strip()
|
||||||
|
.removeprefix(linestart)
|
||||||
|
.removesuffix(lineend)
|
||||||
|
.strip()
|
||||||
|
)
|
||||||
raise RuntimeError('Api version line not found.')
|
raise RuntimeError('Api version line not found.')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,7 @@ OPENSSL_VER_APPLE = '3.0.8'
|
|||||||
OPENSSL_VER_ANDROID = '3.0.8'
|
OPENSSL_VER_ANDROID = '3.0.8'
|
||||||
|
|
||||||
ZLIB_VER_ANDROID = '1.3'
|
ZLIB_VER_ANDROID = '1.3'
|
||||||
|
XZ_VER_ANDROID = '5.4.4'
|
||||||
|
|
||||||
# Filenames we prune from Python lib dirs in source repo to cut down on size.
|
# Filenames we prune from Python lib dirs in source repo to cut down on size.
|
||||||
PRUNE_LIB_NAMES = [
|
PRUNE_LIB_NAMES = [
|
||||||
@ -301,6 +302,14 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
|||||||
count=1,
|
count=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Set specific XZ version.
|
||||||
|
ftxt = replace_exact(
|
||||||
|
ftxt,
|
||||||
|
"source = 'https://tukaani.org/xz/xz-5.2.7.tar.xz'",
|
||||||
|
f"source = 'https://tukaani.org/xz/xz-{XZ_VER_ANDROID}.tar.xz'",
|
||||||
|
count=1,
|
||||||
|
)
|
||||||
|
|
||||||
# Give ourselves a handle to patch the OpenSSL build.
|
# Give ourselves a handle to patch the OpenSSL build.
|
||||||
ftxt = replace_exact(
|
ftxt = replace_exact(
|
||||||
ftxt,
|
ftxt,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user