mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-19 13:25:31 +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/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
|
||||
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "bb5e0df17efe96476c3c2b8c41c7979b",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "056115be35ac1afc1f62b58dcc8f217a",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cf2b052caaa06d512ef379687b3aff86",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "97b8d5f261f84b8047d43df1ca81777a",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "f304ee46675e7552c31e174d81199307",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "70b54361b557f5e8d4271a0c976b28b6",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "06c778dc4c2772acf6dbaf67eb7321c9",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "212a5c7206c1aa9a8427b61471e9e89b",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f3a1028602c7bbd3f8d62bd843af628d",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "9c4c6d1c50e225dc61cfbab4a82a37a6",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "4db4b600478f1767fdd0462a137912de",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "628bc102cf6ef17b38804c4c9baa5265",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "fb9d165ab24084e8beaa6d7c51c81a77",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "ae88283e96a9238aab7561d2afcd9a5f",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "e5fe958377b8dcf5d5203dbd17aaab72",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f22e8af184b19b4929162e901a056454",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5fa2cb24b9e78bddb1bf9efb197d0c51",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "d36f11acfa5e68f303d347c3895979d0",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "570a7e325c15ecebcc419d48a046dd24",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "3155f665993e5f850db5b87c9296abe7",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "d1bfae5e75824ba89338892bc0f84c6b",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b1466048e319c0d60139c46751f3eb79",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "d1bfae5e75824ba89338892bc0f84c6b",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "b1466048e319c0d60139c46751f3eb79",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "656176631037184b6e22b0b68c3cd1fa",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "12b633db4dad37bbb5a5c398db0c10dd",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "656176631037184b6e22b0b68c3cd1fa",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "12b633db4dad37bbb5a5c398db0c10dd",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "7e014214c6cb9ddaa0e95f5186ba9df6",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "45ddc559dd5ef563d2df5c5db9c9fbc0",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "7e014214c6cb9ddaa0e95f5186ba9df6",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "45ddc559dd5ef563d2df5c5db9c9fbc0",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "1a15bf7d809addab4992827da9d89895",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "a1e03b7d13482beab8852691b5698974",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "807e3629d9d4611cd93feb87face4e51",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "a1e03b7d13482beab8852691b5698974",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "7f7bc04993982b164f6e86ad6ce350ef",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "d24102dd35c29b6a77cdf3d9921695da",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "8edef08a22594617d2b4273e0e4cba40",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "cb6c0c6efad034b53fe1a8f930f2ce81",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "3ec4aadf128132116fc5479a46bd1f71",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "14c2cf0812e3022391caffd9409d0650",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "67c207425afc5023cea9740e3bd459c3",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "7219b9034f14c5b769818b80135ea61b",
|
||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "a039bab674a99b559440323b965d2274",
|
||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "fb15d3a3e792163f18af727447564192",
|
||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "246782dc8e1f2114c62980f8addbc4f4",
|
||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "8e59c9779e54f22b66ddfe2cd7c21528",
|
||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "0c241652d1669e3bbaf8df19c3ae756c",
|
||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "40ba4e0316c063238ab8e8b94f98351c",
|
||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "9d80b87c57556a0877f260305f571c78",
|
||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "0e7d5147a5b1b9a7ecb8e6fc4cfc1174",
|
||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "943297ef2d247451140c08816fa0b46d",
|
||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "af9ef217e000fb8e2d7fff8770b7bf44",
|
||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c0e09234f16c75313eab30d576783549",
|
||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "3f265457324e3a07a676b4db52a5f111",
|
||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "6482c468d8e798e081310c294553e4da",
|
||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c6354818c9abd243e9b9af03f1f075f7",
|
||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "43908d43f107baa521cee51af01a9583",
|
||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "c91f0c62c989a33caa7b4b4769754f1a",
|
||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "79a0eb8f637e295447150a2c1e03357d",
|
||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "5900beafe0de9b11ce4d00e9163c2d15",
|
||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "7e7a5d0cc2f6fdd8fd9affbc05c5195c",
|
||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "91a1f57c0f4e9ef6fb5eb590f883e167",
|
||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "56ebc8c31e020e515395d3a81d2bb766",
|
||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b9aabf060ca52f9957e5c0c68975dd0d",
|
||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "56ebc8c31e020e515395d3a81d2bb766",
|
||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "b9aabf060ca52f9957e5c0c68975dd0d",
|
||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "21b01b868f38182cbe30dead5e6f6688",
|
||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "5bafa4627b87a3cfc6558d51c2760560",
|
||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "21b01b868f38182cbe30dead5e6f6688",
|
||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "5bafa4627b87a3cfc6558d51c2760560",
|
||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "9ba592d991ebb8724de8cab368bd1ac7",
|
||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "79630364e1f71cedf87140c40b913785",
|
||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "9ba592d991ebb8724de8cab368bd1ac7",
|
||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "79630364e1f71cedf87140c40b913785",
|
||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "7e3d1a1c0bdb8a814f7227f71862fa1d",
|
||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "5bfe717b5f30a67822130299b7342bcf",
|
||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "6c7d4caaad12d39c61b291fe33eef2af",
|
||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "5bfe717b5f30a67822130299b7342bcf",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "ac0e239be82c9f04383eb4400313ad90",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "866ae37140298f2bd1ed9913afa773fb",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "ac8e60b59a767546d1bdb86d68c8e83d",
|
||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "8e4b7a2ae0cd444e818789ac919413d1",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "2ea511bd7f4bf34ec1651cee263f3d11",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "d9dd043cc3518ef0d02ceb302dfa71e1",
|
||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "fbfab4ba2a24a212d4f3b22a259ae3f8",
|
||||
"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/enums.py": "f8cd3af311ac63147882590123b78318",
|
||||
"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>linebegin</w>
|
||||
<w>linebits</w>
|
||||
<w>lineend</w>
|
||||
<w>lineheight</w>
|
||||
<w>linemax</w>
|
||||
<w>lineno</w>
|
||||
@ -2777,6 +2778,7 @@
|
||||
<w>sslcontext</w>
|
||||
<w>sslproto</w>
|
||||
<w>ssval</w>
|
||||
<w>stacklevel</w>
|
||||
<w>stackstr</w>
|
||||
<w>stager</w>
|
||||
<w>standin</w>
|
||||
@ -2857,6 +2859,7 @@
|
||||
<w>successmsg</w>
|
||||
<w>suiciding</w>
|
||||
<w>sunau</w>
|
||||
<w>suppressions</w>
|
||||
<w>suter</w>
|
||||
<w>sval</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
|
||||
'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
|
||||
you see any odd behavior when trying to quit the app.
|
||||
- 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.
|
||||
- Removed `babase.app.iircade_mode`. RIP iiRcade :(.
|
||||
- Removed `app.iircade_mode`. RIP iiRcade :(.
|
||||
- Changed `AppState.INITIAL` to `AppState.NOT_RUNNING`, added a
|
||||
`AppState.NATIVE_BOOTSTRAPPING`, and changed `AppState.LAUNCHING` to
|
||||
`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>linearstep</w>
|
||||
<w>linebegin</w>
|
||||
<w>lineend</w>
|
||||
<w>linemax</w>
|
||||
<w>linestart</w>
|
||||
<w>linestripped</w>
|
||||
@ -1643,6 +1644,7 @@
|
||||
<w>sssssssi</w>
|
||||
<w>ssssssssssss</w>
|
||||
<w>ssval</w>
|
||||
<w>stacklevel</w>
|
||||
<w>stager</w>
|
||||
<w>standin</w>
|
||||
<w>startedptr</w>
|
||||
@ -1690,6 +1692,7 @@
|
||||
<w>subsys</w>
|
||||
<w>subtypestr</w>
|
||||
<w>successmsg</w>
|
||||
<w>suppressions</w>
|
||||
<w>sval</w>
|
||||
<w>swidth</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_display_timer.cc
|
||||
${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.h
|
||||
${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" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_display_timer.cc" />
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
@ -571,6 +571,12 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h">
|
||||
<Filter>ballistica\base\python\class</Filter>
|
||||
</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">
|
||||
<Filter>ballistica\base\python\class</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@ -374,6 +374,8 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_context_ref.h" />
|
||||
<ClCompile Include="..\..\src\ballistica\base\python\class\python_class_display_timer.cc" />
|
||||
<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" />
|
||||
<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" />
|
||||
|
||||
@ -571,6 +571,12 @@
|
||||
<ClInclude Include="..\..\src\ballistica\base\python\class\python_class_display_timer.h">
|
||||
<Filter>ballistica\base\python\class</Filter>
|
||||
</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">
|
||||
<Filter>ballistica\base\python\class</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@ -39,6 +39,7 @@ from _babase import (
|
||||
DisplayTimer,
|
||||
do_once,
|
||||
env,
|
||||
Env,
|
||||
fade_screen,
|
||||
fatal_error,
|
||||
get_display_resolution,
|
||||
@ -48,8 +49,10 @@ from _babase import (
|
||||
get_replays_dir,
|
||||
get_string_height,
|
||||
get_string_width,
|
||||
get_v1_cloud_log_file_path,
|
||||
getsimplesound,
|
||||
has_gamma_control,
|
||||
has_user_run_commands,
|
||||
have_chars,
|
||||
have_permission,
|
||||
in_logic_thread,
|
||||
@ -83,6 +86,9 @@ from _babase import (
|
||||
set_thread_name,
|
||||
set_ui_input_device,
|
||||
show_progress_bar,
|
||||
shutdown_suppress_begin,
|
||||
shutdown_suppress_end,
|
||||
shutdown_suppress_count,
|
||||
SimpleSound,
|
||||
unlock_all_input,
|
||||
user_agent_string,
|
||||
@ -96,6 +102,7 @@ from babase._appconfig import commit_app_config
|
||||
from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec
|
||||
from babase._appmode import AppMode
|
||||
from babase._appsubsystem import AppSubsystem
|
||||
from babase._appmodeselector import AppModeSelector
|
||||
from babase._appconfig import AppConfig
|
||||
from babase._apputils import (
|
||||
handle_leftover_v1_cloud_log_file,
|
||||
@ -175,6 +182,7 @@ __all__ = [
|
||||
'AppMode',
|
||||
'appname',
|
||||
'appnameupper',
|
||||
'AppModeSelector',
|
||||
'AppSubsystem',
|
||||
'apptime',
|
||||
'AppTime',
|
||||
@ -200,6 +208,7 @@ __all__ = [
|
||||
'do_once',
|
||||
'EmptyAppMode',
|
||||
'env',
|
||||
'Env',
|
||||
'Existable',
|
||||
'existing',
|
||||
'fade_screen',
|
||||
@ -214,11 +223,13 @@ __all__ = [
|
||||
'get_replays_dir',
|
||||
'get_string_height',
|
||||
'get_string_width',
|
||||
'get_v1_cloud_log_file_path',
|
||||
'get_type_name',
|
||||
'getclass',
|
||||
'getsimplesound',
|
||||
'handle_leftover_v1_cloud_log_file',
|
||||
'has_gamma_control',
|
||||
'has_user_run_commands',
|
||||
'have_chars',
|
||||
'have_permission',
|
||||
'in_logic_thread',
|
||||
@ -277,6 +288,9 @@ __all__ = [
|
||||
'set_thread_name',
|
||||
'set_ui_input_device',
|
||||
'show_progress_bar',
|
||||
'shutdown_suppress_begin',
|
||||
'shutdown_suppress_end',
|
||||
'shutdown_suppress_count',
|
||||
'SimpleSound',
|
||||
'SpecialChar',
|
||||
'storagename',
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to the high level state of the app."""
|
||||
# pylint: disable=too-many-lines
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import logging
|
||||
from enum import Enum
|
||||
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from functools import cached_property
|
||||
@ -55,6 +58,7 @@ class App:
|
||||
|
||||
plugins: PluginSubsystem
|
||||
lang: LanguageSubsystem
|
||||
env: babase.Env
|
||||
|
||||
health_monitor: AppHealthMonitor
|
||||
|
||||
@ -68,173 +72,72 @@ class App:
|
||||
|
||||
# The app has not yet begun starting and should not be used in
|
||||
# any way.
|
||||
NOT_RUNNING = 'not_running'
|
||||
NOT_RUNNING = 0
|
||||
|
||||
# The native layer is spinning up its machinery (screens,
|
||||
# renderers, etc.). Nothing should happen in the Python layer
|
||||
# until this completes.
|
||||
NATIVE_BOOTSTRAPPING = 'native_bootstrapping'
|
||||
NATIVE_BOOTSTRAPPING = 1
|
||||
|
||||
# Python app subsystems are being inited but should not yet
|
||||
# interact or do any work.
|
||||
INITING = 'initing'
|
||||
INITING = 2
|
||||
|
||||
# Python app subsystems are inited and interacting, but the app
|
||||
# has not yet embarked on a high level course of action. It is
|
||||
# doing initial account logins, workspace & asset downloads,
|
||||
# etc.
|
||||
LOADING = 'loading'
|
||||
LOADING = 3
|
||||
|
||||
# All pieces are in place and the app is now doing its thing.
|
||||
RUNNING = 'running'
|
||||
RUNNING = 4
|
||||
|
||||
# The app is backgrounded or otherwise suspended.
|
||||
PAUSED = 'paused'
|
||||
PAUSED = 5
|
||||
|
||||
# The app is shutting down.
|
||||
SHUTTING_DOWN = 'shutting_down'
|
||||
SHUTTING_DOWN = 6
|
||||
|
||||
@property
|
||||
def aioloop(self) -> asyncio.AbstractEventLoop:
|
||||
"""The logic thread's asyncio event loop.
|
||||
class DefaultAppModeSelector(AppModeSelector):
|
||||
"""Decides which app modes to use to handle intents.
|
||||
|
||||
This allow async tasks to be run in the logic thread.
|
||||
Note that, at this time, the asyncio loop is encapsulated
|
||||
and explicitly stepped by the engine's logic thread loop and
|
||||
thus things like asyncio.get_running_loop() will not return this
|
||||
loop from most places in the logic thread; only from within a
|
||||
task explicitly created in this loop.
|
||||
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.
|
||||
"""
|
||||
assert self._aioloop is not None
|
||||
return self._aioloop
|
||||
|
||||
@property
|
||||
def build_number(self) -> int:
|
||||
"""Integer build number.
|
||||
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode]:
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
This value increases by at least 1 with each release of the game.
|
||||
It is independent of the human readable babase.App.version string.
|
||||
"""
|
||||
assert isinstance(self._env['build_number'], int)
|
||||
return self._env['build_number']
|
||||
# __GOOD_PLACE_FOR_CUSTOM_SPINOFF_LOGIC__
|
||||
|
||||
@property
|
||||
def device_name(self) -> str:
|
||||
"""Name of the device running the game."""
|
||||
assert isinstance(self._env['device_name'], str)
|
||||
return self._env['device_name']
|
||||
# __DEFAULT_APP_MODE_SELECTION_BEGIN__
|
||||
# This section generated by batools.appmodule; do not edit.
|
||||
|
||||
@property
|
||||
def config_file_path(self) -> str:
|
||||
"""Where the game's config file is stored on disk."""
|
||||
assert isinstance(self._env['config_file_path'], str)
|
||||
return self._env['config_file_path']
|
||||
# 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
|
||||
|
||||
@property
|
||||
def version(self) -> str:
|
||||
"""Human-readable version string; something like '1.3.24'.
|
||||
import babase
|
||||
|
||||
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 'babase.App.build_number'.
|
||||
"""
|
||||
assert isinstance(self._env['version'], str)
|
||||
return self._env['version']
|
||||
if bascenev1.SceneV1AppMode.supports_intent(intent):
|
||||
return bascenev1.SceneV1AppMode
|
||||
|
||||
@property
|
||||
def debug_build(self) -> bool:
|
||||
"""Whether the app was compiled in debug mode.
|
||||
if babase.EmptyAppMode.supports_intent(intent):
|
||||
return babase.EmptyAppMode
|
||||
|
||||
Debug builds generally run substantially slower than non-debug
|
||||
builds due to compiler optimizations being disabled and extra
|
||||
checks being run.
|
||||
"""
|
||||
assert isinstance(self._env['debug_build'], bool)
|
||||
return self._env['debug_build']
|
||||
raise RuntimeError(f'No handler found for intent {type(intent)}.')
|
||||
|
||||
@property
|
||||
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']
|
||||
# __DEFAULT_APP_MODE_SELECTION_END__
|
||||
|
||||
def __init__(self) -> None:
|
||||
"""(internal)
|
||||
@ -248,6 +151,8 @@ class App:
|
||||
|
||||
self.state = self.State.NOT_RUNNING
|
||||
|
||||
self.env: babase.Env = _babase.Env()
|
||||
|
||||
self._subsystems: list[AppSubsystem] = []
|
||||
|
||||
self._native_bootstrapping_completed = False
|
||||
@ -263,12 +168,11 @@ class App:
|
||||
self._subsystem_registration_ended = False
|
||||
self._pending_apply_app_config = False
|
||||
|
||||
# Config.
|
||||
self.config_file_healthy = False
|
||||
self.config_file_healthy: bool = False
|
||||
|
||||
# This is incremented any time the app is
|
||||
# backgrounded/foregrounded; can be a simple way to determine if
|
||||
# network data should be refreshed/etc.
|
||||
# This is incremented any time the app is backgrounded or
|
||||
# foregrounded; can be a simple way to determine if network data
|
||||
# should be refreshed/etc.
|
||||
self.fg_state = 0
|
||||
|
||||
self._aioloop: asyncio.AbstractEventLoop | None = None
|
||||
@ -294,20 +198,25 @@ class App:
|
||||
self._config: babase.AppConfig | None = None
|
||||
|
||||
self.components = AppComponentSubsystem()
|
||||
|
||||
# Testing this.
|
||||
self.meta = MetadataSubsystem()
|
||||
|
||||
self.net = NetworkSubsystem()
|
||||
self.workspaces = WorkspaceSubsystem()
|
||||
self._pending_intent: AppIntent | None = None
|
||||
self._intent: AppIntent | None = None
|
||||
self._mode: AppMode | 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
|
||||
# app-intents. Plugins can override this to change high level
|
||||
# app behavior and spinoff projects can change the default
|
||||
# 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
|
||||
|
||||
@ -324,35 +233,25 @@ class App:
|
||||
self.lang = LanguageSubsystem()
|
||||
self.plugins = PluginSubsystem()
|
||||
|
||||
def register_subsystem(self, subsystem: AppSubsystem) -> None:
|
||||
"""Called by the AppSubsystem class. Do not use directly."""
|
||||
@property
|
||||
def aioloop(self) -> asyncio.AbstractEventLoop:
|
||||
"""The logic thread's asyncio event loop.
|
||||
|
||||
# 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 _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.
|
||||
This allow async tasks to be run in the logic thread.
|
||||
Note that, at this time, the asyncio loop is encapsulated
|
||||
and explicitly stepped by the engine's logic thread loop and
|
||||
thus things like asyncio.get_running_loop() will not return this
|
||||
loop from most places in the logic thread; only from within a
|
||||
task explicitly created in this loop.
|
||||
"""
|
||||
fut = self.threadpool.submit(call)
|
||||
fut.add_done_callback(self._threadpool_no_wait_done)
|
||||
assert self._aioloop is not None
|
||||
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__
|
||||
# This section generated by batools.appmodule; do not edit.
|
||||
@ -398,6 +297,48 @@ class App:
|
||||
|
||||
# __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:
|
||||
"""Set the intent for the app.
|
||||
|
||||
@ -417,6 +358,92 @@ class App:
|
||||
# since it may block for a moment to load modules/etc.
|
||||
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:
|
||||
# This should be running in a bg thread.
|
||||
assert not _babase.in_logic_thread()
|
||||
@ -492,14 +519,6 @@ class App:
|
||||
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
|
||||
_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:
|
||||
"""Called when the app enters the initing state.
|
||||
|
||||
@ -633,14 +652,6 @@ class App:
|
||||
# plugin hasn't already told it to do something.
|
||||
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:
|
||||
assert _babase.in_logic_thread()
|
||||
|
||||
@ -667,47 +678,6 @@ class App:
|
||||
# Let the native layer do its thing.
|
||||
_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:
|
||||
# pylint: disable=too-many-branches
|
||||
assert _babase.in_logic_thread()
|
||||
@ -761,18 +731,6 @@ class App:
|
||||
if bool(True):
|
||||
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:
|
||||
import asyncio
|
||||
|
||||
@ -802,40 +760,6 @@ class App:
|
||||
except Exception:
|
||||
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:
|
||||
"""Called when the app goes to a paused state."""
|
||||
assert _babase.in_logic_thread()
|
||||
@ -881,46 +805,198 @@ class App:
|
||||
assert self._aioloop is not None
|
||||
self._shutdown_task = self._aioloop.create_task(self._shutdown())
|
||||
|
||||
def read_config(self) -> None:
|
||||
"""(internal)"""
|
||||
from babase._appconfig import read_app_config
|
||||
async def _wait_for_shutdown_suppressions(self) -> None:
|
||||
import asyncio
|
||||
|
||||
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:
|
||||
"""Handle a deep link URL."""
|
||||
from babase._language import Lstr
|
||||
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()'
|
||||
)
|
||||
|
||||
assert _babase.in_logic_thread()
|
||||
# --------------------------------------------------------------------
|
||||
# THE FOLLOWING ARE DEPRECATED AND WILL BE REMOVED IN A FUTURE UPDATE.
|
||||
# --------------------------------------------------------------------
|
||||
|
||||
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
|
||||
@property
|
||||
def build_number(self) -> int:
|
||||
"""Integer build number.
|
||||
|
||||
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.
|
||||
This value increases by at least 1 with each release of the engine.
|
||||
It is independent of the human readable babase.App.version string.
|
||||
"""
|
||||
assert _babase.in_logic_thread()
|
||||
assert not self._initial_sign_in_completed
|
||||
warnings.warn(
|
||||
'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
|
||||
# up (namely account workspaces).
|
||||
self.meta.start_extra_scan()
|
||||
@property
|
||||
def device_name(self) -> str:
|
||||
"""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
|
||||
self._update_state()
|
||||
@property
|
||||
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
|
||||
# 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 = ''
|
||||
try:
|
||||
if os.path.exists(config_file_path):
|
||||
|
||||
@ -48,7 +48,7 @@ def is_browser_likely_available() -> bool:
|
||||
# assume no browser.
|
||||
# FIXME: Might not be the case anymore; should make this definable
|
||||
# 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
|
||||
|
||||
# Anywhere else assume we've got one.
|
||||
@ -103,8 +103,8 @@ def handle_v1_cloud_log() -> None:
|
||||
|
||||
info = {
|
||||
'log': _babase.get_v1_cloud_log(),
|
||||
'version': app.version,
|
||||
'build': app.build_number,
|
||||
'version': app.env.version,
|
||||
'build': app.env.build_number,
|
||||
'userAgentString': classic.legacy_user_agent_string,
|
||||
'session': sessionname,
|
||||
'activity': activityname,
|
||||
@ -279,7 +279,8 @@ def dump_app_state(
|
||||
# the dump in that case.
|
||||
try:
|
||||
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:
|
||||
outfile.write(
|
||||
@ -297,7 +298,7 @@ def dump_app_state(
|
||||
return
|
||||
|
||||
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')
|
||||
@ -329,7 +330,8 @@ def log_dumped_app_state() -> None:
|
||||
try:
|
||||
out = ''
|
||||
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):
|
||||
# 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}'
|
||||
)
|
||||
tbpath = os.path.join(
|
||||
os.path.dirname(_babase.app.config_file_path),
|
||||
os.path.dirname(_babase.app.env.config_file_path),
|
||||
'_appstate_dump_tb',
|
||||
)
|
||||
if os.path.exists(tbpath):
|
||||
|
||||
@ -6,6 +6,7 @@ from __future__ import annotations
|
||||
import sys
|
||||
import signal
|
||||
import logging
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
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.
|
||||
_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
|
||||
# explicit times to avoid random hitches and keep things more
|
||||
# deterministic. Non-reference-looped objects will still get cleaned
|
||||
|
||||
@ -354,7 +354,7 @@ def show_client_too_old_error() -> None:
|
||||
# a newer build.
|
||||
if (
|
||||
_babase.app.config.get('SuppressClientTooOldErrorForBuild')
|
||||
== _babase.app.build_number
|
||||
== _babase.app.env.build_number
|
||||
):
|
||||
return
|
||||
|
||||
|
||||
@ -68,7 +68,10 @@ class LanguageSubsystem(AppSubsystem):
|
||||
try:
|
||||
names = os.listdir(
|
||||
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]
|
||||
@ -121,7 +124,7 @@ class LanguageSubsystem(AppSubsystem):
|
||||
|
||||
with open(
|
||||
os.path.join(
|
||||
_babase.app.data_directory,
|
||||
_babase.app.env.data_directory,
|
||||
'ba_data',
|
||||
'data',
|
||||
'languages',
|
||||
@ -139,7 +142,7 @@ class LanguageSubsystem(AppSubsystem):
|
||||
lmodvalues = None
|
||||
else:
|
||||
lmodfile = os.path.join(
|
||||
_babase.app.data_directory,
|
||||
_babase.app.env.data_directory,
|
||||
'ba_data',
|
||||
'data',
|
||||
'languages',
|
||||
|
||||
@ -18,11 +18,6 @@ import _babase
|
||||
if TYPE_CHECKING:
|
||||
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.
|
||||
# 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 is None
|
||||
env = _babase.app.env
|
||||
|
||||
self._scan_complete_cb = scan_complete_cb
|
||||
self._scan = DirectoryScan(
|
||||
[
|
||||
path
|
||||
for path in [
|
||||
_babase.app.python_directory_app,
|
||||
_babase.app.python_directory_user,
|
||||
env.python_directory_app,
|
||||
env.python_directory_user,
|
||||
]
|
||||
if path is not None
|
||||
]
|
||||
@ -212,7 +208,7 @@ class MetadataSubsystem:
|
||||
'${NUM}',
|
||||
str(len(results.incorrect_api_modules) - 1),
|
||||
),
|
||||
('${API}', str(CURRENT_API_VERSION)),
|
||||
('${API}', str(_babase.app.env.api_version)),
|
||||
],
|
||||
)
|
||||
else:
|
||||
@ -220,7 +216,7 @@ class MetadataSubsystem:
|
||||
resource='scanScriptsSingleModuleNeedsUpdatesText',
|
||||
subs=[
|
||||
('${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))
|
||||
@ -344,13 +340,16 @@ class DirectoryScan:
|
||||
|
||||
# If we find a module requiring a different api version, warn
|
||||
# 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(
|
||||
'metascan: %s requires api %s but we are running'
|
||||
' %s. Ignoring module.',
|
||||
subpath,
|
||||
required_api,
|
||||
CURRENT_API_VERSION,
|
||||
_babase.app.env.api_version,
|
||||
)
|
||||
self.results.incorrect_api_modules.append(
|
||||
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)".
|
||||
"""
|
||||
app = _babase.app
|
||||
path: str | None = app.python_directory_user
|
||||
path: str | None = app.env.python_directory_user
|
||||
if path is None:
|
||||
return '<Not Available>'
|
||||
|
||||
@ -66,19 +66,20 @@ def _request_storage_permission() -> bool:
|
||||
def show_user_scripts() -> None:
|
||||
"""Open or nicely print the location of the user-scripts directory."""
|
||||
app = _babase.app
|
||||
env = app.env
|
||||
|
||||
# First off, if we need permission for this, ask for it.
|
||||
if _request_storage_permission():
|
||||
return
|
||||
|
||||
# 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>')
|
||||
return
|
||||
|
||||
# Secondly, if the dir doesn't exist, attempt to make it.
|
||||
if not os.path.exists(app.python_directory_user):
|
||||
os.makedirs(app.python_directory_user)
|
||||
if not os.path.exists(env.python_directory_user):
|
||||
os.makedirs(env.python_directory_user)
|
||||
|
||||
# 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
|
||||
@ -88,7 +89,7 @@ def show_user_scripts() -> None:
|
||||
# they can see it.
|
||||
if app.classic is not None and app.classic.platform == 'android':
|
||||
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):
|
||||
file_name = usd + '/about_this_folder.txt'
|
||||
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.
|
||||
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.
|
||||
else:
|
||||
@ -120,18 +121,19 @@ def create_user_system_scripts() -> None:
|
||||
import shutil
|
||||
|
||||
app = _babase.app
|
||||
env = app.env
|
||||
|
||||
# First off, if we need permission for this, ask for it.
|
||||
if _request_storage_permission():
|
||||
return
|
||||
|
||||
# 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')
|
||||
if app.python_directory_app is None:
|
||||
if env.python_directory_app is None:
|
||||
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'
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
@ -147,8 +149,8 @@ def create_user_system_scripts() -> None:
|
||||
# /Knowledge-Nuggets#python-cache-files-gotcha
|
||||
return ('__pycache__',)
|
||||
|
||||
print(f'COPYING "{app.python_directory_app}" -> "{pathtmp}".')
|
||||
shutil.copytree(app.python_directory_app, pathtmp, ignore=_ignore_filter)
|
||||
print(f'COPYING "{env.python_directory_app}" -> "{pathtmp}".')
|
||||
shutil.copytree(env.python_directory_app, pathtmp, ignore=_ignore_filter)
|
||||
|
||||
print(f'MOVING "{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()."""
|
||||
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')
|
||||
|
||||
path = app.python_directory_user + '/sys/' + app.version
|
||||
path = f'{env.python_directory_user}/sys/{env.version}'
|
||||
if os.path.exists(path):
|
||||
shutil.rmtree(path)
|
||||
print(
|
||||
@ -185,6 +187,6 @@ def delete_user_system_scripts() -> None:
|
||||
print(f"User system scripts not found at '{path}'.")
|
||||
|
||||
# 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):
|
||||
os.rmdir(dpath)
|
||||
|
||||
@ -4,14 +4,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import logging
|
||||
import weakref
|
||||
import threading
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import babase
|
||||
from babase import DEFAULT_REQUEST_TIMEOUT_SECONDS
|
||||
import bascenev1
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -72,6 +70,7 @@ class MasterServerV1CallThread(threading.Thread):
|
||||
|
||||
def run(self) -> None:
|
||||
# pylint: disable=consider-using-with
|
||||
# pylint: disable=too-many-branches
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
@ -79,20 +78,15 @@ class MasterServerV1CallThread(threading.Thread):
|
||||
|
||||
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
|
||||
assert plus is not None
|
||||
response_data: Any = 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:
|
||||
classic = babase.app.classic
|
||||
assert classic is not None
|
||||
@ -114,7 +108,7 @@ class MasterServerV1CallThread(threading.Thread):
|
||||
{'User-Agent': classic.legacy_user_agent_string},
|
||||
),
|
||||
context=babase.app.net.sslcontext,
|
||||
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
timeout=babase.DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
elif self._request_type == 'post':
|
||||
url = plus.get_master_server_address() + '/' + self._request
|
||||
@ -126,7 +120,7 @@ class MasterServerV1CallThread(threading.Thread):
|
||||
{'User-Agent': classic.legacy_user_agent_string},
|
||||
),
|
||||
context=babase.app.net.sslcontext,
|
||||
timeout=DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
timeout=babase.DEFAULT_REQUEST_TIMEOUT_SECONDS,
|
||||
)
|
||||
else:
|
||||
raise TypeError('Invalid request_type: ' + self._request_type)
|
||||
@ -160,6 +154,9 @@ class MasterServerV1CallThread(threading.Thread):
|
||||
|
||||
response_data = None
|
||||
|
||||
finally:
|
||||
babase.shutdown_suppress_end()
|
||||
|
||||
if self._callback is not None:
|
||||
babase.pushcall(
|
||||
babase.Call(self._run_callback, response_data),
|
||||
|
||||
@ -214,7 +214,10 @@ class ServerController:
|
||||
|
||||
babase.app.classic.master_server_v1_get(
|
||||
'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,
|
||||
)
|
||||
|
||||
@ -379,8 +382,8 @@ class ServerController:
|
||||
if self._first_run:
|
||||
curtimestr = time.strftime('%c')
|
||||
startupmsg = (
|
||||
f'{Clr.BLD}{Clr.BLU}{babase.appnameupper()} {app.version}'
|
||||
f' ({app.build_number})'
|
||||
f'{Clr.BLD}{Clr.BLU}{babase.appnameupper()} {app.env.version}'
|
||||
f' ({app.env.build_number})'
|
||||
f' entering server-mode {curtimestr}{Clr.RST}'
|
||||
)
|
||||
logging.info(startupmsg)
|
||||
|
||||
@ -153,6 +153,7 @@ class ClassicSubsystem(babase.AppSubsystem):
|
||||
plus = babase.app.plus
|
||||
assert plus is not None
|
||||
|
||||
env = babase.app.env
|
||||
cfg = babase.app.config
|
||||
|
||||
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.
|
||||
# (so I don't accidentally release a build that can't play tourneys)
|
||||
if (
|
||||
not babase.app.debug_build
|
||||
and not babase.app.test_build
|
||||
and not plus.is_blessed()
|
||||
):
|
||||
if not env.debug and not env.test and not plus.is_blessed():
|
||||
babase.screenmessage('WARNING: NON-BLESSED BUILD', color=(1, 0, 0))
|
||||
|
||||
# FIXME: This should not be hard-coded.
|
||||
|
||||
@ -113,7 +113,7 @@ def get_all_tips() -> list[str]:
|
||||
if (
|
||||
app.classic is not None
|
||||
and app.classic.platform in ('android', 'ios')
|
||||
and not app.on_tv
|
||||
and not app.env.tv
|
||||
):
|
||||
tips += [
|
||||
(
|
||||
|
||||
@ -52,8 +52,8 @@ if TYPE_CHECKING:
|
||||
|
||||
# Build number and version of the ballistica binary we expect to be
|
||||
# using.
|
||||
TARGET_BALLISTICA_BUILD = 21256
|
||||
TARGET_BALLISTICA_VERSION = '1.7.26'
|
||||
TARGET_BALLISTICA_BUILD = 21269
|
||||
TARGET_BALLISTICA_VERSION = '1.7.27'
|
||||
|
||||
|
||||
@dataclass
|
||||
|
||||
@ -108,7 +108,7 @@ class CoopGameActivity(GameActivity[PlayerT, TeamT]):
|
||||
)
|
||||
if not a.complete
|
||||
]
|
||||
vrmode = babase.app.vr_mode
|
||||
vrmode = babase.app.env.vr
|
||||
if achievements:
|
||||
Text(
|
||||
babase.Lstr(resource='achievementsRemainingText'),
|
||||
|
||||
@ -600,7 +600,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
||||
translate=('gameDescriptions', sb_desc_l[0]), subs=subs
|
||||
)
|
||||
sb_desc = translation
|
||||
vrmode = babase.app.vr_mode
|
||||
vrmode = babase.app.env.vr
|
||||
yval = -34 if is_empty else -20
|
||||
yval -= 16
|
||||
sbpos = (
|
||||
@ -706,7 +706,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
||||
resource='epicDescriptionFilterText',
|
||||
subs=[('${DESCRIPTION}', translation)],
|
||||
)
|
||||
vrmode = babase.app.vr_mode
|
||||
vrmode = babase.app.env.vr
|
||||
dnode = _bascenev1.newnode(
|
||||
'text',
|
||||
attrs={
|
||||
@ -761,7 +761,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
|
||||
base_position = (75, 50)
|
||||
tip_scale = 0.8
|
||||
tip_title_scale = 1.2
|
||||
vrmode = babase.app.vr_mode
|
||||
vrmode = babase.app.env.vr
|
||||
|
||||
t_offs = -350.0
|
||||
tnode = _bascenev1.newnode(
|
||||
|
||||
@ -185,7 +185,7 @@ def show_damage_count(
|
||||
# (connected clients may have differing configs so they won't
|
||||
# get the intended results).
|
||||
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(
|
||||
'text',
|
||||
attrs={
|
||||
|
||||
@ -49,7 +49,7 @@ class JoinInfo:
|
||||
if keyboard is not None:
|
||||
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(
|
||||
_bascenev1.newnode(
|
||||
'text',
|
||||
|
||||
@ -76,7 +76,7 @@ class CoopJoinActivity(bs.JoinActivity):
|
||||
]
|
||||
have_achievements = bool(achievements)
|
||||
achievements = [a for a in achievements if not a.complete]
|
||||
vrmode = bs.app.vr_mode
|
||||
vrmode = bs.app.env.vr
|
||||
if have_achievements:
|
||||
Text(
|
||||
bs.Lstr(resource='achievementsRemainingText'),
|
||||
|
||||
@ -74,7 +74,7 @@ class Background(bs.Actor):
|
||||
self.node.connectattr('opacity', self.logo, 'opacity')
|
||||
# add jitter/pulse for a stop-motion-y look unless we're in VR
|
||||
# in which case stillness is better
|
||||
if not bs.app.vr_mode:
|
||||
if not bs.app.env.vr:
|
||||
self.cmb = bs.newnode(
|
||||
'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)
|
||||
self._run_text_pos_top = (position[0], position[1] - 135.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(
|
||||
'text',
|
||||
attrs={
|
||||
'scale': sval,
|
||||
'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,
|
||||
'maxwidth': 380,
|
||||
'v_align': 'top',
|
||||
|
||||
@ -45,7 +45,7 @@ class _Entry:
|
||||
|
||||
# FIXME: Should not do things conditionally for vr-mode, as there may
|
||||
# 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 vrmode:
|
||||
|
||||
@ -69,7 +69,7 @@ class ZoomText(bs.Actor):
|
||||
)
|
||||
|
||||
# we never jitter in vr mode..
|
||||
if bs.app.vr_mode:
|
||||
if bs.app.env.vr:
|
||||
jitter = 0.0
|
||||
|
||||
# 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.
|
||||
# (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(
|
||||
bs.newnode(
|
||||
'text',
|
||||
|
||||
@ -50,6 +50,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
||||
super().on_transition_in()
|
||||
random.seed(123)
|
||||
app = bs.app
|
||||
env = app.env
|
||||
assert app.classic is not None
|
||||
|
||||
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).
|
||||
# Any differences need to happen at the engine level so everyone
|
||||
# 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:
|
||||
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).
|
||||
# Any differences need to happen at the engine level so everyone sees
|
||||
# things in their own optimal way.
|
||||
vr_mode = app.vr_mode
|
||||
vr_mode = app.env.vr
|
||||
uiscale = app.ui_v1.uiscale
|
||||
|
||||
# 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
|
||||
|
||||
if not bs.app.toolbar_test:
|
||||
if app.debug_build or app.test_build or force_show_build_number:
|
||||
if app.debug_build:
|
||||
if env.debug or env.test or force_show_build_number:
|
||||
if env.debug:
|
||||
text = bs.Lstr(
|
||||
value='${V} (${B}) (${D})',
|
||||
subs=[
|
||||
('${V}', app.version),
|
||||
('${B}', str(app.build_number)),
|
||||
('${V}', app.env.version),
|
||||
('${B}', str(app.env.build_number)),
|
||||
('${D}', bs.Lstr(resource='debugText')),
|
||||
],
|
||||
)
|
||||
@ -139,12 +140,12 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
||||
text = bs.Lstr(
|
||||
value='${V} (${B})',
|
||||
subs=[
|
||||
('${V}', app.version),
|
||||
('${B}', str(app.build_number)),
|
||||
('${V}', app.env.version),
|
||||
('${B}', str(app.env.build_number)),
|
||||
],
|
||||
)
|
||||
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
|
||||
color = (1, 1, 1, 1) if vr_mode else (0.5, 0.6, 0.5, 0.7)
|
||||
self.version = bs.NodeActor(
|
||||
@ -170,7 +171,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
|
||||
|
||||
# Throw in test build info.
|
||||
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)
|
||||
self.beta_info = bs.NodeActor(
|
||||
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
|
||||
# (unless we're in VR mode in which case its best to
|
||||
# leave things still).
|
||||
if not bs.app.vr_mode:
|
||||
if not bs.app.env.vr:
|
||||
cmb: bs.Node | None
|
||||
cmb2: bs.Node | None
|
||||
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
|
||||
# leave things still).
|
||||
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.connectattr('output', logo.node, 'position')
|
||||
keys = {}
|
||||
@ -882,7 +883,7 @@ class NewsDisplay:
|
||||
self._phrases.insert(0, phr)
|
||||
val = self._phrases.pop()
|
||||
if val == '__ACH__':
|
||||
vrmode = app.vr_mode
|
||||
vrmode = app.env.vr
|
||||
Text(
|
||||
bs.Lstr(resource='nextAchievementsText'),
|
||||
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
|
||||
# (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 != ''
|
||||
]
|
||||
self._phrase_change_timer = bs.Timer(
|
||||
@ -960,12 +961,12 @@ class NewsDisplay:
|
||||
assert bs.app.classic is not None
|
||||
scl = (
|
||||
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
|
||||
)
|
||||
|
||||
color2 = (1, 1, 1, 1) if bs.app.vr_mode else (0.7, 0.65, 0.75, 1.0)
|
||||
shadow = 1.0 if bs.app.vr_mode else 0.4
|
||||
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.env.vr else 0.4
|
||||
self._text = bs.NodeActor(
|
||||
bs.newnode(
|
||||
'text',
|
||||
|
||||
@ -141,7 +141,7 @@ class AccountViewerWindow(PopupWindow):
|
||||
bui.app.classic.master_server_v1_get(
|
||||
'bsAccountInfo',
|
||||
{
|
||||
'buildNumber': bui.app.build_number,
|
||||
'buildNumber': bui.app.env.build_number,
|
||||
'accountID': self._account_id,
|
||||
'profileID': self._profile_id,
|
||||
},
|
||||
|
||||
@ -11,7 +11,7 @@ class ConfigErrorWindow(bui.Window):
|
||||
"""Window for dealing with a broken config."""
|
||||
|
||||
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
|
||||
super().__init__(
|
||||
bui.containerwidget(size=(width, 400), transition='in_right')
|
||||
|
||||
@ -197,13 +197,19 @@ class QuitWindow:
|
||||
time=0.2,
|
||||
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()
|
||||
|
||||
# Unlock and fade back in shortly. Just in case something goes
|
||||
# wrong (or on Android where quit just backs out of our activity
|
||||
# and we may come back after).
|
||||
def _come_back() -> None:
|
||||
bui.unlock_all_input()
|
||||
bui.fade_screen(True, time=0.1)
|
||||
# On systems supporting soft-quit, unlock and fade back in shortly
|
||||
# (soft-quit basically just backgrounds/hides the app).
|
||||
if bui.app.env.supports_soft_quit:
|
||||
# Unlock and fade back in shortly. Just in case something goes
|
||||
# wrong (or on Android where quit just backs out of our activity
|
||||
# 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:
|
||||
with open(
|
||||
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',
|
||||
) as infile:
|
||||
|
||||
@ -15,7 +15,7 @@ def ask_for_rating() -> bui.Widget | None:
|
||||
subplatform = app.classic.subplatform
|
||||
|
||||
# FIXME: should whitelist platforms we *do* want this for.
|
||||
if bui.app.test_build:
|
||||
if bui.app.env.test:
|
||||
return None
|
||||
if not (
|
||||
platform == 'mac'
|
||||
|
||||
@ -43,7 +43,7 @@ class AboutGatherTab(GatherTab):
|
||||
|
||||
# Let's not talk about sharing in vr-mode; its tricky to fit more
|
||||
# than one head in a VR-headset ;-)
|
||||
if not bui.app.vr_mode:
|
||||
if not bui.app.env.vr:
|
||||
message = bui.Lstr(
|
||||
value='${A}\n\n${B}',
|
||||
subs=[
|
||||
|
||||
@ -1010,7 +1010,7 @@ class ManualGatherTab(GatherTab):
|
||||
self._t_accessible_extra = t_accessible_extra
|
||||
bui.app.classic.master_server_v1_get(
|
||||
'bsAccessCheck',
|
||||
{'b': bui.app.build_number},
|
||||
{'b': bui.app.env.build_number},
|
||||
callback=bui.WeakCall(self._on_accessible_response),
|
||||
)
|
||||
|
||||
|
||||
@ -1327,7 +1327,7 @@ class PublicGatherTab(GatherTab):
|
||||
)
|
||||
bui.app.classic.master_server_v1_get(
|
||||
'bsAccessCheck',
|
||||
{'b': bui.app.build_number},
|
||||
{'b': bui.app.env.build_number},
|
||||
callback=bui.WeakCall(self._on_public_party_accessible_response),
|
||||
)
|
||||
|
||||
|
||||
@ -621,7 +621,7 @@ class GetCurrencyWindow(bui.Window):
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
if (
|
||||
app.test_build
|
||||
app.env.test
|
||||
or (
|
||||
app.classic.platform == 'android'
|
||||
and app.classic.subplatform in ['oculus', 'cardboard']
|
||||
@ -664,8 +664,8 @@ class GetCurrencyWindow(bui.Window):
|
||||
'item': item,
|
||||
'platform': app.classic.platform,
|
||||
'subplatform': app.classic.subplatform,
|
||||
'version': app.version,
|
||||
'buildNumber': app.build_number,
|
||||
'version': app.env.version,
|
||||
'buildNumber': app.env.build_number,
|
||||
},
|
||||
callback=bui.WeakCall(self._purchase_check_result, item),
|
||||
)
|
||||
|
||||
@ -353,7 +353,7 @@ class HelpWindow(bui.Window):
|
||||
v -= spacing * 45.0
|
||||
txt = (
|
||||
bui.Lstr(resource=self._r + '.devicesText').evaluate()
|
||||
if app.vr_mode
|
||||
if app.env.vr
|
||||
else bui.Lstr(resource=self._r + '.controllersText').evaluate()
|
||||
)
|
||||
txt_scale = 0.74
|
||||
@ -372,7 +372,7 @@ class HelpWindow(bui.Window):
|
||||
)
|
||||
|
||||
txt_scale = 0.7
|
||||
if not app.vr_mode:
|
||||
if not app.env.vr:
|
||||
infotxt = '.controllersInfoText'
|
||||
txt = bui.Lstr(
|
||||
resource=self._r + infotxt,
|
||||
|
||||
@ -117,7 +117,7 @@ class MainMenuWindow(bui.Window):
|
||||
force_test = False
|
||||
bs.get_local_active_input_devices_count()
|
||||
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
|
||||
) or force_test:
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ class PopupWindow:
|
||||
focus_size = size
|
||||
|
||||
# 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_position = (0, 0)
|
||||
|
||||
|
||||
@ -155,7 +155,7 @@ class ProfileUpgradeWindow(bui.Window):
|
||||
|
||||
bui.app.classic.master_server_v1_get(
|
||||
'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),
|
||||
)
|
||||
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,
|
||||
# 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_height = self._height - 115.0
|
||||
@ -102,7 +102,7 @@ class AdvancedSettingsWindow(bui.Window):
|
||||
if self._show_disable_gyro:
|
||||
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._extra_button_spacing = self._spacing * 2.5
|
||||
|
||||
@ -178,7 +178,7 @@ class AdvancedSettingsWindow(bui.Window):
|
||||
# Fetch the list of completed languages.
|
||||
bui.app.classic.master_server_v1_get(
|
||||
'bsLangGetCompleted',
|
||||
{'b': app.build_number},
|
||||
{'b': app.env.build_number},
|
||||
callback=bui.WeakCall(self._completed_langs_cb),
|
||||
)
|
||||
|
||||
@ -322,7 +322,10 @@ class AdvancedSettingsWindow(bui.Window):
|
||||
|
||||
with open(
|
||||
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',
|
||||
) as infile:
|
||||
|
||||
@ -47,14 +47,14 @@ class ControlsSettingsWindow(bui.Window):
|
||||
|
||||
space_height = spacing * 0.3
|
||||
|
||||
# FIXME: should create vis settings in platform for these,
|
||||
# not hard code them here.
|
||||
# FIXME: should create vis settings under platform or app-adapter
|
||||
# to determine whether to show this stuff; not hard code it.
|
||||
|
||||
show_gamepads = False
|
||||
platform = app.classic.platform
|
||||
subplatform = app.classic.subplatform
|
||||
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:
|
||||
show_gamepads = True
|
||||
@ -70,11 +70,12 @@ class ControlsSettingsWindow(bui.Window):
|
||||
show_space_1 = True
|
||||
height += space_height
|
||||
|
||||
print('hello')
|
||||
show_keyboard = False
|
||||
if bs.getinputdevice('Keyboard', '#1', doraise=False) is not None:
|
||||
show_keyboard = True
|
||||
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:
|
||||
height += spacing
|
||||
|
||||
@ -91,7 +92,7 @@ class ControlsSettingsWindow(bui.Window):
|
||||
|
||||
# On windows (outside of oculus/vr), show an option to disable xinput.
|
||||
show_xinput_toggle = False
|
||||
if platform == 'windows' and not app.vr_mode:
|
||||
if platform == 'windows' and not app.env.vr:
|
||||
show_xinput_toggle = True
|
||||
|
||||
# On mac builds, show an option to switch between generic and
|
||||
@ -352,6 +353,7 @@ class ControlsSettingsWindow(bui.Window):
|
||||
maxwidth=width * 0.8,
|
||||
)
|
||||
v -= spacing * 1.5
|
||||
|
||||
self._restore_state()
|
||||
|
||||
def _set_mac_controller_subsystem(self, val: str) -> None:
|
||||
|
||||
@ -829,7 +829,7 @@ class GamepadSettingsWindow(bui.Window):
|
||||
'controllerConfig',
|
||||
{
|
||||
'ua': classic.legacy_user_agent_string,
|
||||
'b': bui.app.build_number,
|
||||
'b': bui.app.env.build_number,
|
||||
'name': self._name,
|
||||
'inputMapHash': inputhash,
|
||||
'config': dst2,
|
||||
|
||||
@ -91,7 +91,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
||||
self._sub_height = (
|
||||
940 if self._parent_window.get_is_secondary() else 1040
|
||||
)
|
||||
if app.vr_mode:
|
||||
if app.env.vr:
|
||||
self._sub_height += 50
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
@ -183,7 +183,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
|
||||
)
|
||||
|
||||
# in vr mode, allow assigning a reset-view button
|
||||
if app.vr_mode:
|
||||
if app.env.vr:
|
||||
v -= 50
|
||||
self._capture_button(
|
||||
pos=(h2, v),
|
||||
|
||||
@ -61,7 +61,7 @@ class GraphicsSettingsWindow(bui.Window):
|
||||
show_vsync = True
|
||||
|
||||
show_resolution = True
|
||||
if app.vr_mode:
|
||||
if app.env.vr:
|
||||
show_resolution = (
|
||||
app.classic.platform == 'android'
|
||||
and app.classic.subplatform == 'cardboard'
|
||||
@ -400,7 +400,7 @@ class GraphicsSettingsWindow(bui.Window):
|
||||
)
|
||||
|
||||
# (tv mode doesnt apply to vr)
|
||||
if not bui.app.vr_mode:
|
||||
if not bui.app.env.vr:
|
||||
tvc = ConfigCheckBox(
|
||||
parent=self._root_widget,
|
||||
position=(240, v - 6),
|
||||
|
||||
@ -301,7 +301,7 @@ class ConfigKeyboardWindow(bui.Window):
|
||||
{
|
||||
'ua': bui.app.classic.legacy_user_agent_string,
|
||||
'name': self._name,
|
||||
'b': bui.app.build_number,
|
||||
'b': bui.app.env.build_number,
|
||||
'config': dst2,
|
||||
'v': 2,
|
||||
},
|
||||
|
||||
@ -44,14 +44,14 @@ class SpecialOfferWindow(bui.Window):
|
||||
real_price = plus.get_price(
|
||||
'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.')
|
||||
real_price = '$1.23'
|
||||
zombie = real_price is None
|
||||
elif isinstance(offer['price'], str):
|
||||
# (a string price implies IAP id)
|
||||
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.')
|
||||
real_price = '$1.23'
|
||||
zombie = real_price is None
|
||||
|
||||
@ -566,8 +566,8 @@ class StoreBrowserWindow(bui.Window):
|
||||
'item': item,
|
||||
'platform': app.classic.platform,
|
||||
'subplatform': app.classic.subplatform,
|
||||
'version': app.version,
|
||||
'buildNumber': app.build_number,
|
||||
'version': app.env.version,
|
||||
'buildNumber': app.env.build_number,
|
||||
'purchaseType': 'ticket' if is_ticket_purchase else 'real',
|
||||
},
|
||||
callback=bui.WeakCall(
|
||||
|
||||
@ -715,5 +715,10 @@ void BaseFeatureSet::DoPushObjCall(const PythonObjectSetBase* objset, int id,
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@ -690,6 +690,9 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
||||
void DoPushObjCall(const PythonObjectSetBase* objset, int id,
|
||||
const std::string& arg) override;
|
||||
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
|
||||
/// loading.
|
||||
@ -748,6 +751,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
|
||||
StressTest* stress_test_;
|
||||
|
||||
std::string console_startup_messages_;
|
||||
int shutdown_suppress_count_{};
|
||||
bool tried_importing_plus_{};
|
||||
bool tried_importing_classic_{};
|
||||
bool tried_importing_ui_v1_{};
|
||||
|
||||
@ -1931,8 +1931,10 @@ auto Graphics::ScreenMessageEntry::GetText() -> TextGroup& {
|
||||
return *s_mesh_;
|
||||
}
|
||||
|
||||
void Graphics::OnScreenSizeChange(float virtual_width, float virtual_height,
|
||||
float pixel_width, float pixel_height) {
|
||||
void Graphics::OnScreenSizeChange() {}
|
||||
|
||||
void Graphics::SetScreenSize(float virtual_width, float virtual_height,
|
||||
float pixel_width, float pixel_height) {
|
||||
assert(g_base->InLogicThread());
|
||||
res_x_virtual_ = virtual_width;
|
||||
res_y_virtual_ = virtual_height;
|
||||
|
||||
@ -54,9 +54,11 @@ class Graphics {
|
||||
void OnAppPause();
|
||||
void OnAppResume();
|
||||
void OnAppShutdown();
|
||||
void OnScreenSizeChange();
|
||||
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();
|
||||
|
||||
static auto IsShaderTransparent(ShadingType c) -> bool;
|
||||
|
||||
@ -419,9 +419,10 @@ void GraphicsServer::HandleFullContextScreenRebuild(
|
||||
|
||||
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(
|
||||
[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);
|
||||
});
|
||||
}
|
||||
@ -568,9 +569,10 @@ void GraphicsServer::SetScreenResolution(float h, float v) {
|
||||
renderer_->ScreenSizeChanged();
|
||||
}
|
||||
|
||||
// Inform logic thread of the change.
|
||||
// Inform graphics client and logic thread subsystems of the change.
|
||||
g_base->logic->event_loop()->PushCall(
|
||||
[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);
|
||||
});
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "ballistica/base/audio/audio.h"
|
||||
#include "ballistica/base/input/input.h"
|
||||
#include "ballistica/base/networking/networking.h"
|
||||
#include "ballistica/base/platform/base_platform.h"
|
||||
#include "ballistica/base/python/base_python.h"
|
||||
#include "ballistica/base/support/plus_soft.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
|
||||
// subsystems in their normal states which is less likely to lead to
|
||||
// problems.
|
||||
g_base->platform->OnAppStart();
|
||||
g_base->graphics->OnAppStart();
|
||||
g_base->audio->OnAppStart();
|
||||
g_base->input->OnAppStart();
|
||||
@ -184,6 +186,7 @@ void Logic::OnAppPause() {
|
||||
g_base->input->OnAppPause();
|
||||
g_base->audio->OnAppPause();
|
||||
g_base->graphics->OnAppPause();
|
||||
g_base->platform->OnAppPause();
|
||||
}
|
||||
|
||||
void Logic::OnAppResume() {
|
||||
@ -191,6 +194,7 @@ void Logic::OnAppResume() {
|
||||
assert(g_base->CurrentContext().IsEmpty());
|
||||
|
||||
// Note: keep these in the same order as OnAppStart.
|
||||
g_base->platform->OnAppResume();
|
||||
g_base->graphics->OnAppResume();
|
||||
g_base->audio->OnAppResume();
|
||||
g_base->input->OnAppResume();
|
||||
@ -235,6 +239,7 @@ void Logic::OnAppShutdown() {
|
||||
g_base->input->OnAppShutdown();
|
||||
g_base->audio->OnAppShutdown();
|
||||
g_base->graphics->OnAppShutdown();
|
||||
g_base->platform->OnAppShutdown();
|
||||
}
|
||||
|
||||
void Logic::CompleteShutdown() {
|
||||
@ -283,12 +288,10 @@ void Logic::OnScreenSizeChange(float virtual_width, float virtual_height,
|
||||
float pixel_width, float pixel_height) {
|
||||
assert(g_base->InLogicThread());
|
||||
|
||||
// First, pass the new values to the graphics subsystem. Then inform
|
||||
// everyone else simply that they changed; they can ask g_graphics for
|
||||
// whatever specific values they need. Note: keep these in the same order
|
||||
// as OnAppStart.
|
||||
g_base->graphics->OnScreenSizeChange(virtual_width, virtual_height,
|
||||
pixel_width, pixel_height);
|
||||
// Inform all subsystems.
|
||||
// Note: keep these in the same order as OnAppStart.
|
||||
g_base->platform->OnScreenSizeChange();
|
||||
g_base->graphics->OnScreenSizeChange();
|
||||
g_base->audio->OnScreenSizeChange();
|
||||
g_base->input->OnScreenSizeChange();
|
||||
g_base->ui->OnScreenSizeChange();
|
||||
|
||||
@ -314,4 +314,11 @@ void BasePlatform::GetCursorPosition(float* x, float* y) {
|
||||
|
||||
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
|
||||
|
||||
@ -26,6 +26,13 @@ class BasePlatform {
|
||||
/// start talking to them.
|
||||
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 --------------------------------------------------
|
||||
|
||||
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_ref.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_simple_sound.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<PythonClassAppTimer>(module);
|
||||
PythonModuleBuilder::AddClass<PythonClassDisplayTimer>(module);
|
||||
PythonModuleBuilder::AddClass<PythonClassEnv>(module);
|
||||
PythonModuleBuilder::AddClass<PythonClassSimpleSound>(module);
|
||||
PythonModuleBuilder::AddClass<PythonClassContextCall>(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"
|
||||
"Tells whether the OS is currently playing music of some sort.\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 -----------------------------------
|
||||
@ -1491,6 +1491,67 @@ static PyMethodDef PyGetImmediateReturnCodeDef = {
|
||||
"(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> {
|
||||
@ -1540,6 +1601,9 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
|
||||
PyEmptyAppModeHandleIntentExecDef,
|
||||
PyGetImmediateReturnCodeDef,
|
||||
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
|
||||
// might need core_config access at any time.
|
||||
if (!g_buildconfig.monolithic_build()) {
|
||||
if (!HaveBaEnvVals()) {
|
||||
if (!have_ba_env_vals()) {
|
||||
static bool did_warn = false;
|
||||
if (!did_warn) {
|
||||
did_warn = true;
|
||||
|
||||
@ -115,7 +115,7 @@ class CoreFeatureSet {
|
||||
/// Return true if baenv values have been locked in: python paths, log
|
||||
/// handling, etc. Early-running code may wish to explicitly avoid making log
|
||||
/// 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
|
||||
/// files.
|
||||
|
||||
@ -396,7 +396,7 @@ auto CorePython::FetchPythonArgs(std::vector<std::string>* buffer)
|
||||
// argv pointers to it.
|
||||
std::vector<char*> out;
|
||||
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()));
|
||||
}
|
||||
return out;
|
||||
|
||||
@ -39,8 +39,9 @@ auto main(int argc, char** argv) -> int {
|
||||
namespace ballistica {
|
||||
|
||||
// These are set automatically via script; don't modify them here.
|
||||
const int kEngineBuildNumber = 21256;
|
||||
const char* kEngineVersion = "1.7.26";
|
||||
const int kEngineBuildNumber = 21269;
|
||||
const char* kEngineVersion = "1.7.27";
|
||||
const int kEngineApiVersion = 8;
|
||||
|
||||
#if BA_MONOLITHIC_BUILD
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ namespace ballistica {
|
||||
|
||||
extern const int kEngineBuildNumber;
|
||||
extern const char* kEngineVersion;
|
||||
extern const int kEngineApiVersion;
|
||||
|
||||
// Protocol version we host games with and write replays to.
|
||||
// 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) {
|
||||
BA_PYTHON_TRY;
|
||||
|
||||
// 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
|
||||
// simply print an error; we don't set any Python error state. Not sure if
|
||||
// that is ever even allowed from destructors anyway.
|
||||
// Because we used placement-new, we need to manually run the equivalent
|
||||
// destructor to clean ourself up. Note that if anything goes wrong here
|
||||
// it'll simply print an error; we don't set any Python error state. Not
|
||||
// sure if that is ever even allowed from destructors anyway.
|
||||
|
||||
// IMPORTANT: With Python objects we can't guarantee that this destructor runs
|
||||
// in a particular thread; if our object contains anything that must be
|
||||
// destructed in a particular thread then we should manually allocate &
|
||||
// deallocate things so we can ship it off to the proper thread for cleanup as
|
||||
// needed.
|
||||
// IMPORTANT: With Python objects we can't guarantee that this destructor
|
||||
// runs in a particular thread, so if that is something we need then we
|
||||
// should manually allocate stuff in tp_new and then ship a pointer off
|
||||
// from here to whatever thread needs to clean it up.
|
||||
self->~PythonClassHello();
|
||||
BA_PYTHON_DEALLOC_CATCH;
|
||||
Py_TYPE(self)->tp_free(reinterpret_cast<PyObject*>(self));
|
||||
@ -58,7 +57,32 @@ PythonClassHello::~PythonClassHello() {
|
||||
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;
|
||||
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
|
||||
|
||||
@ -36,8 +36,8 @@ class PythonClassHello : public PythonClass {
|
||||
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static void tp_dealloc(PythonClassHello* self);
|
||||
static auto Play(PythonClassHello* self, PyObject* args, PyObject* keywds)
|
||||
-> PyObject*;
|
||||
static auto TestMethod(PythonClassHello* self, PyObject* args,
|
||||
PyObject* keywds) -> PyObject*;
|
||||
};
|
||||
|
||||
} // namespace ballistica::template_fs
|
||||
|
||||
@ -71,13 +71,19 @@ def get_current_version() -> tuple[str, int]:
|
||||
def get_current_api_version() -> int:
|
||||
"""Pull current api version from the project."""
|
||||
with open(
|
||||
'src/assets/ba_data/python/babase/_meta.py', encoding='utf-8'
|
||||
'src/ballistica/shared/ballistica.cc', encoding='utf-8'
|
||||
) as infile:
|
||||
lines = infile.readlines()
|
||||
linestart = 'CURRENT_API_VERSION = '
|
||||
linestart = 'const int kEngineApiVersion = '
|
||||
lineend = ';'
|
||||
for line in lines:
|
||||
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.')
|
||||
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ OPENSSL_VER_APPLE = '3.0.8'
|
||||
OPENSSL_VER_ANDROID = '3.0.8'
|
||||
|
||||
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.
|
||||
PRUNE_LIB_NAMES = [
|
||||
@ -301,6 +302,14 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
|
||||
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.
|
||||
ftxt = replace_exact(
|
||||
ftxt,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user