Merge branch 'efroemling:master' into master

This commit is contained in:
Era 2023-11-10 12:49:25 +03:30 committed by GitHub
commit 42e1fdd302
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
162 changed files with 4212 additions and 2925 deletions

136
.efrocachemap generated
View File

@ -421,41 +421,41 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
"build/assets/ba_data/data/langdata.json": "2a2c4783fddc4b24d07b4ce0d8a74393",
"build/assets/ba_data/data/languages/arabic.json": "295c559911fa251f401f8cdcad91c226",
"build/assets/ba_data/data/languages/belarussian.json": "e151808b6b4f6dc159cf55ee62adad3c",
"build/assets/ba_data/data/languages/chinese.json": "0a9d9534e7329d1e886adae6fdc007c4",
"build/assets/ba_data/data/langdata.json": "dca47b4976752e76e852f7a4be2c7aa3",
"build/assets/ba_data/data/languages/arabic.json": "e0001d8542c904bc3c98a174f256efd9",
"build/assets/ba_data/data/languages/belarussian.json": "7fe38341815ca6ff4d95224196e7a67e",
"build/assets/ba_data/data/languages/chinese.json": "a87b1ebef100d9cdce0d29123735e90c",
"build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078",
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
"build/assets/ba_data/data/languages/czech.json": "93c5fe0d884d95435da6c675f64e30e0",
"build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e",
"build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e",
"build/assets/ba_data/data/languages/english.json": "6fb6ec37e79064edb4b8864eabdd024d",
"build/assets/ba_data/data/languages/english.json": "fe32cbe79483153db283e363da039a7a",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
"build/assets/ba_data/data/languages/filipino.json": "2efdfb879135b196a272dd47fc2039a2",
"build/assets/ba_data/data/languages/french.json": "4e218dcd488fa63e7db5b4da2261b9e1",
"build/assets/ba_data/data/languages/filipino.json": "afbda3adf14555e1567ee63c32e340e7",
"build/assets/ba_data/data/languages/french.json": "49ff6d211537b8003b8241438dca661d",
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
"build/assets/ba_data/data/languages/gibberish.json": "63c6212c774622346f3ad0d87ff31e80",
"build/assets/ba_data/data/languages/gibberish.json": "00a9f70bccdfa407043e69a42e5b944c",
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
"build/assets/ba_data/data/languages/hindi.json": "8ea0c58a44a24edb131d0e53b074d1f6",
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
"build/assets/ba_data/data/languages/indonesian.json": "53961b1484a1831f32bec2cc2941e672",
"build/assets/ba_data/data/languages/italian.json": "58ecf53a963dbeca1bbf3605e5ab6a2f",
"build/assets/ba_data/data/languages/indonesian.json": "ba39cade3c965b2ddbee122880b6f0db",
"build/assets/ba_data/data/languages/italian.json": "b19a9f0b868a6c89e13a04609bf377dd",
"build/assets/ba_data/data/languages/korean.json": "ca1122a9ee551da3f75ae632012bd0e2",
"build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38",
"build/assets/ba_data/data/languages/persian.json": "a391d80ff58ea22926499e4b19d2c0d0",
"build/assets/ba_data/data/languages/polish.json": "7a4a6cb882cf90dad32e6607215525bf",
"build/assets/ba_data/data/languages/portuguese.json": "51e362956f89da3eec980f587c092253",
"build/assets/ba_data/data/languages/persian.json": "89f5c6f900317aaa20ec520e5fd65d32",
"build/assets/ba_data/data/languages/polish.json": "8bb8f7baa5efcf6cdd627140d209c2b2",
"build/assets/ba_data/data/languages/portuguese.json": "7374b6a5cadcf573ee4ab4a5b4bc8c0a",
"build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826",
"build/assets/ba_data/data/languages/russian.json": "561504cca28eb3204ac194950029e565",
"build/assets/ba_data/data/languages/russian.json": "9e068a3afdec43b207c83aedfb812e82",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef",
"build/assets/ba_data/data/languages/spanish.json": "5a4dbd505060dd02d634bd4de4d5faab",
"build/assets/ba_data/data/languages/swedish.json": "77d671f10613291ebf9c71da66f18a18",
"build/assets/ba_data/data/languages/tamil.json": "65ab7798d637fa62a703750179eeb723",
"build/assets/ba_data/data/languages/spanish.json": "0fca8361c6873e6445f0bea34a211b78",
"build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac",
"build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c",
"build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc",
"build/assets/ba_data/data/languages/turkish.json": "776d1a0c9ef2333a9110d93558ab19e2",
"build/assets/ba_data/data/languages/ukrainian.json": "f72eb51abfbbb56e27866895d7e947d2",
"build/assets/ba_data/data/languages/turkish.json": "c3674336e746bf7b23617a6ff296c69a",
"build/assets/ba_data/data/languages/ukrainian.json": "e5c861187c4c6db37d1a033f4ef3dd5a",
"build/assets/ba_data/data/languages/venetian.json": "9fe1a58d9e5dfb00f31ce3b2eb9993f4",
"build/assets/ba_data/data/languages/vietnamese.json": "921cd1e50f60fe3e101f246e172750ba",
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
@ -1524,9 +1524,9 @@
"build/assets/ba_data/textures/fontBig.ktx": "94b56c2488d6c9ebabfbbb740eca07dd",
"build/assets/ba_data/textures/fontBig.pvr": "dff3f6c04a8c7b0bb937001640b42c8d",
"build/assets/ba_data/textures/fontBig_preview.png": "f8b15cb04f0deca7774def335a72f053",
"build/assets/ba_data/textures/fontExtras.dds": "7ab11df1b3a3daa651dfad34219b89f5",
"build/assets/ba_data/textures/fontExtras.ktx": "30c3c8ca2cdf1209ff177017bb10f0a8",
"build/assets/ba_data/textures/fontExtras.pvr": "fd3b0bd902c30e4b7aa5fe00e1eec4be",
"build/assets/ba_data/textures/fontExtras.dds": "0a5a39028853c443cd88bc2492cb6ad9",
"build/assets/ba_data/textures/fontExtras.ktx": "5b14075ce3d1d29c6d5635602e2176d8",
"build/assets/ba_data/textures/fontExtras.pvr": "8cc68ca85ba327c20c45bad73b000d8c",
"build/assets/ba_data/textures/fontExtras2.dds": "18063a12912dadc9528afd90d1cf2369",
"build/assets/ba_data/textures/fontExtras2.ktx": "36da7f6cfbfb8d32fb14371de0a8f660",
"build/assets/ba_data/textures/fontExtras2.pvr": "7a4e8e64ac05313b1782fb5b958150d0",
@ -1539,7 +1539,7 @@
"build/assets/ba_data/textures/fontExtras4.ktx": "6d872ac15e2e874c1252f63b4584722b",
"build/assets/ba_data/textures/fontExtras4.pvr": "6a0a0a1a8bbbc3ee9d6b8b914e7aa697",
"build/assets/ba_data/textures/fontExtras4_preview.png": "363e2647621917b3821c9068267d2516",
"build/assets/ba_data/textures/fontExtras_preview.png": "9c9c58aff612e7b6386f3522c0b4f1f6",
"build/assets/ba_data/textures/fontExtras_preview.png": "b6503267cc15e9e2524f41fabd94e773",
"build/assets/ba_data/textures/fontSmall0.dds": "b30bfe5f9e436be7be8b5eae6e8490c3",
"build/assets/ba_data/textures/fontSmall0.ktx": "7e6058f37e6c5a4ea628f35b5f92c227",
"build/assets/ba_data/textures/fontSmall0.pvr": "c66e3d6aa1f7def83aaacd8a6c9185e5",
@ -4056,53 +4056,53 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "ad13d636bcb25150044a7644846b8a09",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "7c5df955611590ef491bf614fbd60179",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "ae38bd212ae64b51482a2ccb9c1cbfd3",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "d0bcee2dd5567719aa35667c5206dffc",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "eb3cd4f86175afcf8ffa2749afa32fa3",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "6b86ba36c3719773008feaa6cdc0d0f8",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "877c9ae4532fef809a3dcbd8ffea343c",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "7df313c48c87460f56fa837502965088",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "bc40bb549d26437fb8679c1e9d088272",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "47aa08cb9f5e660023f0f3c0e4ffd65e",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "bbb0a8383d6ce1ca887190ea49223f4f",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "7dd91e3407d49981c1c975d4f01ac205",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "c87883aa2f832e792e945fd9208d712a",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "fea8fd84d8c060f2f82f402902b8c54e",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "0a454a8be47f37231655761d15e3f7e5",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "4c79db3a882eb0b8b225a8df0339b1cc",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "43b9ef321f8e80da29ddb19a760dbd77",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "6f891004f2f07c452dea29bd53f29d30",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "bf7a1ce0e7a2015d538406c6f6df761c",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "cf213dce81901a67c9970b3befdaa320",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "473e7e6c0cf90b9e6ac653552b18f68d",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "4e11b895cbf2e1339cf34bc06c54a4ea",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "473e7e6c0cf90b9e6ac653552b18f68d",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "4e11b895cbf2e1339cf34bc06c54a4ea",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "d9af1a429cff9346e0cad6fcea017e5b",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "ae5f87286947575463c386cfe1c443e4",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "d9af1a429cff9346e0cad6fcea017e5b",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "ae5f87286947575463c386cfe1c443e4",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "110eef3dc285a35a1899510e368c73b1",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "2692dc69f7cb2501f0aaa8675f559987",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "110eef3dc285a35a1899510e368c73b1",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "2692dc69f7cb2501f0aaa8675f559987",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "344954c4f788d7d9b4d7035ebb6131d8",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "48c4873dae2344c1d4092a1d85dab424",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "abcede4e60fa8877f18e66e086fb7387",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "48c4873dae2344c1d4092a1d85dab424",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "6149911c660a9864b651cc1a8e50eec1",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "57cef68ab703ba819bd0fbe9e4b1c331",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a47bab28b86c7cefce891b8e5c8b687a",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "d68ebb1139363d711b044de65e17b204",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ea1349137f64f3d662b9a95278ca4c02",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c8731ff226716cee3d1e46027ead1cfe",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4ee6b633a99c5bcbea4f5dee5bda186e",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "2c3bd4952b30d88247229ad309f73092",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "a2c1b1a31a42a24e5f2188983ff97c0e",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "8d146105a6df3376ec2f575401f29ec2",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "5e0e63f5fea108212bfb35c4fdc05dd2",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "85797e98f9a21f168a986b7eb3219688",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "95bc2213d2fb30e8248ddf06764a3663",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "f09d7055120d50f37b605ff3a4cbf61d",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "8f23de3825b9e9cf6ed4715125aeb956",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "f7b88875b37a39db57d77bebb05f822f",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "0b719dc609de364814299a5baa9a5b0b",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "07aa53835bdd2d2a4113d61a89a7da1d",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "1a1c4db580157e38effe4b81c35aca52",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "1e44d0a7160d442b1eb4d348952b0d06",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "a85fcf6a12483bde66559dbdce88b730",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "d53e31771fe31615c66bd0d1e1292b2f",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "e1804380d5ebec56f8f0af2e2547316e",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "d9665cdf870247b6d8e9475419b04038",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "0cac36823713816d03a6922b96cecd0a",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "bd741122315ced7b703de1fed2d372cd",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "f11edd5da3ce1a3e804bfc61b7ca9c1e",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "3d647f5f63b3ac3900903cf72496cef2",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "f812111f1eb4ea76e99558305ce3980f",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "c16c1b360b27850f1ac4d3dc55194b0a",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "f812111f1eb4ea76e99558305ce3980f",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "c16c1b360b27850f1ac4d3dc55194b0a",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "473c84d44b472d3ae73df6a2f1b56839",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "970c415cbb7a3087a163fa6e96e634a1",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "473c84d44b472d3ae73df6a2f1b56839",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "970c415cbb7a3087a163fa6e96e634a1",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "86498db547380838a1c95b45ef17d28b",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "bf11ae92718ae3dd5116d623799dd27f",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "86498db547380838a1c95b45ef17d28b",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "bf11ae92718ae3dd5116d623799dd27f",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "147c5a5210a55978060381cc44a122db",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "380d456bc409849174a36c5d2f61aa1b",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "4fae9b286a6d1c5c729c49c483c1422a",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "380d456bc409849174a36c5d2f61aa1b",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "93f75d31841fe18d6bc66681c99b05a1",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "cd6500ab076fa3bdff92fc8b65bc1d64",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "0dcde1146f11e4cfd3d609f4330f7a9a",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "663e6ed62de0f857fd9a4de57067dbd3",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ae28db2f400d3b778d4a011a72339757",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "3b13cdbaf70cf43d0a14b7a2dd744945",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "54798340a1dfedaa9052aeee180e28ff",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "18b635d88c074261a3be4f45e8244340",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "6df0f34207346d89a72924249ddd4706",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "00f81f9bd92386ec12a6e60170678a98",
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "3ceb412513963f0818ab39c58bf292e3",
"src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69",

View File

@ -1,4 +1,4 @@
### 1.7.28 (build 21491, api 8, 2023-10-22)
### 1.7.28 (build 21583, api 8, 2023-11-09)
- Massively cleaned up code related to rendering and window systems (OpenGL,
SDL, etc). This code had been growing into a nasty tangle for 15 years
@ -6,7 +6,7 @@
huge chunks of it and put back still-relevant pieces in a much more cleanly
designed way. This should put us in a much better place for supporting various
platforms and making graphical improvements going forward.
`ballistica/base/app_adapter/app_adapter_sdl.cc` for an example of the now
`ballistica/base/app_adapter/app_adapter_sdl.cc` is an example of the now
nicely implemented system.
- The engine now requires OpenGL 3.0 or newer on desktop and OpenGL ES 3.0 or
newer on mobile. This means we're cutting off a few percent of old devices on
@ -154,6 +154,57 @@
seems pretty awesome these days. It should support most stuff SDL does and
with less configuring involved. Please holler if you come across something
that doesn't work.
- Mac build is also now using the Game Controller Framework to handle keyboard
events. This should better handle things like modifier keys and also will
allow us to use that exact same code on the iPad/iPhone version.
- OS key repeat events are no longer passed through the engine. This means that
any time we want repeating behavior, such as holding an arrow key to move
through UI elements, we will need to wire it up ourselves. We already do this
for things like game controllers however, so this is more consistent in a way.
- Dev console no longer claims key events unless the Python tab is showing and
there is a hardware keyboard attached. This allows showing dev console tabs
above gameplay without interfering with it.
- Added clipboard paste support to the dev console python terminal.
- Added various text editing functionality to the dev console python terminal
(cursor movement, deleting chars and words, etc.)
- Internal on-screen-keyboard now has a cancel button (thanks vishal332008!)
- Public servers list now shows 'No servers found' if there are no servers to
show instead of just remaining mysteriously blank (thanks vishal332008!)
- Players are now prevented from rejoining a session for 10 seconds after they
leave to prevent game exploits. Note this is different than the existing
system that prevents joining a *party* for 10 seconds; this covers people
who never leave the party (Thanks EraOSBeta!).
- Fixes an issue where servers could be crashed by flooding them with join
requests (Thanks for the heads-up Era!).
- The engine will now ignore empty device config dicts and fall back to
defaults; these could theoretically happen if device config code fails
somewhere and it previously would leave the device mysteriously inoperable.
- The game will now show <unset> for controls with no bindings in the in-game
guide and controller/keyboard config screens.
- Fixed a crash that could occur if SDL couldn't find a name for connected
joystick.
- Simplified the app's handling of broken config files. Previously it would do
various complex things such as offering to edit the broken config on desktop
builds, avoiding overwriting broken configs, and automatically loading
previous configs. Now, if it finds a broken config, it will simply back it up
to a .broken file, log an error message, and then start up normally with a
default config. This way, things are more consistent across platforms, and
technical users can still fix and restore their old configs. Note that the app
still also writes .prev configs for extra security, though it no longer uses
them for anything itself.
- Converted more internal engine time values from milliseconds to microseconds,
including things like the internal EventLoop timeline. Please holler if you
notice anything running 1000x too fast or slow. In general my strategy going
forward is to use microseconds for exact internal time values but to mostly
expose float seconds to the user, especially on the Python layer. There were
starting to be a few cases were integer milliseconds was not enough precision
for internal values. For instance, if we run with unclamped framerates and hit
several hundred FPS, milliseconds per frame would drop to 0 which caused some
problems. Note that scenev1 will be remaining on milliseconds internally for
compatibility reasons. Scenev2 should move to microseconds though.
- The V2 account id for the signed in account is now available at
`ba*.app.plus.accounts.primary.accountid` (alongside some other existing
account info).
### 1.7.27 (build 21282, api 8, 2023-08-30)

View File

@ -43,6 +43,10 @@
### Era0S
- Bug Fixer
- Modder
- Added a feature
### VinniTR
- Fixes
### Rikko
- Created the original "reject_recently_left_players" plugin

View File

@ -360,6 +360,8 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/base/graphics/support/net_graph.cc
${BA_SRC_ROOT}/ballistica/base/graphics/support/net_graph.h
${BA_SRC_ROOT}/ballistica/base/graphics/support/render_command_buffer.h
${BA_SRC_ROOT}/ballistica/base/graphics/support/screen_messages.cc
${BA_SRC_ROOT}/ballistica/base/graphics/support/screen_messages.h
${BA_SRC_ROOT}/ballistica/base/graphics/text/font_page_map_data.h
${BA_SRC_ROOT}/ballistica/base/graphics/text/text_graphics.cc
${BA_SRC_ROOT}/ballistica/base/graphics/text/text_graphics.h
@ -436,16 +438,19 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/base/support/app_config.cc
${BA_SRC_ROOT}/ballistica/base/support/app_config.h
${BA_SRC_ROOT}/ballistica/base/support/app_timer.h
${BA_SRC_ROOT}/ballistica/base/support/base_build_switches.cc
${BA_SRC_ROOT}/ballistica/base/support/base_build_switches.h
${BA_SRC_ROOT}/ballistica/base/support/classic_soft.h
${BA_SRC_ROOT}/ballistica/base/support/context.cc
${BA_SRC_ROOT}/ballistica/base/support/context.h
${BA_SRC_ROOT}/ballistica/base/support/display_timer.h
${BA_SRC_ROOT}/ballistica/base/support/huffman.cc
${BA_SRC_ROOT}/ballistica/base/support/huffman.h
${BA_SRC_ROOT}/ballistica/base/support/plus_soft.h
${BA_SRC_ROOT}/ballistica/base/support/repeater.cc
${BA_SRC_ROOT}/ballistica/base/support/repeater.h
${BA_SRC_ROOT}/ballistica/base/support/stdio_console.cc
${BA_SRC_ROOT}/ballistica/base/support/stdio_console.h
${BA_SRC_ROOT}/ballistica/base/support/stress_test.cc
${BA_SRC_ROOT}/ballistica/base/support/stress_test.h
${BA_SRC_ROOT}/ballistica/base/ui/dev_console.cc
${BA_SRC_ROOT}/ballistica/base/ui/dev_console.h
${BA_SRC_ROOT}/ballistica/base/ui/ui.cc
@ -458,6 +463,8 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/classic/python/classic_python.h
${BA_SRC_ROOT}/ballistica/classic/python/methods/python_methods_classic.cc
${BA_SRC_ROOT}/ballistica/classic/python/methods/python_methods_classic.h
${BA_SRC_ROOT}/ballistica/classic/support/stress_test.cc
${BA_SRC_ROOT}/ballistica/classic/support/stress_test.h
${BA_SRC_ROOT}/ballistica/classic/support/v1_account.cc
${BA_SRC_ROOT}/ballistica/classic/support/v1_account.h
${BA_SRC_ROOT}/ballistica/core/core.cc

View File

@ -352,6 +352,8 @@
<ClCompile Include="..\..\src\ballistica\base\graphics\support\net_graph.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\net_graph.h" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h" />
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\text\text_graphics.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\text\text_graphics.h" />
@ -428,16 +430,19 @@
<ClCompile Include="..\..\src\ballistica\base\support\app_config.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\app_config.h" />
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h" />
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h" />
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\context.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\context.h" />
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h" />
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\huffman.h" />
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
@ -450,6 +455,8 @@
<ClInclude Include="..\..\src\ballistica\classic\python\classic_python.h" />
<ClCompile Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.cc" />
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\v1_account.h" />
<ClCompile Include="..\..\src\ballistica\core\core.cc" />

View File

@ -490,6 +490,12 @@
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h">
<Filter>ballistica\base\graphics\text</Filter>
</ClInclude>
@ -718,6 +724,12 @@
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
@ -727,6 +739,9 @@
<ClInclude Include="..\..\src\ballistica\base\support\context.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
@ -736,18 +751,18 @@
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
<Filter>ballistica\base\ui</Filter>
</ClCompile>
@ -784,6 +799,12 @@
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h">
<Filter>ballistica\classic\python\methods</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h">
<Filter>ballistica\classic\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>

View File

@ -347,6 +347,8 @@
<ClCompile Include="..\..\src\ballistica\base\graphics\support\net_graph.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\net_graph.h" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h" />
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h" />
<ClCompile Include="..\..\src\ballistica\base\graphics\text\text_graphics.cc" />
<ClInclude Include="..\..\src\ballistica\base\graphics\text\text_graphics.h" />
@ -423,16 +425,19 @@
<ClCompile Include="..\..\src\ballistica\base\support\app_config.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\app_config.h" />
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h" />
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h" />
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\context.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\context.h" />
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h" />
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\huffman.h" />
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
@ -445,6 +450,8 @@
<ClInclude Include="..\..\src\ballistica\classic\python\classic_python.h" />
<ClCompile Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.cc" />
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\v1_account.h" />
<ClCompile Include="..\..\src\ballistica\core\core.cc" />

View File

@ -490,6 +490,12 @@
<ClInclude Include="..\..\src\ballistica\base\graphics\support\render_command_buffer.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\graphics\support\screen_messages.cc">
<Filter>ballistica\base\graphics\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\graphics\support\screen_messages.h">
<Filter>ballistica\base\graphics\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\graphics\text\font_page_map_data.h">
<Filter>ballistica\base\graphics\text</Filter>
</ClInclude>
@ -718,6 +724,12 @@
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\base_build_switches.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\base_build_switches.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
@ -727,6 +739,9 @@
<ClInclude Include="..\..\src\ballistica\base\support\context.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\display_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
@ -736,18 +751,18 @@
<ClInclude Include="..\..\src\ballistica\base\support\plus_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\repeater.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\repeater.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stdio_console.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
<Filter>ballistica\base\ui</Filter>
</ClCompile>
@ -784,6 +799,12 @@
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h">
<Filter>ballistica\classic\python\methods</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h">
<Filter>ballistica\classic\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>

View File

@ -10,7 +10,8 @@
"src/ballistica/base/graphics/texture/ktx.cc",
"src/ballistica/core/platform/android/android_gl3.h",
"src/ballistica/base/platform/apple/app_delegate.h",
"src/ballistica/base/platform/apple/scripting_bridge_music.h",
"src/ballistica/base/platform/apple/MacMusicApp.h",
"src/ballistica/base/platform/apple/MacMusicAppScriptingBridge.h",
"src/ballistica/core/platform/android/utf8/checked.h",
"src/ballistica/core/platform/android/utf8/unchecked.h",
"src/ballistica/core/platform/android/utf8/core.h",

View File

