diff --git a/.efrocachemap b/.efrocachemap index da87dc82..17cfbba3 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -1416,10 +1416,10 @@ "build/assets/ba_data/textures/crossOutMask.pvr": "94110cc4e3e47f81b68f548951a33c2b", "build/assets/ba_data/textures/crossOutMask_preview.png": "d5df4d494cfbf700e3c8726b3693716c", "build/assets/ba_data/textures/crossOut_preview.png": "a0628f1e6b7e9f7d3b73d1c835ec9286", - "build/assets/ba_data/textures/cursor.dds": "575b05e3adc74adf5a5d4b482a54adc9", - "build/assets/ba_data/textures/cursor.ktx": "56ef6481222c23cbc1ab0fe825f19b03", - "build/assets/ba_data/textures/cursor.pvr": "344b8856a315af23f495ebd283ee54fa", - "build/assets/ba_data/textures/cursor_preview.png": "0f6820abfe6b79b4133971ace8f3bc42", + "build/assets/ba_data/textures/cursor.dds": "4655d0746ba75bcd5f44f47dde9e9fec", + "build/assets/ba_data/textures/cursor.ktx": "7835afb0579c2ca3a6477314121f49a5", + "build/assets/ba_data/textures/cursor.pvr": "18803c269b9b544d6c0606d7b9fb2d85", + "build/assets/ba_data/textures/cursor_preview.png": "d7189625af474f06f1c953dd41e701a0", "build/assets/ba_data/textures/cuteSpaz.dds": "5876162f89e558a2220935a1d63493c3", "build/assets/ba_data/textures/cuteSpaz.ktx": "4a3bc3c1739991298d21a66256289d57", "build/assets/ba_data/textures/cuteSpaz.pvr": "a236803464dc49b61b63a5e83d305c4c", @@ -4056,54 +4056,54 @@ "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": "6d1f9c2c53c02a35d87bb0aea62f7408", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "cfbf3e80472077cbccc98b681d24e7cd", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "7af76def8c480e88ddd6257ab4d0dfff", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "6c3372ab5283cbd91362c23a32629ee5", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "80f3683bc192c94a6d1a1dc2137f0844", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "9fb5cc47cfd4bf4c717acf3877dde233", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "095a758aacb5940b793d82dddc142d34", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "f312f5066c8bc883a24b0e3a86884bb3", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "b9f8a78b14d439e8ace4c70e18ae9e19", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "5a25d4de9c3124822538602fb9273280", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "7ea84daf12222b10517bf71870b82b53", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "28dba23a69dc1159cf340f899f78f4d9", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "3b653a753ac0cb919431528908c9cccc", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "9df409c73fb121b4d5fcd0fde6d4e42f", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "be64f13a639454d8b82311dcfd6815e0", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f713443831ee2d9f912ca3c5e10201d8", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "6135e6256411bf24e44834d3445f94af", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "e782b975adada7282b5aaa6b1b1b0e1d", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "142aa10ee8c2747b011ef18062d5de48", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "01d7543c88bf94b9478b261003c5521c", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "dd14e0abacf5a27d9823b0a41127e3e3", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "bd994ca8a1896ada5c582be155db5c36", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "dd14e0abacf5a27d9823b0a41127e3e3", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "bd994ca8a1896ada5c582be155db5c36", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "d855693f8342c4080ce0f452784e5cf9", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "eeaf4e383752fdcdaaae1cb863208870", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "d855693f8342c4080ce0f452784e5cf9", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "eeaf4e383752fdcdaaae1cb863208870", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "ba391e47cc87b609ea794cdf9e693163", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "84e913835ae280a59045bed189c920b0", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "ba391e47cc87b609ea794cdf9e693163", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "84e913835ae280a59045bed189c920b0", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "8f147ca53e6e261becb37d7ff5490b59", - "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "7d386ab4fc78cc7598188df82bf4f04a", - "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "5627e6b08b61024650420b849d41721b", - "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "7d386ab4fc78cc7598188df82bf4f04a", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "cc3c3f837962636cbcd542b9b54946f2", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "b6a9ccfc44c215d7f31e985bbc8eab06", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "056a71331f28fbe6a07ae4086e3e8391", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "ae9f5d7069310cb18f7dfc90ad207203", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "b014ca1f2fec594cb149f11d783ee165", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "2085834b3d1523f6e29cd89011d4c062", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "aa9fadf3a2410df7b5aca9c88194cfcc", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "d6ccc0796e64abab851caac47ce54a86", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "f0961021482a27c4904f33cf85ab86bb", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "b04eefee14228822208b6143048867de", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "83bde2fb0374153d753f917d6ab693d9", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "6765c40945fdecd8fd0eae796c2a1075", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "839e1cecbdec8364aea5b1c31fe1cf7b", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "2b3c91667b63f9fa6ecf807e021367e2", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "b8486cc3705974cf880a0c7bbd210902", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "a04084a19bbb6ef8111113ff7c003198", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "aef8e462af8e1d0439bf72ec84778611", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "f85863b0ea0145e7a51dd3f85d37663f", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0dbc09374baeabbf20cf0a052be94b8b", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "67545befcd59c6764d2868c1aae24776", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "7b1a4d61fd25efb514dbc005c20369b5", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "5c755b14c9ec4938504cbffcd0369a25", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "b8229bd1716bd4787d1efd58f476c21f", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "146eb16386394bfc34baa40b31007c5d", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "d08c8a35cc951adb8c78f6b5ed61a41e", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "ecd356d753266666d36b67b018ed2b6e", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "2c9ad739ec8a2b0f333ef7d0f38cd7b0", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e9c2a5c54ecc34cf35a8db42367ddb3d", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "58946f3534363d88f713c54d3d643d6d", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "be356d05ecccd68043258d87b1892805", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "58946f3534363d88f713c54d3d643d6d", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "be356d05ecccd68043258d87b1892805", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "bf7d793d62416db7273590a796001cb6", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "b309e0cc3ec04024712c4ca938efdb92", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "bf7d793d62416db7273590a796001cb6", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "b309e0cc3ec04024712c4ca938efdb92", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "a86b09c31abf0b5ec934ef28c8bd9fa3", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "87be7a2f6e83c495f99024bb68660e17", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "a86b09c31abf0b5ec934ef28c8bd9fa3", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "87be7a2f6e83c495f99024bb68660e17", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "9fb5d3cb36dd53bd18c7ca831e7c73ee", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "88332859e6e9ee70848f5252e5ee6ce0", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "55b6db8700acfc573cc3db31c6b210f7", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "88332859e6e9ee70848f5252e5ee6ce0", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "cf40ba3bce2391e82978b08785405a5e", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "a8a74156e04932a2a5cc6d2d4b202acf", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a135f9210a1c3be6b5d5d8228c8f6184", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "e412e20e4a0ac33b9f83c7750cde7109", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "cfcae11dab1c6752f821f0816706fa47", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "16daa37287a6d9d3404461da8565aadb", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "b3faf8b8925145f121b09e67d6114fb8", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "ed7ec02978df94f92168c5990cb6c78c", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", - "src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318", - "src/ballistica/base/mgen/pyembed/binding_base.inc": "9f71f171464dc004dbaab87e9bb4b03b", - "src/ballistica/base/mgen/pyembed/binding_base_app.inc": "a521bc86a7e98e56fec14cea029996f8", + "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", + "src/ballistica/base/mgen/pyembed/binding_base.inc": "ba8ce3ca3858b4c2d20db68f99b788b2", + "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", "src/ballistica/core/mgen/pyembed/env.inc": "8be46e5818f360d10b7b0224a9e91d07", diff --git a/.gitignore b/.gitignore index 07de7ab4..1c856436 100644 --- a/.gitignore +++ b/.gitignore @@ -120,6 +120,7 @@ xcuserdata/ /ballisticakit-android/BallisticaKit/src/main/res/mipmap-*/ic_launcher*.png /ballisticakit-android/BallisticaKit/src/cardboard/res/mipmap-*/ic_launcher*.png BallisticaKit.ico +/ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/Cursor macOS.appiconset/cursor_*.png /ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/AppIcon iOS.appiconset/icon_*.png /ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/AppIcon macOS.appiconset/icon_*.png /ballisticakit-xcode/BallisticaKit Shared/Assets.xcassets/tvOS App Icon & Top Shelf Image.brandassets/App Icon - App Store.imagestack/Layer*.imagestacklayer/Content.imageset/*.png diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index d1931465..032a8ebd 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -3192,6 +3192,8 @@ unstripped unstrl unsubscriptable + unsuspend + unsuspending untracked unwritable upcase diff --git a/CHANGELOG.md b/CHANGELOG.md index 70da1fd7..24b6c70d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.28 (build 21422, api 8, 2023-10-05) +### 1.7.28 (build 21443, api 8, 2023-10-11) - 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 @@ -104,6 +104,32 @@ - Created a custom icon for BallisticaKit (previously it was just the BombSquad icon with an ugly 'C' on it). BombSquad itself will still have the BombSquad icon. +- Changed `AppState.NOT_RUNNING` to `AppState.NOT_STARTED` since not-running + could be confused with a state such as paused. +- Changed the general app-state terms 'pause' and 'resume' to 'suspend' and + 'unsuspend'. (note this has nothing to do with pausing in the game which is + still called pausing). The suspend state is used by mobile versions when + backgrounded and basically stops all activity in the app. I may later add + another state called 'paused' for when the app is still running but there is + an OS dialog or ad or something in front of it. Though perhaps another term + would be better to avoid confusion with the act of pausing in the game + ('inactive' maybe?). +- Fixed an issue that could cause a few seconds delay when shutting down if + internet access is unavailable. +- Generalized the UI system to accept a delegate object, of which UIV1 is now + one. In the future this will allow plugging in UIV2 instead or other UI + systems. +- Headless builds now plug in *no* ui delegate instead of UIV1, so one must + avoid calling UI code from servers now. This should reduce server resource + usage a bit. Please holler if this causes non-trivial problems. In general, + code that brings up UI from gameplay contexts should check the value of + `ba.app.env.headless` and avoid doing so when that is True. +- Cleaned up quit behavior a bit more. The `babase.quit()` call now takes a + single `babase.QuitType` enum instead of the multiple bool options it took + before. It also takes a `confirm` bool arg which allows it to be used to bring + up a confirm dialog. +- Clicking on a window close button to quit no longer brings up a confirm dialog + and instead quits immediately (though with a proper graceful shutdown). ### 1.7.27 (build 21282, api 8, 2023-08-30) diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml index d97854ef..424a4df3 100644 --- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml @@ -1876,6 +1876,8 @@ unpremultiply unsignaled unstuff + unsuspend + unsuspending unsynchronized unwritable uppercased diff --git a/ballisticakit-cmake/CMakeLists.txt b/ballisticakit-cmake/CMakeLists.txt index 7aa1ccc3..b9050756 100644 --- a/ballisticakit-cmake/CMakeLists.txt +++ b/ballisticakit-cmake/CMakeLists.txt @@ -442,11 +442,11 @@ set(BALLISTICA_SOURCES ${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/support/ui_v1_soft.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 ${BA_SRC_ROOT}/ballistica/base/ui/ui.h + ${BA_SRC_ROOT}/ballistica/base/ui/ui_delegate.h ${BA_SRC_ROOT}/ballistica/base/ui/widget_message.h ${BA_SRC_ROOT}/ballistica/classic/classic.cc ${BA_SRC_ROOT}/ballistica/classic/classic.h diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj index d33711c0..3ec34dcc 100644 --- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj +++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj @@ -148,7 +148,7 @@ SyncCThrow false ../../src;../../src/external/windows/include/SDL2;../../src/external/windows/include/AL;../../src/external/windows/include/python;../../src/external/windows/include;../../src/external/open_dynamics_engine-ef - stdcpp17 + stdcpp20 Fast @@ -173,7 +173,7 @@ SyncCThrow false ../../src;../../src/external/windows/include/SDL2;../../src/external/windows/include/AL;../../src/external/windows/include/python;../../src/external/windows/include;../../src/external/open_dynamics_engine-ef - stdcpp17 + stdcpp20 Fast @@ -434,11 +434,11 @@ - + diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters index 7cc31a90..f77535ec 100644 --- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters +++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters @@ -736,9 +736,6 @@ ballistica\base\support - - ballistica\base\support - ballistica\base\ui @@ -751,6 +748,9 @@ ballistica\base\ui + + ballistica\base\ui + ballistica\base\ui diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj index 1de80047..505b71c1 100644 --- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj +++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj @@ -145,7 +145,7 @@ stdafx.h SyncCThrow ../../src;../../src/external/windows/include/SDL2;../../src/external/windows/include/AL;../../src/external/windows/include/python;../../src/external/windows/include;../../src/external/open_dynamics_engine-ef - stdcpp17 + stdcpp20 Fast @@ -169,7 +169,7 @@ stdafx.h true ../../src;../../src/external/windows/include/SDL2;../../src/external/windows/include/AL;../../src/external/windows/include/python;../../src/external/windows/include;../../src/external/open_dynamics_engine-ef - stdcpp17 + stdcpp20 Fast @@ -429,11 +429,11 @@ - + diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters index 7cc31a90..f77535ec 100644 --- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters +++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters @@ -736,9 +736,6 @@ ballistica\base\support - - ballistica\base\support - ballistica\base\ui @@ -751,6 +748,9 @@ ballistica\base\ui + + ballistica\base\ui + ballistica\base\ui diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py index 4d5b7fb9..fa542c74 100644 --- a/src/assets/ba_data/python/babase/__init__.py +++ b/src/assets/ba_data/python/babase/__init__.py @@ -162,6 +162,7 @@ from babase._mgen.enums import ( SpecialChar, InputType, UIScale, + QuitType, ) from babase._math import normalized_color, is_point_in_box, vec3validate from babase._meta import MetadataSubsystem @@ -286,6 +287,7 @@ __all__ = [ 'print_load_info', 'pushcall', 'quit', + 'QuitType', 'reload_media', 'request_permission', 'safecolor', diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index 4246f3e0..134caf5d 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -70,7 +70,7 @@ class App: # The app has not yet begun starting and should not be used in # any way. - NOT_RUNNING = 0 + NOT_STARTED = 0 # The native layer is spinning up its machinery (screens, # renderers, etc.). Nothing should happen in the Python layer @@ -90,13 +90,23 @@ class App: # All pieces are in place and the app is now doing its thing. RUNNING = 4 - # The app is backgrounded or otherwise suspended. - PAUSED = 5 + # Used on platforms such as mobile where the app basically needs + # to shut down while backgrounded. In this state, all event + # loops are suspended and all graphics and audio should cease + # completely. Be aware that the suspended state can be entered + # from any other state including NATIVE_BOOTSTRAPPING and + # SHUTTING_DOWN. + SUSPENDED = 5 - # The app is shutting down. + # The app is shutting down. This process may involve sending + # network messages or other things that can take up to a few + # seconds, so ideally graphics and audio should remain + # functional (with fades or spinners or whatever to show + # something is happening). SHUTTING_DOWN = 6 - # The app has completed shutdown. + # The app has completed shutdown. Any code running here should + # be basically immediate. SHUTDOWN_COMPLETE = 7 class DefaultAppModeSelector(AppModeSelector): @@ -150,7 +160,7 @@ class App: return self.env: babase.Env = _babase.Env() - self.state = self.State.NOT_RUNNING + self.state = self.State.NOT_STARTED # Default executor which can be used for misc background # processing. It should also be passed to any additional asyncio @@ -179,7 +189,7 @@ class App: self._init_completed = False self._meta_scan_completed = False self._native_start_called = False - self._native_paused = False + self._native_suspended = False self._native_shutdown_called = False self._native_shutdown_complete_called = False self._initial_sign_in_completed = False @@ -197,7 +207,8 @@ class App: self._mode_selector: babase.AppModeSelector | None = None self._shutdown_task: asyncio.Task[None] | None = None self._shutdown_tasks: list[Coroutine[None, None, None]] = [ - self._wait_for_shutdown_suppressions() + self._wait_for_shutdown_suppressions(), + self._fade_for_shutdown(), ] self._pool_thread_count = 0 @@ -315,7 +326,7 @@ class App: def add_shutdown_task(self, coro: Coroutine[None, None, None]) -> None: """Add a task to be run on app shutdown. - Note that tasks will be killed after + Note that shutdown tasks will be canceled after App.SHUTDOWN_TASK_TIMEOUT_SECONDS if they are still running. """ if ( @@ -389,18 +400,18 @@ class App: self._native_bootstrapping_completed = True self._update_state() - def on_native_pause(self) -> None: - """Called by the native layer when the app pauses.""" + def on_native_suspend(self) -> None: + """Called by the native layer when the app is suspended.""" assert _babase.in_logic_thread() - assert not self._native_paused # Should avoid redundant calls. - self._native_paused = True + assert not self._native_suspended # Should avoid redundant calls. + self._native_suspended = True self._update_state() - def on_native_resume(self) -> None: - """Called by the native layer when the app resumes.""" + def on_native_unsuspend(self) -> None: + """Called by the native layer when the app suspension ends.""" assert _babase.in_logic_thread() - assert self._native_paused # Should avoid redundant calls. - self._native_paused = False + assert self._native_suspended # Should avoid redundant calls. + self._native_suspended = False self._update_state() def on_native_shutdown(self) -> None: @@ -730,15 +741,15 @@ class App: _babase.lifecyclelog('app state shutting down') self._on_shutting_down() - elif self._native_paused: - # Entering paused state: - if self.state is not self.State.PAUSED: - self.state = self.State.PAUSED - self._on_pause() + elif self._native_suspended: + # Entering suspended state: + if self.state is not self.State.SUSPENDED: + self.state = self.State.SUSPENDED + self._on_suspend() else: - # Leaving paused state: - if self.state is self.State.PAUSED: - self._on_resume() + # Leaving suspended state: + if self.state is self.State.SUSPENDED: + self._on_unsuspend() # Entering or returning to running state if self._initial_sign_in_completed and self._meta_scan_completed: @@ -772,7 +783,7 @@ class App: self.state = self.State.NATIVE_BOOTSTRAPPING _babase.lifecyclelog('app state native bootstrapping') else: - # Only logical possibility left is NOT_RUNNING, in which + # Only logical possibility left is NOT_STARTED, in which # case we should not be getting called. logging.warning( 'App._update_state called while in %s state;' @@ -813,33 +824,33 @@ class App: try: await asyncio.wait_for(task, self.SHUTDOWN_TASK_TIMEOUT_SECONDS) except Exception: - logging.exception('Error in shutdown task.') + logging.exception('Error in shutdown task (%s).', coro) - def _on_pause(self) -> None: - """Called when the app goes to a paused state.""" + def _on_suspend(self) -> None: + """Called when the app goes to a suspended state.""" assert _babase.in_logic_thread() - # Pause all app subsystems in the opposite order they were inited. + # Suspend all app subsystems in the opposite order they were inited. for subsystem in reversed(self._subsystems): try: - subsystem.on_app_pause() + subsystem.on_app_suspend() except Exception: logging.exception( - 'Error in on_app_pause for subsystem %s.', subsystem + 'Error in on_app_suspend for subsystem %s.', subsystem ) - def _on_resume(self) -> None: - """Called when resuming.""" + def _on_unsuspend(self) -> None: + """Called when unsuspending.""" assert _babase.in_logic_thread() self.fg_state += 1 - # Resume all app subsystems in the same order they were inited. + # Unsuspend all app subsystems in the same order they were inited. for subsystem in self._subsystems: try: - subsystem.on_app_resume() + subsystem.on_app_unsuspend() except Exception: logging.exception( - 'Error in on_app_resume for subsystem %s.', subsystem + 'Error in on_app_unsuspend for subsystem %s.', subsystem ) def _on_shutting_down(self) -> None: @@ -884,6 +895,19 @@ class App: await asyncio.sleep(0.001) _babase.lifecyclelog('shutdown-suppress wait end') + async def _fade_for_shutdown(self) -> None: + import asyncio + + # Kick off a fade, block input, and wait for a short bit. + # Ideally most shutdown activity completes during the fade so + # there's no tangible wait. + _babase.lifecyclelog('fade-for-shutdown begin') + _babase.fade_screen(False, time=0.15) + _babase.lock_all_input() + # _babase.getsimplesound('swish2').play() + await asyncio.sleep(0.15) + _babase.lifecyclelog('fade-for-shutdown end') + def _threadpool_no_wait_done(self, fut: Future) -> None: try: fut.result() diff --git a/src/assets/ba_data/python/babase/_appsubsystem.py b/src/assets/ba_data/python/babase/_appsubsystem.py index eae0981a..812dc600 100644 --- a/src/assets/ba_data/python/babase/_appsubsystem.py +++ b/src/assets/ba_data/python/babase/_appsubsystem.py @@ -39,10 +39,10 @@ class AppSubsystem: def on_app_running(self) -> None: """Called when the app reaches the running state.""" - def on_app_pause(self) -> None: + def on_app_suspend(self) -> None: """Called when the app enters the paused state.""" - def on_app_resume(self) -> None: + def on_app_unsuspend(self) -> None: """Called when the app exits the paused state.""" def on_app_shutdown(self) -> None: diff --git a/src/assets/ba_data/python/babase/_apputils.py b/src/assets/ba_data/python/babase/_apputils.py index 4226d0fd..655bc8b3 100644 --- a/src/assets/ba_data/python/babase/_apputils.py +++ b/src/assets/ba_data/python/babase/_apputils.py @@ -64,7 +64,9 @@ def get_remote_app_name() -> babase.Lstr: def should_submit_debug_info() -> bool: """(internal)""" - return _babase.app.config.get('Submit Debug Info', True) + val = _babase.app.config.get('Submit Debug Info', True) + assert isinstance(val, bool) + return val def handle_v1_cloud_log() -> None: @@ -442,10 +444,10 @@ class AppHealthMonitor(AppSubsystem): self._first_check = False - def on_app_pause(self) -> None: + def on_app_suspend(self) -> None: assert _babase.in_logic_thread() self._running = False - def on_app_resume(self) -> None: + def on_app_unsuspend(self) -> None: assert _babase.in_logic_thread() self._running = True diff --git a/src/assets/ba_data/python/babase/_plugin.py b/src/assets/ba_data/python/babase/_plugin.py index 7711328e..692840b1 100644 --- a/src/assets/ba_data/python/babase/_plugin.py +++ b/src/assets/ba_data/python/babase/_plugin.py @@ -170,23 +170,23 @@ class PluginSubsystem(AppSubsystem): _error.print_exception('Error in plugin on_app_running()') - def on_app_pause(self) -> None: + def on_app_suspend(self) -> None: for plugin in self.active_plugins: try: - plugin.on_app_pause() + plugin.on_app_suspend() except Exception: from babase import _error - _error.print_exception('Error in plugin on_app_pause()') + _error.print_exception('Error in plugin on_app_suspend()') - def on_app_resume(self) -> None: + def on_app_unsuspend(self) -> None: for plugin in self.active_plugins: try: - plugin.on_app_resume() + plugin.on_app_unsuspend() except Exception: from babase import _error - _error.print_exception('Error in plugin on_app_resume()') + _error.print_exception('Error in plugin on_app_unsuspend()') def on_app_shutdown(self) -> None: for plugin in self.active_plugins: @@ -327,11 +327,11 @@ class Plugin: def on_app_running(self) -> None: """Called when the app reaches the running state.""" - def on_app_pause(self) -> None: - """Called when the app is switching to a paused state.""" + def on_app_suspend(self) -> None: + """Called when the app enters the suspended state.""" - def on_app_resume(self) -> None: - """Called when the app is resuming from a paused state.""" + def on_app_unsuspend(self) -> None: + """Called when the app exits the suspended state.""" def on_app_shutdown(self) -> None: """Called when the app is beginning the shutdown process.""" diff --git a/src/assets/ba_data/python/baclassic/_accountv1.py b/src/assets/ba_data/python/baclassic/_accountv1.py index 5dab06cd..f4667edf 100644 --- a/src/assets/ba_data/python/baclassic/_accountv1.py +++ b/src/assets/ba_data/python/baclassic/_accountv1.py @@ -49,10 +49,10 @@ class AccountV1Subsystem: babase.pushcall(do_auto_sign_in) - def on_app_pause(self) -> None: + def on_app_suspend(self) -> None: """Should be called when app is pausing.""" - def on_app_resume(self) -> None: + def on_app_unsuspend(self) -> None: """Should be called when the app is resumed.""" # Mark our cached tourneys as invalid so anyone using them knows diff --git a/src/assets/ba_data/python/baclassic/_music.py b/src/assets/ba_data/python/baclassic/_music.py index 6e6c4aec..bdda3a3a 100644 --- a/src/assets/ba_data/python/baclassic/_music.py +++ b/src/assets/ba_data/python/baclassic/_music.py @@ -239,7 +239,7 @@ class MusicSubsystem: logging.exception('Error in get_soundtrack_entry_name.') return 'default' - def on_app_resume(self) -> None: + def on_app_unsuspend(self) -> None: """Should be run when the app resumes from a suspended state.""" if babase.is_os_playing_music(): self.do_play_music(None) diff --git a/src/assets/ba_data/python/baclassic/_subsystem.py b/src/assets/ba_data/python/baclassic/_subsystem.py index db4fea55..757661fc 100644 --- a/src/assets/ba_data/python/baclassic/_subsystem.py +++ b/src/assets/ba_data/python/baclassic/_subsystem.py @@ -229,12 +229,12 @@ class ClassicSubsystem(babase.AppSubsystem): self.accounts.on_app_loading() - def on_app_pause(self) -> None: - self.accounts.on_app_pause() + def on_app_suspend(self) -> None: + self.accounts.on_app_suspend() - def on_app_resume(self) -> None: - self.accounts.on_app_resume() - self.music.on_app_resume() + def on_app_unsuspend(self) -> None: + self.accounts.on_app_unsuspend() + self.music.on_app_unsuspend() def on_app_shutdown(self) -> None: self.music.on_app_shutdown() @@ -701,11 +701,11 @@ class ClassicSubsystem(babase.AppSubsystem): ShowURLWindow(address) - def quit_window(self) -> None: + def quit_window(self, quit_type: babase.QuitType) -> None: """(internal)""" from bauiv1lib.confirm import QuitWindow - QuitWindow() + QuitWindow(quit_type) def tournament_entry_window( self, diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 6c636e36..1846a826 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21422 +TARGET_BALLISTICA_BUILD = 21443 TARGET_BALLISTICA_VERSION = '1.7.28' diff --git a/src/assets/ba_data/python/bascenev1/_campaign.py b/src/assets/ba_data/python/bascenev1/_campaign.py index 220e68b0..56cdba51 100644 --- a/src/assets/ba_data/python/bascenev1/_campaign.py +++ b/src/assets/ba_data/python/bascenev1/_campaign.py @@ -87,7 +87,9 @@ class Campaign: def get_selected_level(self) -> str: """Return the name of the Level currently selected in the UI.""" - return self.configdict.get('Selection', self._levels[0].name) + val = self.configdict.get('Selection', self._levels[0].name) + assert isinstance(val, str) + return val @property def configdict(self) -> dict[str, Any]: diff --git a/src/assets/ba_data/python/bascenev1/_level.py b/src/assets/ba_data/python/bascenev1/_level.py index c7469bb8..725c962e 100644 --- a/src/assets/ba_data/python/bascenev1/_level.py +++ b/src/assets/ba_data/python/bascenev1/_level.py @@ -105,7 +105,9 @@ class Level: def complete(self) -> bool: """Whether this Level has been completed.""" config = self._get_config_dict() - return config.get('Complete', False) + val = config.get('Complete', False) + assert isinstance(val, bool) + return val def set_complete(self, val: bool) -> None: """Set whether or not this level is complete.""" @@ -147,7 +149,9 @@ class Level: @property def rating(self) -> float: """The current rating for this Level.""" - return self._get_config_dict().get('Rating', 0.0) + val = self._get_config_dict().get('Rating', 0.0) + assert isinstance(val, float) + return val def set_rating(self, rating: float) -> None: """Set a rating for this Level, replacing the old ONLY IF higher.""" diff --git a/src/assets/ba_data/python/bascenev1/_multiteamsession.py b/src/assets/ba_data/python/bascenev1/_multiteamsession.py index e31c0a05..cdd1bf2a 100644 --- a/src/assets/ba_data/python/bascenev1/_multiteamsession.py +++ b/src/assets/ba_data/python/bascenev1/_multiteamsession.py @@ -162,8 +162,11 @@ class MultiTeamSession(Session): def get_max_players(self) -> int: """Return max number of Players allowed to join the game at once.""" if self.use_teams: - return babase.app.config.get('Team Game Max Players', 8) - return babase.app.config.get('Free-for-All Max Players', 8) + val = babase.app.config.get('Team Game Max Players', 8) + else: + val = babase.app.config.get('Free-for-All Max Players', 8) + assert isinstance(val, int) + return val def _instantiate_next_game(self) -> None: self._next_game_instance = _bascenev1.newactivity( diff --git a/src/assets/ba_data/python/bascenev1lib/game/conquest.py b/src/assets/ba_data/python/bascenev1lib/game/conquest.py index d748ee28..8b1589c1 100644 --- a/src/assets/ba_data/python/bascenev1lib/game/conquest.py +++ b/src/assets/ba_data/python/bascenev1lib/game/conquest.py @@ -14,13 +14,12 @@ from bascenev1lib.actor.flag import Flag from bascenev1lib.actor.scoreboard import Scoreboard from bascenev1lib.actor.playerspaz import PlayerSpaz from bascenev1lib.gameutils import SharedObjects +from bascenev1lib.actor.respawnicon import RespawnIcon import bascenev1 as bs if TYPE_CHECKING: from typing import Any, Sequence - from bascenev1lib.actor.respawnicon import RespawnIcon - class ConquestFlag(Flag): """A custom flag for use with Conquest games.""" @@ -49,7 +48,9 @@ class Player(bs.Player['Team']): @property def respawn_timer(self) -> bs.Timer | None: """Type safe access to standard respawn timer.""" - return self.customdata.get('respawn_timer', None) + val = self.customdata.get('respawn_timer', None) + assert isinstance(val, (bs.Timer, type(None))) + return val @respawn_timer.setter def respawn_timer(self, value: bs.Timer | None) -> None: @@ -58,7 +59,9 @@ class Player(bs.Player['Team']): @property def respawn_icon(self) -> RespawnIcon | None: """Type safe access to standard respawn icon.""" - return self.customdata.get('respawn_icon', None) + val = self.customdata.get('respawn_icon', None) + assert isinstance(val, (RespawnIcon, type(None))) + return val @respawn_icon.setter def respawn_icon(self, value: RespawnIcon | None) -> None: diff --git a/src/assets/ba_data/python/bascenev1lib/mainmenu.py b/src/assets/ba_data/python/bascenev1lib/mainmenu.py index b697a6b1..88b85249 100644 --- a/src/assets/ba_data/python/bascenev1lib/mainmenu.py +++ b/src/assets/ba_data/python/bascenev1lib/mainmenu.py @@ -300,7 +300,10 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): from bauiv1lib import specialoffer assert bs.app.classic is not None - if bool(False): + if bui.app.env.headless: + # UI stuff fails now in headless builds; avoid it. + pass + elif bool(False): uicontroller = bs.app.ui_v1.controller assert uicontroller is not None uicontroller.show_main_menu() diff --git a/src/assets/ba_data/python/bauiv1/__init__.py b/src/assets/ba_data/python/bauiv1/__init__.py index 9c8cc58d..3d3a7b42 100644 --- a/src/assets/ba_data/python/bauiv1/__init__.py +++ b/src/assets/ba_data/python/bauiv1/__init__.py @@ -69,6 +69,7 @@ from babase import ( PluginSpec, pushcall, quit, + QuitType, request_permission, safecolor, screenmessage, @@ -192,6 +193,7 @@ __all__ = [ 'PluginSpec', 'pushcall', 'quit', + 'QuitType', 'request_permission', 'rowwidget', 'safecolor', diff --git a/src/assets/ba_data/python/bauiv1/_hooks.py b/src/assets/ba_data/python/bauiv1/_hooks.py index 0925bc6f..c7b6072a 100644 --- a/src/assets/ba_data/python/bauiv1/_hooks.py +++ b/src/assets/ba_data/python/bauiv1/_hooks.py @@ -13,6 +13,8 @@ import _bauiv1 if TYPE_CHECKING: from typing import Sequence + import babase + def ticket_icon_press() -> None: from babase import app @@ -57,14 +59,14 @@ def party_icon_activate(origin: Sequence[float]) -> None: logging.warning('party_icon_activate: no classic.') -def quit_window() -> None: +def quit_window(quit_type: babase.QuitType) -> None: from babase import app if app.classic is None: logging.exception('Classic not present.') return - app.classic.quit_window() + app.classic.quit_window(quit_type) def device_menu_press(device_id: int | None) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/confirm.py b/src/assets/ba_data/python/bauiv1lib/confirm.py index f378a94c..2882b598 100644 --- a/src/assets/ba_data/python/bauiv1lib/confirm.py +++ b/src/assets/ba_data/python/bauiv1lib/confirm.py @@ -153,15 +153,15 @@ class QuitWindow: def __init__( self, + quit_type: bui.QuitType | None = None, swish: bool = False, - back: bool = False, origin_widget: bui.Widget | None = None, ): classic = bui.app.classic assert classic is not None ui = bui.app.ui_v1 app = bui.app - self._back = back + self._quit_type = quit_type # If there's already one of us up somewhere, kill it. if ui.quit_window is not None: @@ -187,29 +187,8 @@ class QuitWindow: resource=quit_resource, subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))], ), - self._fade_and_quit, + lambda: bui.quit(confirm=False, quit_type=self._quit_type) + if self._quit_type is not None + else bui.quit(confirm=False), origin_widget=origin_widget, ).root_widget - - def _fade_and_quit(self) -> None: - bui.fade_screen( - False, - time=0.2, - endcall=lambda: bui.quit(soft=True, back=self._back), - ) - - # Prevent the user from doing anything else while we're on our - # way out. - bui.lock_all_input() - - # On systems supporting soft-quit, unlock and fade back in shortly - # (soft-quit basically just backgrounds/hides the app). - if bui.app.env.supports_soft_quit: - # Unlock and fade back in shortly. Just in case something goes - # wrong (or on Android where quit just backs out of our activity - # and we may come back after). - def _come_back() -> None: - bui.unlock_all_input() - bui.fade_screen(True) - - bui.apptimer(0.5, _come_back) diff --git a/src/assets/ba_data/python/bauiv1lib/kiosk.py b/src/assets/ba_data/python/bauiv1lib/kiosk.py index f3a03696..377a86d0 100644 --- a/src/assets/ba_data/python/bauiv1lib/kiosk.py +++ b/src/assets/ba_data/python/bauiv1lib/kiosk.py @@ -21,7 +21,7 @@ class KioskWindow(bui.Window): self._height = 340.0 def _do_cancel() -> None: - QuitWindow(swish=True, back=True) + QuitWindow(swish=True, quit_type=bui.QuitType.BACK) super().__init__( root_widget=bui.containerwidget( diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py index 73dc0ccf..d1a00332 100644 --- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py +++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py @@ -190,7 +190,6 @@ class MainMenuWindow(bui.Window): # pylint: disable=too-many-branches # pylint: disable=too-many-locals # pylint: disable=too-many-statements - from bauiv1lib.confirm import QuitWindow from bauiv1lib.store.button import StoreButton plus = bui.app.plus @@ -422,7 +421,7 @@ class MainMenuWindow(bui.Window): ): def _do_quit() -> None: - QuitWindow(swish=True, back=True) + bui.quit(confirm=True, quit_type=bui.QuitType.BACK) bui.containerwidget( edit=self._root_widget, on_cancel_call=_do_quit @@ -1040,6 +1039,9 @@ class MainMenuWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.confirm import QuitWindow + # Note: Normally we should go through bui.quit(confirm=True) but + # invoking the window directly lets us scale it up from the + # button. QuitWindow(origin_widget=self._quit_button) def _demo_menu_press(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py b/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py index c8cc20a1..c9780d15 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py @@ -431,7 +431,9 @@ class GamepadSettingsWindow(bui.Window): def get_unassigned_buttons_run_value(self) -> bool: """(internal)""" assert self._settings is not None - return self._settings.get('unassignedButtonsRun', True) + val = self._settings.get('unassignedButtonsRun', True) + assert isinstance(val, bool) + return val def set_unassigned_buttons_run_value(self, value: bool) -> None: """(internal)""" @@ -446,7 +448,9 @@ class GamepadSettingsWindow(bui.Window): def get_start_button_activates_default_widget_value(self) -> bool: """(internal)""" assert self._settings is not None - return self._settings.get('startButtonActivatesDefaultWidget', True) + val = self._settings.get('startButtonActivatesDefaultWidget', True) + assert isinstance(val, bool) + return val def set_start_button_activates_default_widget_value( self, value: bool @@ -463,7 +467,9 @@ class GamepadSettingsWindow(bui.Window): def get_ui_only_value(self) -> bool: """(internal)""" assert self._settings is not None - return self._settings.get('uiOnly', False) + val = self._settings.get('uiOnly', False) + assert isinstance(val, bool) + return val def set_ui_only_value(self, value: bool) -> None: """(internal)""" @@ -478,7 +484,9 @@ class GamepadSettingsWindow(bui.Window): def get_ignore_completely_value(self) -> bool: """(internal)""" assert self._settings is not None - return self._settings.get('ignoreCompletely', False) + val = self._settings.get('ignoreCompletely', False) + assert isinstance(val, bool) + return val def set_ignore_completely_value(self, value: bool) -> None: """(internal)""" @@ -493,7 +501,9 @@ class GamepadSettingsWindow(bui.Window): def get_auto_recalibrate_analog_stick_value(self) -> bool: """(internal)""" assert self._settings is not None - return self._settings.get('autoRecalibrateAnalogStick', False) + val = self._settings.get('autoRecalibrateAnalogStick', False) + assert isinstance(val, bool) + return val def set_auto_recalibrate_analog_stick_value(self, value: bool) -> None: """(internal)""" @@ -510,7 +520,9 @@ class GamepadSettingsWindow(bui.Window): assert self._settings is not None if not self._is_secondary: raise RuntimeError('Enable value only applies to secondary editor.') - return self._settings.get('enableSecondary', False) + val = self._settings.get('enableSecondary', False) + assert isinstance(val, bool) + return val def show_secondary_editor(self) -> None: """(internal)""" diff --git a/src/assets/ba_data/python/bauiv1lib/store/browser.py b/src/assets/ba_data/python/bauiv1lib/store/browser.py index 6365b879..269a8164 100644 --- a/src/assets/ba_data/python/bauiv1lib/store/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/store/browser.py @@ -1411,6 +1411,6 @@ def _check_merch_availability_in_bg_thread() -> None: # to do this during docs generation/etc.) if ( os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') != '1' - and bui.app.state is not bui.app.State.NOT_RUNNING + and bui.app.state is not bui.app.State.NOT_STARTED ): Thread(target=_check_merch_availability_in_bg_thread, daemon=True).start() diff --git a/src/ballistica/base/app_adapter/app_adapter.cc b/src/ballistica/base/app_adapter/app_adapter.cc index 0ca4d909..f7fb480c 100644 --- a/src/ballistica/base/app_adapter/app_adapter.cc +++ b/src/ballistica/base/app_adapter/app_adapter.cc @@ -76,7 +76,7 @@ void AppAdapter::OnMainThreadStartApp() { 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()); + // g_base->platform->SetHardwareCursorVisible(g_buildconfig.hardware_cursor()); // On desktop systems we just assume keyboard input exists and add it // immediately. @@ -100,7 +100,7 @@ void AppAdapter::OnAppShutdownComplete() { assert(g_base->InLogicThread()); } void AppAdapter::OnScreenSizeChange() { assert(g_base->InLogicThread()); } void AppAdapter::DoApplyAppConfig() { assert(g_base->InLogicThread()); } -void AppAdapter::OnAppPause_() { +void AppAdapter::OnAppSuspend_() { assert(g_core->InMainThread()); // IMPORTANT: Any pause related stuff that event-loop-threads need to do @@ -109,7 +109,7 @@ void AppAdapter::OnAppPause_() { // their event-loop is actually paused. // Pause all event loops. - EventLoop::SetEventLoopsPaused(true); + EventLoop::SetEventLoopsSuspended(true); if (g_base->network_reader) { g_base->network_reader->OnAppPause(); @@ -117,24 +117,26 @@ void AppAdapter::OnAppPause_() { g_base->networking->OnAppPause(); } -void AppAdapter::OnAppResume_() { +void AppAdapter::OnAppUnsuspend_() { assert(g_core->InMainThread()); // Spin all event-loops back up. - EventLoop::SetEventLoopsPaused(false); + EventLoop::SetEventLoopsSuspended(false); // Run resumes that expect to happen in the main thread. g_base->network_reader->OnAppResume(); g_base->networking->OnAppResume(); - // When resuming from a paused state, we may want to pause whatever game - // was running when we last were active. + // When resuming from a suspended state, we may want to pause whatever + // game was running when we last were active. // // TODO(efro): we should make this smarter so it doesn't happen if we're // in a network game or something that we can't pause; bringing up the // menu doesn't really accomplish anything there. - if (g_core->should_pause) { - g_core->should_pause = false; + // + // In general this probably should be handled at a higher level. + if (g_core->should_pause_active_game) { + g_core->should_pause_active_game = false; // If we've been completely backgrounded, send a menu-press command to // the game; this will bring up a pause menu if we're in the game/etc. @@ -144,13 +146,13 @@ void AppAdapter::OnAppResume_() { } } -void AppAdapter::PauseApp() { +void AppAdapter::SuspendApp() { assert(g_core); assert(g_core->InMainThread()); - if (app_paused_) { + if (app_suspended_) { Log(LogLevel::kWarning, - "AppAdapter::PauseApp() called with app already paused."); + "AppAdapter::SuspendApp() called with app already suspended."); return; } @@ -161,11 +163,12 @@ void AppAdapter::PauseApp() { millisecs_t max_duration{2000}; g_core->platform->DebugLog( - "PauseApp@" + std::to_string(core::CorePlatform::GetCurrentMillisecs())); + "SuspendApp@" + + std::to_string(core::CorePlatform::GetCurrentMillisecs())); // assert(!app_pause_requested_); // app_pause_requested_ = true; - app_paused_ = true; - OnAppPause_(); + app_suspended_ = true; + OnAppSuspend_(); // UpdatePauseResume_(); // We assume that the OS will completely suspend our process the moment we @@ -177,12 +180,12 @@ void AppAdapter::PauseApp() { < max_duration) { // If/when we get to a point with no threads waiting to be paused, we're // good to go. - auto threads{EventLoop::GetStillPausingThreads()}; + auto threads{EventLoop::GetStillSuspendingEventLoops()}; running_thread_count = threads.size(); if (running_thread_count == 0) { if (g_buildconfig.debug_build()) { Log(LogLevel::kDebug, - "PauseApp() completed in " + "SuspendApp() completed in " + std::to_string(core::CorePlatform::GetCurrentMillisecs() - start_time) + "ms."); @@ -193,7 +196,7 @@ void AppAdapter::PauseApp() { // If we made it here, we timed out. Complain. Log(LogLevel::kError, - std::string("PauseApp() took too long; ") + std::string("SuspendApp() took too long; ") + std::to_string(running_thread_count) + " threads not yet paused after " + std::to_string(core::CorePlatform::GetCurrentMillisecs() @@ -201,26 +204,27 @@ void AppAdapter::PauseApp() { + " ms."); } -void AppAdapter::ResumeApp() { +void AppAdapter::UnsuspendApp() { assert(g_core); assert(g_core->InMainThread()); - if (!app_paused_) { + if (!app_suspended_) { Log(LogLevel::kWarning, - "AppAdapter::ResumeApp() called with app not in paused state."); + "AppAdapter::UnsuspendApp() called with app not in paused state."); return; } millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()}; g_core->platform->DebugLog( - "ResumeApp@" + std::to_string(core::CorePlatform::GetCurrentMillisecs())); + "UnsuspendApp@" + + std::to_string(core::CorePlatform::GetCurrentMillisecs())); // assert(app_pause_requested_); // app_pause_requested_ = false; // UpdatePauseResume_(); - app_paused_ = false; - OnAppResume_(); + app_suspended_ = false; + OnAppUnsuspend_(); if (g_buildconfig.debug_build()) { Log(LogLevel::kDebug, - "ResumeApp() completed in " + "UnsuspendApp() completed in " + std::to_string(core::CorePlatform::GetCurrentMillisecs() - start_time) + "ms."); @@ -249,4 +253,32 @@ void AppAdapter::DoPushGraphicsContextRunnable(Runnable* runnable) { DoPushMainThreadRunnable(runnable); } +void AppAdapter::CursorPositionForDraw(float* x, float* y) { + assert(x && y); + + // By default, just use our latest event-delivered cursor position; + // this should work everywhere though perhaps might not be most optimal. + if (g_base->input == nullptr) { + *x = 0.0f; + *y = 0.0f; + return; + } + *x = g_base->input->cursor_pos_x(); + *y = g_base->input->cursor_pos_y(); +} + +auto AppAdapter::ShouldUseCursor() -> bool { return true; } + +auto AppAdapter::HasHardwareCursor() -> bool { return false; } + +void AppAdapter::SetHardwareCursorVisible(bool visible) { + printf("SHOULD SET VIS %d\n", static_cast(visible)); +} + +auto AppAdapter::CanSoftQuit() -> bool { return false; } +auto AppAdapter::CanBackQuit() -> bool { return false; } +void AppAdapter::DoBackQuit() { FatalError("Fixme unimplemented."); } +void AppAdapter::DoSoftQuit() { FatalError("Fixme unimplemented."); } +void AppAdapter::TerminateApp() { FatalError("Fixme unimplemented."); } + } // namespace ballistica::base diff --git a/src/ballistica/base/app_adapter/app_adapter.h b/src/ballistica/base/app_adapter/app_adapter.h index fe92d959..427f536c 100644 --- a/src/ballistica/base/app_adapter/app_adapter.h +++ b/src/ballistica/base/app_adapter/app_adapter.h @@ -55,30 +55,62 @@ class AppAdapter { /// Should return whether the current thread and/or context setup is the /// one where graphics calls should be made. For the default - /// implementation, this simply returns true in the main thread. + /// implementation, this simply returns true in the main thread. Note + /// that, while it is valid for the graphics context thread to change over + /// time, no more than one thread at a time should ever be considered the + /// graphics context. virtual auto InGraphicsContext() -> bool; - /// Push a call to be run in the app's graphics context. Be aware that - /// this may mean different threads on different platforms. + /// Push a call to be run in the app's graphics context. This may mean + /// different things depending on the graphics architecture in use. On + /// some platforms this simply pushes to the main thread. On others it may + /// schedule a call to be run just before or after a frame draw in a + /// dedicated render thread. The default implementation pushes to the main + /// thread. template void PushGraphicsContextCall(const F& lambda) { DoPushGraphicsContextRunnable(NewLambdaRunnableUnmanaged(lambda)); } + /// Return whether the current setup should show a cursor for mouse + /// motion. This generally should be true for desktop type situations or + /// if a mouse is present on a mobile device and false for purely touch + /// based situations. This value may change over time if a mouse is + /// plugged in or unplugged/etc. Default implementation returns true. + virtual auto ShouldUseCursor() -> bool; + + /// Return whether the app-adapter is having the OS show a cursor. + /// If this returns false, the engine will take care of drawing a cursor + /// when necessary. If true, SetHardwareCursorVisible will be called + /// periodically to inform the adapter what the cursor state should be. + /// The default implementation returns false; + virtual auto HasHardwareCursor() -> bool; + + /// If HasHardwareCursor() returns true, this will be called in the main + /// thread periodically when the adapter should be hiding/showing the + /// cursor. + virtual void SetHardwareCursorVisible(bool visible); + + /// Called to get the cursor position when drawing. Default implementation + /// returns the latest position delivered through the input subsystem, but + /// subclasses may want to override to provide slightly more up to date + /// values. + virtual void CursorPositionForDraw(float* x, float* y); + /// Put the app into a paused state. Should be called from the main /// thread. Pauses work, closes network sockets, etc. May correspond to /// being backgrounded on mobile, being minimized on desktop, etc. It is /// assumed that, as soon as this call returns, all work is finished and /// all threads can be suspended by the OS without any negative side /// effects. - void PauseApp(); + void SuspendApp(); /// Resume the app; can correspond to foregrounding on mobile, /// unminimizing on desktop, etc. Spins threads back up, re-opens network /// sockets, etc. - void ResumeApp(); + void UnsuspendApp(); - auto app_paused() const { return app_paused_; } + auto app_suspended() const { return app_suspended_; } /// Return whether this AppAdapter supports a 'fullscreen' toggle for its /// display. This currently will simply affect whether that option is @@ -91,6 +123,36 @@ class AppAdapter { /// Return whether this AppAdapter supports max-fps controls for its display. virtual auto SupportsMaxFPS() -> bool const; + /// Return whether this platform supports soft-quit. A soft quit is + /// when the app is reset/backgrounded/etc. but remains running in case + /// needed again. Generally this is the behavior on mobile apps. + virtual auto CanSoftQuit() -> bool; + + /// Implement soft-quit behavior. Will always be called in the logic + /// thread. Make sure to also override CanBackQuit to reflect this being + /// present. Note that when quitting the app yourself, you should use + /// g_base->QuitApp(); do not call this directly. + virtual void DoSoftQuit(); + + /// Return whether this platform supports back-quit. A back quit is a + /// variation of soft-quit generally triggered by a back button, which may + /// give different results in the OS. For example on Android this may + /// result in jumping back to the previous Android activity instead of + /// just ending the current one and dumping to the home screen as normal + /// soft quit might do. + virtual auto CanBackQuit() -> bool; + + /// Implement back-quit behavior. Will always be called in the logic + /// thread. Make sure to also override CanBackQuit to reflect this being + /// present. Note that when quitting the app yourself, you should use + /// g_base->QuitApp(); do not call this directly. + virtual void DoBackQuit(); + + /// Terminate the app. This can be immediate or by posting some high + /// level event. There should be nothing left to do in the engine at + /// this point. + virtual void TerminateApp(); + protected: AppAdapter(); virtual ~AppAdapter(); @@ -104,9 +166,9 @@ class AppAdapter { virtual void DoPushGraphicsContextRunnable(Runnable* runnable); private: - void OnAppPause_(); - void OnAppResume_(); - bool app_paused_{}; + void OnAppSuspend_(); + void OnAppUnsuspend_(); + bool app_suspended_{}; }; } // namespace ballistica::base diff --git a/src/ballistica/base/app_adapter/app_adapter_apple.cc b/src/ballistica/base/app_adapter/app_adapter_apple.cc index 19475ce2..9d87088d 100644 --- a/src/ballistica/base/app_adapter/app_adapter_apple.cc +++ b/src/ballistica/base/app_adapter/app_adapter_apple.cc @@ -5,10 +5,34 @@ #include +#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/support/app_config.h" #include "ballistica/shared/ballistica.h" +#include "ballistica/shared/foundation/event_loop.h" namespace ballistica::base { +/// RAII-friendly way to mark the thread and calls we're allowed to run graphics +/// stuff in. +class AppAdapterApple::ScopedAllowGraphics_ { + public: + explicit ScopedAllowGraphics_(AppAdapterApple* adapter) : adapter_{adapter} { + assert(!adapter_->graphics_allowed_); + adapter->graphics_thread_ = std::this_thread::get_id(); + adapter->graphics_allowed_ = true; + } + ~ScopedAllowGraphics_() { + assert(adapter_->graphics_allowed_); + adapter_->graphics_allowed_ = false; + } + + private: + AppAdapterApple* adapter_; +}; + auto AppAdapterApple::ManagesMainThreadEventLoop() const -> bool { // Nope; we run under a standard Cocoa/UIKit environment and they call us; we // don't call them. @@ -17,7 +41,166 @@ auto AppAdapterApple::ManagesMainThreadEventLoop() const -> bool { void AppAdapterApple::DoPushMainThreadRunnable(Runnable* runnable) { // Kick this along to swift. - BallisticaKit::PushRawRunnableToMain(runnable); + BallisticaKit::FromCppPushRawRunnableToMain(runnable); +} + +void AppAdapterApple::DoApplyAppConfig() { + assert(g_base->InLogicThread()); + + g_base->graphics_server->PushSetScreenPixelScaleCall( + g_base->app_config->Resolve(AppConfig::FloatID::kScreenPixelScale)); + + auto graphics_quality_requested = + g_base->graphics->GraphicsQualityFromAppConfig(); + + auto texture_quality_requested = + g_base->graphics->TextureQualityFromAppConfig(); + + g_base->app_adapter->PushGraphicsContextCall([=] { + SetScreen_(texture_quality_requested, graphics_quality_requested); + }); +} + +void AppAdapterApple::SetScreen_( + TextureQualityRequest texture_quality_requested, + GraphicsQualityRequest graphics_quality_requested) { + // If we know what we support, filter our request types to what is + // supported. This will keep us from rebuilding contexts if request type + // is flipping between different types that we don't support. + if (g_base->graphics->has_supports_high_quality_graphics_value()) { + if (!g_base->graphics->supports_high_quality_graphics() + && graphics_quality_requested > GraphicsQualityRequest::kMedium) { + graphics_quality_requested = GraphicsQualityRequest::kMedium; + } + } + + auto* gs = g_base->graphics_server; + + // We need a full renderer reload if quality values have changed + // or if we don't have one yet. + bool need_full_reload = + ((gs->texture_quality_requested() != texture_quality_requested) + || (gs->graphics_quality_requested() != graphics_quality_requested) + || !gs->texture_quality_set() || !gs->graphics_quality_set()); + + if (need_full_reload) { + ReloadRenderer_(graphics_quality_requested, texture_quality_requested); + } + + // Let the logic thread know we've got a graphics system up and running. + // It may use this cue to kick off asset loads and other bootstrapping. + g_base->logic->event_loop()->PushCall( + [] { g_base->logic->OnGraphicsReady(); }); +} + +void AppAdapterApple::ReloadRenderer_( + GraphicsQualityRequest graphics_quality_requested, + TextureQualityRequest texture_quality_requested) { + auto* gs = g_base->graphics_server; + + if (gs->renderer() && gs->renderer_loaded()) { + gs->UnloadRenderer(); + } + if (!gs->renderer()) { + gs->set_renderer(new RendererGL()); + } + + // Set a dummy screen resolution to start with. + // The main thread will kick along the latest real resolution just before + // each frame draw, but we need *something* here or else we'll get errors due + // to framebuffers getting made at size 0/etc. + g_base->graphics_server->SetScreenResolution(320.0, 240.0); + + // Update graphics quality based on request. + gs->set_graphics_quality_requested(graphics_quality_requested); + gs->set_texture_quality_requested(texture_quality_requested); + + // (Re)load stuff with these latest quality settings. + gs->LoadRenderer(); +} + +void AppAdapterApple::UpdateScreenSizes_() { + assert(g_base->app_adapter->InGraphicsContext()); +} + +void AppAdapterApple::SetScreenResolution(float pixel_width, + float pixel_height) { + auto allow = ScopedAllowGraphics_(this); + g_base->graphics_server->SetScreenResolution(pixel_width, pixel_height); +} + +auto AppAdapterApple::TryRender() -> bool { + auto allow = ScopedAllowGraphics_(this); + + // Run & release any pending runnables. + std::vector calls; + { + // Pull calls off the list before running them; this way we only need + // to grab the list lock for a moment. + auto lock = std::scoped_lock(graphics_calls_mutex_); + if (!graphics_calls_.empty()) { + graphics_calls_.swap(calls); + } + } + for (auto* call : calls) { + call->RunAndLogErrors(); + delete call; + } + // Lastly render. + return g_base->graphics_server->TryRender(); + + return true; +} + +auto AppAdapterApple::InGraphicsContext() -> bool { + return std::this_thread::get_id() == graphics_thread_ && graphics_allowed_; +} + +void AppAdapterApple::DoPushGraphicsContextRunnable(Runnable* runnable) { + // In strict mode, make sure we're in our TryRender() call. + auto lock = std::scoped_lock(graphics_calls_mutex_); + if (graphics_calls_.size() > 1000) { + BA_LOG_ONCE(LogLevel::kError, "graphics_calls_ got too big."); + } + graphics_calls_.push_back(runnable); +} + +auto AppAdapterApple::ShouldUseCursor() -> bool { + // On Mac of course we want our nice custom hardware cursor. + if (g_buildconfig.ostype_macos()) { + return true; + } + + // Anywhere else (iOS, tvOS, etc.) just say no cursor for now. The OS + // may draw one in some cases (trackpad connected to iPad, etc.) but we + // don't interfere and just let the OS draw its normal cursor in that + // case. Can revisit this later if that becomes a more common scenario. + return false; +} + +auto AppAdapterApple::HasHardwareCursor() -> bool { + // (mac should be only build getting called here) + assert(g_buildconfig.ostype_macos()); + + return true; +} + +void AppAdapterApple::SetHardwareCursorVisible(bool visible) { + // (mac should be only build getting called here) + assert(g_buildconfig.ostype_macos()); + assert(g_core->InMainThread()); + +#if BA_OSTYPE_MACOS + BallisticaKit::CocoaSupportSetCursorVisible(visible); +#endif +} + +void AppAdapterApple::TerminateApp() { +#if BA_OSTYPE_MACOS + BallisticaKit::CocoaSupportTerminateApp(); +#else + AppAdapter::TerminateApp(); +#endif } } // namespace ballistica::base diff --git a/src/ballistica/base/app_adapter/app_adapter_apple.h b/src/ballistica/base/app_adapter/app_adapter_apple.h index cf1297df..09ccdbc2 100644 --- a/src/ballistica/base/app_adapter/app_adapter_apple.h +++ b/src/ballistica/base/app_adapter/app_adapter_apple.h @@ -5,18 +5,55 @@ #if BA_XCODE_BUILD +#include +#include +#include + #include "ballistica/base/app_adapter/app_adapter.h" +#include "ballistica/shared/generic/runnable.h" namespace ballistica::base { class AppAdapterApple : public AppAdapter { public: + /// Given base, returns app-adapter cast to our type. This assumes it + /// actually *is* our type. + static auto Get(BaseFeatureSet* base) -> AppAdapterApple* { + auto* val = static_cast(base->app_adapter); + assert(val); + assert(dynamic_cast(base->app_adapter) == val); + return val; + } + auto ManagesMainThreadEventLoop() const -> bool override; + void DoApplyAppConfig() override; + + /// Called by FromSwift. + auto TryRender() -> bool; + + /// Called by FromSwift. + void SetScreenResolution(float pixel_width, float pixel_height); protected: void DoPushMainThreadRunnable(Runnable* runnable) override; + void DoPushGraphicsContextRunnable(Runnable* runnable) override; + auto InGraphicsContext() -> bool override; + auto ShouldUseCursor() -> bool override; + auto HasHardwareCursor() -> bool override; + void SetHardwareCursorVisible(bool visible) override; + void TerminateApp() override; private: + void UpdateScreenSizes_(); + class ScopedAllowGraphics_; + void SetScreen_(TextureQualityRequest texture_quality_requested, + GraphicsQualityRequest graphics_quality_requested); + void ReloadRenderer_(GraphicsQualityRequest graphics_quality_requested, + TextureQualityRequest texture_quality_requested); + std::thread::id graphics_thread_{}; + bool graphics_allowed_; + std::mutex graphics_calls_mutex_; + std::vector graphics_calls_; }; } // namespace ballistica::base diff --git a/src/ballistica/base/app_adapter/app_adapter_sdl.cc b/src/ballistica/base/app_adapter/app_adapter_sdl.cc index 6c50f68a..3ccf2ddf 100644 --- a/src/ballistica/base/app_adapter/app_adapter_sdl.cc +++ b/src/ballistica/base/app_adapter/app_adapter_sdl.cc @@ -1,9 +1,9 @@ // Released under the MIT License. See LICENSE for details. -#include "ballistica/shared/buildconfig/buildconfig_common.h" #if BA_SDL_BUILD #include "ballistica/base/app_adapter/app_adapter_sdl.h" + #include "ballistica/base/base.h" #include "ballistica/base/dynamics/bg/bg_dynamics.h" #include "ballistica/base/graphics/gl/gl_sys.h" @@ -17,10 +17,28 @@ #include "ballistica/base/support/app_config.h" #include "ballistica/base/ui/ui.h" #include "ballistica/core/platform/core_platform.h" +#include "ballistica/shared/buildconfig/buildconfig_common.h" #include "ballistica/shared/foundation/event_loop.h" namespace ballistica::base { +/// RAII-friendly way to mark where in the main thread we're allowed to run +/// graphics code (only applies in strict-graphics-context mode). +class AppAdapterSDL::ScopedAllowGraphics_ { + public: + explicit ScopedAllowGraphics_(AppAdapterSDL* adapter) : adapter_{adapter} { + assert(!adapter_->strict_graphics_allowed_); + adapter->strict_graphics_allowed_ = true; + } + ~ScopedAllowGraphics_() { + assert(adapter_->strict_graphics_allowed_); + adapter_->strict_graphics_allowed_ = false; + } + + private: + AppAdapterSDL* adapter_; +}; + AppAdapterSDL::AppAdapterSDL() { assert(!g_core->HeadlessMode()); assert(g_core->InMainThread()); @@ -36,6 +54,11 @@ void AppAdapterSDL::OnMainThreadStartApp() { // App is starting. Let's fire up the ol' SDL. uint32_t sdl_flags{SDL_INIT_VIDEO | SDL_INIT_JOYSTICK}; + if (strict_graphics_context_) { + Log(LogLevel::kWarning, + "AppAdapterSDL strict_graphics_context_ is enabled." + " Remember to turn this off."); + } // We may or may not want xinput on windows. if (g_buildconfig.ostype_windows()) { if (!g_core->platform->GetLowLevelConfigValue("enablexinput", 1)) { @@ -72,6 +95,9 @@ void AppAdapterSDL::OnMainThreadStartApp() { } } } + + // We currently use a software cursor, so hide the system one. + SDL_ShowCursor(SDL_DISABLE); } void AppAdapterSDL::DoApplyAppConfig() { @@ -91,6 +117,7 @@ void AppAdapterSDL::DoApplyAppConfig() { // g_base->app_config->Resolve(AppConfig::StringID::kResolutionAndroid); bool fullscreen = g_base->app_config->Resolve(AppConfig::BoolID::kFullscreen); + fullscreen = false; auto vsync = g_base->graphics->VSyncFromAppConfig(); int max_fps = g_base->app_config->Resolve(AppConfig::IntID::kMaxFPS); @@ -114,7 +141,7 @@ void AppAdapterSDL::RunMainThreadEventLoopToCompletion() { } // Draw. - if (!hidden_ && g_base->graphics_server->TryRender()) { + if (!hidden_ && TryRender()) { SDL_GL_SwapWindow(sdl_window_); } @@ -125,6 +152,34 @@ void AppAdapterSDL::RunMainThreadEventLoopToCompletion() { } } +auto AppAdapterSDL::TryRender() -> bool { + if (strict_graphics_context_) { + // In strict mode, allow graphics stuff in here. Normally we allow it + // anywhere in the main thread. + auto allow = ScopedAllowGraphics_(this); + + // Run & release any pending runnables. + std::vector calls; + { + // Pull calls off the list before running them; this way we only need + // to grab the list lock for a moment. + auto lock = std::scoped_lock(strict_graphics_calls_mutex_); + if (!strict_graphics_calls_.empty()) { + strict_graphics_calls_.swap(calls); + } + } + for (auto* call : calls) { + call->RunAndLogErrors(); + delete call; + } + // Lastly render. + return g_base->graphics_server->TryRender(); + } else { + // Simple path; just render. + return g_base->graphics_server->TryRender(); + } +} + void AppAdapterSDL::SleepUntilNextEventCycle_(microsecs_t cycle_start_time) { // Special case: if we're hidden, we simply sleep for a long bit; no fancy // timing. @@ -300,7 +355,13 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) { break; case SDL_QUIT: - g_base->logic->event_loop()->PushCall([] { g_base->ui->ConfirmQuit(); }); + if (g_core->GetAppTimeMillisecs() - last_windowevent_close_time_ < 100) { + // If they hit the window close button, skip the confirm. + g_base->QuitApp(false); + } else { + // By default, confirm before quitting. + g_base->QuitApp(true); + } break; case SDL_TEXTINPUT: { @@ -310,6 +371,13 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) { case SDL_WINDOWEVENT: { switch (event.window.event) { + case SDL_WINDOWEVENT_CLOSE: { + // Simply note that this happened. We use this to adjust our + // SDL_QUIT behavior (quit is called right after this). + last_windowevent_close_time_ = g_core->GetAppTimeMillisecs(); + break; + } + case SDL_WINDOWEVENT_MAXIMIZED: { if (g_buildconfig.ostype_macos() && !fullscreen_) { // Special case: on Mac, we wind up here if someone fullscreens @@ -476,9 +544,12 @@ void AppAdapterSDL::SetScreen_( bool fullscreen, int max_fps, VSyncRequest vsync_requested, TextureQualityRequest texture_quality_requested, GraphicsQualityRequest graphics_quality_requested) { - assert(InGraphicsContext()); + assert(g_core->InMainThread()); assert(!g_core->HeadlessMode()); + // In strict mode, allow graphics stuff in here. + auto allow = ScopedAllowGraphics_(this); + // If we know what we support, filter our request types to what is // supported. This will keep us from rebuilding contexts if request type // is flipping between different types that we don't support. @@ -676,6 +747,74 @@ void AppAdapterSDL::UpdateScreenSizes_() { static_cast(pixels_y)); } +/// As a default, allow graphics stuff in the main thread. +auto AppAdapterSDL::InGraphicsContext() -> bool { + // In strict mode, make sure we're in the right thread *and* within our + // render call. + if (strict_graphics_context_) { + return g_core->InMainThread() && strict_graphics_allowed_; + } + // By default, allow anywhere in main thread. + return g_core->InMainThread(); +} + +void AppAdapterSDL::DoPushGraphicsContextRunnable(Runnable* runnable) { + // In strict mode, make sure we're in our TryRender() call. + if (strict_graphics_context_) { + auto lock = std::scoped_lock(strict_graphics_calls_mutex_); + if (strict_graphics_calls_.size() > 1000) { + BA_LOG_ONCE(LogLevel::kError, "strict_graphics_calls_ got too big."); + } + strict_graphics_calls_.push_back(runnable); + } else { + DoPushMainThreadRunnable(runnable); + } +} + +void AppAdapterSDL::CursorPositionForDraw(float* x, float* y) { + // Note: disabling this code, but leaving it in here for now as a proof of + // concept in case its worth revisiting later. In my current tests on Mac, + // Windows, and Linux, I'm seeing basicaly zero difference between + // immediate calculated values and ones from the event system, so I'm + // guessing remaining latency might be coming from the fact that frames + // tend to get assembled 1/60th of a second before it is displayed or + // whatnot. It'd probably be a better use of time to just wire up hardware + // cursor support for this build. + if (explicit_bool(true)) { + AppAdapter::CursorPositionForDraw(x, y); + return; + } + + assert(x && y); + + // Grab latest values from the input subsystem (what would get used by + // default). + float event_x = g_base->input->cursor_pos_x(); + float event_y = g_base->input->cursor_pos_y(); + + // Now ask sdl for it's latest values and wrangle the math ourself. + int sdl_x, sdl_y; + SDL_GetMouseState(&sdl_x, &sdl_y); + + // Convert window coords to normalized. + float normalized_x = static_cast(sdl_x) / window_size_.x; + float normalized_y = 1.0f - static_cast(sdl_y) / window_size_.y; + + // Convert normalized coords to virtual coords. + float immediate_x = g_base->graphics->PixelToVirtualX( + normalized_x * g_base->graphics->screen_pixel_width()); + float immediate_y = g_base->graphics->PixelToVirtualY( + normalized_y * g_base->graphics->screen_pixel_height()); + + float diff_x = immediate_x - event_x; + float diff_y = immediate_y - event_y; + printf("DIFFS: %.2f %.2f\n", diff_x, diff_y); + fflush(stdout); + + *x = immediate_x; + *y = immediate_y; +} + auto AppAdapterSDL::CanToggleFullscreen() -> bool const { return true; } auto AppAdapterSDL::SupportsVSync() -> bool const { return true; } auto AppAdapterSDL::SupportsMaxFPS() -> bool const { return true; } diff --git a/src/ballistica/base/app_adapter/app_adapter_sdl.h b/src/ballistica/base/app_adapter/app_adapter_sdl.h index ab66e578..cc0f0405 100644 --- a/src/ballistica/base/app_adapter/app_adapter_sdl.h +++ b/src/ballistica/base/app_adapter/app_adapter_sdl.h @@ -32,6 +32,8 @@ class AppAdapterSDL : public AppAdapter { void OnMainThreadStartApp() override; void DoApplyAppConfig() override; + auto TryRender() -> bool; + auto CanToggleFullscreen() -> bool const override; auto SupportsVSync() -> bool const override; auto SupportsMaxFPS() -> bool const override; @@ -40,8 +42,12 @@ class AppAdapterSDL : public AppAdapter { void DoPushMainThreadRunnable(Runnable* runnable) override; void RunMainThreadEventLoopToCompletion() override; void DoExitMainThreadEventLoop() override; + auto InGraphicsContext() -> bool override; + void DoPushGraphicsContextRunnable(Runnable* runnable) override; + void CursorPositionForDraw(float* x, float* y) override; private: + class ScopedAllowGraphics_; void SetScreen_(bool fullscreen, int max_fps, VSyncRequest vsync_requested, TextureQualityRequest texture_quality_requested, GraphicsQualityRequest graphics_quality_requested); @@ -60,11 +66,23 @@ class AppAdapterSDL : public AppAdapter { void RemoveSDLInputDevice_(int index); void SleepUntilNextEventCycle_(microsecs_t cycle_start_time); - bool done_{}; - bool fullscreen_{}; - bool vsync_actually_enabled_{}; - bool debug_log_sdl_frame_timing_{}; - bool hidden_{}; + bool done_ : 1 {}; + bool fullscreen_ : 1 {}; + bool vsync_actually_enabled_ : 1 {}; + bool debug_log_sdl_frame_timing_ : 1 {}; + bool hidden_ : 1 {}; + + /// With this off, graphics call pushes simply get pushed to the main + /// thread and graphics code is allowed to run any time in the main + /// thread. When this is on, pushed graphics-context calls get enqueued + /// and run as part of drawing, and graphics context calls are only + /// allowed during draws. This strictness is generally not needed here but + /// can be useful to test with, as it more closely matches other platforms + /// that require such a setup. + bool strict_graphics_context_ : 1 {}; + bool strict_graphics_allowed_ : 1 {}; + std::mutex strict_graphics_calls_mutex_; + std::vector strict_graphics_calls_; VSync vsync_{VSync::kUnset}; uint32_t sdl_runnable_event_id_{}; int max_fps_{60}; @@ -73,6 +91,7 @@ class AppAdapterSDL : public AppAdapter { Vector2f window_size_{1.0f, 1.0f}; SDL_Window* sdl_window_{}; void* sdl_gl_context_{}; + millisecs_t last_windowevent_close_time_{}; }; } // namespace ballistica::base diff --git a/src/ballistica/base/assets/assets.cc b/src/ballistica/base/assets/assets.cc index 4a1f1bc6..a8da1408 100644 --- a/src/ballistica/base/assets/assets.cc +++ b/src/ballistica/base/assets/assets.cc @@ -1327,6 +1327,10 @@ void Assets::SetLanguageKeys( std::scoped_lock lock(language_mutex_); language_ = language; } + // Log our unique change state so things that go inactive and stop + // receiving callbacks can see if they're out of date if they become + // active again. + language_state_++; // Let some subsystems know that language has changed. g_base->app_mode()->LanguageChanged(); diff --git a/src/ballistica/base/assets/assets.h b/src/ballistica/base/assets/assets.h index 6d19f655..6db00287 100644 --- a/src/ballistica/base/assets/assets.h +++ b/src/ballistica/base/assets/assets.h @@ -115,6 +115,8 @@ class Assets { auto sys_assets_loaded() const { return sys_assets_loaded_; } + auto language_state() const { return language_state_; } + private: static void MarkAssetForLoad(Asset* c); void LoadSystemTexture(SysTextureID id, const char* name); @@ -175,6 +177,7 @@ class Assets { // Text & Language (need to mold this into more asset-like concepts). std::mutex language_mutex_; std::unordered_map language_; + int language_state_{}; std::mutex special_char_mutex_; std::unordered_map special_char_strings_; }; diff --git a/src/ballistica/base/assets/assets_server.cc b/src/ballistica/base/assets/assets_server.cc index 447e8776..e5352ddb 100644 --- a/src/ballistica/base/assets/assets_server.cc +++ b/src/ballistica/base/assets/assets_server.cc @@ -15,7 +15,7 @@ AssetsServer::AssetsServer() = default; void AssetsServer::OnMainThreadStartApp() { // Spin up our thread. event_loop_ = new EventLoop(EventLoopID::kAssets); - g_core->pausable_event_loops.push_back(event_loop_); + g_core->suspendable_event_loops.push_back(event_loop_); event_loop_->PushCallSynchronous([this] { OnAppStartInThread(); }); } diff --git a/src/ballistica/base/assets/mesh_asset_renderer_data.h b/src/ballistica/base/assets/mesh_asset_renderer_data.h index 4b249042..aba21840 100644 --- a/src/ballistica/base/assets/mesh_asset_renderer_data.h +++ b/src/ballistica/base/assets/mesh_asset_renderer_data.h @@ -11,8 +11,8 @@ namespace ballistica::base { // this is provided by the renderer class MeshAssetRendererData : public Object { public: - auto GetDefaultOwnerThread() const -> EventLoopID override { - return EventLoopID::kMain; + auto GetThreadOwnership() const -> ThreadOwnership override { + return ThreadOwnership::kGraphicsContext; } }; diff --git a/src/ballistica/base/assets/texture_asset.cc b/src/ballistica/base/assets/texture_asset.cc index e64b0665..b43c13bb 100644 --- a/src/ballistica/base/assets/texture_asset.cc +++ b/src/ballistica/base/assets/texture_asset.cc @@ -5,14 +5,12 @@ #include "ballistica/base/app_adapter/app_adapter.h" #include "ballistica/base/assets/texture_asset_preload_data.h" #include "ballistica/base/assets/texture_asset_renderer_data.h" -#include "ballistica/base/graphics/graphics.h" #include "ballistica/base/graphics/graphics_server.h" #include "ballistica/base/graphics/renderer/renderer.h" #include "ballistica/base/graphics/text/text_packer.h" #include "ballistica/base/graphics/texture/dds.h" #include "ballistica/base/graphics/texture/ktx.h" #include "ballistica/base/graphics/texture/pvr.h" -#include "ballistica/core/core.h" #include "external/qr_code_generator/QrCode.hpp" namespace ballistica::base { diff --git a/src/ballistica/base/assets/texture_asset_renderer_data.h b/src/ballistica/base/assets/texture_asset_renderer_data.h index 85f75915..b7839430 100644 --- a/src/ballistica/base/assets/texture_asset_renderer_data.h +++ b/src/ballistica/base/assets/texture_asset_renderer_data.h @@ -10,8 +10,8 @@ namespace ballistica::base { // Renderer-specific data (gl tex, etc). To be extended by the renderer. class TextureAssetRendererData : public Object { public: - auto GetDefaultOwnerThread() const -> EventLoopID override { - return EventLoopID::kMain; + auto GetThreadOwnership() const -> ThreadOwnership override { + return ThreadOwnership::kGraphicsContext; } // Create the renderer data but don't load it in yet. diff --git a/src/ballistica/base/audio/audio_server.cc b/src/ballistica/base/audio/audio_server.cc index b446f686..5ba5a304 100644 --- a/src/ballistica/base/audio/audio_server.cc +++ b/src/ballistica/base/audio/audio_server.cc @@ -136,14 +136,14 @@ AudioServer::AudioServer() : impl_{new AudioServer::Impl()} {} void AudioServer::OnMainThreadStartApp() { // Spin up our thread. event_loop_ = new EventLoop(EventLoopID::kAudio); - g_core->pausable_event_loops.push_back(event_loop_); + g_core->suspendable_event_loops.push_back(event_loop_); // Run some setup stuff from our shiny new thread. event_loop_->PushCall([this] { // We want to be informed when our event-loop is pausing and unpausing. - event_loop()->AddPauseCallback( + event_loop()->AddSuspendCallback( NewLambdaRunnableUnmanaged([this] { OnThreadPause(); })); - event_loop()->AddResumeCallback( + event_loop()->AddUnsuspendCallback( NewLambdaRunnableUnmanaged([this] { OnThreadResume(); })); }); @@ -1136,7 +1136,7 @@ void AudioServer::PushSetSoundPitchCall(float val) { event_loop()->PushCall([this, val] { SetSoundPitch(val); }); } -void AudioServer::PushSetPausedCall(bool pause) { +void AudioServer::PushSetSuspendedCall(bool pause) { event_loop()->PushCall([this, pause] { if (g_buildconfig.ostype_android()) { Log(LogLevel::kError, "Shouldn't be getting SetPausedCall on android."); @@ -1189,7 +1189,7 @@ void AudioServer::ClearSoundRefDeleteList() { void AudioServer::BeginInterruption() { assert(!g_base->InAudioThread()); - g_base->audio_server->PushSetPausedCall(true); + g_base->audio_server->PushSetSuspendedCall(true); // Wait a reasonable amount of time for the thread to act on it. millisecs_t t = g_core->GetAppTimeMillisecs(); @@ -1211,7 +1211,7 @@ void AudioServer::OnThreadResume() { SetPaused(false); } void AudioServer::EndInterruption() { assert(!g_base->InAudioThread()); - g_base->audio_server->PushSetPausedCall(false); + g_base->audio_server->PushSetSuspendedCall(false); // Wait a reasonable amount of time for the thread to act on it. millisecs_t t = g_core->GetAppTimeMillisecs(); diff --git a/src/ballistica/base/audio/audio_server.h b/src/ballistica/base/audio/audio_server.h index 1d7dbd55..5c5202ec 100644 --- a/src/ballistica/base/audio/audio_server.h +++ b/src/ballistica/base/audio/audio_server.h @@ -28,7 +28,7 @@ class AudioServer { void PushSetVolumesCall(float music_volume, float sound_volume); void PushSetSoundPitchCall(float val); - void PushSetPausedCall(bool pause); + void PushSetSuspendedCall(bool pause); static void BeginInterruption(); static void EndInterruption(); diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index 5bedf8d5..dcedd3c3 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -26,6 +26,7 @@ #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" #include "ballistica/shared/foundation/logging.h" @@ -226,7 +227,7 @@ void BaseFeatureSet::OnAppShutdownComplete() { if (app_adapter->ManagesMainThreadEventLoop()) { app_adapter->DoExitMainThreadEventLoop(); } else { - platform->TerminateApp(); + app_adapter->TerminateApp(); } } @@ -297,10 +298,6 @@ void BaseFeatureSet::RunAppToCompletion() { g_base->app_adapter->RunMainThreadEventLoopToCompletion(); } -// void BaseFeatureSet::PrimeAppMainThreadEventPump() { -// app_adapter->PrimeMainThreadEventPump(); -// } - auto BaseFeatureSet::HavePlus() -> bool { if (!plus_soft_ && !tried_importing_plus_) { python->SoftImportPlus(); @@ -363,37 +360,6 @@ void BaseFeatureSet::set_classic(ClassicSoftInterface* classic) { classic_soft_ = classic; } -auto BaseFeatureSet::HaveUIV1() -> bool { - if (!ui_v1_soft_ && !tried_importing_ui_v1_) { - python->SoftImportUIV1(); - // Important to set this *after* import attempt, or a second import - // attempt while first is ongoing can insta-fail. Multiple import - // attempts shouldn't hurt anything. - tried_importing_ui_v1_ = true; - } - return ui_v1_soft_ != nullptr; -} - -/// Access the plus feature-set. Will throw an exception if not present. -auto BaseFeatureSet::ui_v1() -> UIV1SoftInterface* { - if (!ui_v1_soft_ && !tried_importing_ui_v1_) { - python->SoftImportUIV1(); - // Important to set this *after* import attempt, or a second import - // attempt while first is ongoing can insta-fail. Multiple import - // attempts shouldn't hurt anything. - tried_importing_ui_v1_ = true; - } - if (!ui_v1_soft_) { - throw Exception("ui_v1 feature-set not present."); - } - return ui_v1_soft_; -} - -void BaseFeatureSet::set_ui_v1(UIV1SoftInterface* ui_v1) { - assert(ui_v1_soft_ == nullptr); - ui_v1_soft_ = ui_v1; -} - auto BaseFeatureSet::GetAppInstanceUUID() -> const std::string& { static std::string app_instance_uuid; static bool have_app_instance_uuid = false; @@ -489,6 +455,10 @@ auto BaseFeatureSet::InNetworkWriteThread() const -> bool { return false; } +auto BaseFeatureSet::InGraphicsContext() const -> bool { + return app_adapter->InGraphicsContext(); +} + void BaseFeatureSet::ScreenMessage(const std::string& s, const Vector3f& color) { logic->event_loop()->PushCall( @@ -719,15 +689,25 @@ void BaseFeatureSet::ShutdownSuppressDisallow() { auto BaseFeatureSet::GetReturnValue() const -> int { return return_value(); } -void BaseFeatureSet::QuitApp(QuitType quit_type) { - // If they ask for 'back' and we support that, do it. - // Otherwise if they want 'back' or 'soft' and we support soft, do it. - // Otherwise go with a regular app shutdown. - if (quit_type == QuitType::kBack && g_base->platform->CanBackQuit()) { - logic->event_loop()->PushCall([this] { platform->DoBackQuit(); }); +void BaseFeatureSet::QuitApp(bool confirm, QuitType quit_type) { + // If they want a confirm dialog and we're able to present one, do that. + if (confirm && !g_core->HeadlessMode() && !g_base->input->IsInputLocked() + && g_base->ui->delegate() + && g_base->ui->delegate()->HasQuitConfirmDialog()) { + logic->event_loop()->PushCall( + [this, quit_type] { g_base->ui->delegate()->ConfirmQuit(quit_type); }); + return; + } + // Ok looks like we're quitting immediately. + // + // If they ask for 'back' and we support that, do it. Otherwise if they + // want 'back' or 'soft' and we support soft, do it. Otherwise go with a + // regular app shutdown. + if (quit_type == QuitType::kBack && app_adapter->CanBackQuit()) { + logic->event_loop()->PushCall([this] { app_adapter->DoBackQuit(); }); } else if ((quit_type == QuitType::kBack || quit_type == QuitType::kSoft) - && g_base->platform->CanSoftQuit()) { - logic->event_loop()->PushCall([this] { platform->DoSoftQuit(); }); + && app_adapter->CanSoftQuit()) { + logic->event_loop()->PushCall([this] { app_adapter->DoSoftQuit(); }); } else { logic->event_loop()->PushCall([this] { logic->Shutdown(); }); } diff --git a/src/ballistica/base/base.h b/src/ballistica/base/base.h index 2f283575..b45aa0b6 100644 --- a/src/ballistica/base/base.h +++ b/src/ballistica/base/base.h @@ -115,22 +115,10 @@ class TextureAssetPreloadData; class TextureAssetRendererData; class TouchInput; class UI; -class UIV1SoftInterface; +class UIDelegateInterface; class AppAdapterVR; class GraphicsVR; -enum class QuitType : uint8_t { - /// Leads to the process exiting. - kHard, - /// May hide/reset the app but keep the process running. Generally how - /// mobile apps behave. - kSoft, - /// Same as kSoft but gives 'back-button-pressed' behavior which may - /// differ depending on the OS (returning to a previous Activity in - /// Android instead of dumping to the home screen, etc.) - kBack, -}; - enum class AssetType : uint8_t { kTexture, kCollisionMesh, @@ -619,9 +607,11 @@ class BaseFeatureSet : public FeatureSetNativeComponent, /// 'soft' means the app can simply reset/hide itself instead of actually /// exiting the process (common behavior on mobile platforms). 'back' /// means that a soft-quit should behave as if a back-button was pressed, - /// whic may trigger different behavior in the OS than a standard soft - /// quit. - void QuitApp(QuitType quit_type = QuitType::kSoft); + /// which may trigger different behavior in the OS than a standard soft + /// quit. If 'confirm' is true, a confirmation dialog will be presented if + /// the current app-mode provides one and the app is in gui mode. + /// Otherwise the quit will be immediate. + void QuitApp(bool confirm = false, QuitType quit_type = QuitType::kSoft); /// Called when app shutdown process completes. Sets app to exit. void OnAppShutdownComplete(); @@ -657,14 +647,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent, void set_classic(ClassicSoftInterface* classic); - /// Try to load the ui_v1 feature-set and return whether it is available. - auto HaveUIV1() -> bool; - - /// Access the ui_v1 feature-set. Will throw an exception if not present. - auto ui_v1() -> UIV1SoftInterface*; - - void set_ui_v1(UIV1SoftInterface* ui_v1); - /// Return a string that should be universally unique to this particular /// running instance of the app. auto GetAppInstanceUUID() -> const std::string&; @@ -686,6 +668,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent, auto InAudioThread() const -> bool override; auto InBGDynamicsThread() const -> bool override; auto InNetworkWriteThread() const -> bool override; + auto InGraphicsContext() const -> bool override; /// High level screen-message call usable from any thread. void ScreenMessage(const std::string& s, const Vector3f& color) override; @@ -755,9 +738,10 @@ class BaseFeatureSet : public FeatureSetNativeComponent, Utils* const utils; // Variable subsystems. - auto* app_mode() const { return app_mode_; } - auto* stress_test() const { return stress_test_; } 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). @@ -781,7 +765,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent, AppMode* app_mode_; PlusSoftInterface* plus_soft_{}; ClassicSoftInterface* classic_soft_{}; - UIV1SoftInterface* ui_v1_soft_{}; StressTest* stress_test_; std::mutex shutdown_suppress_lock_; diff --git a/src/ballistica/base/dynamics/bg/bg_dynamics.cc b/src/ballistica/base/dynamics/bg/bg_dynamics.cc index 8ef05891..a1b82815 100644 --- a/src/ballistica/base/dynamics/bg/bg_dynamics.cc +++ b/src/ballistica/base/dynamics/bg/bg_dynamics.cc @@ -142,7 +142,7 @@ void BGDynamics::SetDrawSnapshot(BGDynamicsDrawSnapshot* s) { } void BGDynamics::TooSlow() { - if (!EventLoop::AreEventLoopsPaused()) { + if (!EventLoop::AreEventLoopsSuspended()) { g_base->bg_dynamics_server->PushTooSlowCall(); } } diff --git a/src/ballistica/base/dynamics/bg/bg_dynamics_server.cc b/src/ballistica/base/dynamics/bg/bg_dynamics_server.cc index 1d7f138d..424e46c5 100644 --- a/src/ballistica/base/dynamics/bg/bg_dynamics_server.cc +++ b/src/ballistica/base/dynamics/bg/bg_dynamics_server.cc @@ -691,7 +691,7 @@ BGDynamicsServer::BGDynamicsServer() void BGDynamicsServer::OnMainThreadStartApp() { // Spin up our thread. event_loop_ = new EventLoop(EventLoopID::kBGDynamics); - g_core->pausable_event_loops.push_back(event_loop_); + g_core->suspendable_event_loops.push_back(event_loop_); } BGDynamicsServer::Tendril::~Tendril() { diff --git a/src/ballistica/base/graphics/gl/program/program_gl.h b/src/ballistica/base/graphics/gl/program/program_gl.h index 7bf60b8a..437d2723 100644 --- a/src/ballistica/base/graphics/gl/program/program_gl.h +++ b/src/ballistica/base/graphics/gl/program/program_gl.h @@ -14,8 +14,8 @@ namespace ballistica::base { // Base class for fragment/vertex shaders. class RendererGL::ShaderGL : public Object { public: - auto GetDefaultOwnerThread() const -> EventLoopID override { - return EventLoopID::kMain; + auto GetThreadOwnership() const -> ThreadOwnership override { + return ThreadOwnership::kGraphicsContext; } ShaderGL(GLenum type_in, const std::string& src_in) : type_(type_in) { diff --git a/src/ballistica/base/graphics/gl/renderer_gl.cc b/src/ballistica/base/graphics/gl/renderer_gl.cc index ad6dcf46..ffa42e0f 100644 --- a/src/ballistica/base/graphics/gl/renderer_gl.cc +++ b/src/ballistica/base/graphics/gl/renderer_gl.cc @@ -2080,7 +2080,7 @@ void RendererGL::ProcessRenderCommandBuffer(RenderCommandBuffer* buffer, } case RenderCommandBuffer::Command::kCursorTranslate: { float x, y; - g_base->platform->GetCursorPosition(&x, &y); + g_base->app_adapter->CursorPositionForDraw(&x, &y); g_base->graphics_server->Translate(Vector3f(x, y, 0)); break; } diff --git a/src/ballistica/base/graphics/graphics.cc b/src/ballistica/base/graphics/graphics.cc index 56e0c7c1..c2e873ef 100644 --- a/src/ballistica/base/graphics/graphics.cc +++ b/src/ballistica/base/graphics/graphics.cc @@ -1452,11 +1452,11 @@ void Graphics::DrawCursor(FrameDef* frame_def) { millisecs_t app_time_millisecs = frame_def->app_time_millisecs(); - bool can_show_cursor = g_core->platform->IsRunningOnDesktop(); + bool can_show_cursor = g_base->app_adapter->ShouldUseCursor(); bool should_show_cursor = camera_->manual() || g_base->input->IsCursorVisible(); - if (g_buildconfig.hardware_cursor()) { + if (g_base->app_adapter->HasHardwareCursor()) { // If we're using a hardware cursor, ship hardware cursor visibility // updates to the app thread periodically. bool new_cursor_visibility = false; @@ -1472,7 +1472,7 @@ void Graphics::DrawCursor(FrameDef* frame_def) { last_cursor_visibility_event_time_ = app_time_millisecs; g_base->app_adapter->PushMainThreadCall([this] { assert(g_core && g_core->InMainThread()); - g_base->platform->SetHardwareCursorVisible(hardware_cursor_visible_); + g_base->app_adapter->SetHardwareCursorVisible(hardware_cursor_visible_); }); } } else { @@ -1486,10 +1486,10 @@ void Graphics::DrawCursor(FrameDef* frame_def) { auto xf = c.ScopedTransform(); // Note: we don't plug in known cursor position values here; we tell the - // renderer to insert the latest values on its end; this lessens cursor - // lag substantially. + // renderer to insert the latest values on its end; this can lessen + // cursor lag substantially. c.CursorTranslate(); - c.Translate(csize * 0.44f, csize * -0.44f, kCursorZDepth); + c.Translate(csize * 0.40f, csize * -0.38f, kCursorZDepth); c.Scale(csize, csize); c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1)); } diff --git a/src/ballistica/base/graphics/graphics_server.cc b/src/ballistica/base/graphics/graphics_server.cc index 04a7b484..8a89c655 100644 --- a/src/ballistica/base/graphics/graphics_server.cc +++ b/src/ballistica/base/graphics/graphics_server.cc @@ -2,22 +2,9 @@ #include "ballistica/base/graphics/graphics_server.h" -// Kill this. -#include "ballistica/base/graphics/gl/renderer_gl.h" - -// FIXME: clear out this conditional stuff. -#if BA_SDL_BUILD -#include "ballistica/base/app_adapter/app_adapter_sdl.h" -#else #include "ballistica/base/app_adapter/app_adapter.h" -#endif - -#include "ballistica/base/assets/assets.h" -#include "ballistica/base/graphics/mesh/mesh_data.h" #include "ballistica/base/graphics/renderer/renderer.h" -#include "ballistica/base/graphics/support/frame_def.h" #include "ballistica/base/logic/logic.h" -#include "ballistica/core/platform/core_platform.h" #include "ballistica/shared/foundation/event_loop.h" namespace ballistica::base { @@ -83,7 +70,7 @@ auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* { } // If the app is paused, never render. - if (g_base->app_adapter->app_paused()) { + if (g_base->app_adapter->app_suspended()) { return nullptr; } diff --git a/src/ballistica/base/graphics/graphics_server.h b/src/ballistica/base/graphics/graphics_server.h index 37b9071c..40309f8d 100644 --- a/src/ballistica/base/graphics/graphics_server.h +++ b/src/ballistica/base/graphics/graphics_server.h @@ -264,16 +264,6 @@ class GraphicsServer { auto renderer_context_lost() const { return renderer_context_lost_; } - // auto fullscreen_enabled() const { return fullscreen_enabled_; } - - // This doesn't actually toggle fullscreen. It is used to inform the game - // when fullscreen changes under it. - // auto set_fullscreen_enabled(bool fs) { fullscreen_enabled_ = fs; } - - // #if BA_ENABLE_OPENGL - // auto gl_context() const -> GLContext* { return gl_context_.get(); } - // #endif - auto graphics_quality_requested() const { return graphics_quality_requested_; } @@ -290,17 +280,8 @@ class GraphicsServer { auto texture_quality_requested() const { return texture_quality_requested_; } - // auto initial_screen_created() const { return initial_screen_created_; } - void HandlePushAndroidRes(const std::string& android_res); - // void HandleFullContextScreenRebuild( - // bool need_full_context_rebuild, bool fullscreen, - // GraphicsQualityRequest graphics_quality_requested, - // TextureQualityRequest texture_quality_requested); - // void HandleFullscreenToggling(bool do_set_existing_fs, bool do_toggle_fs, - // bool fullscreen); - private: // So we don't have to include app_adapter.h here for asserts. auto InGraphicsContext_() const -> bool; @@ -332,53 +313,49 @@ class GraphicsServer { } } - float res_x_{}; - float res_y_{}; - float res_x_virtual_{}; - float res_y_virtual_{}; - uint32_t texture_compression_types_{}; + bool renderer_loaded_ : 1 {}; + bool v_sync_ : 1 {}; + bool auto_vsync_ : 1 {}; + bool model_view_projection_matrix_dirty_ : 1 {true}; + bool model_world_matrix_dirty_ : 1 {true}; + bool graphics_quality_set_ : 1 {}; + bool texture_quality_set_ : 1 {}; + bool tv_border_ : 1 {}; + bool renderer_context_lost_ : 1 {}; + bool texture_compression_types_set_ : 1 {}; + bool cam_orient_matrix_dirty_ : 1 {true}; TextureQualityRequest texture_quality_requested_{ TextureQualityRequest::kUnset}; TextureQuality texture_quality_{TextureQuality::kLow}; GraphicsQualityRequest graphics_quality_requested_{ GraphicsQualityRequest::kUnset}; GraphicsQuality graphics_quality_{GraphicsQuality::kUnset}; - // bool fullscreen_enabled_{}; - // float target_res_x_{800.0f}; - // float target_res_y_{600.0f}; + int render_hold_{}; + float res_x_{}; + float res_y_{}; + float res_x_virtual_{}; + float res_y_virtual_{}; Matrix44f model_view_matrix_{kMatrix44fIdentity}; Matrix44f view_world_matrix_{kMatrix44fIdentity}; Matrix44f projection_matrix_{kMatrix44fIdentity}; Matrix44f model_view_projection_matrix_{kMatrix44fIdentity}; Matrix44f model_world_matrix_{kMatrix44fIdentity}; std::vector model_view_stack_; + uint32_t texture_compression_types_{}; uint32_t projection_matrix_state_{1}; uint32_t model_view_projection_matrix_state_{1}; uint32_t model_world_matrix_state_{1}; - Matrix44f light_shadow_projection_matrix_{kMatrix44fIdentity}; uint32_t light_shadow_projection_matrix_state_{1}; + uint32_t cam_pos_state_{1}; + uint32_t cam_orient_matrix_state_{1}; Vector3f cam_pos_{0.0f, 0.0f, 0.0f}; Vector3f cam_target_{0.0f, 0.0f, 0.0f}; - uint32_t cam_pos_state_{1}; + Matrix44f light_shadow_projection_matrix_{kMatrix44fIdentity}; Matrix44f cam_orient_matrix_ = kMatrix44fIdentity; - uint32_t cam_orient_matrix_state_{1}; std::list mesh_datas_; Timer* render_timer_{}; Renderer* renderer_{}; FrameDef* frame_def_{}; - // bool initial_screen_created_{}; - bool renderer_loaded_{}; - bool v_sync_{}; - bool auto_vsync_{}; - bool model_view_projection_matrix_dirty_{true}; - bool model_world_matrix_dirty_{true}; - bool graphics_quality_set_{}; - bool texture_quality_set_{}; - bool tv_border_{}; - bool renderer_context_lost_{}; - bool texture_compression_types_set_{}; - bool cam_orient_matrix_dirty_{true}; - int render_hold_{}; std::mutex frame_def_mutex_{}; }; diff --git a/src/ballistica/base/graphics/mesh/nine_patch_mesh.cc b/src/ballistica/base/graphics/mesh/nine_patch_mesh.cc index 1e243551..90ca292a 100644 --- a/src/ballistica/base/graphics/mesh/nine_patch_mesh.cc +++ b/src/ballistica/base/graphics/mesh/nine_patch_mesh.cc @@ -2,8 +2,6 @@ #include "ballistica/base/graphics/mesh/nine_patch_mesh.h" -#include "ballistica/shared/foundation/macros.h" - namespace ballistica::base { NinePatchMesh::NinePatchMesh(float x, float y, float z, float width, diff --git a/src/ballistica/base/graphics/renderer/framebuffer.h b/src/ballistica/base/graphics/renderer/framebuffer.h index 0dc84928..5966e6a5 100644 --- a/src/ballistica/base/graphics/renderer/framebuffer.h +++ b/src/ballistica/base/graphics/renderer/framebuffer.h @@ -9,8 +9,8 @@ namespace ballistica::base { class Framebuffer : public Object { public: - auto GetDefaultOwnerThread() const -> EventLoopID override { - return EventLoopID::kMain; + auto GetThreadOwnership() const -> ThreadOwnership override { + return ThreadOwnership::kGraphicsContext; } }; diff --git a/src/ballistica/base/graphics/renderer/render_target.h b/src/ballistica/base/graphics/renderer/render_target.h index e6bf453e..8ccc0a8d 100644 --- a/src/ballistica/base/graphics/renderer/render_target.h +++ b/src/ballistica/base/graphics/renderer/render_target.h @@ -11,8 +11,8 @@ namespace ballistica::base { // Encapsulates framebuffers, main windows, etc. class RenderTarget : public Object { public: - auto GetDefaultOwnerThread() const -> EventLoopID override { - return EventLoopID::kMain; + auto GetThreadOwnership() const -> ThreadOwnership override { + return ThreadOwnership::kGraphicsContext; } enum class Type { kScreen, kFramebuffer }; explicit RenderTarget(Type type); diff --git a/src/ballistica/base/graphics/texture/dds.cc b/src/ballistica/base/graphics/texture/dds.cc index b8ff2350..80a80921 100644 --- a/src/ballistica/base/graphics/texture/dds.cc +++ b/src/ballistica/base/graphics/texture/dds.cc @@ -2,7 +2,6 @@ #include "ballistica/base/graphics/texture/dds.h" -#include "ballistica/core/core.h" #include "ballistica/core/platform/core_platform.h" /* DDS loader written by Jon Watte 2002 */ diff --git a/src/ballistica/base/input/device/keyboard_input.cc b/src/ballistica/base/input/device/keyboard_input.cc index 062a68cb..4a1908d4 100644 --- a/src/ballistica/base/input/device/keyboard_input.cc +++ b/src/ballistica/base/input/device/keyboard_input.cc @@ -113,9 +113,6 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) pass = true; } -#pragma clang diagnostic push -#pragma ide diagnostic ignored "ConstantConditionsOC" - // if we're keyboard 1 we always send at least a key press event // along.. if (!parent_keyboard_input_ && !pass) { @@ -123,8 +120,6 @@ auto KeyboardInput::HandleKey(const SDL_Keysym* keysym, bool repeat, bool down) pass = true; } break; - -#pragma clang diagnostic pop } } if (pass) { diff --git a/src/ballistica/base/input/input.cc b/src/ballistica/base/input/input.cc index 0fde2709..06d31f27 100644 --- a/src/ballistica/base/input/input.cc +++ b/src/ballistica/base/input/input.cc @@ -15,7 +15,6 @@ #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" @@ -26,10 +25,10 @@ Input::Input() = default; void Input::PushCreateKeyboardInputDevices() { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall( - [this] { CreateKeyboardInputDevices(); }); + [this] { CreateKeyboardInputDevices_(); }); } -void Input::CreateKeyboardInputDevices() { +void Input::CreateKeyboardInputDevices_() { assert(g_base->InLogicThread()); if (keyboard_input_ != nullptr || keyboard_input_2_ != nullptr) { Log(LogLevel::kError, @@ -45,10 +44,10 @@ void Input::CreateKeyboardInputDevices() { void Input::PushDestroyKeyboardInputDevices() { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall( - [this] { DestroyKeyboardInputDevices(); }); + [this] { DestroyKeyboardInputDevices_(); }); } -void Input::DestroyKeyboardInputDevices() { +void Input::DestroyKeyboardInputDevices_() { assert(g_base->InLogicThread()); if (keyboard_input_ == nullptr || keyboard_input_2_ == nullptr) { Log(LogLevel::kError, @@ -80,8 +79,8 @@ auto Input::GetInputDevice(const std::string& name, return nullptr; } -auto Input::GetNewNumberedIdentifier(const std::string& name, - const std::string& identifier) -> int { +auto Input::GetNewNumberedIdentifier_(const std::string& name, + const std::string& identifier) -> int { assert(g_base->InLogicThread()); // Stuff like reserved_identifiers["JoyStickType"]["0x812312314"] = 2; @@ -154,7 +153,7 @@ void Input::CreateTouchInput() { PushAddInputDeviceCall(touch_input_, false); } -void Input::AnnounceConnects() { +void Input::AnnounceConnects_() { static bool first_print = true; // For the first announcement just say "X controllers detected" and don't @@ -205,7 +204,7 @@ void Input::AnnounceConnects() { newly_connected_controllers_.clear(); } -void Input::AnnounceDisconnects() { +void Input::AnnounceDisconnects_() { // If there's been several connected, just give a number. if (newly_disconnected_controllers_.size() > 1) { std::string s = @@ -228,7 +227,7 @@ void Input::AnnounceDisconnects() { newly_disconnected_controllers_.clear(); } -void Input::ShowStandardInputDeviceConnectedMessage(InputDevice* j) { +void Input::ShowStandardInputDeviceConnectedMessage_(InputDevice* j) { assert(g_base->InLogicThread()); std::string suffix; suffix += j->GetPersistentIdentifier(); @@ -243,10 +242,10 @@ void Input::ShowStandardInputDeviceConnectedMessage(InputDevice* j) { g_base->logic->DeleteAppTimer(connect_print_timer_id_); } connect_print_timer_id_ = g_base->logic->NewAppTimer( - 250, false, NewLambdaRunnable([this] { AnnounceConnects(); })); + 250, false, NewLambdaRunnable([this] { AnnounceConnects_(); })); } -void Input::ShowStandardInputDeviceDisconnectedMessage(InputDevice* j) { +void Input::ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j) { assert(g_base->InLogicThread()); newly_disconnected_controllers_.push_back(j->GetDeviceName() + " " @@ -258,7 +257,7 @@ 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, false, NewLambdaRunnable([this] { AnnounceDisconnects_(); })); } void Input::PushAddInputDeviceCall(InputDevice* input_device, @@ -313,8 +312,8 @@ void Input::AddInputDevice(InputDevice* device, bool standard_message) { // or something, but if it doesn't and thus matches an already-existing one, // we tack an index on to it. that way we can at least uniquely address them // based off how many are connected. - device->set_number(GetNewNumberedIdentifier(device->GetRawDeviceName(), - device->GetDeviceIdentifier())); + device->set_number(GetNewNumberedIdentifier_(device->GetRawDeviceName(), + device->GetDeviceIdentifier())); // Let the device know it's been added (for custom announcements, etc.) device->OnAdded(); @@ -327,7 +326,7 @@ void Input::AddInputDevice(InputDevice* device, bool standard_message) { // Need to do this after updating controls, as some control settings can // affect things we count (such as whether start activates default button). - UpdateInputDeviceCounts(); + UpdateInputDeviceCounts_(); } if (g_buildconfig.ostype_macos()) { @@ -344,7 +343,7 @@ void Input::AddInputDevice(InputDevice* device, bool standard_message) { } if (standard_message && !device->ShouldBeHiddenFromUser()) { - ShowStandardInputDeviceConnectedMessage(device); + ShowStandardInputDeviceConnectedMessage_(device); } } @@ -360,7 +359,7 @@ void Input::RemoveInputDevice(InputDevice* input, bool standard_message) { assert(g_base->InLogicThread()); if (standard_message && !input->ShouldBeHiddenFromUser()) { - ShowStandardInputDeviceDisconnectedMessage(input); + ShowStandardInputDeviceDisconnectedMessage_(input); } // Just look for it in our list.. if we find it, simply clear the ref @@ -380,14 +379,14 @@ void Input::RemoveInputDevice(InputDevice* input, bool standard_message) { // This should kill the device. device.Clear(); - UpdateInputDeviceCounts(); + UpdateInputDeviceCounts_(); return; } } throw Exception("Input::RemoveInputDevice: invalid device provided"); } -void Input::UpdateInputDeviceCounts() { +void Input::UpdateInputDeviceCounts_() { assert(g_base->InLogicThread()); auto current_time_millisecs = @@ -528,7 +527,7 @@ auto Input::ShouldCompletelyIgnoreInputDevice(InputDevice* input_device) return ignore_sdl_controllers_ && input_device->IsSDLController(); } -void Input::UpdateEnabledControllerSubsystems() { +void Input::UpdateEnabledControllerSubsystems_() { assert(g_base); // First off, on mac, let's update whether we want to completely ignore @@ -566,7 +565,7 @@ void Input::OnAppShutdownComplete() { assert(g_base->InLogicThread()); } void Input::DoApplyAppConfig() { assert(g_base->InLogicThread()); - UpdateEnabledControllerSubsystems(); + UpdateEnabledControllerSubsystems_(); // It's technically possible that updating these controls will add or // remove devices, thus changing the input_devices_ list, so lets work @@ -579,7 +578,7 @@ void Input::DoApplyAppConfig() { } // Some config settings can affect this. - UpdateInputDeviceCounts(); + UpdateInputDeviceCounts_(); } void Input::OnScreenSizeChange() { assert(g_base->InLogicThread()); } @@ -595,7 +594,7 @@ void Input::StepDisplayTime() { Log(LogLevel::kError, "Input has been temp-locked for 10 seconds; unlocking."); input_lock_count_temp_ = 0; - PrintLockLabels(); + PrintLockLabels_(); input_lock_temp_labels_.clear(); input_unlock_temp_labels_.clear(); } @@ -610,7 +609,7 @@ void Input::StepDisplayTime() { // now. millisecs_t incr = 249; if (real_time - last_input_device_count_update_time_ > incr) { - UpdateInputDeviceCounts(); + UpdateInputDeviceCounts_(); last_input_device_count_update_time_ = real_time; // Keep our idle-time up to date. @@ -682,7 +681,7 @@ void Input::UnlockAllInput(bool permanent, const std::string& label) { input_unlock_permanent_labels_.push_back(label); if (input_lock_count_permanent_ < 0) { BA_LOG_PYTHON_TRACE_ONCE("lock-count-permanent < 0"); - PrintLockLabels(); + PrintLockLabels_(); input_lock_count_permanent_ = 0; } @@ -713,7 +712,7 @@ void Input::UnlockAllInput(bool permanent, const std::string& label) { } } -void Input::PrintLockLabels() { +void Input::PrintLockLabels_() { std::string s = "INPUT LOCK REPORT (time=" + std::to_string(g_core->GetAppTimeMillisecs()) + "):"; int num; @@ -827,12 +826,12 @@ void Input::PushJoystickEvent(const SDL_Event& event, InputDevice* input_device) { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall([this, event, input_device] { - HandleJoystickEvent(event, input_device); + HandleJoystickEvent_(event, input_device); }); } -void Input::HandleJoystickEvent(const SDL_Event& event, - InputDevice* input_device) { +void Input::HandleJoystickEvent_(const SDL_Event& event, + InputDevice* input_device) { assert(g_base->InLogicThread()); assert(input_device); @@ -859,16 +858,28 @@ void Input::HandleJoystickEvent(const SDL_Event& event, input_device->HandleSDLEvent(&event); } +void Input::PushKeyPressEventSimple(int key) { + assert(g_base->logic->event_loop()); + g_base->logic->event_loop()->PushCall( + [this, key] { HandleKeyPressSimple_(key); }); +} + +void Input::PushKeyReleaseEventSimple(int key) { + assert(g_base->logic->event_loop()); + g_base->logic->event_loop()->PushCall( + [this, key] { HandleKeyReleaseSimple_(key); }); +} + void Input::PushKeyPressEvent(const SDL_Keysym& keysym) { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall( - [this, keysym] { HandleKeyPress(&keysym); }); + [this, keysym] { HandleKeyPress_(keysym); }); } void Input::PushKeyReleaseEvent(const SDL_Keysym& keysym) { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall( - [this, keysym] { HandleKeyRelease(&keysym); }); + [this, keysym] { HandleKeyRelease_(keysym); }); } void Input::CaptureKeyboardInput(HandleKeyPressCall* press_call, @@ -900,7 +911,41 @@ void Input::ReleaseJoystickInput() { joystick_input_capture_ = nullptr; } -void Input::HandleKeyPress(const SDL_Keysym* keysym) { +void Input::AddFakeMods_(SDL_Keysym* sym) { + // In cases where we are only passed simple keycodes, we fill in modifiers + // ourself by looking at currently held key states. This is less than + // ideal because modifier key states can fall out of sync in some cases + // but is generally 'good enough' for our minimal keyboard needs. + if (keys_held_.contains(SDLK_LCTRL) || keys_held_.contains(SDLK_RCTRL)) { + sym->mod |= KMOD_CTRL; + } + if (keys_held_.contains(SDLK_LSHIFT) || keys_held_.contains(SDLK_RSHIFT)) { + sym->mod |= KMOD_SHIFT; + } + if (keys_held_.contains(SDLK_LALT) || keys_held_.contains(SDLK_RALT)) { + sym->mod |= KMOD_ALT; + } + if (keys_held_.contains(SDLK_LGUI) || keys_held_.contains(SDLK_RGUI)) { + sym->mod |= KMOD_GUI; + } +} + +void Input::HandleKeyPressSimple_(int keycode) { + SDL_Keysym keysym{}; + keysym.sym = keycode; + AddFakeMods_(&keysym); + HandleKeyPress_(keysym); +} + +void Input::HandleKeyReleaseSimple_(int keycode) { + // See notes above. + SDL_Keysym keysym{}; + keysym.sym = keycode; + AddFakeMods_(&keysym); + HandleKeyRelease_(keysym); +} + +void Input::HandleKeyPress_(const SDL_Keysym& keysym) { assert(g_base->InLogicThread()); MarkInputActive(); @@ -912,7 +957,7 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { // If someone is capturing these events, give them a crack at it. if (keyboard_input_capture_press_) { - if (keyboard_input_capture_press_(*keysym)) { + if (keyboard_input_capture_press_(keysym)) { return; } } @@ -920,19 +965,19 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { // Regardless of what else we do, keep track of mod key states. // (for things like manual camera moves. For individual key presses // ideally we should use the modifiers bundled with the key presses) - UpdateModKeyStates(keysym, true); + UpdateModKeyStates_(&keysym, true); bool repeat_press; - if (keys_held_.count(keysym->sym) != 0) { + if (keys_held_.count(keysym.sym) != 0) { repeat_press = true; } else { repeat_press = false; - keys_held_.insert(keysym->sym); + keys_held_.insert(keysym.sym); } // Mobile-specific stuff. if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) { - switch (keysym->sym) { + switch (keysym.sym) { // FIXME: See if this stuff is still necessary. Was this perhaps // specifically to support the console? case SDLK_DELETE: @@ -944,7 +989,7 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { // 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)); + WidgetMessage(WidgetMessage::Type::kTextInput, &keysym)); break; } default: @@ -954,8 +999,8 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { // Command-F or Control-F toggles full-screen if the app-adapter supports it. if (g_base->app_adapter->CanToggleFullscreen()) { - if (!repeat_press && keysym->sym == SDLK_f - && ((keysym->mod & KMOD_CTRL) || (keysym->mod & KMOD_GUI))) { + if (!repeat_press && keysym.sym == SDLK_f + && ((keysym.mod & KMOD_CTRL) || (keysym.mod & KMOD_GUI))) { g_base->python->objs() .Get(BasePython::ObjID::kToggleFullscreenCall) .Call(); @@ -963,24 +1008,24 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { } } - // Control-Q quits. On Mac, the usual cmd-q gets handled by SDL/etc. - // implicitly. - if (!repeat_press && keysym->sym == SDLK_q && (keysym->mod & KMOD_CTRL)) { - g_base->ui->ConfirmQuit(); + // Control-Q quits. On Mac, the usual Cmd-Q gets handled + // by the app-adapter implicitly. + if (!repeat_press && keysym.sym == SDLK_q && (keysym.mod & KMOD_CTRL)) { + g_base->QuitApp(true); return; } // Let the console intercept stuff if it wants at this point. if (auto* console = g_base->ui->dev_console()) { - if (console->HandleKeyPress(keysym)) { + 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))) { + if (!repeat_press && keysym.sym == SDLK_v + && ((keysym.mod & KMOD_CTRL) || (keysym.mod & KMOD_GUI))) { g_base->ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste)); return; } @@ -989,7 +1034,7 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { // None of the following stuff accepts key repeats. if (!repeat_press) { - switch (keysym->sym) { + switch (keysym.sym) { // Menu button on android/etc. pops up the menu. case SDLK_MENU: { if (!g_base->ui->MainMenuVisible()) { @@ -1001,14 +1046,14 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { case SDLK_EQUALS: case SDLK_PLUS: - if (keysym->mod & KMOD_CTRL) { + if (keysym.mod & KMOD_CTRL) { g_base->app_mode()->ChangeGameSpeed(1); handled = true; } break; case SDLK_MINUS: - if (keysym->mod & KMOD_CTRL) { + if (keysym.mod & KMOD_CTRL) { g_base->app_mode()->ChangeGameSpeed(-1); handled = true; } @@ -1073,12 +1118,12 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { // If we haven't claimed it, pass it along as potential player/widget input. if (!handled) { if (keyboard_input_) { - keyboard_input_->HandleKey(keysym, repeat_press, true); + keyboard_input_->HandleKey(&keysym, repeat_press, true); } } } -void Input::HandleKeyRelease(const SDL_Keysym* keysym) { +void Input::HandleKeyRelease_(const SDL_Keysym& keysym) { assert(g_base); assert(g_base->InLogicThread()); @@ -1089,32 +1134,32 @@ 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_.count(keysym.sym) == 0) { return; } // If someone is capturing these events, give them a crack at it. if (keyboard_input_capture_release_) { - (keyboard_input_capture_release_(*keysym)); + (keyboard_input_capture_release_(keysym)); } // Keep track of mod key states for things like manual camera moves. For // individual key presses ideally we should instead use modifiers bundled // with the key press events. - UpdateModKeyStates(keysym, false); + UpdateModKeyStates_(&keysym, false); - keys_held_.erase(keysym->sym); + keys_held_.erase(keysym.sym); if (g_base->ui->dev_console() != nullptr) { - g_base->ui->dev_console()->HandleKeyRelease(keysym); + g_base->ui->dev_console()->HandleKeyRelease(&keysym); } if (keyboard_input_) { - keyboard_input_->HandleKey(keysym, false, false); + keyboard_input_->HandleKey(&keysym, false, false); } } -void Input::UpdateModKeyStates(const SDL_Keysym* keysym, bool press) { +void Input::UpdateModKeyStates_(const SDL_Keysym* keysym, bool press) { switch (keysym->sym) { case SDLK_LCTRL: case SDLK_RCTRL: { @@ -1145,10 +1190,10 @@ void Input::UpdateModKeyStates(const SDL_Keysym* keysym, bool press) { void Input::PushMouseScrollEvent(const Vector2f& amount) { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall( - [this, amount] { HandleMouseScroll(amount); }); + [this, amount] { HandleMouseScroll_(amount); }); } -void Input::HandleMouseScroll(const Vector2f& amount) { +void Input::HandleMouseScroll_(const Vector2f& amount) { assert(g_base->InLogicThread()); // If input is locked, allow it to mark us active but nothing more. @@ -1181,11 +1226,11 @@ void Input::PushSmoothMouseScrollEvent(const Vector2f& velocity, bool momentum) { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall([this, velocity, momentum] { - HandleSmoothMouseScroll(velocity, momentum); + HandleSmoothMouseScroll_(velocity, momentum); }); } -void Input::HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum) { +void Input::HandleSmoothMouseScroll_(const Vector2f& velocity, bool momentum) { assert(g_base->InLogicThread()); // If input is locked, allow it to mark us active but nothing more. @@ -1216,10 +1261,10 @@ void Input::HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum) { void Input::PushMouseMotionEvent(const Vector2f& position) { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall( - [this, position] { HandleMouseMotion(position); }); + [this, position] { HandleMouseMotion_(position); }); } -void Input::HandleMouseMotion(const Vector2f& position) { +void Input::HandleMouseMotion_(const Vector2f& position) { assert(g_base); assert(g_base->InLogicThread()); @@ -1266,10 +1311,10 @@ void Input::HandleMouseMotion(const Vector2f& position) { void Input::PushMouseDownEvent(int button, const Vector2f& position) { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall( - [this, button, position] { HandleMouseDown(button, position); }); + [this, button, position] { HandleMouseDown_(button, position); }); } -void Input::HandleMouseDown(int button, const Vector2f& position) { +void Input::HandleMouseDown_(int button, const Vector2f& position) { assert(g_base); assert(g_base->InLogicThread()); @@ -1330,10 +1375,10 @@ void Input::HandleMouseDown(int button, const Vector2f& position) { void Input::PushMouseUpEvent(int button, const Vector2f& position) { assert(g_base->logic->event_loop()); g_base->logic->event_loop()->PushCall( - [this, button, position] { HandleMouseUp(button, position); }); + [this, button, position] { HandleMouseUp_(button, position); }); } -void Input::HandleMouseUp(int button, const Vector2f& position) { +void Input::HandleMouseUp_(int button, const Vector2f& position) { assert(g_base->InLogicThread()); MarkInputActive(); @@ -1373,10 +1418,10 @@ void Input::HandleMouseUp(int button, const Vector2f& position) { void Input::PushTouchEvent(const TouchEvent& e) { assert(g_base->logic->event_loop()); - g_base->logic->event_loop()->PushCall([e, this] { HandleTouchEvent(e); }); + g_base->logic->event_loop()->PushCall([e, this] { HandleTouchEvent_(e); }); } -void Input::HandleTouchEvent(const TouchEvent& e) { +void Input::HandleTouchEvent_(const TouchEvent& e) { assert(g_base->InLogicThread()); assert(g_base->graphics); @@ -1422,11 +1467,11 @@ void Input::HandleTouchEvent(const TouchEvent& e) { // mouse events which covers most UI stuff. if (e.type == TouchEvent::Type::kDown && single_touch_ == nullptr) { single_touch_ = e.touch; - HandleMouseDown(SDL_BUTTON_LEFT, Vector2f(e.x, e.y)); + HandleMouseDown_(SDL_BUTTON_LEFT, Vector2f(e.x, e.y)); } if (e.type == TouchEvent::Type::kMoved && e.touch == single_touch_) { - HandleMouseMotion(Vector2f(e.x, e.y)); + HandleMouseMotion_(Vector2f(e.x, e.y)); } // Currently just applying touch-cancel the same as touch-up here; @@ -1434,7 +1479,7 @@ void Input::HandleTouchEvent(const TouchEvent& e) { if ((e.type == TouchEvent::Type::kUp || e.type == TouchEvent::Type::kCanceled) && (e.touch == single_touch_ || e.overall)) { single_touch_ = nullptr; - HandleMouseUp(SDL_BUTTON_LEFT, Vector2f(e.x, e.y)); + HandleMouseUp_(SDL_BUTTON_LEFT, Vector2f(e.x, e.y)); } // If we've got a touch input device, forward events along to it. @@ -1460,7 +1505,7 @@ void Input::ResetKeyboardHeldKeys() { SDL_Keysym k; memset(&k, 0, sizeof(k)); k.sym = (SDL_Keycode)(*keys_held_.begin()); - HandleKeyRelease(&k); + HandleKeyRelease_(k); } } } diff --git a/src/ballistica/base/input/input.h b/src/ballistica/base/input/input.h index 407439f6..b38aea5c 100644 --- a/src/ballistica/base/input/input.h +++ b/src/ballistica/base/input/input.h @@ -119,6 +119,8 @@ class Input { void CreateTouchInput(); void PushTextInputEvent(const std::string& text); + void PushKeyPressEventSimple(int keycode); + void PushKeyReleaseEventSimple(int keycode); void PushKeyPressEvent(const SDL_Keysym& keysym); void PushKeyReleaseEvent(const SDL_Keysym& keysym); void PushMouseDownEvent(int button, const Vector2f& position); @@ -152,27 +154,30 @@ class Input { void RebuildInputDeviceDelegates(); private: - void UpdateInputDeviceCounts(); - auto GetNewNumberedIdentifier(const std::string& name, - const std::string& identifier) -> int; - void UpdateEnabledControllerSubsystems(); - void AnnounceConnects(); - void AnnounceDisconnects(); - void HandleKeyPress(const SDL_Keysym* keysym); - void HandleKeyRelease(const SDL_Keysym* keysym); - void HandleMouseMotion(const Vector2f& position); - void HandleMouseDown(int button, const Vector2f& position); - void HandleMouseUp(int button, const Vector2f& position); - void HandleMouseScroll(const Vector2f& amount); - void HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum); - void HandleJoystickEvent(const SDL_Event& event, InputDevice* input_device); - void HandleTouchEvent(const TouchEvent& e); - void ShowStandardInputDeviceConnectedMessage(InputDevice* j); - void ShowStandardInputDeviceDisconnectedMessage(InputDevice* j); - void PrintLockLabels(); - void UpdateModKeyStates(const SDL_Keysym* keysym, bool press); - void CreateKeyboardInputDevices(); - void DestroyKeyboardInputDevices(); + void UpdateInputDeviceCounts_(); + auto GetNewNumberedIdentifier_(const std::string& name, + const std::string& identifier) -> int; + void UpdateEnabledControllerSubsystems_(); + void AnnounceConnects_(); + void AnnounceDisconnects_(); + void HandleKeyPressSimple_(int keycode); + void HandleKeyReleaseSimple_(int keycode); + void HandleKeyPress_(const SDL_Keysym& keysym); + void HandleKeyRelease_(const SDL_Keysym& keysym); + void HandleMouseMotion_(const Vector2f& position); + void HandleMouseDown_(int button, const Vector2f& position); + void HandleMouseUp_(int button, const Vector2f& position); + void HandleMouseScroll_(const Vector2f& amount); + void HandleSmoothMouseScroll_(const Vector2f& velocity, bool momentum); + void HandleJoystickEvent_(const SDL_Event& event, InputDevice* input_device); + void HandleTouchEvent_(const TouchEvent& e); + void ShowStandardInputDeviceConnectedMessage_(InputDevice* j); + void ShowStandardInputDeviceDisconnectedMessage_(InputDevice* j); + void PrintLockLabels_(); + void UpdateModKeyStates_(const SDL_Keysym* keysym, bool press); + void CreateKeyboardInputDevices_(); + void DestroyKeyboardInputDevices_(); + void AddFakeMods_(SDL_Keysym* sym); HandleKeyPressCall* keyboard_input_capture_press_{}; HandleKeyReleaseCall* keyboard_input_capture_release_{}; diff --git a/src/ballistica/base/logic/logic.cc b/src/ballistica/base/logic/logic.cc index 44a5a0d9..60563458 100644 --- a/src/ballistica/base/logic/logic.cc +++ b/src/ballistica/base/logic/logic.cc @@ -29,7 +29,7 @@ Logic::Logic() : display_timers_(new TimerList()) { void Logic::OnMainThreadStartApp() { // Spin up our logic thread and sit and wait for it to init. event_loop_ = new EventLoop(EventLoopID::kLogic); - g_core->pausable_event_loops.push_back(event_loop_); + g_core->suspendable_event_loops.push_back(event_loop_); event_loop_->PushCallSynchronous([this] { OnAppStart(); }); } @@ -47,9 +47,9 @@ void Logic::OnAppStart() { event_loop_->SetAcquiresPythonGIL(); // Stay informed when our event loop is pausing/unpausing. - event_loop_->AddPauseCallback( + event_loop_->AddSuspendCallback( NewLambdaRunnableUnmanaged([this] { OnAppPause(); })); - event_loop_->AddResumeCallback( + event_loop_->AddUnsuspendCallback( NewLambdaRunnableUnmanaged([this] { OnAppResume(); })); // Running in a specific order here and should try to stick to it in @@ -454,7 +454,7 @@ void Logic::UpdateDisplayTimeForFrameDraw() { // look like the game is slowing down or speeding up. // Flip debug-log-display-time on to debug this stuff. - // Things to look for:' + // Things to look for: // - 'final' value should mostly stay constant. // - 'final' value should not be *too* far from 'current'. // - 'current' should mostly show '(avg)'; rarely '(sample)'. @@ -484,51 +484,16 @@ void Logic::UpdateDisplayTimeForFrameDraw() { (recent_display_time_increments_index_ + 1) % kDisplayTimeSampleCount; } - // It seems that when things get thrown off it is often due to a single - // rogue sample being unusually long and often the next one being - // unusually short. Let's try to filter out some of these cases by - // ignoring both the longest and shortest sample in our set. - // - // SCRATCH THAT; we want to gracefully handle cases where vsync combined - // with target-framerate might give us crazy sample times like 5, 10, 5, - // 10, etc. So we can't expect filtering a single sample to help. - // - // int max_index{}; - // int min_index{}; - // double max_val{recent_display_time_increments_[0]}; - // double min_val{recent_display_time_increments_[0]}; - // for (int i = 0; i < kDisplayTimeSampleCount; ++i) { - // auto val = recent_display_time_increments_[i]; - // if (val > max_val) { - // max_val = val; - // max_index = i; - // } - // if (val < min_val) { - // min_val = val; - // min_index = i; - // } - // } - double avg{}; - double min{}; - double max{}; - int count{}; + double min, max; + min = max = recent_display_time_increments_[0]; for (int i = 0; i < kDisplayTimeSampleCount; ++i) { - // if (i == min_index || i == max_index) { - // continue; - // } auto val = recent_display_time_increments_[i]; - if (count == 0) { - // We may have skipped first index(es) so need to do this here - // instead of initing min/max to first value. - min = max = val; - } avg += val; min = std::min(min, val); max = std::max(max, val); - count += 1; } - avg /= count; + avg /= kDisplayTimeSampleCount; double range = max - min; // If our range of recent increment values is somewhat large relative to @@ -541,19 +506,16 @@ void Logic::UpdateDisplayTimeForFrameDraw() { // the lower chaos will be and thus the more the engine will stick to // smoothed values. A good way to determine if this value is too high is // to launch the game and watch the menu animation. If it visibly speeds - // up or slows down in the moment after launch, it means the value is - // too high and the engine is sticking with smoothed values when it - // should instead be reacting immediately. So basically this value - // should be as high as possible while avoiding that look. + // up or slows down in a 'rubber band' looking way the moment after + // launch, it means the value is too high and the engine is sticking + // with smoothed values when it should instead be reacting immediately. + // So basically this value should be as high as possible while avoiding + // that look. double chaos_fudge{1.25}; double chaos = (range / avg) / chaos_fudge; bool use_avg = chaos < 1.0; auto used = use_avg ? avg : this_increment; - // double chaos = 0.0; - // bool use_avg = true; - // auto used = use_avg ? avg : this_increment; - // Lastly use this 'used' value to update our actual increment - our // increment moves only if 'used' value gets farther than [trail_buffer] // from it. So ideally it will sit in the middle of the smoothed value @@ -595,6 +557,7 @@ void Logic::UpdateDisplayTimeForFrameDraw() { Log(LogLevel::kDebug, buffer); } } + // Lastly, apply our updated increment value to our time. display_time_ += display_time_increment_; diff --git a/src/ballistica/base/networking/network_writer.cc b/src/ballistica/base/networking/network_writer.cc index 51f5719b..6c4704c8 100644 --- a/src/ballistica/base/networking/network_writer.cc +++ b/src/ballistica/base/networking/network_writer.cc @@ -14,7 +14,7 @@ NetworkWriter::NetworkWriter() {} void NetworkWriter::OnMainThreadStartApp() { // Spin up our thread. event_loop_ = new EventLoop(EventLoopID::kNetworkWrite); - g_core->pausable_event_loops.push_back(event_loop_); + g_core->suspendable_event_loops.push_back(event_loop_); } void NetworkWriter::PushSendToCall(const std::vector& msg, diff --git a/src/ballistica/base/networking/networking.cc b/src/ballistica/base/networking/networking.cc index c93d9d95..cf672073 100644 --- a/src/ballistica/base/networking/networking.cc +++ b/src/ballistica/base/networking/networking.cc @@ -5,7 +5,6 @@ #include "ballistica/base/app_adapter/app_adapter.h" #include "ballistica/base/networking/network_reader.h" #include "ballistica/base/support/app_config.h" -#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/networking/sockaddr.h" namespace ballistica::base { diff --git a/src/ballistica/base/platform/apple/base_platform_apple.cc b/src/ballistica/base/platform/apple/base_platform_apple.cc index 1b518e15..ef83cd71 100644 --- a/src/ballistica/base/platform/apple/base_platform_apple.cc +++ b/src/ballistica/base/platform/apple/base_platform_apple.cc @@ -62,14 +62,6 @@ void BasePlatformApple::DoOpenURL(const std::string& url) { #endif } -void BasePlatformApple::TerminateApp() { -#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD - AppleUtils::TerminateApp(); -#else - BasePlatform::TerminateApp(); -#endif -} - // void BasePlatformApple::SetHardwareCursorVisible(bool visible) { // // Set our nifty custom hardware cursor on mac; // // otherwise fall back to default. diff --git a/src/ballistica/base/platform/apple/base_platform_apple.h b/src/ballistica/base/platform/apple/base_platform_apple.h index 852e9374..50cf78cc 100644 --- a/src/ballistica/base/platform/apple/base_platform_apple.h +++ b/src/ballistica/base/platform/apple/base_platform_apple.h @@ -16,12 +16,9 @@ class BasePlatformApple : public BasePlatform { void RestorePurchases() override; void PurchaseAck(const std::string& purchase, const std::string& order_id) override; - void TerminateApp() override; void DoOpenURL(const std::string& url) override; - // void SetHardwareCursorVisible(bool visible) override; - private: }; diff --git a/src/ballistica/base/platform/base_platform.cc b/src/ballistica/base/platform/base_platform.cc index 97bef111..9be958c2 100644 --- a/src/ballistica/base/platform/base_platform.cc +++ b/src/ballistica/base/platform/base_platform.cc @@ -245,20 +245,6 @@ void BasePlatform::SetupInterruptHandling() { #endif } -void BasePlatform::GetCursorPosition(float* x, float* y) { - assert(x && y); - - // By default, just use our latest event-delivered cursor position; - // this should work everywhere though perhaps might not be most optimal. - if (g_base->input == nullptr) { - *x = 0.0f; - *y = 0.0f; - return; - } - *x = g_base->input->cursor_pos_x(); - *y = g_base->input->cursor_pos_y(); -} - void BasePlatform::OnMainThreadStartAppComplete() {} void BasePlatform::OnAppStart() { assert(g_base->InLogicThread()); } @@ -269,13 +255,6 @@ void BasePlatform::OnAppShutdownComplete() { assert(g_base->InLogicThread()); } void BasePlatform::OnScreenSizeChange() { assert(g_base->InLogicThread()); } void BasePlatform::DoApplyAppConfig() { assert(g_base->InLogicThread()); } -void BasePlatform::TerminateApp() { exit(g_base->return_value()); } - -auto BasePlatform::CanSoftQuit() -> bool { return false; } -auto BasePlatform::CanBackQuit() -> bool { return false; } -void BasePlatform::DoBackQuit() {} -void BasePlatform::DoSoftQuit() {} - auto BasePlatform::HaveStringEditor() -> bool { return false; } void BasePlatform::InvokeStringEditor(PyObject* string_edit_adapter) { @@ -322,11 +301,4 @@ void BasePlatform::DoInvokeStringEditor(const std::string& title, Log(LogLevel::kError, "FIXME: DoInvokeStringEditor() unimplemented"); } -void BasePlatform::SetHardwareCursorVisible(bool visible) { -// FIXME: Move/forward this to AppAdapter. -#if BA_SDL_BUILD - SDL_ShowCursor(visible ? SDL_ENABLE : SDL_DISABLE); -#endif -} - } // namespace ballistica::base diff --git a/src/ballistica/base/platform/base_platform.h b/src/ballistica/base/platform/base_platform.h index 9eacccb0..82f38679 100644 --- a/src/ballistica/base/platform/base_platform.h +++ b/src/ballistica/base/platform/base_platform.h @@ -33,36 +33,6 @@ class BasePlatform { virtual void OnScreenSizeChange(); virtual void DoApplyAppConfig(); - /// Return whether this platform supports soft-quit. A soft quit is - /// when the app is reset/backgrounded/etc. but remains running in case - /// needed again. Generally this is the behavior on mobile apps. - virtual auto CanSoftQuit() -> bool; - - /// Implement soft-quit behavior. Will always be called in the logic - /// thread. Make sure to also override CanBackQuit to reflect this being - /// present. Note that when quitting the app yourself, you should use - /// g_base->QuitApp(); do not call this directly. - virtual void DoSoftQuit(); - - /// Return whether this platform supports back-quit. A back quit is a - /// variation of soft-quit generally triggered by a back button, which may - /// give different results in the OS. For example on Android this may - /// result in jumping back to the previous Android activity instead of - /// just ending the current one and dumping to the home screen as normal - /// soft quit might do. - virtual auto CanBackQuit() -> bool; - - /// Implement back-quit behavior. Will always be called in the logic - /// thread. Make sure to also override CanBackQuit to reflect this being - /// present. Note that when quitting the app yourself, you should use - /// g_base->QuitApp(); do not call this directly. - virtual void DoBackQuit(); - - /// Terminate the app. This can be immediate or by posting some high - /// level event. There should be nothing left to do in the engine at - /// this point. - virtual void TerminateApp(); - #pragma mark IN APP PURCHASES -------------------------------------------------- void Purchase(const std::string& item); @@ -116,12 +86,6 @@ class BasePlatform { /// Open the provided URL in a browser or whatnot. void OpenURL(const std::string& url); - /// Get the most up-to-date cursor position. - void GetCursorPosition(float* x, float* y); - - /// Show/hide the hardware cursor. - virtual void SetHardwareCursorVisible(bool visible); - /// Should be called by platform StringEditor to apply a value. /// Must be called in the logic thread. void StringEditorApply(const std::string& val); diff --git a/src/ballistica/base/python/base_python.cc b/src/ballistica/base/python/base_python.cc index 1e579f60..882bd42d 100644 --- a/src/ballistica/base/python/base_python.cc +++ b/src/ballistica/base/python/base_python.cc @@ -103,20 +103,20 @@ void BasePython::SoftImportClassic() { } } -void BasePython::SoftImportUIV1() { - // To keep our init order clean, we want to root out any attempted uses - // of this before _babase/babase has been fully imported. - assert(g_base); - assert(g_base->IsBaseCompletelyImported()); +// void BasePython::SoftImportUIV1() { +// // To keep our init order clean, we want to root out any attempted uses +// // of this before _babase/babase has been fully imported. +// assert(g_base); +// assert(g_base->IsBaseCompletelyImported()); - auto gil{Python::ScopedInterpreterLock()}; - auto result = PythonRef::StolenSoft(PyImport_ImportModule("_bauiv1")); - if (!result.Exists()) { - // Ignore any errors here for now. All that will matter is whether plus - // gave us its interface. - PyErr_Clear(); - } -} +// auto gil{Python::ScopedInterpreterLock()}; +// auto result = PythonRef::StolenSoft(PyImport_ImportModule("_bauiv1")); +// if (!result.Exists()) { +// // Ignore any errors here for now. All that will matter is whether plus +// // gave us its interface. +// PyErr_Clear(); +// } +// } void BasePython::ReadConfig() { auto gil{Python::ScopedInterpreterLock()}; @@ -151,12 +151,12 @@ void BasePython::OnAppStart() { void BasePython::OnAppPause() { assert(g_base->InLogicThread()); - objs().Get(BasePython::ObjID::kAppOnNativePauseCall).Call(); + objs().Get(BasePython::ObjID::kAppOnNativeSuspendCall).Call(); } void BasePython::OnAppResume() { assert(g_base->InLogicThread()); - objs().Get(BasePython::ObjID::kAppOnNativeResumeCall).Call(); + objs().Get(BasePython::ObjID::kAppOnNativeUnsuspendCall).Call(); } void BasePython::OnAppShutdown() { @@ -467,6 +467,10 @@ auto BasePython::GetPyEnum_TimeType(PyObject* obj) -> TimeType { return GetPyEnum(BasePython::ObjID::kTimeTypeClass, obj); } +auto BasePython::GetPyEnum_QuitType(PyObject* obj) -> QuitType { + return GetPyEnum(BasePython::ObjID::kQuitTypeClass, obj); +} + auto BasePython::GetPyEnum_TimeFormat(PyObject* obj) -> TimeFormat { return GetPyEnum(BasePython::ObjID::kTimeFormatClass, obj); } @@ -479,6 +483,14 @@ auto BasePython::GetPyEnum_InputType(PyObject* obj) -> InputType { return GetPyEnum(BasePython::ObjID::kInputTypeClass, obj); } +// TODO(ericf): Make this a template. +auto BasePython::PyQuitType(QuitType val) -> PythonRef { + auto args = PythonRef::Stolen(Py_BuildValue("(d)", static_cast(val))); + auto out = objs().Get(BasePython::ObjID::kQuitTypeClass).Call(args); + BA_PRECONDITION(out.Exists()); + return out; +} + auto BasePython::GetResource(const char* key, const char* fallback_resource, const char* fallback_value) -> std::string { assert(Python::HaveGIL()); diff --git a/src/ballistica/base/python/base_python.h b/src/ballistica/base/python/base_python.h index 5764fd79..7b807b1b 100644 --- a/src/ballistica/base/python/base_python.h +++ b/src/ballistica/base/python/base_python.h @@ -70,8 +70,8 @@ class BasePython { kUIRemotePressCall, kRemoveInGameAdsMessageCall, kAppOnNativeStartCall, - kAppOnNativePauseCall, - kAppOnNativeResumeCall, + kAppOnNativeSuspendCall, + kAppOnNativeUnsuspendCall, kAppOnNativeShutdownCall, kAppOnNativeShutdownCompleteCall, kQuitCall, @@ -88,6 +88,7 @@ class BasePython { kSessionNotFoundError, kTimeFormatClass, kTimeTypeClass, + kQuitTypeClass, kInputTypeClass, kPermissionClass, kSpecialCharClass, @@ -151,6 +152,9 @@ class BasePython { static auto GetPyEnum_TimeFormat(PyObject* obj) -> TimeFormat; static auto IsPyEnum_InputType(PyObject* obj) -> bool; static auto GetPyEnum_InputType(PyObject* obj) -> InputType; + static auto GetPyEnum_QuitType(PyObject* obj) -> QuitType; + + auto PyQuitType(QuitType val) -> PythonRef; auto CanPyStringEditAdapterBeReplaced(PyObject* o) -> bool; @@ -167,7 +171,7 @@ class BasePython { void SoftImportPlus(); void SoftImportClassic(); - void SoftImportUIV1(); + // void SoftImportUIV1(); private: std::set do_once_locations_; diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index f55547ac..4f040259 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -6,13 +6,11 @@ #include "ballistica/base/app_mode/app_mode_empty.h" #include "ballistica/base/graphics/graphics_server.h" #include "ballistica/base/logic/logic.h" -#include "ballistica/base/platform/base_platform.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/core/platform/core_platform.h" #include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/foundation/logging.h" #include "ballistica/shared/python/python.h" @@ -511,26 +509,20 @@ static auto PyQuit(PyObject* self, PyObject* args, PyObject* keywds) BA_PYTHON_TRY; BA_PRECONDITION(g_base->IsAppStarted()); - static const char* kwlist[] = {"soft", "back", nullptr}; - int soft = 0; - int back = 0; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "|pp", - const_cast(kwlist), &soft, &back)) { + static const char* kwlist[] = {"confirm", "quit_type", nullptr}; + QuitType quit_type = QuitType::kSoft; + PyObject* quit_type_obj{Py_None}; + int confirm{}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|pO", + const_cast(kwlist), &confirm, + &quit_type_obj)) { return nullptr; } - QuitType quit_type{}; - if (back) { - if (!soft) { - Log(LogLevel::kWarning, - "Got soft=False back=True in quit() which is ambiguous."); - } - quit_type = QuitType::kBack; - } else if (soft) { - quit_type = QuitType::kSoft; - } else { - quit_type = QuitType::kHard; + if (quit_type_obj != Py_None) { + quit_type = BasePython::GetPyEnum_QuitType(quit_type_obj); } - g_base->QuitApp(quit_type); + + g_base->QuitApp(confirm, quit_type); Py_RETURN_NONE; BA_PYTHON_CATCH; } @@ -540,18 +532,17 @@ static PyMethodDef PyQuitDef = { (PyCFunction)PyQuit, // method METH_VARARGS | METH_KEYWORDS, // flags - "quit(soft: bool = True, back: bool = False) -> None\n" + "quit(confirm: bool = False,\n" + " quit_type: babase.QuitType = babase.QuitType.SOFT\n" + ") -> None\n" "\n" "Quit the app.\n" "\n" "Category: **General Utility Functions**\n" "\n" - "On platforms such as mobile, a 'soft' quit may background and/or reset\n" - "the app but keep it running. A 'back' quit is a special form of soft\n" - "quit that may trigger different behavior in the OS. On Android, for\n" - "example, a back-quit may simply jump to the previous Android activity,\n" - "while a regular soft quit may just exit the current activity and dump\n" - "the user at their home screen."}; + "If 'confirm' is True, a confirm dialog will be presented if conditions\n" + "allow; otherwise the quit will still be immediate.\n" + "See docs for babase.QuitType for explanations of its behavior."}; // ----------------------------- apply_config ---------------------------------- diff --git a/src/ballistica/base/python/methods/python_methods_misc.cc b/src/ballistica/base/python/methods/python_methods_misc.cc index ff46fa8b..2f968b12 100644 --- a/src/ballistica/base/python/methods/python_methods_misc.cc +++ b/src/ballistica/base/python/methods/python_methods_misc.cc @@ -5,7 +5,6 @@ #include #include -#include "ballistica/base/assets/assets.h" #include "ballistica/base/assets/sound_asset.h" #include "ballistica/base/input/input.h" #include "ballistica/base/platform/base_platform.h" diff --git a/src/ballistica/base/support/stdio_console.cc b/src/ballistica/base/support/stdio_console.cc index ec674538..15263f3e 100644 --- a/src/ballistica/base/support/stdio_console.cc +++ b/src/ballistica/base/support/stdio_console.cc @@ -26,7 +26,7 @@ void StdioConsole::StartInMainThread() { // Spin up our thread. event_loop_ = new EventLoop(EventLoopID::kStdin); - g_core->pausable_event_loops.push_back(event_loop_); + g_core->suspendable_event_loops.push_back(event_loop_); // Tell our thread to start reading. event_loop()->PushCall([this] { diff --git a/src/ballistica/base/ui/dev_console.cc b/src/ballistica/base/ui/dev_console.cc index 38e385a9..8b7dc05a 100644 --- a/src/ballistica/base/ui/dev_console.cc +++ b/src/ballistica/base/ui/dev_console.cc @@ -11,12 +11,8 @@ #include "ballistica/base/logic/logic.h" #include "ballistica/base/platform/base_platform.h" #include "ballistica/base/python/base_python.h" -#include "ballistica/base/support/context.h" #include "ballistica/base/ui/ui.h" -#include "ballistica/core/core.h" -#include "ballistica/core/platform/support/min_sdl.h" #include "ballistica/shared/foundation/event_loop.h" -#include "ballistica/shared/foundation/macros.h" #include "ballistica/shared/generic/utils.h" #include "ballistica/shared/python/python_command.h" #include "ballistica/shared/python/python_sys.h" @@ -102,20 +98,6 @@ static void DrawRect(RenderPass* pass, Mesh* mesh, float bottom, float x, c.Translate(x, y + bottom, kDevConsoleZDepth); c.DrawMesh(mesh); } - // Draw text. - // { - // auto xf = c.ScopedTransform(); - // c.Translate(x + width * 0.5f, y + bottom + height * 0.5f, - // kDevConsoleZDepth); - // c.Scale(tscale, tscale, 1.0f); - // int elem_count = tgrp->GetElementCount(); - // c.SetColor(fgcolor.x, fgcolor.y, fgcolor.z, 1.0f); - // c.SetFlatness(1.0f); - // for (int e = 0; e < elem_count; e++) { - // c.SetTexture(tgrp->GetElementTexture(e)); - // c.DrawMesh(tgrp->GetElementMesh(e)); - // } - // } } static void DrawText(RenderPass* pass, TextGroup* tgrp, float tscale, diff --git a/src/ballistica/base/ui/ui.cc b/src/ballistica/base/ui/ui.cc index 66bb00de..0b774b9c 100644 --- a/src/ballistica/base/ui/ui.cc +++ b/src/ballistica/base/ui/ui.cc @@ -7,12 +7,10 @@ #include "ballistica/base/input/device/keyboard_input.h" #include "ballistica/base/input/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/support/ui_v1_soft.h" #include "ballistica/base/ui/dev_console.h" +#include "ballistica/base/ui/ui_delegate.h" #include "ballistica/shared/foundation/event_loop.h" -#include "ballistica/shared/foundation/inline.h" #include "ballistica/shared/generic/utils.h" namespace ballistica::base { @@ -59,9 +57,11 @@ void UI::StepDisplayTime() { void UI::OnAppStart() { assert(g_base->InLogicThread()); - if (g_base->HaveUIV1()) { - g_base->ui_v1()->OnAppStart(); - } + // if (auto* ui_delegate = g_base->ui->delegate()) { + // printf("HAVE DEL %d\n", + // static_cast(g_base->ui->delegate() != nullptr)); + // g_base->ui_v1()->OnAppStart(); + // } // Make sure user knows when forced-ui-scale is enabled. if (force_scale_) { @@ -92,36 +92,36 @@ void UI::OnAppShutdownComplete() { assert(g_base->InLogicThread()); } void UI::DoApplyAppConfig() { assert(g_base->InLogicThread()); - if (g_base->HaveUIV1()) { - g_base->ui_v1()->DoApplyAppConfig(); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->DoApplyAppConfig(); } show_dev_console_button_ = g_base->app_config->Resolve(AppConfig::BoolID::kShowDevConsoleButton); } auto UI::MainMenuVisible() const -> bool { - if (g_base->HaveUIV1()) { - return g_base->ui_v1()->MainMenuVisible(); + if (auto* ui_delegate = g_base->ui->delegate()) { + return ui_delegate->MainMenuVisible(); } return false; } auto UI::PartyIconVisible() -> bool { - if (g_base->HaveUIV1()) { - return g_base->ui_v1()->PartyIconVisible(); + if (auto* ui_delegate = g_base->ui->delegate()) { + return ui_delegate->PartyIconVisible(); } return false; } void UI::ActivatePartyIcon() { - if (g_base->HaveUIV1()) { - g_base->ui_v1()->ActivatePartyIcon(); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->ActivatePartyIcon(); } } auto UI::PartyWindowOpen() -> bool { - if (g_base->HaveUIV1()) { - return g_base->ui_v1()->PartyWindowOpen(); + if (auto* ui_delegate = g_base->ui->delegate()) { + return ui_delegate->PartyWindowOpen(); } return false; } @@ -145,8 +145,10 @@ auto UI::HandleMouseDown(int button, float x, float y, bool double_click) handled = dev_console_->HandleMouseDown(button, x, y); } - if (!handled && g_base->HaveUIV1()) { - handled = g_base->ui_v1()->HandleLegacyRootUIMouseDown(x, y); + if (!handled) { + if (auto* ui_delegate = g_base->ui->delegate()) { + handled = ui_delegate->HandleLegacyRootUIMouseDown(x, y); + } } if (!handled) { @@ -176,8 +178,8 @@ void UI::HandleMouseUp(int button, float x, float y) { } } - if (g_base->HaveUIV1()) { - g_base->ui_v1()->HandleLegacyRootUIMouseUp(x, y); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->HandleLegacyRootUIMouseUp(x, y); } } @@ -205,8 +207,8 @@ void UI::HandleMouseMotion(float x, float y) { SendWidgetMessage( WidgetMessage(WidgetMessage::Type::kMouseMove, nullptr, x, y)); - if (g_base->HaveUIV1()) { - g_base->ui_v1()->HandleLegacyRootUIMouseMotion(x, y); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->HandleLegacyRootUIMouseMotion(x, y); } } @@ -232,8 +234,8 @@ void UI::PushMainMenuPressCall(InputDevice* device) { void UI::MainMenuPress_(InputDevice* device) { assert(g_base->InLogicThread()); - if (g_base->HaveUIV1()) { - g_base->ui_v1()->DoHandleDeviceMenuPress(device); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->DoHandleDeviceMenuPress(device); } else { Log(LogLevel::kWarning, "UI::MainMenuPress called without ui_v1 present; unexpected."); @@ -250,8 +252,8 @@ void UI::SetUIInputDevice(InputDevice* input_device) { } void UI::Reset() { - if (g_base->HaveUIV1()) { - g_base->ui_v1()->Reset(); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->Reset(); } } @@ -267,21 +269,21 @@ auto UI::ShouldShowButtonShortcuts() const -> bool { } auto UI::SendWidgetMessage(const WidgetMessage& m) -> int { - if (g_base->HaveUIV1()) { - return g_base->ui_v1()->SendWidgetMessage(m); + if (auto* ui_delegate = g_base->ui->delegate()) { + return ui_delegate->SendWidgetMessage(m); } return false; } void UI::OnScreenSizeChange() { - if (g_base->HaveUIV1()) { - g_base->ui_v1()->OnScreenSizeChange(); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->OnScreenSizeChange(); } } void UI::LanguageChanged() { - if (g_base->HaveUIV1()) { - g_base->ui_v1()->OnLanguageChange(); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->OnLanguageChange(); } } @@ -313,7 +315,9 @@ auto UI::GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget* { // events are received by that device for a long time, it is up for grabs // to the next device that requests it. - if (!g_base->HaveUIV1()) { + auto* ui_delegate = g_base->ui->delegate(); + + if (!ui_delegate) { ret_val = nullptr; } else if ((GetUIInputDevice() == nullptr) || (input_device == GetUIInputDevice()) @@ -325,7 +329,7 @@ auto UI::GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget* { // seconds ago to automatically own a newly created widget). last_input_device_use_time_ = time; ui_input_device_ = input_device; - ret_val = g_base->ui_v1()->GetRootWidget(); + ret_val = ui_delegate->GetRootWidget(); } else { // For rejected input devices, play error sounds sometimes so they know // they're not the chosen one. @@ -382,8 +386,8 @@ auto UI::GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget* { } void UI::Draw(FrameDef* frame_def) { - if (g_base->HaveUIV1()) { - g_base->ui_v1()->Draw(frame_def); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->Draw(frame_def); } } @@ -468,40 +472,43 @@ void UI::DrawDevConsoleButton_(FrameDef* frame_def) { } void UI::ShowURL(const std::string& url) { - if (g_base->HaveUIV1()) { - g_base->ui_v1()->DoShowURL(url); + if (auto* ui_delegate = g_base->ui->delegate()) { + ui_delegate->DoShowURL(url); } else { - Log(LogLevel::kWarning, - "UI::ShowURL called without g_ui_v1_soft present; unexpected."); + Log(LogLevel::kWarning, "UI::ShowURL called without ui_delegate present."); } } -void UI::ConfirmQuit() { - g_base->logic->event_loop()->PushCall([this] { - // If the in-app console is active, dismiss it. - if (dev_console_ != nullptr && dev_console_->IsActive()) { - dev_console_->Dismiss(); - } +void UI::set_ui_delegate(base::UIDelegateInterface* delegate) { + assert(g_base->InLogicThread()); - assert(g_base->InLogicThread()); - // If we're headless or we don't have ui-v1, just quit immediately; a - // confirm screen wouldn't work anyway. - if (g_core->HeadlessMode() || g_base->input->IsInputLocked() - || !g_base->HaveUIV1()) { - g_base->QuitApp(); - return; - } else { - ScopedSetContext ssc(nullptr); - g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kSwish)); - g_base->ui_v1()->DoQuitWindow(); + if (delegate == delegate_) { + return; + } - // If we have a keyboard, give it UI ownership. - InputDevice* keyboard = g_base->input->keyboard_input(); - if (keyboard) { - g_base->ui->SetUIInputDevice(keyboard); - } + try { + auto* old_delegate = delegate_; + delegate_ = nullptr; + if (old_delegate) { + old_delegate->OnDeactivate(); } - }); + delegate_ = delegate; + if (delegate_) { + delegate_->OnActivate(); + + // Inform them that a few things changed, since they might have since + // the last time they were active (these callbacks only go to the *active* + // ui delegate). + delegate_->DoApplyAppConfig(); + delegate_->OnScreenSizeChange(); + delegate_->OnLanguageChange(); + } + } catch (const Exception& exc) { + // Switching UI delegates is a big deal; don't try to continue if + // something goes wrong. + FatalError(std::string("Error setting native layer ui-delegate: ") + + exc.what()); + } } void UI::PushDevConsolePrintCall(const std::string& msg) { diff --git a/src/ballistica/base/ui/ui.h b/src/ballistica/base/ui/ui.h index 94c7eb30..02cecbb7 100644 --- a/src/ballistica/base/ui/ui.h +++ b/src/ballistica/base/ui/ui.h @@ -40,6 +40,8 @@ class UI { /// switching app-modes or when resetting things within an app mode. void Reset(); + void set_ui_delegate(base::UIDelegateInterface* delegate); + /// Pop up an in-app window to display a URL (NOT to open the URL in a /// browser). Can be called from any thread. void ShowURL(const std::string& url); @@ -47,7 +49,7 @@ class UI { /// High level call to request a quit; ideally with a confirmation ui. /// When a UI can't be shown, triggers an immediate shutdown. This can be /// called from any thread. - void ConfirmQuit(); + // void ConfirmQuit(); /// Return whether there is UI present in either the main or overlay /// stacks. Generally this implies the focus should be on the UI. @@ -111,12 +113,15 @@ class UI { void PushDevConsolePrintCall(const std::string& msg); + auto* delegate() const { return delegate_; } + private: void MainMenuPress_(InputDevice* device); auto DevConsoleButtonSize_() const -> float; auto InDevConsoleButton_(float x, float y) const -> bool; void DrawDevConsoleButton_(FrameDef* frame_def); + base::UIDelegateInterface* delegate_{}; DevConsole* dev_console_{}; std::string dev_console_startup_messages_; Object::WeakRef ui_input_device_; diff --git a/src/ballistica/base/support/ui_v1_soft.h b/src/ballistica/base/ui/ui_delegate.h similarity index 58% rename from src/ballistica/base/support/ui_v1_soft.h rename to src/ballistica/base/ui/ui_delegate.h index 62fce014..c3e89fe5 100644 --- a/src/ballistica/base/support/ui_v1_soft.h +++ b/src/ballistica/base/ui/ui_delegate.h @@ -1,7 +1,7 @@ // Released under the MIT License. See LICENSE for details. -#ifndef BALLISTICA_BASE_SUPPORT_UI_V1_SOFT_H_ -#define BALLISTICA_BASE_SUPPORT_UI_V1_SOFT_H_ +#ifndef BALLISTICA_BASE_UI_UI_DELEGATE_H_ +#define BALLISTICA_BASE_UI_UI_DELEGATE_H_ #include "ballistica/base/ui/ui.h" @@ -14,15 +14,21 @@ class Widget; namespace ballistica::base { -/// 'Soft' interface to the ui_v1 feature-set, managed by base. Feature-sets -/// listing ui_v1 as a soft requirement must limit their use of it to these -/// methods and should be prepared to handle the not-present case. -class UIV1SoftInterface { +class UIDelegateInterface { public: + /// Called when this delegate is becoming the active one. + virtual void OnActivate() = 0; + + /// Called when this delegate is resigning active status. + virtual void OnDeactivate() = 0; + + virtual void OnScreenSizeChange() = 0; + virtual void OnLanguageChange() = 0; + virtual void DoApplyAppConfig() = 0; + virtual void DoHandleDeviceMenuPress(base::InputDevice* device) = 0; virtual void DoShowURL(const std::string& url) = 0; - virtual void DoQuitWindow() = 0; - virtual auto NewRootUI() -> ui_v1::RootUI* = 0; + // virtual void DoQuitWindow() = 0; virtual auto MainMenuVisible() -> bool = 0; virtual auto PartyIconVisible() -> bool = 0; virtual void ActivatePartyIcon() = 0; @@ -30,16 +36,20 @@ class UIV1SoftInterface { virtual auto HandleLegacyRootUIMouseDown(float x, float y) -> bool = 0; virtual void HandleLegacyRootUIMouseUp(float x, float y) = 0; virtual void Draw(FrameDef* frame_def) = 0; - virtual void OnAppStart() = 0; virtual auto PartyWindowOpen() -> bool = 0; virtual void Reset() = 0; - virtual void OnScreenSizeChange() = 0; - virtual void OnLanguageChange() = 0; virtual auto GetRootWidget() -> ui_v1::Widget* = 0; virtual auto SendWidgetMessage(const WidgetMessage& m) -> int = 0; - virtual void DoApplyAppConfig() = 0; + + /// Should return true if this app mode can confirm quitting the app. + virtual auto HasQuitConfirmDialog() -> bool = 0; + + /// Will be called in the logic thread if HasQuitConfirmDialog() returns + /// true. Should present a quit confirmation dialog to the user and call + /// BaseFeatureSet::QuitApp() with the provided quit_type if confirmed. + virtual void ConfirmQuit(QuitType quit_type) = 0; }; } // namespace ballistica::base -#endif // BALLISTICA_BASE_SUPPORT_UI_V1_SOFT_H_ +#endif // BALLISTICA_BASE_UI_UI_DELEGATE_H_ diff --git a/src/ballistica/core/core.h b/src/ballistica/core/core.h index 0c0fba7f..b4057ebe 100644 --- a/src/ballistica/core/core.h +++ b/src/ballistica/core/core.h @@ -157,10 +157,10 @@ class CoreFeatureSet { // The following are misc values that should be migrated to applicable // subsystem classes. - bool threads_paused{}; + bool event_loops_suspended{}; bool workspaces_in_use{}; bool replay_open{}; - std::vector pausable_event_loops; + std::vector suspendable_event_loops; std::mutex v1_cloud_log_mutex; std::string v1_cloud_log; bool did_put_v1_cloud_log{}; @@ -168,7 +168,7 @@ class CoreFeatureSet { int master_server_source{}; int session_count{}; bool have_incentivized_ad{false}; - bool should_pause{}; + bool should_pause_active_game{}; bool reset_vr_orientation{}; bool user_ran_commands{}; std::thread::id main_thread_id{}; diff --git a/src/ballistica/core/support/base_soft.h b/src/ballistica/core/support/base_soft.h index 4c7a7ac8..49fc62c9 100644 --- a/src/ballistica/core/support/base_soft.h +++ b/src/ballistica/core/support/base_soft.h @@ -21,6 +21,7 @@ class BaseSoftInterface { virtual auto InAssetsThread() const -> bool = 0; virtual auto InLogicThread() const -> bool = 0; virtual auto InAudioThread() const -> bool = 0; + virtual auto InGraphicsContext() const -> bool = 0; virtual auto InBGDynamicsThread() const -> bool = 0; virtual auto InNetworkWriteThread() const -> bool = 0; virtual void PlusDirectSendV1CloudLogs(const std::string& prefix, diff --git a/src/ballistica/scene_v1/node/node.h b/src/ballistica/scene_v1/node/node.h index d42bb4b2..83078d3f 100644 --- a/src/ballistica/scene_v1/node/node.h +++ b/src/ballistica/scene_v1/node/node.h @@ -28,20 +28,26 @@ class Node : public Object { public: Node(Scene* scene, NodeType* node_type); ~Node() override; - auto id() const -> int64_t { - return id_; - } // Return the node's id in its scene. - virtual void Step() {} // Called for each step of the sim. - virtual void OnScreenSizeChange() {} // Called when screen size changes. - virtual void OnLanguageChange() {} // Called when the language changes. + + /// Return the node's id in its scene. + auto id() const -> int64_t { return id_; } + + /// Called for each step of the sim. + virtual void Step() {} + + /// Called when screen size changes. + virtual void OnScreenSizeChange() {} + + /// Called when the language changes. + virtual void OnLanguageChange() {} virtual void OnGraphicsQualityChanged(base::GraphicsQuality q) {} - // The node can rule out collisions between particular bodies using this. + /// The node can rule out collisions between particular bodies using this. virtual auto PreFilterCollision(RigidBody* b1, RigidBody* r2) -> bool { return true; } - // Pull a node type out of a buffer. + /// Pull a node type out of a buffer. static auto extract_node_message_type(const char** b) -> NodeMessageType { auto t = static_cast(**b); (*b) += 1; @@ -51,10 +57,10 @@ class Node : public Object { void ConnectAttribute(NodeAttributeUnbound* src_attr, Node* dst_node, NodeAttributeUnbound* dst_attr); - // Return an attribute by name. + /// Return an attribute by name. auto GetAttribute(const std::string& name) -> NodeAttribute; - // Return an attribute by index. + /// Return an attribute by index. auto GetAttribute(int index) -> NodeAttribute; void SetDelegate(PyObject* delegate_obj); @@ -62,37 +68,35 @@ class Node : public Object { auto NewPyRef() -> PyObject* { return GetPyRef(true); } auto BorrowPyRef() -> PyObject* { return GetPyRef(false); } - // Return the delegate, or nullptr if it doesn't have one - // (or if the delegate has since died). + /// Return the delegate, or nullptr if it doesn't have one (or if the + /// delegate has since died). auto GetDelegate() -> PyObject*; void AddNodeDeathAction(PyObject* call_obj); - // Add a node to auto-kill when this one dies. + /// Add a node to auto-kill when this one dies. void AddDependentNode(Node* node); - // Update birth times for all the node's parts. - // This should be done when teleporting or otherwise spawning at - // a new location. + /// Update birth times for all the node's parts. This should be done when + /// teleporting or otherwise spawning at a new location. void UpdatePartBirthTimes(); - // Retrieve an existing part from a node. + /// Retrieve an existing part from a node. auto GetPart(unsigned int id) -> Part* { assert(id < parts_.size()); return parts_[id]; } - // Used by RigidBodies when adding themselves to the part. + /// Used by RigidBodies when adding themselves to the part. auto AddPart(Part* part_in) -> int { parts_.push_back(part_in); return static_cast(parts_.size() - 1); } - // Used to send messages to a node + /// Used to send messages to a node void DispatchNodeMessage(const char* buffer); - // Used to send custom user messages to a node - // returns true if handled. + /// Used to send custom user messages to a node. void DispatchUserMessage(PyObject* obj, const char* label); void DispatchOutOfBoundsMessage(); void DispatchPickedUpMessage(Node* n); @@ -102,21 +106,21 @@ class Node : public Object { void DispatchShouldShatterMessage(); void DispatchImpactDamageMessage(float intensity); - // Utility function to get a rigid body. + /// Utility function to get a rigid body. virtual auto GetRigidBody(int id) -> RigidBody* { return nullptr; } - // Given a rigid body, return the relative position where it should be picked - // up from. + /// Given a rigid body, return the relative position where it should be + /// picked up from. virtual void GetRigidBodyPickupLocations(int id, float* posObj, float* pos_char, float* hand_offset_1, float* hand_offset_2); - // Called for each Node when it should render itself. + /// Called for each Node when it should render itself. virtual void Draw(base::FrameDef* frame_def); - // Called for each node once construction is completed - // this can be a good time to create things from the initial attr set, etc + /// Called for each node once construction is completed this can be a good + /// time to create things from the initial attr set, etc virtual void OnCreate(); auto scene() const -> Scene* { @@ -124,14 +128,14 @@ class Node : public Object { return scene_; } - // Used to re-sync client versions of a node from the host version. + /// Used to re-sync client versions of a node from the host version. virtual auto GetResyncDataSize() -> int; virtual auto GetResyncData() -> std::vector; virtual void ApplyResyncData(const std::vector& data); auto context_ref() const -> const ContextRefSceneV1& { return context_ref_; } - // Node labels are purely for local debugging - they aren't unique or sent - // across the network or anything. + /// Node labels are purely for local debugging - they aren't unique or + /// sent across the network or anything. void set_label(const std::string& label) { label_ = label; } auto label() const -> const std::string& { return label_; } @@ -156,17 +160,21 @@ class Node : public Object { auto GetObjectDescription() const -> std::string override; auto parts() const -> const std::vector& { return parts_; } + auto death_actions() const -> const std::vector >& { return death_actions_; } + auto dependent_nodes() const -> const std::vector >& { return dependent_nodes_; } + auto attribute_connections() const -> const std::list >& { return attribute_connections_; } + auto attribute_connections_incoming() const -> const std::unordered_map >& { return attribute_connections_incoming_; @@ -177,13 +185,14 @@ class Node : public Object { assert(stream_id_ == -1); stream_id_ = val; } + void clear_stream_id() { assert(stream_id_ != -1); stream_id_ = -1; } - // Return a reference to a python wrapper for this node, - // creating one if need be. + /// Return a reference to a python wrapper for this node, creating one if + /// need be. auto GetPyRef(bool new_ref = true) -> PyObject*; void AddToScene(Scene* scene); @@ -197,8 +206,8 @@ class Node : public Object { PyObject* py_ref_ = nullptr; - // FIXME - We can get by with *just* a pointer to our scene - // if we add a way to pull context from a scene. + /// FIXME - We can get by with *just* a pointer to our scene if we add a + /// way to pull context from a scene. ContextRefSceneV1 context_ref_; Scene* scene_{}; std::string label_; @@ -211,10 +220,10 @@ class Node : public Object { PythonRef delegate_; std::vector > death_actions_; - // Outgoing attr connections in order created. + /// Outgoing attr connections in order created. std::list > attribute_connections_; - // Incoming attr connections by attr index. + /// Incoming attr connections by attr index. std::unordered_map > attribute_connections_incoming_; diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc index 36c545ab..e7807406 100644 --- a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc +++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc @@ -25,6 +25,7 @@ #include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/generic/json.h" #include "ballistica/shared/generic/utils.h" +#include "ballistica/ui_v1/ui_v1.h" namespace ballistica::scene_v1 { @@ -76,8 +77,14 @@ bool SceneV1AppMode::InClassicMainMenuSession() const { static SceneV1AppMode* g_scene_v1_app_mode{}; void SceneV1AppMode::OnActivate() { + assert(g_base->InLogicThread()); Reset(); + // We use UIV1. + if (!g_core->HeadlessMode()) { + g_base->ui->set_ui_delegate(ui_v1::UIV1FeatureSet::Import()); + } + // To set initial states, explicitly fire some of our 'On-Foo-Changed' // callbacks. DoApplyAppConfig(); diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 73de5452..02f412ed 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21422; +const int kEngineBuildNumber = 21443; const char* kEngineVersion = "1.7.28"; const int kEngineApiVersion = 8; diff --git a/src/ballistica/shared/buildconfig/buildconfig_common.h b/src/ballistica/shared/buildconfig/buildconfig_common.h index 2787724a..7c68aa97 100644 --- a/src/ballistica/shared/buildconfig/buildconfig_common.h +++ b/src/ballistica/shared/buildconfig/buildconfig_common.h @@ -140,10 +140,6 @@ namespace ballistica { #define BA_ENABLE_STDIO_CONSOLE 0 #endif -#ifndef BA_HARDWARE_CURSOR -#define BA_HARDWARE_CURSOR 0 -#endif - #ifndef BA_ENABLE_OS_FONT_RENDERING #define BA_ENABLE_OS_FONT_RENDERING 0 #endif @@ -277,7 +273,6 @@ class BuildConfig { bool enable_os_font_rendering() const { return EXPBOOL_(BA_ENABLE_OS_FONT_RENDERING); } - bool hardware_cursor() const { return EXPBOOL_(BA_HARDWARE_CURSOR); } }; #undef EXPBOOL_ diff --git a/src/ballistica/shared/foundation/event_loop.cc b/src/ballistica/shared/foundation/event_loop.cc index be0cf543..9c6a1ad3 100644 --- a/src/ballistica/shared/foundation/event_loop.cc +++ b/src/ballistica/shared/foundation/event_loop.cc @@ -171,12 +171,13 @@ auto EventLoop::ThreadMainAssetsP_(void* data) -> void* { return nullptr; } -void EventLoop::PushSetPaused(bool paused) { +void EventLoop::PushSetSuspended(bool suspended) { assert(g_core); // Can be toggled from the main thread only. assert(std::this_thread::get_id() == g_core->main_thread_id); - PushThreadMessage_(ThreadMessage_(paused ? ThreadMessage_::Type::kPause - : ThreadMessage_::Type::kResume)); + PushThreadMessage_(ThreadMessage_(suspended + ? ThreadMessage_::Type::kSuspend + : ThreadMessage_::Type::kUnsuspend)); } void EventLoop::WaitForNextEvent_(bool single_cycle) { @@ -184,24 +185,27 @@ void EventLoop::WaitForNextEvent_(bool single_cycle) { // If we're running a single cycle we never stop to wait. if (single_cycle) { - // Need to revisit this if we ever do single-cycle for - // the gil-holding thread so we don't starve other Python threads. + // Need to revisit this if we ever do single-cycle for the gil-holding + // thread so we don't starve other Python threads. assert(!acquires_python_gil_); return; } - // We also never wait if we have pending runnables; we want to run - // things as soon as we can. We chew through all runnables at the end - // of the loop so it might seem like there should never be any here, - // but runnables can add other runnables that won't get processed until - // the next time through. - // BUG FIX: We now skip this if we're paused since we don't run runnables - // in that case. This was preventing us from releasing the GIL while paused - // (and I assume causing us to spin full-speed through the loop; ugh). - // NOTE: It is theoretically possible for a runnable to add another runnable - // each time through the loop which would effectively starve the GIL as - // well; do we need to worry about that case? - if (has_pending_runnables() && !paused_) { + // We also never wait if we have pending runnables; we want to run things + // as soon as we can. We chew through all runnables at the end of the loop + // so it might seem like there should never be any here, but runnables can + // add other runnables that won't get processed until the next time + // through. + // + // BUG FIX: We now skip this if we're suspended since we don't run + // runnables in that case. This was preventing us from releasing the GIL + // while suspended (and I assume causing us to spin full-speed through + // the loop; ugh). + // + // NOTE: It is theoretically possible for a runnable to add another + // runnable each time through the loop which would effectively starve the + // GIL as well; do we need to worry about that case? + if (has_pending_runnables() && !suspended_) { return; } @@ -212,7 +216,7 @@ void EventLoop::WaitForNextEvent_(bool single_cycle) { // If we've got active timers, wait for messages with a timeout so we can // run the next timer payload. - if (!paused_ && timers_.ActiveTimerCount() > 0) { + if (!suspended_ && timers_.ActiveTimerCount() > 0) { millisecs_t apptime = g_core->GetAppTimeMillisecs(); millisecs_t wait_time = timers_.TimeToNextExpire(apptime); if (wait_time > 0) { @@ -287,18 +291,18 @@ void EventLoop::Run_(bool single_cycle) { done_ = true; break; } - case ThreadMessage_::Type::kPause: { - assert(!paused_); - RunPauseCallbacks_(); - paused_ = true; - last_pause_time_ = g_core->GetAppTimeMillisecs(); - messages_since_paused_ = 0; + case ThreadMessage_::Type::kSuspend: { + assert(!suspended_); + RunSuspendCallbacks_(); + suspended_ = true; + last_suspend_time_ = g_core->GetAppTimeMillisecs(); + messages_since_suspended_ = 0; break; } - case ThreadMessage_::Type::kResume: { - assert(paused_); - RunResumeCallbacks_(); - paused_ = false; + case ThreadMessage_::Type::kUnsuspend: { + assert(suspended_); + RunUnsuspendCallbacks_(); + suspended_ = false; break; } default: { @@ -311,7 +315,7 @@ void EventLoop::Run_(bool single_cycle) { } } - if (!paused_) { + if (!suspended_) { timers_.Run(g_core->GetAppTimeMillisecs()); RunPendingRunnables_(); } @@ -473,11 +477,11 @@ void EventLoop::LogThreadMessageTally_( case ThreadMessage_::Type::kRunnable: s += "kRunnable"; break; - case ThreadMessage_::Type::kPause: - s += "kPause"; + case ThreadMessage_::Type::kSuspend: + s += "kSuspend"; break; - case ThreadMessage_::Type::kResume: - s += "kResume"; + case ThreadMessage_::Type::kUnsuspend: + s += "kUnsuspend"; break; default: s += "UNKNOWN(" + std::to_string(static_cast(m.type)) + ")"; @@ -570,24 +574,24 @@ void EventLoop::PushThreadMessage_(const ThreadMessage_& t) { } } -void EventLoop::SetEventLoopsPaused(bool paused) { +void EventLoop::SetEventLoopsSuspended(bool suspended) { assert(g_core); assert(std::this_thread::get_id() == g_core->main_thread_id); - g_core->threads_paused = paused; - for (auto&& i : g_core->pausable_event_loops) { - i->PushSetPaused(paused); + g_core->event_loops_suspended = suspended; + for (auto&& i : g_core->suspendable_event_loops) { + i->PushSetSuspended(suspended); } } -auto EventLoop::GetStillPausingThreads() -> std::vector { +auto EventLoop::GetStillSuspendingEventLoops() -> std::vector { assert(g_core); std::vector threads; assert(std::this_thread::get_id() == g_core->main_thread_id); - // Only return results if an actual pause is in effect. - if (g_core->threads_paused) { - for (auto&& i : g_core->pausable_event_loops) { - if (!i->paused()) { + // Only return results if an actual suspend is in effect. + if (g_core->event_loops_suspended) { + for (auto&& i : g_core->suspendable_event_loops) { + if (!i->suspended()) { threads.push_back(i); } } @@ -595,9 +599,9 @@ auto EventLoop::GetStillPausingThreads() -> std::vector { return threads; } -auto EventLoop::AreEventLoopsPaused() -> bool { +auto EventLoop::AreEventLoopsSuspended() -> bool { assert(g_core); - return g_core->threads_paused; + return g_core->event_loops_suspended; } auto EventLoop::NewTimer(millisecs_t length, bool repeat, @@ -678,14 +682,14 @@ void EventLoop::RunPendingRunnables_() { } } -void EventLoop::RunPauseCallbacks_() { - for (Runnable* i : pause_callbacks_) { +void EventLoop::RunSuspendCallbacks_() { + for (Runnable* i : suspend_callbacks_) { i->RunAndLogErrors(); } } -void EventLoop::RunResumeCallbacks_() { - for (Runnable* i : resume_callbacks_) { +void EventLoop::RunUnsuspendCallbacks_() { + for (Runnable* i : unsuspend_callbacks_) { i->RunAndLogErrors(); } } @@ -701,14 +705,14 @@ void EventLoop::PushCrossThreadRunnable_(Runnable* runnable, EventLoop::ThreadMessage_::Type::kRunnable, runnable, completion_flag)); } -void EventLoop::AddPauseCallback(Runnable* runnable) { +void EventLoop::AddSuspendCallback(Runnable* runnable) { assert(std::this_thread::get_id() == thread_id()); - pause_callbacks_.push_back(runnable); + suspend_callbacks_.push_back(runnable); } -void EventLoop::AddResumeCallback(Runnable* runnable) { +void EventLoop::AddUnsuspendCallback(Runnable* runnable) { assert(std::this_thread::get_id() == thread_id()); - resume_callbacks_.push_back(runnable); + unsuspend_callbacks_.push_back(runnable); } void EventLoop::PushRunnable(Runnable* runnable) { diff --git a/src/ballistica/shared/foundation/event_loop.h b/src/ballistica/shared/foundation/event_loop.h index 159efa20..c8d93ac2 100644 --- a/src/ballistica/shared/foundation/event_loop.h +++ b/src/ballistica/shared/foundation/event_loop.h @@ -29,8 +29,8 @@ class EventLoop { static auto CurrentThreadName() -> std::string; - static void SetEventLoopsPaused(bool enable); - static auto AreEventLoopsPaused() -> bool; + static void SetEventLoopsSuspended(bool enable); + static auto AreEventLoopsSuspended() -> bool; auto ThreadIsCurrent() const -> bool { return std::this_thread::get_id() == thread_id(); @@ -41,7 +41,7 @@ class EventLoop { void SetAcquiresPythonGIL(); - void PushSetPaused(bool paused); + void PushSetSuspended(bool suspended); auto thread_id() const -> std::thread::id { return thread_id_; } @@ -81,11 +81,11 @@ class EventLoop { PushRunnableSynchronous(NewLambdaRunnableUnmanaged(lambda)); } - /// Add a callback to be run on event-loop pauses. - void AddPauseCallback(Runnable* runnable); + /// Add a callback to be run on event-loop suspends. + void AddSuspendCallback(Runnable* runnable); - /// Add a callback to be run on event-loop resumes. - void AddResumeCallback(Runnable* runnable); + /// Add a callback to be run on event-loop unsuspends. + void AddUnsuspendCallback(Runnable* runnable); auto has_pending_runnables() const -> bool { return !runnables_.empty(); } @@ -97,14 +97,14 @@ class EventLoop { /// the app through a flood of packets. auto CheckPushSafety() -> bool; - static auto GetStillPausingThreads() -> std::vector; + static auto GetStillSuspendingEventLoops() -> std::vector; - auto paused() { return paused_; } + auto suspended() { return suspended_; } auto done() -> bool { return done_; } private: struct ThreadMessage_ { - enum class Type { kShutdown = 999, kRunnable, kPause, kResume }; + enum class Type { kShutdown = 999, kRunnable, kSuspend, kUnsuspend }; Type type; union { Runnable* runnable{}; @@ -147,8 +147,8 @@ class EventLoop { void PushThreadMessage_(const ThreadMessage_& t); void RunPendingRunnables_(); - void RunPauseCallbacks_(); - void RunResumeCallbacks_(); + void RunSuspendCallbacks_(); + void RunUnsuspendCallbacks_(); void AcquireGIL_(); void ReleaseGIL_(); @@ -156,10 +156,10 @@ class EventLoop { void BootstrapThread_(); bool writing_tally_{}; - bool paused_{}; - millisecs_t last_pause_time_{}; - int messages_since_paused_{}; - millisecs_t last_paused_message_report_time_{}; + bool suspended_{}; + millisecs_t last_suspend_time_{}; + int messages_since_suspended_{}; + millisecs_t last_suspended_message_report_time_{}; bool done_{}; ThreadSource source_; int listen_sd_{}; @@ -175,8 +175,8 @@ class EventLoop { bool bootstrapped_{}; std::list> runnables_; - std::list pause_callbacks_; - std::list resume_callbacks_; + std::list suspend_callbacks_; + std::list unsuspend_callbacks_; std::condition_variable thread_message_cv_; std::mutex thread_message_mutex_; std::list thread_messages_; diff --git a/src/ballistica/shared/foundation/fatal_error.cc b/src/ballistica/shared/foundation/fatal_error.cc index 51f1ce91..901394cf 100644 --- a/src/ballistica/shared/foundation/fatal_error.cc +++ b/src/ballistica/shared/foundation/fatal_error.cc @@ -4,7 +4,6 @@ #include "ballistica/core/platform/core_platform.h" #include "ballistica/core/support/base_soft.h" -#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/foundation/logging.h" namespace ballistica { diff --git a/src/ballistica/shared/foundation/object.cc b/src/ballistica/shared/foundation/object.cc index 9b140109..a24dd221 100644 --- a/src/ballistica/shared/foundation/object.cc +++ b/src/ballistica/shared/foundation/object.cc @@ -199,6 +199,16 @@ void Object::ObjectThreadCheck() { ThreadOwnership thread_ownership = GetThreadOwnership(); + // Special case; graphics context (not simply a thread so have to handle + // specially). + if (thread_ownership == ThreadOwnership::kGraphicsContext) { + if (!(g_base_soft && g_base_soft->InGraphicsContext())) { + throw Exception("ObjectThreadCheck failed for " + GetObjectDescription() + + "; expected graphics context."); + } + return; + } + EventLoopID t; if (thread_ownership == ThreadOwnership::kClassDefault) { t = GetDefaultOwnerThread(); diff --git a/src/ballistica/shared/foundation/object.h b/src/ballistica/shared/foundation/object.h index 8b98b457..91c38c46 100644 --- a/src/ballistica/shared/foundation/object.h +++ b/src/ballistica/shared/foundation/object.h @@ -32,8 +32,12 @@ class Object { virtual auto GetObjectDescription() const -> std::string; enum class ThreadOwnership { - kClassDefault, // Uses class' GetDefaultOwnerThread() call. - kNextReferencing // Uses whichever thread next acquires/accesses a ref. + /// Uses class' GetDefaultOwnerThread() call. + kClassDefault, + /// Requires graphics context to be active. + kGraphicsContext, + /// Uses whichever thread next acquires/accesses a reference. + kNextReferencing }; #if BA_DEBUG_BUILD diff --git a/src/ballistica/shared/foundation/types.h b/src/ballistica/shared/foundation/types.h index 50678979..32989060 100644 --- a/src/ballistica/shared/foundation/types.h +++ b/src/ballistica/shared/foundation/types.h @@ -97,6 +97,27 @@ enum class InputType { kLast // Sentinel }; +// BA_EXPORT_PYTHON_ENUM +/// Types of input a controller can send to the game. +/// +/// Category: Enums +/// +/// 'soft' may hide/reset the app but keep the process running, depending +/// on the platform. +/// +/// 'back' is a variant of 'soft' which may give 'back-button-pressed' +/// behavior depending on the platform. (returning to some previous +/// activity instead of dumping to the home screen, etc.) +/// +/// 'hard' leads to the process exiting. This generally should be avoided +/// on platforms such as mobile. +enum class QuitType { + kSoft, + kBack, + kHard, + kLast // Sentinel. +}; + typedef int64_t TimerMedium; // BA_EXPORT_PYTHON_ENUM diff --git a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc index 8f6c570b..ac51c315 100644 --- a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc +++ b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc @@ -9,7 +9,6 @@ #include "ballistica/base/platform/base_platform.h" #include "ballistica/base/python/base_python.h" #include "ballistica/base/support/plus_soft.h" -#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/ui_v1/python/class/python_class_ui_mesh.h" #include "ballistica/ui_v1/python/class/python_class_ui_sound.h" #include "ballistica/ui_v1/python/class/python_class_ui_texture.h" diff --git a/src/ballistica/ui_v1/python/ui_v1_python.cc b/src/ballistica/ui_v1/python/ui_v1_python.cc index 0fb2343b..d76ecd34 100644 --- a/src/ballistica/ui_v1/python/ui_v1_python.cc +++ b/src/ballistica/ui_v1/python/ui_v1_python.cc @@ -4,9 +4,12 @@ #include "ballistica/base/assets/assets.h" #include "ballistica/base/audio/audio.h" +#include "ballistica/base/input/device/keyboard_input.h" #include "ballistica/base/input/input.h" #include "ballistica/base/logic/logic.h" +#include "ballistica/base/python/base_python.h" #include "ballistica/base/python/support/python_context_call.h" +#include "ballistica/base/ui/dev_console.h" #include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/python/python_command.h" #include "ballistica/shared/python/python_module_builder.h" @@ -132,4 +135,27 @@ void UIV1Python::LaunchStringEditOld(TextWidget* w) { ->Schedule(args); } +void UIV1Python::InvokeQuitWindow(QuitType quit_type) { + assert(g_base->InLogicThread()); + base::ScopedSetContext ssc(nullptr); + + // If the in-app console is active, dismiss it. + if (auto* dev_console = g_base->ui->dev_console()) { + if (dev_console->IsActive()) { + dev_console->Dismiss(); + } + } + + g_base->audio->PlaySound(g_base->assets->SysSound(base::SysSoundID::kSwish)); + auto py_enum = g_base->python->PyQuitType(quit_type); + auto args = PythonRef::Stolen(Py_BuildValue("(O)", py_enum.Get())); + objs().Get(UIV1Python::ObjID::kQuitWindowCall).Call(args); + + // If we have a keyboard, give it UI ownership. + base::InputDevice* keyboard = g_base->input->keyboard_input(); + if (keyboard) { + g_base->ui->SetUIInputDevice(keyboard); + } +} + } // namespace ballistica::ui_v1 diff --git a/src/ballistica/ui_v1/python/ui_v1_python.h b/src/ballistica/ui_v1/python/ui_v1_python.h index 4e1bff21..8e10e9a1 100644 --- a/src/ballistica/ui_v1/python/ui_v1_python.h +++ b/src/ballistica/ui_v1/python/ui_v1_python.h @@ -23,6 +23,7 @@ class UIV1Python { void ShowURL(const std::string& url); static auto GetPyWidget(PyObject* o) -> Widget*; + void InvokeQuitWindow(QuitType quit_type); /// Specific Python objects we hold in objs_. enum class ObjID { diff --git a/src/ballistica/ui_v1/ui_v1.cc b/src/ballistica/ui_v1/ui_v1.cc index 327a651d..30083701 100644 --- a/src/ballistica/ui_v1/ui_v1.cc +++ b/src/ballistica/ui_v1/ui_v1.cc @@ -3,14 +3,15 @@ #include "ballistica/ui_v1/ui_v1.h" #include "ballistica/base/app_mode/app_mode.h" +#include "ballistica/base/audio/audio.h" #include "ballistica/base/graphics/component/empty_component.h" #include "ballistica/base/input/input.h" +#include "ballistica/base/python/base_python.h" #include "ballistica/base/support/app_config.h" #include "ballistica/ui_v1/python/ui_v1_python.h" #include "ballistica/ui_v1/support/root_ui.h" #include "ballistica/ui_v1/widget/root_widget.h" #include "ballistica/ui_v1/widget/stack_widget.h" -#include "ballistica/ui_v1/widget/text_widget.h" namespace ballistica::ui_v1 { @@ -54,7 +55,7 @@ void UIV1FeatureSet::OnModuleExec(PyObject* module) { // Let base know we exist. // (save it the trouble of trying to load us if it uses us passively). - g_base->set_ui_v1(g_ui_v1); + // g_base->set_ui_v1(g_ui_v1); g_core->LifecycleLog("_bauiv1 exec end"); } @@ -72,11 +73,9 @@ void UIV1FeatureSet::DoHandleDeviceMenuPress(base::InputDevice* device) { void UIV1FeatureSet::DoShowURL(const std::string& url) { python->ShowURL(url); } -void UIV1FeatureSet::DoQuitWindow() { - g_ui_v1->python->objs().Get(UIV1Python::ObjID::kQuitWindowCall).Call(); -} - -RootUI* UIV1FeatureSet::NewRootUI() { return new RootUI(); } +// void UIV1FeatureSet::DoQuitWindow() { +// g_ui_v1->python->objs().Get(UIV1Python::ObjID::kQuitWindowCall).Call(); +// } bool UIV1FeatureSet::MainMenuVisible() { // We consider anything on our screen or overlay stacks to be a 'main menu'. @@ -101,6 +100,7 @@ void UIV1FeatureSet::ActivatePartyIcon() { r->ActivatePartyIcon(); } } + bool UIV1FeatureSet::PartyWindowOpen() { if (auto* r = root_ui()) { return r->party_window_open(); @@ -182,16 +182,16 @@ void UIV1FeatureSet::Draw(base::FrameDef* frame_def) { } } -void UIV1FeatureSet::OnAppStart() { +void UIV1FeatureSet::OnActivate() { assert(g_base->InLogicThread()); - root_ui_ = g_base->ui_v1()->NewRootUI(); + if (root_ui_ == nullptr) { + root_ui_ = new RootUI(); + } } +void UIV1FeatureSet::OnDeactivate() { assert(g_base->InLogicThread()); } void UIV1FeatureSet::Reset() { - // Hmm; technically we don't need to recreate these each time we reset. root_widget_.Clear(); - - // Kill our screen-root widget. screen_root_widget_.Clear(); // (Re)create our screen-root widget. @@ -252,9 +252,15 @@ void UIV1FeatureSet::OnScreenSizeChange() { } void UIV1FeatureSet::OnLanguageChange() { - // As well as existing UI stuff. - if (auto* r = root_widget()) { - r->OnLanguageChange(); + // Since switching languages is a bit costly, ignore redundant change + // notifications. These will tend to happen nowadays since change + // notifications go out anytime the ui-delegate switches. + auto asset_language_state = g_base->assets->language_state(); + if (asset_language_state != language_state_) { + language_state_ = asset_language_state; + if (auto* r = root_widget()) { + r->OnLanguageChange(); + } } } @@ -282,6 +288,11 @@ void UIV1FeatureSet::DoApplyAppConfig() { base::AppConfig::BoolID::kAlwaysUseInternalKeyboard); } +auto UIV1FeatureSet::HasQuitConfirmDialog() -> bool { return true; } +void UIV1FeatureSet::ConfirmQuit(QuitType quit_type) { + python->InvokeQuitWindow(quit_type); +} + UIV1FeatureSet::UILock::UILock(bool write) { assert(g_base->ui); assert(g_base->InLogicThread()); diff --git a/src/ballistica/ui_v1/ui_v1.h b/src/ballistica/ui_v1/ui_v1.h index e9a0ac9e..e2409d9a 100644 --- a/src/ballistica/ui_v1/ui_v1.h +++ b/src/ballistica/ui_v1/ui_v1.h @@ -5,7 +5,7 @@ #include -#include "ballistica/base/support/ui_v1_soft.h" +#include "ballistica/base/ui/ui_delegate.h" #include "ballistica/shared/foundation/feature_set_native_component.h" // Common header that most everything using our feature-set should include. @@ -64,7 +64,7 @@ extern UIV1FeatureSet* g_ui_v1; /// Our C++ front-end to our feature set. This is what other C++ /// feature-sets can 'Import' from us. class UIV1FeatureSet : public FeatureSetNativeComponent, - public base::UIV1SoftInterface { + public base::UIDelegateInterface { public: /// Instantiate our FeatureSet if needed and return the single instance of /// it. Basically a Python import statement. @@ -85,8 +85,7 @@ class UIV1FeatureSet : public FeatureSetNativeComponent, static void OnModuleExec(PyObject* module); void DoHandleDeviceMenuPress(base::InputDevice* device) override; void DoShowURL(const std::string& url) override; - void DoQuitWindow() override; - auto NewRootUI() -> ui_v1::RootUI* override; + // void DoQuitWindow() override; auto MainMenuVisible() -> bool override; auto PartyIconVisible() -> bool override; void ActivatePartyIcon() override; @@ -101,7 +100,10 @@ class UIV1FeatureSet : public FeatureSetNativeComponent, assert(root_ui_); return root_ui_; } - void OnAppStart() override; + // void OnAppStart() override; + void OnActivate() override; + void OnDeactivate() override; + auto PartyWindowOpen() -> bool override; // Return the root widget containing all windows & dialogs. Whenever this @@ -134,6 +136,9 @@ class UIV1FeatureSet : public FeatureSetNativeComponent, return always_use_internal_on_screen_keyboard_; } + auto HasQuitConfirmDialog() -> bool override; + void ConfirmQuit(QuitType quit_type) override; + private: UIV1FeatureSet(); RootUI* root_ui_{}; @@ -142,6 +147,7 @@ class UIV1FeatureSet : public FeatureSetNativeComponent, Object::Ref root_widget_; bool always_use_internal_on_screen_keyboard_{}; int ui_lock_count_{}; + int language_state_{}; }; } // namespace ballistica::ui_v1 diff --git a/src/ballistica/ui_v1/widget/text_widget.cc b/src/ballistica/ui_v1/widget/text_widget.cc index 9beca1dc..2225580c 100644 --- a/src/ballistica/ui_v1/widget/text_widget.cc +++ b/src/ballistica/ui_v1/widget/text_widget.cc @@ -2,7 +2,6 @@ #include "ballistica/ui_v1/widget/text_widget.h" -#include "ballistica/base/app_adapter/app_adapter.h" #include "ballistica/base/audio/audio.h" #include "ballistica/base/graphics/component/empty_component.h" #include "ballistica/base/graphics/component/simple_component.h" @@ -13,11 +12,6 @@ #include "ballistica/base/platform/base_platform.h" #include "ballistica/base/python/base_python.h" #include "ballistica/base/python/support/python_context_call.h" -#include "ballistica/base/ui/ui.h" -#include "ballistica/core/core.h" -#include "ballistica/shared/foundation/event_loop.h" -#include "ballistica/shared/foundation/logging.h" -#include "ballistica/shared/foundation/types.h" #include "ballistica/shared/generic/utils.h" #include "ballistica/shared/python/python.h" #include "ballistica/shared/python/python_sys.h" diff --git a/src/meta/babasemeta/pyembed/binding_base.py b/src/meta/babasemeta/pyembed/binding_base.py index c5d15011..5e71b25a 100644 --- a/src/meta/babasemeta/pyembed/binding_base.py +++ b/src/meta/babasemeta/pyembed/binding_base.py @@ -69,6 +69,7 @@ values = [ _error.SessionNotFoundError, # kSessionNotFoundError enums.TimeFormat, # kTimeFormatClass enums.TimeType, # kTimeTypeClass + enums.QuitType, # kQuitTypeClass enums.InputType, # kInputTypeClass enums.Permission, # kPermissionClass enums.SpecialChar, # kSpecialCharClass diff --git a/src/meta/babasemeta/pyembed/binding_base_app.py b/src/meta/babasemeta/pyembed/binding_base_app.py index 7248a778..d5fcf519 100644 --- a/src/meta/babasemeta/pyembed/binding_base_app.py +++ b/src/meta/babasemeta/pyembed/binding_base_app.py @@ -14,8 +14,8 @@ values = [ app.push_apply_app_config, # kAppPushApplyAppConfigCall app.on_native_start, # kAppOnNativeStartCall app.on_native_bootstrapping_complete, # kAppOnNativeBootstrappingCompleteCall - app.on_native_pause, # kAppOnNativePauseCall - app.on_native_resume, # kAppOnNativeResumeCall + app.on_native_suspend, # kAppOnNativeSuspendCall + app.on_native_unsuspend, # kAppOnNativeUnsuspendCall app.on_native_shutdown, # kAppOnNativeShutdownCall app.on_native_shutdown_complete, # kAppOnNativeShutdownCompleteCall app.read_config, # kAppReadConfigCall diff --git a/tools/batools/androidsdkutils.py b/tools/batools/androidsdkutils.py index 7c17e7b4..83ae682f 100755 --- a/tools/batools/androidsdkutils.py +++ b/tools/batools/androidsdkutils.py @@ -48,7 +48,7 @@ def _gen_lprop_file(local_properties_path: str) -> str: if not found: home = os.getenv('HOME') assert home is not None - test_paths = [home + '/Library/Android/sdk'] + test_paths = [home + '/Library/Android/sdk', home + '/AndroidSDK'] for sdk_dir in test_paths: if os.path.exists(sdk_dir): found = True @@ -58,12 +58,10 @@ def _gen_lprop_file(local_properties_path: str) -> str: assert sdk_dir is not None if not found: if not os.path.exists(sdk_dir): - print( - 'ERROR: Android sdk not found; install ' - 'the android sdk and try again', - file=sys.stderr, + raise CleanError( + f'Android sdk not found at {sdk_dir}; install ' + 'the android sdk and try again.', ) - sys.exit(255) config = ( '\n# This file was automatically generated by ' + os.path.abspath(sys.argv[0]) diff --git a/tools/batools/build.py b/tools/batools/build.py index aa037aa9..22818bcd 100644 --- a/tools/batools/build.py +++ b/tools/batools/build.py @@ -43,8 +43,8 @@ class PyRequirement: # remove our custom module based stuff soon if nobody complains, which # would free us to theoretically move to a requirements.txt based setup. PY_REQUIREMENTS = [ - PyRequirement(pipname='pylint', minversion=[3, 0, 0]), - PyRequirement(pipname='mypy', minversion=[1, 5, 1]), + PyRequirement(pipname='pylint', minversion=[3, 0, 1]), + PyRequirement(pipname='mypy', minversion=[1, 6, 0]), PyRequirement(pipname='cpplint', minversion=[1, 6, 1]), PyRequirement(pipname='pytest', minversion=[7, 4, 2]), PyRequirement(pipname='pytz', minversion=[2023, 3]), @@ -706,7 +706,7 @@ def cmake_prep_dir(dirname: str, verbose: bool = False) -> None: # away all cmake builds everywhere (to keep things clean if we # rename or move something in the build dir or if we change # something cmake doesn't properly handle without a fresh start). - entries: list[Entry] = [Entry('explicit cmake rebuild', '3')] + entries: list[Entry] = [Entry('explicit cmake rebuild', '4')] # Start fresh if cmake version changes. cmake_ver_output = subprocess.run( diff --git a/tools/batools/resourcesmakefile.py b/tools/batools/resourcesmakefile.py index ffed6f29..a08ebdd5 100755 --- a/tools/batools/resourcesmakefile.py +++ b/tools/batools/resourcesmakefile.py @@ -124,6 +124,7 @@ class ResourcesMakefileGenerator: self._add_apple_tv_3d_icon() self._add_apple_tv_store_icon() self._add_google_vr_icon() + self._add_macos_cursor() our_lines_private_2 = ( ['# __PUBSYNC_STRIP_BEGIN__'] + _empty_line_if(bool(self.targets)) @@ -570,6 +571,33 @@ class ResourcesMakefileGenerator: ) self.targets.append(Target(src=[src], dst=dst, cmd=cmd, mkdir=True)) + def _add_macos_cursor(self) -> None: + sizes = [ + (64, 1), + (64, 2), + ] + for size in sizes: + res = int(size[0] * size[1]) + src = os.path.join('cursor.png') + dst = os.path.join( + ROOT_DIR, + f'{self.namel}-xcode', + f'{self.nameu} Shared', + 'Assets.xcassets', + 'Cursor macOS.imageset', + 'cursor_' + str(size[0]) + 'x' + str(size[1]) + '.png', + ) + cmd = ' '.join( + [ + RESIZE_CMD, + str(res), + str(res), + '"' + src + '"', + '"' + dst + '"', + ] + ) + self.targets.append(Target(src=[src], dst=dst, cmd=cmd)) + def _empty_line_if(condition: bool) -> list[str]: return [''] if condition else [] diff --git a/tools/efro/cloudshell.py b/tools/efro/cloudshell.py index b3c1cf68..3965d072 100644 --- a/tools/efro/cloudshell.py +++ b/tools/efro/cloudshell.py @@ -39,6 +39,7 @@ class HostConfig: precommand_noninteractive: str | None = None precommand_interactive: str | None = None managed: bool = False + region: str | None = None idle_minutes: int = 5 can_sudo_reboot: bool = False max_sessions: int = 4 diff --git a/tools/efrotools/pybuild.py b/tools/efrotools/pybuild.py index c3a596c4..e561a314 100644 --- a/tools/efrotools/pybuild.py +++ b/tools/efrotools/pybuild.py @@ -131,24 +131,31 @@ def build_apple(arch: str, debug: bool = False) -> None: ) os.chdir(builddir) - # TEMP: Check out a particular commit while the branch head is - # broken. We can actually fix this to use the current one, but - # something broke in the underlying build even on old commits so - # keeping it locked for now... - # - # run('git checkout bf1ed73d0d5ff46862ba69dd5eb2ffaeff6f19b6') - - # Grab the branch corresponding to our target python version. - subprocess.run(['git', 'checkout', PY_VER_APPLE], check=True) + # TEMP: The recent update (Oct 2023) switched a bit of stuff around + # (apparently dylib support has been revamped more) so I need to + # re-test things and probably make adjustments. Holding off for now. + # Might just do this when I update everything to 3.12 which will be + # a bit of work anyway. + if bool(True): + subprocess.run( + ['git', 'checkout', 'a8c93fed2bdf0122fc2ca663faa1e46e5cf28d69'], + check=True, + ) + else: + # Grab the branch corresponding to our target Python version. + subprocess.run(['git', 'checkout', PY_VER_APPLE], check=True) txt = readfile('Makefile') # Sanity check; we don't actually change Python version for these # builds but we need to make sure exactly what version the repo is - # building (for path purposes and whatnot). + # building (for path purposes and whatnot). Ideally we should just + # parse these values from the Makefile so we don't have to keep + # things in sync. if f'\nPYTHON_VERSION={PY_VER_EXACT_APPLE}\n' not in txt: raise RuntimeError( - 'Does not look like our PY_VER_EXACT_APPLE matches the repo;' + f'Does not look like our PY_VER_EXACT_APPLE' + f' ({PY_VER_EXACT_APPLE}) matches the repo;' f' please update it in {__name__}.' ) @@ -328,7 +335,7 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None: # be fixed to build with it. I *think* this has already been done; we just # need to wait for the official update beyond 3.4.4. print('TEMP TEMP TEMP HARD-CODING OLD NDK FOR LIBFFI BUG') - os.environ['ANDROID_NDK'] = '/home/ubuntu/android/ndk/25.2.9519653' + os.environ['ANDROID_NDK'] = '/home/ubuntu/AndroidSDK/ndk/25.2.9519653' # Disable builds for dependencies we don't use. ftxt = readfile('Android/build_deps.py')