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 @@
unstrippedunstrlunsubscriptable
+ unsuspend
+ unsuspendinguntrackedunwritableupcase
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 @@
unpremultiplyunsignaledunstuff
+ unsuspend
+ unsuspendingunsynchronizedunwritableuppercased
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 @@
SyncCThrowfalse../../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
+ stdcpp20Fast
@@ -173,7 +173,7 @@
SyncCThrowfalse../../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
+ stdcpp20Fast
@@ -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.hSyncCThrow../../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
+ stdcpp20Fast
@@ -169,7 +169,7 @@
stdafx.htrue../../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
+ stdcpp20Fast
@@ -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')