@ -103,6 +103,7 @@ ctx.src_unchecked_paths = {
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/*/*.png',
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/*/*/*.png',
'ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/*/*/*/*/*.png',
'ballisticakit-xcode/BallisticaKit.xcodeproj/xcuserdata',
'ballisticakit-android/BallisticaKit/src/*/res/*/*.png',
'ballisticakit-android/BallisticaKit/src/*/assets/ballistica_files',
'ballisticakit-android/local.properties',
@ -274,6 +275,7 @@ ctx.filter_file_extensions = {
'.frag',
'.vert',
'.xcsettings',
'.xcstrings',
'.filters',
}

View File

@ -366,7 +366,6 @@
"ba_data/python/bauiv1lib/__pycache__/characterpicker.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/colorpicker.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/config.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/configerror.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/confirm.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/continues.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/creditslist.cpython-311.opt-1.pyc",
@ -418,7 +417,6 @@
"ba_data/python/bauiv1lib/characterpicker.py",
"ba_data/python/bauiv1lib/colorpicker.py",
"ba_data/python/bauiv1lib/config.py",
"ba_data/python/bauiv1lib/configerror.py",
"ba_data/python/bauiv1lib/confirm.py",
"ba_data/python/bauiv1lib/continues.py",
"ba_data/python/bauiv1lib/coop/__init__.py",

View File

@ -341,7 +341,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/characterpicker.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/colorpicker.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/config.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/configerror.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/confirm.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/continues.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__init__.py \
@ -616,7 +615,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/characterpicker.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/colorpicker.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/config.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/configerror.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/confirm.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/continues.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__pycache__/__init__.cpython-311.opt-1.pyc \

View File

@ -64,7 +64,6 @@ from _babase import (
is_running_on_fire_tv,
is_xcode_build,
lock_all_input,
mac_music_app_get_library_source,
mac_music_app_get_playlists,
mac_music_app_get_volume,
mac_music_app_init,
@ -75,6 +74,8 @@ from _babase import (
music_player_set_volume,
music_player_shutdown,
music_player_stop,
native_review_request,
native_review_request_supported,
native_stack_trace,
print_load_info,
pushcall,
@ -85,7 +86,6 @@ from _babase import (
screenmessage,
set_analytics_screen,
set_low_level_config_value,
set_stress_testing,
set_thread_name,
set_ui_input_device,
show_progress_bar,
@ -266,7 +266,6 @@ __all__ = [
'lock_all_input',
'LoginAdapter',
'Lstr',
'mac_music_app_get_library_source',
'mac_music_app_get_playlists',
'mac_music_app_get_volume',
'mac_music_app_init',
@ -279,6 +278,8 @@ __all__ = [
'music_player_set_volume',
'music_player_shutdown',
'music_player_stop',
'native_review_request',
'native_review_request_supported',
'native_stack_trace',
'NodeNotFoundError',
'normalized_color',
@ -303,7 +304,6 @@ __all__ = [
'SessionTeamNotFoundError',
'set_analytics_screen',
'set_low_level_config_value',
'set_stress_testing',
'set_thread_name',
'set_ui_input_device',
'show_progress_bar',

View File

@ -31,6 +31,8 @@ class AccountV2Subsystem:
"""
def __init__(self) -> None:
from babase._login import LoginAdapterGPGS, LoginAdapterGameCenter
# Whether or not everything related to an initial login
# (or lack thereof) has completed. This includes things like
# workspace syncing. Completion of this is what flips the app
@ -45,16 +47,13 @@ class AccountV2Subsystem:
self._implicit_state_changed = False
self._can_do_auto_sign_in = True
if _babase.app.classic is None:
raise RuntimeError('Needs updating for no-classic case.')
if (
_babase.app.classic.platform == 'android'
and _babase.app.classic.subplatform == 'google'
):
from babase._login import LoginAdapterGPGS
self.login_adapters[LoginType.GPGS] = LoginAdapterGPGS()
adapter: LoginAdapter
if _babase.using_google_play_game_services():
adapter = LoginAdapterGPGS()
self.login_adapters[adapter.login_type] = adapter
if _babase.using_game_center():
adapter = LoginAdapterGameCenter()
self.login_adapters[adapter.login_type] = adapter
def on_app_loading(self) -> None:
"""Should be called at standard on_app_loading time."""
@ -62,10 +61,6 @@ class AccountV2Subsystem:
for adapter in self.login_adapters.values():
adapter.on_app_loading()
def set_primary_credentials(self, credentials: str | None) -> None:
"""Set credentials for the primary app account."""
raise NotImplementedError('This should be overridden.')
def have_primary_credentials(self) -> bool:
"""Are credentials currently set for the primary app account?
@ -80,10 +75,6 @@ class AccountV2Subsystem:
"""The primary account for the app, or None if not logged in."""
return self.do_get_primary()
def do_get_primary(self) -> AccountV2Handle | None:
"""Internal - should be overridden by subclass."""
return None
def on_primary_account_changed(
self, account: AccountV2Handle | None
) -> None:
@ -142,6 +133,8 @@ class AccountV2Subsystem:
"""An implicit sign-in happened (called by native layer)."""
from babase._login import LoginAdapter
assert _babase.in_logic_thread()
with _babase.ContextRef.empty():
self.login_adapters[login_type].set_implicit_login_state(
LoginAdapter.ImplicitLoginState(
@ -151,6 +144,7 @@ class AccountV2Subsystem:
def on_implicit_sign_out(self, login_type: LoginType) -> None:
"""An implicit sign-out happened (called by native layer)."""
assert _babase.in_logic_thread()
with _babase.ContextRef.empty():
self.login_adapters[login_type].set_implicit_login_state(None)
@ -259,6 +253,14 @@ class AccountV2Subsystem:
# We may want to auto-sign-in based on this new state.
self._update_auto_sign_in()
def do_get_primary(self) -> AccountV2Handle | None:
"""Internal - should be overridden by subclass."""
raise NotImplementedError('This should be overridden.')
def set_primary_credentials(self, credentials: str | None) -> None:
"""Set credentials for the primary app account."""
raise NotImplementedError('This should be overridden.')
def _update_auto_sign_in(self) -> None:
plus = _babase.app.plus
assert plus is not None
@ -266,7 +268,7 @@ class AccountV2Subsystem:
# If implicit state has changed, try to respond.
if self._implicit_state_changed:
if self._implicit_signed_in_adapter is None:
# If implicit back-end is signed out, follow suit
# If implicit back-end has signed out, we follow suit
# immediately; no need to wait for network connectivity.
if DEBUG_LOG:
logging.debug(
@ -419,14 +421,11 @@ class AccountV2Handle:
used with some operations such as cloud messaging.
"""
def __init__(self) -> None:
self.tag = '?'
self.workspacename: str | None = None
self.workspaceid: str | None = None
# Login types and their display-names associated with this account.
self.logins: dict[LoginType, str] = {}
accountid: str
tag: str
workspacename: str | None
workspaceid: str | None
logins: dict[LoginType, str]
def __enter__(self) -> None:
"""Support for "with" statement.

View File

@ -184,7 +184,6 @@ class App:
# foregrounded; can be a simple way to determine if network data
# should be refreshed/etc.
self.fg_state = 0
self.config_file_healthy: bool = False
self._subsystems: list[AppSubsystem] = []
self._native_bootstrapping_completed = False
@ -433,7 +432,7 @@ class App:
"""(internal)"""
from babase._appconfig import read_app_config
self._config, self.config_file_healthy = read_app_config()
self._config = read_app_config()
def handle_deep_link(self, url: str) -> None:
"""Handle a deep link URL."""
@ -583,19 +582,6 @@ class App:
self._aioloop = _asyncio.setup_asyncio()
self.health_monitor = AppHealthMonitor()
# Only proceed if our config file is healthy so we don't
# overwrite a broken one or whatnot and wipe out data.
if not self.config_file_healthy:
if self.classic is not None:
handled = self.classic.show_config_error_window()
if handled:
return
# For now on other systems we just overwrite the bum config.
# At this point settings are already set; lets just commit
# them to disk.
_appconfig.commit_app_config(force=True)
# __FEATURESET_APP_SUBSYSTEM_CREATE_BEGIN__
# This section generated by batools.appmodule; do not edit.
@ -915,6 +901,12 @@ class App:
_babase.lifecyclelog('fade-and-shutdown-graphics begin')
_babase.fade_screen(False, time=0.15)
await asyncio.sleep(0.15)
# Now tell the graphics system to go down and wait until
# it has done so.
_babase.graphics_shutdown_begin()
while not _babase.graphics_shutdown_is_complete():
await asyncio.sleep(0.01)
_babase.lifecyclelog('fade-and-shutdown-graphics end')
async def _fade_and_shutdown_audio(self) -> None:

View File

@ -101,15 +101,13 @@ class AppConfig(dict):
self.commit()
def read_app_config() -> tuple[AppConfig, bool]:
def read_app_config() -> AppConfig:
"""Read the app config."""
import os
import json
config_file_healthy = False
# NOTE: it is assumed that this only gets called once and the
# config object will not change from here on out
# NOTE: it is assumed that this only gets called once and the config
# object will not change from here on out
config_file_path = _babase.app.env.config_file_path
config_contents = ''
try:
@ -119,20 +117,16 @@ def read_app_config() -> tuple[AppConfig, bool]:
config = AppConfig(json.loads(config_contents))
else:
config = AppConfig()
config_file_healthy = True
except Exception:
logging.exception(
"Error reading config file at time %.3f: '%s'.",
"Error reading config file '%s' at time %.3f.\n"
"Backing up broken config to'%s.broken'.",
config_file_path,
_babase.apptime(),
config_file_path,
)
# Whenever this happens lets back up the broken one just in case it
# gets overwritten accidentally.
logging.info(
"Backing up current config file to '%s.broken'", config_file_path
)
try:
import shutil
@ -141,23 +135,10 @@ def read_app_config() -> tuple[AppConfig, bool]:
logging.exception('Error copying broken config.')
config = AppConfig()
# Now attempt to read one of our 'prev' backup copies.
prev_path = config_file_path + '.prev'
try:
if os.path.exists(prev_path):
with open(prev_path, encoding='utf-8') as infile:
config_contents = infile.read()
config = AppConfig(json.loads(config_contents))
else:
config = AppConfig()
config_file_healthy = True
logging.info('Successfully read backup config.')
except Exception:
logging.exception('Error reading prev backup config.')
return config, config_file_healthy
return config
def commit_app_config(force: bool = False) -> None:
def commit_app_config() -> None:
"""Commit the config to persistent storage.
Category: **General Utility Functions**
@ -167,10 +148,4 @@ def commit_app_config(force: bool = False) -> None:
plus = _babase.app.plus
assert plus is not None
if not _babase.app.config_file_healthy and not force:
logging.warning(
'Current config file is broken; '
'skipping write to avoid losing settings.'
)
return
plus.mark_config_dirty()

View File

@ -3,6 +3,7 @@
"""Dev-Console functionality."""
from __future__ import annotations
import os
from typing import TYPE_CHECKING
from dataclasses import dataclass
import logging
@ -154,9 +155,10 @@ class DevConsoleSubsystem:
# All tabs in the dev-console. Add your own stuff here via
# plugins or whatnot.
self.tabs: list[DevConsoleTabEntry] = [
DevConsoleTabEntry('Python', DevConsoleTabPython),
DevConsoleTabEntry('Test', DevConsoleTabTest),
DevConsoleTabEntry('Python', DevConsoleTabPython)
]
if os.environ.get('BA_DEV_CONSOLE_TEST_TAB', '0') == '1':
self.tabs.append(DevConsoleTabEntry('Test', DevConsoleTabTest))
self.is_refreshing = False
def do_refresh_tab(self, tabname: str) -> None:

View File

@ -33,6 +33,23 @@ def reset_to_main_menu() -> None:
logging.warning('reset_to_main_menu: no-op due to classic not present.')
def get_v2_account_id() -> str | None:
"""Return the current V2 account id if signed in, or None if not."""
try:
plus = _babase.app.plus
if plus is not None:
account = plus.accounts.primary
if account is not None:
accountid = account.accountid
# (Avoids mypy complaints when plus is not present)
assert isinstance(accountid, (str, type(None)))
return accountid
return None
except Exception:
logging.exception('Error fetching v2 account id.')
return None
def store_config_fullscreen_on() -> None:
"""The OS has changed our fullscreen state and we should take note."""
_babase.app.config['Fullscreen'] = True
@ -123,6 +140,14 @@ def error_message() -> None:
_babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0))
def success_message() -> None:
from babase._language import Lstr
if _babase.app.env.gui:
_babase.getsimplesound('dingSmall').play()
_babase.screenmessage(Lstr(resource='successText'), color=(0, 1, 0))
def purchase_not_valid_error() -> None:
from babase._language import Lstr
@ -312,6 +337,7 @@ def implicit_sign_in(
from bacommon.login import LoginType
assert _babase.app.plus is not None
_babase.app.plus.accounts.on_implicit_sign_in(
login_type=LoginType(login_type_str),
login_id=login_id,

View File

@ -149,7 +149,7 @@ class LoginAdapter:
result_cb: Callable[[LoginAdapter, SignInResult | Exception], None],
description: str,
) -> None:
"""Attempt an explicit sign in via this adapter.
"""Attempt to sign in via this adapter.
This can be called even if the back-end is not implicitly signed in;
the adapter will attempt to sign in if possible. An exception will
@ -161,7 +161,7 @@ class LoginAdapter:
# Have been seeing multiple sign-in attempts come through
# nearly simultaneously which can be problematic server-side.
# Let's error if a sign-in attempt is made within a few seconds
# of the last one to address this.
# of the last one to try and address this.
now = time.monotonic()
appnow = _babase.apptime()
if self._last_sign_in_time is not None:
@ -229,6 +229,7 @@ class LoginAdapter:
def _got_sign_in_response(
response: bacommon.cloud.SignInResponse | Exception,
) -> None:
# This likely means we couldn't communicate with the server.
if isinstance(response, Exception):
if DEBUG_LOG:
logging.debug(
@ -239,20 +240,18 @@ class LoginAdapter:
)
_babase.pushcall(Call(result_cb, self, response))
else:
if DEBUG_LOG:
logging.debug(
'LoginAdapter: %s adapter got successful'
' sign-in response',
self.login_type.name,
)
# This means our credentials were explicitly rejected.
if response.credentials is None:
result2: LoginAdapter.SignInResult | Exception = (
RuntimeError(
'No credentials returned after'
' submitting sign-in-token.'
)
RuntimeError('Sign-in-token was rejected.')
)
else:
if DEBUG_LOG:
logging.debug(
'LoginAdapter: %s adapter got successful'
' sign-in response',
self.login_type.name,
)
result2 = self.SignInResult(
credentials=response.credentials
)
@ -269,7 +268,7 @@ class LoginAdapter:
on_response=_got_sign_in_response,
)
# Kick off the process by fetching a sign-in token.
# Kick off the sign-in process by fetching a sign-in token.
self.get_sign_in_token(completion_cb=_got_sign_in_token_result)
def is_back_end_active(self) -> bool:
@ -282,11 +281,10 @@ class LoginAdapter:
"""Get a sign-in token from the adapter back end.
This token is then passed to the master-server to complete the
login process.
The adapter can use this opportunity to bring up account creation
UI, call its internal sign_in function, etc. as needed.
The provided completion_cb should then be called with either a token
or None if sign in failed or was cancelled.
sign-in process. The adapter can use this opportunity to bring
up account creation UI, call its internal sign_in function, etc.
as needed. The provided completion_cb should then be called with
either a token or None if sign in failed or was cancelled.
"""
from babase._general import Call
@ -340,8 +338,8 @@ class LoginAdapter:
class LoginAdapterNative(LoginAdapter):
"""A login adapter that does its work in the native layer."""
def __init__(self) -> None:
super().__init__(LoginType.GPGS)
def __init__(self, login_type: LoginType) -> None:
super().__init__(login_type)
# Store int ids for in-flight attempts since they may go through
# various platform layers and back.
@ -375,3 +373,13 @@ class LoginAdapterNative(LoginAdapter):
class LoginAdapterGPGS(LoginAdapterNative):
"""Google Play Game Services adapter."""
def __init__(self) -> None:
super().__init__(LoginType.GPGS)
class LoginAdapterGameCenter(LoginAdapterNative):
"""Apple Game Center adapter."""
def __init__(self) -> None:
super().__init__(LoginType.GAME_CENTER)

View File

@ -8,6 +8,7 @@ from typing import TYPE_CHECKING
import babase
import bascenev1
import _baclassic
if TYPE_CHECKING:
from typing import Any, Sequence
@ -84,7 +85,8 @@ def run_stress_test(
def stop_stress_test() -> None:
"""End a running stress test."""
babase.set_stress_testing(False, 0)
_baclassic.set_stress_testing(False, 0)
assert babase.app.classic is not None
try:
if babase.app.classic.stress_test_reset_timer is not None:
@ -134,14 +136,14 @@ def start_stress_test(args: dict[str, Any]) -> None:
babase.Call(bascenev1.new_host_session, FreeForAllSession),
),
)
babase.set_stress_testing(True, args['player_count'])
_baclassic.set_stress_testing(True, args['player_count'])
babase.app.classic.stress_test_reset_timer = babase.AppTimer(
args['round_duration'], babase.Call(_reset_stress_test, args)
)
def _reset_stress_test(args: dict[str, Any]) -> None:
babase.set_stress_testing(False, args['player_count'])
_baclassic.set_stress_testing(False, args['player_count'])
babase.screenmessage('Resetting stress test...')
session = bascenev1.get_foreground_host_session()
assert session is not None

View File

@ -40,7 +40,14 @@ def get_input_device_mapped_value(
mapping = ccfgs[devicename][unique_id]
elif 'default' in ccfgs[devicename]:
mapping = ccfgs[devicename]['default']
if mapping is not None:
# We now use the config mapping *only* if it is not empty.
# There have been cases of config writing code messing up
# and leaving empty dicts in the app config, which currently
# leaves the device unusable. Alternatively, we'd perhaps
# want to fall back to defaults for individual missing
# values, but that is a bigger change we can make later.
if isinstance(mapping, dict) and mapping:
return mapping.get(name, -1)
if platform == 'windows':

View File

@ -165,15 +165,16 @@ class MusicSubsystem:
def supports_soundtrack_entry_type(self, entry_type: str) -> bool:
"""Return whether provided soundtrack entry type is supported here."""
uas = babase.env()['legacy_user_agent_string']
assert isinstance(uas, str)
# FIXME: Generalize this.
# Note to self; can't access babase.app.classic here because
# we are called during its construction.
env = babase.env()
platform = env.get('platform')
assert isinstance(platform, str)
if entry_type == 'iTunesPlaylist':
return 'Mac' in uas
return platform == 'mac' and babase.is_xcode_build()
if entry_type in ('musicFile', 'musicFolder'):
return (
'android' in uas
platform == 'android'
and babase.android_get_external_files_dir() is not None
)
if entry_type == 'default':

View File

@ -627,15 +627,6 @@ class ClassicSubsystem(babase.AppSubsystem):
"""(internal)"""
return bascenev1.get_foreground_host_activity()
def show_config_error_window(self) -> bool:
"""(internal)"""
if self.platform in ('mac', 'linux', 'windows'):
from bauiv1lib.configerror import ConfigErrorWindow
babase.pushcall(ConfigErrorWindow)
return True
return False
def value_test(
self,
arg: str,

View File

@ -80,14 +80,13 @@ class _MacMusicAppThread(threading.Thread):
def run(self) -> None:
"""Run the Music.app thread."""
babase.set_thread_name('BA_MacMusicAppThread')
babase.mac_music_app_init()
# Let's mention to the user we're launching Music.app in case
# it causes any funny business (this used to background the app
# sometimes, though I think that is fixed now)
def do_print() -> None:
babase.apptimer(
1.0,
0.5,
babase.Call(
babase.screenmessage,
babase.Lstr(resource='usingItunesText'),
@ -97,9 +96,8 @@ class _MacMusicAppThread(threading.Thread):
babase.pushcall(do_print, from_other_thread=True)
# Here we grab this to force the actual launch.
babase.mac_music_app_get_volume()
babase.mac_music_app_get_library_source()
babase.mac_music_app_init()
done = False
while not done:
self._commands_available.wait()

View File

@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
TARGET_BALLISTICA_BUILD = 21491
TARGET_BALLISTICA_BUILD = 21583
TARGET_BALLISTICA_VERSION = '1.7.28'

View File

@ -1,6 +1,6 @@
# Released under the MIT License. See LICENSE for details.
#
"""Provides classic app subsystem."""
"""Provides plus app subsystem."""
from __future__ import annotations
from typing import TYPE_CHECKING

View File

@ -3,6 +3,7 @@
"""Defines base session class."""
from __future__ import annotations
import math
import weakref
import logging
from typing import TYPE_CHECKING
@ -17,6 +18,11 @@ if TYPE_CHECKING:
import bascenev1
# How long someone who left the session (but not the party) must
# wait to rejoin the session again. Intended to prevent game exploits
# such as skipping respawn waits.
REJOIN_COOLDOWN = 10
class Session:
"""Defines a high level series of bascenev1.Activity-es.
@ -203,6 +209,11 @@ class Session:
# Instantiate our session globals node which will apply its settings.
self._sessionglobalsnode = _bascenev1.newnode('sessionglobals')
# Rejoin cooldown stuff.
self._players_on_wait: dict = {}
self._player_requested_identifiers: dict = {}
self._waitlist_timers: dict = {}
@property
def context(self) -> bascenev1.ContextRef:
"""A context-ref pointing at this activity."""
@ -253,6 +264,29 @@ class Session:
)
return False
# Rejoin cooldown.
identifier = player.get_v1_account_id()
if identifier:
leave_time = self._players_on_wait.get(identifier)
if leave_time:
diff = str(
math.ceil(REJOIN_COOLDOWN - babase.apptime() + leave_time)
)
_bascenev1.broadcastmessage(
babase.Lstr(
translate=(
'serverResponses',
'You can join in ${COUNT} seconds.',
),
subs=[('${COUNT}', diff)],
),
color=(1, 1, 0),
clients=[player.inputdevice.client_id],
transient=True,
)
return False
self._player_requested_identifiers[player.id] = identifier
_bascenev1.getsound('dripity').play()
return True
@ -270,6 +304,16 @@ class Session:
activity = self._activity_weak()
# Rejoin cooldown.
identifier = self._player_requested_identifiers.get(sessionplayer.id)
if identifier:
self._players_on_wait[identifier] = babase.apptime()
with babase.ContextRef.empty():
self._waitlist_timers[identifier] = babase.AppTimer(
REJOIN_COOLDOWN,
babase.Call(self._remove_player_from_waitlist, identifier),
)
if not sessionplayer.in_game:
# Ok, the player is still in the lobby; simply remove them.
with self.context:
@ -770,3 +814,9 @@ class Session:
if pass_to_activity:
activity.add_player(sessionplayer)
return sessionplayer
def _remove_player_from_waitlist(self, identifier: str) -> None:
try:
self._players_on_wait.pop(identifier)
except KeyError:
pass

View File

@ -35,7 +35,7 @@ class ControlsGuide(bs.Actor):
delay: is the time in seconds before the overlay fades in.
lifespan: if not None, the overlay will fade back out and die after
that long (in milliseconds).
that long (in seconds).
bright: if True, brighter colors will be used; handy when showing
over gameplay but may be too bright for join-screens, etc.
@ -50,6 +50,7 @@ class ControlsGuide(bs.Actor):
offs5 = 43.0 * scale
ouya = False
maxw = 50
xtweak = -2.8 * scale
self._lifespan = lifespan
self._dead = False
self._bright = bright
@ -117,7 +118,7 @@ class ControlsGuide(bs.Actor):
'host_only': True,
'shadow': 1.0,
'maxwidth': maxw,
'position': (pos[0], pos[1] - offs5),
'position': (pos[0] + xtweak, pos[1] - offs5),
'color': clr,
},
)
@ -145,7 +146,7 @@ class ControlsGuide(bs.Actor):
'host_only': True,
'shadow': 1.0,
'maxwidth': maxw,
'position': (pos[0], pos[1] - offs5),
'position': (pos[0] + xtweak, pos[1] - offs5),
'color': clr,
},
)
@ -173,7 +174,7 @@ class ControlsGuide(bs.Actor):
'host_only': True,
'shadow': 1.0,
'maxwidth': maxw,
'position': (pos[0], pos[1] - offs5),
'position': (pos[0] + xtweak, pos[1] - offs5),
'color': clr,
},
)
@ -201,7 +202,7 @@ class ControlsGuide(bs.Actor):
'host_only': True,
'shadow': 1.0,
'maxwidth': maxw,
'position': (pos[0], pos[1] - offs5),
'position': (pos[0] + xtweak, pos[1] - offs5),
'color': clr,
},
)
@ -264,10 +265,19 @@ class ControlsGuide(bs.Actor):
bs.timer(delay, bs.WeakCall(self._start_updating))
@staticmethod
def _meaningful_button_name(device: bs.InputDevice, button: int) -> str:
def _meaningful_button_name(
device: bs.InputDevice, button_name: str
) -> str:
"""Return a flattened string button name; empty for non-meaningful."""
if not device.has_meaningful_button_names:
return ''
assert bs.app.classic is not None
button = bs.app.classic.get_input_device_mapped_value(
device, button_name
)
# -1 means unset; let's show that.
if button == -1:
return bs.Lstr(resource='configGamepadWindow.unsetText').evaluate()
return device.get_button_name(button).evaluate()
def _start_updating(self) -> None:
@ -289,10 +299,10 @@ class ControlsGuide(bs.Actor):
def _check_fade_in(self) -> None:
assert bs.app.classic is not None
# If we have a touchscreen, we only fade in if we have a player with
# an input device that is *not* the touchscreen.
# (otherwise it is confusing to see the touchscreen buttons right
# next to our display buttons)
# If we have a touchscreen, we only fade in if we have a player
# with an input device that is *not* the touchscreen. Otherwise
# it is confusing to see the touchscreen buttons right next to
# our display buttons.
touchscreen: bs.InputDevice | None = bs.getinputdevice(
'TouchScreen', '#1', doraise=False
)
@ -318,15 +328,7 @@ class ControlsGuide(bs.Actor):
'buttonBomb',
'buttonPickUp',
):
if (
self._meaningful_button_name(
device,
bs.app.classic.get_input_device_mapped_value(
device, name
),
)
!= ''
):
if self._meaningful_button_name(device, name) != '':
fade_in = True
break
if fade_in:
@ -401,58 +403,30 @@ class ControlsGuide(bs.Actor):
# We only care about movement buttons in the case of keyboards.
if all_keyboards:
right_button_names.add(
device.get_button_name(
classic.get_input_device_mapped_value(
device, 'buttonRight'
)
)
self._meaningful_button_name(device, 'buttonRight')
)
left_button_names.add(
device.get_button_name(
classic.get_input_device_mapped_value(
device, 'buttonLeft'
)
)
self._meaningful_button_name(device, 'buttonLeft')
)
down_button_names.add(
device.get_button_name(
classic.get_input_device_mapped_value(
device, 'buttonDown'
)
)
self._meaningful_button_name(device, 'buttonDown')
)
up_button_names.add(
device.get_button_name(
classic.get_input_device_mapped_value(
device, 'buttonUp'
)
)
self._meaningful_button_name(device, 'buttonUp')
)
# Ignore empty values; things like the remote app or
# wiimotes can return these.
bname = self._meaningful_button_name(
device,
classic.get_input_device_mapped_value(device, 'buttonPunch'),
)
bname = self._meaningful_button_name(device, 'buttonPunch')
if bname != '':
punch_button_names.add(bname)
bname = self._meaningful_button_name(
device,
classic.get_input_device_mapped_value(device, 'buttonJump'),
)
bname = self._meaningful_button_name(device, 'buttonJump')
if bname != '':
jump_button_names.add(bname)
bname = self._meaningful_button_name(
device,
classic.get_input_device_mapped_value(device, 'buttonBomb'),
)
bname = self._meaningful_button_name(device, 'buttonBomb')
if bname != '':
bomb_button_names.add(bname)
bname = self._meaningful_button_name(
device,
classic.get_input_device_mapped_value(device, 'buttonPickUp'),
)
bname = self._meaningful_button_name(device, 'buttonPickUp')
if bname != '':
pickup_button_names.add(bname)
@ -582,8 +556,8 @@ class ControlsGuide(bs.Actor):
if msg.immediate:
self._die()
else:
# If they don't need immediate,
# fade out our nodes and die later.
# If they don't need immediate, fade out our nodes and
# die later.
for node in self._nodes:
bs.animate(node, 'opacity', {0: node.opacity, 3.0: 0.0})
bs.timer(3.1, bs.WeakCall(self._die))

View File

@ -66,6 +66,8 @@ from babase import (
lock_all_input,
LoginAdapter,
Lstr,
native_review_request,
native_review_request_supported,
NotFoundError,
Permission,
Plugin,
@ -191,6 +193,8 @@ __all__ = [
'LoginAdapter',
'Lstr',
'Mesh',
'native_review_request',
'native_review_request_supported',
'NotFoundError',
'open_file_externally',
'open_url',

View File

@ -51,6 +51,18 @@ class OnScreenKeyboardWindow(Window):
else (0, 0),
)
)
self._cancel_button = _bauiv1.buttonwidget(
parent=self._root_widget,
scale=0.5,
position=(30, self._height - 55),
size=(60, 60),
label='',
on_activate_call=self._cancel,
autoselect=True,
color=(0.55, 0.5, 0.6),
icon=_bauiv1.gettexture('crossOut'),
iconscale=1.2,
)
self._done_button = _bauiv1.buttonwidget(
parent=self._root_widget,
position=(self._width - 200, 44),

View File

@ -64,12 +64,13 @@ class AccountSettingsWindow(bui.Window):
)
# Currently we can only reset achievements on game-center.
v1_account_type: str | None
if self._v1_signed_in:
v1_account_type = plus.get_v1_account_type()
else:
v1_account_type = None
self._can_reset_achievements = v1_account_type == 'Game Center'
# v1_account_type: str | None
# if self._v1_signed_in:
# v1_account_type = plus.get_v1_account_type()
# else:
# v1_account_type = None
# self._can_reset_achievements = v1_account_type == 'Game Center'
self._can_reset_achievements = False
app = bui.app
assert app.classic is not None
@ -98,6 +99,9 @@ class AccountSettingsWindow(bui.Window):
if LoginType.GPGS in plus.accounts.login_adapters:
self._show_sign_in_buttons.append('Google Play')
if LoginType.GAME_CENTER in plus.accounts.login_adapters:
self._show_sign_in_buttons.append('Game Center')
# Always want to show our web-based v2 login option.
self._show_sign_in_buttons.append('V2Proxy')
@ -227,6 +231,8 @@ class AccountSettingsWindow(bui.Window):
plus = bui.app.plus
assert plus is not None
via_lines: list[str] = []
primary_v2_account = plus.accounts.primary
v1_state = plus.get_v1_account_state()
@ -242,9 +248,53 @@ class AccountSettingsWindow(bui.Window):
False if gpgs_adapter is None else gpgs_adapter.is_back_end_active()
)
# Ditto for Game Center.
game_center_adapter = plus.accounts.login_adapters.get(
LoginType.GAME_CENTER
)
is_game_center = (
False
if game_center_adapter is None
else game_center_adapter.is_back_end_active()
)
show_signed_in_as = self._v1_signed_in
signed_in_as_space = 95.0
# To reduce confusion about the whole V2 account situation for
# people used to seeing their Google Play Games or Game Center
# account name and icon and whatnot, let's show those underneath
# the V2 tag to help communicate that they are in fact logged in
# through that account.
via_space = 25.0
if show_signed_in_as and bui.app.plus is not None:
primary_account = bui.app.plus.accounts.primary
if primary_account is not None:
# Show Google Play Games account name if the current account
# has such a login attached.
lname = primary_account.logins.get(LoginType.GPGS)
if lname is not None:
icontxt = bui.charstr(
bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO
)
via_lines.append(f'{icontxt}{lname}')
# Show Game Center account name if the current account
# has such a login attached.
lname = primary_account.logins.get(LoginType.GAME_CENTER)
if lname is not None:
icontxt = bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
via_lines.append(f'{icontxt}{lname}')
# TEMP TESTING
if bool(False):
icontxt = bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO)
via_lines.append(f'{icontxt}FloofDibble')
icontxt = bui.charstr(
bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO
)
via_lines.append(f'{icontxt}StinkBobble')
show_sign_in_benefits = not self._v1_signed_in
sign_in_benefits_space = 80.0
@ -258,6 +308,11 @@ class AccountSettingsWindow(bui.Window):
and self._signing_in_adapter is None
and 'Google Play' in self._show_sign_in_buttons
)
show_game_center_sign_in_button = (
v1_state == 'signed_out'
and self._signing_in_adapter is None
and 'Game Center' in self._show_sign_in_buttons
)
show_v2_proxy_sign_in_button = (
v1_state == 'signed_out'
and self._signing_in_adapter is None
@ -281,11 +336,9 @@ class AccountSettingsWindow(bui.Window):
show_linked_accounts_text = self._v1_signed_in
linked_accounts_text_space = 60.0
show_achievements_button = self._v1_signed_in and v1_account_type in (
'Google Play',
'Local',
'V2',
)
# Always show achievements except in the game-center case where
# its unified UI covers them.
show_achievements_button = self._v1_signed_in and not is_game_center
achievements_button_space = 60.0
show_achievements_text = (
@ -349,10 +402,13 @@ class AccountSettingsWindow(bui.Window):
self._sub_height = 60.0
if show_signed_in_as:
self._sub_height += signed_in_as_space
self._sub_height += via_space * len(via_lines)
if show_signing_in_text:
self._sub_height += signing_in_text_space
if show_google_play_sign_in_button:
self._sub_height += sign_in_button_space
if show_game_center_sign_in_button:
self._sub_height += sign_in_button_space
if show_v2_proxy_sign_in_button:
self._sub_height += sign_in_button_space
if show_device_sign_in_button:
@ -466,6 +522,54 @@ class AccountSettingsWindow(bui.Window):
v -= signed_in_as_space * 0.4
for via in via_lines:
v -= via_space * 0.1
sscale = 0.7
swidth = (
bui.get_string_width(via, suppress_warning=True) * sscale
)
bui.textwidget(
parent=self._subcontainer,
position=(self._sub_width * 0.5, v),
size=(0, 0),
text=via,
scale=sscale,
color=(0.6, 0.6, 0.6),
flatness=1.0,
shadow=0.0,
h_align='center',
v_align='center',
)
bui.textwidget(
parent=self._subcontainer,
position=(self._sub_width * 0.5 - swidth * 0.5 - 5, v),
size=(0, 0),
text=bui.Lstr(
value='(${VIA}',
subs=[('${VIA}', bui.Lstr(resource='viaText'))],
),
scale=0.6,
color=(0.4, 0.6, 0.4, 0.5),
flatness=1.0,
shadow=0.0,
h_align='right',
v_align='center',
)
bui.textwidget(
parent=self._subcontainer,
position=(self._sub_width * 0.5 + swidth * 0.5 + 10, v),
size=(0, 0),
text=')',
scale=0.6,
color=(0.4, 0.6, 0.4, 0.5),
flatness=1.0,
shadow=0.0,
h_align='right',
v_align='center',
)
v -= via_space * 0.9
else:
self._account_name_text = None
self._account_name_what_is_text = None
@ -477,22 +581,6 @@ class AccountSettingsWindow(bui.Window):
if show_sign_in_benefits:
v -= sign_in_benefits_space
app = bui.app
assert app.classic is not None
extra: str | bui.Lstr | None
if (
app.classic.platform in ['mac', 'ios']
and app.classic.subplatform == 'appstore'
):
extra = bui.Lstr(
value='\n${S}',
subs=[
('${S}', bui.Lstr(resource='signInWithGameCenterText'))
],
)
else:
extra = ''
bui.textwidget(
parent=self._subcontainer,
position=(
@ -500,16 +588,7 @@ class AccountSettingsWindow(bui.Window):
v + sign_in_benefits_space * 0.4,
),
size=(0, 0),
text=bui.Lstr(
value='${A}${B}',
subs=[
(
'${A}',
bui.Lstr(resource=self._r + '.signInInfoText'),
),
('${B}', extra),
],
),
text=bui.Lstr(resource=self._r + '.signInInfoText'),
max_height=sign_in_benefits_space * 0.9,
scale=0.9,
color=(0.75, 0.7, 0.8),
@ -554,7 +633,13 @@ class AccountSettingsWindow(bui.Window):
(
'${B}',
bui.Lstr(
resource=self._r + '.signInWithGooglePlayText'
resource=self._r + '.signInWithText',
subs=[
(
'${SERVICE}',
bui.Lstr(resource='googlePlayText'),
)
],
),
),
],
@ -572,6 +657,45 @@ class AccountSettingsWindow(bui.Window):
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
if show_game_center_sign_in_button:
button_width = 350
v -= sign_in_button_space
self._sign_in_google_play_button = btn = bui.buttonwidget(
parent=self._subcontainer,
position=((self._sub_width - button_width) * 0.5, v - 20),
autoselect=True,
size=(button_width, 60),
label=bui.Lstr(
value='${A}${B}',
subs=[
(
'${A}',
bui.charstr(bui.SpecialChar.GAME_CENTER_LOGO),
),
(
'${B}',
bui.Lstr(
resource=self._r + '.signInWithText',
subs=[('${SERVICE}', 'Game Center')],
),
),
],
),
on_activate_call=lambda: self._sign_in_press(
LoginType.GAME_CENTER
),
)
if first_selectable is None:
first_selectable = btn
if bui.app.ui_v1.use_toolbars:
bui.widget(
edit=btn,
right_widget=bui.get_special_widget('party_button'),
)
bui.widget(edit=btn, left_widget=bbtn)
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
if show_v2_proxy_sign_in_button:
button_width = 350
v -= sign_in_button_space
@ -748,7 +872,11 @@ class AccountSettingsWindow(bui.Window):
v -= game_service_button_space * 0.85
v1_account_type = plus.get_v1_account_type()
if v1_account_type == 'Game Center':
v1_account_type_name = bui.Lstr(resource='gameCenterText')
# Update: Apparently Game Center is just called 'Game Center'
# in all languages. Can revisit if not true.
# https://developer.apple.com/forums/thread/725779
v1_account_type_name = bui.Lstr(value='Game Center')
# v1_account_type_name = bui.Lstr(resource='gameCenterText')
else:
raise ValueError(
"unknown account type: '" + str(v1_account_type) + "'"
@ -1469,8 +1597,11 @@ class AccountSettingsWindow(bui.Window):
if isinstance(result, Exception):
# For now just make a bit of noise if anything went wrong;
# can get more specific as needed later.
logging.warning('Got error in v2 sign-in result: %s.', result)
bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0))
logging.warning('Got error in v2 sign-in result: %s', result)
bui.screenmessage(
bui.Lstr(resource='internal.signInNoConnectionText'),
color=(1, 0, 0),
)
bui.getsound('error').play()
else:
# Success! Plug in these credentials which will begin

View File

@ -93,6 +93,7 @@ class ConfigNumberEdit:
displayname: str | bui.Lstr | None = None,
changesound: bool = True,
textscale: float = 1.0,
as_percent: bool = False,
):
if displayname is None:
displayname = configkey
@ -103,6 +104,7 @@ class ConfigNumberEdit:
self._increment = increment
self._callback = callback
self._value = bui.app.config.resolve(configkey)
self._as_percent = as_percent
self.nametext = bui.textwidget(
parent=parent,
@ -166,4 +168,8 @@ class ConfigNumberEdit:
bui.app.config.apply_and_commit()
def _update_display(self) -> None:
bui.textwidget(edit=self.valuetext, text=f'{self._value:.1f}')
if self._as_percent:
val = f'{round(self._value*100.0)}%'
else:
val = f'{self._value:.1f}'
bui.textwidget(edit=self.valuetext, text=val)

View File

@ -1,80 +0,0 @@
# Released under the MIT License. See LICENSE for details.
#
"""UI for dealing with broken config files."""
from __future__ import annotations
import bauiv1 as bui
class ConfigErrorWindow(bui.Window):
"""Window for dealing with a broken config."""
def __init__(self) -> None:
self._config_file_path = bui.app.env.config_file_path
width = 800
super().__init__(
bui.containerwidget(size=(width, 400), transition='in_right')
)
padding = 20
bui.textwidget(
parent=self._root_widget,
position=(padding, 220 + 60),
size=(width - 2 * padding, 100 - 2 * padding),
h_align='center',
v_align='top',
scale=0.73,
text=(
f'Error reading {bui.appnameupper()} config file'
':\n\n\nCheck the console'
' (press ~ twice) for details.\n\nWould you like to quit and'
' try to fix it by hand\nor overwrite it with defaults?\n\n'
'(high scores, player profiles, etc will be lost if you'
' overwrite)'
),
)
bui.textwidget(
parent=self._root_widget,
position=(padding, 198 + 60),
size=(width - 2 * padding, 100 - 2 * padding),
h_align='center',
v_align='top',
scale=0.5,
text=self._config_file_path,
)
quit_button = bui.buttonwidget(
parent=self._root_widget,
position=(35, 30),
size=(240, 54),
label='Quit and Edit',
on_activate_call=self._quit,
)
bui.buttonwidget(
parent=self._root_widget,
position=(width - 370, 30),
size=(330, 54),
label='Overwrite with Defaults',
on_activate_call=self._defaults,
)
bui.containerwidget(
edit=self._root_widget,
cancel_button=quit_button,
selected_child=quit_button,
)
def _quit(self) -> None:
bui.apptimer(0.001, self._edit_and_quit)
bui.lock_all_input()
def _edit_and_quit(self) -> None:
bui.open_file_externally(self._config_file_path)
bui.apptimer(0.1, bui.quit)
def _defaults(self) -> None:
bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.getsound('gunCocking').play()
bui.screenmessage('settings reset.', color=(1, 1, 0))
# At this point settings are already set; lets just commit them
# to disk.
bui.commit_app_config(force=True)

View File

@ -458,9 +458,9 @@ class PrivateGatherTab(GatherTab):
scale=1.5,
size=(300, 50),
editable=True,
max_chars=20,
description=bui.Lstr(resource='gatherWindow.partyCodeText'),
autoselect=True,
maxwidth=250,
h_align='left',
v_align='center',
text='',
@ -962,7 +962,7 @@ class PrivateGatherTab(GatherTab):
code = cast(str, bui.textwidget(query=self._join_party_code_text))
if not code:
bui.screenmessage(
bui.Lstr(resource='internal.invalidAddressErrorText'),
bui.Lstr(translate=('serverResponses', 'Invalid code.')),
color=(1, 0, 0),
)
bui.getsound('error').play()

View File

@ -361,6 +361,7 @@ class PublicGatherTab(GatherTab):
self._last_server_list_query_time: float | None = None
self._join_list_column: bui.Widget | None = None
self._join_status_text: bui.Widget | None = None
self._no_servers_found_text: bui.Widget | None = None
self._host_max_party_size_value: bui.Widget | None = None
self._host_max_party_size_minus_button: (bui.Widget | None) = None
self._host_max_party_size_plus_button: (bui.Widget | None) = None
@ -658,6 +659,18 @@ class PublicGatherTab(GatherTab):
color=(0.6, 0.6, 0.6),
position=(c_width * 0.5, c_height * 0.5),
)
self._no_servers_found_text = bui.textwidget(
parent=self._container,
text='',
size=(0, 0),
scale=0.9,
flatness=1.0,
shadow=0.0,
h_align='center',
v_align='top',
color=(0.6, 0.6, 0.6),
position=(c_width * 0.5, c_height * 0.5),
)
def _build_host_tab(
self, region_width: float, region_height: float
@ -950,6 +963,9 @@ class PublicGatherTab(GatherTab):
self._update_party_rows()
def _update_party_rows(self) -> None:
plus = bui.app.plus
assert plus is not None
columnwidget = self._join_list_column
if not columnwidget:
return
@ -963,6 +979,7 @@ class PublicGatherTab(GatherTab):
edit=self._host_scrollwidget,
claims_up_down=(len(self._parties_displayed) > 0),
)
bui.textwidget(edit=self._no_servers_found_text, text='')
# Clip if we have more UI rows than parties to show.
clipcount = len(self._ui_rows) - len(self._parties_displayed)
@ -972,6 +989,15 @@ class PublicGatherTab(GatherTab):
# If we have no parties to show, we're done.
if not self._parties_displayed:
text = self._join_status_text
if (
plus.get_v1_account_state() == 'signed_in'
and cast(str, bui.textwidget(query=text)) == ''
):
bui.textwidget(
edit=self._no_servers_found_text,
text=bui.Lstr(resource='noServersFoundText'),
)
return
sub_scroll_width = 830

View File

@ -1350,7 +1350,7 @@ class MainMenuWindow(bui.Window):
bui.app.classic.resume()
if self._root_widget:
bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.app.ui_v1.clear_main_menu_window()
bui.app.ui_v1.clear_main_menu_window(transition='out_right')
# If there's callbacks waiting for this window to go away, call them.
for call in bui.app.ui_v1.main_menu_resume_callbacks:

View File

@ -807,6 +807,8 @@ class AdvancedSettingsWindow(bui.Window):
sel_name = 'ModdingGuide'
elif sel == self._language_inform_checkbox:
sel_name = 'LangInform'
elif sel == self._show_dev_console_button_check_box.widget:
sel_name = 'ShowDevConsole'
else:
raise ValueError(f'unrecognized selection \'{sel}\'')
elif sel == self._back_button:
@ -870,6 +872,8 @@ class AdvancedSettingsWindow(bui.Window):
sel = self._modding_guide_button
elif sel_name == 'LangInform':
sel = self._language_inform_checkbox
elif sel_name == 'ShowDevConsole':
sel = self._show_dev_console_button_check_box.widget
else:
sel = None
if sel is not None:

View File

@ -121,7 +121,8 @@ class AudioSettingsWindow(bui.Window):
displayname=bui.Lstr(resource=self._r + '.soundVolumeText'),
minval=0.0,
maxval=1.0,
increment=0.1,
increment=0.05,
as_percent=True,
)
if bui.app.ui_v1.use_toolbars:
bui.widget(
@ -137,9 +138,10 @@ class AudioSettingsWindow(bui.Window):
displayname=bui.Lstr(resource=self._r + '.musicVolumeText'),
minval=0.0,
maxval=1.0,
increment=0.1,
increment=0.05,
callback=music.music_volume_changed,
changesound=False,
as_percent=True,
)
v -= 0.5 * spacing

View File

