Merge branch 'efroemling:master' into master

This commit is contained in:
Vishal 2023-10-12 06:45:46 +05:30 committed by GitHub
commit 192eb8d39e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
102 changed files with 1454 additions and 918 deletions

102
.efrocachemap generated
View File

@ -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",

1
.gitignore vendored
View File

@ -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

View File

@ -3192,6 +3192,8 @@
<w>unstripped</w>
<w>unstrl</w>
<w>unsubscriptable</w>
<w>unsuspend</w>
<w>unsuspending</w>
<w>untracked</w>
<w>unwritable</w>
<w>upcase</w>

View File

@ -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)

View File

@ -1876,6 +1876,8 @@
<w>unpremultiply</w>
<w>unsignaled</w>
<w>unstuff</w>
<w>unsuspend</w>
<w>unsuspending</w>
<w>unsynchronized</w>
<w>unwritable</w>
<w>uppercased</w>

View File

@ -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

View File

@ -148,7 +148,7 @@
<ExceptionHandling>SyncCThrow</ExceptionHandling>
<SDLCheck>false</SDLCheck>
<AdditionalIncludeDirectories>../../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</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
@ -173,7 +173,7 @@
<ExceptionHandling>SyncCThrow</ExceptionHandling>
<SDLCheck>false</SDLCheck>
<AdditionalIncludeDirectories>../../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</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
@ -434,11 +434,11 @@
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
<ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\ui.h" />
<ClInclude Include="..\..\src\ballistica\base\ui\ui_delegate.h" />
<ClInclude Include="..\..\src\ballistica\base\ui\widget_message.h" />
<ClCompile Include="..\..\src\ballistica\classic\classic.cc" />
<ClInclude Include="..\..\src\ballistica\classic\classic.h" />

View File

@ -736,9 +736,6 @@
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
<Filter>ballistica\base\ui</Filter>
</ClCompile>
@ -751,6 +748,9 @@
<ClInclude Include="..\..\src\ballistica\base\ui\ui.h">
<Filter>ballistica\base\ui</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\ui\ui_delegate.h">
<Filter>ballistica\base\ui</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\ui\widget_message.h">
<Filter>ballistica\base\ui</Filter>
</ClInclude>

View File

@ -145,7 +145,7 @@
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
<ExceptionHandling>SyncCThrow</ExceptionHandling>
<AdditionalIncludeDirectories>../../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</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
@ -169,7 +169,7 @@
<ForcedIncludeFiles>stdafx.h</ForcedIncludeFiles>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<AdditionalIncludeDirectories>../../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</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
<LanguageStandard>stdcpp20</LanguageStandard>
<FloatingPointModel>Fast</FloatingPointModel>
</ClCompile>
<Link>
@ -429,11 +429,11 @@
<ClInclude Include="..\..\src\ballistica\base\support\stdio_console.h" />
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
<ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\ui.h" />
<ClInclude Include="..\..\src\ballistica\base\ui\ui_delegate.h" />
<ClInclude Include="..\..\src\ballistica\base\ui\widget_message.h" />
<ClCompile Include="..\..\src\ballistica\classic\classic.cc" />
<ClInclude Include="..\..\src\ballistica\classic\classic.h" />

View File

@ -736,9 +736,6 @@
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
<Filter>ballistica\base\ui</Filter>
</ClCompile>
@ -751,6 +748,9 @@
<ClInclude Include="..\..\src\ballistica\base\ui\ui.h">
<Filter>ballistica\base\ui</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\ui\ui_delegate.h">
<Filter>ballistica\base\ui</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\ui\widget_message.h">
<Filter>ballistica\base\ui</Filter>
</ClInclude>

View File

@ -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',

View File

@ -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()

View File

@ -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:

View File

@ -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

View File

@ -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."""

View File

@ -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

View File

@ -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)

View File

@ -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,

View File

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

View File

@ -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]:

View File

@ -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."""

View File

@ -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(

View File

@ -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:

View File

@ -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()

View File

@ -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',

View File

@ -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:

View File

@ -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)

View File

