Merge branch 'efroemling:master' into master

This commit is contained in:
Vishal 2023-12-19 06:47:31 +05:30 committed by GitHub
commit 1a99f7491b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 968 additions and 905 deletions

114
.efrocachemap generated
View File

@ -421,10 +421,10 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
"build/assets/ba_data/data/langdata.json": "f200cdf431b9494d8b96cdd47e950dd1",
"build/assets/ba_data/data/langdata.json": "7508c882bba2bbf6350cf1345c8c1347",
"build/assets/ba_data/data/languages/arabic.json": "00ba700de6c672a56658a6bd1ad27523",
"build/assets/ba_data/data/languages/belarussian.json": "40883823367f04c5a2403a96525bfcc3",
"build/assets/ba_data/data/languages/chinese.json": "5761468d25f2bd4e79921826cebd572b",
"build/assets/ba_data/data/languages/belarussian.json": "5602b4e83ab78af37097a29593dff3d5",
"build/assets/ba_data/data/languages/chinese.json": "ff9a595726f0aff42a39be576d0ff037",
"build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078",
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
"build/assets/ba_data/data/languages/czech.json": "cd21ad8c6b8e9ed700284cf1e1aecbf8",
@ -432,31 +432,31 @@
"build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e",
"build/assets/ba_data/data/languages/english.json": "6a3fab4fb8b2879e00ed9877709bf504",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
"build/assets/ba_data/data/languages/filipino.json": "6f4051ce78861a4666f4978d6f9a0ed2",
"build/assets/ba_data/data/languages/french.json": "49ff6d211537b8003b8241438dca661d",
"build/assets/ba_data/data/languages/filipino.json": "e750fb1a95e4c5611115f9ece9ecab53",
"build/assets/ba_data/data/languages/french.json": "163362f7b33866ef069cae62d0387551",
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
"build/assets/ba_data/data/languages/gibberish.json": "25fcb5130fae56985bee175aa19f86a2",
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
"build/assets/ba_data/data/languages/hindi.json": "8848f6b0caec0fcf9d85bc6e683809ec",
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
"build/assets/ba_data/data/languages/indonesian.json": "408fb026e84c24a8dd7a43cb2b794541",
"build/assets/ba_data/data/languages/italian.json": "f2f5641bc924dfba37af2aac03e469e0",
"build/assets/ba_data/data/languages/korean.json": "ae179765711bead59d0de7fb15fac72f",
"build/assets/ba_data/data/languages/italian.json": "1824b50a9154594e7039883f6d35504f",
"build/assets/ba_data/data/languages/korean.json": "03fd99d5e1155e81053fc028f69df982",
"build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38",
"build/assets/ba_data/data/languages/persian.json": "d742f4a6d3c3555031102b21abdcbb5b",
"build/assets/ba_data/data/languages/persian.json": "d9de4a0b17331434859e0a6a9ae160c5",
"build/assets/ba_data/data/languages/polish.json": "b9a58b70ed5e99d8b7fa2392b2eb0cda",
"build/assets/ba_data/data/languages/portuguese.json": "e3adc6c04486d21e84019a0b03ce11b1",
"build/assets/ba_data/data/languages/portuguese.json": "bc3b97a8213427ebbd1cfa43b4c5e6b6",
"build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826",
"build/assets/ba_data/data/languages/russian.json": "e120993371f52edd2d99f2236188933c",
"build/assets/ba_data/data/languages/russian.json": "e93713be12870dd0a01e2d1359d7c792",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef",
"build/assets/ba_data/data/languages/spanish.json": "1d14210b4eefb48130608bd0495b7900",
"build/assets/ba_data/data/languages/spanish.json": "b4a51de7298ca2273bae804a98b3804d",
"build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac",
"build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c",
"build/assets/ba_data/data/languages/thai.json": "77755219bbf5fb7eea0d6b226684f403",
"build/assets/ba_data/data/languages/turkish.json": "b1e491b70cec59cfa1057b8c5e7801b2",
"build/assets/ba_data/data/languages/turkish.json": "ab149ebbd57cf4daa3cf8f310d91519a",
"build/assets/ba_data/data/languages/ukrainian.json": "e5c861187c4c6db37d1a033f4ef3dd5a",
"build/assets/ba_data/data/languages/venetian.json": "9fe1a58d9e5dfb00f31ce3b2eb9993f4",
"build/assets/ba_data/data/languages/venetian.json": "a559a5608d2e0b4708f7a4dee42ff354",
"build/assets/ba_data/data/languages/vietnamese.json": "921cd1e50f60fe3e101f246e172750ba",
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
"build/assets/ba_data/data/maps/bridgit.json": "6aea74805f4880cc11237c5734a24422",
@ -4060,50 +4060,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": "6135aeb242afaf9d1114810a67c89cec",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "bbbbb14d42ed6eb0c5eb56867b7fb870",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cd28f9cc4652736a31c677fc4e5dbaf1",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "239c608cc52c0320210e56ad6abe57a5",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e76d67cacf1393d33796d6b6b1bf1413",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a7eaa8dc4d859ef7a735483b04ccec4a",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "7a2eef42da34a35ddcc2fd7c66843b1b",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "694599ac6a967b2ed383b27bf8093e5b",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "c91cbab6a07affa22e0612210f8b807c",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "d460f7a3909f92d5dbf752e4521a9fbc",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0a0abfe75bc987e7b65a3cfa106e8353",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "8f21405b29f2b2ab01323d711492cca0",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "96dc73e819f41f99a1b2dbb45f79d551",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c79ac51cd2deabb1c2d0acddeaf81c30",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "f06ec14e8c3106be9df91af7da621dc9",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f389f9a7b1afc81f76787722340cfa9c",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "c7dab78aac11cb1430d8456d5d48107a",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "67e29852dfee2e63e179cfebf608ef26",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "9778f8faf91c9993fbf3015bd4554a87",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "73477bd15b9e3834314fd878c9e108d4",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "fb9b8443c1b4cccad749df7d6328220f",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "384fb7fd55ad5a6cdbb662da1ec402ab",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "fb9b8443c1b4cccad749df7d6328220f",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "384fb7fd55ad5a6cdbb662da1ec402ab",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "bc7d0811bcd87156ebf5292a38a1c350",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "bb32f45054b6999300bf8b41d6a4b402",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "bc7d0811bcd87156ebf5292a38a1c350",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "bb32f45054b6999300bf8b41d6a4b402",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "8d9a1505bf397f4902baabed7c1cf438",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "f4d9c115e22dd81e36d1c5baeac8d848",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "8d9a1505bf397f4902baabed7c1cf438",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "f4d9c115e22dd81e36d1c5baeac8d848",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "fb72c92ec6ec0e1c8f4ced32abd86505",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "131aab20cfe77fe89c3f452a855f1e68",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "ee10cdc9f9a861e2be0f1a208c0ca0fe",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "131aab20cfe77fe89c3f452a855f1e68",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "678fabc6dfd6f401ee8942d088ee9181",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "e092d2aed8464a61a623d79ca25308d8",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6b658f49be396ad645c5e57464739a3b",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "9d79a56403a6d806ff131a7de664dfa7",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "e831a26d2c28e862d51e24393d158c99",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "46fe1c89bcc75c781729ec9e5491c610",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "9c6278d7df3ce4db2ffe7794a0fd35b7",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "110c35a17b462864075800756b5e541a",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "4d6df65a5417a0eb23f4838b26054ce6",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "d4c5bd03eda639f73b769664fae9691b",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "8837eed81869a6d9404afc890a99aeda",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "906ca39550dc91235ebdf1c217b3116b",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e54895522cdd27f020b66260acdcc42d",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a293a3b7af38f265c995beba6027be93",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "380a18b86065840c44285dc8762f42c4",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "2dc321ce47a02190e51c2f6f4f807449",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "117f585f14bb816ed0ec5bac792077c0",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "124b14dbca819bfc300ec6c521641f78",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "4df0f5f8183b325f5be25f2f9d5fa17e",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "023558d9028ba3250384f9d7d9ef9a17",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "4990e055b064408ede929a4c57253344",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "439574bda6d8d320ce38968886e83181",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "d62da566c946136a746d55ec04c28020",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "def8d189ca344df21b0a5bb9619fef12",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "b75406a2f66d2cb74359b78356f89710",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "cc1dccb6c5e63acf7bc42ee4346df9c6",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "27028549259c2f3843d54c3d29fad470",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "656d50ed1514203e967dcf90a594fe66",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "db535f0ca1e01af825f75f204fbc8928",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "97d51afca996ae15b61fd9f409a00459",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "db535f0ca1e01af825f75f204fbc8928",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "97d51afca996ae15b61fd9f409a00459",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "43794f4973b09588367261faf46b652a",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "9bc71c9874596dd708841d84dff69b55",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "43794f4973b09588367261faf46b652a",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "9bc71c9874596dd708841d84dff69b55",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "87f6bf3ea1196d91cd6486785702a5b2",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "60336f9d664825ca08236dda311276ca",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "87f6bf3ea1196d91cd6486785702a5b2",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "60336f9d664825ca08236dda311276ca",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "4399c87b58fbe58fa67096cfa878f86a",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "452623f0495dd4375e5b5d9b80d643d5",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "ca49b32ed573feea11613d62cd89840c",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "452623f0495dd4375e5b5d9b80d643d5",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "dfb0839e79411869b196bc48565a36bc",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "e2beda37ec0d3efde860d0c2e248b9e0",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "2adae2522084e571c51595c7d31ac1dc",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "4ef71e752bfb318db84e88dfdd51d955",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "a123d9ecd0d53d9ad2f13abdf9842ade",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "6afeb02344f2faf2979188e54c6d2dc4",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "abedb44a923a4845fdc350fd979a1dc8",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "c728bfecc990701ce831644fb43520f1",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",

View File

@ -1,8 +1,21 @@
### 1.7.31 (build 21707, api 8, 2023-12-13)
### 1.7.32 (build 21728, api 8, 2023-12-17)
### 1.7.31 (build 21727, api 8, 2023-12-17)
- Added `bascenev1.get_connection_to_host_info_2()` which is an improved
type-safe version of `bascenev1.get_connection_to_host_info()`.
- There is now a link to the official Discord server in the About section
(thanks EraOSBeta!).
- Native stack traces now work on Android; woohoo! Should be very helpful for
debugging.
- Added the concept of 'ui-operations' in the native layer to hopefully clear
out the remaining double-window bugs. Basically, widgets used to schedule
their payload commands to a future cycle of the event loop, meaning it was
possible for commands that switched the main window to get scheduled twice
before the first one ran (due to 2 key presses, etc), which could lead to all
sorts of weirdness happening such as multiple windows popping up when one was
intended. Now, however, such commands get scheduled to a current
'ui-operation' and then run *almost* immediately, which should prevent such
situations. Please holler if you run into any UI weirdness at this point.
### 1.7.30 (build 21697, api 8, 2023-12-08)
- Continued work on the big 1.7.28 update.

View File

@ -697,6 +697,7 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/shared/generic/json.cc
${BA_SRC_ROOT}/ballistica/shared/generic/json.h
${BA_SRC_ROOT}/ballistica/shared/generic/lambda_runnable.h
${BA_SRC_ROOT}/ballistica/shared/generic/native_stack_trace.h
${BA_SRC_ROOT}/ballistica/shared/generic/runnable.cc
${BA_SRC_ROOT}/ballistica/shared/generic/runnable.h
${BA_SRC_ROOT}/ballistica/shared/generic/snapshot.h

View File

@ -689,6 +689,7 @@
<ClCompile Include="..\..\src\ballistica\shared\generic\json.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\json.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\native_stack_trace.h" />
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h" />

View File

@ -1501,6 +1501,9 @@
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\shared\generic\native_stack_trace.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc">
<Filter>ballistica\shared\generic</Filter>
</ClCompile>

View File

@ -684,6 +684,7 @@
<ClCompile Include="..\..\src\ballistica\shared\generic\json.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\json.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\native_stack_trace.h" />
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc" />
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h" />
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h" />

View File

@ -1501,6 +1501,9 @@
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\shared\generic\native_stack_trace.h">
<Filter>ballistica\shared\generic</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc">
<Filter>ballistica\shared\generic</Filter>
</ClCompile>

View File