@ -545,20 +545,24 @@ class GamepadSettingsWindow(bui.Window):
if 'analogStickLR' + self._ext in self._settings
else 5
if self._is_secondary
else 1
else None
)
sval2 = (
self._settings['analogStickUD' + self._ext]
if 'analogStickUD' + self._ext in self._settings
else 6
if self._is_secondary
else 2
)
return (
self._input.get_axis_name(sval1)
+ ' / '
+ self._input.get_axis_name(sval2)
else None
)
assert isinstance(sval1, (int, type(None)))
assert isinstance(sval2, (int, type(None)))
if sval1 is not None and sval2 is not None:
return (
self._input.get_axis_name(sval1)
+ ' / '
+ self._input.get_axis_name(sval2)
)
return bui.Lstr(resource=self._r + '.unsetText')
# If they're looking for triggers.
if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]:
@ -573,7 +577,7 @@ class GamepadSettingsWindow(bui.Window):
return str(1.0)
# For dpad buttons: show individual buttons if any are set.
# Otherwise show whichever dpad is set (defaulting to 1).
# Otherwise show whichever dpad is set.
dpad_buttons = [
'buttonLeft' + self._ext,
'buttonRight' + self._ext,
@ -588,24 +592,28 @@ class GamepadSettingsWindow(bui.Window):
return bui.Lstr(resource=self._r + '.unsetText')
# No dpad buttons - show the dpad number for all 4.
return bui.Lstr(
value='${A} ${B}',
subs=[
('${A}', bui.Lstr(resource=self._r + '.dpadText')),
(
'${B}',
str(
self._settings['dpad' + self._ext]
if 'dpad' + self._ext in self._settings
else 2
if self._is_secondary
else 1
),
),
],
dpadnum = (
self._settings['dpad' + self._ext]
if 'dpad' + self._ext in self._settings
else 2
if self._is_secondary
else None
)
assert isinstance(dpadnum, (int, type(None)))
if dpadnum is not None:
return bui.Lstr(
value='${A} ${B}',
subs=[
('${A}', bui.Lstr(resource=self._r + '.dpadText')),
(
'${B}',
str(dpadnum),
),
],
)
return bui.Lstr(resource=self._r + '.unsetText')
# other buttons..
# Other buttons.
if control in self._settings:
return self._input.get_button_name(self._settings[control])
return bui.Lstr(resource=self._r + '.unsetText')
@ -616,9 +624,7 @@ class GamepadSettingsWindow(bui.Window):
event: dict[str, Any],
dialog: AwaitGamepadInputWindow,
) -> None:
# pylint: disable=too-many-nested-blocks
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
assert self._settings is not None
ext = self._ext
@ -648,10 +654,6 @@ class GamepadSettingsWindow(bui.Window):
if btn in self._settings:
del self._settings[btn]
if event['hat'] == (2 if self._is_secondary else 1):
# Exclude value in default case.
if 'dpad' + ext in self._settings:
del self._settings['dpad' + ext]
else:
self._settings['dpad' + ext] = event['hat']
# Update the 4 dpad button txt widgets.
@ -680,10 +682,6 @@ class GamepadSettingsWindow(bui.Window):
if abs(event['value']) > 0.5:
axis = event['axis']
if axis == (5 if self._is_secondary else 1):
# Exclude value in default case.
if 'analogStickLR' + ext in self._settings:
del self._settings['analogStickLR' + ext]
else:
self._settings['analogStickLR' + ext] = axis
bui.textwidget(
edit=self._textwidgets['analogStickLR' + ext],
@ -713,10 +711,6 @@ class GamepadSettingsWindow(bui.Window):
lr_axis = 5 if self._is_secondary else 1
if axis != lr_axis:
if axis == (6 if self._is_secondary else 2):
# Exclude value in default case.
if 'analogStickUD' + ext in self._settings:
del self._settings['analogStickUD' + ext]
else:
self._settings['analogStickUD' + ext] = axis
bui.textwidget(
edit=self._textwidgets['analogStickLR' + ext],
@ -795,7 +789,7 @@ class GamepadSettingsWindow(bui.Window):
),
)
bui.apptimer(0, doit)
bui.pushcall(doit)
return btn
def _cancel(self) -> None:

View File

@ -452,7 +452,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
),
)
bui.apptimer(0, doit)
bui.pushcall(doit)
return btn, btn2
def _inc(

View File

@ -213,6 +213,12 @@ class ConfigKeyboardWindow(bui.Window):
scale=1.0,
)
def _pretty_button_name(self, button_name: str) -> bui.Lstr:
button_id = self._settings[button_name]
if button_id == -1:
return bs.Lstr(resource='configGamepadWindow.unsetText')
return self._input.get_button_name(button_id)
def _capture_button(
self,
pos: tuple[float, float],
@ -250,7 +256,7 @@ class ConfigKeyboardWindow(bui.Window):
v_align='top',
scale=uiscale,
maxwidth=maxwidth,
text=self._input.get_button_name(self._settings[button]),
text=self._pretty_button_name(button),
)
bui.buttonwidget(
edit=btn,

View File

@ -518,10 +518,13 @@ def show_offer() -> bool:
assert plus is not None
app = bui.app
assert app.classic is not None
if app.classic is None:
raise RuntimeError(
'Classic feature-set is required to show offers.'
)
# Space things out a bit so we don't hit the poor user with an ad and
# then an in-game offer.
# Space things out a bit so we don't hit the poor user with an
# ad and then an in-game offer.
has_been_long_enough_since_ad = True
if app.classic.ads.last_ad_completion_time is not None and (
bui.apptime() - app.classic.ads.last_ad_completion_time < 30.0
@ -532,8 +535,9 @@ def show_offer() -> bool:
app.classic.special_offer is not None
and has_been_long_enough_since_ad
):
# Special case: for pro offers, store this in our prefs so we
# can re-show it if the user kills us (set phasers to 'NAG'!!!).
# Special case: for pro offers, store this in our prefs so
# we can re-show it if the user kills us (set phasers to
# 'NAG'!!!).
if app.classic.special_offer.get('item') == 'pro_fullprice':
cfg = app.config
cfg['pendingSpecialOffer'] = {
@ -543,7 +547,11 @@ def show_offer() -> bool:
cfg.commit()
if app.classic.special_offer['item'] == 'rating':
feedback.ask_for_rating()
# Go with a native thing if we've got one.
if bui.native_review_request_supported():
bui.native_review_request()
else:
feedback.ask_for_rating()
else:
SpecialOfferWindow(app.classic.special_offer)

View File

@ -352,7 +352,7 @@ class StoreBrowserWindow(bui.Window):
plus = bui.app.plus
assert plus is not None
if plus.get_v1_account_state() != 'signed_in':
if plus.accounts.primary is None:
account.show_sign_in_prompt()
else:
plus.restore_purchases()

View File

@ -2,64 +2,17 @@
#include "ballistica/base/app_adapter/app_adapter.h"
#if BA_OSTYPE_ANDROID // Remove conditional once android sources are public.
#include "ballistica/base/app_adapter/app_adapter_android.h"
#endif
#include "ballistica/base/app_adapter/app_adapter_apple.h"
#include "ballistica/base/app_adapter/app_adapter_headless.h"
#include "ballistica/base/app_adapter/app_adapter_sdl.h"
#include "ballistica/base/app_adapter/app_adapter_vr.h"
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/graphics/renderer/renderer.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/networking/network_reader.h"
#include "ballistica/base/networking/networking.h"
#include "ballistica/base/platform/base_platform.h"
#include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/support/stress_test.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/python/python.h"
namespace ballistica::base {
auto AppAdapter::Create() -> AppAdapter* {
assert(g_core);
// TEMP - need to init sdl on our legacy mac build even though its not
// technically an SDL app. Kill this once the old mac build is gone.
#if BA_LEGACY_MACOS_BUILD
AppAdapterSDL::InitSDL();
#endif
AppAdapter* app_adapter{};
#if BA_HEADLESS_BUILD
app_adapter = new AppAdapterHeadless();
#elif BA_OSTYPE_ANDROID
app_adapter = new AppAdapterAndroid();
#elif BA_XCODE_BUILD
app_adapter = new AppAdapterApple();
#elif BA_RIFT_BUILD
// Rift build can spin up in either VR or regular mode.
if (g_core->vr_mode) {
app_adapter = new AppAdapterVR();
} else {
app_adapter = new AppAdapterSDL();
}
#elif BA_CARDBOARD_BUILD
app_adapter = new AppAdapterVR();
#elif BA_SDL_BUILD
app_adapter = new AppAdapterSDL();
#else
#error No app adapter defined for this build.
#endif
assert(app_adapter);
return app_adapter;
}
AppAdapter::AppAdapter() = default;
AppAdapter::~AppAdapter() = default;
@ -67,31 +20,8 @@ AppAdapter::~AppAdapter() = default;
auto AppAdapter::ManagesMainThreadEventLoop() const -> bool { return true; }
void AppAdapter::OnMainThreadStartApp() {
assert(g_base);
assert(g_core);
assert(g_core->InMainThread());
// Add some common input devices where applicable. More specific ones (SDL
// Joysticks, etc.) get added in subclasses.
// FIXME: This stuff should probably go elsewhere.
if (!g_core->HeadlessMode()) {
// If we've got a nice themed hardware cursor, show it. Otherwise we'll
// render it manually, which is laggier but gets the job done.
// g_base->platform->SetHardwareCursorVisible(g_buildconfig.hardware_cursor());
// On desktop systems we just assume keyboard input exists and add it
// immediately.
if (g_core->platform->IsRunningOnDesktop()) {
g_base->input->PushCreateKeyboardInputDevices();
}
// On non-tv, non-desktop, non-vr systems, create a touchscreen input.
if (!g_core->platform->IsRunningOnTV() && !g_core->IsVRMode()
&& !g_core->platform->IsRunningOnDesktop()) {
g_base->input->CreateTouchInput();
}
}
}
void AppAdapter::OnAppStart() { assert(g_base->InLogicThread()); }
@ -315,4 +245,76 @@ auto AppAdapter::GetGraphicsClientContext() -> GraphicsClientContext* {
return new GraphicsClientContext();
}
auto AppAdapter::GetKeyRepeatDelay() -> float { return 0.3f; }
auto AppAdapter::GetKeyRepeatInterval() -> float { return 0.08f; }
auto AppAdapter::ClipboardIsSupported() -> bool {
// We only call our actual virtual function once.
if (!have_clipboard_is_supported_) {
clipboard_is_supported_ = DoClipboardIsSupported();
have_clipboard_is_supported_ = true;
}
return clipboard_is_supported_;
}
auto AppAdapter::ClipboardHasText() -> bool {
// If subplatform says they don't support clipboards, don't even ask.
if (!ClipboardIsSupported()) {
return false;
}
return DoClipboardHasText();
}
void AppAdapter::ClipboardSetText(const std::string& text) {
// If subplatform says they don't support clipboards, this is an error.
if (!ClipboardIsSupported()) {
throw Exception("ClipboardSetText called with no clipboard support.",
PyExcType::kRuntime);
}
DoClipboardSetText(text);
}
auto AppAdapter::ClipboardGetText() -> std::string {
// If subplatform says they don't support clipboards, this is an error.
if (!ClipboardIsSupported()) {
throw Exception("ClipboardGetText called with no clipboard support.",
PyExcType::kRuntime);
}
return DoClipboardGetText();
}
auto AppAdapter::DoClipboardIsSupported() -> bool { return false; }
auto AppAdapter::DoClipboardHasText() -> bool {
// Shouldn't get here since we default to no clipboard support.
FatalError("Shouldn't get here.");
return false;
}
void AppAdapter::DoClipboardSetText(const std::string& text) {
// Shouldn't get here since we default to no clipboard support.
FatalError("Shouldn't get here.");
}
auto AppAdapter::DoClipboardGetText() -> std::string {
// Shouldn't get here since we default to no clipboard support.
FatalError("Shouldn't get here.");
return "";
}
auto AppAdapter::GetKeyName(int keycode) -> std::string {
BA_LOG_ONCE(LogLevel::kWarning,
"CorePlatform::GetKeyName not implemented here.");
return "?";
}
auto AppAdapter::NativeReviewRequestSupported() -> bool { return false; }
void AppAdapter::NativeReviewRequest() {
BA_PRECONDITION(NativeReviewRequestSupported());
PushMainThreadCall([this] { DoNativeReviewRequest(); });
}
void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); }
} // namespace ballistica::base

View File

