mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-02-06 23:59:18 +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/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
|
||||||
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
|
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
|
||||||
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
|
"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/arabic.json": "00ba700de6c672a56658a6bd1ad27523",
|
||||||
"build/assets/ba_data/data/languages/belarussian.json": "40883823367f04c5a2403a96525bfcc3",
|
"build/assets/ba_data/data/languages/belarussian.json": "5602b4e83ab78af37097a29593dff3d5",
|
||||||
"build/assets/ba_data/data/languages/chinese.json": "5761468d25f2bd4e79921826cebd572b",
|
"build/assets/ba_data/data/languages/chinese.json": "ff9a595726f0aff42a39be576d0ff037",
|
||||||
"build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078",
|
"build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078",
|
||||||
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
|
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
|
||||||
"build/assets/ba_data/data/languages/czech.json": "cd21ad8c6b8e9ed700284cf1e1aecbf8",
|
"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/dutch.json": "22b44a33bf81142ba2befad14eb5746e",
|
||||||
"build/assets/ba_data/data/languages/english.json": "6a3fab4fb8b2879e00ed9877709bf504",
|
"build/assets/ba_data/data/languages/english.json": "6a3fab4fb8b2879e00ed9877709bf504",
|
||||||
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
|
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
|
||||||
"build/assets/ba_data/data/languages/filipino.json": "6f4051ce78861a4666f4978d6f9a0ed2",
|
"build/assets/ba_data/data/languages/filipino.json": "e750fb1a95e4c5611115f9ece9ecab53",
|
||||||
"build/assets/ba_data/data/languages/french.json": "49ff6d211537b8003b8241438dca661d",
|
"build/assets/ba_data/data/languages/french.json": "163362f7b33866ef069cae62d0387551",
|
||||||
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
|
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
|
||||||
"build/assets/ba_data/data/languages/gibberish.json": "25fcb5130fae56985bee175aa19f86a2",
|
"build/assets/ba_data/data/languages/gibberish.json": "25fcb5130fae56985bee175aa19f86a2",
|
||||||
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
|
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
|
||||||
"build/assets/ba_data/data/languages/hindi.json": "8848f6b0caec0fcf9d85bc6e683809ec",
|
"build/assets/ba_data/data/languages/hindi.json": "8848f6b0caec0fcf9d85bc6e683809ec",
|
||||||
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
|
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
|
||||||
"build/assets/ba_data/data/languages/indonesian.json": "408fb026e84c24a8dd7a43cb2b794541",
|
"build/assets/ba_data/data/languages/indonesian.json": "408fb026e84c24a8dd7a43cb2b794541",
|
||||||
"build/assets/ba_data/data/languages/italian.json": "f2f5641bc924dfba37af2aac03e469e0",
|
"build/assets/ba_data/data/languages/italian.json": "1824b50a9154594e7039883f6d35504f",
|
||||||
"build/assets/ba_data/data/languages/korean.json": "ae179765711bead59d0de7fb15fac72f",
|
"build/assets/ba_data/data/languages/korean.json": "03fd99d5e1155e81053fc028f69df982",
|
||||||
"build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38",
|
"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/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/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/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
|
||||||
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef",
|
"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/swedish.json": "5142a96597d17d8344be96a603da64ac",
|
||||||
"build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c",
|
"build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c",
|
||||||
"build/assets/ba_data/data/languages/thai.json": "77755219bbf5fb7eea0d6b226684f403",
|
"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/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/languages/vietnamese.json": "921cd1e50f60fe3e101f246e172750ba",
|
||||||
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
|
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
|
||||||
"build/assets/ba_data/data/maps/bridgit.json": "6aea74805f4880cc11237c5734a24422",
|
"build/assets/ba_data/data/maps/bridgit.json": "6aea74805f4880cc11237c5734a24422",
|
||||||
@ -4060,50 +4060,50 @@
|
|||||||
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
|
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
|
||||||
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
|
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
|
||||||
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
|
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
|
||||||
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "6135aeb242afaf9d1114810a67c89cec",
|
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "4d6df65a5417a0eb23f4838b26054ce6",
|
||||||
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "bbbbb14d42ed6eb0c5eb56867b7fb870",
|
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "d4c5bd03eda639f73b769664fae9691b",
|
||||||
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cd28f9cc4652736a31c677fc4e5dbaf1",
|
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "8837eed81869a6d9404afc890a99aeda",
|
||||||
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "239c608cc52c0320210e56ad6abe57a5",
|
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "906ca39550dc91235ebdf1c217b3116b",
|
||||||
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e76d67cacf1393d33796d6b6b1bf1413",
|
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e54895522cdd27f020b66260acdcc42d",
|
||||||
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a7eaa8dc4d859ef7a735483b04ccec4a",
|
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a293a3b7af38f265c995beba6027be93",
|
||||||
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "7a2eef42da34a35ddcc2fd7c66843b1b",
|
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "380a18b86065840c44285dc8762f42c4",
|
||||||
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "694599ac6a967b2ed383b27bf8093e5b",
|
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "2dc321ce47a02190e51c2f6f4f807449",
|
||||||
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "c91cbab6a07affa22e0612210f8b807c",
|
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "117f585f14bb816ed0ec5bac792077c0",
|
||||||
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "d460f7a3909f92d5dbf752e4521a9fbc",
|
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "124b14dbca819bfc300ec6c521641f78",
|
||||||
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0a0abfe75bc987e7b65a3cfa106e8353",
|
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "4df0f5f8183b325f5be25f2f9d5fa17e",
|
||||||
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "8f21405b29f2b2ab01323d711492cca0",
|
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "023558d9028ba3250384f9d7d9ef9a17",
|
||||||
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "96dc73e819f41f99a1b2dbb45f79d551",
|
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "4990e055b064408ede929a4c57253344",
|
||||||
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c79ac51cd2deabb1c2d0acddeaf81c30",
|
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "439574bda6d8d320ce38968886e83181",
|
||||||
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "f06ec14e8c3106be9df91af7da621dc9",
|
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "d62da566c946136a746d55ec04c28020",
|
||||||
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f389f9a7b1afc81f76787722340cfa9c",
|
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "def8d189ca344df21b0a5bb9619fef12",
|
||||||
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "c7dab78aac11cb1430d8456d5d48107a",
|
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "b75406a2f66d2cb74359b78356f89710",
|
||||||
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "67e29852dfee2e63e179cfebf608ef26",
|
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "cc1dccb6c5e63acf7bc42ee4346df9c6",
|
||||||
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "9778f8faf91c9993fbf3015bd4554a87",
|
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "27028549259c2f3843d54c3d29fad470",
|
||||||
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "73477bd15b9e3834314fd878c9e108d4",
|
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "656d50ed1514203e967dcf90a594fe66",
|
||||||
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "fb9b8443c1b4cccad749df7d6328220f",
|
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "db535f0ca1e01af825f75f204fbc8928",
|
||||||
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "384fb7fd55ad5a6cdbb662da1ec402ab",
|
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "97d51afca996ae15b61fd9f409a00459",
|
||||||
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "fb9b8443c1b4cccad749df7d6328220f",
|
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "db535f0ca1e01af825f75f204fbc8928",
|
||||||
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "384fb7fd55ad5a6cdbb662da1ec402ab",
|
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "97d51afca996ae15b61fd9f409a00459",
|
||||||
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "bc7d0811bcd87156ebf5292a38a1c350",
|
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "43794f4973b09588367261faf46b652a",
|
||||||
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "bb32f45054b6999300bf8b41d6a4b402",
|
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "9bc71c9874596dd708841d84dff69b55",
|
||||||
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "bc7d0811bcd87156ebf5292a38a1c350",
|
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "43794f4973b09588367261faf46b652a",
|
||||||
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "bb32f45054b6999300bf8b41d6a4b402",
|
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "9bc71c9874596dd708841d84dff69b55",
|
||||||
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "8d9a1505bf397f4902baabed7c1cf438",
|
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "87f6bf3ea1196d91cd6486785702a5b2",
|
||||||
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "f4d9c115e22dd81e36d1c5baeac8d848",
|
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "60336f9d664825ca08236dda311276ca",
|
||||||
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "8d9a1505bf397f4902baabed7c1cf438",
|
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "87f6bf3ea1196d91cd6486785702a5b2",
|
||||||
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "f4d9c115e22dd81e36d1c5baeac8d848",
|
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "60336f9d664825ca08236dda311276ca",
|
||||||
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "fb72c92ec6ec0e1c8f4ced32abd86505",
|
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "4399c87b58fbe58fa67096cfa878f86a",
|
||||||
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "131aab20cfe77fe89c3f452a855f1e68",
|
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "452623f0495dd4375e5b5d9b80d643d5",
|
||||||
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "ee10cdc9f9a861e2be0f1a208c0ca0fe",
|
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "ca49b32ed573feea11613d62cd89840c",
|
||||||
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "131aab20cfe77fe89c3f452a855f1e68",
|
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "452623f0495dd4375e5b5d9b80d643d5",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "678fabc6dfd6f401ee8942d088ee9181",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "dfb0839e79411869b196bc48565a36bc",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "e092d2aed8464a61a623d79ca25308d8",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "e2beda37ec0d3efde860d0c2e248b9e0",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6b658f49be396ad645c5e57464739a3b",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "2adae2522084e571c51595c7d31ac1dc",
|
||||||
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "9d79a56403a6d806ff131a7de664dfa7",
|
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "4ef71e752bfb318db84e88dfdd51d955",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "e831a26d2c28e862d51e24393d158c99",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "a123d9ecd0d53d9ad2f13abdf9842ade",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "46fe1c89bcc75c781729ec9e5491c610",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "6afeb02344f2faf2979188e54c6d2dc4",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "9c6278d7df3ce4db2ffe7794a0fd35b7",
|
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "abedb44a923a4845fdc350fd979a1dc8",
|
||||||
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "110c35a17b462864075800756b5e541a",
|
"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/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
|
||||||
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
|
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
|
||||||
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",
|
"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
|
- Added `bascenev1.get_connection_to_host_info_2()` which is an improved
|
||||||
type-safe version of `bascenev1.get_connection_to_host_info()`.
|
type-safe version of `bascenev1.get_connection_to_host_info()`.
|
||||||
- There is now a link to the official Discord server in the About section
|
- There is now a link to the official Discord server in the About section
|
||||||
(thanks EraOSBeta!).
|
(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)
|
### 1.7.30 (build 21697, api 8, 2023-12-08)
|
||||||
- Continued work on the big 1.7.28 update.
|
- 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.cc
|
||||||
${BA_SRC_ROOT}/ballistica/shared/generic/json.h
|
${BA_SRC_ROOT}/ballistica/shared/generic/json.h
|
||||||
${BA_SRC_ROOT}/ballistica/shared/generic/lambda_runnable.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.cc
|
||||||
${BA_SRC_ROOT}/ballistica/shared/generic/runnable.h
|
${BA_SRC_ROOT}/ballistica/shared/generic/runnable.h
|
||||||
${BA_SRC_ROOT}/ballistica/shared/generic/snapshot.h
|
${BA_SRC_ROOT}/ballistica/shared/generic/snapshot.h
|
||||||
|
|||||||
@ -689,6 +689,7 @@
|
|||||||
<ClCompile Include="..\..\src\ballistica\shared\generic\json.cc" />
|
<ClCompile Include="..\..\src\ballistica\shared\generic\json.cc" />
|
||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\json.h" />
|
<ClInclude Include="..\..\src\ballistica\shared\generic\json.h" />
|
||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.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" />
|
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc" />
|
||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h" />
|
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h" />
|
||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h" />
|
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h" />
|
||||||
|
|||||||
@ -1501,6 +1501,9 @@
|
|||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h">
|
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h">
|
||||||
<Filter>ballistica\shared\generic</Filter>
|
<Filter>ballistica\shared\generic</Filter>
|
||||||
</ClInclude>
|
</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">
|
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc">
|
||||||
<Filter>ballistica\shared\generic</Filter>
|
<Filter>ballistica\shared\generic</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
@ -684,6 +684,7 @@
|
|||||||
<ClCompile Include="..\..\src\ballistica\shared\generic\json.cc" />
|
<ClCompile Include="..\..\src\ballistica\shared\generic\json.cc" />
|
||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\json.h" />
|
<ClInclude Include="..\..\src\ballistica\shared\generic\json.h" />
|
||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.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" />
|
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc" />
|
||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h" />
|
<ClInclude Include="..\..\src\ballistica\shared\generic\runnable.h" />
|
||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h" />
|
<ClInclude Include="..\..\src\ballistica\shared\generic\snapshot.h" />
|
||||||
|
|||||||
@ -1501,6 +1501,9 @@
|
|||||||
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h">
|
<ClInclude Include="..\..\src\ballistica\shared\generic\lambda_runnable.h">
|
||||||
<Filter>ballistica\shared\generic</Filter>
|
<Filter>ballistica\shared\generic</Filter>
|
||||||
</ClInclude>
|
</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">
|
<ClCompile Include="..\..\src\ballistica\shared\generic\runnable.cc">
|
||||||
<Filter>ballistica\shared\generic</Filter>
|
<Filter>ballistica\shared\generic</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
@ -61,7 +61,6 @@ from _babase import (
|
|||||||
in_logic_thread,
|
in_logic_thread,
|
||||||
increment_analytics_count,
|
increment_analytics_count,
|
||||||
is_os_playing_music,
|
is_os_playing_music,
|
||||||
is_running_on_fire_tv,
|
|
||||||
is_xcode_build,
|
is_xcode_build,
|
||||||
lock_all_input,
|
lock_all_input,
|
||||||
mac_music_app_get_playlists,
|
mac_music_app_get_playlists,
|
||||||
@ -77,6 +76,7 @@ from _babase import (
|
|||||||
native_review_request,
|
native_review_request,
|
||||||
native_review_request_supported,
|
native_review_request_supported,
|
||||||
native_stack_trace,
|
native_stack_trace,
|
||||||
|
open_file_externally,
|
||||||
print_load_info,
|
print_load_info,
|
||||||
pushcall,
|
pushcall,
|
||||||
quit,
|
quit,
|
||||||
@ -258,7 +258,6 @@ __all__ = [
|
|||||||
'is_browser_likely_available',
|
'is_browser_likely_available',
|
||||||
'is_os_playing_music',
|
'is_os_playing_music',
|
||||||
'is_point_in_box',
|
'is_point_in_box',
|
||||||
'is_running_on_fire_tv',
|
|
||||||
'is_xcode_build',
|
'is_xcode_build',
|
||||||
'LanguageSubsystem',
|
'LanguageSubsystem',
|
||||||
'lock_all_input',
|
'lock_all_input',
|
||||||
@ -283,6 +282,7 @@ __all__ = [
|
|||||||
'NodeNotFoundError',
|
'NodeNotFoundError',
|
||||||
'normalized_color',
|
'normalized_color',
|
||||||
'NotFoundError',
|
'NotFoundError',
|
||||||
|
'open_file_externally',
|
||||||
'Permission',
|
'Permission',
|
||||||
'PlayerNotFoundError',
|
'PlayerNotFoundError',
|
||||||
'Plugin',
|
'Plugin',
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import logging
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import babase
|
import babase
|
||||||
import bauiv1
|
|
||||||
import bascenev1
|
import bascenev1
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -72,7 +71,8 @@ class AdsSubsystem:
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
self.last_ad_purpose = purpose
|
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(
|
def show_ad_2(
|
||||||
self,
|
self,
|
||||||
@ -81,7 +81,8 @@ class AdsSubsystem:
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
self.last_ad_purpose = purpose
|
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:
|
def call_after_ad(self, call: Callable[[], Any]) -> None:
|
||||||
"""Run a call after potentially showing an ad."""
|
"""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
|
This checks the user config and falls back to default values
|
||||||
where available.
|
where available.
|
||||||
"""
|
"""
|
||||||
# pylint: disable=too-many-statements
|
|
||||||
# pylint: disable=too-many-return-statements
|
# pylint: disable=too-many-return-statements
|
||||||
# pylint: disable=too-many-branches
|
# pylint: disable=too-many-branches
|
||||||
|
|
||||||
@ -83,91 +82,6 @@ def get_input_device_mapped_value(
|
|||||||
'triggerRun1': 5,
|
'triggerRun1': 5,
|
||||||
}.get(name, -1)
|
}.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:
|
elif 'NVIDIA SHIELD;' in useragentstring:
|
||||||
if 'NVIDIA Controller' in devicename:
|
if 'NVIDIA Controller' in devicename:
|
||||||
return {
|
return {
|
||||||
@ -182,112 +96,6 @@ def get_input_device_mapped_value(
|
|||||||
'buttonIgnored': 184,
|
'buttonIgnored': 184,
|
||||||
'buttonIgnored2': 86,
|
'buttonIgnored2': 86,
|
||||||
}.get(name, -1)
|
}.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 = {
|
default_android_mapping = {
|
||||||
'triggerRun2': 19,
|
'triggerRun2': 19,
|
||||||
@ -310,6 +118,41 @@ def get_input_device_mapped_value(
|
|||||||
|
|
||||||
# Generic android...
|
# Generic android...
|
||||||
if platform == '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.
|
# Steelseries stratus xl.
|
||||||
if devicename == 'SteelSeries Stratus XL':
|
if devicename == 'SteelSeries Stratus XL':
|
||||||
return {
|
return {
|
||||||
@ -387,14 +230,6 @@ def get_input_device_mapped_value(
|
|||||||
'uiOnly': True,
|
'uiOnly': True,
|
||||||
}.get(name, -1)
|
}.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
|
# Nvidia controller is default, but gets some strange
|
||||||
# keypresses we want to ignore.. touching the touchpad,
|
# keypresses we want to ignore.. touching the touchpad,
|
||||||
# so lets ignore those.
|
# so lets ignore those.
|
||||||
@ -452,76 +287,11 @@ def get_input_device_mapped_value(
|
|||||||
'buttonRight': 100,
|
'buttonRight': 100,
|
||||||
}.get(name, -1)
|
}.get(name, -1)
|
||||||
|
|
||||||
# Ok, this gamepad's not in our specific preset list;
|
# Ok, this gamepad's not in our specific preset list; fall back to
|
||||||
# fall back to some (hopefully) reasonable defaults.
|
# 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)
|
|
||||||
|
|
||||||
# Reasonable defaults.
|
# Reasonable defaults.
|
||||||
if platform == 'android':
|
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)
|
return default_android_mapping.get(name, -1)
|
||||||
|
|
||||||
# Is there a point to any sort of fallbacks here?.. should check.
|
# 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()
|
md5 = hashlib.md5()
|
||||||
|
|
||||||
# Currently we just do a single hash of *all* inputs on android
|
# Currently we just do a single hash of *all* inputs on android and
|
||||||
# and that's it.. good enough.
|
# that's it. Good enough. (grabbing mappings for a specific device
|
||||||
# (grabbing mappings for a specific device looks to be non-trivial)
|
# looks to be non-trivial)
|
||||||
for dirname in [
|
for dirname in [
|
||||||
'/system/usr/keylayout',
|
'/system/usr/keylayout',
|
||||||
'/data/usr/keylayout',
|
'/data/usr/keylayout',
|
||||||
@ -551,9 +321,9 @@ def _gen_android_input_hash() -> str:
|
|||||||
try:
|
try:
|
||||||
if os.path.isdir(dirname):
|
if os.path.isdir(dirname):
|
||||||
for f_name in os.listdir(dirname):
|
for f_name in os.listdir(dirname):
|
||||||
# This is usually volume keys and stuff;
|
# This is usually volume keys and stuff; assume we
|
||||||
# assume we can skip it?..
|
# can skip it?.. (since it'll vary a lot across
|
||||||
# (since it'll vary a lot across devices)
|
# devices)
|
||||||
if f_name == 'gpio-keys.kl':
|
if f_name == 'gpio-keys.kl':
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
@ -576,8 +346,8 @@ def get_input_device_map_hash() -> str:
|
|||||||
"""
|
"""
|
||||||
app = babase.app
|
app = babase.app
|
||||||
|
|
||||||
# Currently only using this when classic is present.
|
# Currently only using this when classic is present. Need to replace
|
||||||
# Need to replace with a modern equivalent.
|
# with a modern equivalent.
|
||||||
if app.classic is not None:
|
if app.classic is not None:
|
||||||
try:
|
try:
|
||||||
if app.classic.input_map_hash is None:
|
if app.classic.input_map_hash is None:
|
||||||
|
|||||||
@ -451,15 +451,6 @@ class ClassicSubsystem(babase.AppSubsystem):
|
|||||||
if playtype in val.get_play_types()
|
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:
|
def game_begin_analytics(self) -> None:
|
||||||
"""(internal)"""
|
"""(internal)"""
|
||||||
from baclassic import _analytics
|
from baclassic import _analytics
|
||||||
|
|||||||
@ -52,8 +52,8 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
# Build number and version of the ballistica binary we expect to be
|
# Build number and version of the ballistica binary we expect to be
|
||||||
# using.
|
# using.
|
||||||
TARGET_BALLISTICA_BUILD = 21707
|
TARGET_BALLISTICA_BUILD = 21728
|
||||||
TARGET_BALLISTICA_VERSION = '1.7.31'
|
TARGET_BALLISTICA_VERSION = '1.7.32'
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
|||||||
@ -264,3 +264,26 @@ class PlusSubsystem(AppSubsystem):
|
|||||||
def can_show_ad() -> bool:
|
def can_show_ad() -> bool:
|
||||||
"""Can we show an ad?"""
|
"""Can we show an ad?"""
|
||||||
return _baplus.can_show_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'})
|
self.end({'outcome': 'next_level'})
|
||||||
|
|
||||||
def _ui_gc(self) -> None:
|
def _ui_gc(self) -> None:
|
||||||
if bs.app.classic is not None:
|
if bs.app.plus is not None:
|
||||||
bs.app.classic.show_online_score_ui(
|
bs.app.plus.show_game_service_ui(
|
||||||
'leaderboard',
|
'leaderboard',
|
||||||
game=self._game_name_str,
|
game=self._game_name_str,
|
||||||
game_version=self._game_config_str,
|
game_version=self._game_config_str,
|
||||||
)
|
)
|
||||||
else:
|
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:
|
def _ui_show_achievements(self) -> None:
|
||||||
if bs.app.classic is not None:
|
if bs.app.plus is not None:
|
||||||
bs.app.classic.show_online_score_ui('achievements')
|
bs.app.plus.show_game_service_ui('achievements')
|
||||||
else:
|
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:
|
def _ui_worlds_best(self) -> None:
|
||||||
if self._score_link is None:
|
if self._score_link is None:
|
||||||
|
|||||||
@ -60,7 +60,6 @@ from babase import (
|
|||||||
in_logic_thread,
|
in_logic_thread,
|
||||||
increment_analytics_count,
|
increment_analytics_count,
|
||||||
is_browser_likely_available,
|
is_browser_likely_available,
|
||||||
is_running_on_fire_tv,
|
|
||||||
is_xcode_build,
|
is_xcode_build,
|
||||||
lock_all_input,
|
lock_all_input,
|
||||||
LoginAdapter,
|
LoginAdapter,
|
||||||
@ -69,6 +68,7 @@ from babase import (
|
|||||||
native_review_request,
|
native_review_request,
|
||||||
native_review_request_supported,
|
native_review_request_supported,
|
||||||
NotFoundError,
|
NotFoundError,
|
||||||
|
open_file_externally,
|
||||||
Permission,
|
Permission,
|
||||||
Plugin,
|
Plugin,
|
||||||
PluginSpec,
|
PluginSpec,
|
||||||
@ -105,15 +105,11 @@ from _bauiv1 import (
|
|||||||
imagewidget,
|
imagewidget,
|
||||||
is_party_icon_visible,
|
is_party_icon_visible,
|
||||||
Mesh,
|
Mesh,
|
||||||
open_file_externally,
|
|
||||||
open_url,
|
open_url,
|
||||||
rowwidget,
|
rowwidget,
|
||||||
scrollwidget,
|
scrollwidget,
|
||||||
set_party_icon_always_visible,
|
set_party_icon_always_visible,
|
||||||
set_party_window_open,
|
set_party_window_open,
|
||||||
show_ad,
|
|
||||||
show_ad_2,
|
|
||||||
show_online_score_ui,
|
|
||||||
Sound,
|
Sound,
|
||||||
Texture,
|
Texture,
|
||||||
textwidget,
|
textwidget,
|
||||||
@ -181,7 +177,6 @@ __all__ = [
|
|||||||
'increment_analytics_count',
|
'increment_analytics_count',
|
||||||
'is_browser_likely_available',
|
'is_browser_likely_available',
|
||||||
'is_party_icon_visible',
|
'is_party_icon_visible',
|
||||||
'is_running_on_fire_tv',
|
|
||||||
'is_xcode_build',
|
'is_xcode_build',
|
||||||
'Keyboard',
|
'Keyboard',
|
||||||
'lock_all_input',
|
'lock_all_input',
|
||||||
@ -210,9 +205,6 @@ __all__ = [
|
|||||||
'set_party_icon_always_visible',
|
'set_party_icon_always_visible',
|
||||||
'set_party_window_open',
|
'set_party_window_open',
|
||||||
'set_ui_input_device',
|
'set_ui_input_device',
|
||||||
'show_ad',
|
|
||||||
'show_ad_2',
|
|
||||||
'show_online_score_ui',
|
|
||||||
'Sound',
|
'Sound',
|
||||||
'SpecialChar',
|
'SpecialChar',
|
||||||
'supports_max_fps',
|
'supports_max_fps',
|
||||||
|
|||||||
@ -58,6 +58,7 @@ class OnScreenKeyboardWindow(Window):
|
|||||||
position=(30, self._height - 55),
|
position=(30, self._height - 55),
|
||||||
size=(60, 60),
|
size=(60, 60),
|
||||||
label='',
|
label='',
|
||||||
|
enable_sound=False,
|
||||||
on_activate_call=self._cancel,
|
on_activate_call=self._cancel,
|
||||||
autoselect=True,
|
autoselect=True,
|
||||||
color=(0.55, 0.5, 0.6),
|
color=(0.55, 0.5, 0.6),
|
||||||
|
|||||||
@ -1258,19 +1258,21 @@ class AccountSettingsWindow(bui.Window):
|
|||||||
self._needs_refresh = False
|
self._needs_refresh = False
|
||||||
|
|
||||||
def _on_game_service_button_press(self) -> None:
|
def _on_game_service_button_press(self) -> None:
|
||||||
if bui.app.classic is not None:
|
if bui.app.plus is not None:
|
||||||
bui.app.classic.show_online_score_ui()
|
bui.app.plus.show_game_service_ui()
|
||||||
else:
|
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:
|
def _on_custom_achievements_press(self) -> None:
|
||||||
if bui.app.classic is not None:
|
if bui.app.plus is not None:
|
||||||
bui.apptimer(
|
bui.apptimer(
|
||||||
0.15,
|
0.15,
|
||||||
bui.Call(bui.app.classic.show_online_score_ui, 'achievements'),
|
bui.Call(bui.app.plus.show_game_service_ui, 'achievements'),
|
||||||
)
|
)
|
||||||
else:
|
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:
|
def _on_achievements_press(self) -> None:
|
||||||
# pylint: disable=cyclic-import
|
# pylint: disable=cyclic-import
|
||||||
@ -1327,13 +1329,13 @@ class AccountSettingsWindow(bui.Window):
|
|||||||
bui.open_url(response.url)
|
bui.open_url(response.url)
|
||||||
|
|
||||||
def _on_leaderboards_press(self) -> None:
|
def _on_leaderboards_press(self) -> None:
|
||||||
if bui.app.classic is not None:
|
if bui.app.plus is not None:
|
||||||
bui.apptimer(
|
bui.apptimer(
|
||||||
0.15,
|
0.15,
|
||||||
bui.Call(bui.app.classic.show_online_score_ui, 'leaderboards'),
|
bui.Call(bui.app.plus.show_game_service_ui, 'leaderboards'),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logging.warning('show_online_score_ui requires classic')
|
logging.warning('show_game_service_ui requires classic')
|
||||||
|
|
||||||
def _have_unlinkable_v1_accounts(self) -> bool:
|
def _have_unlinkable_v1_accounts(self) -> bool:
|
||||||
plus = bui.app.plus
|
plus = bui.app.plus
|
||||||
|
|||||||
@ -1244,10 +1244,12 @@ class MainMenuWindow(bui.Window):
|
|||||||
|
|
||||||
def _do_game_service_press(self) -> None:
|
def _do_game_service_press(self) -> None:
|
||||||
self._save_state()
|
self._save_state()
|
||||||
if bui.app.classic is not None:
|
if bui.app.plus is not None:
|
||||||
bui.app.classic.show_online_score_ui()
|
bui.app.plus.show_game_service_ui()
|
||||||
else:
|
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:
|
def _save_state(self) -> None:
|
||||||
# Don't do this for the in-game menu.
|
# Don't do this for the in-game menu.
|
||||||
|
|||||||
@ -20,6 +20,13 @@
|
|||||||
#if BA_OPENAL_IS_SOFT
|
#if BA_OPENAL_IS_SOFT
|
||||||
#define AL_ALEXT_PROTOTYPES
|
#define AL_ALEXT_PROTOTYPES
|
||||||
#include <alext.h>
|
#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
|
#endif
|
||||||
|
|
||||||
#define CHECK_AL_ERROR _check_al_error(__FILE__, __LINE__)
|
#define CHECK_AL_ERROR _check_al_error(__FILE__, __LINE__)
|
||||||
|
|||||||
@ -4,6 +4,11 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
// Ew fixme.
|
||||||
|
#if BA_OSTYPE_ANDROID
|
||||||
|
#include <android/log.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include "ballistica/base/app_adapter/app_adapter.h"
|
#include "ballistica/base/app_adapter/app_adapter.h"
|
||||||
#include "ballistica/base/assets/assets.h"
|
#include "ballistica/base/assets/assets.h"
|
||||||
#include "ballistica/base/assets/sound_asset.h"
|
#include "ballistica/base/assets/sound_asset.h"
|
||||||
@ -34,6 +39,8 @@ LPALCDEVICERESUMESOFT alcDeviceResumeSOFT{};
|
|||||||
LPALCRESETDEVICESOFT alcResetDeviceSOFT{};
|
LPALCRESETDEVICESOFT alcResetDeviceSOFT{};
|
||||||
LPALEVENTCALLBACKSOFT alEventCallbackSOFT{};
|
LPALEVENTCALLBACKSOFT alEventCallbackSOFT{};
|
||||||
LPALEVENTCONTROLSOFT alEventControlSOFT{};
|
LPALEVENTCONTROLSOFT alEventControlSOFT{};
|
||||||
|
// LPALSOFTSETLOGCALLBACK alsoft_set_log_callback{};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const int kAudioProcessIntervalNormal{500 * 1000};
|
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)));
|
+ 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
|
#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_() {
|
void AudioServer::OnAppStartInThread_() {
|
||||||
assert(g_base->InAudioThread());
|
assert(g_base->InAudioThread());
|
||||||
|
|
||||||
@ -219,14 +260,38 @@ void AudioServer::OnAppStartInThread_() {
|
|||||||
}
|
}
|
||||||
#endif // BA_RIFT_BUILD
|
#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);
|
auto* device = alcOpenDevice(al_device_name);
|
||||||
if (!device) {
|
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(
|
FatalError(
|
||||||
"No audio devices found. Do you have speakers/headphones/etc. "
|
"No audio devices found. Do you have speakers/headphones/etc. "
|
||||||
"connected?");
|
"connected?");
|
||||||
}
|
}
|
||||||
impl_->alc_context = alcCreateContext(device, nullptr);
|
impl_->alc_context = alcCreateContext(device, nullptr);
|
||||||
if (!impl_->alc_context) {
|
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(
|
FatalError(
|
||||||
"Unable to init audio. Do you have speakers/headphones/etc. "
|
"Unable to init audio. Do you have speakers/headphones/etc. "
|
||||||
"connected?");
|
"connected?");
|
||||||
@ -253,6 +318,9 @@ void AudioServer::OnAppStartInThread_() {
|
|||||||
alEventControlSOFT = reinterpret_cast<LPALEVENTCONTROLSOFT>(
|
alEventControlSOFT = reinterpret_cast<LPALEVENTCONTROLSOFT>(
|
||||||
alcGetProcAddress(device, "alEventControlSOFT"));
|
alcGetProcAddress(device, "alEventControlSOFT"));
|
||||||
BA_PRECONDITION_FATAL(alEventControlSOFT != nullptr);
|
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.
|
// Ask to be notified when a device is disconnected.
|
||||||
alEventCallbackSOFT(ALEventCallback_, nullptr);
|
alEventCallbackSOFT(ALEventCallback_, nullptr);
|
||||||
|
|||||||
@ -29,9 +29,6 @@ class AudioServer {
|
|||||||
void PushSetVolumesCall(float music_volume, float sound_volume);
|
void PushSetVolumesCall(float music_volume, float sound_volume);
|
||||||
void PushSetSoundPitchCall(float val);
|
void PushSetSoundPitchCall(float val);
|
||||||
|
|
||||||
// static void BeginInterruption();
|
|
||||||
// static void EndInterruption();
|
|
||||||
|
|
||||||
void PushSetListenerPositionCall(const Vector3f& p);
|
void PushSetListenerPositionCall(const Vector3f& p);
|
||||||
void PushSetListenerOrientationCall(const Vector3f& forward,
|
void PushSetListenerOrientationCall(const Vector3f& forward,
|
||||||
const Vector3f& up);
|
const Vector3f& up);
|
||||||
@ -68,6 +65,7 @@ class AudioServer {
|
|||||||
auto event_loop() const -> EventLoop* { return event_loop_; }
|
auto event_loop() const -> EventLoop* { return event_loop_; }
|
||||||
|
|
||||||
void OnDeviceDisconnected();
|
void OnDeviceDisconnected();
|
||||||
|
void OpenALSoftLogCallback(const std::string& msg);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class ThreadSource_;
|
class ThreadSource_;
|
||||||
@ -132,6 +130,9 @@ class AudioServer {
|
|||||||
seconds_t last_started_playing_time_{};
|
seconds_t last_started_playing_time_{};
|
||||||
millisecs_t last_sound_fade_process_time_{};
|
millisecs_t last_sound_fade_process_time_{};
|
||||||
|
|
||||||
|
std::mutex openalsoft_log_mutex_;
|
||||||
|
std::string openalsoft_log_;
|
||||||
|
|
||||||
/// Indexed list of sources.
|
/// Indexed list of sources.
|
||||||
std::vector<ThreadSource_*> sources_;
|
std::vector<ThreadSource_*> sources_;
|
||||||
std::vector<ThreadSource_*> streaming_sources_;
|
std::vector<ThreadSource_*> streaming_sources_;
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
#include "ballistica/base/support/app_config.h"
|
#include "ballistica/base/support/app_config.h"
|
||||||
#include "ballistica/base/ui/dev_console.h"
|
#include "ballistica/base/ui/dev_console.h"
|
||||||
#include "ballistica/base/ui/ui.h"
|
#include "ballistica/base/ui/ui.h"
|
||||||
|
#include "ballistica/shared/generic/native_stack_trace.h"
|
||||||
#include "ballistica/shared/generic/utils.h"
|
#include "ballistica/shared/generic/utils.h"
|
||||||
|
|
||||||
namespace ballistica::base {
|
namespace ballistica::base {
|
||||||
@ -232,24 +233,6 @@ static PyMethodDef PyClipboardGetTextDef = {
|
|||||||
" this function.",
|
" 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 ---------------------------------
|
// ------------------------------ setup_sigint ---------------------------------
|
||||||
|
|
||||||
static auto PySetUpSigInt(PyObject* self) -> PyObject* {
|
static auto PySetUpSigInt(PyObject* self) -> PyObject* {
|
||||||
@ -273,27 +256,6 @@ static PyMethodDef PySetUpSigIntDef = {
|
|||||||
"(internal)",
|
"(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 --------------------------------
|
// ---------------------------- have_permission --------------------------------
|
||||||
|
|
||||||
static auto PyHavePermission(PyObject* self, PyObject* args, PyObject* keywds)
|
static auto PyHavePermission(PyObject* self, PyObject* args, PyObject* keywds)
|
||||||
@ -1369,7 +1331,7 @@ static PyMethodDef PyUnlockAllInputDef = {
|
|||||||
static auto PyNativeStackTrace(PyObject* self) -> PyObject* {
|
static auto PyNativeStackTrace(PyObject* self) -> PyObject* {
|
||||||
BA_PYTHON_TRY;
|
BA_PYTHON_TRY;
|
||||||
assert(g_core);
|
assert(g_core);
|
||||||
auto* trace = g_core->platform->GetStackTrace();
|
auto* trace = g_core->platform->GetNativeStackTrace();
|
||||||
if (!trace) {
|
if (!trace) {
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
@ -1814,6 +1776,35 @@ static PyMethodDef PyTempTestingDef = {
|
|||||||
"(internal)",
|
"(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> {
|
auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
|
||||||
@ -1859,8 +1850,6 @@ auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
|
|||||||
PyInLogicThreadDef,
|
PyInLogicThreadDef,
|
||||||
PyRequestPermissionDef,
|
PyRequestPermissionDef,
|
||||||
PyHavePermissionDef,
|
PyHavePermissionDef,
|
||||||
PyIsRunningOnFireTVDef,
|
|
||||||
PyIsRunningOnOuyaDef,
|
|
||||||
PyUnlockAllInputDef,
|
PyUnlockAllInputDef,
|
||||||
PyLockAllInputDef,
|
PyLockAllInputDef,
|
||||||
PySetUpSigIntDef,
|
PySetUpSigIntDef,
|
||||||
@ -1883,6 +1872,7 @@ auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
|
|||||||
PyNativeReviewRequestSupportedDef,
|
PyNativeReviewRequestSupportedDef,
|
||||||
PyNativeReviewRequestDef,
|
PyNativeReviewRequestDef,
|
||||||
PyTempTestingDef,
|
PyTempTestingDef,
|
||||||
|
PyOpenFileExternallyDef,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,8 +3,10 @@
|
|||||||
#include "ballistica/base/python/support/python_context_call.h"
|
#include "ballistica/base/python/support/python_context_call.h"
|
||||||
|
|
||||||
#include "ballistica/base/logic/logic.h"
|
#include "ballistica/base/logic/logic.h"
|
||||||
|
#include "ballistica/base/ui/ui.h"
|
||||||
#include "ballistica/core/python/core_python.h"
|
#include "ballistica/core/python/core_python.h"
|
||||||
#include "ballistica/shared/foundation/event_loop.h"
|
#include "ballistica/shared/foundation/event_loop.h"
|
||||||
|
#include "ballistica/shared/generic/lambda_runnable.h"
|
||||||
#include "ballistica/shared/generic/utils.h"
|
#include "ballistica/shared/generic/utils.h"
|
||||||
#include "ballistica/shared/python/python.h"
|
#include "ballistica/shared/python/python.h"
|
||||||
#include "ballistica/shared/python/python_sys.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
|
} // namespace ballistica::base
|
||||||
|
|||||||
@ -58,6 +58,28 @@ class PythonContextCall : public Object {
|
|||||||
/// scheduled run.
|
/// scheduled run.
|
||||||
void ScheduleWeak(const PythonRef& args);
|
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:
|
private:
|
||||||
void GetTrace(); // we try to grab basic trace info
|
void GetTrace(); // we try to grab basic trace info
|
||||||
|
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include "ballistica/base/ui/ui.h"
|
#include "ballistica/base/ui/ui.h"
|
||||||
|
|
||||||
|
#include <exception>
|
||||||
|
|
||||||
#include "ballistica/base/app_adapter/app_adapter.h"
|
#include "ballistica/base/app_adapter/app_adapter.h"
|
||||||
#include "ballistica/base/audio/audio.h"
|
#include "ballistica/base/audio/audio.h"
|
||||||
#include "ballistica/base/graphics/component/simple_component.h"
|
#include "ballistica/base/graphics/component/simple_component.h"
|
||||||
@ -12,12 +14,100 @@
|
|||||||
#include "ballistica/base/ui/dev_console.h"
|
#include "ballistica/base/ui/dev_console.h"
|
||||||
#include "ballistica/base/ui/ui_delegate.h"
|
#include "ballistica/base/ui/ui_delegate.h"
|
||||||
#include "ballistica/shared/foundation/event_loop.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"
|
#include "ballistica/shared/generic/utils.h"
|
||||||
|
|
||||||
namespace ballistica::base {
|
namespace ballistica::base {
|
||||||
|
|
||||||
static const int kUIOwnerTimeoutSeconds = 30;
|
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() {
|
UI::UI() {
|
||||||
assert(g_core);
|
assert(g_core);
|
||||||
|
|
||||||
@ -58,12 +148,6 @@ void UI::StepDisplayTime() {
|
|||||||
void UI::OnAppStart() {
|
void UI::OnAppStart() {
|
||||||
assert(g_base->InLogicThread());
|
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.
|
// Make sure user knows when forced-ui-scale is enabled.
|
||||||
if (force_scale_) {
|
if (force_scale_) {
|
||||||
if (scale_ == UIScale::kSmall) {
|
if (scale_ == UIScale::kSmall) {
|
||||||
@ -129,6 +213,8 @@ auto UI::PartyWindowOpen() -> bool {
|
|||||||
|
|
||||||
auto UI::HandleMouseDown(int button, float x, float y, bool double_click)
|
auto UI::HandleMouseDown(int button, float x, float y, bool double_click)
|
||||||
-> bool {
|
-> bool {
|
||||||
|
assert(g_base->InLogicThread());
|
||||||
|
|
||||||
bool handled{};
|
bool handled{};
|
||||||
|
|
||||||
// Dev console button.
|
// Dev console button.
|
||||||
@ -265,11 +351,20 @@ auto UI::ShouldShowButtonShortcuts() const -> bool {
|
|||||||
return g_base->input->have_non_touch_inputs();
|
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()) {
|
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() {
|
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
|
} // namespace ballistica::base
|
||||||
|
|||||||
@ -77,13 +77,22 @@ class UI {
|
|||||||
/// Draw dev UI on top.
|
/// Draw dev UI on top.
|
||||||
void DrawDev(FrameDef* frame_def);
|
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.
|
/// Return the widget an input-device should send commands to, if any.
|
||||||
/// Potentially assigns UI control to the provide device, so only call
|
/// Potentially assigns UI control to the provide device, so only call
|
||||||
/// this if you intend on actually sending a message to that widget.
|
/// this if you intend on actually sending a message to that widget.
|
||||||
auto GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget*;
|
auto GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget*;
|
||||||
|
|
||||||
/// Send a message to the active widget.
|
/// Send a message to the active widget. This is a high level call that
|
||||||
auto SendWidgetMessage(const WidgetMessage& msg) -> int;
|
/// should only be used by top level event handling/etc.
|
||||||
|
auto SendWidgetMessage(const WidgetMessage& msg) -> bool;
|
||||||
|
|
||||||
/// Set the device controlling the UI.
|
/// Set the device controlling the UI.
|
||||||
void SetUIInputDevice(InputDevice* input_device);
|
void SetUIInputDevice(InputDevice* input_device);
|
||||||
@ -123,12 +132,28 @@ class UI {
|
|||||||
|
|
||||||
auto* delegate() const { return delegate_; }
|
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:
|
private:
|
||||||
void MainMenuPress_(InputDevice* device);
|
void MainMenuPress_(InputDevice* device);
|
||||||
auto DevConsoleButtonSize_() const -> float;
|
auto DevConsoleButtonSize_() const -> float;
|
||||||
auto InDevConsoleButton_(float x, float y) const -> bool;
|
auto InDevConsoleButton_(float x, float y) const -> bool;
|
||||||
void DrawDevConsoleButton_(FrameDef* frame_def);
|
void DrawDevConsoleButton_(FrameDef* frame_def);
|
||||||
|
|
||||||
|
OperationContext* operation_context_{};
|
||||||
base::UIDelegateInterface* delegate_{};
|
base::UIDelegateInterface* delegate_{};
|
||||||
DevConsole* dev_console_{};
|
DevConsole* dev_console_{};
|
||||||
std::string dev_console_startup_messages_;
|
std::string dev_console_startup_messages_;
|
||||||
|
|||||||
@ -323,14 +323,14 @@ auto CorePlatformApple::HaveLeaderboard(const std::string& game,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void CorePlatformApple::ShowOnlineScoreUI(const std::string& show,
|
void CorePlatformApple::ShowGameServiceUI(const std::string& show,
|
||||||
const std::string& game,
|
const std::string& game,
|
||||||
const std::string& game_version) {
|
const std::string& game_version) {
|
||||||
#if BA_USE_GAME_CENTER
|
#if BA_USE_GAME_CENTER
|
||||||
BallisticaKit::GameCenterContext::showOnlineScoreUI(show, game, game_version);
|
BallisticaKit::GameCenterContext::showGameServiceUI(show, game, game_version);
|
||||||
// base::AppleUtils::ShowOnlineScoreUI(show, game, game_version);
|
// base::AppleUtils::ShowGameServiceUI(show, game, game_version);
|
||||||
#else
|
#else
|
||||||
CorePlatform::ShowOnlineScoreUI(show, game, game_version);
|
CorePlatform::ShowGameServiceUI(show, game, game_version);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -41,7 +41,7 @@ class CorePlatformApple : public CorePlatform {
|
|||||||
void ReportAchievement(const std::string& achievement) override;
|
void ReportAchievement(const std::string& achievement) override;
|
||||||
auto HaveLeaderboard(const std::string& game, const std::string& config)
|
auto HaveLeaderboard(const std::string& game, const std::string& config)
|
||||||
-> bool override;
|
-> 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;
|
const std::string& game_version) override;
|
||||||
void ResetAchievements() override;
|
void ResetAchievements() override;
|
||||||
auto IsOSPlayingMusic() -> bool override;
|
auto IsOSPlayingMusic() -> bool override;
|
||||||
|
|||||||
@ -9,10 +9,15 @@
|
|||||||
|
|
||||||
// Trying to avoid platform-specific headers here except for
|
// Trying to avoid platform-specific headers here except for
|
||||||
// a few mostly-cross-platform bits where its worth the mess.
|
// a few mostly-cross-platform bits where its worth the mess.
|
||||||
#if !BA_OSTYPE_WINDOWS
|
|
||||||
#if BA_ENABLE_EXECINFO_BACKTRACES
|
#if BA_ENABLE_EXECINFO_BACKTRACES
|
||||||
|
#if BA_OSTYPE_ANDROID
|
||||||
|
#include "ballistica/core/platform/android/execinfo.h"
|
||||||
|
#else
|
||||||
#include <execinfo.h>
|
#include <execinfo.h>
|
||||||
#endif
|
#endif // BA_OSTYPE_ANDROID
|
||||||
|
#endif // BA_ENABLE_EXECINFO_BACKTRACES
|
||||||
|
|
||||||
|
#if !BA_OSTYPE_WINDOWS
|
||||||
#include <cxxabi.h>
|
#include <cxxabi.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
@ -20,6 +25,7 @@
|
|||||||
#include "ballistica/core/platform/support/min_sdl.h"
|
#include "ballistica/core/platform/support/min_sdl.h"
|
||||||
#include "ballistica/core/support/base_soft.h"
|
#include "ballistica/core/support/base_soft.h"
|
||||||
#include "ballistica/shared/foundation/event_loop.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/generic/utils.h"
|
||||||
#include "ballistica/shared/networking/networking_sys.h"
|
#include "ballistica/shared/networking/networking_sys.h"
|
||||||
#include "ballistica/shared/python/python.h"
|
#include "ballistica/shared/python/python.h"
|
||||||
@ -689,10 +695,10 @@ auto CorePlatform::HaveLeaderboard(const std::string& game,
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CorePlatform::ShowOnlineScoreUI(const std::string& show,
|
void CorePlatform::ShowGameServiceUI(const std::string& show,
|
||||||
const std::string& game,
|
const std::string& game,
|
||||||
const std::string& game_version) {
|
const std::string& game_version) {
|
||||||
Log(LogLevel::kError, "FIXME: ShowOnlineScoreUI() unimplemented");
|
Log(LogLevel::kError, "FIXME: ShowGameServiceUI() unimplemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
void CorePlatform::AndroidSetResString(const std::string& res) {
|
void CorePlatform::AndroidSetResString(const std::string& res) {
|
||||||
@ -952,7 +958,7 @@ auto CorePlatform::GetSubplatformName() -> std::string {
|
|||||||
#if BA_ENABLE_EXECINFO_BACKTRACES
|
#if BA_ENABLE_EXECINFO_BACKTRACES
|
||||||
|
|
||||||
// Stack traces using the functionality in execinfo.h
|
// Stack traces using the functionality in execinfo.h
|
||||||
class PlatformStackTraceExecInfo : public PlatformStackTrace {
|
class NativeStackTraceExecInfo : public NativeStackTrace {
|
||||||
public:
|
public:
|
||||||
static constexpr int kMaxStackLevels = 64;
|
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
|
// construction but should do the bare minimum amount of work to store it. Any
|
||||||
// expensive operations such as symbolification should be deferred until
|
// expensive operations such as symbolification should be deferred until
|
||||||
// FormatForDisplay().
|
// FormatForDisplay().
|
||||||
PlatformStackTraceExecInfo() { nsize_ = backtrace(array_, kMaxStackLevels); }
|
NativeStackTraceExecInfo() { nsize_ = backtrace(array_, kMaxStackLevels); }
|
||||||
|
|
||||||
auto FormatForDisplay() noexcept -> std::string override {
|
auto FormatForDisplay() noexcept -> std::string override {
|
||||||
try {
|
try {
|
||||||
std::string s;
|
std::string s;
|
||||||
char** symbols = backtrace_symbols(array_, nsize_);
|
char** symbols = backtrace_symbols(array_, nsize_);
|
||||||
for (int i = 0; i < nsize_; i++) {
|
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) {
|
if (i < nsize_ - 1) {
|
||||||
s += "\n";
|
s += "\n";
|
||||||
}
|
}
|
||||||
@ -979,9 +994,9 @@ class PlatformStackTraceExecInfo : public PlatformStackTrace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto Copy() const noexcept -> PlatformStackTrace* override {
|
auto Copy() const noexcept -> NativeStackTrace* override {
|
||||||
try {
|
try {
|
||||||
auto s = new PlatformStackTraceExecInfo(*this);
|
auto s = new NativeStackTraceExecInfo(*this);
|
||||||
|
|
||||||
// Vanilla copy constructor should do the right thing here.
|
// Vanilla copy constructor should do the right thing here.
|
||||||
assert(s->nsize_ == nsize_
|
assert(s->nsize_ == nsize_
|
||||||
@ -999,11 +1014,11 @@ class PlatformStackTraceExecInfo : public PlatformStackTrace {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
auto CorePlatform::GetStackTrace() -> PlatformStackTrace* {
|
auto CorePlatform::GetNativeStackTrace() -> NativeStackTrace* {
|
||||||
// Our default handler here supports execinfo backtraces where available
|
// Our default handler here supports execinfo backtraces where available
|
||||||
// and gives nothing elsewhere.
|
// and gives nothing elsewhere.
|
||||||
#if BA_ENABLE_EXECINFO_BACKTRACES
|
#if BA_ENABLE_EXECINFO_BACKTRACES
|
||||||
return new PlatformStackTraceExecInfo();
|
return new NativeStackTraceExecInfo();
|
||||||
#else
|
#else
|
||||||
return nullptr;
|
return nullptr;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -281,7 +281,7 @@ class CorePlatform {
|
|||||||
virtual auto HaveLeaderboard(const std::string& game,
|
virtual auto HaveLeaderboard(const std::string& game,
|
||||||
const std::string& config) -> bool;
|
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,
|
||||||
const std::string& game_version);
|
const std::string& game_version);
|
||||||
virtual void ResetAchievements();
|
virtual void ResetAchievements();
|
||||||
@ -294,11 +294,11 @@ class CorePlatform {
|
|||||||
|
|
||||||
#pragma mark ERRORS & DEBUGGING ------------------------------------------------
|
#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
|
/// is up to the caller to call delete on the returned trace when done
|
||||||
/// with it. Platforms with no meaningful stack trace functionality can
|
/// with it. Platforms with no meaningful stack trace functionality can
|
||||||
/// return nullptr.
|
/// return nullptr.
|
||||||
virtual auto GetStackTrace() -> PlatformStackTrace*;
|
virtual auto GetNativeStackTrace() -> NativeStackTrace*;
|
||||||
|
|
||||||
/// Optionally override fatal error reporting. If true is returned, default
|
/// Optionally override fatal error reporting. If true is returned, default
|
||||||
/// fatal error reporting will not run.
|
/// fatal error reporting will not run.
|
||||||
@ -472,25 +472,6 @@ class CorePlatform {
|
|||||||
std::list<std::string> mac_music_app_playlists_;
|
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
|
} // namespace ballistica::core
|
||||||
|
|
||||||
#endif // BALLISTICA_CORE_PLATFORM_CORE_PLATFORM_H_
|
#endif // BALLISTICA_CORE_PLATFORM_CORE_PLATFORM_H_
|
||||||
|
|||||||
@ -40,6 +40,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "ballistica/shared/foundation/event_loop.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/generic/utils.h"
|
||||||
#include "ballistica/shared/networking/networking_sys.h"
|
#include "ballistica/shared/networking/networking_sys.h"
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ namespace ballistica::core {
|
|||||||
static const int kTraceMaxStackFrames{256};
|
static const int kTraceMaxStackFrames{256};
|
||||||
static const int kTraceMaxFunctionNameLength{1024};
|
static const int kTraceMaxFunctionNameLength{1024};
|
||||||
|
|
||||||
class WinStackTrace : public PlatformStackTrace {
|
class WinStackTrace : public NativeStackTrace {
|
||||||
public:
|
public:
|
||||||
explicit WinStackTrace(CorePlatformWindows* platform) : platform_{platform} {
|
explicit WinStackTrace(CorePlatformWindows* platform) : platform_{platform} {
|
||||||
number_of_frames_ =
|
number_of_frames_ =
|
||||||
@ -67,7 +68,7 @@ class WinStackTrace : public PlatformStackTrace {
|
|||||||
|
|
||||||
// Should return a copy of itself allocated via new() (or nullptr if not
|
// Should return a copy of itself allocated via new() (or nullptr if not
|
||||||
// possible).
|
// possible).
|
||||||
auto Copy() const noexcept -> PlatformStackTrace* override {
|
auto Copy() const noexcept -> NativeStackTrace* override {
|
||||||
try {
|
try {
|
||||||
auto s = new WinStackTrace(*this);
|
auto s = new WinStackTrace(*this);
|
||||||
|
|
||||||
@ -169,7 +170,7 @@ auto CorePlatformWindows::FormatWinStackTraceForDisplay(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto CorePlatformWindows::GetStackTrace() -> PlatformStackTrace* {
|
auto CorePlatformWindows::GetNativeStackTrace() -> NativeStackTrace* {
|
||||||
return new WinStackTrace(this);
|
return new WinStackTrace(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -21,7 +21,7 @@ class CorePlatformWindows : public CorePlatform {
|
|||||||
static auto UTF8Encode(const std::wstring& wstr) -> std::string;
|
static auto UTF8Encode(const std::wstring& wstr) -> std::string;
|
||||||
static auto UTF8Decode(const std::string& str) -> std::wstring;
|
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 GetDeviceV1AccountUUIDPrefix() -> std::string override { return "w"; }
|
||||||
auto GetDeviceUUIDInputs() -> std::list<std::string> override;
|
auto GetDeviceUUIDInputs() -> std::list<std::string> override;
|
||||||
auto GenerateUUID() -> std::string override;
|
auto GenerateUUID() -> std::string override;
|
||||||
|
|||||||
@ -287,7 +287,7 @@ void HostSession::RemovePlayer(Player* player) {
|
|||||||
return;
|
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) {
|
void HostSession::IssuePlayerLeft(Player* player) {
|
||||||
|
|||||||
@ -39,8 +39,8 @@ auto main(int argc, char** argv) -> int {
|
|||||||
namespace ballistica {
|
namespace ballistica {
|
||||||
|
|
||||||
// These are set automatically via script; don't modify them here.
|
// These are set automatically via script; don't modify them here.
|
||||||
const int kEngineBuildNumber = 21707;
|
const int kEngineBuildNumber = 21728;
|
||||||
const char* kEngineVersion = "1.7.31";
|
const char* kEngineVersion = "1.7.32";
|
||||||
const int kEngineApiVersion = 8;
|
const int kEngineApiVersion = 8;
|
||||||
|
|
||||||
#if BA_MONOLITHIC_BUILD
|
#if BA_MONOLITHIC_BUILD
|
||||||
@ -164,8 +164,9 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int {
|
|||||||
// with reports from dev builds.
|
// with reports from dev builds.
|
||||||
bool try_to_exit_cleanly = !(l_base && l_base->IsUnmodifiedBlessedBuild());
|
bool try_to_exit_cleanly = !(l_base && l_base->IsUnmodifiedBlessedBuild());
|
||||||
|
|
||||||
// If this returns true, it means the app is handling things (showing a
|
// If this returns true, it means the platform/app-adapter is handling
|
||||||
// fatal error dialog, etc.) and it's out of our hands.
|
// things (showing a fatal error dialog, etc.) and it's out of our
|
||||||
|
// hands.
|
||||||
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
|
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
|
||||||
|
|
||||||
// If it's not been handled, take the app down ourself.
|
// If it's not been handled, take the app down ourself.
|
||||||
@ -229,8 +230,9 @@ class IncrementalInitRunner_ {
|
|||||||
// with reports from dev builds.
|
// with reports from dev builds.
|
||||||
bool try_to_exit_cleanly = !(base_ && base_->IsUnmodifiedBlessedBuild());
|
bool try_to_exit_cleanly = !(base_ && base_->IsUnmodifiedBlessedBuild());
|
||||||
|
|
||||||
// If this returns true, it means the app is handling things (showing a
|
// If this returns true, it means the platform/app-adapter is handling
|
||||||
// fatal error dialog, etc.) and it's out of our hands.
|
// things (showing a fatal error dialog, etc.) and it's out of our
|
||||||
|
// hands.
|
||||||
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
|
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
|
||||||
|
|
||||||
// If it's not been handled, take the app down ourself.
|
// 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.
|
// report logs with reports from dev builds.
|
||||||
bool try_to_exit_cleanly =
|
bool try_to_exit_cleanly =
|
||||||
!(g_base_soft && g_base_soft->IsUnmodifiedBlessedBuild());
|
!(g_base_soft && g_base_soft->IsUnmodifiedBlessedBuild());
|
||||||
|
|
||||||
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
|
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
|
||||||
|
|
||||||
// Do the default thing if platform didn't handle it.
|
// Do the default thing if platform didn't handle it.
|
||||||
|
|||||||
@ -52,9 +52,9 @@ class EventLoop {
|
|||||||
Timer* GetTimer(int id);
|
Timer* GetTimer(int id);
|
||||||
void DeleteTimer(int id);
|
void DeleteTimer(int id);
|
||||||
|
|
||||||
/// Add a runnable to this thread's event-loop.
|
/// Add a runnable to this thread's event-loop. Pass a Runnable that has
|
||||||
/// Pass a Runnable that has been allocated with NewUnmanaged().
|
/// been allocated with NewUnmanaged(). It will be owned and disposed of
|
||||||
/// It will be owned and disposed of by the thread.
|
/// by the thread.
|
||||||
void PushRunnable(Runnable* runnable);
|
void PushRunnable(Runnable* runnable);
|
||||||
|
|
||||||
/// Convenience function to push a lambda as a runnable.
|
/// Convenience function to push a lambda as a runnable.
|
||||||
@ -63,7 +63,8 @@ class EventLoop {
|
|||||||
PushRunnable(NewLambdaRunnableUnmanaged(lambda));
|
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);
|
void PushRunnableSynchronous(Runnable* runnable);
|
||||||
|
|
||||||
/// Convenience function to push a lambda as a runnable.
|
/// Convenience function to push a lambda as a runnable.
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include "ballistica/core/core.h"
|
#include "ballistica/core/core.h"
|
||||||
#include "ballistica/core/platform/core_platform.h"
|
#include "ballistica/core/platform/core_platform.h"
|
||||||
|
#include "ballistica/shared/generic/native_stack_trace.h"
|
||||||
|
|
||||||
namespace ballistica {
|
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
|
// If core has been inited, attempt to capture a stack-trace here we
|
||||||
// can print out later if desired.
|
// can print out later if desired.
|
||||||
if (core::g_core) {
|
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
|
// If core has been inited, attempt to capture a stack-trace here we
|
||||||
// can print out later if desired.
|
// can print out later if desired.
|
||||||
if (core::g_core) {
|
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::runtime_error at a later time and also catches exceptions coming from
|
||||||
// std itself.
|
// std itself.
|
||||||
|
|
||||||
namespace core {
|
|
||||||
class PlatformStackTrace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a short description for an exception.
|
/// Get a short description for an exception.
|
||||||
/// By default, our Exception classes provide what() values that may include
|
/// By default, our Exception classes provide what() values that may include
|
||||||
/// backtraces of the throw location or other extended info that can be useful
|
/// 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 message_;
|
||||||
std::string full_description_;
|
std::string full_description_;
|
||||||
PyExcType python_type_;
|
PyExcType python_type_;
|
||||||
core::PlatformStackTrace* stack_trace_{};
|
NativeStackTrace* stack_trace_{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ballistica
|
} // namespace ballistica
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "ballistica/core/support/base_soft.h"
|
#include "ballistica/core/support/base_soft.h"
|
||||||
#include "ballistica/shared/foundation/logging.h"
|
#include "ballistica/shared/foundation/logging.h"
|
||||||
#include "ballistica/shared/generic/lambda_runnable.h"
|
#include "ballistica/shared/generic/lambda_runnable.h"
|
||||||
|
#include "ballistica/shared/generic/native_stack_trace.h"
|
||||||
#include "ballistica/shared/python/python.h"
|
#include "ballistica/shared/python/python.h"
|
||||||
|
|
||||||
namespace ballistica {
|
namespace ballistica {
|
||||||
@ -87,7 +88,7 @@ void FatalError::ReportFatalError(const std::string& message,
|
|||||||
// since we know where those are anyway.
|
// since we know where those are anyway.
|
||||||
if (!in_top_level_exception_handler) {
|
if (!in_top_level_exception_handler) {
|
||||||
if (g_core && g_core->platform) {
|
if (g_core && g_core->platform) {
|
||||||
core::PlatformStackTrace* trace{g_core->platform->GetStackTrace()};
|
NativeStackTrace* trace{g_core->platform->GetNativeStackTrace()};
|
||||||
if (trace) {
|
if (trace) {
|
||||||
std::string tracestr = trace->FormatForDisplay();
|
std::string tracestr = trace->FormatForDisplay();
|
||||||
if (!tracestr.empty()) {
|
if (!tracestr.empty()) {
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
#include "ballistica/core/core.h"
|
#include "ballistica/core/core.h"
|
||||||
#include "ballistica/core/platform/core_platform.h"
|
#include "ballistica/core/platform/core_platform.h"
|
||||||
|
#include "ballistica/shared/generic/native_stack_trace.h"
|
||||||
#include "ballistica/shared/python/python.h"
|
#include "ballistica/shared/python/python.h"
|
||||||
|
|
||||||
// Snippets of compiled functionality used by our evil macros.
|
// Snippets of compiled functionality used by our evil macros.
|
||||||
@ -35,7 +36,6 @@ void MacroFunctionTimerEndThread(core::CoreFeatureSet* corefs,
|
|||||||
if (g_buildconfig.test_build()) {
|
if (g_buildconfig.test_build()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// EW: Using core's internal globals directly; shouldn't do this.
|
|
||||||
assert(corefs);
|
assert(corefs);
|
||||||
millisecs_t endtime = corefs->platform->GetTicks();
|
millisecs_t endtime = corefs->platform->GetTicks();
|
||||||
if (endtime - starttime > time) {
|
if (endtime - starttime > time) {
|
||||||
@ -53,7 +53,6 @@ void MacroFunctionTimerEndEx(core::CoreFeatureSet* corefs,
|
|||||||
if (g_buildconfig.test_build()) {
|
if (g_buildconfig.test_build()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// EW: Using core's internal globals directly; shouldn't do this.
|
|
||||||
assert(corefs);
|
assert(corefs);
|
||||||
millisecs_t endtime = corefs->platform->GetTicks();
|
millisecs_t endtime = corefs->platform->GetTicks();
|
||||||
if (endtime - starttime > time) {
|
if (endtime - starttime > time) {
|
||||||
@ -72,7 +71,6 @@ void MacroFunctionTimerEndThreadEx(core::CoreFeatureSet* corefs,
|
|||||||
if (g_buildconfig.test_build()) {
|
if (g_buildconfig.test_build()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// EW: Using core's internal globals directly; shouldn't do this.
|
|
||||||
assert(corefs);
|
assert(corefs);
|
||||||
millisecs_t endtime = corefs->platform->GetTicks();
|
millisecs_t endtime = corefs->platform->GetTicks();
|
||||||
if (endtime - starttime > time) {
|
if (endtime - starttime > time) {
|
||||||
@ -91,7 +89,6 @@ void MacroTimeCheckEnd(core::CoreFeatureSet* corefs, millisecs_t starttime,
|
|||||||
if (g_buildconfig.test_build()) {
|
if (g_buildconfig.test_build()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// EW: Using core's internal globals directly; shouldn't do this.
|
|
||||||
assert(corefs);
|
assert(corefs);
|
||||||
millisecs_t e = corefs->platform->GetTicks();
|
millisecs_t e = corefs->platform->GetTicks();
|
||||||
if (e - starttime > time) {
|
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,
|
void MacroLogErrorNativeTrace(core::CoreFeatureSet* corefs,
|
||||||
const char* fname, int line) {
|
const std::string& msg, const char* fname,
|
||||||
|
int line) {
|
||||||
char buffer[2048];
|
char buffer[2048];
|
||||||
snprintf(buffer, sizeof(buffer), "%s:%d:", MacroPathFilter(corefs, fname),
|
snprintf(buffer, sizeof(buffer), "%s:%d:", MacroPathFilter(corefs, fname),
|
||||||
line);
|
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();
|
Python::PrintStackTrace();
|
||||||
Log(LogLevel::kError, std::string(buffer) + " error: " + msg);
|
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];
|
char e_buffer[2048];
|
||||||
snprintf(e_buffer, sizeof(e_buffer), "%s:%d:", MacroPathFilter(corefs, fname),
|
snprintf(e_buffer, sizeof(e_buffer), "%s:%d:", MacroPathFilter(corefs, fname),
|
||||||
line);
|
line);
|
||||||
e_buffer[sizeof(e_buffer) - 1] = 0;
|
|
||||||
Log(LogLevel::kError, std::string(e_buffer) + " error: " + msg);
|
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)
|
auto MacroPathFilter(core::CoreFeatureSet* corefs, const char* filename)
|
||||||
-> const char* {
|
-> 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);
|
assert(corefs);
|
||||||
if (corefs && !corefs->build_src_dir().empty()
|
if (corefs && !corefs->build_src_dir().empty()
|
||||||
&& strstr(filename, core::g_core->build_src_dir().c_str()) == filename) {
|
&& strstr(filename, core::g_core->build_src_dir().c_str()) == filename) {
|
||||||
|
|||||||
@ -75,19 +75,34 @@
|
|||||||
type(const type& foo) = delete; \
|
type(const type& foo) = delete; \
|
||||||
type& operator=(const type& src) = delete; /* NOLINT (macro parens) */
|
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
|
// Call this for errors which are non-fatal but should be noted so they can
|
||||||
// fixed.
|
// be fixed.
|
||||||
#define BA_LOG_ERROR_TRACE(msg) \
|
#define BA_LOG_ERROR_NATIVE_TRACE(msg) \
|
||||||
::ballistica::MacroLogErrorTrace(g_core, msg, __FILE__, __LINE__)
|
::ballistica::MacroLogErrorNativeTrace(g_core, msg, __FILE__, __LINE__)
|
||||||
|
|
||||||
#define BA_LOG_ERROR_TRACE_ONCE(msg) \
|
#define BA_LOG_ERROR_NATIVE_TRACE_ONCE(msg) \
|
||||||
{ \
|
{ \
|
||||||
static bool did_log_error_trace_here = false; \
|
static bool did_log_error_trace_here = false; \
|
||||||
if (!did_log_error_trace_here) { \
|
if (!did_log_error_trace_here) { \
|
||||||
::ballistica::MacroLogErrorTrace(g_core, msg, __FILE__, __LINE__); \
|
::ballistica::MacroLogErrorNativeTrace(g_core, msg, __FILE__, __LINE__); \
|
||||||
did_log_error_trace_here = true; \
|
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)
|
((void)0) // (see 'Trailing-semicolon note' at top)
|
||||||
|
|
||||||
#define BA_LOG_ONCE(lvl, msg) \
|
#define BA_LOG_ONCE(lvl, msg) \
|
||||||
@ -175,8 +190,12 @@ void MacroFunctionTimerEndThreadEx(core::CoreFeatureSet* corefs,
|
|||||||
void MacroTimeCheckEnd(core::CoreFeatureSet* corefs, millisecs_t starttime,
|
void MacroTimeCheckEnd(core::CoreFeatureSet* corefs, millisecs_t starttime,
|
||||||
millisecs_t time, const char* name, const char* file,
|
millisecs_t time, const char* name, const char* file,
|
||||||
int line);
|
int line);
|
||||||
void MacroLogErrorTrace(core::CoreFeatureSet* corefs, const std::string& msg,
|
void MacroLogErrorNativeTrace(core::CoreFeatureSet* corefs,
|
||||||
const char* fname, int line);
|
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,
|
void MacroLogError(core::CoreFeatureSet* corefs, const std::string& msg,
|
||||||
const char* fname, int line);
|
const char* fname, int line);
|
||||||
void MacroLogPythonTrace(core::CoreFeatureSet* corefs, const std::string& msg);
|
void MacroLogPythonTrace(core::CoreFeatureSet* corefs, const std::string& msg);
|
||||||
|
|||||||
@ -42,6 +42,7 @@ class EventLoop;
|
|||||||
class FeatureSetNativeComponent;
|
class FeatureSetNativeComponent;
|
||||||
class JsonDict;
|
class JsonDict;
|
||||||
class Matrix44f;
|
class Matrix44f;
|
||||||
|
class NativeStackTrace;
|
||||||
class Object;
|
class Object;
|
||||||
class Python;
|
class Python;
|
||||||
class PythonRef;
|
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"
|
"\n"
|
||||||
"This class represents a weak reference to a widget object\n"
|
"This class represents a weak reference to a widget object\n"
|
||||||
"in the internal C++ layer. Currently, functions such as\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"
|
"Attributes:\n"
|
||||||
" " ATTR_TRANSITIONING_OUT " (bool):\n"
|
" " ATTR_TRANSITIONING_OUT " (bool):\n"
|
||||||
" Whether this widget is in the process of dying (read only).\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));
|
assert(TypeIsSetUp(&type_obj));
|
||||||
auto* py_widget = reinterpret_cast<PythonClassWidget*>(
|
auto* py_widget = reinterpret_cast<PythonClassWidget*>(
|
||||||
PyObject_CallObject(reinterpret_cast<PyObject*>(&type_obj), nullptr));
|
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;
|
*py_widget->widget_ = widget;
|
||||||
|
|
||||||
@ -285,6 +287,10 @@ auto PythonClassWidget::Delete(PythonClassWidget* self, PyObject* args,
|
|||||||
args, keywds, "|i", const_cast<char**>(kwlist), &ignore_missing)) {
|
args, keywds, "|i", const_cast<char**>(kwlist), &ignore_missing)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Defer any user code triggered by selects/etc until the end.
|
||||||
|
base::UI::OperationContext ui_op_context;
|
||||||
|
|
||||||
Widget* w = self->widget_->Get();
|
Widget* w = self->widget_->Get();
|
||||||
if (!w) {
|
if (!w) {
|
||||||
if (!ignore_missing) {
|
if (!ignore_missing) {
|
||||||
@ -298,6 +304,9 @@ auto PythonClassWidget::Delete(PythonClassWidget* self, PyObject* args,
|
|||||||
Log(LogLevel::kError, "Can't delete widget: no parent.");
|
Log(LogLevel::kError, "Can't delete widget: no parent.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any user code that got triggered.
|
||||||
|
ui_op_context.Finish();
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
BA_PYTHON_CATCH;
|
BA_PYTHON_CATCH;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -87,35 +87,6 @@ static PyMethodDef PyGetTextureDef = {
|
|||||||
"Load a texture for use in the ui.",
|
"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 -------------------------------
|
// -------------------------- get_qrcode_texture -------------------------------
|
||||||
|
|
||||||
static auto PyGetQRCodeTexture(PyObject* self, PyObject* args, PyObject* keywds)
|
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.",
|
"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 ----------------------------------
|
// ----------------------------- buttonwidget ----------------------------------
|
||||||
|
|
||||||
static auto PyButtonWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
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.");
|
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.
|
// Grab the edited widget or create a new one.
|
||||||
Object::Ref<ButtonWidget> b;
|
Object::Ref<ButtonWidget> b;
|
||||||
if (edit_obj != Py_None) {
|
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);
|
g_ui_v1->AddWidget(b.Get(), parent_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
return b->NewPyRef();
|
return b->NewPyRef();
|
||||||
|
|
||||||
BA_PYTHON_CATCH;
|
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.");
|
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.
|
// Grab the edited widget or create a new one.
|
||||||
Object::Ref<CheckBoxWidget> widget;
|
Object::Ref<CheckBoxWidget> widget;
|
||||||
if (edit_obj != Py_None) {
|
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);
|
g_ui_v1->AddWidget(widget.Get(), parent_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
return widget->NewPyRef();
|
return widget->NewPyRef();
|
||||||
|
|
||||||
BA_PYTHON_CATCH;
|
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.");
|
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.
|
// Grab the edited widget or create a new one.
|
||||||
Object::Ref<ImageWidget> b;
|
Object::Ref<ImageWidget> b;
|
||||||
if (edit_obj != Py_None) {
|
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);
|
g_ui_v1->AddWidget(b.Get(), parent_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
return b->NewPyRef();
|
return b->NewPyRef();
|
||||||
BA_PYTHON_CATCH;
|
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.");
|
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.
|
// Grab the edited widget or create a new one.
|
||||||
Object::Ref<ColumnWidget> widget;
|
Object::Ref<ColumnWidget> widget;
|
||||||
if (edit_obj != Py_None) {
|
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));
|
widget->set_background(Python::GetPyBool(background_obj));
|
||||||
}
|
}
|
||||||
if (selected_child_obj != Py_None) {
|
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));
|
widget->SelectWidget(UIV1Python::GetPyWidget(selected_child_obj));
|
||||||
|
|
||||||
|
// Run any user code/etc.
|
||||||
|
operation_context.Finish();
|
||||||
}
|
}
|
||||||
if (visible_child_obj != Py_None) {
|
if (visible_child_obj != Py_None) {
|
||||||
widget->ShowWidget(UIV1Python::GetPyWidget(visible_child_obj));
|
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);
|
g_ui_v1->AddWidget(widget.Get(), parent_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
return widget->NewPyRef();
|
return widget->NewPyRef();
|
||||||
|
|
||||||
BA_PYTHON_CATCH;
|
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.");
|
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.
|
// Grab the edited widget or create a new one.
|
||||||
Object::Ref<ContainerWidget> widget;
|
Object::Ref<ContainerWidget> widget;
|
||||||
if (edit_obj != Py_None) {
|
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));
|
widget->SetRootSelectable(Python::GetPyBool(root_selectable_obj));
|
||||||
}
|
}
|
||||||
if (selected_child_obj != Py_None) {
|
if (selected_child_obj != Py_None) {
|
||||||
// special case: passing 0 implies deselect
|
// Special case: passing 0 implies deselect.
|
||||||
if (PyLong_Check(selected_child_obj)
|
if (PyLong_Check(selected_child_obj)
|
||||||
&& (PyLong_AsLong(selected_child_obj) == 0)) {
|
&& (PyLong_AsLong(selected_child_obj) == 0)) {
|
||||||
widget->SelectWidget(nullptr);
|
widget->SelectWidget(nullptr);
|
||||||
@ -1218,18 +1255,19 @@ static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
|
|
||||||
if (transition_obj != Py_None) {
|
if (transition_obj != Py_None) {
|
||||||
std::string t = Python::GetPyString(transition_obj);
|
std::string t = Python::GetPyString(transition_obj);
|
||||||
if (t == "in_left")
|
if (t == "in_left") {
|
||||||
widget->SetTransition(ContainerWidget::TransitionType::kInLeft);
|
widget->SetTransition(ContainerWidget::TransitionType::kInLeft);
|
||||||
else if (t == "in_right")
|
} else if (t == "in_right") {
|
||||||
widget->SetTransition(ContainerWidget::TransitionType::kInRight);
|
widget->SetTransition(ContainerWidget::TransitionType::kInRight);
|
||||||
else if (t == "out_left")
|
} else if (t == "out_left") {
|
||||||
widget->SetTransition(ContainerWidget::TransitionType::kOutLeft);
|
widget->SetTransition(ContainerWidget::TransitionType::kOutLeft);
|
||||||
else if (t == "out_right")
|
} else if (t == "out_right") {
|
||||||
widget->SetTransition(ContainerWidget::TransitionType::kOutRight);
|
widget->SetTransition(ContainerWidget::TransitionType::kOutRight);
|
||||||
else if (t == "in_scale")
|
} else if (t == "in_scale") {
|
||||||
widget->SetTransition(ContainerWidget::TransitionType::kInScale);
|
widget->SetTransition(ContainerWidget::TransitionType::kInScale);
|
||||||
else if (t == "out_scale")
|
} else if (t == "out_scale") {
|
||||||
widget->SetTransition(ContainerWidget::TransitionType::kOutScale);
|
widget->SetTransition(ContainerWidget::TransitionType::kOutScale);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cancel_button_obj != Py_None) {
|
if (cancel_button_obj != Py_None) {
|
||||||
@ -1301,6 +1339,10 @@ static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
widget->set_claims_outside_clicks(
|
widget->set_claims_outside_clicks(
|
||||||
Python::GetPyBool(claim_outside_clicks_obj));
|
Python::GetPyBool(claim_outside_clicks_obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
return widget->NewPyRef();
|
return widget->NewPyRef();
|
||||||
BA_PYTHON_CATCH;
|
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.");
|
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.
|
// Grab the edited widget or create a new one.
|
||||||
Object::Ref<RowWidget> widget;
|
Object::Ref<RowWidget> widget;
|
||||||
if (edit_obj != Py_None) {
|
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);
|
g_ui_v1->AddWidget(widget.Get(), parent_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
return widget->NewPyRef();
|
return widget->NewPyRef();
|
||||||
|
|
||||||
BA_PYTHON_CATCH;
|
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.");
|
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.
|
// Grab the edited widget or create a new one.
|
||||||
Object::Ref<ScrollWidget> widget;
|
Object::Ref<ScrollWidget> widget;
|
||||||
if (edit_obj != Py_None) {
|
if (edit_obj != Py_None) {
|
||||||
@ -1615,6 +1668,10 @@ static auto PyScrollWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
if (edit_obj == Py_None) {
|
if (edit_obj == Py_None) {
|
||||||
g_ui_v1->AddWidget(widget.Get(), parent_widget);
|
g_ui_v1->AddWidget(widget.Get(), parent_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
return widget->NewPyRef();
|
return widget->NewPyRef();
|
||||||
|
|
||||||
BA_PYTHON_CATCH;
|
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.");
|
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.
|
// Grab the edited widget or create a new one.
|
||||||
Object::Ref<HScrollWidget> widget;
|
Object::Ref<HScrollWidget> widget;
|
||||||
if (edit_obj != Py_None) {
|
if (edit_obj != Py_None) {
|
||||||
@ -1788,6 +1849,10 @@ static auto PyHScrollWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
if (edit_obj == Py_None) {
|
if (edit_obj == Py_None) {
|
||||||
g_ui_v1->AddWidget(widget.Get(), parent_widget);
|
g_ui_v1->AddWidget(widget.Get(), parent_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
return widget->NewPyRef();
|
return widget->NewPyRef();
|
||||||
|
|
||||||
BA_PYTHON_CATCH;
|
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.
|
// Grab the edited widget or create a new one.
|
||||||
Object::Ref<TextWidget> widget;
|
Object::Ref<TextWidget> widget;
|
||||||
|
|
||||||
|
// Handle query special cases first.
|
||||||
if (query_obj != Py_None) {
|
if (query_obj != Py_None) {
|
||||||
widget = dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(query_obj));
|
widget = dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(query_obj));
|
||||||
if (!widget.Exists()) {
|
if (!widget.Exists()) {
|
||||||
@ -1957,6 +2024,13 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
}
|
}
|
||||||
return PyUnicode_FromString(widget->description().c_str());
|
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) {
|
if (edit_obj != Py_None) {
|
||||||
widget = dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(edit_obj));
|
widget = dynamic_cast<TextWidget*>(UIV1Python::GetPyWidget(edit_obj));
|
||||||
if (!widget.Exists()) {
|
if (!widget.Exists()) {
|
||||||
@ -2142,6 +2216,10 @@ static auto PyTextWidget(PyObject* self, PyObject* args, PyObject* keywds)
|
|||||||
if (edit_obj == Py_None) {
|
if (edit_obj == Py_None) {
|
||||||
g_ui_v1->AddWidget(widget.Get(), parent_widget);
|
g_ui_v1->AddWidget(widget.Get(), parent_widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
return widget->NewPyRef();
|
return widget->NewPyRef();
|
||||||
|
|
||||||
BA_PYTHON_CATCH;
|
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.");
|
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;
|
Widget* widget = nullptr;
|
||||||
if (edit_obj != Py_None) {
|
if (edit_obj != Py_None) {
|
||||||
widget = UIV1Python::GetPyWidget(edit_obj);
|
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));
|
widget->set_auto_select(Python::GetPyBool(autoselect_obj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
BA_PYTHON_CATCH;
|
BA_PYTHON_CATCH;
|
||||||
}
|
}
|
||||||
@ -2356,135 +2441,6 @@ static PyMethodDef PyUIBoundsDef = {
|
|||||||
"center remains onscreen.",
|
"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 -------------------------
|
// --------------------- set_party_icon_always_visible -------------------------
|
||||||
|
|
||||||
static auto PySetPartyIconAlwaysVisible(PyObject* self, PyObject* args,
|
static auto PySetPartyIconAlwaysVisible(PyObject* self, PyObject* args,
|
||||||
@ -2658,74 +2614,6 @@ static PyMethodDef PyOpenURLDef = {
|
|||||||
"is True).",
|
"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 ------------------------------
|
// ------------------------ is_party_icon_visible ------------------------------
|
||||||
|
|
||||||
static auto PyIsPartyIconVisible(PyObject* self) -> PyObject* {
|
static auto PyIsPartyIconVisible(PyObject* self) -> PyObject* {
|
||||||
@ -2803,18 +2691,12 @@ static PyMethodDef PyIsAvailableDef = {
|
|||||||
|
|
||||||
auto PythonMethodsUIV1::GetMethods() -> std::vector<PyMethodDef> {
|
auto PythonMethodsUIV1::GetMethods() -> std::vector<PyMethodDef> {
|
||||||
return {
|
return {
|
||||||
PyGetQRCodeTextureDef,
|
|
||||||
PyIsPartyIconVisibleDef,
|
PyIsPartyIconVisibleDef,
|
||||||
PyConsolePrintDef,
|
|
||||||
PyOpenFileExternallyDef,
|
|
||||||
PyOpenURLDef,
|
PyOpenURLDef,
|
||||||
PyBackPressDef,
|
PyBackPressDef,
|
||||||
PyGetSpecialWidgetDef,
|
PyGetSpecialWidgetDef,
|
||||||
PySetPartyWindowOpenDef,
|
PySetPartyWindowOpenDef,
|
||||||
PySetPartyIconAlwaysVisibleDef,
|
PySetPartyIconAlwaysVisibleDef,
|
||||||
PyShowAdDef,
|
|
||||||
PyShowAd2Def,
|
|
||||||
PyShowOnlineScoreUIDef,
|
|
||||||
PyButtonWidgetDef,
|
PyButtonWidgetDef,
|
||||||
PyCheckBoxWidgetDef,
|
PyCheckBoxWidgetDef,
|
||||||
PyImageWidgetDef,
|
PyImageWidgetDef,
|
||||||
@ -2828,6 +2710,7 @@ auto PythonMethodsUIV1::GetMethods() -> std::vector<PyMethodDef> {
|
|||||||
PyUIBoundsDef,
|
PyUIBoundsDef,
|
||||||
PyGetSoundDef,
|
PyGetSoundDef,
|
||||||
PyGetTextureDef,
|
PyGetTextureDef,
|
||||||
|
PyGetQRCodeTextureDef,
|
||||||
PyGetMeshDef,
|
PyGetMeshDef,
|
||||||
PyToolbarTestDef,
|
PyToolbarTestDef,
|
||||||
PyIsAvailableDef,
|
PyIsAvailableDef,
|
||||||
|
|||||||
@ -110,28 +110,23 @@ void UIV1Python::InvokeStringEditor(PyObject* string_edit_adapter_instance) {
|
|||||||
base::ScopedSetContext ssc(nullptr);
|
base::ScopedSetContext ssc(nullptr);
|
||||||
g_base->audio->PlaySound(g_base->assets->SysSound(base::SysSoundID::kSwish));
|
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 args(Py_BuildValue("(O)", string_edit_adapter_instance),
|
||||||
PythonRef::kSteal);
|
PythonRef::kSteal);
|
||||||
Object::New<base::PythonContextCall>(
|
|
||||||
objs().Get(ObjID::kOnScreenKeyboardClass))
|
|
||||||
->Schedule(args);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UIV1Python::LaunchStringEditOld(TextWidget* w) {
|
auto context_call = Object::New<base::PythonContextCall>(
|
||||||
assert(g_base->InLogicThread());
|
objs().Get(ObjID::kOnScreenKeyboardClass));
|
||||||
BA_PRECONDITION(w);
|
|
||||||
|
|
||||||
base::ScopedSetContext ssc(nullptr);
|
// This is probably getting called from within UI handling, so we
|
||||||
g_base->audio->PlaySound(g_base->assets->SysSound(base::SysSoundID::kSwish));
|
// need to schedule things to run post-ui-traversal in that case.
|
||||||
|
if (g_base->ui->InUIOperation()) {
|
||||||
// Gotta run this in the next cycle.
|
context_call->ScheduleInUIOperation(args);
|
||||||
PythonRef args(Py_BuildValue("(Osi)", w->BorrowPyRef(),
|
} else {
|
||||||
w->description().c_str(), w->max_chars()),
|
// Otherwise just run immediately.
|
||||||
PythonRef::kSteal);
|
Log(LogLevel::kWarning,
|
||||||
Object::New<base::PythonContextCall>(
|
"UIV1Python::InvokeStringEditor running outside of UIInteraction; "
|
||||||
objs().Get(ObjID::kOnScreenKeyboardClass))
|
"unexpected.");
|
||||||
->Schedule(args);
|
context_call->Run(args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UIV1Python::InvokeQuitWindow(QuitType quit_type) {
|
void UIV1Python::InvokeQuitWindow(QuitType quit_type) {
|
||||||
|
|||||||
@ -17,7 +17,6 @@ class UIV1Python {
|
|||||||
void AddPythonClasses(PyObject* module);
|
void AddPythonClasses(PyObject* module);
|
||||||
void ImportPythonObjs();
|
void ImportPythonObjs();
|
||||||
|
|
||||||
void LaunchStringEditOld(TextWidget* w);
|
|
||||||
void InvokeStringEditor(PyObject* string_edit_adapter_instance);
|
void InvokeStringEditor(PyObject* string_edit_adapter_instance);
|
||||||
void HandleDeviceMenuPress(base::InputDevice* device);
|
void HandleDeviceMenuPress(base::InputDevice* device);
|
||||||
void ShowURL(const std::string& url);
|
void ShowURL(const std::string& url);
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include "ballistica/base/graphics/component/empty_component.h"
|
#include "ballistica/base/graphics/component/empty_component.h"
|
||||||
#include "ballistica/base/input/input.h"
|
#include "ballistica/base/input/input.h"
|
||||||
#include "ballistica/base/support/app_config.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/python/ui_v1_python.h"
|
||||||
#include "ballistica/ui_v1/support/root_ui.h"
|
#include "ballistica/ui_v1/support/root_ui.h"
|
||||||
#include "ballistica/ui_v1/widget/root_widget.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::DoShowURL(const std::string& url) { python->ShowURL(url); }
|
||||||
|
|
||||||
// void UIV1FeatureSet::DoQuitWindow() {
|
|
||||||
// g_ui_v1->python->objs().Get(UIV1Python::ObjID::kQuitWindowCall).Call();
|
|
||||||
// }
|
|
||||||
|
|
||||||
bool UIV1FeatureSet::MainMenuVisible() {
|
bool UIV1FeatureSet::MainMenuVisible() {
|
||||||
// We consider anything on our screen or overlay stacks to be a 'main menu'.
|
// We consider anything on our screen or overlay stacks to be a 'main menu'.
|
||||||
// Probably need a better name than 'main menu' though.
|
// Probably need a better name than 'main menu' though.
|
||||||
@ -296,7 +293,7 @@ UIV1FeatureSet::UILock::UILock(bool write) {
|
|||||||
assert(g_base->InLogicThread());
|
assert(g_base->InLogicThread());
|
||||||
|
|
||||||
if (write && g_ui_v1->ui_lock_count_ != 0) {
|
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_++;
|
g_ui_v1->ui_lock_count_++;
|
||||||
}
|
}
|
||||||
@ -304,7 +301,7 @@ UIV1FeatureSet::UILock::UILock(bool write) {
|
|||||||
UIV1FeatureSet::UILock::~UILock() {
|
UIV1FeatureSet::UILock::~UILock() {
|
||||||
g_ui_v1->ui_lock_count_--;
|
g_ui_v1->ui_lock_count_--;
|
||||||
if (g_ui_v1->ui_lock_count_ < 0) {
|
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;
|
g_ui_v1->ui_lock_count_ = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -54,10 +54,18 @@ void ButtonWidget::SetIcon(base::TextureAsset* val) { icon_ = val; }
|
|||||||
void ButtonWidget::OnRepeatTimerExpired() {
|
void ButtonWidget::OnRepeatTimerExpired() {
|
||||||
// Repeat our action unless we somehow lost focus but didn't get a mouse-up.
|
// Repeat our action unless we somehow lost focus but didn't get a mouse-up.
|
||||||
if (IsHierarchySelected() && pressed_) {
|
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);
|
DoActivate(true);
|
||||||
|
|
||||||
// Speed up repeats after the first.
|
// Speed up repeats after the first.
|
||||||
repeat_timer_->SetLength(0.150);
|
repeat_timer_->SetLength(0.150);
|
||||||
|
|
||||||
|
// Run any calls built up by UI callbacks.
|
||||||
|
ui_op_context.Finish();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
repeat_timer_.Clear();
|
repeat_timer_.Clear();
|
||||||
}
|
}
|
||||||
@ -560,9 +568,8 @@ void ButtonWidget::DoActivate(bool is_repeat) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (auto* call = on_activate_call_.Get()) {
|
if (auto* call = on_activate_call_.Get()) {
|
||||||
// Call this in the next cycle (don't want to risk mucking with UI from
|
// Schedule this to run immediately after any current UI traversal.
|
||||||
// within a UI loop.)
|
call->ScheduleInUIOperation();
|
||||||
call->ScheduleWeak();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -48,9 +48,9 @@ class ButtonWidget : public Widget {
|
|||||||
icon_color_alpha_ = a;
|
icon_color_alpha_ = a;
|
||||||
}
|
}
|
||||||
auto set_text_flatness(float f) { text_flatness_ = f; }
|
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; }
|
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);
|
void set_text(const std::string& text);
|
||||||
auto text() const -> std::string { return text_->text_raw(); }
|
auto text() const -> std::string { return text_->text_raw(); }
|
||||||
auto set_icon_type(IconType i) { icon_type_ = i; }
|
auto set_icon_type(IconType i) { icon_type_ = i; }
|
||||||
@ -91,37 +91,32 @@ class ButtonWidget : public Widget {
|
|||||||
bool color_set_ = false;
|
bool color_set_ = false;
|
||||||
void DoActivate(bool is_repeat = false);
|
void DoActivate(bool is_repeat = false);
|
||||||
auto GetMult(millisecs_t current_time) const -> float;
|
auto GetMult(millisecs_t current_time) const -> float;
|
||||||
IconType icon_type_ = IconType::kNone;
|
|
||||||
bool enabled_ = true;
|
IconType icon_type_{};
|
||||||
bool selectable_ = true;
|
Style style_{};
|
||||||
float icon_tint_ = 0.0f;
|
bool enabled_{true};
|
||||||
Style style_ = Style::kRegular;
|
bool selectable_{true};
|
||||||
bool sound_enabled_ = true;
|
bool sound_enabled_{true};
|
||||||
bool mouse_over_ = false;
|
bool mouse_over_{};
|
||||||
bool repeat_ = false;
|
bool repeat_{};
|
||||||
bool pressed_ = false;
|
bool pressed_{};
|
||||||
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};
|
|
||||||
millisecs_t last_activate_time_millisecs_{};
|
millisecs_t last_activate_time_millisecs_{};
|
||||||
millisecs_t birth_time_millisecs_{};
|
millisecs_t birth_time_millisecs_{};
|
||||||
millisecs_t transition_delay_{};
|
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 opacity_{1.0f};
|
||||||
float text_flatness_{0.5f};
|
float text_flatness_{0.5f};
|
||||||
float text_color_r_{0.75f};
|
float text_color_r_{0.75f};
|
||||||
@ -134,11 +129,17 @@ class ButtonWidget : public Widget {
|
|||||||
float tint2_color_red_{1.0f};
|
float tint2_color_red_{1.0f};
|
||||||
float tint2_color_green_{1.0f};
|
float tint2_color_green_{1.0f};
|
||||||
float tint2_color_blue_{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<TextWidget> text_;
|
||||||
Object::Ref<base::PythonContextCall> on_activate_call_;
|
Object::Ref<base::PythonContextCall> on_activate_call_;
|
||||||
// Object::Ref<AppTimerOld<ButtonWidget> > repeat_timer_;
|
|
||||||
Object::Ref<base::AppTimer> 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 args(Py_BuildValue("(O)", checked_ ? Py_True : Py_False),
|
||||||
PythonRef::kSteal);
|
PythonRef::kSteal);
|
||||||
|
|
||||||
// Call this in the next cycle (don't want to risk mucking with UI from
|
// Schedule this to run immediately after any current UI traversal.
|
||||||
// within a UI loop)
|
call->ScheduleInUIOperation(args);
|
||||||
call->ScheduleWeak(args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -346,9 +346,8 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
} else if (auto* call = on_cancel_call_.Get()) {
|
} else if (auto* call = on_cancel_call_.Get()) {
|
||||||
claimed = true;
|
claimed = true;
|
||||||
|
|
||||||
// Call this in the next cycle (don't wanna risk mucking with UI from
|
// Schedule this to run immediately after any current UI traversal.
|
||||||
// within a UI loop).
|
call->ScheduleInUIOperation();
|
||||||
call->ScheduleWeak();
|
|
||||||
} else {
|
} else {
|
||||||
OnCancelCustom();
|
OnCancelCustom();
|
||||||
}
|
}
|
||||||
@ -630,9 +629,8 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
|
|
||||||
// Call our outside-click callback if unclaimed.
|
// Call our outside-click callback if unclaimed.
|
||||||
if (!claimed && on_outside_click_call_.Exists()) {
|
if (!claimed && on_outside_click_call_.Exists()) {
|
||||||
// Call this in the next cycle (don't wanna risk mucking with UI from
|
// Schedule this to run immediately after any current UI traversal.
|
||||||
// within a UI loop).
|
on_outside_click_call_->ScheduleInUIOperation();
|
||||||
on_outside_click_call_->ScheduleWeak();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always claim if they want.
|
// Always claim if they want.
|
||||||
@ -1070,9 +1068,8 @@ void ContainerWidget::Activate() {
|
|||||||
last_activate_time_millisecs_ =
|
last_activate_time_millisecs_ =
|
||||||
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
|
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
|
||||||
if (auto* call = on_activate_call_.Get()) {
|
if (auto* call = on_activate_call_.Get()) {
|
||||||
// Call this in the next cycle (don't wanna risk mucking with UI from within
|
// Schedule this to run immediately after any current UI traversal.
|
||||||
// a UI loop).
|
call->ScheduleInUIOperation();
|
||||||
call->ScheduleWeak();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,11 +23,17 @@ void HScrollWidget::OnTouchDelayTimerExpired() {
|
|||||||
if (touch_held_) {
|
if (touch_held_) {
|
||||||
// Pass a mouse-down event if we haven't moved.
|
// Pass a mouse-down event if we haven't moved.
|
||||||
if (!touch_is_scrolling_ && !touch_down_sent_) {
|
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(
|
ContainerWidget::HandleMessage(base::WidgetMessage(
|
||||||
base::WidgetMessage::Type::kMouseDown, nullptr, touch_x_, touch_y_,
|
base::WidgetMessage::Type::kMouseDown, nullptr, touch_x_, touch_y_,
|
||||||
static_cast<float>(touch_held_click_count_)));
|
static_cast<float>(touch_held_click_count_)));
|
||||||
touch_down_sent_ = true;
|
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_) {
|
if (touch_held_) {
|
||||||
// Pass a mouse-down event if we haven't moved.
|
// Pass a mouse-down event if we haven't moved.
|
||||||
if (!touch_is_scrolling_ && !touch_down_sent_) {
|
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(
|
ContainerWidget::HandleMessage(base::WidgetMessage(
|
||||||
base::WidgetMessage::Type::kMouseDown, nullptr, touch_x_, touch_y_,
|
base::WidgetMessage::Type::kMouseDown, nullptr, touch_x_, touch_y_,
|
||||||
static_cast<float>(touch_held_click_count_)));
|
static_cast<float>(touch_held_click_count_)));
|
||||||
touch_down_sent_ = true;
|
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);
|
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
|
||||||
|
|
||||||
if (auto* call = on_activate_call_.Get()) {
|
if (auto* call = on_activate_call_.Get()) {
|
||||||
// Call this in the next cycle (don't wanna risk mucking with UI from
|
// Schedule this to run immediately after any current UI traversal.
|
||||||
// within a UI loop).
|
call->ScheduleInUIOperation();
|
||||||
call->ScheduleWeak();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bring up an editor if applicable.
|
// Bring up an editor if applicable.
|
||||||
@ -719,9 +718,8 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
|
|||||||
} else {
|
} else {
|
||||||
if (auto* call = on_return_press_call_.Get()) {
|
if (auto* call = on_return_press_call_.Get()) {
|
||||||
claimed = true;
|
claimed = true;
|
||||||
// Call this in the next cycle (don't wanna risk mucking with UI
|
// Schedule this to run immediately after any current UI traversal.
|
||||||
// from within a UI loop)
|
call->ScheduleInUIOperation();
|
||||||
call->ScheduleWeak();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -19,12 +19,10 @@ Widget::~Widget() {
|
|||||||
Py_DECREF(py_ref_);
|
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_;
|
auto on_delete_calls = on_delete_calls_;
|
||||||
for (auto&& i : 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) {
|
void Widget::SetSelected(bool s, SelectionCause cause) {
|
||||||
if (selected_ == s) return;
|
if (selected_ == s) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
selected_ = s;
|
selected_ = s;
|
||||||
if (selected_ && on_select_call_.Exists()) {
|
if (selected_ && on_select_call_.Exists()) {
|
||||||
// Call this in the next cycle (don't wanna risk mucking
|
// Schedule this to run immediately after any current UI traversal.
|
||||||
// with UI from within a UI loop).
|
on_select_call_->ScheduleInUIOperation();
|
||||||
on_select_call_->ScheduleWeak();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -31,6 +31,8 @@ def _build_dir(arch: str, mode: str) -> str:
|
|||||||
|
|
||||||
def build_openal(arch: str, mode: str) -> None:
|
def build_openal(arch: str, mode: str) -> None:
|
||||||
"""Do the thing."""
|
"""Do the thing."""
|
||||||
|
# pylint: disable=too-many-statements
|
||||||
|
# pylint: disable=too-many-locals
|
||||||
from efrotools import replace_exact
|
from efrotools import replace_exact
|
||||||
|
|
||||||
if arch not in ARCHS:
|
if arch not in ARCHS:
|
||||||
@ -39,7 +41,11 @@ def build_openal(arch: str, mode: str) -> None:
|
|||||||
if mode not in MODES:
|
if mode not in MODES:
|
||||||
raise CleanError(f"Invalid mode '{mode}'.")
|
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.
|
# Get ndk path.
|
||||||
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],
|
['git', 'clone', 'https://github.com/kcat/openal-soft.git', builddir],
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
# subprocess.run(['git', 'checkout', '1.23.1'], check=True, cwd=builddir)
|
|
||||||
subprocess.run(
|
subprocess.run(
|
||||||
['git', 'checkout', 'b81a270f6c1e795ca70d7684e0ccf35a19f247e2'],
|
[
|
||||||
|
'git',
|
||||||
|
'checkout',
|
||||||
|
# '1.23.1',
|
||||||
|
'1381a951bea78c67281a2e844e6db1dedbd5ed7c',
|
||||||
|
],
|
||||||
check=True,
|
check=True,
|
||||||
cwd=builddir,
|
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
|
# 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
|
# only the ones that it would send to stderr so I don't always have
|
||||||
# to worry about filtering.
|
# to worry about filtering.
|
||||||
loggingpath = f'{builddir}/core/logging.cpp'
|
if reduce_logs or reroute_logs:
|
||||||
with open(loggingpath, encoding='utf-8') as infile:
|
loggingpath = f'{builddir}/core/logging.cpp'
|
||||||
txt = infile.read()
|
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 = replace_exact(
|
||||||
txt,
|
txt,
|
||||||
' __android_log_print(android_severity(level),'
|
'ALC_API ALCenum ALC_APIENTRY'
|
||||||
' "openal", "%s", str);',
|
' alcGetError(ALCdevice *device) noexcept\n',
|
||||||
' // ericf tweak; only send logs to'
|
(
|
||||||
' android that we\'d send to stderr.\n'
|
'void (*alcCustomAndroidLogger)(int, const char*) = nullptr;\n'
|
||||||
' if (gLogLevel >= level) {\n'
|
'\n'
|
||||||
' __android_log_print(android_severity(level),'
|
'ALC_API void ALC_APIENTRY'
|
||||||
' "openal", "%s", str);\n'
|
' 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)
|
outfile.write(txt)
|
||||||
|
|
||||||
# Add a function to set a logging function so we can gather info
|
fpath = f'{builddir}/include/AL/alc.h'
|
||||||
# on AL fatal errors/etc.
|
with open(fpath, encoding='utf-8') as infile:
|
||||||
# fpath = f'{builddir}/alc/alc.cpp'
|
txt = infile.read()
|
||||||
# with open(fpath, encoding='utf-8') as infile:
|
txt = replace_exact(
|
||||||
# txt = infile.read()
|
txt,
|
||||||
# txt = replace_exact(
|
(
|
||||||
# txt,
|
'ALC_API ALCenum ALC_APIENTRY'
|
||||||
# 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device)\n',
|
' alcGetError(ALCdevice *device) ALC_API_NOEXCEPT;\n'
|
||||||
# (
|
),
|
||||||
# 'void (*alcDebugLogger)(const char*) = nullptr;\n'
|
(
|
||||||
# '\n'
|
'ALC_API ALCenum ALC_APIENTRY'
|
||||||
# 'ALC_API void ALC_APIENTRY'
|
' alcGetError(ALCdevice *device) ALC_API_NOEXCEPT;\n'
|
||||||
# ' alcSetDebugLogger(void (*fn)(const char*)) {\n'
|
'ALC_API void ALC_APIENTRY alcSetCustomAndroidLogger('
|
||||||
# ' alcDebugLogger = fn;\n'
|
'void (*fn)(int, const char*));\n'
|
||||||
# '}\n'
|
),
|
||||||
# '\n'
|
)
|
||||||
# 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device)\n'
|
with open(fpath, 'w', encoding='utf-8') as outfile:
|
||||||
# ),
|
outfile.write(txt)
|
||||||
# )
|
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
# 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'
|
fpath = f'{builddir}/core/except.h'
|
||||||
with open(fpath, encoding='utf-8') as infile:
|
with open(fpath, encoding='utf-8') as infile:
|
||||||
txt = infile.read()
|
txt = infile.read()
|
||||||
@ -151,18 +204,6 @@ def build_openal(arch: str, mode: str) -> None:
|
|||||||
txt = replace_exact(
|
txt = replace_exact(
|
||||||
txt, '#define START_API_FUNC try\n', '#define START_API_FUNC\n'
|
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:
|
with open(fpath, 'w', encoding='utf-8') as outfile:
|
||||||
outfile.write(txt)
|
outfile.write(txt)
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user