@ -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(

View File

@ -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:

View File

@ -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)"""

View File

@ -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()

View File

@ -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<int>(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

View File

@ -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 <typename F>
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

View File

@ -5,10 +5,34 @@
#include <BallisticaKit-Swift.h>
#include "ballistica/base/graphics/gl/renderer_gl.h"
#include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/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<Runnable*> 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

View File

@ -5,18 +5,55 @@
#if BA_XCODE_BUILD
#include <mutex>
#include <thread>
#include <vector>
#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<AppAdapterApple*>(base->app_adapter);
assert(val);
assert(dynamic_cast<AppAdapterApple*>(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<Runnable*> graphics_calls_;
};
} // namespace ballistica::base

View File

@ -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<Runnable*> 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<float>(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<float>(sdl_x) / window_size_.x;
float normalized_y = 1.0f - static_cast<float>(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; }

View File

@ -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<Runnable*> 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

View File

@ -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();

View File

@ -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<std::string, std::string> language_;
int language_state_{};
std::mutex special_char_mutex_;
std::unordered_map<SpecialChar, std::string> special_char_strings_;
};

View File

@ -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(); });
}

View File

@ -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;
}
};

View File

@ -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 {

View File

@ -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.

View File

@ -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();

View File

@ -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();

View File

@ -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(); });
}

View File

@ -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_;

View File

@ -142,7 +142,7 @@ void BGDynamics::SetDrawSnapshot(BGDynamicsDrawSnapshot* s) {
}
void BGDynamics::TooSlow() {
if (!EventLoop::AreEventLoopsPaused()) {
if (!EventLoop::AreEventLoopsSuspended()) {
g_base->bg_dynamics_server->PushTooSlowCall();
}
}

View File

@ -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() {

View File

@ -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) {

View File

@ -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;
}

View File

@ -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));
}

View File

@ -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;
}

View File

@ -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<Matrix44f> 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<MeshData*> 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_{};
};

View File

@ -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,

View File

@ -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;
}
};

View File

@ -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);

View File

@ -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 */

View File

@ -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) {

View File

@ -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);
}
}
}

View File

@ -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_{};

View File

@ -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_;

View File

@ -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<uint8_t>& msg,

View File

@ -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 {

View File

@ -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.

View File

@ -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:
};

View File

@ -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

View File

@ -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);

View File

@ -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<TimeType>(BasePython::ObjID::kTimeTypeClass, obj);
}
auto BasePython::GetPyEnum_QuitType(PyObject* obj) -> QuitType {
return GetPyEnum<QuitType>(BasePython::ObjID::kQuitTypeClass, obj);
}
auto BasePython::GetPyEnum_TimeFormat(PyObject* obj) -> TimeFormat {
return GetPyEnum<TimeFormat>(BasePython::ObjID::kTimeFormatClass, obj);
}
@ -479,6 +483,14 @@ auto BasePython::GetPyEnum_InputType(PyObject* obj) -> InputType {
return GetPyEnum<InputType>(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<int>(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());

View File

@ -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<std::string> do_once_locations_;

View File

@ -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<char**>(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<char**>(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 ----------------------------------

View File

@ -5,7 +5,6 @@
#include <list>
#include <unordered_map>
#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"

View File

@ -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] {

View File

@ -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,

View File

@ -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<int>(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) {

View File

@ -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<InputDevice> ui_input_device_;

View File

@ -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_

View File

@ -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<EventLoop*> pausable_event_loops;
std::vector<EventLoop*> 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{};

View File

@ -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,

View File

@ -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<NodeMessageType>(**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<int>(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<uint8_t>;
virtual void ApplyResyncData(const std::vector<uint8_t>& 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<Part*>& { return parts_; }
auto death_actions() const
-> const std::vector<Object::Ref<base::PythonContextCall> >& {
return death_actions_;
}
auto dependent_nodes() const -> const std::vector<Object::WeakRef<Node> >& {
return dependent_nodes_;
}
auto attribute_connections() const
-> const std::list<Object::Ref<NodeAttributeConnection> >& {
return attribute_connections_;
}
auto attribute_connections_incoming() const
-> const std::unordered_map<int, Object::Ref<NodeAttributeConnection> >& {
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<Object::Ref<base::PythonContextCall> > death_actions_;
// Outgoing attr connections in order created.
/// Outgoing attr connections in order created.
std::list<Object::Ref<NodeAttributeConnection> > attribute_connections_;
// Incoming attr connections by attr index.
/// Incoming attr connections by attr index.
std::unordered_map<int, Object::Ref<NodeAttributeConnection> >
attribute_connections_incoming_;

View File

@ -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();

View File

@ -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;

View File

@ -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_

View File

@ -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<int>(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<EventLoop*> {
auto EventLoop::GetStillSuspendingEventLoops() -> std::vector<EventLoop*> {
assert(g_core);
std::vector<EventLoop*> 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<EventLoop*> {
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) {

View File

@ -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<EventLoop*>;
static auto GetStillSuspendingEventLoops() -> std::vector<EventLoop*>;
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<std::pair<Runnable*, bool*>> runnables_;
std::list<Runnable*> pause_callbacks_;
std::list<Runnable*> resume_callbacks_;
std::list<Runnable*> suspend_callbacks_;
std::list<Runnable*> unsuspend_callbacks_;
std::condition_variable thread_message_cv_;
std::mutex thread_message_mutex_;
std::list<ThreadMessage_> thread_messages_;

View File

@ -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 {

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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 {

View File

@ -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());

View File

@ -5,7 +5,7 @@
#include <ballistica/base/input/device/input_device.h>
#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<RootWidget> root_widget_;
bool always_use_internal_on_screen_keyboard_{};
int ui_lock_count_{};
int language_state_{};
};
} // namespace ballistica::ui_v1

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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])

View File

@ -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(

View File

@ -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 []

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