@ -15,8 +15,7 @@ namespace ballistica::base {
/// all might share the same CorePlatform and BasePlatform classes.
class AppAdapter {
public:
/// Instantiate the AppAdapter subclass for the current build.
static auto Create() -> AppAdapter*;
AppAdapter();
/// Called in the main thread when the app is being started.
virtual void OnMainThreadStartApp();
@ -189,6 +188,13 @@ class AppAdapter {
/// changing (it may be preferable to rely on dialogs for non-english
/// languages/etc.). Default implementation returns false. This function
/// should be callable from any thread.
///
/// Note that UI elements wanting to accept direct keyboard input should
/// not call this directly, but instead should call
/// UI::UIHasDirectKeyboardInput, as that takes into account other factors
/// such as which device is currently controlling the UI (Someone
/// navigating the UI with a game controller may still get an on-screen
/// keyboard even if there is a physical keyboard attached).
virtual auto HasDirectKeyboardInput() -> bool;
/// Called in the graphics context to apply new settings coming in from
@ -197,9 +203,24 @@ class AppAdapter {
/// settings coming in.
virtual void ApplyGraphicsSettings(const GraphicsSettings* settings);
protected:
AppAdapter();
virtual ~AppAdapter();
virtual auto GetKeyRepeatDelay() -> float;
virtual auto GetKeyRepeatInterval() -> float;
/// Return whether clipboard operations are supported at all. This gets
/// called when determining whether to display clipboard related UI
/// elements/etc.
auto ClipboardIsSupported() -> bool;
/// Return whether there is currently text on the clipboard.
auto ClipboardHasText() -> bool;
/// Set current clipboard text. Raises an Exception if clipboard is
/// unsupported.
void ClipboardSetText(const std::string& text);
/// Return current text from the clipboard. Raises an Exception if
/// clipboard is unsupported or if there's no text on the clipboard.
auto ClipboardGetText() -> std::string;
/// Push a raw pointer Runnable to the platform's 'main' thread. The main
/// thread should call its RunAndLogErrors() method and then delete it.
@ -209,10 +230,34 @@ class AppAdapter {
/// context. By default this is simply the main thread.
virtual void DoPushGraphicsContextRunnable(Runnable* runnable);
/// Return a name for a ballistica keyboard keycode.
virtual auto GetKeyName(int keycode) -> std::string;
/// Return whether there is a native 'review-this-app' prompt.
virtual auto NativeReviewRequestSupported() -> bool;
/// Asynchronously kick off a native review request.
void NativeReviewRequest();
protected:
virtual ~AppAdapter();
virtual auto DoClipboardIsSupported() -> bool;
virtual auto DoClipboardHasText() -> bool;
virtual void DoClipboardSetText(const std::string& text);
virtual auto DoClipboardGetText() -> std::string;
/// Override to implement native review requests. Will be called in the
/// main thread.
virtual void DoNativeReviewRequest();
private:
void OnAppSuspend_();
void OnAppUnsuspend_();
bool app_suspended_{};
bool app_suspended_ : 1 {};
bool have_clipboard_is_supported_ : 1 {};
bool clipboard_is_supported_ : 1 {};
};
} // namespace ballistica::base

View File

@ -3,16 +3,23 @@
#include "ballistica/base/app_adapter/app_adapter_apple.h"
#include <BallisticaKit-Swift.h>
#include "ballistica/base/graphics/gl/renderer_gl.h"
#include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/platform/apple/apple_utils.h"
#include "ballistica/base/platform/apple/from_swift.h"
#include "ballistica/base/platform/support/min_sdl_key_names.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/shared/ballistica.h"
#include "ballistica/shared/foundation/event_loop.h"
// clang-format off
// This needs to be below ballistica headers since it relies on
// some types in them but does not include headers itself.
#include <BallisticaKit-Swift.h>
// clang-format on
namespace ballistica::base {
/// RAII-friendly way to mark the thread and calls we're allowed to run graphics
@ -41,7 +48,17 @@ auto AppAdapterApple::ManagesMainThreadEventLoop() const -> bool {
void AppAdapterApple::DoPushMainThreadRunnable(Runnable* runnable) {
// Kick this along to swift.
BallisticaKit::FromCppPushRawRunnableToMain(runnable);
BallisticaKit::FromCpp::pushRawRunnableToMain(runnable);
}
void AppAdapterApple::OnMainThreadStartApp() {
AppAdapter::OnMainThreadStartApp();
#if BA_USE_STORE_KIT
BallisticaKit::StoreKitContext::onAppStart();
#endif
#if BA_USE_GAME_CENTER
BallisticaKit::GameCenterContext::onAppStart();
#endif
}
void AppAdapterApple::DoApplyAppConfig() { assert(g_base->InLogicThread()); }
@ -122,16 +139,16 @@ auto AppAdapterApple::TryRender() -> bool {
// matches what we have (or until we try for too long or fail at drawing).
seconds_t start_time = g_core->GetAppTimeSeconds();
for (int i = 0; i < 5; ++i) {
if (((std::abs(resize_target_resolution_.x
bool size_differs =
((std::abs(resize_target_resolution_.x
- g_base->graphics_server->screen_pixel_width())
> 0.01f)
|| (std::abs(resize_target_resolution_.y
- g_base->graphics_server->screen_pixel_height())
> 0.01f))
&& g_core->GetAppTimeSeconds() - start_time < 0.1 && result) {
> 0.01f));
if (size_differs && g_core->GetAppTimeSeconds() - start_time < 0.1
&& result) {
result = g_base->graphics_server->TryRender();
} else {
break;
}
}
}
@ -182,13 +199,13 @@ void AppAdapterApple::SetHardwareCursorVisible(bool visible) {
assert(g_core->InMainThread());
#if BA_OSTYPE_MACOS
BallisticaKit::CocoaFromCppSetCursorVisible(visible);
BallisticaKit::CocoaFromCpp::setCursorVisible(visible);
#endif
}
void AppAdapterApple::TerminateApp() {
#if BA_OSTYPE_MACOS
BallisticaKit::CocoaFromCppTerminateApp();
BallisticaKit::CocoaFromCpp::terminateApp();
#else
AppAdapter::TerminateApp();
#endif
@ -205,7 +222,7 @@ auto AppAdapterApple::FullscreenControlAvailable() const -> bool {
auto AppAdapterApple::FullscreenControlGet() const -> bool {
#if BA_OSTYPE_MACOS
return BallisticaKit::CocoaFromCppGetMainWindowIsFullscreen();
return BallisticaKit::CocoaFromCpp::getMainWindowIsFullscreen();
#else
return false;
#endif
@ -213,7 +230,7 @@ auto AppAdapterApple::FullscreenControlGet() const -> bool {
void AppAdapterApple::FullscreenControlSet(bool fullscreen) {
#if BA_OSTYPE_MACOS
return BallisticaKit::CocoaFromCppSetMainWindowFullscreen(fullscreen);
return BallisticaKit::CocoaFromCpp::setMainWindowFullscreen(fullscreen);
#endif
}
@ -224,6 +241,79 @@ auto AppAdapterApple::FullscreenControlKeyShortcut() const
auto AppAdapterApple::HasDirectKeyboardInput() -> bool { return true; };
auto AppAdapterApple::GetKeyRepeatDelay() -> float {
#if BA_OSTYPE_MACOS
return BallisticaKit::CocoaFromCpp::getKeyRepeatDelay();
#else
return AppAdapter::GetKeyRepeatDelay();
#endif
}
auto AppAdapterApple::GetKeyRepeatInterval() -> float {
#if BA_OSTYPE_MACOS
return BallisticaKit::CocoaFromCpp::getKeyRepeatInterval();
#else
return AppAdapter::GetKeyRepeatDelay();
#endif
}
auto AppAdapterApple::DoClipboardIsSupported() -> bool {
#if BA_OSTYPE_MACOS
return BallisticaKit::CocoaFromCpp::clipboardIsSupported();
#else
return AppAdapter::DoClipboardIsSupported();
#endif
}
auto AppAdapterApple::DoClipboardHasText() -> bool {
#if BA_OSTYPE_MACOS
return BallisticaKit::CocoaFromCpp::clipboardHasText();
#else
return AppAdapter::DoClipboardHasText();
#endif
}
void AppAdapterApple::DoClipboardSetText(const std::string& text) {
#if BA_OSTYPE_MACOS
BallisticaKit::CocoaFromCpp::clipboardSetText(text);
#else
AppAdapter::DoClipboardSetText(text);
#endif
}
auto AppAdapterApple::DoClipboardGetText() -> std::string {
#if BA_OSTYPE_MACOS
auto contents = BallisticaKit::CocoaFromCpp::clipboardGetText();
if (contents) {
return std::string(contents.get());
}
throw Exception("No text on clipboard.");
#else
return AppAdapter::DoClipboardGetText();
#endif
}
auto AppAdapterApple::GetKeyName(int keycode) -> std::string {
return MinSDL_GetKeyName(keycode);
}
auto AppAdapterApple::NativeReviewRequestSupported() -> bool {
// StoreKit currently supports this everywhere except tvOS.
if (g_buildconfig.xcode_build() && g_buildconfig.use_store_kit()
&& !g_buildconfig.ostype_tvos()) {
return true;
}
return false;
}
void AppAdapterApple::DoNativeReviewRequest() {
#if BA_XCODE_BUILD && BA_USE_STORE_KIT && !BA_OSTYPE_TVOS
BallisticaKit::StoreKitContext::requestReview();
#else
FatalError("This should not be getting called.");
#endif
}
} // namespace ballistica::base
#endif // BA_XCODE_BUILD

View File

@ -26,6 +26,8 @@ class AppAdapterApple : public AppAdapter {
return val;
}
void OnMainThreadStartApp() override;
auto ManagesMainThreadEventLoop() const -> bool override;
void DoApplyAppConfig() override;
@ -41,6 +43,11 @@ class AppAdapterApple : public AppAdapter {
auto HasDirectKeyboardInput() -> bool override;
void EnableResizeFriendlyMode(int width, int height);
auto GetKeyRepeatDelay() -> float override;
auto GetKeyRepeatInterval() -> float override;
auto GetKeyName(int keycode) -> std::string override;
auto NativeReviewRequestSupported() -> bool override;
protected:
void DoPushMainThreadRunnable(Runnable* runnable) override;
void DoPushGraphicsContextRunnable(Runnable* runnable) override;
@ -50,6 +57,11 @@ class AppAdapterApple : public AppAdapter {
void SetHardwareCursorVisible(bool visible) override;
void TerminateApp() override;
void ApplyGraphicsSettings(const GraphicsSettings* settings) override;
auto DoClipboardIsSupported() -> bool override;
auto DoClipboardHasText() -> bool override;
void DoClipboardSetText(const std::string& text) override;
auto DoClipboardGetText() -> std::string override;
void DoNativeReviewRequest() override;
private:
class ScopedAllowGraphics_;

View File

@ -51,6 +51,8 @@ AppAdapterSDL::AppAdapterSDL() {
}
void AppAdapterSDL::OnMainThreadStartApp() {
AppAdapter::OnMainThreadStartApp();
// App is starting. Let's fire up the ol' SDL.
uint32_t sdl_flags{SDL_INIT_VIDEO | SDL_INIT_JOYSTICK};
@ -79,9 +81,8 @@ void AppAdapterSDL::OnMainThreadStartApp() {
sdl_runnable_event_id_ = SDL_RegisterEvents(1);
assert(sdl_runnable_event_id_ != (uint32_t)-1);
// Note: parent class can add some input devices so need to bring up sdl
// before we let it run. That code should maybe be relocated/refactored.
AppAdapter::OnMainThreadStartApp();
// SDL builds just assume keyboard input is available.
g_base->input->PushCreateKeyboardInputDevices();
if (g_buildconfig.enable_sdl_joysticks()) {
// We want events from joysticks.
@ -415,7 +416,9 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
}
case SDL_KEYDOWN: {
g_base->input->PushKeyPressEvent(event.key.keysym);
if (!event.key.repeat) {
g_base->input->PushKeyPressEvent(event.key.keysym);
}
break;
}
@ -805,23 +808,9 @@ void AppAdapterSDL::CursorPositionForDraw(float* x, float* y) {
auto AppAdapterSDL::FullscreenControlAvailable() const -> bool { return true; }
auto AppAdapterSDL::FullscreenControlKeyShortcut() const
-> std::optional<std::string> {
if (g_buildconfig.ostype_windows()) {
// On Windows we support F11 and Alt+Enter to toggle fullscreen. Let's
// mention Alt+Enter which seems like it might be more commonly used
return "Alt+Enter";
}
if (g_buildconfig.ostype_macos()) {
// The Mac+SDL situation is a bit of a mess. By default, there is 'Enter
// Full Screen' in the window menu which is mapped to fn-F, but that
// will only work if a window was created in SDL as windowed. If we
// fullscreen that window and restart the app, we'll then have a *real*
// fullscreen sdl window and that shortcut won't work anymore. So to
// keep things consistent we advertise ctrl-f which we always handle
// ourselves. Maybe this situation will be cleaned up in SDL 3, but its
// not a huge deal anyway since our Cocoa Mac version behaves cleanly.
return "Ctrl+F";
}
return {};
// On our SDL build we support F11 and Alt+Enter to toggle fullscreen.
// Let's mention Alt+Enter which seems like it might be more commonly used
return "Alt+Enter";
};
auto AppAdapterSDL::SupportsVSync() -> bool const { return true; }
@ -832,6 +821,32 @@ auto AppAdapterSDL::HasDirectKeyboardInput() -> bool {
return true;
}
auto AppAdapterSDL::DoClipboardIsSupported() -> bool { return true; }
auto AppAdapterSDL::DoClipboardHasText() -> bool {
return SDL_HasClipboardText();
}
void AppAdapterSDL::DoClipboardSetText(const std::string& text) {
SDL_SetClipboardText(text.c_str());
}
auto AppAdapterSDL::DoClipboardGetText() -> std::string {
// Go through SDL functionality on SDL based platforms;
// otherwise default to no clipboard.
char* out = SDL_GetClipboardText();
if (out == nullptr) {
throw Exception("Error fetching clipboard contents.", PyExcType::kRuntime);
}
std::string out_s{out};
SDL_free(out);
return out_s;
}
auto AppAdapterSDL::GetKeyName(int keycode) -> std::string {
return SDL_GetKeyName(static_cast<SDL_Keycode>(keycode));
}
} // namespace ballistica::base
#endif // BA_SDL_BUILD

View File

@ -45,6 +45,8 @@ class AppAdapterSDL : public AppAdapter {
auto GetGraphicsSettings() -> GraphicsSettings* override;
auto GetKeyName(int keycode) -> std::string override;
protected:
void DoPushMainThreadRunnable(Runnable* runnable) override;
void RunMainThreadEventLoopToCompletion() override;
@ -52,6 +54,10 @@ class AppAdapterSDL : public AppAdapter {
auto InGraphicsContext() -> bool override;
void DoPushGraphicsContextRunnable(Runnable* runnable) override;
void CursorPositionForDraw(float* x, float* y) override;
auto DoClipboardIsSupported() -> bool override;
auto DoClipboardHasText() -> bool override;
void DoClipboardSetText(const std::string& text) override;
auto DoClipboardGetText() -> std::string override;
private:
class ScopedAllowGraphics_;

View File

@ -5,7 +5,6 @@
#include "ballistica/base/assets/asset.h"
#include "ballistica/base/assets/assets.h"
#include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/support/huffman.h"
#include "ballistica/shared/foundation/event_loop.h"
@ -26,7 +25,7 @@ void AssetsServer::OnAppStartInThread() {
// Ask our thread to give us periodic processing time (close to but
// not *exactly* one second; try to avoid aliasing with similar updates).
process_timer_ = event_loop()->NewTimer(
987, true, NewLambdaRunnable([this] { Process(); }));
987 * 1000, true, NewLambdaRunnable([this] { Process(); }).Get());
}
void AssetsServer::PushPendingPreload(Object::Ref<Asset>* asset_ref_ptr) {
@ -259,7 +258,7 @@ void AssetsServer::Process() {
// we're writing a replay.. otherwise just sleep indefinitely.
if (pending_preloads_.empty() && pending_preloads_audio_.empty()) {
if (writing_replay_) {
process_timer_->SetLength(1000);
process_timer_->SetLength(1000 * 1000);
} else {
process_timer_->SetLength(-1);
}

View File

@ -15,7 +15,7 @@
namespace ballistica::base {
static void rgba8888_unpremultiply_in_place(uint8_t* src, size_t cb) {
static void Rgba8888UnpremultiplyInPlace_(uint8_t* src, size_t cb) {
// Compute the actual number of pixel elements in the buffer.
size_t cpel = cb / 4;
auto* psrc = src;
@ -157,7 +157,7 @@ void TextureAsset::DoPreload() {
auto* buffer = static_cast<uint8_t*>(malloc(buffer_size));
preload_datas_[0].buffers[0] = buffer;
memcpy(buffer, pixels, buffer_size);
rgba8888_unpremultiply_in_place(buffer, buffer_size);
Rgba8888UnpremultiplyInPlace_(buffer, buffer_size);
preload_datas_[0].widths[0] = width;
preload_datas_[0].heights[0] = height;
preload_datas_[0].formats[0] = TextureFormat::kRGBA_8888;

View File

@ -32,9 +32,9 @@ LPALCDEVICEPAUSESOFT alcDevicePauseSOFT;
LPALCDEVICERESUMESOFT alcDeviceResumeSOFT;
#endif
const int kAudioProcessIntervalNormal{500};
const int kAudioProcessIntervalFade{50};
const int kAudioProcessIntervalPendingLoad{1};
const int kAudioProcessIntervalNormal{500 * 1000};
const int kAudioProcessIntervalFade{50 * 1000};
const int kAudioProcessIntervalPendingLoad{1 * 1000};
#if BA_DEBUG_BUILD || BA_TEST_BUILD
const bool kShowInUseSounds{};
@ -161,7 +161,7 @@ void AudioServer::OnAppStartInThread_() {
// Get our thread to give us periodic processing time.
process_timer_ =
event_loop()->NewTimer(kAudioProcessIntervalNormal, true,
NewLambdaRunnable([this] { Process_(); }));
NewLambdaRunnable([this] { Process_(); }).Get());
#if BA_ENABLE_AUDIO

View File

@ -9,6 +9,7 @@
#include "ballistica/base/audio/audio_server.h"
#include "ballistica/base/dynamics/bg/bg_dynamics_server.h"
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/graphics/support/screen_messages.h"
#include "ballistica/base/graphics/text/text_graphics.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/logic/logic.h"
@ -20,12 +21,12 @@
#include "ballistica/base/python/class/python_class_feature_set_data.h"
#include "ballistica/base/python/support/python_context_call.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/support/app_timer.h"
#include "ballistica/base/support/base_build_switches.h"
#include "ballistica/base/support/huffman.h"
#include "ballistica/base/support/plus_soft.h"
#include "ballistica/base/support/stdio_console.h"
#include "ballistica/base/support/stress_test.h"
#include "ballistica/base/ui/dev_console.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/base/ui/ui_delegate.h"
#include "ballistica/core/python/core_python.h"
#include "ballistica/shared/foundation/event_loop.h"
@ -39,7 +40,7 @@ core::CoreFeatureSet* g_core{};
BaseFeatureSet* g_base{};
BaseFeatureSet::BaseFeatureSet()
: app_adapter{AppAdapter::Create()},
: app_adapter{BaseBuildSwitches::CreateAppAdapter()},
app_config{new AppConfig()},
app_mode_{AppModeEmpty::GetSingleton()},
assets{new Assets()},
@ -51,7 +52,7 @@ BaseFeatureSet::BaseFeatureSet()
bg_dynamics_server{g_core->HeadlessMode() ? nullptr
: new BGDynamicsServer},
context_ref{new ContextRef(nullptr)},
graphics{Graphics::Create()},
graphics{BaseBuildSwitches::CreateGraphics()},
graphics_server{new GraphicsServer()},
huffman{new Huffman()},
input{new Input()},
@ -59,11 +60,10 @@ BaseFeatureSet::BaseFeatureSet()
network_reader{new NetworkReader()},
network_writer{new NetworkWriter()},
networking{new Networking()},
platform{BasePlatform::Create()},
platform{BaseBuildSwitches::CreatePlatform()},
python{new BasePython()},
stdio_console{g_buildconfig.enable_stdio_console() ? new StdioConsole()
: nullptr},
stress_test_{new StressTest()},
text_graphics{new TextGraphics()},
ui{new UI()},
utils{new Utils()} {
@ -151,6 +151,43 @@ auto BaseFeatureSet::IsBaseCompletelyImported() -> bool {
return base_import_completed_ && base_native_import_completed_;
}
void BaseFeatureSet::SuccessScreenMessage() {
if (auto* event_loop = logic->event_loop()) {
event_loop->PushCall([this] {
python->objs().Get(BasePython::ObjID::kSuccessMessageCall).Call();
});
} else {
Log(LogLevel::kError,
"SuccessScreenMessage called without logic event_loop in place.");
}
}
void BaseFeatureSet::ErrorScreenMessage() {
if (auto* event_loop = logic->event_loop()) {
event_loop->PushCall([this] {
python->objs().Get(BasePython::ObjID::kErrorMessageCall).Call();
});
} else {
Log(LogLevel::kError,
"ErrorScreenMessage called without logic event_loop in place.");
}
}
auto BaseFeatureSet::GetV2AccountID() -> std::optional<std::string> {
auto gil = Python::ScopedInterpreterLock();
auto result =
python->objs().Get(BasePython::ObjID::kGetV2AccountIdCall).Call();
if (result.Exists()) {
if (result.ValueIsNone()) {
return {};
}
return result.ValueAsString();
} else {
Log(LogLevel::kError, "GetV2AccountID() py call errored.");
return {};
}
}
void BaseFeatureSet::OnAssetsAvailable() {
assert(InLogicThread());
@ -465,8 +502,9 @@ auto BaseFeatureSet::InGraphicsContext() const -> bool {
void BaseFeatureSet::ScreenMessage(const std::string& s,
const Vector3f& color) {
logic->event_loop()->PushCall(
[this, s, color] { graphics->AddScreenMessage(s, color); });
logic->event_loop()->PushCall([this, s, color] {
graphics->screenmessages->AddScreenMessage(s, color);
});
}
void BaseFeatureSet::DoV1CloudLog(const std::string& msg) {
@ -721,4 +759,8 @@ void BaseFeatureSet::QuitApp(bool confirm, QuitType quit_type) {
}
}
void BaseFeatureSet::PushMainThreadRunnable(Runnable* runnable) {
app_adapter->DoPushMainThreadRunnable(runnable);
}
} // namespace ballistica::base

View File

@ -53,6 +53,7 @@ class ClassicSoftInterface;
class CollisionMeshAsset;
class CollisionCache;
class DevConsole;
class DisplayTimer;
class Context;
class ContextRef;
class DataAsset;
@ -99,13 +100,14 @@ class RenderPass;
class RenderTarget;
class RemoteAppServer;
class RemoteControlInput;
class Repeater;
class ScoreToBeat;
class ScreenMessages;
class AppAdapterSDL;
class SDLContext;
class SoundAsset;
class SpriteMesh;
class StdioConsole;
class StressTest;
class Module;
class TestInput;
class TextGroup;
@ -630,6 +632,13 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
return *context_ref;
}
/// Utility call to print 'Success!' with a happy sound.
/// Safe to call from any thread.
void SuccessScreenMessage();
/// Utility call to print 'Error.' with a beep sound.
/// Safe to call from any thread.
void ErrorScreenMessage();
void SetCurrentContext(const ContextRef& context);
/// Try to load the plus feature-set and return whether it is available.
@ -721,6 +730,12 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// loading.
void OnAssetsAvailable();
void PushMainThreadRunnable(Runnable* runnable) override;
/// Return the currently signed in V2 account id as
/// reported by the Python layer.
auto GetV2AccountID() -> std::optional<std::string>;
// Const subsystems.
AppAdapter* const app_adapter;
AppConfig* const app_config;
@ -750,8 +765,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
void set_app_mode(AppMode* mode);
auto* app_mode() const { return app_mode_; }
auto* stress_test() const { return stress_test_; }
/// Whether we're running under ballisticakit_server.py
/// (affects some app behavior).
auto server_wrapper_managed() { return server_wrapper_managed_; }
@ -774,7 +787,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
AppMode* app_mode_;
PlusSoftInterface* plus_soft_{};
ClassicSoftInterface* classic_soft_{};
StressTest* stress_test_;
std::mutex shutdown_suppress_lock_;
bool shutdown_suppress_disallowed_{};

View File

@ -185,9 +185,11 @@ void RendererGL::CheckGLCapabilities_() {
basestr = "OpenGL";
}
Log(LogLevel::kInfo, std::string("Using ") + basestr + " (vendor: " + vendor
+ ", renderer: " + renderer
+ ", version: " + version_str + ").");
if (g_buildconfig.debug_build()) {
Log(LogLevel::kInfo, std::string("Using ") + basestr + " (vendor: " + vendor
+ ", renderer: " + renderer
+ ", version: " + version_str + ").");
}
// Build a vector of extensions. Newer GLs give us extensions as lists
// already, but on older ones we may need to break a single string apart
@ -453,44 +455,51 @@ void RendererGL::CheckGLCapabilities_() {
}
auto RendererGL::GetMSAASamplesForFramebuffer_(int width, int height) -> int {
#if BA_RIFT_BUILD
return 4;
#else
// We currently aim for 4 up to 800 height and 2 beyond that.
if (height > 800) {
return 2;
if (g_buildconfig.ostype_android()) {
// We currently aim for 4 up to 800 height and 2 beyond that.
if (height > 800) {
return 2;
} else {
return 4;
}
} else {
return 4;
}
#endif
}
void RendererGL::UpdateMSAAEnabled_() {
#if BA_RIFT_BUILD
if (msaa_max_samples_rgb8_ > 0) {
if (g_buildconfig.ostype_macos()) {
// Let's go ahead and flip this on for Apple Silicon Macs.
#if __aarch64__
enable_msaa_ = true;
#else
enable_msaa_ = false;
#endif
} else if (g_buildconfig.rift_build()) {
if (msaa_max_samples_rgb8_ > 0) {
enable_msaa_ = true;
} else {
enable_msaa_ = false;
}
} else if (g_buildconfig.ostype_android()) {
// lets allow full 1080p msaa with newer stuff..
int max_msaa_res = is_tegra_k1_ ? 1200 : 800;
// To start, see if it looks like we support msaa on paper.
enable_msaa_ =
((screen_render_target()->physical_height()
<= static_cast<float>(max_msaa_res))
&& (msaa_max_samples_rgb8_ > 0) && (msaa_max_samples_rgb565_ > 0));
// Ok, lets be careful here; msaa blitting/etc seems to be particular in
// terms of supported formats/etc so let's only enable it on
// explicitly-tested hardware for now.
if (!is_tegra_4_ && !is_tegra_k1_ && !is_recent_adreno_) {
enable_msaa_ = false;
}
} else {
enable_msaa_ = false;
}
#else
// lets allow full 1080p msaa with newer stuff..
int max_msaa_res = is_tegra_k1_ ? 1200 : 800;
// To start, see if it looks like we support msaa on paper.
enable_msaa_ =
((screen_render_target()->physical_height()
<= static_cast<float>(max_msaa_res))
&& (msaa_max_samples_rgb8_ > 0) && (msaa_max_samples_rgb565_ > 0));
// Ok, lets be careful here; msaa blitting/etc seems to be particular in
// terms of supported formats/etc so let's only enable it on
// explicitly-tested hardware for now.
if (!is_tegra_4_ && !is_tegra_k1_ && !is_recent_adreno_) {
enable_msaa_ = false;
}
#endif // BA_RIFT_BUILD
}
auto RendererGL::IsMSAAEnabled() const -> bool { return enable_msaa_; }

View File

@ -5,44 +5,29 @@
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/app_mode/app_mode.h"
#include "ballistica/base/dynamics/bg/bg_dynamics.h"
#include "ballistica/base/graphics/component/empty_component.h"
#include "ballistica/base/graphics/component/object_component.h"
#include "ballistica/base/graphics/component/post_process_component.h"
#include "ballistica/base/graphics/component/simple_component.h"
#include "ballistica/base/graphics/component/special_component.h"
#include "ballistica/base/graphics/component/sprite_component.h"
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/graphics/graphics_vr.h"
#include "ballistica/base/graphics/support/camera.h"
#include "ballistica/base/graphics/support/net_graph.h"
#include "ballistica/base/graphics/text/text_graphics.h"
#include "ballistica/base/graphics/support/screen_messages.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/platform/base_platform.h"
#include "ballistica/base/python/support/python_context_call.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/ui/dev_console.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/core/core.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/generic/utils.h"
#include "ballistica/shared/python/python.h"
namespace ballistica::base {
const float kScreenMessageZDepth{-0.06f};
const float kScreenMeshZDepth{-0.05f};
const float kScreenTextZDepth{-0.06f};
const float kProgressBarZDepth{0.0f};
const int kProgressBarFadeTime{500};
const float kDebugImgZDepth{-0.04f};
auto Graphics::Create() -> Graphics* {
#if BA_VR_BUILD
return new GraphicsVR();
#else
return new Graphics();
#endif
}
const float kScreenMeshZDepth{-0.05f};
auto Graphics::IsShaderTransparent(ShadingType c) -> bool {
switch (c) {
@ -93,7 +78,7 @@ auto Graphics::IsShaderTransparent(ShadingType c) -> bool {
}
}
Graphics::Graphics() = default;
Graphics::Graphics() : screenmessages{new ScreenMessages()} {}
Graphics::~Graphics() = default;
void Graphics::OnAppStart() { assert(g_base->InLogicThread()); }
@ -182,7 +167,7 @@ void Graphics::UpdateInitialGraphicsSettingsSend_() {
void Graphics::StepDisplayTime() { assert(g_base->InLogicThread()); }
void Graphics::AddCleanFrameCommand(const Object::Ref<PythonContextCall>& c) {
BA_PRECONDITION(g_base->InLogicThread());
assert(g_base->InLogicThread());
clean_frame_commands_.push_back(c);
}
@ -231,11 +216,9 @@ auto Graphics::VSyncFromAppConfig() -> VSyncRequest {
}
auto Graphics::GraphicsQualityFromAppConfig() -> GraphicsQualityRequest {
// Graphics quality.
std::string gqualstr =
g_base->app_config->Resolve(AppConfig::StringID::kGraphicsQuality);
GraphicsQualityRequest graphics_quality_requested;
if (gqualstr == "Auto") {
graphics_quality_requested = GraphicsQualityRequest::kAuto;
} else if (gqualstr == "Higher") {
@ -359,40 +342,6 @@ auto Graphics::GetShadowDensity(float x, float y, float z) -> float {
}
}
class Graphics::ScreenMessageEntry {
public:
ScreenMessageEntry(std::string s_in, bool align_left_in, uint32_t c,
const Vector3f& color_in, TextureAsset* texture_in,
TextureAsset* tint_texture_in, const Vector3f& tint_in,
const Vector3f& tint2_in)
: align_left(align_left_in),
creation_time(c),
s_raw(std::move(s_in)),
color(color_in),
texture(texture_in),
tint_texture(tint_texture_in),
tint(tint_in),
tint2(tint2_in) {}
auto GetText() -> TextGroup&;
void UpdateTranslation();
bool align_left;
uint32_t creation_time;
Vector3f color;
Vector3f tint;
Vector3f tint2;
std::string s_raw;
std::string s_translated;
Object::Ref<TextureAsset> texture;
Object::Ref<TextureAsset> tint_texture;
float v_smoothed{};
bool translation_dirty{true};
bool mesh_dirty{true};
millisecs_t smooth_time{};
private:
Object::Ref<TextGroup> s_mesh_;
};
// Draw controls and things that lie on top of the action.
void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
RenderPass* pass = frame_def->overlay_pass();
@ -410,7 +359,6 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
last_fps_ = total_frames_rendered - last_total_frames_rendered_;
last_total_frames_rendered_ = total_frames_rendered;
}
float v{};
if (show_fps_) {
char fps_str[32];
@ -472,7 +420,7 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
{
auto xf = c.ScopedTransform();
c.Translate(14.0f + (show_fps_ ? 30.0f : 0.0f), 0.1f,
kScreenMessageZDepth);
kScreenTextZDepth);
c.Scale(0.7f, 0.7f);
c.DrawMesh(ping_text_group_->GetElementMesh(e));
}
@ -500,7 +448,7 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
c.SetFlatness(1.0f);
{
auto xf = c.ScopedTransform();
c.Translate(4.0f, (show_fps_ ? 66.0f : 40.0f), kScreenMessageZDepth);
c.Translate(4.0f, (show_fps_ ? 66.0f : 40.0f), kScreenTextZDepth);
c.Scale(0.7f, 0.7f);
c.DrawMesh(net_info_text_group_->GetElementMesh(e));
}
@ -528,382 +476,7 @@ void Graphics::DrawMiscOverlays(FrameDef* frame_def) {
}
}
// Screen messages (bottom).
{
// Delete old ones.
if (!screen_messages_.empty()) {
millisecs_t cutoff;
if (g_core->GetAppTimeMillisecs() > 5000) {
cutoff = g_core->GetAppTimeMillisecs() - 5000;
for (auto i = screen_messages_.begin(); i != screen_messages_.end();) {
if (i->creation_time < cutoff) {
auto next = i;
next++;
screen_messages_.erase(i);
i = next;
} else {
i++;
}
}
}
}
// Delete if we have too many.
while ((screen_messages_.size()) > 4) {
screen_messages_.erase(screen_messages_.begin());
}
// Draw all existing.
if (!screen_messages_.empty()) {
bool vr = g_core->IsVRMode();
// These are less disruptive in the middle for menus but at the bottom
// during gameplay.
float start_v = g_base->graphics->screen_virtual_height() * 0.05f;
float scale;
switch (g_base->ui->scale()) {
case UIScale::kSmall:
scale = 1.5f;
break;
case UIScale::kMedium:
scale = 1.2f;
break;
default:
scale = 1.0f;
break;
}
// Shadows.
{
SimpleComponent c(pass);
c.SetTransparent(true);
c.SetTexture(
g_base->assets->SysTexture(SysTextureID::kSoftRectVertical));
float screen_width = g_base->graphics->screen_virtual_width();
v = start_v;
millisecs_t youngest_age = 9999;
for (auto i = screen_messages_.rbegin(); i != screen_messages_.rend();
i++) {
// Update the translation if need be.
i->UpdateTranslation();
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
youngest_age = std::min(youngest_age, age);
float s_extra = 1.0f;
if (age < 100) {
s_extra = std::min(1.2f, 1.2f * (static_cast<float>(age) / 100.0f));
} else if (age < 150) {
s_extra =
1.2f - 0.2f * ((150.0f - static_cast<float>(age)) / 50.0f);
}
float a;
if (age > 3000) {
a = 1.0f - static_cast<float>(age - 3000) / 2000;
} else {
a = 1;
}
a *= 0.8f;
if (vr) {
a *= 0.8f;
}
if (i->translation_dirty) {
BA_LOG_ONCE(
LogLevel::kWarning,
"Found dirty translation on screenmessage draw pass 1; raw="
+ i->s_raw);
}
float str_height =
g_base->text_graphics->GetStringHeight(i->s_translated.c_str());
float str_width =
g_base->text_graphics->GetStringWidth(i->s_translated.c_str());
if ((str_width * scale) > (screen_width - 40)) {
s_extra *= ((screen_width - 40) / (str_width * scale));
}
float r = i->color.x;
float g = i->color.y;
float b = i->color.z;
GetSafeColor(&r, &g, &b);
float v_extra = scale * (static_cast<float>(youngest_age) * 0.01f);
float fade;
if (age < 100) {
fade = 1.0f;
} else {
fade = std::max(0.0f, (200.0f - static_cast<float>(age)) / 100.0f);
}
c.SetColor(r * fade, g * fade, b * fade, a);
{
auto xf = c.ScopedTransform();
// This logic needs to run at a fixed hz or it breaks on high frame
// rates.
auto now_millisecs = pass->frame_def()->display_time_millisecs();
i->smooth_time = std::max(i->smooth_time, now_millisecs - 100);
while (i->smooth_time < now_millisecs) {
i->smooth_time += 1000 / 60;
if (i->v_smoothed == 0.0f) {
i->v_smoothed = v + v_extra;
} else {
float smoothing = 0.8f;
i->v_smoothed = smoothing * i->v_smoothed
+ (1.0f - smoothing) * (v + v_extra);
}
}
c.Translate(screen_width * 0.5f, i->v_smoothed,
vr ? 60 : kScreenMessageZDepth);
if (vr) {
// Let's drop down a bit in vr mode.
c.Translate(0, -10.0f, 0);
c.Scale((str_width + 60) * scale * s_extra,
(str_height + 20) * scale * s_extra);
// Align our bottom with where we just scaled from.
c.Translate(0, 0.5f, 0);
} else {
c.Scale((str_width + 110) * scale * s_extra,
(str_height + 40) * scale * s_extra);
// Align our bottom with where we just scaled from.
c.Translate(0, 0.5f, 0);
}
c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
}
v += scale * (36 + str_height);
if (v > g_base->graphics->screen_virtual_height() + 30) {
break;
}
}
c.Submit();
}
// Now the strings themselves.
{
SimpleComponent c(pass);
c.SetTransparent(true);
float screen_width = g_base->graphics->screen_virtual_width();
v = start_v;
millisecs_t youngest_age = 9999;
for (auto i = screen_messages_.rbegin(); i != screen_messages_.rend();
i++) {
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
youngest_age = std::min(youngest_age, age);
float s_extra = 1.0f;
if (age < 100) {
s_extra = std::min(1.2f, 1.2f * (static_cast<float>(age) / 100.0f));
} else if (age < 150) {
s_extra =
1.2f - 0.2f * ((150.0f - static_cast<float>(age)) / 50.0f);
}
float a;
if (age > 3000) {
a = 1.0f - static_cast<float>(age - 3000) / 2000;
} else {
a = 1;
}
if (i->translation_dirty) {
BA_LOG_ONCE(
LogLevel::kWarning,
"Found dirty translation on screenmessage draw pass 2; raw="
+ i->s_raw);
}
float str_height =
g_base->text_graphics->GetStringHeight(i->s_translated.c_str());
float str_width =
g_base->text_graphics->GetStringWidth(i->s_translated.c_str());
if ((str_width * scale) > (screen_width - 40)) {
s_extra *= ((screen_width - 40) / (str_width * scale));
}
float r = i->color.x;
float g = i->color.y;
float b = i->color.z;
GetSafeColor(&r, &g, &b, 0.85f);
int elem_count = i->GetText().GetElementCount();
for (int e = 0; e < elem_count; e++) {
// Gracefully skip unloaded textures.
TextureAsset* t = i->GetText().GetElementTexture(e);
if (!t->preloaded()) {
continue;
}
c.SetTexture(t);
if (i->GetText().GetElementCanColor(e)) {
c.SetColor(r, g, b, a);
} else {
c.SetColor(1, 1, 1, a);
}
c.SetFlatness(i->GetText().GetElementMaxFlatness(e));
{
auto xf = c.ScopedTransform();
c.Translate(screen_width * 0.5f, i->v_smoothed,
vr ? 150 : kScreenMessageZDepth);
c.Scale(scale * s_extra, scale * s_extra);
c.Translate(0, 20);
c.DrawMesh(i->GetText().GetElementMesh(e));
}
}
v += scale * (36 + str_height);
if (v > g_base->graphics->screen_virtual_height() + 30) {
break;
}
}
c.Submit();
}
}
}
// Screen messages (top).
{
// Delete old ones.
if (!screen_messages_top_.empty()) {
millisecs_t cutoff;
if (g_core->GetAppTimeMillisecs() > 5000) {
cutoff = g_core->GetAppTimeMillisecs() - 5000;
for (auto i = screen_messages_top_.begin();
i != screen_messages_top_.end();) {
if (i->creation_time < cutoff) {
auto next = i;
next++;
screen_messages_top_.erase(i);
i = next;
} else {
i++;
}
}
}
}
// Delete if we have too many.
while ((screen_messages_top_.size()) > 6) {
screen_messages_top_.erase(screen_messages_top_.begin());
}
if (!screen_messages_top_.empty()) {
SimpleComponent c(pass);
c.SetTransparent(true);
// Draw all existing.
float h = pass->virtual_width() - 300.0f;
v = g_base->graphics->screen_virtual_height() - 50.0f;
float v_base = g_base->graphics->screen_virtual_height();
float last_v = -999.0f;
float min_spacing = 25.0f;
for (auto i = screen_messages_top_.rbegin();
i != screen_messages_top_.rend(); i++) {
// Update the translation if need be.
i->UpdateTranslation();
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
float s_extra = 1.0f;
if (age < 100) {
s_extra = std::min(1.1f, 1.1f * (static_cast<float>(age) / 100.0f));
} else if (age < 150) {
s_extra = 1.1f - 0.1f * ((150.0f - static_cast<float>(age)) / 50.0f);
}
float a;
if (age > 3000) {
a = 1.0f - static_cast<float>(age - 3000) / 2000;
} else {
a = 1;
}
// This logic needs to run at a fixed hz or it breaks on high frame
// rates.
auto now_millisecs = pass->frame_def()->display_time_millisecs();
i->smooth_time = std::max(i->smooth_time, now_millisecs - 100);
while (i->smooth_time < now_millisecs) {
i->smooth_time += 1000 / 60;
i->v_smoothed += 0.1f;
if (i->v_smoothed - last_v < min_spacing) {
i->v_smoothed +=
8.0f * (1.0f - ((i->v_smoothed - last_v) / min_spacing));
}
}
last_v = i->v_smoothed;
// Draw the image if they provided one.
if (i->texture.Exists()) {
c.Submit();
SimpleComponent c2(pass);
c2.SetTransparent(true);
c2.SetTexture(i->texture);
if (i->tint_texture.Exists()) {
c2.SetColorizeTexture(i->tint_texture.Get());
c2.SetColorizeColor(i->tint.x, i->tint.y, i->tint.z);
c2.SetColorizeColor2(i->tint2.x, i->tint2.y, i->tint2.z);
c2.SetMaskTexture(
g_base->assets->SysTexture(SysTextureID::kCharacterIconMask));
}
c2.SetColor(1, 1, 1, a);
{
auto xf = c2.ScopedTransform();
c2.Translate(h - 14, v_base + 10 + i->v_smoothed,
kScreenMessageZDepth);
c2.Scale(22.0f * s_extra, 22.0f * s_extra);
c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
}
c2.Submit();
}
float r = i->color.x;
float g = i->color.y;
float b = i->color.z;
GetSafeColor(&r, &g, &b);
int elem_count = i->GetText().GetElementCount();
for (int e = 0; e < elem_count; e++) {
// Gracefully skip unloaded textures.
TextureAsset* t = i->GetText().GetElementTexture(e);
if (!t->preloaded()) {
continue;
}
c.SetTexture(t);
if (i->GetText().GetElementCanColor(e)) {
c.SetColor(r, g, b, a);
} else {
c.SetColor(1, 1, 1, a);
}
c.SetShadow(-0.003f * i->GetText().GetElementUScale(e),
-0.003f * i->GetText().GetElementVScale(e), 0.0f,
1.0f * a);
c.SetFlatness(i->GetText().GetElementMaxFlatness(e));
c.SetMaskUV2Texture(i->GetText().GetElementMaskUV2Texture(e));
{
auto xf = c.ScopedTransform();
c.Translate(h, v_base + 2 + i->v_smoothed, kScreenMessageZDepth);
c.Scale(0.6f * s_extra, 0.6f * s_extra);
c.DrawMesh(i->GetText().GetElementMesh(e));
}
}
assert(!i->translation_dirty);
v -= g_base->text_graphics->GetStringHeight(i->s_translated.c_str())
* 0.6f
+ 8.0f;
}
c.Submit();
}
}
screenmessages->DrawMiscOverlays(frame_def);
}
auto Graphics::GetDebugGraph(const std::string& name, bool smoothed)
@ -949,32 +522,6 @@ void Graphics::GetSafeColor(float* red, float* green, float* blue,
}
}
void Graphics::AddScreenMessage(const std::string& msg, const Vector3f& color,
bool top, TextureAsset* texture,
TextureAsset* tint_texture,
const Vector3f& tint, const Vector3f& tint2) {
assert(g_base->InLogicThread());
// So we know we're always dealing with valid utf8.
std::string m = Utils::GetValidUTF8(msg.c_str(), "ga9msg");
if (top) {
float start_v = -40.0f;
if (!screen_messages_top_.empty()) {
start_v = std::min(
start_v,
std::max(-100.0f, screen_messages_top_.back().v_smoothed - 25.0f));
}
screen_messages_top_.emplace_back(m, true, g_core->GetAppTimeMillisecs(),
color, texture, tint_texture, tint,
tint2);
screen_messages_top_.back().v_smoothed = start_v;
} else {
screen_messages_.emplace_back(m, false, g_core->GetAppTimeMillisecs(),
color, texture, tint_texture, tint, tint2);
}
}
void Graphics::Reset() {
assert(g_base->InLogicThread());
fade_ = 0;
@ -984,9 +531,7 @@ void Graphics::Reset() {
camera_ = Object::New<Camera>();
}
// Wipe out top screen messages since they might be using textures that are
// being reset. Bottom ones are ok since they have no textures.
screen_messages_top_.clear();
screenmessages->Reset();
}
void Graphics::InitInternalComponents(FrameDef* frame_def) {
@ -1058,7 +603,7 @@ void Graphics::ClearFrameDefDeleteList() {
}
void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) {
BA_PRECONDITION(g_base->InLogicThread());
assert(g_base->InLogicThread());
// If there's an ourstanding fade-end command, go ahead and run it.
// (otherwise, overlapping fades can cause things to get lost)
if (fade_end_call_.Exists()) {
@ -1186,7 +731,7 @@ void Graphics::DrawDevUI(FrameDef* frame_def) {
void Graphics::BuildAndPushFrameDef() {
assert(g_base->InLogicThread());
BA_PRECONDITION_FATAL(g_base->logic->app_bootstrapping_complete());
assert(g_base->logic->app_bootstrapping_complete());
assert(camera_.Exists());
assert(!g_core->HeadlessMode());
@ -1611,16 +1156,6 @@ void Graphics::DrawBlotches(FrameDef* frame_def) {
}
}
void Graphics::ClearScreenMessageTranslations() {
assert(g_base && g_base->InLogicThread());
for (auto&& i : screen_messages_) {
i.translation_dirty = true;
}
for (auto&& i : screen_messages_top_) {
i.translation_dirty = true;
}
}
void Graphics::ReturnCompletedFrameDef(FrameDef* frame_def) {
std::scoped_lock lock(frame_def_delete_list_mutex_);
g_base->graphics->frame_def_delete_list_.push_back(frame_def);
@ -1951,26 +1486,6 @@ void Graphics::DrawRadialMeter(MeshIndexedSimpleFull* m, float amt) {
}
}
auto Graphics::ScreenMessageEntry::GetText() -> TextGroup& {
if (translation_dirty) {
BA_LOG_ONCE(
LogLevel::kWarning,
"Found dirty translation on screenmessage GetText; raw=" + s_raw);
}
if (!s_mesh_.Exists()) {
s_mesh_ = Object::New<TextGroup>();
mesh_dirty = true;
}
if (mesh_dirty) {
s_mesh_->SetText(
s_translated,
align_left ? TextMesh::HAlign::kLeft : TextMesh::HAlign::kCenter,
TextMesh::VAlign::kBottom);
mesh_dirty = false;
}
return *s_mesh_;
}
void Graphics::OnScreenSizeChange() {}
void Graphics::CalcVirtualRes_(float* x, float* y) {
@ -2024,15 +1539,6 @@ void Graphics::SetScreenResolution(float x, float y) {
UpdateInitialGraphicsSettingsSend_();
}
void Graphics::ScreenMessageEntry::UpdateTranslation() {
if (translation_dirty) {
s_translated = g_base->assets->CompileResourceString(
s_raw, "Graphics::ScreenMessageEntry::UpdateTranslation");
translation_dirty = false;
mesh_dirty = true;
}
}
auto Graphics::CubeMapFromReflectionType(ReflectionType reflection_type)
-> SysCubeMapTextureID {
switch (reflection_type) {
@ -2112,8 +1618,8 @@ void Graphics::LanguageChanged() {
Log(LogLevel::kWarning,
"Graphics::LanguageChanged() called during draw; should not happen.");
}
// Also clear translations on all screen-messages.
ClearScreenMessageTranslations();
screenmessages->ClearScreenMessageTranslations();
}
auto Graphics::GraphicsQualityFromRequest(GraphicsQualityRequest request,
@ -2136,6 +1642,7 @@ auto Graphics::GraphicsQualityFromRequest(GraphicsQualityRequest request,
return GraphicsQuality::kLow;
}
}
auto Graphics::TextureQualityFromRequest(TextureQualityRequest request,
TextureQuality auto_val)
-> TextureQuality {

View File

@ -53,8 +53,7 @@ const float kCursorZDepth{0.9f};
// Client class for graphics operations (used from the logic thread).
class Graphics {
public:
/// Instantiate the Graphics subclass for the current build.
static auto Create() -> Graphics*;
Graphics();
void OnAppStart();
void OnAppPause();
@ -119,8 +118,6 @@ class Graphics {
return res_y_virtual_;
}
void ClearScreenMessageTranslations();
// Given a point in space, returns the shadow density that should be drawn
// into the shadow pass. Does this belong somewhere else?
auto GetShadowDensity(float x, float y, float z) -> float;
@ -128,21 +125,13 @@ class Graphics {
static void GetSafeColor(float* r, float* g, float* b,
float target_intensity = 0.6f);
// Print a message to the on-screen list.
void AddScreenMessage(const std::string& msg,
const Vector3f& color = {1, 1, 1}, bool top = false,
TextureAsset* texture = nullptr,
TextureAsset* tint_texture = nullptr,
const Vector3f& tint = {1, 1, 1},
const Vector3f& tint2 = {1, 1, 1});
// Fade the local screen in or out over the given time period.
void FadeScreen(bool to, millisecs_t time, PyObject* endcall);
static void DrawRadialMeter(MeshIndexedSimpleFull* m, float amt);
// Ways to add a few simple component types quickly.
// (uses particle rendering for efficient batches).
// Ways to add a few simple component types quickly (uses particle
// rendering for efficient batches).
void DrawBlotch(const Vector3f& pos, float size, float r, float g, float b,
float a) {
DoDrawBlotch(&blotch_indices_, &blotch_verts_, pos, size, r, g, b, a);
@ -240,13 +229,8 @@ class Graphics {
float upper_top);
void ReleaseFadeEndCommand();
// auto tv_border() const {
// assert(g_base->InLogicThread());
// return tv_border_;
// }
// Nodes that draw flat stuff into the overlay pass should query this z value
// for where to draw in z.
// Nodes that draw flat stuff into the overlay pass should query this z
// value for where to draw in z.
auto overlay_node_z_depth() {
fetched_overlay_node_z_depth_ = true;
return overlay_node_z_depth_;
@ -296,8 +280,8 @@ class Graphics {
void AddMeshDataCreate(MeshData* d);
void AddMeshDataDestroy(MeshData* d);
// For debugging: ensures that only transparent or opaque components
// are submitted while enabled.
// For debugging: ensures that only transparent or opaque components are
// submitted while enabled.
auto drawing_transparent_only() const { return drawing_transparent_only_; }
void set_drawing_transparent_only(bool val) {
drawing_transparent_only_ = val;
@ -362,8 +346,8 @@ class Graphics {
}
/// For temporary use in arbitrary threads. This should be removed when
/// possible and replaced with proper safe thread-specific access
/// patterns (so we can support switching renderers/etc.).
/// possible and replaced with proper safe thread-specific access patterns
/// (so we can support switching renderers/etc.).
auto placeholder_client_context() const -> const GraphicsClientContext* {
// Using this from arbitrary threads is currently ok currently since
// context never changes once set. Will need to kill this call once that
@ -372,10 +356,9 @@ class Graphics {
return client_context_snapshot_.Get()->Get();
}
protected:
class ScreenMessageEntry;
ScreenMessages* const screenmessages;
Graphics();
protected:
virtual ~Graphics();
virtual void DoDrawFade(FrameDef* frame_def, float amt);
static void CalcVirtualRes_(float* x, float* y);
@ -453,8 +436,6 @@ class Graphics {
std::map<std::string, Object::Ref<NetGraph>> debug_graphs_;
std::mutex frame_def_delete_list_mutex_;
std::list<Object::Ref<PythonContextCall>> clean_frame_commands_;
std::list<ScreenMessageEntry> screen_messages_;
std::list<ScreenMessageEntry> screen_messages_top_;
std::vector<FrameDef*> recycle_frame_defs_;
std::vector<uint16_t> blotch_indices_;
std::vector<VertexSprite> blotch_verts_;
@ -473,23 +454,22 @@ class Graphics {
float gyro_mag_test_{};
float overlay_node_z_depth_{};
float progress_bar_progress_{};
float screen_gamma_{1.0f};
float shadow_lower_bottom_{-4.0f};
float shadow_lower_top_{4.0f};
float shadow_upper_bottom_{30.0f};
float shadow_upper_top_{40.0f};
seconds_t last_cursor_visibility_event_time_{};
millisecs_t fade_start_{};
millisecs_t fade_time_{};
millisecs_t next_stat_update_time_{};
millisecs_t progress_bar_end_time_{-9999};
millisecs_t last_progress_bar_draw_time_{};
millisecs_t last_progress_bar_start_time_{};
microsecs_t last_suppress_gyro_time_{};
seconds_t last_cursor_visibility_event_time_{};
microsecs_t next_frame_number_filtered_increment_time_{};
microsecs_t last_create_frame_def_time_microsecs_{};
millisecs_t last_create_frame_def_time_millisecs_{};
millisecs_t last_jitter_update_time_{};
microsecs_t last_suppress_gyro_time_{};
microsecs_t next_frame_number_filtered_increment_time_{};
microsecs_t last_create_frame_def_time_microsecs_{};
Object::Ref<ImageMesh> screen_mesh_;
Object::Ref<ImageMesh> progress_bar_bottom_mesh_;
Object::Ref<ImageMesh> progress_bar_top_mesh_;

View File

@ -137,14 +137,10 @@ auto GraphicsServer::TryRender() -> bool {
auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
assert(g_base->app_adapter->InGraphicsContext());
millisecs_t app_time = g_core->GetAppTimeMillisecs();
millisecs_t start_time = g_core->GetAppTimeMillisecs();
if (!renderer_) {
return nullptr;
}
// If the app is paused, never render.
if (g_base->app_adapter->app_suspended()) {
// Don't bother waiting if we can't/shouldn't render anyway.
if (!renderer_ || shutting_down_ || g_base->app_adapter->app_suspended()) {
return nullptr;
}
@ -173,12 +169,12 @@ auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
// if we've been waiting for too long, give up. On some platforms such
// as Android, this frame will still get flipped whether we draw in it
// or not, so we *really* want to not skip drawing if we can help it.
millisecs_t t = g_core->GetAppTimeMillisecs() - app_time;
millisecs_t t = g_core->GetAppTimeMillisecs() - start_time;
if (t >= 1000) {
if (g_buildconfig.debug_build()) {
Log(LogLevel::kWarning,
"GraphicsServer: aborting GetRenderFrameDef after "
+ std::to_string(t) + "ms.");
"GraphicsServer: timed out at " + std::to_string(t)
+ "ms waiting for logic thread to send us a FrameDef.");
}
break; // Fail.
}
@ -336,38 +332,6 @@ void GraphicsServer::LoadRenderer() {
texture_quality_ = Graphics::TextureQualityFromRequest(
texture_quality_requested_, renderer_->GetAutoTextureQuality());
// If we don't support high quality graphics, make sure we're no higher than
// medium.
// BA_PRECONDITION(g_base->graphics->has_supports_high_quality_graphics_value());
// if (!g_base->graphics->supports_high_quality_graphics()
// && graphics_quality_ > GraphicsQuality::kMedium) {
// graphics_quality_ = GraphicsQuality::kMedium;
// }
// graphics_quality_set_ = true;
// Update texture quality based on request.
// switch (texture_quality_requested_) {
// case TextureQualityRequest::kLow:
// texture_quality_ = TextureQuality::kLow;
// break;
// case TextureQualityRequest::kMedium:
// texture_quality_ = TextureQuality::kMedium;
// break;
// case TextureQualityRequest::kHigh:
// texture_quality_ = TextureQuality::kHigh;
// break;
// case TextureQualityRequest::kAuto:
// texture_quality_ = renderer_->GetAutoTextureQuality();
// break;
// default:
// Log(LogLevel::kError,
// "Unhandled TextureQualityRequest value: "
// +
// std::to_string(static_cast<int>(texture_quality_requested_)));
// texture_quality_ = TextureQuality::kLow;
// }
// texture_quality_set_ = true;
// Ok we've got our qualities figured out; now load/update the renderer.
renderer_->Load();
@ -657,4 +621,15 @@ auto GraphicsServer::InGraphicsContext_() const -> bool {
return g_base->app_adapter->InGraphicsContext();
}
void GraphicsServer::Shutdown() {
BA_PRECONDITION(!shutting_down_);
BA_PRECONDITION(g_base->InGraphicsContext());
shutting_down_ = true;
// We don't actually do anything here currently; just take note
// that we're shutting down so we no longer wait for frames to come
// in from the main thread.
shutdown_completed_ = true;
}
} // namespace ballistica::base

View File

@ -231,14 +231,6 @@ class GraphicsServer {
return tv_border_;
}
// auto graphics_quality_set() const {
// return graphics_quality_ != GraphicsQuality::kUnset;
// }
// auto texture_quality_set() const {
// return texture_quality_ != TextureQuality::kUnset;
// }
auto SupportsTextureCompressionType(TextureCompressionType t) const -> bool {
assert(InGraphicsContext_());
assert(texture_compression_types_set_);
@ -249,10 +241,6 @@ class GraphicsServer {
void SetTextureCompressionTypes(
const std::list<TextureCompressionType>& types);
// auto texture_compression_types_are_set() const {
// return texture_compression_types_set_;
// }
void set_renderer_context_lost(bool lost) { renderer_context_lost_ = lost; }
auto renderer_context_lost() const { return renderer_context_lost_; }
@ -289,6 +277,11 @@ class GraphicsServer {
return texture_compression_types_;
}
/// Start spinning down the graphics server/etc.
void Shutdown();
auto shutdown_completed() const { return shutdown_completed_; }
private:
/// Pass a freshly allocated GraphicsContext instance, which the graphics
/// system will take ownership of.
@ -324,6 +317,10 @@ class GraphicsServer {
}
}
TextureQualityRequest texture_quality_requested_{};
TextureQuality texture_quality_{};
GraphicsQualityRequest graphics_quality_requested_{};
GraphicsQuality graphics_quality_{};
bool renderer_loaded_ : 1 {};
bool model_view_projection_matrix_dirty_ : 1 {true};
bool model_world_matrix_dirty_ : 1 {true};
@ -331,11 +328,9 @@ class GraphicsServer {
bool renderer_context_lost_ : 1 {};
bool texture_compression_types_set_ : 1 {};
bool cam_orient_matrix_dirty_ : 1 {true};
bool shutting_down_ : 1 {};
bool shutdown_completed_ : 1 {};
Snapshot<GraphicsClientContext>* client_context_{};
TextureQualityRequest texture_quality_requested_{};
TextureQuality texture_quality_{};
GraphicsQualityRequest graphics_quality_requested_{};
GraphicsQuality graphics_quality_{};
float res_x_{};
float res_y_{};
float res_x_virtual_{};

View File

@ -18,7 +18,7 @@ class NinePatchMesh : public MeshIndexedSimpleFull {
/// Calculate a border value for a NinePatchMesh based on dimensions and a
/// desired max corner radius. For calculating left or right borders,
/// matching_dimension should be width and other_dimension should be
/// `matching_dimension` should be width and `other_dimension` should be
/// height. For top or bottom borders it is the opposite.
static auto BorderForRadius(float corner_radius, float matching_dimension,
float other_dimension) -> float {

View File

@ -360,7 +360,7 @@ void TextMesh::SetText(const std::string& text_in, HAlign alignment_h,
// compile it and add its final spans to our mesh.
if (packer) {
std::vector<TextPacker::Span> spans;
packer->compile();
packer->Compile();
// DEBUGGING - add a single quad above our first
// span showing the entire texture for debugging purposes

View File

@ -23,8 +23,7 @@ const float kPanMax = 9.0f;
const float kPanMin = -9.0f;
Camera::Camera()
: last_mode_set_time_(g_core->GetAppTimeMillisecs()),
lock_panning_(g_core->IsVRMode()),
: lock_panning_(g_core->IsVRMode()),
pan_speed_scale_(g_core->IsVRMode() ? 0.3f : 1.0f) {}
Camera::~Camera() = default;
@ -607,7 +606,8 @@ void Camera::Update(millisecs_t elapsed) {
float damping = 0.006f;
float damping2 = 0.006f;
float xy_blend_speed = 0.0002f;
millisecs_t real_time = g_core->GetAppTimeMillisecs();
time_ += elapsed;
// millisecs_t real_time = g_core->GetAppTimeMillisecs();
// Prevent camera "explosions" if we've been unable to update for a while.
elapsed = std::min(millisecs_t{100}, elapsed);
@ -653,18 +653,18 @@ void Camera::Update(millisecs_t elapsed) {
}
if (!g_core->IsVRMode()) {
smooth_speed_.x += elapsedf * rand_component
* (-0.5f
+ Utils::precalc_rand_1((real_time / rand_incr_1)
% kPrecalcRandsCount));
smooth_speed_.y += elapsedf * rand_component
* (-0.5f
+ Utils::precalc_rand_2((real_time / rand_incr_2)
% kPrecalcRandsCount));
smooth_speed_.z += elapsedf * rand_component
* (-0.5f
+ Utils::precalc_rand_3((real_time / rand_incr_3)
% kPrecalcRandsCount));
smooth_speed_.x +=
elapsedf * rand_component
* (-0.5f
+ Utils::precalc_rand_1((time_ / rand_incr_1) % kPrecalcRandsCount));
smooth_speed_.y +=
elapsedf * rand_component
* (-0.5f
+ Utils::precalc_rand_2((time_ / rand_incr_2) % kPrecalcRandsCount));
smooth_speed_.z +=
elapsedf * rand_component
* (-0.5f
+ Utils::precalc_rand_3((time_ / rand_incr_3) % kPrecalcRandsCount));
}
if (RandomFloat() < 0.1f && !g_core->IsVRMode()) {
@ -714,15 +714,14 @@ void Camera::Update(millisecs_t elapsed) {
shake_vel_.x +=
0.05f * shake_amount_
* (0.5f
- Utils::precalc_rand_1(real_time % 122 * i % kPrecalcRandsCount));
- Utils::precalc_rand_1(time_ % 122 * i % kPrecalcRandsCount));
shake_vel_.y +=
0.05f * shake_amount_
* (0.5f
- Utils::precalc_rand_2(real_time % 323 * i % kPrecalcRandsCount));
- Utils::precalc_rand_2(time_ % 323 * i % kPrecalcRandsCount));
shake_vel_.z +=
0.05f * shake_amount_
* (0.5f
- Utils::precalc_rand_3(real_time % 76 * i % kPrecalcRandsCount));
* (0.5f - Utils::precalc_rand_3(time_ % 76 * i % kPrecalcRandsCount));
}
for (int j = 0; j < iterations; j++) {
@ -740,8 +739,8 @@ void Camera::Update(millisecs_t elapsed) {
uint32_t interval = g_core->IsVRMode() ? 50 : 100;
// Occasionally, update microphone position for audio.
if (real_time - last_listener_update_time_ > interval) {
last_listener_update_time_ = real_time;
if (time_ - last_listener_update_time_ > interval) {
last_listener_update_time_ = time_;
bool do_regular_update = true;
if (g_core->IsVRMode()) {
#if BA_VR_MODE
@ -891,7 +890,8 @@ void Camera::SetMode(CameraMode m) {
if (mode_ != m) {
mode_ = m;
smooth_next_frame_ = false;
last_mode_set_time_ = g_core->GetAppTimeMillisecs();
// last_mode_set_time_ = g_core->GetAppTimeMillisecs();
// last_mode_set_time_ = time_;
heading_ = kInitialHeading;
}
}

View File

@ -92,37 +92,51 @@ class Camera : public Object {
void set_pan_speed_scale(float val) { pan_speed_scale_ = val; }
private:
float pan_speed_scale_{1.0f};
bool lock_panning_{};
Vector3f vr_offset_{0.0f, 0.0f, 0.0f};
Vector3f vr_extra_offset_{0.0f, 0.0f, 0.0f};
Vector3f vr_offset_smooth_{0.0f, 0.0f, 0.0f};
millisecs_t last_mode_set_time_{};
std::list<AreaOfInterest> areas_of_interest_;
CameraMode mode_{CameraMode::kFollow};
bool manual_{};
bool smooth_next_frame_{};
bool have_real_areas_of_interest_{};
bool manual_ : 1 {};
bool smooth_next_frame_ : 1 {};
bool have_real_areas_of_interest_ : 1 {};
bool lock_panning_ : 1 {};
// Manual stuff.
bool panning_{};
bool orbiting_{};
bool rolling_{};
bool trucking_{};
bool alt_down_{};
bool cmd_down_{};
bool ctrl_down_{};
bool mouse_left_down_{};
bool mouse_middle_down_{};
bool mouse_right_down_{};
bool panning_ : 1 {};
bool orbiting_ : 1 {};
bool rolling_ : 1 {};
bool trucking_ : 1 {};
bool alt_down_ : 1 {};
bool cmd_down_ : 1 {};
bool ctrl_down_ : 1 {};
bool mouse_left_down_ : 1 {};
bool mouse_middle_down_ : 1 {};
bool mouse_right_down_ : 1 {};
bool happy_thoughts_mode_ : 1 {};
bool x_constrained_ : 1 {true};
float pan_speed_scale_{1.0f};
float heading_{kInitialHeading};
Vector3f extra_pos_{0.0f, 0.0f, 0.0f};
Vector3f extra_pos_2_{0.0f, 0.0f, 0.0f};
float area_of_interest_bounds_[6]{-9999, -9999, -9999, 9999, 9999, 9999};
float pan_pos_{};
float pan_speed_{};
float pan_target_{};
float shake_amount_{};
float target_radius_{2.0f};
float target_radius_smoothed_{2.0f};
float field_of_view_x_{5.0f};
float field_of_view_y_{kCameraFOVY};
float field_of_view_x_smoothed_{1.0f};
float field_of_view_y_smoothed_{1.0f};
float min_target_radius_{5.0f};
float area_of_interest_near_{1.0f};
float area_of_interest_far_{2.0f};
float xy_constrain_blend_{0.5f};
// millisecs_t last_mode_set_time_{};
millisecs_t last_listener_update_time_{};
millisecs_t time_{};
Vector3f vr_offset_{0.0f, 0.0f, 0.0f};
Vector3f vr_extra_offset_{0.0f, 0.0f, 0.0f};
Vector3f vr_offset_smooth_{0.0f, 0.0f, 0.0f};
Vector3f extra_pos_{0.0f, 0.0f, 0.0f};
Vector3f extra_pos_2_{0.0f, 0.0f, 0.0f};
Vector3f shake_pos_{0.0f, 0.0f, 0.0f};
Vector3f shake_vel_{0.0f, 0.0f, 0.0f};
Vector3f position_{0.0f, 1.0f, -1.0f};
@ -131,20 +145,8 @@ class Camera : public Object {
Vector3f position_offset_smoothed_{0.0f, 0.0f, 0.0f};
Vector3f smooth_speed_{0.0f, 0.0f, 0.0f};
Vector3f smooth_speed_2_{0.0f, 0.0f, 0.0f};
float target_radius_{2.0f};
float target_radius_smoothed_{2.0f};
float field_of_view_x_{5.0f};
float field_of_view_y_{kCameraFOVY};
float field_of_view_x_smoothed_{1.0f};
float field_of_view_y_smoothed_{1.0f};
millisecs_t last_listener_update_time_{};
bool happy_thoughts_mode_{};
float min_target_radius_{5.0f};
Vector3f up_{0.0f, 1.0f, 0.0f};
float area_of_interest_near_{1.0f};
float area_of_interest_far_{2.0f};
bool x_constrained_{true};
float xy_constrain_blend_{0.5f};
std::list<AreaOfInterest> areas_of_interest_;
std::vector<Vector3f> area_of_interest_points_{{0.0f, 0.0f, 0.0f}};
};

View File

@ -6,7 +6,6 @@
#include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/shared/foundation/object.h"
namespace ballistica::base {

View File

@ -0,0 +1,538 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/base/graphics/support/screen_messages.h"
#include "ballistica/base/graphics/component/simple_component.h"
#include "ballistica/base/graphics/mesh/nine_patch_mesh.h"
#include "ballistica/base/graphics/text/text_graphics.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/shared/generic/utils.h"
namespace ballistica::base {
const float kScreenMessageZDepth{-0.06f};
class ScreenMessages::ScreenMessageEntry {
public:
ScreenMessageEntry(std::string text, bool top_style, uint32_t c,
const Vector3f& color, TextureAsset* texture,
TextureAsset* tint_texture, const Vector3f& tint,
const Vector3f& tint2)
: top_style(top_style),
creation_time(c),
s_raw(std::move(text)),
color(color),
texture(texture),
tint_texture(tint_texture),
tint(tint),
tint2(tint2) {}
auto GetText() -> TextGroup&;
void UpdateTranslation();
bool top_style;
uint32_t creation_time;
Vector3f color;
Vector3f tint;
Vector3f tint2;
std::string s_raw;
std::string s_translated;
float str_width{};
float str_height{};
Object::Ref<TextureAsset> texture;
Object::Ref<TextureAsset> tint_texture;
float v_smoothed{};
bool translation_dirty{true};
bool mesh_dirty{true};
millisecs_t smooth_time{};
Object::Ref<NinePatchMesh> shadow_mesh_;
private:
Object::Ref<TextGroup> s_mesh_;
};
ScreenMessages::ScreenMessages() = default;
void ScreenMessages::DrawMiscOverlays(FrameDef* frame_def) {
RenderPass* pass = frame_def->overlay_pass();
// Screen messages (bottom).
{
// Delete old ones.
if (!screen_messages_.empty()) {
millisecs_t cutoff;
if (g_core->GetAppTimeMillisecs() > 5000) {
cutoff = g_core->GetAppTimeMillisecs() - 5000;
for (auto i = screen_messages_.begin(); i != screen_messages_.end();) {
if (i->creation_time < cutoff) {
auto next = i;
next++;
screen_messages_.erase(i);
i = next;
} else {
i++;
}
}
}
}
// Delete if we have too many.
while ((screen_messages_.size()) > 4) {
screen_messages_.erase(screen_messages_.begin());
}
// Draw all existing.
if (!screen_messages_.empty()) {
bool vr = g_core->IsVRMode();
// These are less disruptive in the middle for menus but at the bottom
// during gameplay.
float start_v = g_base->graphics->screen_virtual_height() * 0.05f;
float scale;
switch (g_base->ui->scale()) {
case UIScale::kSmall:
scale = 1.5f;
break;
case UIScale::kMedium:
scale = 1.2f;
break;
default:
scale = 1.0f;
break;
}
// Shadows.
{
SimpleComponent c(pass);
c.SetTransparent(true);
c.SetTexture(
// g_base->assets->SysTexture(SysTextureID::kSoftRectVertical));
g_base->assets->SysTexture(SysTextureID::kShadowSharp));
float screen_width = g_base->graphics->screen_virtual_width();
float v = start_v;
millisecs_t youngest_age = 9999;
for (auto i = screen_messages_.rbegin(); i != screen_messages_.rend();
i++) {
// Update the translation if need be.
i->UpdateTranslation();
// Don't actually need the text just yet but need shadow mesh
// which is calculated as part of it.
i->GetText();
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
youngest_age = std::min(youngest_age, age);
float s_extra = 1.0f;
if (age < 100) {
s_extra = std::min(1.2f, 1.2f * (static_cast<float>(age) / 100.0f));
} else if (age < 150) {
s_extra =
1.2f - 0.2f * ((150.0f - static_cast<float>(age)) / 50.0f);
}
float a;
if (age > 3000) {
a = 1.0f - static_cast<float>(age - 3000) / 2000;
} else {
a = 1;
}
a *= 0.7f;
// if (vr) {
// a *= 0.8f;
// }
if (i->translation_dirty) {
BA_LOG_ONCE(
LogLevel::kWarning,
"Found dirty translation on screenmessage draw pass 1; raw="
+ i->s_raw);
}
float str_height = i->str_height;
float str_width = i->str_width;
if ((str_width * scale) > (screen_width - 40)) {
s_extra *= ((screen_width - 40) / (str_width * scale));
}
float r = i->color.x;
float g = i->color.y;
float b = i->color.z;
Graphics::GetSafeColor(&r, &g, &b);
float v_extra = scale * (static_cast<float>(youngest_age) * 0.01f);
float fade;
if (age < 100) {
fade = 1.0f;
} else {
// Don't fade ALL the way to black; leaves a tiny bit of color
// showing which looks nice.
fade = std::max(0.07f, (200.0f - static_cast<float>(age)) / 100.0f);
}
c.SetColor(r * fade, g * fade, b * fade, a);
{
auto xf = c.ScopedTransform();
// This logic needs to run at a fixed hz or it breaks on high frame
// rates.
auto now_millisecs = pass->frame_def()->display_time_millisecs();
i->smooth_time = std::max(i->smooth_time, now_millisecs - 100);
while (i->smooth_time < now_millisecs) {
i->smooth_time += 1000 / 60;
if (i->v_smoothed == 0.0f) {
i->v_smoothed = v + v_extra;
} else {
float smoothing = 0.8f;
i->v_smoothed = smoothing * i->v_smoothed
+ (1.0f - smoothing) * (v + v_extra);
}
}
c.Translate(screen_width * 0.5f, i->v_smoothed,
vr ? 60 : kScreenMessageZDepth);
// if (vr) {
// // Let's drop down a bit in vr mode.
// // c.Translate(0, -10.0f, 0);
// // c.Scale((str_width + 60) * scale * s_extra,
// // (str_height + 20) * scale * s_extra);
// c.Scale(scale * s_extra, scale * s_extra);
// // Align our bottom with where we just scaled from.
// c.Translate(0, 0.5f, 0);
{
// c.Scale((str_width + 110) * scale * s_extra,
// (str_height + 40) * scale * s_extra);
c.Scale(scale * s_extra, scale * s_extra);
c.Translate(0, 20);
// Align our bottom with where we just scaled from.
c.Translate(0, 0.5f, 0);
}
// c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
assert(i->shadow_mesh_.Exists());
c.DrawMesh(i->shadow_mesh_.Get());
}
v += scale * (36 + str_height);
if (v > g_base->graphics->screen_virtual_height() + 30) {
break;
}
}
c.Submit();
}
// Now the strings themselves.
{
SimpleComponent c(pass);
c.SetTransparent(true);
float screen_width = g_base->graphics->screen_virtual_width();
float v = start_v;
millisecs_t youngest_age = 9999;
for (auto i = screen_messages_.rbegin(); i != screen_messages_.rend();
i++) {
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
youngest_age = std::min(youngest_age, age);
float s_extra = 1.0f;
if (age < 100) {
s_extra = std::min(1.2f, 1.2f * (static_cast<float>(age) / 100.0f));
} else if (age < 150) {
s_extra =
1.2f - 0.2f * ((150.0f - static_cast<float>(age)) / 50.0f);
}
float a;
if (age > 3000) {
a = 1.0f - static_cast<float>(age - 3000) / 2000;
} else {
a = 1;
}
if (i->translation_dirty) {
BA_LOG_ONCE(
LogLevel::kWarning,
"Found dirty translation on screenmessage draw pass 2; raw="
+ i->s_raw);
}
float str_height = i->str_height;
float str_width = i->str_width;
if ((str_width * scale) > (screen_width - 40)) {
s_extra *= ((screen_width - 40) / (str_width * scale));
}
float r = i->color.x;
float g = i->color.y;
float b = i->color.z;
Graphics::GetSafeColor(&r, &g, &b, 0.85f);
int elem_count = i->GetText().GetElementCount();
for (int e = 0; e < elem_count; e++) {
// Gracefully skip unloaded textures.
TextureAsset* t = i->GetText().GetElementTexture(e);
if (!t->preloaded()) {
continue;
}
c.SetTexture(t);
if (i->GetText().GetElementCanColor(e)) {
c.SetColor(r, g, b, a);
} else {
c.SetColor(1, 1, 1, a);
}
c.SetFlatness(i->GetText().GetElementMaxFlatness(e));
{
auto xf = c.ScopedTransform();
c.Translate(screen_width * 0.5f, i->v_smoothed,
vr ? 150 : kScreenMessageZDepth);
c.Scale(scale * s_extra, scale * s_extra);
c.Translate(0, 20);
c.DrawMesh(i->GetText().GetElementMesh(e));
}
}
v += scale * (36 + str_height);
if (v > g_base->graphics->screen_virtual_height() + 30) {
break;
}
}
c.Submit();
}
}
}
// Screen messages (top).
{
// Delete old ones.
if (!screen_messages_top_.empty()) {
millisecs_t cutoff;
if (g_core->GetAppTimeMillisecs() > 5000) {
cutoff = g_core->GetAppTimeMillisecs() - 5000;
for (auto i = screen_messages_top_.begin();
i != screen_messages_top_.end();) {
if (i->creation_time < cutoff) {
auto next = i;
next++;
screen_messages_top_.erase(i);
i = next;
} else {
i++;
}
}
}
}
// Delete if we have too many.
while ((screen_messages_top_.size()) > 6) {
screen_messages_top_.erase(screen_messages_top_.begin());
}
if (!screen_messages_top_.empty()) {
SimpleComponent c(pass);
c.SetTransparent(true);
// Draw all existing.
float h = pass->virtual_width() - 300.0f;
float v = g_base->graphics->screen_virtual_height() - 50.0f;
float v_base = g_base->graphics->screen_virtual_height();
float last_v = -999.0f;
float min_spacing = 25.0f;
for (auto i = screen_messages_top_.rbegin();
i != screen_messages_top_.rend(); i++) {
// Update the translation if need be.
i->UpdateTranslation();
millisecs_t age = g_core->GetAppTimeMillisecs() - i->creation_time;
float s_extra = 1.0f;
if (age < 100) {
s_extra = std::min(1.1f, 1.1f * (static_cast<float>(age) / 100.0f));
} else if (age < 150) {
s_extra = 1.1f - 0.1f * ((150.0f - static_cast<float>(age)) / 50.0f);
}
float a;
if (age > 3000) {
a = 1.0f - static_cast<float>(age - 3000) / 2000;
} else {
a = 1;
}
// This logic needs to run at a fixed hz or it breaks on high frame
// rates.
auto now_millisecs = pass->frame_def()->display_time_millisecs();
i->smooth_time = std::max(i->smooth_time, now_millisecs - 100);
while (i->smooth_time < now_millisecs) {
i->smooth_time += 1000 / 60;
i->v_smoothed += 0.1f;
if (i->v_smoothed - last_v < min_spacing) {
i->v_smoothed +=
8.0f * (1.0f - ((i->v_smoothed - last_v) / min_spacing));
}
}
last_v = i->v_smoothed;
// Draw the image if they provided one.
if (i->texture.Exists()) {
c.Submit();
SimpleComponent c2(pass);
c2.SetTransparent(true);
c2.SetTexture(i->texture);
if (i->tint_texture.Exists()) {
c2.SetColorizeTexture(i->tint_texture.Get());
c2.SetColorizeColor(i->tint.x, i->tint.y, i->tint.z);
c2.SetColorizeColor2(i->tint2.x, i->tint2.y, i->tint2.z);
c2.SetMaskTexture(
g_base->assets->SysTexture(SysTextureID::kCharacterIconMask));
}
c2.SetColor(1, 1, 1, a);
{
auto xf = c2.ScopedTransform();
c2.Translate(h - 14, v_base + 10 + i->v_smoothed,
kScreenMessageZDepth);
c2.Scale(22.0f * s_extra, 22.0f * s_extra);
c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
}
c2.Submit();
}
float r = i->color.x;
float g = i->color.y;
float b = i->color.z;
Graphics::GetSafeColor(&r, &g, &b);
int elem_count = i->GetText().GetElementCount();
for (int e = 0; e < elem_count; e++) {
// Gracefully skip unloaded textures.
TextureAsset* t = i->GetText().GetElementTexture(e);
if (!t->preloaded()) {
continue;
}
c.SetTexture(t);
if (i->GetText().GetElementCanColor(e)) {
c.SetColor(r, g, b, a);
} else {
c.SetColor(1, 1, 1, a);
}
c.SetShadow(-0.003f * i->GetText().GetElementUScale(e),
-0.003f * i->GetText().GetElementVScale(e), 0.0f,
1.0f * a);
c.SetFlatness(i->GetText().GetElementMaxFlatness(e));
c.SetMaskUV2Texture(i->GetText().GetElementMaskUV2Texture(e));
{
auto xf = c.ScopedTransform();
c.Translate(h, v_base + 2 + i->v_smoothed, kScreenMessageZDepth);
c.Scale(0.6f * s_extra, 0.6f * s_extra);
c.DrawMesh(i->GetText().GetElementMesh(e));
}
}
assert(!i->translation_dirty);
v -= g_base->text_graphics->GetStringHeight(i->s_translated.c_str())
* 0.6f
+ 8.0f;
}
c.Submit();
}
}
}
void ScreenMessages::AddScreenMessage(const std::string& msg,
const Vector3f& color, bool top,
TextureAsset* texture,
TextureAsset* tint_texture,
const Vector3f& tint,
const Vector3f& tint2) {
assert(g_base->InLogicThread());
// So we know we're always dealing with valid utf8.
std::string m = Utils::GetValidUTF8(msg.c_str(), "ga9msg");
if (top) {
float start_v = -40.0f;
if (!screen_messages_top_.empty()) {
start_v = std::min(
start_v,
std::max(-100.0f, screen_messages_top_.back().v_smoothed - 25.0f));
}
screen_messages_top_.emplace_back(m, true, g_core->GetAppTimeMillisecs(),
color, texture, tint_texture, tint,
tint2);
screen_messages_top_.back().v_smoothed = start_v;
} else {
screen_messages_.emplace_back(m, false, g_core->GetAppTimeMillisecs(),
color, texture, tint_texture, tint, tint2);
}
}
void ScreenMessages::Reset() {
// Wipe out top screen messages since they might be using textures that are
// being reset. Bottom ones are ok since they have no textures.
screen_messages_top_.clear();
}
void ScreenMessages::ClearScreenMessageTranslations() {
assert(g_base && g_base->InLogicThread());
for (auto&& i : screen_messages_) {
i.translation_dirty = true;
}
for (auto&& i : screen_messages_top_) {
i.translation_dirty = true;
}
}
auto ScreenMessages::ScreenMessageEntry::GetText() -> TextGroup& {
if (translation_dirty) {
BA_LOG_ONCE(
LogLevel::kWarning,
"Found dirty translation on screenmessage GetText; raw=" + s_raw);
}
if (!s_mesh_.Exists()) {
s_mesh_ = Object::New<TextGroup>();
mesh_dirty = true;
}
if (mesh_dirty) {
s_mesh_->SetText(
s_translated,
top_style ? TextMesh::HAlign::kLeft : TextMesh::HAlign::kCenter,
TextMesh::VAlign::kBottom);
str_width = g_base->text_graphics->GetStringWidth(s_translated.c_str());
str_height = g_base->text_graphics->GetStringHeight(s_translated.c_str());
if (!top_style) {
float x_extend = 40.0f;
float y_extend = 40.0f;
float y_offset = -5.0f;
float corner_radius = 60.0f;
float width_fin = str_width + x_extend * 2.0f;
float height_fin = str_height + y_extend * 2.0f;
float x_border =
NinePatchMesh::BorderForRadius(corner_radius, width_fin, height_fin);
float y_border =
NinePatchMesh::BorderForRadius(corner_radius, height_fin, width_fin);
shadow_mesh_ = Object::New<NinePatchMesh>(
-0.5f * width_fin, -y_extend + y_offset, 0.0f, width_fin, height_fin,
x_border, y_border, x_border, y_border);
}
mesh_dirty = false;
}
return *s_mesh_;
}
void ScreenMessages::ScreenMessageEntry::UpdateTranslation() {
if (translation_dirty) {
s_translated = g_base->assets->CompileResourceString(
s_raw, "Graphics::ScreenMessageEntry::UpdateTranslation");
translation_dirty = false;
mesh_dirty = true;
}
}
} // namespace ballistica::base

View File

@ -0,0 +1,37 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_BASE_GRAPHICS_SUPPORT_SCREEN_MESSAGES_H_
#define BALLISTICA_BASE_GRAPHICS_SUPPORT_SCREEN_MESSAGES_H_
#include "ballistica/base/base.h"
#include "ballistica/shared/math/vector3f.h"
namespace ballistica::base {
/// Wrangles a set of screen-messages.
class ScreenMessages {
public:
ScreenMessages();
void ClearScreenMessageTranslations();
// Print a message to the on-screen list.
void AddScreenMessage(const std::string& msg,
const Vector3f& color = {1, 1, 1}, bool top = false,
TextureAsset* texture = nullptr,
TextureAsset* tint_texture = nullptr,
const Vector3f& tint = {1, 1, 1},
const Vector3f& tint2 = {1, 1, 1});
void DrawMiscOverlays(FrameDef* frame_def);
void Reset();
private:
class ScreenMessageEntry;
std::list<ScreenMessageEntry> screen_messages_;
std::list<ScreenMessageEntry> screen_messages_top_;
};
} // namespace ballistica::base
#endif // BALLISTICA_BASE_GRAPHICS_SUPPORT_SCREEN_MESSAGES_H_

View File

@ -12,25 +12,25 @@
#include "ballistica/base/graphics/text/text_graphics.h"
namespace ballistica::base {
// the total number of glyph pages we have
// The total number of glyph pages we have.
#define BA_GLYPH_PAGE_COUNT 8
// the total number of glyphs we have
// The total number of glyphs we have.
const int kGlyphCount = 1280;
// the starting glyph index for each page
// The starting glyph index for each page.
uint32_t g_glyph_page_start_index_map[8] = {0, 258, 416, 546,
698, 981, 1138, 1276};
// the number of glyphs on each page
// The number of glyphs on each page.
uint32_t g_glyph_page_glyph_counts[8] = {258, 158, 130, 152, 283, 157, 138, 4};
// our dynamically-loaded glyph structs for each page
// Our dynamically-loaded glyph structs for each page.
TextGraphics::Glyph* g_glyph_pages[8] = {nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr};
// the page index for each glyph
uint16_t g_glyph_map[kGlyphCount] = {
// The page index for each glyph.
uint8_t g_glyph_map[kGlyphCount] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,

View File

@ -116,8 +116,7 @@ TextGraphics::TextGraphics() {
}
}
// init glyph values for our big font page
// (a 8x8 array)
// Init glyph values for our big font page (a 8x8 array).
{
float x_offs = 0.009f;
float y_offs = 0.0059f;
@ -284,7 +283,7 @@ TextGraphics::TextGraphics() {
(1.0f / 8.0f) * static_cast<float>(x + 1) + x_offs + scale_extra;
g.tex_max_y = (1.0f / 8.0f) * static_cast<float>(y) + y_offs;
// just scooted letters over.. account for that
// Just scooted letters over; account for that.
float foo_x = 0.0183f;
float foo_y = 0.000f;
g.tex_min_x += foo_x;
@ -292,12 +291,12 @@ TextGraphics::TextGraphics() {
g.tex_min_y += foo_y;
g.tex_max_y += foo_y;
// clamp based on char width
// Clamp based on char width.
float scale = w * 1.32f;
g.x_size *= scale;
g.tex_max_x = g.tex_min_x + (g.tex_max_x - g.tex_min_x) * scale;
// add bot offset
// Add bot offset.
if (bot_offset != 0.0f) {
g.tex_min_y = g.tex_max_y
+ (g.tex_min_y - g.tex_max_y)
@ -305,7 +304,7 @@ TextGraphics::TextGraphics() {
g.pen_offset_y -= bot_offset;
g.y_size += bot_offset;
}
// add left offset
// Add left offset.
if (left_offset != 0.0f) {
g.tex_min_x = g.tex_max_x
+ (g.tex_min_x - g.tex_max_x)
@ -313,14 +312,14 @@ TextGraphics::TextGraphics() {
g.pen_offset_x -= left_offset;
g.x_size += left_offset;
}
// add right offset
// Add right offset.
if (right_offset != 0.0f) {
g.tex_max_x = g.tex_min_x
+ (g.tex_max_x - g.tex_min_x)
* ((g.x_size + right_offset) / g.x_size);
g.x_size += right_offset;
}
// add top offset
// Add top offset.
if (top_offset != 0.0f) {
g.tex_max_y = g.tex_min_y
+ (g.tex_max_y - g.tex_min_y)
@ -844,13 +843,13 @@ void TextGraphics::GetFontPageCharRange(int page, uint32_t* first_char,
// Our special pages:
switch (page) {
case static_cast<int>(FontPage::kOSRendered): {
// we allow the OS to render anything not in one of our glyph textures
// (technically this overlaps the private-use range which we use our own
// textures for, but that's handled as a special-case by
// TextGroup::setText
// We allow the OS to render anything not in one of our glyph textures
// (technically this overlaps the private-use range which we use our
// own textures for, but that's handled as a special-case by
// TextGroup::SetText.
(*first_char) = kGlyphCount;
(*last_char) = kTextMaxUnicodeVal; // hmm what's the max unicode value we
// should ever see?..
// hmm what's the max unicode value we should ever see?..
(*last_char) = kTextMaxUnicodeVal;
break;
}
case static_cast<int>(FontPage::kExtras1): {
@ -887,52 +886,57 @@ void TextGraphics::GetFontPagesForText(const std::string& text,
int last_page = -1;
std::vector<uint32_t> unicode = Utils::UnicodeFromUTF8(text, "c03853");
for (uint32_t val : unicode) {
int page;
int page{-1};
// Hack: allow showing euro even if we don't support unicode font rendering.
if (g_buildconfig.enable_os_font_rendering()) {
if (val == 8364) {
val = 0xE000;
}
}
// Hack: allow showing euro even if we don't support unicode font
// rendering.
// if (g_buildconfig.enable_os_font_rendering()) {
// if (val == 8364) {
// val = 0xE000;
// }
// }
// For values in the custom-char range (U+E000U+F8FF) we point at our own
// custom page(s)
bool covered{};
// For values in the custom-char range (U+E000U+F8FF) we point at our
// own custom page(s)
if (val >= 0xE000 && val <= 0xF8FF) {
// The 25 chars after this are in our fontExtras sheet.
if (val < 0xE000 + 25) {
// Special value denoting our custom font page.
page = static_cast<int>(FontPage::kExtras1);
covered = true;
} else if (val < 0xE000 + 50) {
// Special value denoting our custom font page.
page = static_cast<int>(FontPage::kExtras2);
covered = true;
} else if (val < 0xE000 + 75) {
// Special value denoting our custom font page.
page = static_cast<int>(FontPage::kExtras3);
covered = true;
} else if (val < 0xE000 + 100) {
// Special value denoting our custom font page.
page = static_cast<int>(FontPage::kExtras4);
} else {
// We dont cover this.. just go with '?'
val = '?';
page = g_glyph_map[val];
covered = true;
}
} else if (val >= kGlyphCount) {
// Otherwise if its outside of our texture-coverage area.
} else if (val < kGlyphCount) {
page = g_glyph_map[val];
covered = true;
}
if (!covered) {
if (g_buildconfig.enable_os_font_rendering()) {
page = static_cast<int>(FontPage::kOSRendered);
} else {
val = '?';
page = g_glyph_map[val];
}
} else {
// yay we cover it!
page = g_glyph_map[val];
}
// compare to lastPage to avoid doing a set insert for *everything* since
// most will be the same
// Compare to last_page to avoid doing a set insert for *everything*
// since most will be the same.
if (page != last_page) {
(*font_pages).insert(page);
font_pages->insert(page);
last_page = page;
}
}
@ -1009,12 +1013,8 @@ void TextGraphics::GetOSTextSpanBoundsAndWidth(const std::string& s, Rect* r,
// Send this entry to the back of the list since we used it.
text_span_bounds_cache_.erase(entry->list_iterator_);
// I guess inspection doesn't realize entry lives on after this?...
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnusedValue"
entry->list_iterator_ =
text_span_bounds_cache_.insert(text_span_bounds_cache_.end(), entry);
#pragma clang diagnostic pop
return;
}
auto entry(Object::New<TextSpanBoundsCacheEntry>());
@ -1068,7 +1068,9 @@ auto TextGraphics::GetStringWidth(const char* text, bool big) -> float {
line_length += GetOSTextSpanWidth(s);
os_span.clear();
}
if (line_length > max_line_length) max_line_length = line_length;
if (line_length > max_line_length) {
max_line_length = line_length;
}
line_length = 0;
t++;
} else {
@ -1145,7 +1147,9 @@ void TextGraphics::BreakUpString(const char* text, float width,
s_begin = t;
}
} else {
if (*t == 0) throw Exception();
if (*t == 0) {
throw Exception();
}
uint32_t val = Utils::GetUTF8Value(t);
Utils::AdvanceUTF8(&t);

View File

@ -24,11 +24,11 @@ void TextPacker::AddSpan(const std::string& text, float x, float y,
}
// FIXME - we currently run into minor problems because we measure our text
// bounds at one size and then scale that linearly when trying to fit things
// into the texture. However, fonts don't always scale linearly (and even when
// that's an option it can be expensive).
// bounds at one size and then scale that linearly when trying to fit
// things into the texture. However, fonts don't always scale linearly (and
// even when that's an option it can be expensive).
void TextPacker::compile() {
void TextPacker::Compile() {
assert(!compiled_);
if (spans_.empty()) {
compiled_ = true;
@ -57,24 +57,23 @@ void TextPacker::compile() {
width *= 2;
}
// Alternately, if we're too big, crank our scale down so that our widest span
// fits.
// Alternately, if we're too big, crank our scale down so that our widest
// span fits.
if (widest_unscaled_span_width * scale > width * 0.9f) {
scale *= ((width * 0.9f) / (widest_unscaled_span_width * scale));
}
float start_height = height;
int mini_shrink_tries = 0;
// Ok; we've now locked in a width and scale.
// Now we go through and position our spans.
// We may need to do this more than once if our height comes out too big.
// (hopefully this will never be a problem in practice)
// Ok; we've now locked in a width and scale. Now we go through and
// position our spans. We may need to do this more than once if our height
// comes out too big. (hopefully this will never be a problem in practice)
while (true) {
height = start_height;
// We currently just lay out left-to-right, top-to-bottom.
// This could be somewhat wasteful in particular configurations.
// (leaving half-filled lines, etc) so it might be worth improving later.
// We currently just lay out left-to-right, top-to-bottom. This could be
// somewhat wasteful in particular configurations. (leaving half-filled
// lines, etc) so it might be worth improving later.
float widest_fill_right = 0.0f;
float fill_right = 0.0f;
float fill_bottom = 0.0f;
@ -87,26 +86,27 @@ void TextPacker::compile() {
// Start a new line if this would put us past the end.
if (fill_right + span_width > width) {
if (fill_right > widest_fill_right) {
widest_fill_right = fill_right; // Keep track of how far over we go.
// Keep track of how far over we go.
widest_fill_right = fill_right;
}
fill_right = 0.0f;
fill_bottom += line_height;
line_height = 0.0f;
}
// Position x such that x + left bound - buffer lines up with our current
// right point.
// Position x such that x + left bound - buffer lines up with our
// current right point.
float to_left = (i.bounds.l - span_buffer) * scale;
i.tex_x = fill_right - to_left;
fill_right += span_width;
// Position y such that y - top bound - buffer lines up with our current
// bottom point.
// Position y such that y - top bound - buffer lines up with our
// current bottom point.
float to_top = (-i.bounds.t - span_buffer) * scale;
i.tex_y = fill_bottom - to_top;
// If our total height is greater than the current line height, expand the
// line's.
// If our total height is greater than the current line height, expand
// the line's.
if (span_height > line_height) {
line_height = span_height;
}
@ -125,9 +125,9 @@ void TextPacker::compile() {
// If it doesn't fit, repeat again with a smaller scale until it does.
// Dropping our scale has a disproportional effect on the final height
// (since it opens up more relative horizontal space).
// I'm not sure how to figure out how much to drop by other than
// incrementally dropping values until we fit.
// (since it opens up more relative horizontal space). I'm not sure
// how to figure out how much to drop by other than incrementally
// dropping values until we fit.
scale *= 0.75f;
} else if (((widest_fill_right < (width * mini_shrink_threshold_h)
@ -135,15 +135,15 @@ void TextPacker::compile() {
|| fill_bottom + line_height
< (height * mini_shrink_threshold_v))
&& mini_shrink_tries < 3) {
// If we're here it means we *barely* use more than half of the texture in
// one direction or the other; let's shrink just a tiny bit and we should
// be able to chop our texture size in half
// If we're here it means we *barely* use more than half of the
// texture in one direction or the other; let's shrink just a tiny bit
// and we should be able to chop our texture size in half
if (widest_fill_right < width * mini_shrink_threshold_h && width > 16) {
float scale_val = 0.99f * (((width * 0.5f) / widest_fill_right));
if (scale_val < 1.0f) {
// FIXME - should think about a fixed multiplier here;
// under the hood the system might be caching glyphs based on scale
// and this would leave us with fewer different scales in the end and
// FIXME - should think about a fixed multiplier here; under the
// hood the system might be caching glyphs based on scale and
// this would leave us with fewer different scales in the end and
// thus better caching performance
scale *= scale_val;
}
@ -151,9 +151,9 @@ void TextPacker::compile() {
} else {
float scale_val = 0.99f * (height * 0.5f) / (fill_bottom + line_height);
if (scale_val < 1.0f) {
// FIXME - should think about a fixed multiplier here;
// under the hood the system might be caching glyphs based on scale
// and this would leave us with fewer different scales in the end and
// FIXME - should think about a fixed multiplier here; under the
// hood the system might be caching glyphs based on scale and
// this would leave us with fewer different scales in the end and
// thus better caching performance
scale *= scale_val;
}
@ -165,8 +165,8 @@ void TextPacker::compile() {
}
}
// Lastly, now that our texture width and height are completely finalized, we
// can calculate UVs.
// Lastly, now that our texture width and height are completely finalized,
// we can calculate UVs.
for (auto&& i : spans_) {
// Now store uv coords for this span; they should include the buffer.
i.u_min = (i.tex_x + (i.bounds.l - span_buffer) * scale) / width;
@ -182,11 +182,11 @@ void TextPacker::compile() {
}
// TODO(ericf): now we calculate a hash that's unique to this text
// configuration; we'll use that as a key for the texture we'll generate/use.
// ..this way multiple meshes can share the same generated texture.
// *technically* we could calculate this hash and check for an existing
// texture before we bother laying out our spans, but that might not save us
// much time and would complicate things.
// configuration; we'll use that as a key for the texture we'll
// generate/use. ..this way multiple meshes can share the same generated
// texture. *technically* we could calculate this hash and check for an
// existing texture before we bother laying out our spans, but that might
// not save us much time and would complicate things.
hash_ = std::to_string(resolution_scale_);
for (auto&& i : spans_) {
char buffer[64];

View File

@ -21,7 +21,7 @@ class TextPacker : public Object {
// outside of here anyway so might as well recycle.
void AddSpan(const std::string& text, float x, float y, const Rect& bounds);
auto hash() const -> const std::string& {
const auto& hash() const {
assert(compiled_);
return hash_;
}
@ -51,32 +51,32 @@ class TextPacker : public Object {
// Once done adding spans, call this to calculate final span UV values,
// texture configuration, and hash.
void compile();
void Compile();
auto spans() const -> const std::list<Span>& { return spans_; }
const auto& spans() const { return spans_; }
auto texture_width() const -> int {
auto texture_width() const {
assert(compiled_);
return texture_width_;
}
auto texture_height() const -> int {
auto texture_height() const {
assert(compiled_);
return texture_height_;
}
auto text_scale() const -> float {
auto text_scale() const {
assert(compiled_);
return text_scale_;
}
private:
bool compiled_{false};
float resolution_scale_;
float text_scale_{};
int texture_width_{};
int texture_height_{};
float text_scale_{};
std::string hash_;
bool compiled_{false};
std::list<Span> spans_;
};

View File

@ -9,6 +9,7 @@
#include "ballistica/base/input/input.h"
#include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/classic_soft.h"
#include "ballistica/base/support/repeater.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/core/core.h"
#include "ballistica/shared/foundation/event_loop.h"
@ -17,10 +18,6 @@
namespace ballistica::base {
const char* kMFiControllerName = "iOS/Mac Controller";
const int kJoystickRepeatDelay{500};
// Joy values below this are candidates for calibration.
const float kJoystickCalibrationThreshold{6000.0f};
@ -68,7 +65,12 @@ JoystickInput::JoystickInput(int sdl_joystick_id,
// that instead.
// #if BA_SDL2_BUILD
sdl_joystick_id_ = SDL_JoystickInstanceID(sdl_joystick_);
raw_sdl_joystick_name_ = SDL_JoystickName(sdl_joystick_);
if (auto* name = SDL_JoystickName(sdl_joystick_)) {
raw_sdl_joystick_name_ = name;
} else {
// This can return nullptr if SDL can't find a name.
raw_sdl_joystick_name_ = "Unknown Controller";
}
// Special case: on windows, xinput stuff comes in with unique names
// "XInput Controller #3", etc. Let's replace these with simply "XInput
@ -78,20 +80,6 @@ JoystickInput::JoystickInput(int sdl_joystick_id,
&& raw_sdl_joystick_name_.size() <= 22) {
raw_sdl_joystick_name_ = "XInput Controller";
}
// #else
// raw_sdl_joystick_name_ = SDL_JoystickName(sdl_joystick_id_);
// #endif // BA_SDL2_BUILD
// If its an SDL joystick and we're using our custom sdl 1.2 build, ask it.
// #if BA_XCODE_BUILD && BA_OSTYPE_MACOS && !BA_SDL2_BUILD
// raw_sdl_joystick_identifier_ =
// SDL_JoystickIdentifier(sdl_joystick_id_);
// #endif
// Some special-cases on mac.
if (strstr(raw_sdl_joystick_name_.c_str(), "PLAYSTATION") != nullptr) {
is_mac_ps3_controller_ = true;
}
#else // BA_ENABLE_SDL_JOYSTICKS
throw Exception(); // Shouldn't happen.
@ -101,8 +89,6 @@ JoystickInput::JoystickInput(int sdl_joystick_id,
// Its a manual joystick.
sdl_joystick_ = nullptr;
is_mfi_controller_ = (custom_device_name_ == kMFiControllerName);
// Hard code a few remote controls.
// The newer way to do this is just set 'UI-Only' on the device config
is_remote_control_ = ((custom_device_name_ == "Amazon Remote")
@ -162,7 +148,17 @@ auto JoystickInput::HasMeaningfulButtonNames() -> bool {
return g_buildconfig.ostype_android();
}
void JoystickInput::SetButtonName(int button, const std::string& name) {
button_names_[button] = name;
}
auto JoystickInput::GetButtonName(int index) -> std::string {
// First check any explicit ones we were passed.
auto i = button_names_.find(index);
if (i != button_names_.end()) {
return i->second;
}
// FIXME: Should get fancier here now that PS4 and XBone
// controllers are supported through this.
if (is_mfi_controller_) {
@ -179,6 +175,7 @@ auto JoystickInput::GetButtonName(int index) -> std::string {
break;
}
}
if (g_buildconfig.ostype_android()) {
// Special case: if this is a samsung controller, return the dice
// button icons.
@ -446,46 +443,6 @@ void JoystickInput::Update() {
}
}
}
// If a button's being held, potentially pass repeats along.
if (up_held_ || down_held_ || left_held_ || right_held_) {
// Don't ask for the widget unless we have something held.
// (otherwise we prevent other inputs from getting at it)
if (g_base->ui->GetWidgetForInput(this)) {
millisecs_t repeat_delay = kJoystickRepeatDelay;
millisecs_t t = g_core->GetAppTimeMillisecs();
auto c = WidgetMessage::Type::kEmptyMessage;
if (t - last_hold_time_ < repeat_delay) {
return;
}
if (t - last_hold_time_ >= repeat_delay) {
bool pass = false;
if (up_held_) {
pass = true;
c = WidgetMessage::Type::kMoveUp;
} else if (down_held_) {
pass = true;
c = WidgetMessage::Type::kMoveDown;
} else if (left_held_) {
pass = true;
c = WidgetMessage::Type::kMoveLeft;
} else if (right_held_) {
pass = true;
c = WidgetMessage::Type::kMoveRight;
}
if (pass) {
g_base->ui->SendWidgetMessage(WidgetMessage(c));
}
// Set another repeat to happen sooner.
last_hold_time_ =
t
- static_cast<millisecs_t>(static_cast<float>(repeat_delay) * 0.8f);
}
}
}
}
void JoystickInput::SetStandardExtendedButtons() {
@ -508,6 +465,8 @@ void JoystickInput::ResetHeldStates() {
SDL_Event e;
dpad_right_held_ = dpad_left_held_ = dpad_up_held_ = dpad_down_held_ = false;
ui_repeater_.Clear();
run_buttons_held_.clear();
run_trigger1_value_ = run_trigger2_value_ = 0.0f;
UpdateRunningState();
@ -594,28 +553,28 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
|| dpad_down_held_))
return;
bool isHoldPositionEvent = false;
bool is_hold_position_event = false;
// Keep track of whether hold-position is being held. If so, we don't send
// window events. (some joysticks always give us significant axis values but
// window events (some joysticks always give us significant axis values but
// rely on hold position to keep from doing stuff usually).
if (e->type == SDL_JOYBUTTONDOWN
&& e->jbutton.button == hold_position_button_) {
need_to_send_held_state_ = true;
hold_position_held_ = true;
isHoldPositionEvent = true;
is_hold_position_event = true;
}
if (e->type == SDL_JOYBUTTONUP
&& e->jbutton.button == hold_position_button_) {
need_to_send_held_state_ = true;
hold_position_held_ = false;
isHoldPositionEvent = true;
is_hold_position_event = true;
}
// Let's ignore events for just a moment after we're created.
// (some joysticks seem to spit out erroneous button-pressed events when
// first plugged in ).
if (time - creation_time_ < 250 && !isHoldPositionEvent) {
if (time - creation_time_ < 250 && !is_hold_position_event) {
return;
}
@ -700,7 +659,7 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
}
}
// If its the ignore button, ignore it.
// If its an ignored button, ignore it.
if ((e->type == SDL_JOYBUTTONDOWN || e->type == SDL_JOYBUTTONUP)
&& (e->jbutton.button == ignored_button_
|| e->jbutton.button == ignored_button2_
@ -709,49 +668,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
return;
}
// A little pre-filtering on mac PS3 gamepads. (try to filter out some noise
// we're seeing, etc).
if (g_buildconfig.ostype_macos() && is_mac_ps3_controller_) {
switch (e->type) {
case SDL_JOYAXISMOTION: {
// On my ps3 controller, I seem to be seeing occasional joy-axis-events
// coming in with values of -32768 when nothing is being touched.
// Filtering those out here.. Should look into this more and see if its
// SDL's fault or else forward a bug to apple.
if ((e->jaxis.axis == 0 || e->jaxis.axis == 1)
&& e->jaxis.value == -32768
&& (time - ps3_last_joy_press_time_ > 2000) && !ps3_jaxis1_pressed_
&& !ps3_jaxis2_pressed_) {
printf(
"BAJoyStick notice: filtering out errand PS3 axis %d value of "
"%d\n",
static_cast<int>(e->jaxis.axis),
static_cast<int>(e->jaxis.value));
fflush(stdout);
// std::cout << "BSJoyStick notice: filtering out errant PS3 axis " <<
// int(e->jaxis.axis) << " value of " << e->jaxis.value << std::endl;
return;
}
if (abs(e->jaxis.value) >= kJoystickDiscreteThreshold) {
ps3_last_joy_press_time_ = time;
}
// Keep track of whether its pressed for next time.
if (e->jaxis.axis == 0) {
ps3_jaxis1_pressed_ = (abs(e->jaxis.value) > 3000);
} else if (e->jaxis.axis == 1) {
ps3_jaxis2_pressed_ = (abs(e->jaxis.value) > 3000);
}
break;
}
default:
break;
}
}
// A few high level button press interceptions.
if (e->type == SDL_JOYBUTTONDOWN) {
if (e->jbutton.button == start_button_
@ -806,111 +722,116 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
}
}
// If we're in a dialog, send dialog events.
// We keep track of special x/y values for dialog usage.
// If we're in the ui, send ui events.
// We keep track of special x/y values for ui usage.
// These are formed as combinations of the actual joy value
// and the hold-position state.
// Think of hold-position as somewhat of a 'magnitude' to the joy event's
// direction. They're really one and the same event. (we just need to store
// their states ourselves since they don't both come through at once).
bool isAnalogStickJAxisEvent = false;
// FIXME: Ugh need to rip out this old hold-position stuff.
bool is_analog_stick_jaxis_event = false;
if (e->type == SDL_JOYAXISMOTION) {
if (e->jaxis.axis == analog_lr_) {
dialog_jaxis_x_ = e->jaxis.value;
isAnalogStickJAxisEvent = true;
is_analog_stick_jaxis_event = true;
} else if (e->jaxis.axis == analog_ud_) {
dialog_jaxis_y_ = e->jaxis.value;
isAnalogStickJAxisEvent = true;
is_analog_stick_jaxis_event = true;
}
}
int dialogJaxisX = dialog_jaxis_x_;
int ui_jaxis_x = dialog_jaxis_x_;
if (hold_position_held_) {
dialogJaxisX = 0; // Throttle is off.
ui_jaxis_x = 0; // Throttle is off.
}
int dialogJaxisY = dialog_jaxis_y_;
int ui_jaxis_y = dialog_jaxis_y_;
if (hold_position_held_) {
dialogJaxisY = 0; // Throttle is off.
ui_jaxis_y = 0; // Throttle is off.
}
// We might not wanna grab at the UI if we're a axis-motion event
// below our 'pressed' threshold.. Otherwise fuzzy analog joystick
// readings would cause rampant UI stealing even if no events are being sent.
bool would_go_to_dialog = false;
bool would_go_to_ui = false;
auto wm = WidgetMessage::Type::kEmptyMessage;
if (isAnalogStickJAxisEvent || isHoldPositionEvent) {
if (is_analog_stick_jaxis_event || is_hold_position_event) {
// Even when we're not sending, clear out some 'held' states.
if (left_held_ && dialogJaxisX >= -kJoystickDiscreteThreshold) {
if (left_held_ && ui_jaxis_x >= -kJoystickDiscreteThreshold) {
left_held_ = false;
ui_repeater_.Clear();
}
if (right_held_ && dialogJaxisX <= kJoystickDiscreteThreshold) {
if (right_held_ && ui_jaxis_x <= kJoystickDiscreteThreshold) {
right_held_ = false;
ui_repeater_.Clear();
}
if (up_held_ && dialogJaxisY >= -kJoystickDiscreteThreshold) {
if (up_held_ && ui_jaxis_y >= -kJoystickDiscreteThreshold) {
up_held_ = false;
ui_repeater_.Clear();
}
if (down_held_ && dialogJaxisY <= kJoystickDiscreteThreshold) {
if (down_held_ && ui_jaxis_y <= kJoystickDiscreteThreshold) {
down_held_ = false;
ui_repeater_.Clear();
}
if ((!right_held_) && ui_jaxis_x > kJoystickDiscreteThreshold) {
would_go_to_ui = true;
}
if ((!left_held_) && ui_jaxis_x < -kJoystickDiscreteThreshold) {
would_go_to_ui = true;
}
if ((!up_held_) && ui_jaxis_y < -kJoystickDiscreteThreshold) {
would_go_to_ui = true;
}
if ((!down_held_) && ui_jaxis_y > kJoystickDiscreteThreshold) {
would_go_to_ui = true;
}
if ((!right_held_) && dialogJaxisX > kJoystickDiscreteThreshold)
would_go_to_dialog = true;
if ((!left_held_) && dialogJaxisX < -kJoystickDiscreteThreshold)
would_go_to_dialog = true;
if ((!up_held_) && dialogJaxisY < -kJoystickDiscreteThreshold)
would_go_to_dialog = true;
if ((!down_held_) && dialogJaxisY > kJoystickDiscreteThreshold)
would_go_to_dialog = true;
} else if ((e->type == SDL_JOYHATMOTION && e->jhat.hat == hat_)
|| (e->type == SDL_JOYBUTTONDOWN
&& e->jbutton.button != hold_position_button_)) {
// Other button-downs and hat motions always go.
would_go_to_dialog = true;
would_go_to_ui = true;
}
// Resets always circumvent dialogs.
if (resetting_) would_go_to_dialog = false;
if (resetting_) {
would_go_to_ui = false;
}
// Anything that would go to a dialog also counts to mark us as
// 'recently-used'.
if (would_go_to_dialog) {
// Anything that would go to ui also counts to mark us as 'recently-used'.
if (would_go_to_ui) {
UpdateLastInputTime();
}
if (would_go_to_dialog && g_base->ui->GetWidgetForInput(this)) {
bool pass = false;
if (would_go_to_ui && g_base->ui->GetWidgetForInput(this)) {
bool pass{};
// Special case.. either joy-axis-motion or hold-position events trigger
// these.
if (isAnalogStickJAxisEvent || isHoldPositionEvent) {
if (dialogJaxisX > kJoystickDiscreteThreshold) {
// To the right.
if (is_analog_stick_jaxis_event || is_hold_position_event) {
if (ui_jaxis_x > kJoystickDiscreteThreshold) {
if (!right_held_ && !up_held_ && !down_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
right_held_ = true;
pass = true;
wm = WidgetMessage::Type::kMoveRight;
pass = true;
}
} else if (dialogJaxisX < -kJoystickDiscreteThreshold) {
} else if (ui_jaxis_x < -kJoystickDiscreteThreshold) {
if (!left_held_ && !up_held_ && !down_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveLeft;
pass = true;
left_held_ = true;
pass = true;
wm = WidgetMessage::Type::kMoveLeft;
}
}
if (dialogJaxisY > kJoystickDiscreteThreshold) {
if (ui_jaxis_y > kJoystickDiscreteThreshold) {
if (!down_held_ && !left_held_ && !right_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveDown;
pass = true;
down_held_ = true;
}
} else if (dialogJaxisY < -kJoystickDiscreteThreshold) {
if (!up_held_ && !left_held_ && !right_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveUp;
pass = true;
wm = WidgetMessage::Type::kMoveDown;
}
} else if (ui_jaxis_y < -kJoystickDiscreteThreshold) {
if (!up_held_ && !left_held_ && !right_held_) {
up_held_ = true;
pass = true;
wm = WidgetMessage::Type::kMoveUp;
}
}
}
@ -924,7 +845,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
switch (e->jhat.value) {
case SDL_HAT_LEFT: {
if (!left_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveLeft;
pass = true;
left_held_ = true;
@ -935,7 +855,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
case SDL_HAT_RIGHT: {
if (!right_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveRight;
pass = true;
right_held_ = true;
@ -945,7 +864,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
}
case SDL_HAT_UP: {
if (!up_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveUp;
pass = true;
up_held_ = true;
@ -955,7 +873,6 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
}
case SDL_HAT_DOWN: {
if (!down_held_) {
last_hold_time_ = g_core->GetAppTimeMillisecs();
wm = WidgetMessage::Type::kMoveDown;
pass = true;
down_held_ = true;
@ -968,6 +885,7 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
down_held_ = false;
left_held_ = false;
right_held_ = false;
ui_repeater_.Clear();
}
default:
break;
@ -1007,7 +925,20 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) {
break;
}
if (pass) {
g_base->ui->SendWidgetMessage(WidgetMessage(wm));
switch (wm) {
case WidgetMessage::Type::kMoveUp:
case WidgetMessage::Type::kMoveDown:
case WidgetMessage::Type::kMoveLeft:
case WidgetMessage::Type::kMoveRight:
// For UI movement, set up a repeater so we can hold the button.
ui_repeater_ = Repeater::New(
kUINavigationRepeatDelay, kUINavigationRepeatInterval,
[wm] { g_base->ui->SendWidgetMessage(WidgetMessage(wm)); });
break;
default:
// Other messages are just one-shots.
g_base->ui->SendWidgetMessage(WidgetMessage(wm));
}
}
return;
}
@ -1502,22 +1433,7 @@ auto JoystickInput::GetRawDeviceName() -> std::string {
auto JoystickInput::GetDeviceExtraDescription() -> std::string {
std::string s;
// On mac, PS3 controllers can connect via USB or bluetooth,
// and it can be confusing if one is doing both,
// so lets specify here.
if (GetDeviceName() == "PLAYSTATION(R)3 Controller") {
// For bluetooth we get a serial in the form "04-76-6e-d1-17-90" while
// on USB we get a simple int (the usb location id): "-9340234"
// so lets consider it wireless if its got a dash not at the beginning.
s = " (USB)";
auto dname = GetDeviceIdentifier();
for (const char* tst = dname.c_str(); *tst; tst++) {
if (*tst == '-' && tst != dname) {
s = " (Bluetooth)";
}
}
}
// (no longer being used).
return s;
}

View File

@ -3,6 +3,7 @@
#ifndef BALLISTICA_BASE_INPUT_DEVICE_JOYSTICK_INPUT_H_
#define BALLISTICA_BASE_INPUT_DEVICE_JOYSTICK_INPUT_H_
#include <map>
#include <set>
#include <string>
@ -16,7 +17,7 @@ namespace ballistica::base {
const int kJoystickDiscreteThreshold{15000};
const float kJoystickDiscreteThresholdFloat{0.46f};
const int kJoystickAnalogCalibrationDivisions{20};
extern const char* kMFiControllerName;
// extern const char* kMFiControllerName;
/// A physical game controller.
class JoystickInput : public InputDevice {
@ -49,7 +50,6 @@ class JoystickInput : public InputDevice {
auto GetPartyButtonName() const -> std::string override;
auto GetButtonName(int index) -> std::string override;
auto GetAxisName(int index) -> std::string override;
auto IsController() -> bool override { return true; }
@ -73,6 +73,11 @@ class JoystickInput : public InputDevice {
auto HasMeaningfulButtonNames() -> bool override;
auto GetButtonName(int index) -> std::string override;
/// Custom controller types can pass in controller-specific button names.
void SetButtonName(int button, const std::string& name);
protected:
auto GetRawDeviceName() -> std::string override;
auto GetDeviceExtraDescription() -> std::string override;
@ -87,51 +92,51 @@ class JoystickInput : public InputDevice {
void UpdateRunningState();
auto GetCalibratedValue(float raw, float neutral) const -> int32_t;
std::string raw_sdl_joystick_name_;
std::string raw_sdl_joystick_identifier_;
float run_value_{};
JoystickInput* child_joy_stick_{};
JoystickInput* parent_joy_stick_{};
millisecs_t last_ui_only_print_time_{};
bool ui_only_{};
bool unassigned_buttons_run_{true};
bool start_button_activates_default_widget_{true};
bool auto_recalibrate_analog_stick_{};
millisecs_t creation_time_{};
bool did_initial_reset_{};
// FIXME - should take this out and replace it with a bool
// (we never actually access the sdl joystick directly outside of our
// constructor)
SDL_Joystick* sdl_joystick_{};
bool is_test_input_{};
bool is_remote_control_{};
bool is_remote_app_{};
bool is_mfi_controller_{};
bool is_mac_ps3_controller_{};
millisecs_t ps3_last_joy_press_time_{-10000};
bool ui_only_ : 1 {};
bool unassigned_buttons_run_ : 1 {true};
bool start_button_activates_default_widget_ : 1 {true};
bool auto_recalibrate_analog_stick_ : 1 {};
bool did_initial_reset_ : 1 {};
bool is_test_input_ : 1 {};
bool is_remote_control_ : 1 {};
bool is_remote_app_ : 1 {};
bool is_mfi_controller_ : 1 {};
// For dialogs.
bool left_held_{};
bool right_held_{};
bool up_held_{};
bool down_held_{};
bool hold_position_held_{};
bool need_to_send_held_state_{};
int hat_{};
int analog_lr_{};
bool left_held_ : 1 {};
bool right_held_ : 1 {};
bool up_held_ : 1 {};
bool down_held_ : 1 {};
bool hold_position_held_ : 1 {};
bool need_to_send_held_state_ : 1 {};
bool hat_held_ : 1 {};
bool dpad_right_held_ : 1 {};
bool dpad_left_held_ : 1 {};
bool dpad_up_held_ : 1 {};
bool dpad_down_held_ : 1 {};
bool ignore_completely_ : 1 {};
bool resetting_ : 1 {};
bool calibrate_ : 1 {};
bool can_configure_ : 1 {};
int hat_{0};
int analog_lr_{0};
int analog_ud_{1};
millisecs_t last_hold_time_{};
bool hat_held_{};
bool dpad_right_held_{};
bool dpad_left_held_{};
bool dpad_up_held_{};
bool dpad_down_held_{};
// Mappings of ba buttons to SDL buttons.
int jump_button_{};
int jump_button_{0};
int punch_button_{1};
int bomb_button_{2};
int pickup_button_{3};
@ -143,7 +148,6 @@ class JoystickInput : public InputDevice {
// Used on rift build; we have one button which we disallow from joining but
// the rest we allow. (all devices are treated as one and the same there).
int remote_enter_button_{-1};
bool ignore_completely_{};
int ignored_button_{-1};
int ignored_button2_{-1};
int ignored_button3_{-1};
@ -153,12 +157,6 @@ class JoystickInput : public InputDevice {
int run_trigger1_{-1};
int run_trigger2_{-1};
int vr_reorient_button_{-1};
float run_trigger1_min_{};
float run_trigger1_max_{};
float run_trigger2_min_{};
float run_trigger2_max_{};
float run_trigger1_value_{};
float run_trigger2_value_{};
int left_button_{-1};
int right_button_{-1};
int up_button_{-1};
@ -167,15 +165,19 @@ class JoystickInput : public InputDevice {
int right_button2_{-1};
int up_button2_{-1};
int down_button2_{-1};
std::set<int> run_buttons_held_;
int sdl_joystick_id_{};
bool ps3_jaxis1_pressed_{};
bool ps3_jaxis2_pressed_{};
float run_value_{};
float run_trigger1_min_{};
float run_trigger1_max_{};
float run_trigger2_min_{};
float run_trigger2_max_{};
float run_trigger1_value_{};
float run_trigger2_value_{};
float calibration_threshold_{};
float calibration_break_threshold_{};
float analog_calibration_vals_[kJoystickAnalogCalibrationDivisions]{};
std::string custom_device_name_;
bool can_configure_{};
float calibrated_neutral_x_{};
float calibrated_neutral_y_{};
int32_t dialog_jaxis_x_{};
int32_t dialog_jaxis_y_{};
int32_t jaxis_raw_x_{};
@ -183,11 +185,13 @@ class JoystickInput : public InputDevice {
int32_t jaxis_x_{};
int32_t jaxis_y_{};
millisecs_t calibration_start_time_x_{};
float calibrated_neutral_x_{};
millisecs_t calibration_start_time_y_{};
float calibrated_neutral_y_{};
bool resetting_{};
bool calibrate_{};
std::set<int> run_buttons_held_;
std::string custom_device_name_;
std::string raw_sdl_joystick_name_;
std::string raw_sdl_joystick_identifier_;
std::map<int, std::string> button_names_;
Object::Ref<Repeater> ui_repeater_;
BA_DISALLOW_CLASS_COPIES(JoystickInput);
};

View File

@ -2,8 +2,10 @@
#include "ballistica/base/input/device/keyboard_input.h"
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/platform/base_platform.h"
#include "ballistica/base/support/classic_soft.h"
#include "ballistica/base/support/repeater.h"
#include "ballistica/base/ui/ui.h"
namespace ballistica::base {
@ -42,10 +44,12 @@ KeyboardInput::KeyboardInput(KeyboardInput* parent_keyboard_input_in) {
KeyboardInput::~KeyboardInput() = default;
auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down)
-> bool {
auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool down) -> bool {
// Only allow the *main* keyboard to talk to the UI
if (parent_keyboard_input_ == nullptr) {
// Any new event coming in cancels repeats.
ui_repeater_.Clear();
if (g_base->ui->GetWidgetForInput(this)) {
bool pass = false;
auto c = WidgetMessage::Type::kEmptyMessage;
@ -78,10 +82,8 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down)
case SDLK_SPACE:
case SDLK_KP_ENTER:
case SDLK_RETURN:
if (!repeat) {
c = WidgetMessage::Type::kActivate;
pass = true;
}
c = WidgetMessage::Type::kActivate;
pass = true;
break;
case SDLK_ESCAPE:
// (limit to kb1 so we don't get double-beeps on failure)
@ -123,32 +125,44 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down)
}
}
if (pass) {
g_base->ui->SendWidgetMessage(WidgetMessage(c, keysym));
// For movement and key press widget events, set up repeats.
// Otherwise run a single time immediately.
switch (c) {
case WidgetMessage::Type::kMoveUp:
case WidgetMessage::Type::kMoveDown:
case WidgetMessage::Type::kMoveLeft:
case WidgetMessage::Type::kMoveRight:
case WidgetMessage::Type::kKey:
// Note: Need to pass keysym along as a value; not a pointer.
ui_repeater_ = Repeater::New(
g_base->app_adapter->GetKeyRepeatDelay(),
g_base->app_adapter->GetKeyRepeatInterval(),
[c, keysym = *keysym] {
g_base->ui->SendWidgetMessage(WidgetMessage(c, &keysym));
});
break;
default:
g_base->ui->SendWidgetMessage(WidgetMessage(c, keysym));
break;
}
}
return (pass);
}
}
// Bring up menu if start is pressed.
if (keysym->sym == start_key_ && !repeat && !g_base->ui->MainMenuVisible()) {
if (keysym->sym == start_key_ && !g_base->ui->MainMenuVisible()) {
g_base->ui->PushMainMenuPressCall(this);
return true;
}
// Clion seems to think child_keyboard_input_ will never be set here (it will).
#pragma clang diagnostic push
#pragma ide diagnostic ignored "UnreachableCode"
#pragma ide diagnostic ignored "ConstantConditionsOC"
// At this point, if we have a child input, let it try to handle things.
if (child_keyboard_input_ && enable_child_) {
if (child_keyboard_input_->HandleKey(keysym, repeat, down)) {
if (child_keyboard_input_->HandleKey(keysym, down)) {
return true;
}
}
#pragma clang diagnostic pop
if (!AttachedToPlayer()) {
if (down
&& ((keysym->sym == jump_key_) || (keysym->sym == punch_key_)
@ -173,152 +187,151 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down)
// (removing init values from input_type and input_type_2 gives a
// 'possibly uninited value used' warning but leaving them gives a
// 'values unused' warning. Grumble.)
explicit_bool(input_type
== (explicit_bool(false) ? input_type_2 : InputType::kLast));
// explicit_bool(input_type
// == (explicit_bool(false) ? input_type_2 :
// InputType::kLast));
if (!repeat) {
// Keyboard 1 supports assigned keys plus arrow keys if they're unused.
if (keysym->sym == left_key_
|| (device_number() == 1 && keysym->sym == SDLK_LEFT
&& !left_key_assigned())) {
player_input = true;
input_type = InputType::kLeftRight;
left_held_ = down;
if (down) {
if (right_held_) {
input_value = 0;
} else {
input_value = -32767;
}
// Keyboard 1 supports assigned keys plus arrow keys if they're unused.
if (keysym->sym == left_key_
|| (device_number() == 1 && keysym->sym == SDLK_LEFT
&& !left_key_assigned())) {
player_input = true;
input_type = InputType::kLeftRight;
left_held_ = down;
if (down) {
if (right_held_) {
input_value = 0;
} else {
if (right_held_) {
input_value = 32767;
}
}
} else if (keysym->sym == right_key_
|| (device_number() == 1 && keysym->sym == SDLK_RIGHT
&& !right_key_assigned())) {
// Keyboard 1 supports assigned keys plus arrow keys if they're unused.
player_input = true;
input_type = InputType::kLeftRight;
right_held_ = down;
if (down) {
if (left_held_) {
input_value = 0;
} else {
input_value = 32767;
}
} else {
if (left_held_) {
input_value = -32767;
}
}
} else if (keysym->sym == up_key_
|| (device_number() == 1 && keysym->sym == SDLK_UP
&& !up_key_assigned())) {
player_input = true;
input_type = InputType::kUpDown;
up_held_ = down;
if (down) {
if (down_held_) {
input_value = 0;
} else {
input_value = 32767;
}
} else {
if (down_held_) input_value = -32767;
}
} else if (keysym->sym == down_key_
|| (device_number() == 1 && keysym->sym == SDLK_DOWN
&& !down_key_assigned())) {
player_input = true;
input_type = InputType::kUpDown;
down_held_ = down;
if (down) {
if (up_held_) {
input_value = 0;
} else {
input_value = -32767;
}
} else {
if (up_held_) input_value = 32767;
}
} else if (keysym->sym == punch_key_) {
player_input = true;
UpdateRun(keysym->sym, down);
if (down) {
input_type = InputType::kPunchPress;
} else {
input_type = InputType::kPunchRelease;
}
} else if (keysym->sym == bomb_key_) {
player_input = true;
UpdateRun(keysym->sym, down);
if (down)
input_type = InputType::kBombPress;
else
input_type = InputType::kBombRelease;
} else if (keysym->sym == hold_position_key_) {
player_input = true;
if (down) {
input_type = InputType::kHoldPositionPress;
} else {
input_type = InputType::kHoldPositionRelease;
}
} else if (keysym->sym == pick_up_key_) {
player_input = true;
UpdateRun(keysym->sym, down);
if (down) {
input_type = InputType::kPickUpPress;
} else {
input_type = InputType::kPickUpRelease;
}
} else if ((device_number() == 1 && keysym->sym == SDLK_RETURN)
|| (device_number() == 1 && keysym->sym == SDLK_KP_ENTER)
|| keysym->sym == jump_key_) {
// Keyboard 1 claims certain keys if they are otherwise unclaimed
// (arrow keys, enter/return, etc).
player_input = true;
UpdateRun(keysym->sym, down);
if (down) {
input_type = InputType::kJumpPress;
have_input_2 = true;
input_type_2 = InputType::kFlyPress;
} else {
input_type = InputType::kJumpRelease;
have_input_2 = true;
input_type_2 = InputType::kFlyRelease;
input_value = -32767;
}
} else {
// Any other keys get processed as run keys.
// keypad keys go to player 2 - anything else to player 1.
switch (keysym->sym) {
case SDLK_KP_0:
case SDLK_KP_1:
case SDLK_KP_2:
case SDLK_KP_3:
case SDLK_KP_4:
case SDLK_KP_5:
case SDLK_KP_6:
case SDLK_KP_7:
case SDLK_KP_8:
case SDLK_KP_9:
case SDLK_KP_PLUS:
case SDLK_KP_MINUS:
case SDLK_KP_ENTER:
if (device_number() == 2) {
UpdateRun(keysym->sym, down);
return true;
}
break;
default:
if (device_number() == 1) {
UpdateRun(keysym->sym, down);
return true;
}
break;
if (right_held_) {
input_value = 32767;
}
}
} else if (keysym->sym == right_key_
|| (device_number() == 1 && keysym->sym == SDLK_RIGHT
&& !right_key_assigned())) {
// Keyboard 1 supports assigned keys plus arrow keys if they're unused.
player_input = true;
input_type = InputType::kLeftRight;
right_held_ = down;
if (down) {
if (left_held_) {
input_value = 0;
} else {
input_value = 32767;
}
} else {
if (left_held_) {
input_value = -32767;
}
}
} else if (keysym->sym == up_key_
|| (device_number() == 1 && keysym->sym == SDLK_UP
&& !up_key_assigned())) {
player_input = true;
input_type = InputType::kUpDown;
up_held_ = down;
if (down) {
if (down_held_) {
input_value = 0;
} else {
input_value = 32767;
}
} else {
if (down_held_) input_value = -32767;
}
} else if (keysym->sym == down_key_
|| (device_number() == 1 && keysym->sym == SDLK_DOWN
&& !down_key_assigned())) {
player_input = true;
input_type = InputType::kUpDown;
down_held_ = down;
if (down) {
if (up_held_) {
input_value = 0;
} else {
input_value = -32767;
}
} else {
if (up_held_) input_value = 32767;
}
} else if (keysym->sym == punch_key_) {
player_input = true;
UpdateRun_(keysym->sym, down);
if (down) {
input_type = InputType::kPunchPress;
} else {
input_type = InputType::kPunchRelease;
}
} else if (keysym->sym == bomb_key_) {
player_input = true;
UpdateRun_(keysym->sym, down);
if (down)
input_type = InputType::kBombPress;
else
input_type = InputType::kBombRelease;
} else if (keysym->sym == hold_position_key_) {
player_input = true;
if (down) {
input_type = InputType::kHoldPositionPress;
} else {
input_type = InputType::kHoldPositionRelease;
}
} else if (keysym->sym == pick_up_key_) {
player_input = true;
UpdateRun_(keysym->sym, down);
if (down) {
input_type = InputType::kPickUpPress;
} else {
input_type = InputType::kPickUpRelease;
}
} else if ((device_number() == 1 && keysym->sym == SDLK_RETURN)
|| (device_number() == 1 && keysym->sym == SDLK_KP_ENTER)
|| keysym->sym == jump_key_) {
// Keyboard 1 claims certain keys if they are otherwise unclaimed
// (arrow keys, enter/return, etc).
player_input = true;
UpdateRun_(keysym->sym, down);
if (down) {
input_type = InputType::kJumpPress;
have_input_2 = true;
input_type_2 = InputType::kFlyPress;
} else {
input_type = InputType::kJumpRelease;
have_input_2 = true;
input_type_2 = InputType::kFlyRelease;
}
} else {
// Any other keys get processed as run keys.
// keypad keys go to player 2 - anything else to player 1.
switch (keysym->sym) {
case SDLK_KP_0:
case SDLK_KP_1:
case SDLK_KP_2:
case SDLK_KP_3:
case SDLK_KP_4:
case SDLK_KP_5:
case SDLK_KP_6:
case SDLK_KP_7:
case SDLK_KP_8:
case SDLK_KP_9:
case SDLK_KP_PLUS:
case SDLK_KP_MINUS:
case SDLK_KP_ENTER:
if (device_number() == 2) {
UpdateRun_(keysym->sym, down);
return true;
}
break;
default:
if (device_number() == 1) {
UpdateRun_(keysym->sym, down);
return true;
}
break;
}
}
if (player_input) {
@ -344,7 +357,7 @@ void KeyboardInput::ResetHeldStates() {
}
}
void KeyboardInput::UpdateRun(SDL_Keycode key, bool down) {
void KeyboardInput::UpdateRun_(SDL_Keycode key, bool down) {
bool was_held = (!keys_held_.empty());
if (down) {
keys_held_.insert(key);
@ -407,51 +420,51 @@ void KeyboardInput::UpdateMapping() {
int val = cl ? cl->GetControllerValue(this, "buttonJump") : -1;
jump_key_ = (val == -1) ? jump_key_default : (SDL_Keycode)val;
UpdateArrowKeys(jump_key_);
UpdateArrowKeys_(jump_key_);
val = cl ? cl->GetControllerValue(this, "buttonPunch") : -1;
punch_key_ = (val == -1) ? punch_key_default : (SDL_Keycode)val;
UpdateArrowKeys(punch_key_);
UpdateArrowKeys_(punch_key_);
val = cl ? cl->GetControllerValue(this, "buttonBomb") : -1;
bomb_key_ = (val == -1) ? bomb_key_default : (SDL_Keycode)val;
UpdateArrowKeys(bomb_key_);
UpdateArrowKeys_(bomb_key_);
val = cl ? cl->GetControllerValue(this, "buttonPickUp") : -1;
pick_up_key_ = (val == -1) ? pick_up_key_default : (SDL_Keycode)val;
UpdateArrowKeys(pick_up_key_);
UpdateArrowKeys_(pick_up_key_);
val = cl ? cl->GetControllerValue(this, "buttonHoldPosition") : -1;
hold_position_key_ =
(val == -1) ? hold_position_key_default : (SDL_Keycode)val;
UpdateArrowKeys(hold_position_key_);
UpdateArrowKeys_(hold_position_key_);
val = cl ? cl->GetControllerValue(this, "buttonStart") : -1;
start_key_ = (val == -1) ? start_key_default : (SDL_Keycode)val;
UpdateArrowKeys(start_key_);
UpdateArrowKeys_(start_key_);
val = cl ? cl->GetControllerValue(this, "buttonUp") : -1;
up_key_ = (val == -1) ? up_key_default : (SDL_Keycode)val;
UpdateArrowKeys(up_key_);
UpdateArrowKeys_(up_key_);
val = cl ? cl->GetControllerValue(this, "buttonDown") : -1;
down_key_ = (val == -1) ? down_key_default : (SDL_Keycode)val;
UpdateArrowKeys(down_key_);
UpdateArrowKeys_(down_key_);
val = cl ? cl->GetControllerValue(this, "buttonLeft") : -1;
left_key_ = (val == -1) ? left_key_default : (SDL_Keycode)val;
UpdateArrowKeys(left_key_);
UpdateArrowKeys_(left_key_);
val = cl ? cl->GetControllerValue(this, "buttonRight") : -1;
right_key_ = (val == -1) ? right_key_default : (SDL_Keycode)val;
UpdateArrowKeys(right_key_);
UpdateArrowKeys_(right_key_);
enable_child_ = true;
up_held_ = down_held_ = left_held_ = right_held_ = false;
}
void KeyboardInput::UpdateArrowKeys(SDL_Keycode key) {
void KeyboardInput::UpdateArrowKeys_(SDL_Keycode key) {
if (key == SDLK_UP) {
up_key_assigned_ = true;
} else if (key == SDLK_DOWN) {
@ -464,8 +477,7 @@ void KeyboardInput::UpdateArrowKeys(SDL_Keycode key) {
}
auto KeyboardInput::GetButtonName(int index) -> std::string {
return g_base->platform->GetKeyName(index);
// return InputDevice::GetButtonName(index);
return g_base->app_adapter->GetKeyName(index);
}
auto KeyboardInput::GetRawDeviceName() -> std::string { return "Keyboard"; }

View File

@ -8,6 +8,7 @@
#include "ballistica/base/input/device/input_device.h"
#include "ballistica/core/platform/support/min_sdl.h"
#include "ballistica/shared/foundation/object.h"
namespace ballistica::base {
@ -15,7 +16,7 @@ class KeyboardInput : public InputDevice {
public:
explicit KeyboardInput(KeyboardInput* parent);
~KeyboardInput() override;
auto HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) -> bool;
auto HandleKey(const SDL_Keysym* keysym, bool down) -> bool;
void UpdateMapping() override;
auto GetRawDeviceName() -> std::string override;
void ResetHeldStates() override;
@ -29,8 +30,17 @@ class KeyboardInput : public InputDevice {
auto GetButtonName(int index) -> std::string override;
private:
void UpdateArrowKeys(SDL_Keycode key);
void UpdateRun(SDL_Keycode key, bool down);
void UpdateArrowKeys_(SDL_Keycode key);
void UpdateRun_(SDL_Keycode key, bool down);
bool down_held_ : 1 {};
bool up_held_ : 1 {};
bool left_held_ : 1 {};
bool right_held_ : 1 {};
bool enable_child_ : 1 {};
bool left_key_assigned_ : 1 {};
bool right_key_assigned_ : 1 {};
bool up_key_assigned_ : 1 {};
bool down_key_assigned_ : 1 {};
SDL_Keycode up_key_{};
SDL_Keycode down_key_{};
SDL_Keycode left_key_{};
@ -41,18 +51,10 @@ class KeyboardInput : public InputDevice {
SDL_Keycode pick_up_key_{};
SDL_Keycode hold_position_key_{};
SDL_Keycode start_key_{};
bool down_held_{};
bool up_held_{};
bool left_held_{};
bool right_held_{};
bool enable_child_{};
bool left_key_assigned_{};
bool right_key_assigned_{};
bool up_key_assigned_{};
bool down_key_assigned_{};
KeyboardInput* parent_keyboard_input_{};
KeyboardInput* child_keyboard_input_{};
std::set<int> keys_held_;
Object::Ref<Repeater> ui_repeater_;
};
} // namespace ballistica::base

View File

@ -22,7 +22,7 @@ TestInput::~TestInput() {
}
void TestInput::Reset() {
assert(g_core->InMainThread());
assert(g_base->InLogicThread());
reset_ = true;
}
@ -33,7 +33,7 @@ void TestInput::HandleAlreadyPressedTwice() {
}
void TestInput::Process(millisecs_t time) {
assert(g_core->InMainThread());
assert(g_base->InLogicThread());
if (reset_) {
reset_ = false;

View File

@ -16,20 +16,21 @@ class TestInput {
private:
void HandleAlreadyPressedTwice();
int lr_{};
int ud_{};
bool jump_pressed_{};
bool bomb_pressed_{};
bool pickup_pressed_{};
bool punch_pressed_{};
int join_press_count_{};
bool jump_pressed_ : 1 {};
bool bomb_pressed_ : 1 {};
bool pickup_pressed_ : 1 {};
bool punch_pressed_ : 1 {};
bool print_non_join_ : 1 {};
bool print_already_did2_ : 1 {};
bool reset_ : 1 {true};
millisecs_t next_event_time_{};
millisecs_t join_start_time_{};
millisecs_t join_end_time_{9999};
int join_press_count_{};
bool reset_{true};
JoystickInput* joystick_{};
bool print_non_join_{};
bool print_already_did2_{};
};
} // namespace ballistica::base

View File

@ -8,14 +8,11 @@
#include "ballistica/base/graphics/support/camera.h"
#include "ballistica/base/input/device/joystick_input.h"
#include "ballistica/base/input/device/keyboard_input.h"
#include "ballistica/base/input/device/test_input.h"
#include "ballistica/base/input/device/touch_input.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/ui/dev_console.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/shared/buildconfig/buildconfig_common.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/generic/utils.h"
@ -147,24 +144,22 @@ auto Input::GetNewNumberedIdentifier_(const std::string& name,
return full_id;
}
void Input::CreateTouchInput() {
assert(g_core->InMainThread());
assert(touch_input_ == nullptr);
touch_input_ = Object::NewDeferred<TouchInput>();
PushAddInputDeviceCall(touch_input_, false);
}
// void Input::CreateTouchInput() {
// assert(g_core->InMainThread());
// }
void Input::AnnounceConnects_() {
assert(g_base->InLogicThread());
static bool first_print = true;
// For the first announcement just say "X controllers detected" and don't
// have a sound.
if (first_print && g_core->GetAppTimeSeconds() < 5.0) {
if (first_print && g_core->GetAppTimeSeconds() < 3.0) {
first_print = false;
// Disabling this completely on Android for now; we often get large
// numbers of devices there that aren't actually devices.
bool do_print_initial_counts{!g_buildconfig.ostype_android()};
// If there's been several connected, just give a number.
@ -238,12 +233,15 @@ void Input::ShowStandardInputDeviceConnectedMessage_(InputDevice* j) {
}
newly_connected_controllers_.push_back(j->GetDeviceName() + suffix);
// Set a timer to go off and announce the accumulated additions.
// Set a timer to go off and announce controller additions. This allows
// several connecting at (almost) the same time to be announced as a
// single event.
if (connect_print_timer_id_ != 0) {
g_base->logic->DeleteAppTimer(connect_print_timer_id_);
}
connect_print_timer_id_ = g_base->logic->NewAppTimer(
250, false, NewLambdaRunnable([this] { AnnounceConnects_(); }));
500 * 1000, false,
NewLambdaRunnable([this] { AnnounceConnects_(); }).Get());
}
void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) {
@ -258,7 +256,8 @@ void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) {
g_base->logic->DeleteAppTimer(disconnect_print_timer_id_);
}
disconnect_print_timer_id_ = g_base->logic->NewAppTimer(
250, false, NewLambdaRunnable([this] { AnnounceDisconnects_(); }));
250 * 1000, false,
NewLambdaRunnable([this] { AnnounceDisconnects_(); }).Get());
}
void Input::PushAddInputDeviceCall(InputDevice* input_device,
@ -557,7 +556,14 @@ void Input::UpdateEnabledControllerSubsystems_() {
// }
}
void Input::OnAppStart() { assert(g_base->InLogicThread()); }
void Input::OnAppStart() {
assert(g_base->InLogicThread());
if (g_core->platform->HasTouchScreen()) {
assert(touch_input_ == nullptr);
touch_input_ = Object::NewDeferred<TouchInput>();
PushAddInputDeviceCall(touch_input_, false);
}
}
void Input::OnAppPause() { assert(g_base->InLogicThread()); }
@ -759,63 +765,12 @@ void Input::PrintLockLabels_() {
Log(LogLevel::kError, s);
}
void Input::ProcessStressTesting(int player_count) {
assert(g_core->InMainThread());
assert(player_count >= 0);
millisecs_t time = g_core->GetAppTimeMillisecs();
// FIXME: If we don't check for stress_test_last_leave_time_ we totally
// confuse the game.. need to be able to survive that.
// Kill some off if we have too many.
while (static_cast<int>(test_inputs_.size()) > player_count) {
delete test_inputs_.front();
test_inputs_.pop_front();
}
// If we have less than full test-inputs, add one randomly.
if (static_cast<int>(test_inputs_.size()) < player_count
&& ((rand() % 1000 < 10))) { // NOLINT
test_inputs_.push_back(new TestInput());
}
// Every so often lets kill the oldest one off.
if (explicit_bool(true)) {
if (test_inputs_.size() > 0 && (rand() % 2000 < 3)) { // NOLINT
stress_test_last_leave_time_ = time;
// Usually do oldest; sometimes newest.
if (rand() % 5 == 0) { // NOLINT
delete test_inputs_.back();
test_inputs_.pop_back();
} else {
delete test_inputs_.front();
test_inputs_.pop_front();
}
}
}
if (time - stress_test_time_ > 1000) {
stress_test_time_ = time; // reset..
for (auto& test_input : test_inputs_) {
(*test_input).Reset();
}
}
while (stress_test_time_ < time) {
stress_test_time_++;
for (auto& test_input : test_inputs_) {
(*test_input).Process(stress_test_time_);
}
}
}
void Input::PushTextInputEvent(const std::string& text) {
assert(g_base->logic->event_loop());
g_base->logic->event_loop()->PushCall([this, text] {
MarkInputActive();
// If if the app doesn't want direct text input right now.
// If the app doesn't want direct text input right now, ignore.
if (!g_base->app_adapter->HasDirectKeyboardInput()) {
return;
}
@ -825,6 +780,22 @@ void Input::PushTextInputEvent(const std::string& text) {
return;
}
// Also ignore if there are any mod keys being held.
// We process some of our own keyboard shortcuts and don't
// want text input to come through at the same time.
if (keys_held_.contains(SDLK_LCTRL) || keys_held_.contains(SDLK_RCTRL)
|| keys_held_.contains(SDLK_LALT) || keys_held_.contains(SDLK_RALT)
|| keys_held_.contains(SDLK_LGUI) || keys_held_.contains(SDLK_RGUI)) {
return;
}
// Ignore back-tick and tilde because we use that key to toggle the console.
// FIXME: Perhaps should allow typing it if some control-character is
// held?
if (text == "`" || text == "~") {
return;
}
// We try to handle char filtering here (to keep it consistent across
// platforms) but make a stink if they sent us something that we can't
// at least translate to unicode.
@ -848,6 +819,7 @@ void Input::PushTextInputEvent(const std::string& text) {
&& g_base->ui->dev_console()->HandleTextEditing(text)) {
return;
}
g_base->ui->SendWidgetMessage(WidgetMessage(
WidgetMessage::Type::kTextInput, nullptr, 0, 0, 0, 0, text.c_str()));
});
@ -986,6 +958,34 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
return;
}
// Nowadays we don't want the OS to deliver repeat events to us,
// so filter out any that we get and make noise that they should stop. We
// explicitly handle repeats for UI purposes at the InputDevice or Widget
// level now.
if (keys_held_.find(keysym.sym) != keys_held_.end()) {
// Look out for several repeats coming in within the span of a few
// seconds and complain if it happens. This should allow for the random
// fluke repeat key press event due to funky OS circumstances.
static int count{};
static seconds_t last_count_reset_time{};
auto now = g_core->GetAppTimeSeconds();
if (now - last_count_reset_time > 2.0) {
count = 0;
last_count_reset_time = now;
} else {
count++;
if (count > 10) {
BA_LOG_ONCE(
LogLevel::kWarning,
"Input::HandleKeyPress_ seems to be getting passed repeat key"
" press events. Only initial press events should be passed.");
}
}
return;
}
keys_held_.insert(keysym.sym);
// If someone is capturing these events, give them a crack at it.
if (keyboard_input_capture_press_) {
if (keyboard_input_capture_press_(keysym)) {
@ -998,52 +998,36 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
// ideally we should use the modifiers bundled with the key presses)
UpdateModKeyStates_(&keysym, true);
bool repeat_press;
if (keys_held_.count(keysym.sym) != 0) {
repeat_press = true;
} else {
repeat_press = false;
keys_held_.insert(keysym.sym);
}
// Mobile-specific stuff.
if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) {
switch (keysym.sym) {
// FIXME: See if this stuff is still necessary. Was this perhaps
// specifically to support the console?
case SDLK_DELETE:
case SDLK_RETURN:
case SDLK_KP_ENTER:
case SDLK_BACKSPACE: {
// FIXME: I don't remember what this was put here for, but now that
// we have hardware keyboards it crashes text fields by sending
// them a TEXT_INPUT message with no string.. I made them resistant
// to that case but wondering if we can take this out?
g_base->ui->SendWidgetMessage(
WidgetMessage(WidgetMessage::Type::kTextInput, &keysym));
break;
}
default:
break;
}
}
// if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) {
// switch (keysym.sym) {
// // FIXME: See if this stuff is still necessary. Was this perhaps
// // specifically to support the console?
// case SDLK_DELETE:
// case SDLK_RETURN:
// case SDLK_KP_ENTER:
// case SDLK_BACKSPACE: {
// // FIXME: I don't remember what this was put here for, but now that
// // we have hardware keyboards it crashes text fields by sending
// // them a TEXT_INPUT message with no string.. I made them resistant
// // to that case but wondering if we can take this out?
// g_base->ui->SendWidgetMessage(
// WidgetMessage(WidgetMessage::Type::kTextInput, &keysym));
// break;
// }
// default:
// break;
// }
// }
// Explicitly handle fullscreen-toggles in some cases.
if (g_base->app_adapter->FullscreenControlAvailable()) {
bool do_toggle{};
// On our Mac SDL builds we support ctrl+F for toggling fullscreen.
// On our nice Cocoa build, fullscreening happens magically through the
// view menu fullscreen controls.
if (g_buildconfig.ostype_macos() && !g_buildconfig.xcode_build()) {
if (!repeat_press && keysym.sym == SDLK_f && ((keysym.mod & KMOD_CTRL))) {
do_toggle = true;
}
}
// On Windows we support both F11 and Alt+Enter for toggling fullscreen.
if (g_buildconfig.ostype_windows()) {
if (!repeat_press
&& (keysym.sym == SDLK_F11
|| (keysym.sym == SDLK_RETURN && ((keysym.mod & KMOD_ALT))))) {
// On our SDL builds we support both F11 and Alt+Enter for toggling
// fullscreen.
if (g_buildconfig.sdl_build()) {
if ((keysym.sym == SDLK_F11
|| (keysym.sym == SDLK_RETURN && ((keysym.mod & KMOD_ALT))))) {
do_toggle = true;
}
}
@ -1055,120 +1039,117 @@ void Input::HandleKeyPress_(const SDL_Keysym& keysym) {
}
}
// Control-Q quits. On Mac, the usual Cmd-Q gets handled implicitly by the
// app-adapter.
// UPDATE: Disabling this for now. Looks like standard OS shortcuts like
// Alt+F4 on windows or Cmd-Q on Mac are doing the right thing with SDL
// builds these days so these are not needed.
// if (!repeat_press && keysym.sym == SDLK_q && (keysym.mod & KMOD_CTRL)) {
// g_base->QuitApp(true);
// return;
// }
// Ctrl-V or Cmd-V sends paste commands to the console or any interested
// text fields.
if (keysym.sym == SDLK_v
&& ((keysym.mod & KMOD_CTRL) || (keysym.mod & KMOD_GUI))) {
if (auto* console = g_base->ui->dev_console()) {
if (console->PasteFromClipboard()) {
return;
}
}
g_base->ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste));
return;
}
// Let the console intercept stuff if it wants at this point.
// Dev Console.
if (auto* console = g_base->ui->dev_console()) {
if (keysym.sym == SDLK_BACKQUOTE || keysym.sym == SDLK_F2) {
// (reset input so characters don't continue walking and stuff)
g_base->input->ResetHoldStates();
console->ToggleState();
return;
}
if (console->HandleKeyPress(&keysym)) {
return;
}
}
// Ctrl-V or Cmd-V sends paste commands to any interested text fields.
// Command-Q or Control-Q quits.
if (!repeat_press && keysym.sym == SDLK_v
&& ((keysym.mod & KMOD_CTRL) || (keysym.mod & KMOD_GUI))) {
g_base->ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste));
return;
}
bool handled = false;
// None of the following stuff accepts key repeats.
if (!repeat_press) {
switch (keysym.sym) {
// Menu button on android/etc. pops up the menu.
case SDLK_MENU: {
if (!g_base->ui->MainMenuVisible()) {
g_base->ui->PushMainMenuPressCall(touch_input_);
}
handled = true;
break;
switch (keysym.sym) {
// Menu button on android/etc. pops up the menu.
case SDLK_MENU: {
if (!g_base->ui->MainMenuVisible()) {
g_base->ui->PushMainMenuPressCall(touch_input_);
}
case SDLK_EQUALS:
case SDLK_PLUS:
if (keysym.mod & KMOD_CTRL) {
g_base->app_mode()->ChangeGameSpeed(1);
handled = true;
}
break;
case SDLK_MINUS:
if (keysym.mod & KMOD_CTRL) {
g_base->app_mode()->ChangeGameSpeed(-1);
handled = true;
}
break;
case SDLK_F5: {
if (g_base->ui->PartyIconVisible()) {
g_base->ui->ActivatePartyIcon();
}
handled = true;
break;
}
case SDLK_F7:
assert(g_base->logic->event_loop());
g_base->logic->event_loop()->PushCall(
[] { g_base->graphics->ToggleManualCamera(); });
handled = true;
break;
case SDLK_F8:
assert(g_base->logic->event_loop());
g_base->logic->event_loop()->PushCall(
[] { g_base->graphics->ToggleNetworkDebugDisplay(); });
handled = true;
break;
case SDLK_F9:
g_base->python->objs().PushCall(
BasePython::ObjID::kLanguageTestToggleCall);
handled = true;
break;
case SDLK_F10:
assert(g_base->logic->event_loop());
g_base->logic->event_loop()->PushCall(
[] { g_base->graphics->ToggleDebugDraw(); });
handled = true;
break;
case SDLK_ESCAPE:
if (!g_base->ui->MainMenuVisible()) {
// There's no main menu up. Ask for one.
// Note: keyboard_input_ may be nullptr but escape key should
// still function for menus; it just won't claim ownership.
g_base->ui->PushMainMenuPressCall(keyboard_input_);
} else {
// Ok there *is* a main menu up. Send it a cancel message.
g_base->ui->SendWidgetMessage(
WidgetMessage(WidgetMessage::Type::kCancel));
}
handled = true;
break;
default:
break;
handled = true;
break;
}
case SDLK_EQUALS:
case SDLK_PLUS:
if (keysym.mod & KMOD_CTRL) {
g_base->app_mode()->ChangeGameSpeed(1);
handled = true;
}
break;
case SDLK_MINUS:
if (keysym.mod & KMOD_CTRL) {
g_base->app_mode()->ChangeGameSpeed(-1);
handled = true;
}
break;
case SDLK_F5: {
if (g_base->ui->PartyIconVisible()) {
g_base->ui->ActivatePartyIcon();
}
handled = true;
break;
}
case SDLK_F7:
assert(g_base->logic->event_loop());
g_base->logic->event_loop()->PushCall(
[] { g_base->graphics->ToggleManualCamera(); });
handled = true;
break;
case SDLK_F8:
assert(g_base->logic->event_loop());
g_base->logic->event_loop()->PushCall(
[] { g_base->graphics->ToggleNetworkDebugDisplay(); });
handled = true;
break;
case SDLK_F9:
g_base->python->objs().PushCall(
BasePython::ObjID::kLanguageTestToggleCall);
handled = true;
break;
case SDLK_F10:
assert(g_base->logic->event_loop());
g_base->logic->event_loop()->PushCall(
[] { g_base->graphics->ToggleDebugDraw(); });
handled = true;
break;
case SDLK_ESCAPE:
if (!g_base->ui->MainMenuVisible()) {
// There's no main menu up. Ask for one.
// Note: keyboard_input_ may be nullptr but escape key should
// still function for menus; it just won't claim ownership.
g_base->ui->PushMainMenuPressCall(keyboard_input_);
} else {
// Ok there *is* a main menu up. Send it a cancel message.
g_base->ui->SendWidgetMessage(
WidgetMessage(WidgetMessage::Type::kCancel));
}
handled = true;
break;
default:
break;
}
// If we haven't claimed it, pass it along as potential player/widget input.
// If we haven't handled this, pass it along as potential player/widget input.
if (!handled) {
if (keyboard_input_) {
keyboard_input_->HandleKey(&keysym, repeat_press, true);
keyboard_input_->HandleKey(&keysym, true);
}
}
}
@ -1184,7 +1165,7 @@ void Input::HandleKeyRelease_(const SDL_Keysym& keysym) {
// In some cases we may receive duplicate key-release events (if a
// keyboard reset was run, it deals out key releases, but then the
// keyboard driver issues them as well).
if (keys_held_.count(keysym.sym) == 0) {
if (keys_held_.find(keysym.sym) == keys_held_.end()) {
return;
}
@ -1205,7 +1186,7 @@ void Input::HandleKeyRelease_(const SDL_Keysym& keysym) {
}
if (keyboard_input_) {
keyboard_input_->HandleKey(&keysym, false, false);
keyboard_input_->HandleKey(&keysym, false);
}
}

View File

@ -114,10 +114,9 @@ class Input {
// something.
auto HaveControllerWithPlayer() -> bool;
auto HaveRemoteAppController() -> bool;
void ProcessStressTesting(int player_count);
auto keyboard_input() const -> KeyboardInput* { return keyboard_input_; }
auto keyboard_input_2() const -> KeyboardInput* { return keyboard_input_2_; }
void CreateTouchInput();
// void CreateTouchInput();
void PushTextInputEvent(const std::string& text);
void PushKeyPressEventSimple(int keycode);
@ -205,7 +204,6 @@ class Input {
std::list<std::string> input_lock_permanent_labels_;
std::list<std::string> input_unlock_permanent_labels_;
std::list<std::string> recent_input_locks_unlocks_;
std::list<TestInput*> test_inputs_;
std::list<std::string> newly_connected_controllers_;
std::list<std::string> newly_disconnected_controllers_;
std::unordered_map<std::string, std::unordered_map<std::string, int> >
@ -214,8 +212,6 @@ class Input {
std::set<int> keys_held_;
millisecs_t last_input_device_count_update_time_{};
millisecs_t last_input_temp_lock_time_{};
millisecs_t stress_test_time_{};
millisecs_t stress_test_last_leave_time_{};
void* single_touch_{};
KeyboardInput* keyboard_input_{};
KeyboardInput* keyboard_input_2_{};

View File

@ -5,6 +5,7 @@
#include "ballistica/base/assets/assets.h"
#include "ballistica/base/audio/audio.h"
#include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/graphics/support/screen_messages.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/networking/network_reader.h"
@ -162,7 +163,8 @@ void RemoteAppServer::HandleData(int socket, uint8_t* buffer, size_t amt,
g_base->assets->GetResourceString("controllerDisconnectedText");
Utils::StringReplaceOne(&s, "${CONTROLLER}", m);
g_base->logic->event_loop()->PushCall([s] {
g_base->graphics->AddScreenMessage(s, Vector3f(1, 1, 1));
g_base->graphics->screenmessages->AddScreenMessage(
s, Vector3f(1, 1, 1));
});
g_base->logic->event_loop()->PushCall([] {
g_base->audio->PlaySound(
@ -369,8 +371,10 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
std::string s =
g_base->assets->GetResourceString("controllerReconnectedText");
Utils::StringReplaceOne(&s, "${CONTROLLER}", m);
g_base->logic->event_loop()->PushCall(
[s] { g_base->graphics->AddScreenMessage(s, Vector3f(1, 1, 1)); });
g_base->logic->event_loop()->PushCall([s] {
g_base->graphics->screenmessages->AddScreenMessage(s,
Vector3f(1, 1, 1));
});
g_base->logic->event_loop()->PushCall([] {
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kGunCock));
@ -416,8 +420,10 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
std::string s =
g_base->assets->GetResourceString("controllerConnectedText");
Utils::StringReplaceOne(&s, "${CONTROLLER}", m);
g_base->logic->event_loop()->PushCall(
[s] { g_base->graphics->AddScreenMessage(s, Vector3f(1, 1, 1)); });
g_base->logic->event_loop()->PushCall([s] {
g_base->graphics->screenmessages->AddScreenMessage(s,
Vector3f(1, 1, 1));
});
g_base->logic->event_loop()->PushCall([] {
g_base->audio->PlaySound(

View File

@ -99,8 +99,8 @@ void Logic::OnGraphicsReady() {
// Anyone dealing in display-time should be able to handle a wide
// variety of rates anyway. NOTE: This length is currently milliseconds.
headless_display_time_step_timer_ = event_loop()->NewTimer(
kHeadlessMinDisplayTimeStep / 1000, true,
NewLambdaRunnable([this] { StepDisplayTime_(); }));
kHeadlessMinDisplayTimeStep, true,
NewLambdaRunnable([this] { StepDisplayTime_(); }).Get());
} else {
// In gui mode, push an initial frame to the graphics server. From this
// point it will be self-sustaining, sending us a frame request each
@ -133,9 +133,10 @@ void Logic::CompleteAppBootstrapping_() {
// Set up our timers.
process_pending_work_timer_ = event_loop()->NewTimer(
0, true, NewLambdaRunnable([this] { ProcessPendingWork_(); }));
asset_prune_timer_ = event_loop()->NewTimer(
2345, true, NewLambdaRunnable([] { g_base->assets->Prune(); }));
0, true, NewLambdaRunnable([this] { ProcessPendingWork_(); }).Get());
// asset_prune_timer_ = event_loop()->NewTimer(
// 2345 * 1000, true, NewLambdaRunnable([] { g_base->assets->Prune();
// }).Get());
// Let our initial dummy app-mode know it has become active.
g_base->app_mode()->OnActivate();
@ -381,9 +382,7 @@ void Logic::OnAppModeChanged() {
"Resetting headless display step timer due to app-mode change.");
}
assert(headless_display_time_step_timer_);
// NOTE: This is currently milliseconds.
headless_display_time_step_timer_->SetLength(kHeadlessMinDisplayTimeStep
/ 1000);
headless_display_time_step_timer_->SetLength(kHeadlessMinDisplayTimeStep);
}
}
@ -439,8 +438,8 @@ void Logic::PostUpdateDisplayTimeForHeadlessMode_() {
Log(LogLevel::kDebug, buffer);
}
auto sleep_millisecs = headless_display_step_microsecs / 1000;
headless_display_time_step_timer_->SetLength(sleep_millisecs);
auto sleep_microsecs = headless_display_step_microsecs;
headless_display_time_step_timer_->SetLength(sleep_microsecs);
}
void Logic::UpdateDisplayTimeForFrameDraw_() {
@ -581,7 +580,7 @@ void Logic::UpdatePendingWorkTimer_() {
// If there's loading to do, keep at it rather vigorously.
if (have_pending_loads_) {
assert(process_pending_work_timer_);
process_pending_work_timer_->SetLength(1);
process_pending_work_timer_->SetLength(1 * 1000);
} else {
// Otherwise we've got nothing to do; go to sleep until something
// changes.
@ -635,8 +634,8 @@ void Logic::NotifyOfPendingAssetLoads() {
UpdatePendingWorkTimer_();
}
auto Logic::NewAppTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int {
auto Logic::NewAppTimer(microsecs_t length, bool repeat, Runnable* runnable)
-> int {
// App-Timers simply get injected into our loop and run alongside our own
// stuff.
assert(g_base->InLogicThread());
@ -649,7 +648,7 @@ void Logic::DeleteAppTimer(int timer_id) {
event_loop()->DeleteTimer(timer_id);
}
void Logic::SetAppTimerLength(int timer_id, millisecs_t length) {
void Logic::SetAppTimerLength(int timer_id, microsecs_t length) {
assert(g_base->InLogicThread());
Timer* t = event_loop()->GetTimer(timer_id);
if (t) {
@ -660,14 +659,14 @@ void Logic::SetAppTimerLength(int timer_id, millisecs_t length) {
}
}
auto Logic::NewDisplayTimer(microsecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int {
auto Logic::NewDisplayTimer(microsecs_t length, bool repeat, Runnable* runnable)
-> int {
// Display-Timers go into a timer-list that we exec explicitly when we
// step display-time.
assert(g_base->InLogicThread());
int offset = 0;
Timer* t = display_timers_->NewTimer(g_core->GetAppTimeMicrosecs(), length,
offset, repeat ? -1 : 0, runnable);
Timer* t = display_timers_->NewTimer(display_time_microsecs_, length, offset,
repeat ? -1 : 0, runnable);
return t->id();
}

View File

@ -89,13 +89,12 @@ class Logic {
void HandleInterruptSignal();
void HandleTerminateSignal();
auto NewAppTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int;
auto NewAppTimer(microsecs_t length, bool repeat, Runnable* runnable) -> int;
void DeleteAppTimer(int timer_id);
void SetAppTimerLength(int timer_id, millisecs_t length);
void SetAppTimerLength(int timer_id, microsecs_t length);
auto NewDisplayTimer(microsecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int;
auto NewDisplayTimer(microsecs_t length, bool repeat, Runnable* runnable)
-> int;
void DeleteDisplayTimer(int timer_id);
void SetDisplayTimerLength(int timer_id, microsecs_t length);
@ -149,8 +148,6 @@ class Logic {
bool shutdown_completed_ : 1 {};
bool graphics_ready_ : 1 {};
Timer* process_pending_work_timer_{};
Timer* asset_prune_timer_{};
Timer* debug_timer_{};
EventLoop* event_loop_{};
std::unique_ptr<TimerList> display_timers_;
};

View File

@ -4,11 +4,14 @@
#include "ballistica/base/platform/apple/base_platform_apple.h"
#if BA_XCODE_BUILD
#include <BallisticaKit-Swift.h>
#include "ballistica/base/platform/apple/apple_utils.h"
#include "ballistica/base/platform/apple/from_swift.h"
#endif
#if BA_XCODE_BUILD
#include "ballistica/base/platform/apple/apple_utils.h"
// This needs to be below ballistica headers since it relies on
// some types in them but does not include headers itself.
#include <BallisticaKit-Swift.h>
#endif
namespace ballistica::base {
@ -16,13 +19,14 @@ namespace ballistica::base {
BasePlatformApple::BasePlatformApple() {
// On iOS, keep the device from falling asleep in our app
#if BA_OSTYPE_IOS_TVOS
AppleUtils::DisableIdleTimer();
// AppleUtils::DisableIdleTimer();
#endif
}
void BasePlatformApple::DoPurchase(const std::string& item) {
#if BA_USE_STORE_KIT
AppleUtils::DoStoreKitPurchase(item);
BallisticaKit::StoreKitContext::purchase(item);
// AppleUtils::DoStoreKitPurchase(item);
#else
BasePlatform::DoPurchase(item);
#endif
@ -30,7 +34,8 @@ void BasePlatformApple::DoPurchase(const std::string& item) {
void BasePlatformApple::RestorePurchases() {
#if BA_USE_STORE_KIT
AppleUtils::DoStoreKitPurchaseRestore();
BallisticaKit::StoreKitContext::restorePurchases();
// AppleUtils::DoStoreKitPurchaseRestore();
#else
BasePlatform::RestorePurchases();
#endif
@ -38,8 +43,9 @@ void BasePlatformApple::RestorePurchases() {
void BasePlatformApple::PurchaseAck(const std::string& purchase,
const std::string& order_id) {
#if BA_XCODE_BUILD
AppleUtils::PurchaseAck(purchase, order_id);
#if BA_USE_STORE_KIT
BallisticaKit::StoreKitContext::purchaseAck(purchase, order_id);
// AppleUtils::PurchaseAck(purchase, order_id);
#else
BasePlatform::PurchaseAck(purchase, order_id);
#endif
@ -48,9 +54,9 @@ void BasePlatformApple::PurchaseAck(const std::string& purchase,
void BasePlatformApple::DoOpenURL(const std::string& url) {
#if BA_XCODE_BUILD
#if BA_OSTYPE_MACOS
BallisticaKit::CocoaFromCppOpenURL(url);
BallisticaKit::CocoaFromCpp::openURL(url);
#else
BallisticaKit::UIKitFromCppOpenURL(url);
BallisticaKit::UIKitFromCpp::openURL(url);
#endif // BA_OSTYPE_MACOS
#else
@ -59,6 +65,34 @@ void BasePlatformApple::DoOpenURL(const std::string& url) {
#endif // BA_XCODE_BUILD
}
void BasePlatformApple::LoginAdapterGetSignInToken(
const std::string& login_type, int attempt_id) {
#if BA_USE_GAME_CENTER
if (login_type == "game_center") {
BallisticaKit::GameCenterContext::getSignInToken(attempt_id);
} else {
Log(LogLevel::kError,
"Got unexpected get-sign-in-token login-type: " + login_type);
}
#else
BasePlatform::LoginAdapterGetSignInToken(login_type, attempt_id);
#endif
}
void BasePlatformApple::LoginAdapterBackEndActiveChange(
const std::string& login_type, bool active) {
#if BA_USE_GAME_CENTER
if (login_type == "game_center") {
BallisticaKit::GameCenterContext::backEndActiveChange(active);
} else {
Log(LogLevel::kError,
"Got unexpected back-end-active-change login-type: " + login_type);
}
#else
BasePlatform::LoginAdapterBackEndActiveChange(login_type, active);
#endif
}
} // namespace ballistica::base
#endif // BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS

View File

@ -16,6 +16,10 @@ class BasePlatformApple : public BasePlatform {
void PurchaseAck(const std::string& purchase,
const std::string& order_id) override;
void DoOpenURL(const std::string& url) override;
void LoginAdapterGetSignInToken(const std::string& login_type,
int attempt_id) override;
void LoginAdapterBackEndActiveChange(const std::string& login_type,
bool active) override;
};
} // namespace ballistica::base

View File

@ -7,7 +7,6 @@
#include "ballistica/base/base.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/platform/support/min_sdl_key_names.h"
#include "ballistica/base/python/base_python.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/core/core.h"
@ -16,73 +15,8 @@
#include "ballistica/shared/python/python.h"
#include "ballistica/shared/python/python_sys.h"
// ------------------------- PLATFORM SELECTION --------------------------------
// This ugly chunk of macros simply pulls in the correct platform class header
// for each platform and defines the actual class g_base->platform will be.
// Android ---------------------------------------------------------------------
#if BA_OSTYPE_ANDROID
#if BA_GOOGLE_BUILD
#include "ballistica/base/platform/android/google/base_plat_andr_google.h"
#define BA_PLATFORM_CLASS BasePlatformAndroidGoogle
#elif BA_AMAZON_BUILD
#include "ballistica/base/platform/android/amazon/base_plat_andr_amazon.h"
#define BA_PLATFORM_CLASS BasePlatformAndroidAmazon
#elif BA_CARDBOARD_BUILD
#include "ballistica/base/platform/android/cardboard/base_pl_an_cardboard.h"
#define BA_PLATFORM_CLASS BasePlatformAndroidCardboard
#else // Generic android.
#include "ballistica/base/platform/android/base_platform_android.h"
#define BA_PLATFORM_CLASS BasePlatformAndroid
#endif // (Android subplatform)
// Apple -----------------------------------------------------------------------
#elif BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS
#include "ballistica/base/platform/apple/base_platform_apple.h"
#define BA_PLATFORM_CLASS BasePlatformApple
// Windows ---------------------------------------------------------------------
#elif BA_OSTYPE_WINDOWS
#if BA_RIFT_BUILD
#include "ballistica/base/platform/windows/base_platform_windows_oculus.h"
#define BA_PLATFORM_CLASS BasePlatformWindowsOculus
#else // generic windows
#include "ballistica/base/platform/windows/base_platform_windows.h"
#define BA_PLATFORM_CLASS BasePlatformWindows
#endif // windows subtype
// Linux -----------------------------------------------------------------------
#elif BA_OSTYPE_LINUX
#include "ballistica/base/platform/linux/base_platform_linux.h"
#define BA_PLATFORM_CLASS BasePlatformLinux
#else
// Generic ---------------------------------------------------------------------
#define BA_PLATFORM_CLASS BasePlatform
#endif
// ----------------------- END PLATFORM SELECTION ------------------------------
#ifndef BA_PLATFORM_CLASS
#error no BA_PLATFORM_CLASS defined for this platform
#endif
namespace ballistica::base {
auto BasePlatform::Create() -> BasePlatform* {
auto platform = new BA_PLATFORM_CLASS();
platform->PostInit();
assert(platform->ran_base_post_init_);
return platform;
}
BasePlatform::BasePlatform() = default;
void BasePlatform::PostInit() {
@ -92,20 +26,6 @@ void BasePlatform::PostInit() {
BasePlatform::~BasePlatform() = default;
auto BasePlatform::GetKeyName(int keycode) -> std::string {
// On our actual SDL platforms we're trying to be *pure* sdl so
// call their function for this. Otherwise we call our own version
// of it which is basically the same thing (at least for now).
#if BA_MINSDL_BUILD
return MinSDL_GetKeyName(keycode);
#elif BA_SDL_BUILD
return SDL_GetKeyName(static_cast<SDL_Keycode>(keycode));
#else
Log(LogLevel::kWarn, "CorePlatform::GetKeyName not implemented here.");
return "?";
#endif
}
void BasePlatform::LoginAdapterGetSignInToken(const std::string& login_type,
int attempt_id) {
// Default implementation simply calls completion callback immediately.

View File

@ -15,8 +15,12 @@ namespace ballistica::base {
/// with a single platform (Windows, Mac, etc.).
class BasePlatform {
public:
/// Instantiate the CorePlatform subclass for the current build.
static auto Create() -> BasePlatform*;
BasePlatform();
/// Called after our singleton has been instantiated. Any construction
/// functionality requiring virtual functions resolving to their final
/// class versions can go here.
virtual void PostInit();
#pragma mark APP EVENTS / LIFECYCLE --------------------------------------------
@ -57,20 +61,18 @@ class BasePlatform {
/// Called when the app should set itself up to intercept ctrl-c presses.
virtual void SetupInterruptHandling();
#pragma mark INPUT DEVICES -----------------------------------------------------
// Return a name for a ballistica keycode.
virtual auto GetKeyName(int keycode) -> std::string;
#pragma mark ACCOUNTS ----------------------------------------------------------
/// Called when a Python LoginAdapter is requesting an explicit sign-in.
/// See the LoginAdapter class in Python for usage details.
virtual void LoginAdapterGetSignInToken(const std::string& login_type,
int attempt_id);
/// Called when a Python LoginAdapter is informing us that a back-end is
/// active/inactive.
/// active/inactive. See the LoginAdapter class in Python for usage
/// details.
virtual void LoginAdapterBackEndActiveChange(const std::string& login_type,
bool active);
#pragma mark MISC --------------------------------------------------------------
/// Do we define a platform-specific string editor? This is something like
@ -94,6 +96,8 @@ class BasePlatform {
/// Must be called in the logic thread.
void StringEditorCancel();
auto ran_base_post_init() const { return ran_base_post_init_; }
protected:
/// Pop up a text edit dialog.
virtual void DoInvokeStringEditor(const std::string& title,
@ -106,17 +110,11 @@ class BasePlatform {
/// Make a purchase.
virtual void DoPurchase(const std::string& item);
BasePlatform();
virtual ~BasePlatform();
private:
/// Called after our singleton has been instantiated. Any construction
/// functionality requiring virtual functions resolving to their final
/// class versions can go here.
virtual void PostInit();
bool ran_base_post_init_ : 1 {};
PythonRef string_edit_adapter_{};
bool ran_base_post_init_{};
std::string public_device_uuid_;
};

View File

@ -367,6 +367,22 @@ auto MinSDL_GetKeyName(int keycode) -> std::string {
static char name[8];
char* end;
// Handle a few specially per platform.
if (g_buildconfig.ostype_macos()) {
switch (key) {
case SDLK_LGUI:
return "Left Command";
case SDLK_RGUI:
return "Right Command";
case SDLK_LALT:
return "Left Option";
case SDLK_RALT:
return "Right Option";
default:
break;
}
}
if (key & SDLK_SCANCODE_MASK) {
return GetScancodeName((SDL_Scancode)(key & ~SDLK_SCANCODE_MASK));
}

View File

@ -48,6 +48,7 @@ class BasePython {
kTemporarilyUnavailableMessageCall,
kInProgressMessageCall,
kErrorMessageCall,
kSuccessMessageCall,
kPurchaseNotValidErrorCall,
kPurchaseAlreadyInProgressErrorCall,
kVROrientationResetCBMessageCall,
@ -111,6 +112,7 @@ class BasePython {
kGetDevConsoleTabNamesCall,
kAppDevConsoleDoRefreshTabCall,
kUnsupportedControllerMessageCall,
kGetV2AccountIdCall,
kLast // Sentinel; must be at end.
};

View File

@ -94,8 +94,8 @@ auto PythonClassAppTimer::tp_new(PyTypeObject* type, PyObject* args,
auto runnable(Object::New<Runnable, PythonContextCallRunnable>(call_obj));
self->timer_id_ = g_base->logic->NewAppTimer(
static_cast<millisecs_t>(length * 1000.0), repeat,
Object::New<Runnable, PythonContextCallRunnable>(call_obj));
static_cast<microsecs_t>(length * 1000000.0), repeat,
Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
self->have_timer_ = true;

View File

@ -104,7 +104,7 @@ auto PythonClassDisplayTimer::tp_new(PyTypeObject* type, PyObject* args,
}
self->timer_id_ = g_base->logic->NewDisplayTimer(
static_cast<microsecs_t>(length * 1000000.0), repeat,
Object::New<Runnable, PythonContextCallRunnable>(call_obj));
Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
self->have_timer_ = true;

View File

@ -9,7 +9,6 @@
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/python/base_python.h"
#include "ballistica/base/python/support/python_context_call_runnable.h"
#include "ballistica/base/support/stress_test.h"
#include "ballistica/base/ui/dev_console.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/shared/foundation/event_loop.h"
@ -365,8 +364,8 @@ static auto PyAppTimer(PyObject* self, PyObject* args, PyObject* keywds)
throw Exception("Timer length cannot be < 0.", PyExcType::kValue);
}
g_base->logic->NewAppTimer(
static_cast<millisecs_t>(length * 1000.0), false,
Object::New<Runnable, PythonContextCallRunnable>(call_obj));
static_cast<microsecs_t>(length * 1000000.0), false,
Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
@ -459,7 +458,7 @@ static auto PyDisplayTimer(PyObject* self, PyObject* args, PyObject* keywds)
}
g_base->logic->NewDisplayTimer(
static_cast<microsecs_t>(length * 1000000.0), false,
Object::New<Runnable, PythonContextCallRunnable>(call_obj));
Object::New<Runnable, PythonContextCallRunnable>(call_obj).Get());
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
@ -602,7 +601,7 @@ static auto PyCommitConfig(PyObject* self, PyObject* args, PyObject* keywds)
}
fclose(f_out);
// Now backup any existing config to .prev.
// Now move any existing config to .prev.
if (g_core->platform->FilePathExists(path)) {
// On windows, rename doesn't overwrite existing files.. need to kill
// the old explicitly.
@ -784,32 +783,6 @@ static PyMethodDef PyEnvDef = {
"This info is now exposed through babase.App; refer to those docs for\n"
"info on specific elements."};
// -------------------------- set_stress_testing -------------------------------
static auto PySetStressTesting(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
int enable;
int player_count;
if (!PyArg_ParseTuple(args, "pi", &enable, &player_count)) {
return nullptr;
}
g_base->app_adapter->PushMainThreadCall([enable, player_count] {
g_base->stress_test()->Set(enable, player_count);
});
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PySetStressTestingDef = {
"set_stress_testing", // name
PySetStressTesting, // method
METH_VARARGS, // flags
"set_stress_testing(testing: bool, player_count: int) -> None\n"
"\n"
"(internal)",
};
// -------------------------------- emit_log -----------------------------------
static auto PyEmitLog(PyObject* self, PyObject* args, PyObject* keywds)
@ -1115,25 +1088,6 @@ static PyMethodDef PyMacMusicAppSetVolumeDef = {
"(internal)",
};
// ------------------------ mac_music_app_get_library --------------------------
static auto PyMacMusicAppGetLibrarySource(PyObject* self, PyObject* args,
PyObject* keywds) -> PyObject* {
BA_PYTHON_TRY;
g_core->platform->MacMusicAppGetLibrarySource();
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyMacMusicAppGetLibrarySourceDef = {
"mac_music_app_get_library_source", // name
(PyCFunction)PyMacMusicAppGetLibrarySource, // method
METH_VARARGS | METH_KEYWORDS, // flags
"mac_music_app_get_library_source() -> None\n"
"\n"
"(internal)"};
// --------------------------- mac_music_app_stop ------------------------------
static auto PyMacMusicAppStop(PyObject* self, PyObject* args, PyObject* keywds)
@ -1645,6 +1599,52 @@ static PyMethodDef PyAudioShutdownIsCompleteDef = {
"(internal)\n",
};
// ----------------------- graphics_shutdown_begin -----------------------------
static auto PyGraphicsShutdownBegin(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
g_base->app_adapter->PushGraphicsContextCall(
[] { g_base->graphics_server->Shutdown(); });
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyGraphicsShutdownBeginDef = {
"graphics_shutdown_begin", // name
(PyCFunction)PyGraphicsShutdownBegin, // method
METH_NOARGS, // flags
"graphics_shutdown_begin() -> None\n"
"\n"
"(internal)\n",
};
// -------------------- graphics_shutdown_is_complete --------------------------
static auto PyGraphicsShutdownIsComplete(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
if (g_base->graphics_server->shutdown_completed()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyGraphicsShutdownIsCompleteDef = {
"graphics_shutdown_is_complete", // name
(PyCFunction)PyGraphicsShutdownIsComplete, // method
METH_NOARGS, // flags
"graphics_shutdown_is_complete() -> bool\n"
"\n"
"(internal)\n",
};
// -----------------------------------------------------------------------------
auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
@ -1656,7 +1656,6 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
PyCanDisplayFullUnicodeDef,
PyEmitLogDef,
PyV1CloudLogDef,
PySetStressTestingDef,
PyEnvDef,
PyPreEnvDef,
PyCommitConfigDef,
@ -1677,7 +1676,6 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
PyMacMusicAppInitDef,
PyMacMusicAppGetVolumeDef,
PyMacMusicAppSetVolumeDef,
PyMacMusicAppGetLibrarySourceDef,
PyMacMusicAppStopDef,
PyMacMusicAppPlayPlaylistDef,
PyMacMusicAppGetPlaylistsDef,
@ -1702,6 +1700,8 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
PyDevConsoleInputAdapterFinishDef,
PyAudioShutdownBeginDef,
PyAudioShutdownIsCompleteDef,
PyGraphicsShutdownBeginDef,
PyGraphicsShutdownIsCompleteDef,
};
}

View File

@ -6,6 +6,7 @@
#include "ballistica/base/assets/assets.h"
#include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/graphics/support/camera.h"
#include "ballistica/base/graphics/support/screen_messages.h"
#include "ballistica/base/graphics/text/text_graphics.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/python/base_python.h"
@ -74,7 +75,7 @@ static auto PyScreenMessage(PyObject* self, PyObject* args, PyObject* keywds)
}
// This version simply displays it locally.
g_base->graphics->AddScreenMessage(message_str, color);
g_base->graphics->screenmessages->AddScreenMessage(message_str, color);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
@ -536,6 +537,7 @@ static auto PyFadeScreen(PyObject* self, PyObject* args, PyObject* keywds)
&endcall)) {
return nullptr;
}
BA_PRECONDITION(g_base->InLogicThread());
g_base->graphics->FadeScreen(static_cast<bool>(fade),
static_cast<int>(1000.0f * time), endcall);
Py_RETURN_NONE;

View File

@ -5,6 +5,7 @@
#include <list>
#include <unordered_map>
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/assets/sound_asset.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/platform/base_platform.h"
@ -128,7 +129,7 @@ static PyMethodDef PyHasTouchScreenDef = {
static auto PyClipboardIsSupported(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
if (g_core->platform->ClipboardIsSupported()) {
if (g_base->app_adapter->ClipboardIsSupported()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
@ -154,7 +155,7 @@ static PyMethodDef PyClipboardIsSupportedDef = {
static auto PyClipboardHasText(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
if (g_core->platform->ClipboardHasText()) {
if (g_base->app_adapter->ClipboardHasText()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
@ -187,7 +188,7 @@ static auto PyClipboardSetText(PyObject* self, PyObject* args, PyObject* keywds)
const_cast<char**>(kwlist), &value)) {
return nullptr;
}
g_core->platform->ClipboardSetText(value);
g_base->app_adapter->ClipboardSetText(value);
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
@ -211,7 +212,7 @@ static PyMethodDef PyClipboardSetTextDef = {
static auto PyClipboardGetText(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
return PyUnicode_FromString(g_core->platform->ClipboardGetText().c_str());
return PyUnicode_FromString(g_base->app_adapter->ClipboardGetText().c_str());
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
@ -1681,6 +1682,87 @@ static PyMethodDef PyAssetLoadsAllowedDef = {
"(internal)",
};
// -------------------- using_google_play_game_services ------------------------
static auto PyUsingGooglePlayGameServices(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
if (g_buildconfig.use_google_play_game_services()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyUsingGooglePlayGameServicesDef = {
"using_google_play_game_services", // name
(PyCFunction)PyUsingGooglePlayGameServices, // method
METH_NOARGS, // flags
"using_google_play_game_services() -> bool\n"
"\n"
"(internal)",
};
// ---------------------------- using_game_center ------------------------------
static auto PyUsingGameCenter(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
if (g_buildconfig.use_game_center()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyUsingGameCenterDef = {
"using_game_center", // name
(PyCFunction)PyUsingGameCenter, // method
METH_NOARGS, // flags
"using_game_center() -> bool\n"
"\n"
"(internal)",
};
// --------------------- native_review_request_supported -----------------------
static auto PyNativeReviewRequestSupported(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
if (g_base->app_adapter->NativeReviewRequestSupported()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyNativeReviewRequestSupportedDef = {
"native_review_request_supported", // name
(PyCFunction)PyNativeReviewRequestSupported, // method
METH_NOARGS, // flags
"native_review_request_supported() -> bool\n"
"\n"
"(internal)",
};
// -------------------------- native_review_request ----------------------------
static auto PyNativeReviewRequest(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
g_base->app_adapter->NativeReviewRequest();
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyNativeReviewRequestDef = {
"native_review_request", // name
(PyCFunction)PyNativeReviewRequest, // method
METH_NOARGS, // flags
"native_review_request() -> None\n"
"\n"
"(internal)",
};
// -----------------------------------------------------------------------------
auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
@ -1744,6 +1826,10 @@ auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
PyDevConsoleBaseScaleDef,
PyDevConsoleRequestRefreshDef,
PyAssetLoadsAllowedDef,
PyUsingGooglePlayGameServicesDef,
PyUsingGameCenterDef,
PyNativeReviewRequestSupportedDef,
PyNativeReviewRequestDef,
};
}

View File

@ -13,16 +13,24 @@ namespace ballistica::base {
class AppTimer : public Object {
public:
AppTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) {
AppTimer(seconds_t length, bool repeat, Runnable* runnable) {
assert(g_base->InLogicThread());
timer_id_ = base::g_base->logic->NewAppTimer(length, repeat, runnable);
timer_id_ = base::g_base->logic->NewAppTimer(
static_cast<microsecs_t>(length * 1000000.0), repeat, runnable);
}
void SetLength(millisecs_t length) {
assert(g_base->InLogicThread());
base::g_base->logic->SetAppTimerLength(timer_id_, length);
template <typename F>
static auto New(seconds_t length, bool repeat, const F& lambda) {
return Object::New<AppTimer>(length, repeat,
NewLambdaRunnable<F>(lambda).Get());
}
void SetLength(seconds_t length) {
assert(g_base->InLogicThread());
base::g_base->logic->SetAppTimerLength(
timer_id_, static_cast<microsecs_t>(length * 1000000.0));
}
~AppTimer() override {
assert(g_base->InLogicThread());
base::g_base->logic->DeleteAppTimer(timer_id_);
@ -32,13 +40,6 @@ class AppTimer : public Object {
int timer_id_;
};
/// Create a AppTimer from a raw lambda.
template <typename F>
auto NewAppTimer(millisecs_t length, bool repeat, const F& lambda)
-> Object::Ref<AppTimer> {
return Object::New<AppTimer>(length, repeat, NewLambdaRunnable<F>(lambda));
}
} // namespace ballistica::base
#endif // BALLISTICA_BASE_SUPPORT_APP_TIMER_H_

View File

@ -0,0 +1,120 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/base/support/base_build_switches.h"
#if BA_OSTYPE_ANDROID
#include "ballistica/base/app_adapter/app_adapter_android.h"
#endif
#include "ballistica/base/app_adapter/app_adapter_apple.h"
#include "ballistica/base/app_adapter/app_adapter_headless.h"
#include "ballistica/base/app_adapter/app_adapter_sdl.h"
#include "ballistica/base/app_adapter/app_adapter_vr.h"
#include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/graphics/graphics_vr.h"
// ------------------------- PLATFORM SELECTION --------------------------------
// This ugly chunk of macros simply pulls in the correct platform class header
// for each platform and defines the actual class g_base->platform will be.
// Android ---------------------------------------------------------------------
#if BA_OSTYPE_ANDROID
#if BA_GOOGLE_BUILD
#include "ballistica/base/platform/android/google/base_plat_andr_google.h"
#define BA_PLATFORM_CLASS BasePlatformAndroidGoogle
#elif BA_AMAZON_BUILD
#include "ballistica/base/platform/android/amazon/base_plat_andr_amazon.h"
#define BA_PLATFORM_CLASS BasePlatformAndroidAmazon
#elif BA_CARDBOARD_BUILD
#include "ballistica/base/platform/android/cardboard/base_pl_an_cardboard.h"
#define BA_PLATFORM_CLASS BasePlatformAndroidCardboard
#else // Generic android.
#include "ballistica/base/platform/android/base_platform_android.h"
#define BA_PLATFORM_CLASS BasePlatformAndroid
#endif // (Android subplatform)
// Apple -----------------------------------------------------------------------
#elif BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS
#include "ballistica/base/platform/apple/base_platform_apple.h"
#define BA_PLATFORM_CLASS BasePlatformApple
// Windows ---------------------------------------------------------------------
#elif BA_OSTYPE_WINDOWS
#if BA_RIFT_BUILD
#include "ballistica/base/platform/windows/base_platform_windows_oculus.h"
#define BA_PLATFORM_CLASS BasePlatformWindowsOculus
#else // generic windows
#include "ballistica/base/platform/windows/base_platform_windows.h"
#define BA_PLATFORM_CLASS BasePlatformWindows
#endif // windows subtype
// Linux -----------------------------------------------------------------------
#elif BA_OSTYPE_LINUX
#include "ballistica/base/platform/linux/base_platform_linux.h"
#define BA_PLATFORM_CLASS BasePlatformLinux
#else
// Generic ---------------------------------------------------------------------
#define BA_PLATFORM_CLASS BasePlatform
#endif
// ----------------------- END PLATFORM SELECTION ------------------------------
#ifndef BA_PLATFORM_CLASS
#error no BA_PLATFORM_CLASS defined for this platform
#endif
namespace ballistica::base {
auto BaseBuildSwitches::CreatePlatform() -> BasePlatform* {
auto platform = new BA_PLATFORM_CLASS();
platform->PostInit();
assert(platform->ran_base_post_init());
return platform;
}
auto BaseBuildSwitches::CreateGraphics() -> Graphics* {
#if BA_VR_BUILD
return new GraphicsVR();
#else
return new Graphics();
#endif
}
auto BaseBuildSwitches::CreateAppAdapter() -> AppAdapter* {
assert(g_core);
AppAdapter* app_adapter{};
#if BA_HEADLESS_BUILD
app_adapter = new AppAdapterHeadless();
#elif BA_OSTYPE_ANDROID
app_adapter = new AppAdapterAndroid();
#elif BA_XCODE_BUILD
app_adapter = new AppAdapterApple();
#elif BA_RIFT_BUILD
// Rift build can spin up in either VR or regular mode.
if (g_core->vr_mode) {
app_adapter = new AppAdapterVR();
} else {
app_adapter = new AppAdapterSDL();
}
#elif BA_CARDBOARD_BUILD
app_adapter = new AppAdapterVR();
#elif BA_SDL_BUILD
app_adapter = new AppAdapterSDL();
#else
#error No app adapter defined for this build.
#endif
assert(app_adapter);
return app_adapter;
}
} // namespace ballistica::base

View File

@ -0,0 +1,20 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_BASE_SUPPORT_BASE_BUILD_SWITCHES_H_
#define BALLISTICA_BASE_SUPPORT_BASE_BUILD_SWITCHES_H_
#include "ballistica/base/base.h"
namespace ballistica::base {
/// Constructs various app components based on the current build config.
class BaseBuildSwitches {
public:
static auto CreateGraphics() -> Graphics*;
static auto CreatePlatform() -> BasePlatform*;
static auto CreateAppAdapter() -> AppAdapter*;
};
} // namespace ballistica::base
#endif // BALLISTICA_BASE_SUPPORT_BASE_BUILD_SWITCHES_H_

View File

@ -0,0 +1,45 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_BASE_SUPPORT_DISPLAY_TIMER_H_
#define BALLISTICA_BASE_SUPPORT_DISPLAY_TIMER_H_
#include "ballistica/base/base.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/shared/ballistica.h"
#include "ballistica/shared/foundation/object.h"
#include "ballistica/shared/generic/lambda_runnable.h"
namespace ballistica::base {
class DisplayTimer : public Object {
public:
DisplayTimer(seconds_t length, bool repeat, Runnable* runnable) {
assert(g_base->InLogicThread());
timer_id_ = base::g_base->logic->NewDisplayTimer(
static_cast<microsecs_t>(length * 1000000.0), repeat, runnable);
}
template <typename F>
static auto New(seconds_t length, bool repeat, const F& lambda) {
return Object::New<DisplayTimer>(length, repeat,
NewLambdaRunnable<F>(lambda).Get());
}
void SetLength(seconds_t length) {
assert(g_base->InLogicThread());
base::g_base->logic->SetDisplayTimerLength(
timer_id_, static_cast<microsecs_t>(length * 1000000.0));
}
~DisplayTimer() override {
assert(g_base->InLogicThread());
base::g_base->logic->DeleteDisplayTimer(timer_id_);
}
private:
int timer_id_;
};
} // namespace ballistica::base
#endif // BALLISTICA_BASE_SUPPORT_DISPLAY_TIMER_H_

View File

@ -33,6 +33,7 @@ class PlusSoftInterface {
virtual auto PutLog(bool fatal) -> bool = 0;
virtual void AAT() = 0;
virtual void AATE() = 0;
virtual auto GAHU() -> std::optional<std::string> = 0;
virtual void V1LoginDidChange() = 0;
virtual void SetAdCompletionCall(PyObject* obj,
bool pass_actually_showed) = 0;

View File

@ -0,0 +1,55 @@
// Released under the MIT License. See LICENSE for details.
#include "ballistica/base/support/repeater.h"
#include "ballistica/base/support/display_timer.h"
#include "ballistica/shared/foundation/event_loop.h"
namespace ballistica::base {
Repeater::Repeater(seconds_t initial_delay, seconds_t repeat_delay,
Runnable* runnable)
: initial_delay_(initial_delay),
repeat_delay_(repeat_delay),
runnable_(runnable) {
assert(g_base->InLogicThread());
assert(initial_delay >= 0.0);
assert(repeat_delay >= 0.0);
// Let's go ahead and run our initial time in a deferred call;
// this is generally safer than running in the middle of whatever UI
// code set this up.
auto weak_this = Object::WeakRef<Repeater>(this);
g_base->logic->event_loop()->PushCall([weak_this] {
if (weak_this.Exists()) {
weak_this->runnable_->RunAndLogErrors();
if (!weak_this.Exists()) {
// Runnable might have killed us.
return;
}
// Kick off our initial delay timer (generally the longer one).
weak_this->timer_ =
DisplayTimer::New(weak_this->initial_delay_, false, [weak_this] {
// Timer should not have fired if we died.
assert(weak_this.Exists());
weak_this->runnable_->RunAndLogErrors();
if (!weak_this.Exists()) {
// Runnable might have killed us.
return;
}
// Kick off our repeat timer (generally the short one).
weak_this->timer_ =
DisplayTimer::New(weak_this->repeat_delay_, true, [weak_this] {
// Timer should not have fired if we died.
assert(weak_this.Exists());
weak_this->runnable_->RunAndLogErrors();
// Doesn't matter if Runnable killed us since we don't
// touch anything for the remainder of this function.
});
});
}
});
}
Repeater::~Repeater() { assert(g_base->InLogicThread()); }
} // namespace ballistica::base

View File

@ -0,0 +1,36 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_BASE_SUPPORT_REPEATER_H_
#define BALLISTICA_BASE_SUPPORT_REPEATER_H_
#include "ballistica/base/base.h"
#include "ballistica/shared/foundation/object.h"
#include "ballistica/shared/generic/lambda_runnable.h"
namespace ballistica::base {
/// Runs some code immediately and then repeatedly after a delay. Useful for
/// jobs such as selecting ui elements while keys or buttons are held.
/// Uses display-time so emphasizes visual smoothness over accuracy.
class Repeater : public Object {
public:
Repeater(seconds_t initial_delay, seconds_t repeat_delay, Runnable* runnable);
~Repeater();
template <typename F>
static auto New(seconds_t initial_delay, seconds_t repeat_delay,
const F& lambda) {
return Object::New<Repeater>(initial_delay, repeat_delay,
NewLambdaRunnable<F>(lambda).Get());
}
private:
seconds_t initial_delay_;
seconds_t repeat_delay_;
Object::Ref<DisplayTimer> timer_;
Object::Ref<Runnable> runnable_;
};
} // namespace ballistica::base
#endif // BALLISTICA_BASE_SUPPORT_REPEATER_H_

View File

@ -1,25 +0,0 @@
// Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_BASE_SUPPORT_STRESS_TEST_H_
#define BALLISTICA_BASE_SUPPORT_STRESS_TEST_H_
#include "ballistica/shared/ballistica.h"
namespace ballistica::base {
class StressTest {
public:
void Set(bool enable, int player_count);
void Update();
private:
FILE* stress_test_stats_file_{};
millisecs_t last_stress_test_update_time_{};
bool stress_testing_{};
int stress_test_player_count_{8};
int last_total_frames_rendered_{};
};
} // namespace ballistica::base
#endif // BALLISTICA_BASE_SUPPORT_STRESS_TEST_H_

Some files were not shown because too many files have changed in this diff Show More