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