@ -61,7 +61,6 @@ from _babase import (
in_logic_thread,
increment_analytics_count,
is_os_playing_music,
is_running_on_fire_tv,
is_xcode_build,
lock_all_input,
mac_music_app_get_playlists,
@ -77,6 +76,7 @@ from _babase import (
native_review_request,
native_review_request_supported,
native_stack_trace,
open_file_externally,
print_load_info,
pushcall,
quit,
@ -258,7 +258,6 @@ __all__ = [
'is_browser_likely_available',
'is_os_playing_music',
'is_point_in_box',
'is_running_on_fire_tv',
'is_xcode_build',
'LanguageSubsystem',
'lock_all_input',
@ -283,6 +282,7 @@ __all__ = [
'NodeNotFoundError',
'normalized_color',
'NotFoundError',
'open_file_externally',
'Permission',
'PlayerNotFoundError',
'Plugin',

View File

@ -9,7 +9,6 @@ import logging
from typing import TYPE_CHECKING
import babase
import bauiv1
import bascenev1
if TYPE_CHECKING:
@ -72,7 +71,8 @@ class AdsSubsystem:
) -> None:
"""(internal)"""
self.last_ad_purpose = purpose
bauiv1.show_ad(purpose, on_completion_call)
assert babase.app.plus is not None
babase.app.plus.show_ad(purpose, on_completion_call)
def show_ad_2(
self,
@ -81,7 +81,8 @@ class AdsSubsystem:
) -> None:
"""(internal)"""
self.last_ad_purpose = purpose
bauiv1.show_ad_2(purpose, on_completion_call)
assert babase.app.plus is not None
babase.app.plus.show_ad_2(purpose, on_completion_call)
def call_after_ad(self, call: Callable[[], Any]) -> None:
"""Run a call after potentially showing an ad."""

View File

@ -20,7 +20,6 @@ def get_input_device_mapped_value(
This checks the user config and falls back to default values
where available.
"""
# pylint: disable=too-many-statements
# pylint: disable=too-many-return-statements
# pylint: disable=too-many-branches
@ -83,91 +82,6 @@ def get_input_device_mapped_value(
'triggerRun1': 5,
}.get(name, -1)
# Look for some exact types.
if babase.is_running_on_fire_tv():
if devicename in ['Thunder', 'Amazon Fire Game Controller']:
return {
'triggerRun2': 23,
'unassignedButtonsRun': False,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'analogStickDeadZone': 0.0,
'startButtonActivatesDefaultWidget': False,
'buttonStart': 83,
'buttonPunch': 100,
'buttonRun2': 103,
'buttonRun1': 104,
'triggerRun1': 24,
}.get(name, -1)
if devicename == 'NYKO PLAYPAD PRO':
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'buttonUp': 20,
'buttonLeft': 22,
'buttonRight': 23,
'buttonStart': 83,
'buttonPunch': 100,
'buttonDown': 21,
}.get(name, -1)
if devicename == 'Logitech Dual Action':
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 98,
'buttonBomb': 101,
'buttonJump': 100,
'buttonStart': 109,
'buttonPunch': 97,
}.get(name, -1)
if devicename == 'Xbox 360 Wireless Receiver':
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'buttonUp': 20,
'buttonLeft': 22,
'buttonRight': 23,
'buttonStart': 83,
'buttonPunch': 100,
'buttonDown': 21,
}.get(name, -1)
if devicename == 'Microsoft X-Box 360 pad':
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'buttonStart': 83,
'buttonPunch': 100,
}.get(name, -1)
if devicename in [
'Amazon Remote',
'Amazon Bluetooth Dev',
'Amazon Fire TV Remote',
]:
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 24,
'buttonBomb': 91,
'buttonJump': 86,
'buttonUp': 20,
'buttonLeft': 22,
'startButtonActivatesDefaultWidget': False,
'buttonRight': 23,
'buttonStart': 83,
'buttonPunch': 90,
'buttonDown': 21,
}.get(name, -1)
elif 'NVIDIA SHIELD;' in useragentstring:
if 'NVIDIA Controller' in devicename:
return {
@ -182,112 +96,6 @@ def get_input_device_mapped_value(
'buttonIgnored': 184,
'buttonIgnored2': 86,
}.get(name, -1)
elif platform == 'mac':
if devicename == 'PLAYSTATION(R)3 Controller':
return {
'buttonLeft': 8,
'buttonUp': 5,
'buttonRight': 6,
'buttonDown': 7,
'buttonJump': 15,
'buttonPunch': 16,
'buttonBomb': 14,
'buttonPickUp': 13,
'buttonStart': 4,
'buttonIgnored': 17,
}.get(name, -1)
if devicename in ['Wireless 360 Controller', 'Controller']:
# Xbox360 gamepads
return {
'analogStickDeadZone': 1.2,
'buttonBomb': 13,
'buttonDown': 2,
'buttonJump': 12,
'buttonLeft': 3,
'buttonPickUp': 15,
'buttonPunch': 14,
'buttonRight': 4,
'buttonStart': 5,
'buttonUp': 1,
'triggerRun1': 5,
'triggerRun2': 6,
'buttonIgnored': 11,
}.get(name, -1)
if devicename in [
'Logitech Dual Action',
'Logitech Cordless RumblePad 2',
]:
return {
'buttonJump': 2,
'buttonPunch': 1,
'buttonBomb': 3,
'buttonPickUp': 4,
'buttonStart': 10,
}.get(name, -1)
# Old gravis gamepad.
if devicename == 'GamePad Pro USB ':
return {
'buttonJump': 2,
'buttonPunch': 1,
'buttonBomb': 3,
'buttonPickUp': 4,
'buttonStart': 10,
}.get(name, -1)
if devicename == 'Microsoft SideWinder Plug & Play Game Pad':
return {
'buttonJump': 1,
'buttonPunch': 3,
'buttonBomb': 2,
'buttonPickUp': 4,
'buttonStart': 6,
}.get(name, -1)
# Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..)
if devicename == 'Saitek P2500 Rumble Force Pad':
return {
'buttonJump': 3,
'buttonPunch': 1,
'buttonBomb': 4,
'buttonPickUp': 2,
'buttonStart': 11,
}.get(name, -1)
# Some crazy 'Senze' dual gamepad.
if devicename == 'Twin USB Joystick':
return {
'analogStickLR': 3,
'analogStickLR_B': 7,
'analogStickUD': 4,
'analogStickUD_B': 8,
'buttonBomb': 2,
'buttonBomb_B': 14,
'buttonJump': 3,
'buttonJump_B': 15,
'buttonPickUp': 1,
'buttonPickUp_B': 13,
'buttonPunch': 4,
'buttonPunch_B': 16,
'buttonRun1': 7,
'buttonRun1_B': 19,
'buttonRun2': 8,
'buttonRun2_B': 20,
'buttonStart': 10,
'buttonStart_B': 22,
'enableSecondary': 1,
'unassignedButtonsRun': False,
}.get(name, -1)
if devicename == 'USB Gamepad ': # some weird 'JITE' gamepad
return {
'analogStickLR': 4,
'analogStickUD': 5,
'buttonJump': 3,
'buttonPunch': 4,
'buttonBomb': 2,
'buttonPickUp': 1,
'buttonStart': 10,
}.get(name, -1)
default_android_mapping = {
'triggerRun2': 19,
@ -310,6 +118,41 @@ def get_input_device_mapped_value(
# Generic android...
if platform == 'android':
if devicename in ['Amazon Fire Game Controller']:
return {
'triggerRun2': 23,
'unassignedButtonsRun': False,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'analogStickDeadZone': 0.0,
'startButtonActivatesDefaultWidget': False,
'buttonStart': 83,
'buttonPunch': 100,
'buttonRun2': 103,
'buttonRun1': 104,
'triggerRun1': 24,
}.get(name, -1)
if devicename in [
'Amazon Remote',
'Amazon Bluetooth Dev',
'Amazon Fire TV Remote',
]:
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 24,
'buttonBomb': 91,
'buttonJump': 86,
'buttonUp': 20,
'buttonLeft': 22,
'startButtonActivatesDefaultWidget': False,
'buttonRight': 23,
'buttonStart': 83,
'buttonPunch': 90,
'buttonDown': 21,
}.get(name, -1)
# Steelseries stratus xl.
if devicename == 'SteelSeries Stratus XL':
return {
@ -387,14 +230,6 @@ def get_input_device_mapped_value(
'uiOnly': True,
}.get(name, -1)
# flag particular gamepads to use exact android defaults..
# (so they don't even ask to configure themselves)
if devicename in [
'Samsung Game Pad EI-GP20',
'ASUS Gamepad',
] or devicename.startswith('Freefly VR Glide'):
return default_android_mapping.get(name, -1)
# Nvidia controller is default, but gets some strange
# keypresses we want to ignore.. touching the touchpad,
# so lets ignore those.
@ -452,76 +287,11 @@ def get_input_device_mapped_value(
'buttonRight': 100,
}.get(name, -1)
# Ok, this gamepad's not in our specific preset list;
# fall back to some (hopefully) reasonable defaults.
# Leaving these in here for now but not gonna add any more now that we have
# fancy-pants config sharing across the internet.
if platform == 'mac':
if 'PLAYSTATION' in devicename: # ps3 gamepad?..
return {
'buttonLeft': 8,
'buttonUp': 5,
'buttonRight': 6,
'buttonDown': 7,
'buttonJump': 15,
'buttonPunch': 16,
'buttonBomb': 14,
'buttonPickUp': 13,
'buttonStart': 4,
}.get(name, -1)
# Dual Action Config - hopefully applies to more...
if 'Logitech' in devicename:
return {
'buttonJump': 2,
'buttonPunch': 1,
'buttonBomb': 3,
'buttonPickUp': 4,
'buttonStart': 10,
}.get(name, -1)
# Saitek P2500 Rumble Force Pad.. (hopefully works for others too?..)
if 'Saitek' in devicename:
return {
'buttonJump': 3,
'buttonPunch': 1,
'buttonBomb': 4,
'buttonPickUp': 2,
'buttonStart': 11,
}.get(name, -1)
# Gravis stuff?...
if 'GamePad' in devicename:
return {
'buttonJump': 2,
'buttonPunch': 1,
'buttonBomb': 3,
'buttonPickUp': 4,
'buttonStart': 10,
}.get(name, -1)
# Ok, this gamepad's not in our specific preset list; fall back to
# some (hopefully) reasonable defaults.
# Reasonable defaults.
if platform == 'android':
if babase.is_running_on_fire_tv():
# Mostly same as default firetv controller.
return {
'triggerRun2': 23,
'triggerRun1': 24,
'buttonPickUp': 101,
'buttonBomb': 98,
'buttonJump': 97,
'buttonStart': 83,
'buttonPunch': 100,
'buttonDown': 21,
'buttonUp': 20,
'buttonLeft': 22,
'buttonRight': 23,
'startButtonActivatesDefaultWidget': False,
}.get(name, -1)
# Mostly same as 'Gamepad' except with 'menu' for default start
# button instead of 'mode'.
return default_android_mapping.get(name, -1)
# Is there a point to any sort of fallbacks here?.. should check.
@ -540,9 +310,9 @@ def _gen_android_input_hash() -> str:
md5 = hashlib.md5()
# Currently we just do a single hash of *all* inputs on android
# and that's it.. good enough.
# (grabbing mappings for a specific device looks to be non-trivial)
# Currently we just do a single hash of *all* inputs on android and
# that's it. Good enough. (grabbing mappings for a specific device
# looks to be non-trivial)
for dirname in [
'/system/usr/keylayout',
'/data/usr/keylayout',
@ -551,9 +321,9 @@ def _gen_android_input_hash() -> str:
try:
if os.path.isdir(dirname):
for f_name in os.listdir(dirname):
# This is usually volume keys and stuff;
# assume we can skip it?..
# (since it'll vary a lot across devices)
# This is usually volume keys and stuff; assume we
# can skip it?.. (since it'll vary a lot across
# devices)
if f_name == 'gpio-keys.kl':
continue
try:
@ -576,8 +346,8 @@ def get_input_device_map_hash() -> str:
"""
app = babase.app
# Currently only using this when classic is present.
# Need to replace with a modern equivalent.
# Currently only using this when classic is present. Need to replace
# with a modern equivalent.
if app.classic is not None:
try:
if app.classic.input_map_hash is None:

View File

@ -451,15 +451,6 @@ class ClassicSubsystem(babase.AppSubsystem):
if playtype in val.get_play_types()
)
def show_online_score_ui(
self,
show: str = 'general',
game: str | None = None,
game_version: str | None = None,
) -> None:
"""(internal)"""
bauiv1.show_online_score_ui(show, game, game_version)
def game_begin_analytics(self) -> None:
"""(internal)"""
from baclassic import _analytics

View File

@ -52,8 +52,8 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
TARGET_BALLISTICA_BUILD = 21707
TARGET_BALLISTICA_VERSION = '1.7.31'
TARGET_BALLISTICA_BUILD = 21728
TARGET_BALLISTICA_VERSION = '1.7.32'
@dataclass

View File

@ -264,3 +264,26 @@ class PlusSubsystem(AppSubsystem):
def can_show_ad() -> bool:
"""Can we show an ad?"""
return _baplus.can_show_ad()
@staticmethod
def show_ad(
purpose: str, on_completion_call: Callable[[], None] | None = None
) -> None:
"""Show an ad."""
_baplus.show_ad(purpose, on_completion_call)
@staticmethod
def show_ad_2(
purpose: str, on_completion_call: Callable[[bool], None] | None = None
) -> None:
"""Show an ad."""
_baplus.show_ad_2(purpose, on_completion_call)
@staticmethod
def show_game_service_ui(
show: str = 'general',
game: str | None = None,
game_version: str | None = None,
) -> None:
"""Show game-service provided UI."""
_baplus.show_game_service_ui(show, game, game_version)

View File

@ -284,20 +284,20 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
self.end({'outcome': 'next_level'})
def _ui_gc(self) -> None:
if bs.app.classic is not None:
bs.app.classic.show_online_score_ui(
if bs.app.plus is not None:
bs.app.plus.show_game_service_ui(
'leaderboard',
game=self._game_name_str,
game_version=self._game_config_str,
)
else:
logging.warning('show_online_score_ui requires classic')
logging.warning('show_game_service_ui requires plus feature-set')
def _ui_show_achievements(self) -> None:
if bs.app.classic is not None:
bs.app.classic.show_online_score_ui('achievements')
if bs.app.plus is not None:
bs.app.plus.show_game_service_ui('achievements')
else:
logging.warning('show_online_score_ui requires classic')
logging.warning('show_game_service_ui requires plus feature-set')
def _ui_worlds_best(self) -> None:
if self._score_link is None:

View File

@ -60,7 +60,6 @@ from babase import (
in_logic_thread,
increment_analytics_count,
is_browser_likely_available,
is_running_on_fire_tv,
is_xcode_build,
lock_all_input,
LoginAdapter,
@ -69,6 +68,7 @@ from babase import (
native_review_request,
native_review_request_supported,
NotFoundError,
open_file_externally,
Permission,
Plugin,
PluginSpec,
@ -105,15 +105,11 @@ from _bauiv1 import (
imagewidget,
is_party_icon_visible,
Mesh,
open_file_externally,
open_url,
rowwidget,
scrollwidget,
set_party_icon_always_visible,
set_party_window_open,
show_ad,
show_ad_2,
show_online_score_ui,
Sound,
Texture,
textwidget,
@ -181,7 +177,6 @@ __all__ = [
'increment_analytics_count',
'is_browser_likely_available',
'is_party_icon_visible',
'is_running_on_fire_tv',
'is_xcode_build',
'Keyboard',
'lock_all_input',
@ -210,9 +205,6 @@ __all__ = [
'set_party_icon_always_visible',
'set_party_window_open',
'set_ui_input_device',
'show_ad',
'show_ad_2',
'show_online_score_ui',
'Sound',
'SpecialChar',
'supports_max_fps',

View File

@ -58,6 +58,7 @@ class OnScreenKeyboardWindow(Window):
position=(30, self._height - 55),
size=(60, 60),
label='',
enable_sound=False,
on_activate_call=self._cancel,
autoselect=True,
color=(0.55, 0.5, 0.6),

View File

@ -1258,19 +1258,21 @@ class AccountSettingsWindow(bui.Window):
self._needs_refresh = False
def _on_game_service_button_press(self) -> None:
if bui.app.classic is not None:
bui.app.classic.show_online_score_ui()
if bui.app.plus is not None:
bui.app.plus.show_game_service_ui()
else:
logging.warning('game service ui not available without classic.')
logging.warning(
'game-service-ui not available without plus feature-set.'
)
def _on_custom_achievements_press(self) -> None:
if bui.app.classic is not None:
if bui.app.plus is not None:
bui.apptimer(
0.15,
bui.Call(bui.app.classic.show_online_score_ui, 'achievements'),
bui.Call(bui.app.plus.show_game_service_ui, 'achievements'),
)
else:
logging.warning('show_online_score_ui requires classic')
logging.warning('show_game_service_ui requires plus feature-set.')
def _on_achievements_press(self) -> None:
# pylint: disable=cyclic-import
@ -1327,13 +1329,13 @@ class AccountSettingsWindow(bui.Window):
bui.open_url(response.url)
def _on_leaderboards_press(self) -> None:
if bui.app.classic is not None:
if bui.app.plus is not None:
bui.apptimer(
0.15,
bui.Call(bui.app.classic.show_online_score_ui, 'leaderboards'),
bui.Call(bui.app.plus.show_game_service_ui, 'leaderboards'),
)
else:
logging.warning('show_online_score_ui requires classic')
logging.warning('show_game_service_ui requires classic')
def _have_unlinkable_v1_accounts(self) -> bool:
plus = bui.app.plus

View File

@ -1244,10 +1244,12 @@ class MainMenuWindow(bui.Window):
def _do_game_service_press(self) -> None:
self._save_state()
if bui.app.classic is not None:
bui.app.classic.show_online_score_ui()
if bui.app.plus is not None:
bui.app.plus.show_game_service_ui()
else:
logging.warning('classic is required to show game service ui')
logging.warning(
'plus feature-set is required to show game service ui'
)
def _save_state(self) -> None:
# Don't do this for the in-game menu.

View File

@ -20,6 +20,13 @@
#if BA_OPENAL_IS_SOFT
#define AL_ALEXT_PROTOTYPES
#include <alext.h>
// Has not been formalized into an extension yet (from alc/inprogext.h"
// typedef void(ALC_APIENTRY* LPALSOFTLOGCALLBACK)(void* userptr, char level,
// const char* message,
// int length) noexcept;
// typedef void(ALC_APIENTRY* LPALSOFTSETLOGCALLBACK)(LPALSOFTLOGCALLBACK
// callback,
// void* userptr) noexcept;
#endif
#define CHECK_AL_ERROR _check_al_error(__FILE__, __LINE__)

View File

@ -4,6 +4,11 @@
#include <algorithm>
// Ew fixme.
#if BA_OSTYPE_ANDROID
#include <android/log.h>
#endif
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/assets/assets.h"
#include "ballistica/base/assets/sound_asset.h"
@ -34,6 +39,8 @@ LPALCDEVICERESUMESOFT alcDeviceResumeSOFT{};
LPALCRESETDEVICESOFT alcResetDeviceSOFT{};
LPALEVENTCALLBACKSOFT alEventCallbackSOFT{};
LPALEVENTCONTROLSOFT alEventControlSOFT{};
// LPALSOFTSETLOGCALLBACK alsoft_set_log_callback{};
#endif
const int kAudioProcessIntervalNormal{500 * 1000};
@ -168,8 +175,42 @@ static void ALEventCallback_(ALenum eventType, ALuint object, ALuint param,
+ std::to_string(static_cast<int>(eventType)));
}
}
// FIXME: Should convert this to a generalized OpenALSoft log handler since
// we might want to wire it up on other platforms too.
#if BA_OSTYPE_ANDROID
static void ALCustomAndroidLogCallback_(int severity, const char* msg) {
// Let's log everything directly that is a warning or worse and store
// everything else (up to some size limit). We can then explicitly ship
// the full log if a serious problem occurs.
if (severity >= ANDROID_LOG_WARN) {
__android_log_print(severity, "BallisticaKit", "openal-log: %s", msg);
}
g_base->audio_server->OpenALSoftLogCallback(msg);
}
#endif // BA_OSTYPE_ANDROID
void ALCustomLogCallback_(void* userptr, char level, const char* message,
int length) noexcept {
// Log(LogLevel::kInfo, "HELLO FROM GENERIC CUSTOM LOGGER");
}
#endif // BA_OPENAL_IS_SOFT
void AudioServer::OpenALSoftLogCallback(const std::string& msg) {
size_t log_cap{1024 * 11};
std::scoped_lock lock(openalsoft_log_mutex_);
if (openalsoft_log_.size() < log_cap) {
openalsoft_log_ += "openal-log("
+ std::to_string(g_core->GetAppTimeSeconds())
+ "s): " + msg;
if (openalsoft_log_.size() >= log_cap) {
openalsoft_log_ += "\n<max openalsoft log storage size reached>\n";
}
}
}
void AudioServer::OnAppStartInThread_() {
assert(g_base->InAudioThread());
@ -219,14 +260,38 @@ void AudioServer::OnAppStartInThread_() {
}
#endif // BA_RIFT_BUILD
// Wire up our custom log callback where applicable.
#if BA_OSTYPE_ANDROID
// alsoft_set_log_callback(ALCustomLogCallback_, nullptr);
alcSetCustomAndroidLogger(ALCustomAndroidLogCallback_);
#endif
auto* device = alcOpenDevice(al_device_name);
if (!device) {
if (g_buildconfig.ostype_android()) {
std::scoped_lock lock(openalsoft_log_mutex_);
Log(LogLevel::kError,
"------------------------"
" OPENALSOFT-FATAL-ERROR-LOG-BEGIN ----------------------\n"
+ openalsoft_log_
+ "\n-------------------------"
" OPENALSOFT-FATAL-ERROR-LOG-END -----------------------");
}
FatalError(
"No audio devices found. Do you have speakers/headphones/etc. "
"connected?");
}
impl_->alc_context = alcCreateContext(device, nullptr);
if (!impl_->alc_context) {
if (g_buildconfig.ostype_android()) {
std::scoped_lock lock(openalsoft_log_mutex_);
Log(LogLevel::kError,
"------------------------"
" OPENALSOFT-FATAL-ERROR-LOG-BEGIN ----------------------\n"
+ openalsoft_log_
+ "\n-------------------------"
" OPENALSOFT-FATAL-ERROR-LOG-END -----------------------");
}
FatalError(
"Unable to init audio. Do you have speakers/headphones/etc. "
"connected?");
@ -253,6 +318,9 @@ void AudioServer::OnAppStartInThread_() {
alEventControlSOFT = reinterpret_cast<LPALEVENTCONTROLSOFT>(
alcGetProcAddress(device, "alEventControlSOFT"));
BA_PRECONDITION_FATAL(alEventControlSOFT != nullptr);
// alsoft_set_log_callback = reinterpret_cast<LPALSOFTSETLOGCALLBACK>(
// alcGetProcAddress(device, "alsoft_set_log_callback"));
// BA_PRECONDITION_FATAL(alsoft_set_log_callback != nullptr);
// Ask to be notified when a device is disconnected.
alEventCallbackSOFT(ALEventCallback_, nullptr);

View File

@ -29,9 +29,6 @@ class AudioServer {
void PushSetVolumesCall(float music_volume, float sound_volume);
void PushSetSoundPitchCall(float val);
// static void BeginInterruption();
// static void EndInterruption();
void PushSetListenerPositionCall(const Vector3f& p);
void PushSetListenerOrientationCall(const Vector3f& forward,
const Vector3f& up);
@ -68,6 +65,7 @@ class AudioServer {
auto event_loop() const -> EventLoop* { return event_loop_; }
void OnDeviceDisconnected();
void OpenALSoftLogCallback(const std::string& msg);
private:
class ThreadSource_;
@ -132,6 +130,9 @@ class AudioServer {
seconds_t last_started_playing_time_{};
millisecs_t last_sound_fade_process_time_{};
std::mutex openalsoft_log_mutex_;
std::string openalsoft_log_;
/// Indexed list of sources.
std::vector<ThreadSource_*> sources_;
std::vector<ThreadSource_*> streaming_sources_;

View File

@ -14,6 +14,7 @@
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/ui/dev_console.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/shared/generic/utils.h"
namespace ballistica::base {
@ -232,24 +233,6 @@ static PyMethodDef PyClipboardGetTextDef = {
" this function.",
};
// ---------------------------- is_running_on_ouya -----------------------------
auto PyIsRunningOnOuya(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyIsRunningOnOuyaDef = {
"is_running_on_ouya", // name
PyIsRunningOnOuya, // method
METH_VARARGS, // flags
"is_running_on_ouya() -> bool\n"
"\n"
"(internal)",
};
// ------------------------------ setup_sigint ---------------------------------
static auto PySetUpSigInt(PyObject* self) -> PyObject* {
@ -273,27 +256,6 @@ static PyMethodDef PySetUpSigIntDef = {
"(internal)",
};
// -------------------------- is_running_on_fire_tv ----------------------------
static auto PyIsRunningOnFireTV(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
if (g_core->platform->IsRunningOnFireTV()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyIsRunningOnFireTVDef = {
"is_running_on_fire_tv", // name
PyIsRunningOnFireTV, // method
METH_VARARGS, // flags
"is_running_on_fire_tv() -> bool\n"
"\n"
"(internal)",
};
// ---------------------------- have_permission --------------------------------
static auto PyHavePermission(PyObject* self, PyObject* args, PyObject* keywds)
@ -1369,7 +1331,7 @@ static PyMethodDef PyUnlockAllInputDef = {
static auto PyNativeStackTrace(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
assert(g_core);
auto* trace = g_core->platform->GetStackTrace();
auto* trace = g_core->platform->GetNativeStackTrace();
if (!trace) {
Py_RETURN_NONE;
}
@ -1814,6 +1776,35 @@ static PyMethodDef PyTempTestingDef = {
"(internal)",
};
// ------------------------- open_file_externally ------------------------------
static auto PyOpenFileExternally(PyObject* self, PyObject* args,
PyObject* keywds) -> PyObject* {
BA_PYTHON_TRY;
char* path = nullptr;
static const char* kwlist[] = {"path", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
const_cast<char**>(kwlist), &path)) {
return nullptr;
}
g_base->platform->OpenFileExternally(path);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyOpenFileExternallyDef = {
"open_file_externally", // name
(PyCFunction)PyOpenFileExternally, // method
METH_VARARGS | METH_KEYWORDS, // flags
"open_file_externally(path: str) -> None\n"
"\n"
"(internal)\n"
"\n"
"Open the provided file in the default external app.",
};
// -----------------------------------------------------------------------------
auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
@ -1859,8 +1850,6 @@ auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
PyInLogicThreadDef,
PyRequestPermissionDef,
PyHavePermissionDef,
PyIsRunningOnFireTVDef,
PyIsRunningOnOuyaDef,
PyUnlockAllInputDef,
PyLockAllInputDef,
PySetUpSigIntDef,
@ -1883,6 +1872,7 @@ auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
PyNativeReviewRequestSupportedDef,
PyNativeReviewRequestDef,
PyTempTestingDef,
PyOpenFileExternallyDef,
};
}

View File

@ -3,8 +3,10 @@
#include "ballistica/base/python/support/python_context_call.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/core/python/core_python.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/generic/lambda_runnable.h"
#include "ballistica/shared/generic/utils.h"
#include "ballistica/shared/python/python.h"
#include "ballistica/shared/python/python_sys.h"
@ -166,4 +168,28 @@ void PythonContextCall::ScheduleWeak(const PythonRef& args) {
});
}
void PythonContextCall::ScheduleInUIOperation() {
// Since we're mucking with Object::WeakRefs, need to limit to logic thread.
BA_PRECONDITION(g_base->InLogicThread());
Object::Ref<PythonContextCall> ref(this);
assert(base::g_base);
g_base->ui->PushUIOperationRunnable(NewLambdaRunnableUnmanaged([ref] {
assert(ref.Exists());
ref->Run();
}));
}
void PythonContextCall::ScheduleInUIOperation(const PythonRef& args) {
// Since we're mucking with Object::WeakRefs, need to limit to logic thread.
BA_PRECONDITION(g_base->InLogicThread());
Object::Ref<PythonContextCall> ref(this);
assert(base::g_base);
g_base->ui->PushUIOperationRunnable(NewLambdaRunnableUnmanaged([ref, args] {
assert(ref.Exists());
ref->Run(args);
}));
}
} // namespace ballistica::base

View File

@ -58,6 +58,28 @@ class PythonContextCall : public Object {
/// scheduled run.
void ScheduleWeak(const PythonRef& args);
/// Schedule a call to run as part of a current UI interaction such as a
/// button being clicked. Must be called from the logic thread. Calls
/// scheduled this way will be run as part of the handling of the event
/// that triggered them, though safely outside of any UI traversal. This
/// avoids pitfalls that can arise with regular Schedule() where calls
/// that run some action and then disable further UI interaction can get
/// run twice due to interaction not actually being disabled until the
/// next event loop cycle, potentially allowing multiple calls to be
/// scheduled before the disable happens.
void ScheduleInUIOperation();
/// Schedule a call to run as part of a current UI interaction such as a
/// button being clicked. Must be called from the logic thread. Calls
/// scheduled this way will be run as part of the handling of the event
/// that triggered them, though safely outside of any UI traversal. This
/// avoids pitfalls that can arise with regular Schedule() where calls
/// that run some action and then disable further UI interaction can get
/// run twice due to interaction not actually being disabled until the
/// next event loop cycle, potentially allowing multiple calls to be
/// scheduled before the disable happens.
void ScheduleInUIOperation(const PythonRef& args);
private:
void GetTrace(); // we try to grab basic trace info

View File

@ -2,6 +2,8 @@
#include "ballistica/base/ui/ui.h"
#include <exception>
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/audio/audio.h"
#include "ballistica/base/graphics/component/simple_component.h"
@ -12,12 +14,100 @@
#include "ballistica/base/ui/dev_console.h"
#include "ballistica/base/ui/ui_delegate.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/foundation/macros.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/shared/generic/utils.h"
namespace ballistica::base {
static const int kUIOwnerTimeoutSeconds = 30;
/// We use this to gather up runnables triggered by UI elements in response
/// to stuff happening (mouse clicks, elements being added or removed,
/// etc.). It's a bad idea to run such runnables immediately because they
/// might modify UI lists we're in the process of traversing. It's also a
/// bad idea to schedule such runnables in the event loop, because a
/// runnable may wish to modify the UI to prevent further runs from
/// happening and that won't work if multiple runnables can be scheduled
/// before the first runs. So our goldilocks approach here is to gather
/// all runnables that get scheduled as part of each operation and then
/// run them explicitly once we are safely out of any UI list traversal.
UI::OperationContext::OperationContext() {
assert(g_base->InLogicThread());
// Register ourself as current only if there is none.
parent_ = g_base->ui->operation_context_;
if (parent_ == nullptr) {
g_base->ui->operation_context_ = this;
}
}
UI::OperationContext::~OperationContext() {
assert(g_base->InLogicThread());
// If we registered ourself as the top level context, unregister.
if (parent_ == nullptr) {
assert(g_base->ui->operation_context_ == this);
g_base->ui->operation_context_ = nullptr;
} else {
// If a context was set when we came into existence, it should
// still be that same context when we go out of existence.
assert(g_base->ui->operation_context_ == parent_);
assert(runnables_.empty());
}
// Complain if our Finish() call was never run (unless we're being torn
// down due to an exception).
if (!ran_finish_ && !std::current_exception()) {
BA_LOG_ERROR_NATIVE_TRACE_ONCE(
"UI::InteractionContext_ being torn down without Complete called.");
}
// Our runnables are raw unmanaged pointers; need to explicitly kill them.
// Finish generally clears these out as it goes, but there might be some
// left in the case of exceptions or infinite loop breakouts.
for (auto* ptr : runnables_) {
delete ptr;
}
}
void UI::OperationContext::AddRunnable(Runnable* runnable) {
// This should only be getting called when we installed ourself as top
// level context.
assert(parent_ == nullptr);
assert(Object::IsValidUnmanagedObject(runnable));
runnables_.push_back(runnable);
}
/// Should be explicitly called at the end of the operation.
void UI::OperationContext::Finish() {
assert(g_base->InLogicThread());
assert(!ran_finish_);
ran_finish_ = true;
// Run pent up runnaables. It's possible that the payload of something
// scheduled here will itself schedule something here, so we need to do
// this in a loop (and watch for infinite ones).
int cycle_count{};
while (!runnables_.empty()) {
std::vector<Runnable*> runnables;
runnables.swap(runnables_);
for (auto* runnable : runnables) {
runnable->RunAndLogErrors();
// Our runnables are raw unmanaged pointers; need to explicitly kill
// them.
delete runnable;
}
cycle_count += 1;
if (cycle_count >= 10) {
BA_LOG_ERROR_NATIVE_TRACE(
"UIOperationCount cycle-count hit max; you probably have an infinite "
"loop.");
break;
}
}
}
UI::UI() {
assert(g_core);
@ -58,12 +148,6 @@ void UI::StepDisplayTime() {
void UI::OnAppStart() {
assert(g_base->InLogicThread());
// if (auto* ui_delegate = g_base->ui->delegate()) {
// printf("HAVE DEL %d\n",
// static_cast<int>(g_base->ui->delegate() != nullptr));
// g_base->ui_v1()->OnAppStart();
// }
// Make sure user knows when forced-ui-scale is enabled.
if (force_scale_) {
if (scale_ == UIScale::kSmall) {
@ -129,6 +213,8 @@ auto UI::PartyWindowOpen() -> bool {
auto UI::HandleMouseDown(int button, float x, float y, bool double_click)
-> bool {
assert(g_base->InLogicThread());
bool handled{};
// Dev console button.
@ -265,11 +351,20 @@ auto UI::ShouldShowButtonShortcuts() const -> bool {
return g_base->input->have_non_touch_inputs();
}
auto UI::SendWidgetMessage(const WidgetMessage& m) -> int {
auto UI::SendWidgetMessage(const WidgetMessage& m) -> bool {
OperationContext operation_context;
bool result;
if (auto* ui_delegate = g_base->ui->delegate()) {
return ui_delegate->SendWidgetMessage(m);
result = ui_delegate->SendWidgetMessage(m);
} else {
result = false;
}
return false;
// Run anything we triggered.
operation_context.Finish();
return result;
}
void UI::OnScreenSizeChange() {
@ -541,4 +636,28 @@ void UI::OnAssetsAvailable() {
}
}
void UI::PushUIOperationRunnable(Runnable* runnable) {
assert(g_base->InLogicThread());
if (operation_context_ != nullptr) {
operation_context_->AddRunnable(runnable);
return;
}
// For now, gracefully fall back to pushing an event if there's no current
// operation. Once we've got any bugs cleared out, can leave this as just
// an error log.
auto trace = g_core->platform->GetNativeStackTrace();
BA_LOG_ERROR_NATIVE_TRACE_ONCE(
"UI::PushUIOperationRunnable() called outside of UI operation.");
g_base->logic->event_loop()->PushRunnable(runnable);
}
auto UI::InUIOperation() -> bool {
assert(g_base->InLogicThread());
return operation_context_ != nullptr;
}
} // namespace ballistica::base

View File

@ -77,13 +77,22 @@ class UI {
/// Draw dev UI on top.
void DrawDev(FrameDef* frame_def);
/// Add a runnable to be run as part of the currently-being-processed UI
/// operation. Pass a Runnable that has been allocated with
/// NewUnmanaged(). It will be owned and disposed of by the UI from this
/// point. Must be called from the logic thread.
void PushUIOperationRunnable(Runnable* runnable);
auto InUIOperation() -> bool;
/// Return the widget an input-device should send commands to, if any.
/// Potentially assigns UI control to the provide device, so only call
/// this if you intend on actually sending a message to that widget.
auto GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget*;
/// Send a message to the active widget.
auto SendWidgetMessage(const WidgetMessage& msg) -> int;
/// Send a message to the active widget. This is a high level call that
/// should only be used by top level event handling/etc.
auto SendWidgetMessage(const WidgetMessage& msg) -> bool;
/// Set the device controlling the UI.
void SetUIInputDevice(InputDevice* input_device);
@ -123,12 +132,28 @@ class UI {
auto* delegate() const { return delegate_; }
class OperationContext {
public:
OperationContext();
~OperationContext();
/// Should be called before returning from the high level event handling
/// call.
void Finish();
void AddRunnable(Runnable* runnable);
private:
bool ran_finish_{};
OperationContext* parent_{};
std::vector<Runnable*> runnables_;
};
private:
void MainMenuPress_(InputDevice* device);
auto DevConsoleButtonSize_() const -> float;
auto InDevConsoleButton_(float x, float y) const -> bool;
void DrawDevConsoleButton_(FrameDef* frame_def);
OperationContext* operation_context_{};
base::UIDelegateInterface* delegate_{};
DevConsole* dev_console_{};
std::string dev_console_startup_messages_;

View File

@ -323,14 +323,14 @@ auto CorePlatformApple::HaveLeaderboard(const std::string& game,
#endif
}
void CorePlatformApple::ShowOnlineScoreUI(const std::string& show,
void CorePlatformApple::ShowGameServiceUI(const std::string& show,
const std::string& game,
const std::string& game_version) {
#if BA_USE_GAME_CENTER
BallisticaKit::GameCenterContext::showOnlineScoreUI(show, game, game_version);
// base::AppleUtils::ShowOnlineScoreUI(show, game, game_version);
BallisticaKit::GameCenterContext::showGameServiceUI(show, game, game_version);
// base::AppleUtils::ShowGameServiceUI(show, game, game_version);
#else
CorePlatform::ShowOnlineScoreUI(show, game, game_version);
CorePlatform::ShowGameServiceUI(show, game, game_version);
#endif
}

View File

@ -41,7 +41,7 @@ class CorePlatformApple : public CorePlatform {
void ReportAchievement(const std::string& achievement) override;
auto HaveLeaderboard(const std::string& game, const std::string& config)
-> bool override;
void ShowOnlineScoreUI(const std::string& show, const std::string& game,
void ShowGameServiceUI(const std::string& show, const std::string& game,
const std::string& game_version) override;
void ResetAchievements() override;
auto IsOSPlayingMusic() -> bool override;

View File

@ -9,10 +9,15 @@
// Trying to avoid platform-specific headers here except for
// a few mostly-cross-platform bits where its worth the mess.
#if !BA_OSTYPE_WINDOWS
#if BA_ENABLE_EXECINFO_BACKTRACES
#if BA_OSTYPE_ANDROID
#include "ballistica/core/platform/android/execinfo.h"
#else
#include <execinfo.h>
#endif
#endif // BA_OSTYPE_ANDROID
#endif // BA_ENABLE_EXECINFO_BACKTRACES
#if !BA_OSTYPE_WINDOWS
#include <cxxabi.h>
#include <unistd.h>
#endif
@ -20,6 +25,7 @@
#include "ballistica/core/platform/support/min_sdl.h"
#include "ballistica/core/support/base_soft.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/shared/generic/utils.h"
#include "ballistica/shared/networking/networking_sys.h"
#include "ballistica/shared/python/python.h"
@ -689,10 +695,10 @@ auto CorePlatform::HaveLeaderboard(const std::string& game,
return false;
}
void CorePlatform::ShowOnlineScoreUI(const std::string& show,
void CorePlatform::ShowGameServiceUI(const std::string& show,
const std::string& game,
const std::string& game_version) {
Log(LogLevel::kError, "FIXME: ShowOnlineScoreUI() unimplemented");
Log(LogLevel::kError, "FIXME: ShowGameServiceUI() unimplemented");
}
void CorePlatform::AndroidSetResString(const std::string& res) {
@ -952,7 +958,7 @@ auto CorePlatform::GetSubplatformName() -> std::string {
#if BA_ENABLE_EXECINFO_BACKTRACES
// Stack traces using the functionality in execinfo.h
class PlatformStackTraceExecInfo : public PlatformStackTrace {
class NativeStackTraceExecInfo : public NativeStackTrace {
public:
static constexpr int kMaxStackLevels = 64;
@ -960,14 +966,23 @@ class PlatformStackTraceExecInfo : public PlatformStackTrace {
// construction but should do the bare minimum amount of work to store it. Any
// expensive operations such as symbolification should be deferred until
// FormatForDisplay().
PlatformStackTraceExecInfo() { nsize_ = backtrace(array_, kMaxStackLevels); }
NativeStackTraceExecInfo() { nsize_ = backtrace(array_, kMaxStackLevels); }
auto FormatForDisplay() noexcept -> std::string override {
try {
std::string s;
char** symbols = backtrace_symbols(array_, nsize_);
for (int i = 0; i < nsize_; i++) {
s += std::string(symbols[i]);
const char* symbol = symbols[i];
// Special case for Android: there's usually a horrific mess of a
// pathname leading up to libmain.so, which we should never really
// care about, so let's strip that out if possible.
if (g_buildconfig.ostype_android()) {
if (const char* s2 = strstr(symbol, "/libmain.so")) {
symbol = s2 + 1;
}
}
s += std::string(symbol);
if (i < nsize_ - 1) {
s += "\n";
}
@ -979,9 +994,9 @@ class PlatformStackTraceExecInfo : public PlatformStackTrace {
}
}
auto Copy() const noexcept -> PlatformStackTrace* override {
auto Copy() const noexcept -> NativeStackTrace* override {
try {
auto s = new PlatformStackTraceExecInfo(*this);
auto s = new NativeStackTraceExecInfo(*this);
// Vanilla copy constructor should do the right thing here.
assert(s->nsize_ == nsize_
@ -999,11 +1014,11 @@ class PlatformStackTraceExecInfo : public PlatformStackTrace {
};
#endif
auto CorePlatform::GetStackTrace() -> PlatformStackTrace* {
auto CorePlatform::GetNativeStackTrace() -> NativeStackTrace* {
// Our default handler here supports execinfo backtraces where available
// and gives nothing elsewhere.
#if BA_ENABLE_EXECINFO_BACKTRACES
return new PlatformStackTraceExecInfo();
return new NativeStackTraceExecInfo();
#else
return nullptr;
#endif

View File

@ -281,7 +281,7 @@ class CorePlatform {
virtual auto HaveLeaderboard(const std::string& game,
const std::string& config) -> bool;
virtual void ShowOnlineScoreUI(const std::string& show,
virtual void ShowGameServiceUI(const std::string& show,
const std::string& game,
const std::string& game_version);
virtual void ResetAchievements();
@ -294,11 +294,11 @@ class CorePlatform {
#pragma mark ERRORS & DEBUGGING ------------------------------------------------
/// Should return a subclass of PlatformStackTrace allocated via new. It
/// Should return a subclass of NativeStackTrace allocated via new. It
/// is up to the caller to call delete on the returned trace when done
/// with it. Platforms with no meaningful stack trace functionality can
/// return nullptr.
virtual auto GetStackTrace() -> PlatformStackTrace*;
virtual auto GetNativeStackTrace() -> NativeStackTrace*;
/// Optionally override fatal error reporting. If true is returned, default
/// fatal error reporting will not run.
@ -472,25 +472,6 @@ class CorePlatform {
std::list<std::string> mac_music_app_playlists_;
};
/// For capturing and printing stack-traces and related errors. Platforms
/// should subclass this and return instances in GetStackTrace(). Stack
/// trace classes should capture the stack state immediately upon
/// construction but should do the bare minimum amount of work to store it.
/// Any expensive operations such as symbolification should be deferred
/// until FormatForDisplay().
class PlatformStackTrace {
public:
virtual ~PlatformStackTrace() = default;
// Return a human readable version of the trace (with symbolification if
// available).
virtual auto FormatForDisplay() noexcept -> std::string = 0;
// Should return a copy of itself allocated via new() (or nullptr if not
// possible).
virtual auto Copy() const noexcept -> PlatformStackTrace* = 0;
};
} // namespace ballistica::core
#endif // BALLISTICA_CORE_PLATFORM_CORE_PLATFORM_H_

View File

@ -40,6 +40,7 @@
#endif
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/shared/generic/utils.h"
#include "ballistica/shared/networking/networking_sys.h"
@ -52,7 +53,7 @@ namespace ballistica::core {
static const int kTraceMaxStackFrames{256};
static const int kTraceMaxFunctionNameLength{1024};
class WinStackTrace : public PlatformStackTrace {
class WinStackTrace : public NativeStackTrace {
public:
explicit WinStackTrace(CorePlatformWindows* platform) : platform_{platform} {
number_of_frames_ =
@ -67,7 +68,7 @@ class WinStackTrace : public PlatformStackTrace {
// Should return a copy of itself allocated via new() (or nullptr if not
// possible).
auto Copy() const noexcept -> PlatformStackTrace* override {
auto Copy() const noexcept -> NativeStackTrace* override {
try {
auto s = new WinStackTrace(*this);
@ -169,7 +170,7 @@ auto CorePlatformWindows::FormatWinStackTraceForDisplay(
}
}
auto CorePlatformWindows::GetStackTrace() -> PlatformStackTrace* {
auto CorePlatformWindows::GetNativeStackTrace() -> NativeStackTrace* {
return new WinStackTrace(this);
}

View File

@ -21,7 +21,7 @@ class CorePlatformWindows : public CorePlatform {
static auto UTF8Encode(const std::wstring& wstr) -> std::string;
static auto UTF8Decode(const std::string& str) -> std::wstring;
auto GetStackTrace() -> PlatformStackTrace* override;
auto GetNativeStackTrace() -> NativeStackTrace* override;
auto GetDeviceV1AccountUUIDPrefix() -> std::string override { return "w"; }
auto GetDeviceUUIDInputs() -> std::list<std::string> override;
auto GenerateUUID() -> std::string override;

View File

@ -287,7 +287,7 @@ void HostSession::RemovePlayer(Player* player) {
return;
}
}
BA_LOG_ERROR_TRACE("Player not found in HostSession::RemovePlayer()");
BA_LOG_ERROR_PYTHON_TRACE("Player not found in HostSession::RemovePlayer()");
}
void HostSession::IssuePlayerLeft(Player* player) {

View File

@ -39,8 +39,8 @@ auto main(int argc, char** argv) -> int {
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kEngineBuildNumber = 21707;
const char* kEngineVersion = "1.7.31";
const int kEngineBuildNumber = 21728;
const char* kEngineVersion = "1.7.32";
const int kEngineApiVersion = 8;
#if BA_MONOLITHIC_BUILD
@ -164,8 +164,9 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int {
// with reports from dev builds.
bool try_to_exit_cleanly = !(l_base && l_base->IsUnmodifiedBlessedBuild());
// If this returns true, it means the app is handling things (showing a
// fatal error dialog, etc.) and it's out of our hands.
// If this returns true, it means the platform/app-adapter is handling
// things (showing a fatal error dialog, etc.) and it's out of our
// hands.
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
// If it's not been handled, take the app down ourself.
@ -229,8 +230,9 @@ class IncrementalInitRunner_ {
// with reports from dev builds.
bool try_to_exit_cleanly = !(base_ && base_->IsUnmodifiedBlessedBuild());
// If this returns true, it means the app is handling things (showing a
// fatal error dialog, etc.) and it's out of our hands.
// If this returns true, it means the platform/app-adapter is handling
// things (showing a fatal error dialog, etc.) and it's out of our
// hands.
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
// If it's not been handled, take the app down ourself.

View File

@ -396,6 +396,7 @@ auto EventLoop::ThreadMain_() -> int {
// report logs with reports from dev builds.
bool try_to_exit_cleanly =
!(g_base_soft && g_base_soft->IsUnmodifiedBlessedBuild());
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
// Do the default thing if platform didn't handle it.

View File

@ -52,9 +52,9 @@ class EventLoop {
Timer* GetTimer(int id);
void DeleteTimer(int id);
/// Add a runnable to this thread's event-loop.
/// Pass a Runnable that has been allocated with NewUnmanaged().
/// It will be owned and disposed of by the thread.
/// Add a runnable to this thread's event-loop. Pass a Runnable that has
/// been allocated with NewUnmanaged(). It will be owned and disposed of
/// by the thread.
void PushRunnable(Runnable* runnable);
/// Convenience function to push a lambda as a runnable.
@ -63,7 +63,8 @@ class EventLoop {
PushRunnable(NewLambdaRunnableUnmanaged(lambda));
}
/// Add a runnable to this thread's event-loop and wait until it completes.
/// Add a runnable to this thread's event-loop and wait until it
/// completes.
void PushRunnableSynchronous(Runnable* runnable);
/// Convenience function to push a lambda as a runnable.

View File

@ -4,6 +4,7 @@
#include "ballistica/core/core.h"
#include "ballistica/core/platform/core_platform.h"
#include "ballistica/shared/generic/native_stack_trace.h"
namespace ballistica {
@ -21,7 +22,7 @@ Exception::Exception(std::string message_in, PyExcType python_type)
// If core has been inited, attempt to capture a stack-trace here we
// can print out later if desired.
if (core::g_core) {
stack_trace_ = core::g_core->platform->GetStackTrace();
stack_trace_ = core::g_core->platform->GetNativeStackTrace();
}
}
@ -31,7 +32,7 @@ Exception::Exception(PyExcType python_type) : python_type_(python_type) {
// If core has been inited, attempt to capture a stack-trace here we
// can print out later if desired.
if (core::g_core) {
stack_trace_ = core::g_core->platform->GetStackTrace();
stack_trace_ = core::g_core->platform->GetNativeStackTrace();
}
}

View File

@ -36,10 +36,6 @@ namespace ballistica {
// std::runtime_error at a later time and also catches exceptions coming from
// std itself.
namespace core {
class PlatformStackTrace;
}
/// Get a short description for an exception.
/// By default, our Exception classes provide what() values that may include
/// backtraces of the throw location or other extended info that can be useful
@ -77,7 +73,7 @@ class Exception : public std::exception {
std::string message_;
std::string full_description_;
PyExcType python_type_;
core::PlatformStackTrace* stack_trace_{};
NativeStackTrace* stack_trace_{};
};
} // namespace ballistica

View File

@ -6,6 +6,7 @@
#include "ballistica/core/support/base_soft.h"
#include "ballistica/shared/foundation/logging.h"
#include "ballistica/shared/generic/lambda_runnable.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/shared/python/python.h"
namespace ballistica {
@ -87,7 +88,7 @@ void FatalError::ReportFatalError(const std::string& message,
// since we know where those are anyway.
if (!in_top_level_exception_handler) {
if (g_core && g_core->platform) {
core::PlatformStackTrace* trace{g_core->platform->GetStackTrace()};
NativeStackTrace* trace{g_core->platform->GetNativeStackTrace()};
if (trace) {
std::string tracestr = trace->FormatForDisplay();
if (!tracestr.empty()) {

View File

@ -6,6 +6,7 @@
#include "ballistica/core/core.h"
#include "ballistica/core/platform/core_platform.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/shared/python/python.h"
// Snippets of compiled functionality used by our evil macros.
@ -35,7 +36,6 @@ void MacroFunctionTimerEndThread(core::CoreFeatureSet* corefs,
if (g_buildconfig.test_build()) {
return;
}
// EW: Using core's internal globals directly; shouldn't do this.
assert(corefs);
millisecs_t endtime = corefs->platform->GetTicks();
if (endtime - starttime > time) {
@ -53,7 +53,6 @@ void MacroFunctionTimerEndEx(core::CoreFeatureSet* corefs,
if (g_buildconfig.test_build()) {
return;
}
// EW: Using core's internal globals directly; shouldn't do this.
assert(corefs);
millisecs_t endtime = corefs->platform->GetTicks();
if (endtime - starttime > time) {
@ -72,7 +71,6 @@ void MacroFunctionTimerEndThreadEx(core::CoreFeatureSet* corefs,
if (g_buildconfig.test_build()) {
return;
}
// EW: Using core's internal globals directly; shouldn't do this.
assert(corefs);
millisecs_t endtime = corefs->platform->GetTicks();
if (endtime - starttime > time) {
@ -91,7 +89,6 @@ void MacroTimeCheckEnd(core::CoreFeatureSet* corefs, millisecs_t starttime,
if (g_buildconfig.test_build()) {
return;
}
// EW: Using core's internal globals directly; shouldn't do this.
assert(corefs);
millisecs_t e = corefs->platform->GetTicks();
if (e - starttime > time) {
@ -102,12 +99,28 @@ void MacroTimeCheckEnd(core::CoreFeatureSet* corefs, millisecs_t starttime,
}
}
void MacroLogErrorTrace(core::CoreFeatureSet* corefs, const std::string& msg,
const char* fname, int line) {
void MacroLogErrorNativeTrace(core::CoreFeatureSet* corefs,
const std::string& msg, const char* fname,
int line) {
char buffer[2048];
snprintf(buffer, sizeof(buffer), "%s:%d:", MacroPathFilter(corefs, fname),
line);
buffer[sizeof(buffer) - 1] = 0;
auto trace = corefs->platform->GetNativeStackTrace();
auto trace_s =
trace ? trace->FormatForDisplay() : "<native stack trace unavailable>";
Log(LogLevel::kError,
std::string(buffer) + " error: " + msg + "\n" + trace_s);
}
void MacroLogErrorPythonTrace(core::CoreFeatureSet* corefs,
const std::string& msg, const char* fname,
int line) {
char buffer[2048];
snprintf(buffer, sizeof(buffer), "%s:%d:", MacroPathFilter(corefs, fname),
line);
// FIXME: Should have the trace be part of the log; not a separate print.
// Since our logging goes through Python anyway, we should just ask
// Python do include the trace in our log call.
Python::PrintStackTrace();
Log(LogLevel::kError, std::string(buffer) + " error: " + msg);
}
@ -117,7 +130,6 @@ void MacroLogError(core::CoreFeatureSet* corefs, const std::string& msg,
char e_buffer[2048];
snprintf(e_buffer, sizeof(e_buffer), "%s:%d:", MacroPathFilter(corefs, fname),
line);
e_buffer[sizeof(e_buffer) - 1] = 0;
Log(LogLevel::kError, std::string(e_buffer) + " error: " + msg);
}
@ -128,7 +140,8 @@ void MacroLogPythonTrace(core::CoreFeatureSet* corefs, const std::string& msg) {
auto MacroPathFilter(core::CoreFeatureSet* corefs, const char* filename)
-> const char* {
// If we've got a build_src_dir set and filename starts with it, skip past it.
// If we've got a build_src_dir set and filename starts with it, skip past
// it.
assert(corefs);
if (corefs && !corefs->build_src_dir().empty()
&& strstr(filename, core::g_core->build_src_dir().c_str()) == filename) {

View File

@ -75,19 +75,34 @@
type(const type& foo) = delete; \
type& operator=(const type& src) = delete; /* NOLINT (macro parens) */
// Call this for errors which are non-fatal but should be noted so they can be
// fixed.
#define BA_LOG_ERROR_TRACE(msg) \
::ballistica::MacroLogErrorTrace(g_core, msg, __FILE__, __LINE__)
// Call this for errors which are non-fatal but should be noted so they can
// be fixed.
#define BA_LOG_ERROR_NATIVE_TRACE(msg) \
::ballistica::MacroLogErrorNativeTrace(g_core, msg, __FILE__, __LINE__)
#define BA_LOG_ERROR_TRACE_ONCE(msg) \
{ \
static bool did_log_error_trace_here = false; \
if (!did_log_error_trace_here) { \
::ballistica::MacroLogErrorTrace(g_core, msg, __FILE__, __LINE__); \
did_log_error_trace_here = true; \
} \
} \
#define BA_LOG_ERROR_NATIVE_TRACE_ONCE(msg) \
{ \
static bool did_log_error_trace_here = false; \
if (!did_log_error_trace_here) { \
::ballistica::MacroLogErrorNativeTrace(g_core, msg, __FILE__, __LINE__); \
did_log_error_trace_here = true; \
} \
} \
((void)0) // (see 'Trailing-semicolon note' at top)
// Call this for errors which are non-fatal but should be noted so they can
// be fixed.
#define BA_LOG_ERROR_PYTHON_TRACE(msg) \
::ballistica::MacroLogErrorPythonTrace(g_core, msg, __FILE__, __LINE__)
#define BA_LOG_ERROR_PYTHON_TRACE_ONCE(msg) \
{ \
static bool did_log_error_trace_here = false; \
if (!did_log_error_trace_here) { \
::ballistica::MacroLogErrorPythonTrace(g_core, msg, __FILE__, __LINE__); \
did_log_error_trace_here = true; \
} \
} \
((void)0) // (see 'Trailing-semicolon note' at top)
#define BA_LOG_ONCE(lvl, msg) \
@ -175,8 +190,12 @@ void MacroFunctionTimerEndThreadEx(core::CoreFeatureSet* corefs,
void MacroTimeCheckEnd(core::CoreFeatureSet* corefs, millisecs_t starttime,
millisecs_t time, const char* name, const char* file,
int line);
void MacroLogErrorTrace(core::CoreFeatureSet* corefs, const std::string& msg,
const char* fname, int line);
void MacroLogErrorNativeTrace(core::CoreFeatureSet* corefs,
const std::string& msg, const char* fname,
int line);
void MacroLogErrorPythonTrace(core::CoreFeatureSet* corefs,
const std::string& msg, const char* fname,
int line);
void MacroLogError(core::CoreFeatureSet* corefs, const std::string& msg,
const char* fname, int line);
void MacroLogPythonTrace(core::CoreFeatureSet* corefs, const std::string& msg);

View File

@ -42,6 +42,7 @@ class EventLoop;
class FeatureSetNativeComponent;
class JsonDict;
class Matrix44f;
class NativeStackTrace;
class Object;
class Python;
class PythonRef;

View File

@ -0,0 +1,31 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_SHARED_GENERIC_NATIVE_STACK_TRACE_H_
#define BALLISTICA_SHARED_GENERIC_NATIVE_STACK_TRACE_H_
#include <string>
namespace ballistica {
/// For capturing and printing stack-traces and related errors. Platforms
/// should subclass this and return instances in GetNativeStackTrace(). Stack
/// trace classes should capture the stack state immediately upon
/// construction but should do the bare minimum amount of work to store it.
/// Any expensive operations such as symbolification should be deferred
/// until FormatForDisplay().
class NativeStackTrace {
public:
virtual ~NativeStackTrace() = default;
// Return a human readable version of the trace (with symbolification if
// available).
virtual auto FormatForDisplay() noexcept -> std::string = 0;
// Should return a copy of itself allocated via new() (or nullptr if not
// possible).
virtual auto Copy() const noexcept -> NativeStackTrace* = 0;
};
} // namespace ballistica
#endif // BALLISTICA_SHARED_GENERIC_NATIVE_STACK_TRACE_H_

View File

@ -40,7 +40,7 @@ void PythonClassWidget::SetupType(PyTypeObject* cls) {
"\n"
"This class represents a weak reference to a widget object\n"
"in the internal C++ layer. Currently, functions such as\n"
"babase.buttonwidget() must be used to instantiate or edit these.\n"
"bauiv1.buttonwidget() must be used to instantiate or edit these.\n"
"Attributes:\n"
" " ATTR_TRANSITIONING_OUT " (bool):\n"
" Whether this widget is in the process of dying (read only).\n"
@ -72,7 +72,9 @@ auto PythonClassWidget::Create(Widget* widget) -> PyObject* {
assert(TypeIsSetUp(&type_obj));
auto* py_widget = reinterpret_cast<PythonClassWidget*>(
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
if (!py_widget) throw Exception("babase.Widget creation failed");
if (!py_widget) {
throw Exception("bauiv1.Widget creation failed");
}
*py_widget->widget_ = widget;
@ -285,6 +287,10 @@ auto PythonClassWidget::Delete(PythonClassWidget* self, PyObject* args,
args, keywds, "|i", const_cast<char**>(kwlist), &ignore_missing)) {
return nullptr;
}
// Defer any user code triggered by selects/etc until the end.
base::UI::OperationContext ui_op_context;
Widget* w = self->widget_->Get();
if (!w) {
if (!ignore_missing) {
@ -298,6 +304,9 @@ auto PythonClassWidget::Delete(PythonClassWidget* self, PyObject* args,
Log(LogLevel::kError, "Can't delete widget: no parent.");
}
}
// Run any user code that got triggered.
ui_op_context.Finish();
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}

View File

@ -87,35 +87,6 @@ static PyMethodDef PyGetTextureDef = {
"Load a texture for use in the ui.",
};
// ------------------------------- getmesh -------------------------------------
static auto PyGetMesh(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
const char* name;
static const char* kwlist[] = {"name", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
const_cast<char**>(kwlist), &name)) {
return nullptr;
}
{
base::Assets::AssetListLock lock;
return PythonClassUIMesh::Create(g_base->assets->GetMesh(name));
}
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyGetMeshDef = {
"getmesh", // name
(PyCFunction)PyGetMesh, // method
METH_VARARGS | METH_KEYWORDS, // flags
"getmesh(name: str) -> bauiv1.Mesh\n"
"\n"
"Load a mesh for use solely in the local user interface.",
};
// -------------------------- get_qrcode_texture -------------------------------
static auto PyGetQRCodeTexture(PyObject* self, PyObject* args, PyObject* keywds)
@ -146,6 +117,35 @@ static PyMethodDef PyGetQRCodeTextureDef = {
"The provided url must be 64 bytes or less.",
};
// ------------------------------- getmesh -------------------------------------
static auto PyGetMesh(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
const char* name;
static const char* kwlist[] = {"name", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
const_cast<char**>(kwlist), &name)) {
return nullptr;
}
{
base::Assets::AssetListLock lock;
return PythonClassUIMesh::Create(g_base->assets->GetMesh(name));
}
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyGetMeshDef = {
"getmesh", // name
(PyCFunction)PyGetMesh, // method
METH_VARARGS | METH_KEYWORDS, // flags
"getmesh(name: str) -> bauiv1.Mesh\n"
"\n"
"Load a mesh for use solely in the local user interface.",
};
// ----------------------------- buttonwidget ----------------------------------
static auto PyButtonWidget(PyObject* self, PyObject* args, PyObject* keywds)
@ -250,6 +250,10 @@ static auto PyButtonWidget(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("UI functions must be called with no context set.");
}
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
// Grab the edited widget or create a new one.
Object::Ref<ButtonWidget> b;
if (edit_obj != Py_None) {
@ -444,6 +448,9 @@ static auto PyButtonWidget(PyObject* self, PyObject* args, PyObject* keywds)
g_ui_v1->AddWidget(b.Get(), parent_widget);
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
return b->NewPyRef();
BA_PYTHON_CATCH;
@ -552,6 +559,10 @@ static auto PyCheckBoxWidget(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("UI functions must be called with no context set.");
}
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
// Grab the edited widget or create a new one.
Object::Ref<CheckBoxWidget> widget;
if (edit_obj != Py_None) {
@ -633,6 +644,9 @@ static auto PyCheckBoxWidget(PyObject* self, PyObject* args, PyObject* keywds)
g_ui_v1->AddWidget(widget.Get(), parent_widget);
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
return widget->NewPyRef();
BA_PYTHON_CATCH;
@ -725,6 +739,10 @@ static auto PyImageWidget(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("UI functions must be called with no context set.");
}
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
// Grab the edited widget or create a new one.
Object::Ref<ImageWidget> b;
if (edit_obj != Py_None) {
@ -821,6 +839,9 @@ static auto PyImageWidget(PyObject* self, PyObject* args, PyObject* keywds)
g_ui_v1->AddWidget(b.Get(), parent_widget);
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
return b->NewPyRef();
BA_PYTHON_CATCH;
}
@ -914,6 +935,10 @@ static auto PyColumnWidget(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("UI functions must be called with no context set.");
}
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
// Grab the edited widget or create a new one.
Object::Ref<ColumnWidget> widget;
if (edit_obj != Py_None) {
@ -970,7 +995,13 @@ static auto PyColumnWidget(PyObject* self, PyObject* args, PyObject* keywds)
widget->set_background(Python::GetPyBool(background_obj));
}
if (selected_child_obj != Py_None) {
// Need to wrap this in an operation because it can trigger user code.
base::UI::OperationContext operation_context;
widget->SelectWidget(UIV1Python::GetPyWidget(selected_child_obj));
// Run any user code/etc.
operation_context.Finish();
}
if (visible_child_obj != Py_None) {
widget->ShowWidget(UIV1Python::GetPyWidget(visible_child_obj));
@ -991,6 +1022,9 @@ static auto PyColumnWidget(PyObject* self, PyObject* args, PyObject* keywds)
g_ui_v1->AddWidget(widget.Get(), parent_widget);
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
return widget->NewPyRef();
BA_PYTHON_CATCH;
@ -1120,6 +1154,9 @@ static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("UI functions must be called with no context set.");
}
// Defer any user code triggered by selects/etc until the end.
base::UI::OperationContext ui_op_context;
// Grab the edited widget or create a new one.
Object::Ref<ContainerWidget> widget;
if (edit_obj != Py_None) {
@ -1207,7 +1244,7 @@ static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds)
widget->SetRootSelectable(Python::GetPyBool(root_selectable_obj));
}
if (selected_child_obj != Py_None) {
// special case: passing 0 implies deselect
// Special case: passing 0 implies deselect.
if (PyLong_Check(selected_child_obj)
&& (PyLong_AsLong(selected_child_obj) == 0)) {
widget->SelectWidget(nullptr);
@ -1218,18 +1255,19 @@ static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds)
if (transition_obj != Py_None) {
std::string t = Python::GetPyString(transition_obj);
if (t == "in_left")
if (t == "in_left") {
widget->SetTransition(ContainerWidget::TransitionType::kInLeft);
else if (t == "in_right")
} else if (t == "in_right") {
widget->SetTransition(ContainerWidget::TransitionType::kInRight);
else if (t == "out_left")
} else if (t == "out_left") {
widget->SetTransition(ContainerWidget::TransitionType::kOutLeft);
else if (t == "out_right")
} else if (t == "out_right") {
widget->SetTransition(ContainerWidget::TransitionType::kOutRight);
else if (t == "in_scale")
} else if (t == "in_scale") {
widget->SetTransition(ContainerWidget::TransitionType::kInScale);
else if (t == "out_scale")
} else if (t == "out_scale") {
widget->SetTransition(ContainerWidget::TransitionType::kOutScale);
}
}
if (cancel_button_obj != Py_None) {
@ -1301,6 +1339,10 @@ static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds)
widget->set_claims_outside_clicks(
Python::GetPyBool(claim_outside_clicks_obj));
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
return widget->NewPyRef();
BA_PYTHON_CATCH;
}
@ -1387,6 +1429,10 @@ static auto PyRowWidget(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("UI functions must be called with no context set.");
}
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
// Grab the edited widget or create a new one.
Object::Ref<RowWidget> widget;
if (edit_obj != Py_None) {
@ -1443,6 +1489,9 @@ static auto PyRowWidget(PyObject* self, PyObject* args, PyObject* keywds)
g_ui_v1->AddWidget(widget.Get(), parent_widget);
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
return widget->NewPyRef();
BA_PYTHON_CATCH;
@ -1532,6 +1581,10 @@ static auto PyScrollWidget(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("UI functions must be called with no context set.");
}
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
// Grab the edited widget or create a new one.
Object::Ref<ScrollWidget> widget;
if (edit_obj != Py_None) {
@ -1615,6 +1668,10 @@ static auto PyScrollWidget(PyObject* self, PyObject* args, PyObject* keywds)
if (edit_obj == Py_None) {
g_ui_v1->AddWidget(widget.Get(), parent_widget);
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
return widget->NewPyRef();
BA_PYTHON_CATCH;
@ -1710,6 +1767,10 @@ static auto PyHScrollWidget(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("UI functions must be called with no context set.");
}
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
// Grab the edited widget or create a new one.
Object::Ref<HScrollWidget> widget;
if (edit_obj != Py_None) {
@ -1788,6 +1849,10 @@ static auto PyHScrollWidget(PyObject* self, PyObject* args, PyObject* keywds)
if (edit_obj == Py_None) {
g_ui_v1->AddWidget(widget.Get(), parent_widget);
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
return widget->NewPyRef();
BA_PYTHON_CATCH;
@ -1931,6 +1996,8 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
// Grab the edited widget or create a new one.
Object::Ref<TextWidget> widget;
// Handle query special cases first.
if (query_obj != Py_None) {
widget = dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(query_obj));
if (!widget.Exists()) {
@ -1957,6 +2024,13 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
}
return PyUnicode_FromString(widget->description().c_str());
}
// Ok it's not a query; it's a create or edit.
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
if (edit_obj != Py_None) {
widget = dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(edit_obj));
if (!widget.Exists()) {
@ -2142,6 +2216,10 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
if (edit_obj == Py_None) {
g_ui_v1->AddWidget(widget.Get(), parent_widget);
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
return widget->NewPyRef();
BA_PYTHON_CATCH;
@ -2241,6 +2319,10 @@ static auto PyWidgetCall(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("UI functions must be called with no context set.");
}
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
Widget* widget = nullptr;
if (edit_obj != Py_None) {
widget = UIV1Python::GetPyWidget(edit_obj);
@ -2293,6 +2375,9 @@ static auto PyWidgetCall(PyObject* self, PyObject* args, PyObject* keywds)
widget->set_auto_select(Python::GetPyBool(autoselect_obj));
}
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
@ -2356,135 +2441,6 @@ static PyMethodDef PyUIBoundsDef = {
"center remains onscreen.",
};
// ------------------------ show_online_score_ui -------------------------------
static auto PyShowOnlineScoreUI(PyObject* self, PyObject* args,
PyObject* keywds) -> PyObject* {
BA_PYTHON_TRY;
const char* show = "general";
PyObject* game_obj = Py_None;
PyObject* game_version_obj = Py_None;
static const char* kwlist[] = {"show", "game", "game_version", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|sOO",
const_cast<char**>(kwlist), &show, &game_obj,
&game_version_obj)) {
return nullptr;
}
std::string game;
if (game_obj != Py_None) {
game = Python::GetPyString(game_obj);
}
std::string game_version;
if (game_version_obj != Py_None) {
game_version = Python::GetPyString(game_version_obj);
}
g_base->app_adapter->PushMainThreadCall([show, game, game_version] {
assert(g_core->InMainThread());
g_core->platform->ShowOnlineScoreUI(show, game, game_version);
});
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyShowOnlineScoreUIDef = {
"show_online_score_ui", // name
(PyCFunction)PyShowOnlineScoreUI, // method
METH_VARARGS | METH_KEYWORDS, // flags
"show_online_score_ui(show: str = 'general', game: str | None = None,\n"
" game_version: str | None = None) -> None\n"
"\n"
"(internal)",
};
// -------------------------------- show_ad ------------------------------------
static auto PyShowAd(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread());
const char* purpose;
PyObject* on_completion_call_obj = Py_None;
int pass_actually_showed = false;
static const char* kwlist[] = {"purpose", "on_completion_call", nullptr};
if (!PyArg_ParseTupleAndKeywords(
args, keywds, "s|O", const_cast<char**>(kwlist), &purpose,
&on_completion_call_obj, &pass_actually_showed)) {
return nullptr;
}
g_base->plus()->SetAdCompletionCall(on_completion_call_obj,
static_cast<bool>(pass_actually_showed));
// In cases where we support ads, store our callback and kick one off.
// We'll then fire our callback once its done. If we *don't* support ads,
// just store our callback and then kick off an ad-view-complete message
// ourself so the event flow is similar..
if (g_core->platform->GetHasAds()) {
g_core->platform->ShowAd(purpose);
} else {
g_base->plus()->PushAdViewComplete(purpose, false);
}
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyShowAdDef = {
"show_ad", // name
(PyCFunction)PyShowAd, // method
METH_VARARGS | METH_KEYWORDS, // flags
"show_ad(purpose: str,\n"
" on_completion_call: Callable[[], None] | None = None)\n"
" -> None\n"
"\n"
"(internal)",
};
// ------------------------------ show_ad_2 ------------------------------------
// (same as PyShowAd but passes actually_showed arg in callback)
static auto PyShowAd2(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* {
BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread());
const char* purpose;
PyObject* on_completion_call_obj = Py_None;
int pass_actually_showed = true;
static const char* kwlist[] = {"purpose", "on_completion_call", nullptr};
if (!PyArg_ParseTupleAndKeywords(
args, keywds, "s|O", const_cast<char**>(kwlist), &purpose,
&on_completion_call_obj, &pass_actually_showed)) {
return nullptr;
}
g_base->plus()->SetAdCompletionCall(on_completion_call_obj,
static_cast<bool>(pass_actually_showed));
// In cases where we support ads, store our callback and kick one off.
// We'll then fire our callback once its done.
// If we *don't* support ads, just store our callback and then kick off
// an ad-view-complete message ourself so the event flow is similar..
if (g_core->platform->GetHasAds()) {
g_core->platform->ShowAd(purpose);
} else {
g_base->plus()->PushAdViewComplete(purpose, false);
}
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyShowAd2Def = {
"show_ad_2", // name
(PyCFunction)PyShowAd2, // method
METH_VARARGS | METH_KEYWORDS, // flags
"show_ad_2(purpose: str,\n"
" on_completion_call: Callable[[bool], None] | None = None)\n"
" -> None\n"
"\n"
"(internal)",
};
// --------------------- set_party_icon_always_visible -------------------------
static auto PySetPartyIconAlwaysVisible(PyObject* self, PyObject* args,
@ -2658,74 +2614,6 @@ static PyMethodDef PyOpenURLDef = {
"is True).",
};
// ------------------------- open_file_externally ------------------------------
static auto PyOpenFileExternally(PyObject* self, PyObject* args,
PyObject* keywds) -> PyObject* {
BA_PYTHON_TRY;
char* path = nullptr;
static const char* kwlist[] = {"path", nullptr};
if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
const_cast<char**>(kwlist), &path)) {
return nullptr;
}
g_base->platform->OpenFileExternally(path);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyOpenFileExternallyDef = {
"open_file_externally", // name
(PyCFunction)PyOpenFileExternally, // method
METH_VARARGS | METH_KEYWORDS, // flags
"open_file_externally(path: str) -> None\n"
"\n"
"(internal)\n"
"\n"
"Open the provided file in the default external app.",
};
// ----------------------------- console_print ---------------------------------
static auto PyConsolePrint(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
if (!g_core->HeadlessMode()) {
Py_ssize_t tuple_size = PyTuple_GET_SIZE(args);
PyObject* obj;
for (Py_ssize_t i = 0; i < tuple_size; i++) {
obj = PyTuple_GET_ITEM(args, i);
PyObject* str_obj = PyObject_Str(obj);
if (!str_obj) {
PyErr_Clear(); // In case this is caught without setting the py exc.
throw Exception();
}
const char* c = PyUnicode_AsUTF8(str_obj);
g_base->PushDevConsolePrintCall(c);
Py_DECREF(str_obj);
}
}
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyConsolePrintDef = {
"console_print", // name
PyConsolePrint, // method
METH_VARARGS, // flags
"console_print(*args: Any) -> None\n"
"\n"
"(internal)\n"
"\n"
"Print the provided args to the game console (using str()).\n"
"For most debugging/info purposes you should just use Python's "
"standard\n"
"print, which will show up in the game console as well.",
};
// ------------------------ is_party_icon_visible ------------------------------
static auto PyIsPartyIconVisible(PyObject* self) -> PyObject* {
@ -2803,18 +2691,12 @@ static PyMethodDef PyIsAvailableDef = {
auto PythonMethodsUIV1::GetMethods() -> std::vector<PyMethodDef> {
return {
PyGetQRCodeTextureDef,
PyIsPartyIconVisibleDef,
PyConsolePrintDef,
PyOpenFileExternallyDef,
PyOpenURLDef,
PyBackPressDef,
PyGetSpecialWidgetDef,
PySetPartyWindowOpenDef,
PySetPartyIconAlwaysVisibleDef,
PyShowAdDef,
PyShowAd2Def,
PyShowOnlineScoreUIDef,
PyButtonWidgetDef,
PyCheckBoxWidgetDef,
PyImageWidgetDef,
@ -2828,6 +2710,7 @@ auto PythonMethodsUIV1::GetMethods() -> std::vector<PyMethodDef> {
PyUIBoundsDef,
PyGetSoundDef,
PyGetTextureDef,
PyGetQRCodeTextureDef,
PyGetMeshDef,
PyToolbarTestDef,
PyIsAvailableDef,

View File

@ -110,28 +110,23 @@ void UIV1Python::InvokeStringEditor(PyObject* string_edit_adapter_instance) {
base::ScopedSetContext ssc(nullptr);
g_base->audio->PlaySound(g_base->assets->SysSound(base::SysSoundID::kSwish));
// Schedule this in the next cycle to be safe.
PythonRef args(Py_BuildValue("(O)", string_edit_adapter_instance),
PythonRef::kSteal);
Object::New<base::PythonContextCall>(
objs().Get(ObjID::kOnScreenKeyboardClass))
->Schedule(args);
}
void UIV1Python::LaunchStringEditOld(TextWidget* w) {
assert(g_base->InLogicThread());
BA_PRECONDITION(w);
auto context_call = Object::New<base::PythonContextCall>(
objs().Get(ObjID::kOnScreenKeyboardClass));
base::ScopedSetContext ssc(nullptr);
g_base->audio->PlaySound(g_base->assets->SysSound(base::SysSoundID::kSwish));
// Gotta run this in the next cycle.
PythonRef args(Py_BuildValue("(Osi)", w->BorrowPyRef(),
w->description().c_str(), w->max_chars()),
PythonRef::kSteal);
Object::New<base::PythonContextCall>(
objs().Get(ObjID::kOnScreenKeyboardClass))
->Schedule(args);
// This is probably getting called from within UI handling, so we
// need to schedule things to run post-ui-traversal in that case.
if (g_base->ui->InUIOperation()) {
context_call->ScheduleInUIOperation(args);
} else {
// Otherwise just run immediately.
Log(LogLevel::kWarning,
"UIV1Python::InvokeStringEditor running outside of UIInteraction; "
"unexpected.");
context_call->Run(args);
}
}
void UIV1Python::InvokeQuitWindow(QuitType quit_type) {

View File

@ -17,7 +17,6 @@ class UIV1Python {
void AddPythonClasses(PyObject* module);
void ImportPythonObjs();
void LaunchStringEditOld(TextWidget* w);
void InvokeStringEditor(PyObject* string_edit_adapter_instance);
void HandleDeviceMenuPress(base::InputDevice* device);
void ShowURL(const std::string& url);

View File

@ -6,6 +6,7 @@
#include "ballistica/base/graphics/component/empty_component.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/ui_v1/python/ui_v1_python.h"
#include "ballistica/ui_v1/support/root_ui.h"
#include "ballistica/ui_v1/widget/root_widget.h"
@ -71,10 +72,6 @@ void UIV1FeatureSet::DoHandleDeviceMenuPress(base::InputDevice* device) {
void UIV1FeatureSet::DoShowURL(const std::string& url) { python->ShowURL(url); }
// void UIV1FeatureSet::DoQuitWindow() {
// g_ui_v1->python->objs().Get(UIV1Python::ObjID::kQuitWindowCall).Call();
// }
bool UIV1FeatureSet::MainMenuVisible() {
// We consider anything on our screen or overlay stacks to be a 'main menu'.
// Probably need a better name than 'main menu' though.
@ -296,7 +293,7 @@ UIV1FeatureSet::UILock::UILock(bool write) {
assert(g_base->InLogicThread());
if (write && g_ui_v1->ui_lock_count_ != 0) {
BA_LOG_ERROR_TRACE_ONCE("Illegal operation: UI is locked");
BA_LOG_ERROR_NATIVE_TRACE_ONCE("Illegal operation: UI is locked.");
}
g_ui_v1->ui_lock_count_++;
}
@ -304,7 +301,7 @@ UIV1FeatureSet::UILock::UILock(bool write) {
UIV1FeatureSet::UILock::~UILock() {
g_ui_v1->ui_lock_count_--;
if (g_ui_v1->ui_lock_count_ < 0) {
BA_LOG_ERROR_TRACE_ONCE("ui_lock_count_ < 0");
BA_LOG_ERROR_NATIVE_TRACE_ONCE("ui_lock_count_ < 0");
g_ui_v1->ui_lock_count_ = 0;
}
}

View File

@ -54,10 +54,18 @@ void ButtonWidget::SetIcon(base::TextureAsset* val) { icon_ = val; }
void ButtonWidget::OnRepeatTimerExpired() {
// Repeat our action unless we somehow lost focus but didn't get a mouse-up.
if (IsHierarchySelected() && pressed_) {
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
DoActivate(true);
// Speed up repeats after the first.
repeat_timer_->SetLength(0.150);
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
} else {
repeat_timer_.Clear();
}
@ -560,9 +568,8 @@ void ButtonWidget::DoActivate(bool is_repeat) {
}
}
if (auto* call = on_activate_call_.Get()) {
// Call this in the next cycle (don't want to risk mucking with UI from
// within a UI loop.)
call->ScheduleWeak();
// Schedule this to run immediately after any current UI traversal.
call->ScheduleInUIOperation();
return;
}
}

View File

@ -48,9 +48,9 @@ class ButtonWidget : public Widget {
icon_color_alpha_ = a;
}
auto set_text_flatness(float f) { text_flatness_ = f; }
enum class Style { kRegular, kBack, kBackSmall, kTab, kSquare };
enum class Style : uint8_t { kRegular, kBack, kBackSmall, kTab, kSquare };
auto set_style(Style s) { style_ = s; }
enum class IconType { kNone, kCancel, kStart };
enum class IconType : uint8_t { kNone, kCancel, kStart };
void set_text(const std::string& text);
auto text() const -> std::string { return text_->text_raw(); }
auto set_icon_type(IconType i) { icon_type_ = i; }
@ -91,37 +91,32 @@ class ButtonWidget : public Widget {
bool color_set_ = false;
void DoActivate(bool is_repeat = false);
auto GetMult(millisecs_t current_time) const -> float;
IconType icon_type_ = IconType::kNone;
bool enabled_ = true;
bool selectable_ = true;
float icon_tint_ = 0.0f;
Style style_ = Style::kRegular;
bool sound_enabled_ = true;
bool mouse_over_ = false;
bool repeat_ = false;
bool pressed_ = false;
float extra_touch_border_scale_ = 1.0f;
float width_ = 50.0f;
float height_ = 30.0f;
float text_scale_ = 1.0f;
float text_width_ = 0.0f;
float color_red_ = 0.5f;
float color_green_ = 0.7f;
float color_blue_ = 0.2f;
float icon_color_red_ = 1.0f;
float icon_color_green_ = 1.0f;
float icon_color_blue_ = 1.0f;
float icon_color_alpha_ = 1.0f;
Object::Ref<base::TextureAsset> texture_;
Object::Ref<base::TextureAsset> icon_;
Object::Ref<base::TextureAsset> tint_texture_;
Object::Ref<base::TextureAsset> mask_texture_;
Object::Ref<base::MeshAsset> mesh_transparent_;
Object::Ref<base::MeshAsset> mesh_opaque_;
float icon_scale_{1.0f};
IconType icon_type_{};
Style style_{};
bool enabled_{true};
bool selectable_{true};
bool sound_enabled_{true};
bool mouse_over_{};
bool repeat_{};
bool pressed_{};
millisecs_t last_activate_time_millisecs_{};
millisecs_t birth_time_millisecs_{};
millisecs_t transition_delay_{};
float icon_tint_{};
float extra_touch_border_scale_{1.0f};
float width_{50.0f};
float height_{30.0f};
float text_scale_{1.0f};
float text_width_{0.0f};
float color_red_{0.5f};
float color_green_{0.7f};
float color_blue_{0.2f};
float icon_color_red_{1.0f};
float icon_color_green_{1.0f};
float icon_color_blue_{1.0f};
float icon_color_alpha_{1.0f};
float icon_scale_{1.0f};
float opacity_{1.0f};
float text_flatness_{0.5f};
float text_color_r_{0.75f};
@ -134,11 +129,17 @@ class ButtonWidget : public Widget {
float tint2_color_red_{1.0f};
float tint2_color_green_{1.0f};
float tint2_color_blue_{1.0f};
Object::Ref<base::TextureAsset> texture_;
Object::Ref<base::TextureAsset> icon_;
Object::Ref<base::TextureAsset> tint_texture_;
Object::Ref<base::TextureAsset> mask_texture_;
Object::Ref<base::MeshAsset> mesh_transparent_;
Object::Ref<base::MeshAsset> mesh_opaque_;
// Keep these at the bottom, so they're torn down first.
// Keep these at the bottom so they're torn down first (this was a problem
// at some point though I don't remember details).
Object::Ref<TextWidget> text_;
Object::Ref<base::PythonContextCall> on_activate_call_;
// Object::Ref<AppTimerOld<ButtonWidget> > repeat_timer_;
Object::Ref<base::AppTimer> repeat_timer_;
};

View File

@ -245,9 +245,8 @@ void CheckBoxWidget::Activate() {
PythonRef args(Py_BuildValue("(O)", checked_ ? Py_True : Py_False),
PythonRef::kSteal);
// Call this in the next cycle (don't want to risk mucking with UI from
// within a UI loop)
call->ScheduleWeak(args);
// Schedule this to run immediately after any current UI traversal.
call->ScheduleInUIOperation(args);
}
}

View File

@ -346,9 +346,8 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
} else if (auto* call = on_cancel_call_.Get()) {
claimed = true;
// Call this in the next cycle (don't wanna risk mucking with UI from
// within a UI loop).
call->ScheduleWeak();
// Schedule this to run immediately after any current UI traversal.
call->ScheduleInUIOperation();
} else {
OnCancelCustom();
}
@ -630,9 +629,8 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
// Call our outside-click callback if unclaimed.
if (!claimed && on_outside_click_call_.Exists()) {
// Call this in the next cycle (don't wanna risk mucking with UI from
// within a UI loop).
on_outside_click_call_->ScheduleWeak();
// Schedule this to run immediately after any current UI traversal.
on_outside_click_call_->ScheduleInUIOperation();
}
// Always claim if they want.
@ -1070,9 +1068,8 @@ void ContainerWidget::Activate() {
last_activate_time_millisecs_ =
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
if (auto* call = on_activate_call_.Get()) {
// Call this in the next cycle (don't wanna risk mucking with UI from within
// a UI loop).
call->ScheduleWeak();
// Schedule this to run immediately after any current UI traversal.
call->ScheduleInUIOperation();
}
}

View File

@ -23,11 +23,17 @@ void HScrollWidget::OnTouchDelayTimerExpired() {
if (touch_held_) {
// Pass a mouse-down event if we haven't moved.
if (!touch_is_scrolling_ && !touch_down_sent_) {
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
ContainerWidget::HandleMessage(base::WidgetMessage(
base::WidgetMessage::Type::kMouseDown, nullptr, touch_x_, touch_y_,
static_cast<float>(touch_held_click_count_)));
touch_down_sent_ = true;
} else {
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
}
}

View File

@ -24,10 +24,17 @@ void ScrollWidget::OnTouchDelayTimerExpired() {
if (touch_held_) {
// Pass a mouse-down event if we haven't moved.
if (!touch_is_scrolling_ && !touch_down_sent_) {
// Gather up any user code triggered by this stuff and run it at the end
// before we return.
base::UI::OperationContext ui_op_context;
ContainerWidget::HandleMessage(base::WidgetMessage(
base::WidgetMessage::Type::kMouseDown, nullptr, touch_x_, touch_y_,
static_cast<float>(touch_held_click_count_)));
touch_down_sent_ = true;
// Run any calls built up by UI callbacks.
ui_op_context.Finish();
}
}

View File

@ -590,9 +590,8 @@ void TextWidget::Activate() {
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
if (auto* call = on_activate_call_.Get()) {
// Call this in the next cycle (don't wanna risk mucking with UI from
// within a UI loop).
call->ScheduleWeak();
// Schedule this to run immediately after any current UI traversal.
call->ScheduleInUIOperation();
}
// Bring up an editor if applicable.
@ -719,9 +718,8 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
} else {
if (auto* call = on_return_press_call_.Get()) {
claimed = true;
// Call this in the next cycle (don't wanna risk mucking with UI
// from within a UI loop)
call->ScheduleWeak();
// Schedule this to run immediately after any current UI traversal.
call->ScheduleInUIOperation();
}
}
break;

View File

@ -19,12 +19,10 @@ Widget::~Widget() {
Py_DECREF(py_ref_);
}
// The very last thing we do is call our on-delete calls.
// We need to be prepared for anything happening as a result of this,
// so let's work off a copy of our callback list in case it gets mucked with.
auto on_delete_calls = on_delete_calls_;
for (auto&& i : on_delete_calls) {
i->Run();
i->ScheduleInUIOperation();
// i->Run();
}
}
@ -83,12 +81,13 @@ auto Widget::IsInOverlayStack() const -> bool {
}
void Widget::SetSelected(bool s, SelectionCause cause) {
if (selected_ == s) return;
if (selected_ == s) {
return;
}
selected_ = s;
if (selected_ && on_select_call_.Exists()) {
// Call this in the next cycle (don't wanna risk mucking
// with UI from within a UI loop).
on_select_call_->ScheduleWeak();
// Schedule this to run immediately after any current UI traversal.
on_select_call_->ScheduleInUIOperation();
}
}

View File

@ -31,6 +31,8 @@ def _build_dir(arch: str, mode: str) -> str:
def build_openal(arch: str, mode: str) -> None:
"""Do the thing."""
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
from efrotools import replace_exact
if arch not in ARCHS:
@ -39,7 +41,11 @@ def build_openal(arch: str, mode: str) -> None:
if mode not in MODES:
raise CleanError(f"Invalid mode '{mode}'.")
# enable_oboe = True
# If true, we suppress most OpenAL logs to keep logcat tidier.
reduce_logs = False
# Inject a function to reroute OpenAL logs to ourself.
reroute_logs = True
# Get ndk path.
ndk_path = (
@ -60,9 +66,13 @@ def build_openal(arch: str, mode: str) -> None:
['git', 'clone', 'https://github.com/kcat/openal-soft.git', builddir],
check=True,
)
# subprocess.run(['git', 'checkout', '1.23.1'], check=True, cwd=builddir)
subprocess.run(
['git', 'checkout', 'b81a270f6c1e795ca70d7684e0ccf35a19f247e2'],
[
'git',
'checkout',
# '1.23.1',
'1381a951bea78c67281a2e844e6db1dedbd5ed7c',
],
check=True,
cwd=builddir,
)
@ -87,59 +97,102 @@ def build_openal(arch: str, mode: str) -> None:
# possible to filter by tag/level. However I'd prefer it to send
# only the ones that it would send to stderr so I don't always have
# to worry about filtering.
loggingpath = f'{builddir}/core/logging.cpp'
with open(loggingpath, encoding='utf-8') as infile:
txt = infile.read()
if reduce_logs or reroute_logs:
loggingpath = f'{builddir}/core/logging.cpp'
with open(loggingpath, encoding='utf-8') as infile:
txt = infile.read()
logcall = (
'__android_log_print(android_severity(level),'
' "openal", "%s", str);'
)
condition = 'gLogLevel >= level' if reduce_logs else 'true'
if reroute_logs:
logcall = (
'if (alcCustomAndroidLogger) {\n'
' alcCustomAndroidLogger(android_severity(level), str);\n'
'} else {\n'
f' {logcall}\n'
'}'
)
txt = replace_exact(
txt,
(
' __android_log_print(android_severity(level),'
' "openal", "%s", str);'
),
(
' // ericf tweak; only send logs that meet some condition.\n'
f' if ({condition}) {{\n'
f' {logcall}\n'
' }'
),
)
# Note to self: looks like there's actually already a log
# redirect callback function in OpenALSoft, but it's not an
# official extension yet and I haven't been able to get it
# working in its current form. Ideally should adopt that
# eventually though.
if reroute_logs:
txt = replace_exact(
txt,
'namespace {\n',
(
'extern void (*alcCustomAndroidLogger)(int, const char*);\n'
'\n'
'namespace {\n'
),
)
with open(loggingpath, 'w', encoding='utf-8') as outfile:
outfile.write(txt)
# Add a function to set a logging function so we can capture OpenAL
# logging.
fpath = f'{builddir}/alc/alc.cpp'
with open(fpath, encoding='utf-8') as infile:
txt = infile.read()
txt = replace_exact(
txt,
' __android_log_print(android_severity(level),'
' "openal", "%s", str);',
' // ericf tweak; only send logs to'
' android that we\'d send to stderr.\n'
' if (gLogLevel >= level) {\n'
' __android_log_print(android_severity(level),'
' "openal", "%s", str);\n'
' }',
'ALC_API ALCenum ALC_APIENTRY'
' alcGetError(ALCdevice *device) noexcept\n',
(
'void (*alcCustomAndroidLogger)(int, const char*) = nullptr;\n'
'\n'
'ALC_API void ALC_APIENTRY'
' alcSetCustomAndroidLogger(void (*fn)(int, const char*)) {\n'
' alcCustomAndroidLogger = fn;\n'
'}\n'
'\n'
'ALC_API ALCenum ALC_APIENTRY'
' alcGetError(ALCdevice *device) noexcept\n'
),
)
with open(loggingpath, 'w', encoding='utf-8') as outfile:
with open(fpath, 'w', encoding='utf-8') as outfile:
outfile.write(txt)
# Add a function to set a logging function so we can gather info
# on AL fatal errors/etc.
# fpath = f'{builddir}/alc/alc.cpp'
# with open(fpath, encoding='utf-8') as infile:
# txt = infile.read()
# txt = replace_exact(
# txt,
# 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device)\n',
# (
# 'void (*alcDebugLogger)(const char*) = nullptr;\n'
# '\n'
# 'ALC_API void ALC_APIENTRY'
# ' alcSetDebugLogger(void (*fn)(const char*)) {\n'
# ' alcDebugLogger = fn;\n'
# '}\n'
# '\n'
# 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device)\n'
# ),
# )
# with open(fpath, 'w', encoding='utf-8') as outfile:
# outfile.write(txt)
# fpath = f'{builddir}/include/AL/alc.h'
# with open(fpath, encoding='utf-8') as infile:
# txt = infile.read()
# txt = replace_exact(
# txt,
# 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device);\n',
# 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device);\n'
# 'ALC_API void ALC_APIENTRY alcSetDebugLogger('
# 'void (*fn)(const char*));\n',
# )
# with open(fpath, 'w', encoding='utf-8') as outfile:
# outfile.write(txt)
fpath = f'{builddir}/include/AL/alc.h'
with open(fpath, encoding='utf-8') as infile:
txt = infile.read()
txt = replace_exact(
txt,
(
'ALC_API ALCenum ALC_APIENTRY'
' alcGetError(ALCdevice *device) ALC_API_NOEXCEPT;\n'
),
(
'ALC_API ALCenum ALC_APIENTRY'
' alcGetError(ALCdevice *device) ALC_API_NOEXCEPT;\n'
'ALC_API void ALC_APIENTRY alcSetCustomAndroidLogger('
'void (*fn)(int, const char*));\n'
),
)
with open(fpath, 'w', encoding='utf-8') as outfile:
outfile.write(txt)
# Let's modify the try/catch around api calls so that we can catch
# and inspect exceptions thrown by them instead of it just resulting
# in an insta-terminate.
fpath = f'{builddir}/core/except.h'
with open(fpath, encoding='utf-8') as infile:
txt = infile.read()
@ -151,18 +204,6 @@ def build_openal(arch: str, mode: str) -> None:
txt = replace_exact(
txt, '#define START_API_FUNC try\n', '#define START_API_FUNC\n'
)
# txt = replace_exact(
# txt,
# '#define END_API_FUNC catch(...) { std::terminate(); }\n',
# 'extern void (*alcDebugLogger)(const char*);\n'
# '\n'
# '#define END_API_FUNC catch(...) { \\\n'
# ' if (alcDebugLogger != nullptr) { \\\n'
# ' alcDebugLogger("UNKNOWN OpenALSoft fatal exception."); \\\n'
# ' } \\\n'
# ' std::terminate(); \\\n'
# '}\n'
# )
with open(fpath, 'w', encoding='utf-8') as outfile:
outfile.write(txt)