mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-28 01:43:22 +08:00
Merge branch 'efroemling:master' into master
This commit is contained in:
commit
1a99f7491b
114
.efrocachemap
generated
114
.efrocachemap
generated
@ -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",
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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."""
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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__)
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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_;
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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_;
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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_
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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()) {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -42,6 +42,7 @@ class EventLoop;
|
||||
class FeatureSetNativeComponent;
|
||||
class JsonDict;
|
||||
class Matrix44f;
|
||||
class NativeStackTrace;
|
||||
class Object;
|
||||
class Python;
|
||||
class PythonRef;
|
||||
|
||||
31
src/ballistica/shared/generic/native_stack_trace.h
Normal file
31
src/ballistica/shared/generic/native_stack_trace.h
Normal 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_
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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_;
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user