diff --git a/.efrocachemap b/.efrocachemap index 0628b6b6..415f07a1 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4072,26 +4072,26 @@ "build/assets/workspace/ninjafightplug.py": "https://files.ballistica.net/cache/ba1/c5/09/4f10b8a21ba87aa5509cff7a164b", "build/assets/workspace/onslaughtplug.py": "https://files.ballistica.net/cache/ba1/ff/0a/a354984f9c074dab0676ac7e4877", "build/assets/workspace/runaroundplug.py": "https://files.ballistica.net/cache/ba1/2a/1c/9ee5db6d1bceca7fa6638fb8abde", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/7b/fc/40202144638c394e88af0424ac38", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/bd/fb/9a2813b5a23b727b1b246d5b1a31", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/6f/99/9e5c25105d2bc8fd1db0402cdb0f", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/8b/4e/f6496d2ccd7a869e4e9d2eb57ee4", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/f6/ec/dab1171b1c99587a80e959dddaa5", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/2f/23/0d5dd4dd3dd190a3f81eca6d11f0", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/f3/ec/b238f5118def4ecac3ba4363f389", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/b2/d2/307b223ce8182e3c14a982dad7d4", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/08/67/672c0b7edb83bd06f3f08d25c1d1", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/5e/34/7189db32e52d599755bf59587671", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/ce/44/dec692802d8023cd48f5f29fa40d", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/8d/52/084e0817f587f035017bc2595a8d", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/46/33/1975fe658fc9ffc96b95c0071ecd", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/66/ea/4e901a9ad2ad965bdc773ee3b127", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/2f/54/565f3150df759bffea9dcad21685", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/52/a5/594f6db4b4f0d8d9937bb67d80f6", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/0f/86/5228cc1d4a8d2317585028e115f3", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/73/b0/83918095162a68c12bffdb7a4d97", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/12/55/5d5b5a467a1c2ec10c2551465682", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/48/95/a35e7407f5f813cea477dd75130f", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/08/e3/a0b9223475c7706681fad7968ba7", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/6d/ef/b6d1a6b9754d3036f71ad3fa77e2", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/2b/b1/6f30bc9be42939f9399d4a3f2b3a", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/0e/ef/1ce03a67174a21bb153ea7e8e27b", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/d0/26/46d5ff486b95aedaa3caf3c299c9", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/10/ea/b9aa9951192cbc45cccb4c3c2289", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/e0/a0/32c52b4e83da434e22544bdda893", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/0b/09/2986300ce8c70c4e301da76a0533", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/b1/79/ba61f157a98f4ceab818d99edab9", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/09/78/0a5975081c617f7a52c6ddd3d7c0", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/36/91/561ed25a53dd05a321418ef95527", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/8d/27/ee747a3e85a28e124c21ac2b380d", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "https://files.ballistica.net/cache/ba1/a7/b8/ad830a02ee58f2e87466f011fb27", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "https://files.ballistica.net/cache/ba1/ca/71/6fd4afebc57e78d5986a2f657a3f", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/56/16/efa353be8adca6c233db12102463", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "https://files.ballistica.net/cache/ba1/85/98/0aa74afa4bfa7f12b4bb54fe76a3", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/e6/bb/308f33d3c4b549b8b701a6e94db1", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "https://files.ballistica.net/cache/ba1/68/c5/ad76162d865a7a14f2f2677300d1", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/c6/39/21bdc2e4e59855558bbc7d606f28", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "https://files.ballistica.net/cache/ba1/f9/eb/e2be79806312a8615a1e6da93078", "build/prefab/lib/linux_arm64_gui/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/f4/e7/32cfbd534ac61e626f6c6fbe2b01", "build/prefab/lib/linux_arm64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/f3/f8/52577356f2ff5229ed4e5a6764cf", "build/prefab/lib/linux_arm64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/60/2b/35acb337afc2d997ffc94d2da757", @@ -4108,14 +4108,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/30/4a/aa281e0eb46722098ec29d7da4f8", "build/prefab/lib/mac_x86_64_server/debug/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/0b/e4/a9d278c1bc9a5d731f865ac91a0b", "build/prefab/lib/mac_x86_64_server/release/libballistica_plus.a": "https://files.ballistica.net/cache/ba1/92/f7/8898478ab4ef0a342c727dd64195", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/df/9f/960a5370e02cae685ae3452066f9", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/7d/f7/ba5e3f57b5c6fe96542f61ae21f1", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/34/b3/83428e86252522a10d0c097fc3eb", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/1d/31/12a6e62fc12477b4ed1eb3735b08", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/65/ca/8bd967cebd1836e03117c2b10f5d", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/8c/41/4689d7ca391e92fed73b81e5064c", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/7b/a9/f53d5f009e6cc89a265c076bbb51", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/d1/ed/acae7564f9d534d7f78a3a9373ee", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/14/3f/8bffa39ee2e862bfd61886f8dd20", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/de/69/ff2d73feab522f0be693900c534b", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/2a/ac/8e4a2b322079d57a65cf5ec686eb", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/9b/d8/f5dbc2b76e5d6992e6e0f957699d", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "https://files.ballistica.net/cache/ba1/68/f9/0e55e33d9782d596d65770e943d8", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "https://files.ballistica.net/cache/ba1/b4/49/32ebdaa15a795c84e90c3d16fdee", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "https://files.ballistica.net/cache/ba1/e9/3c/b86de4797400df2a7bbba4e957f7", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "https://files.ballistica.net/cache/ba1/93/88/176702f3743aa143dcd2ee651c4a", "src/assets/ba_data/python/babase/_mgen/__init__.py": "https://files.ballistica.net/cache/ba1/52/c6/c11130af7b10d6c0321add5518fa", "src/assets/ba_data/python/babase/_mgen/enums.py": "https://files.ballistica.net/cache/ba1/38/c3/1dedd5e74f2508efc5974c8815a1", "src/ballistica/base/mgen/pyembed/binding_base.inc": "https://files.ballistica.net/cache/ba1/b4/3d/e352190a0e5673d101c0f3ee3ad2", diff --git a/CHANGELOG.md b/CHANGELOG.md index 3590b6db..bcbbf597 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.20 (build 21022, api 8, 2023-05-24) +### 1.7.20 (build 21023, api 8, 2023-05-26) - This seems like a good time for a `refactoring` release in anticipation of changes coming in 1.8. Basically this means that a lot of things will be diff --git a/ballisticakit-cmake/CMakeLists.txt b/ballisticakit-cmake/CMakeLists.txt index 0fd026b4..99580ad9 100644 --- a/ballisticakit-cmake/CMakeLists.txt +++ b/ballisticakit-cmake/CMakeLists.txt @@ -408,6 +408,7 @@ add_executable(ballisticakit ${BA_SRC_ROOT}/ballistica/base/python/support/python_context_call.h ${BA_SRC_ROOT}/ballistica/base/python/support/python_context_call_runnable.h ${BA_SRC_ROOT}/ballistica/base/support/app_timer.h + ${BA_SRC_ROOT}/ballistica/base/support/classic_soft.h ${BA_SRC_ROOT}/ballistica/base/support/context.cc ${BA_SRC_ROOT}/ballistica/base/support/context.h ${BA_SRC_ROOT}/ballistica/base/support/huffman.cc diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj index 09735c75..0f78b098 100644 --- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj +++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj @@ -399,6 +399,7 @@ + diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters index 25b38c56..a2e3a7f1 100644 --- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters +++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters @@ -631,6 +631,9 @@ ballistica\base\support + + ballistica\base\support + ballistica\base\support diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj index 318f42a0..2aa7f63c 100644 --- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj +++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj @@ -394,6 +394,7 @@ + diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters index 25b38c56..a2e3a7f1 100644 --- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters +++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters @@ -631,6 +631,9 @@ ballistica\base\support + + ballistica\base\support + ballistica\base\support diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json index f630e343..8a787e24 100644 --- a/src/assets/.asset_manifest_public.json +++ b/src/assets/.asset_manifest_public.json @@ -6,6 +6,9 @@ "ba_data/python/babase/__pycache__/_app.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_appcomponent.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_appconfig.cpython-311.opt-1.pyc", + "ba_data/python/babase/__pycache__/_appintent.cpython-311.opt-1.pyc", + "ba_data/python/babase/__pycache__/_appmode.cpython-311.opt-1.pyc", + "ba_data/python/babase/__pycache__/_appmodeselector.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_apputils.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_assetmanager.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_asyncio.cpython-311.opt-1.pyc", @@ -27,6 +30,9 @@ "ba_data/python/babase/_app.py", "ba_data/python/babase/_appcomponent.py", "ba_data/python/babase/_appconfig.py", + "ba_data/python/babase/_appintent.py", + "ba_data/python/babase/_appmode.py", + "ba_data/python/babase/_appmodeselector.py", "ba_data/python/babase/_apputils.py", "ba_data/python/babase/_assetmanager.py", "ba_data/python/babase/_asyncio.py", @@ -122,6 +128,7 @@ "ba_data/python/bascenev1/__pycache__/_activity.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_activitytypes.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_actor.cpython-311.opt-1.pyc", + "ba_data/python/bascenev1/__pycache__/_appmode.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_collision.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_coopgame.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_coopsession.cpython-311.opt-1.pyc", @@ -152,6 +159,7 @@ "ba_data/python/bascenev1/_activity.py", "ba_data/python/bascenev1/_activitytypes.py", "ba_data/python/bascenev1/_actor.py", + "ba_data/python/bascenev1/_appmode.py", "ba_data/python/bascenev1/_collision.py", "ba_data/python/bascenev1/_coopgame.py", "ba_data/python/bascenev1/_coopsession.py", diff --git a/src/assets/Makefile b/src/assets/Makefile index 09dcc176..2c526191 100644 --- a/src/assets/Makefile +++ b/src/assets/Makefile @@ -139,6 +139,9 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/babase/_app.py \ $(BUILD_DIR)/ba_data/python/babase/_appcomponent.py \ $(BUILD_DIR)/ba_data/python/babase/_appconfig.py \ + $(BUILD_DIR)/ba_data/python/babase/_appintent.py \ + $(BUILD_DIR)/ba_data/python/babase/_appmode.py \ + $(BUILD_DIR)/ba_data/python/babase/_appmodeselector.py \ $(BUILD_DIR)/ba_data/python/babase/_apputils.py \ $(BUILD_DIR)/ba_data/python/babase/_assetmanager.py \ $(BUILD_DIR)/ba_data/python/babase/_asyncio.py \ @@ -188,6 +191,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bascenev1/_activity.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_activitytypes.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_actor.py \ + $(BUILD_DIR)/ba_data/python/bascenev1/_appmode.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_collision.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_coopgame.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_coopsession.py \ @@ -405,6 +409,9 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_app.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_appcomponent.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_appconfig.cpython-311.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/babase/__pycache__/_appintent.cpython-311.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/babase/__pycache__/_appmode.cpython-311.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/babase/__pycache__/_appmodeselector.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_apputils.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_assetmanager.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_asyncio.cpython-311.opt-1.pyc \ @@ -454,6 +461,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_activity.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_activitytypes.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_actor.cpython-311.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_appmode.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_collision.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_coopgame.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_coopsession.cpython-311.opt-1.pyc \ diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py index fe5229a0..58de3015 100644 --- a/src/assets/ba_data/python/babase/__init__.py +++ b/src/assets/ba_data/python/babase/__init__.py @@ -33,6 +33,8 @@ from _babase import ( in_logic_thread, ) +from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec +from babase._appmode import AppMode from babase._accountv2 import AccountV2Handle from babase._plugin import PotentialPlugin, Plugin, PluginSubsystem from babase._app import App @@ -155,6 +157,10 @@ __all__ = [ 'displaytimer', 'displaytime', 'DisplayTimer', + 'AppIntent', + 'AppIntentDefault', + 'AppIntentExec', + 'AppMode', ] diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index cbd749b1..652ffeed 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -9,6 +9,7 @@ from typing import TYPE_CHECKING from concurrent.futures import ThreadPoolExecutor from functools import cached_property +from efro.call import tpartial import _babase from babase._language import LanguageSubsystem from babase._plugin import PluginSubsystem @@ -16,22 +17,28 @@ from babase._meta import MetadataSubsystem from babase._net import NetworkSubsystem from babase._workspace import WorkspaceSubsystem from babase._appcomponent import AppComponentSubsystem +from babase._appmodeselector import AppModeSelector +from babase._appintent import AppIntentDefault, AppIntentExec if TYPE_CHECKING: - from typing import Any import asyncio + from typing import Any, Callable + from concurrent.futures import Future from efro.log import LogHandler import babase from babase._cloud import CloudSubsystem from babase._accountv2 import AccountV2Subsystem from babase._apputils import AppHealthMonitor + from babase._appintent import AppIntent + from babase._appmode import AppMode + + # WOULD-AUTOGEN-BEGIN - # Would autogen this begin from baclassic import ClassicSubsystem from baplus import PlusSubsystem - # Would autogen this end + # WOULD-AUTOGEN-END class App: @@ -275,7 +282,15 @@ class App: self.lang = LanguageSubsystem() self.net = NetworkSubsystem() self.workspaces = WorkspaceSubsystem() - # self._classic: ClassicSubsystem | None = None + self._pending_intent: AppIntent | None = None + self._intent: AppIntent | None = None + self._mode: AppMode | None = None + + # Controls which app-modes we use for handling given app-intents. + # Plugins can override this to change high level app behavior and + # spinoff projects can change the default implementation for the + # same effect. + self.mode_selector: AppModeSelector | None = None self._asyncio_timer: babase.AppTimer | None = None @@ -298,11 +313,28 @@ class App: # if classic_subsystem_type is not None: # self._classic = classic_subsystem_type() - # Would autogen this begin + def _threadpool_no_wait_done(self, fut: Future) -> None: + try: + fut.result() + except Exception: + logging.exception( + 'Error in work submitted via threadpool_submit_no_wait()' + ) + + def threadpool_submit_no_wait(self, call: Callable[[], Any]) -> None: + """Submit work to our threadpool and log any errors. + + Use this when you want to run something asynchronously but don't + intend to call result() on it to handle errors/etc. + """ + fut = self.threadpool.submit(call) + fut.add_done_callback(self._threadpool_no_wait_done) + + # WOULD-AUTOGEN-BEGIN @cached_property def classic(self) -> ClassicSubsystem | None: - """Our classic subsystem.""" + """Our classic subsystem (if available).""" try: from baclassic import ClassicSubsystem @@ -316,7 +348,7 @@ class App: @cached_property def plus(self) -> PlusSubsystem | None: - """Our plus subsystem.""" + """Our plus subsystem (if available).""" try: from baplus import PlusSubsystem @@ -328,7 +360,96 @@ class App: logging.exception('Error importing baplus') return None - # Would autogen this begin + # WOULD-AUTOGEN-END + + def set_intent(self, intent: AppIntent) -> None: + """Set the intent for the app. + + Intent defines what the app is trying to do at a given time. + This call is asynchronous; the intent switch will happen in the + logic thread in the near future. If set_intent is + called repeatedly before the change takes place, the last intent + set will be used. + """ + + # Mark this one as pending. We do this synchronously so that the + # last one marked actually takes effect if there is overlap + # (doing this in the bg thread could result in race conditions). + self._pending_intent = intent + + # Do the actual work of calcing our app-mode/etc. in a bg thread + # since it may block for a moment to load modules/etc. + self.threadpool_submit_no_wait(tpartial(self._set_intent, intent)) + + def _set_intent(self, intent: AppIntent) -> None: + # This should be running in a bg thread. + assert not _babase.in_logic_thread() + try: + # Ask the selector what app-mode to use for this intent. + if self.mode_selector is None: + raise RuntimeError('No AppModeSelector set.') + modetype = self.mode_selector.app_mode_for_intent(intent) + + # Make sure the app-mode they return *actually* supports the + # intent. + if not modetype.supports_intent(intent): + raise RuntimeError( + f'Intent {intent} is not supported by AppMode class' + f' {modetype}' + ) + + # Kick back to the logic thread to apply. + mode = modetype() + _babase.pushcall( + tpartial(self._apply_intent, intent, mode), + from_other_thread=True, + ) + except Exception: + logging.exception('Error setting app intent to %s.', intent) + _babase.pushcall( + tpartial(self._apply_intent_error, intent), + from_other_thread=True, + ) + + def _apply_intent(self, intent: AppIntent, mode: AppMode) -> None: + assert _babase.in_logic_thread() + + # ONLY apply this intent if it is still the most recent one + # submitted. + if intent is not self._pending_intent: + return + + # If the app-mode for this intent is different than the active + # one, switch. + # pylint: disable=unidiomatic-typecheck + if type(mode) is not type(self._mode): + if self._mode is not None: + try: + self._mode.on_deactivate() + except Exception: + logging.exception( + 'Error deactivating app-mode %s.', self._mode + ) + self._mode = mode + try: + mode.on_activate() + except Exception: + # Hmm; what should we do in this case?... + logging.exception('Error activating app-mode %s.', mode) + + try: + mode.handle_intent(intent) + except Exception: + logging.exception( + 'Error handling intent %s in app-mode %s.', intent, mode + ) + + def _apply_intent_error(self, intent: AppIntent) -> None: + from babase._language import Lstr + + del intent # Unused. + _babase.screenmessage(Lstr(resource='errorText'), color=(1, 0, 0)) + _babase.getsimplesound('error').play() def run(self) -> None: """Run the app to completion. @@ -381,12 +502,40 @@ class App: def on_app_loading(self) -> None: """Called when initially entering the loading state.""" + assert _babase.in_logic_thread() + + class DefaultAppModeSelector(AppModeSelector): + """Decides which app modes to use to handle intents.""" + + def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode]: + # WOULD-AUTOGEN-BEGIN + + import bascenev1 + + return bascenev1.SceneV1AppMode + + # WOULD-AUTOGEN-END def on_app_running(self) -> None: """Called when initially entering the running state.""" + assert _babase.in_logic_thread() + + # Set a default app-mode-selector. Plugins can then override + # this if they want. + self.mode_selector = self.DefaultAppModeSelector() self.plugins.on_app_running() + # If 'exec' code was provided to the app, always kick that off + # here. + exec_cmd = _babase.exec_arg() + if exec_cmd is not None: + self.set_intent(AppIntentExec(exec_cmd)) + elif self._pending_intent is None: + # Otherwise tell the app to do its default thing *only* if a + # plugin hasn't already told it to do something. + self.set_intent(AppIntentDefault()) + def on_app_bootstrapping_complete(self) -> None: """Called by the C++ layer once its ready to rock.""" assert _babase.in_logic_thread() diff --git a/src/assets/ba_data/python/babase/_appcomponent.py b/src/assets/ba_data/python/babase/_appcomponent.py index bbd4cc73..d1c844fc 100644 --- a/src/assets/ba_data/python/babase/_appcomponent.py +++ b/src/assets/ba_data/python/babase/_appcomponent.py @@ -19,19 +19,21 @@ class AppComponentSubsystem: Category: **App Classes** This subsystem acts as a registry for classes providing particular - functionality for the app, and allows plugins or other custom code to - easily override said functionality. + functionality for the app, and allows plugins or other custom code + to easily override said functionality. - Use babase.app.components to get the single shared instance of this class. + Access the single shared instance of this class at + babase.app.components. - The general idea with this setup is that a base-class is defined to - provide some functionality and then anyone wanting that functionality - uses the getclass() method with that base class to return the current - registered implementation. The user should not know or care whether - they are getting the base class itself or some other implementation. + The general idea with this setup is that a base-class Foo is defined + to provide some functionality and then anyone wanting that + functionality calls getclass(Foo) to return the current registered + implementation. The user should not know or care whether they are + getting Foo itself or some subclass of it. Change-callbacks can also be requested for base classes which will - fire in a deferred manner when particular base-classes are overridden. + fire in a deferred manner when particular base-classes are + overridden. """ def __init__(self) -> None: @@ -45,8 +47,9 @@ class AppComponentSubsystem: The provided implementation class must be a subclass of baseclass. """ - # Currently limiting this to logic-thread use; can revisit if needed - # (would need to guard access to our implementations dict). + # Currently limiting this to logic-thread use; can revisit if + # needed (would need to guard access to our implementations + # dict). assert _babase.in_logic_thread() if not issubclass(implementation, baseclass): @@ -58,16 +61,17 @@ class AppComponentSubsystem: self._implementations[baseclass] = implementation # If we're the first thing getting dirtied, set up a callback to - # clean everything. And add ourself to the dirty list regardless. + # clean everything. And add ourself to the dirty list + # regardless. if not self._dirty_base_classes: _babase.pushcall(self._run_change_callbacks) self._dirty_base_classes.add(baseclass) def getclass(self, baseclass: T) -> T: - """Given a base-class, return the currently set implementation class. + """Given a base-class, return the current implementation class. - If no custom implementation has been set, the provided base-class - is returned. + If no custom implementation has been set, the provided + base-class is returned. """ assert _babase.in_logic_thread() @@ -77,7 +81,7 @@ class AppComponentSubsystem: def register_change_callback( self, baseclass: T, callback: Callable[[T], None] ) -> None: - """Register a callback to fire when a class implementation changes. + """Register a callback to fire on class implementation changes. The callback will be scheduled to run in the logic thread event loop. Note that any further setclass calls before the callback diff --git a/src/assets/ba_data/python/babase/_appintent.py b/src/assets/ba_data/python/babase/_appintent.py new file mode 100644 index 00000000..76399af3 --- /dev/null +++ b/src/assets/ba_data/python/babase/_appintent.py @@ -0,0 +1,27 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Provides AppIntent functionality.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + pass + + +class AppIntent: + """A high level directive given to the app. + + Category: **App Classes** + """ + + +class AppIntentDefault(AppIntent): + """Tells the app to simply run in its default mode.""" + + +class AppIntentExec(AppIntent): + """Tells the app to exec some Python code.""" + + def __init__(self, code: str): + self.code = code diff --git a/src/assets/ba_data/python/babase/_appmode.py b/src/assets/ba_data/python/babase/_appmode.py new file mode 100644 index 00000000..fd53e126 --- /dev/null +++ b/src/assets/ba_data/python/babase/_appmode.py @@ -0,0 +1,35 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Provides AppMode functionality.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from babase._appintent import AppIntent + + +class AppMode: + """A high level mode for the app. + + Category: **App Classes** + + """ + + @classmethod + def supports_intent(cls, intent: AppIntent) -> bool: + """Return whether our mode can handle the provided intent.""" + del intent + + # Say no to everything by default. Let's make mode explicitly + # lay out everything they *do* support. + return False + + def handle_intent(self, intent: AppIntent) -> None: + """Handle an intent.""" + + def on_activate(self) -> None: + """Called when the mode is being activated.""" + + def on_deactivate(self) -> None: + """Called when the mode is being deactivated.""" diff --git a/src/assets/ba_data/python/babase/_appmodeselector.py b/src/assets/ba_data/python/babase/_appmodeselector.py new file mode 100644 index 00000000..3ccefe1d --- /dev/null +++ b/src/assets/ba_data/python/babase/_appmodeselector.py @@ -0,0 +1,34 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Provides AppMode functionality.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from babase._appintent import AppIntent + from babase._appmode import AppMode + + +class AppModeSelector: + """Defines which AppModes to use to handle given AppIntents. + + Category: **App Classes** + + The app calls an instance of this class when passed an AppIntent to + determine which AppMode to use to handle the intent. Plugins or + spinoff projects can modify high level app behavior by replacing or + modifying this. + """ + + # pylint: disable=useless-return + + def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode]: + """Given an AppIntent, return the AppMode that should handle it. + + If None is returned, the AppIntent will be ignored. + + This is called in a background thread, so avoid any calls + limited to logic thread use/etc. + """ + raise RuntimeError('app_mode_for_intent() should be overridden.') diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index e8075868..4f42aeb7 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -31,7 +31,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21022 +TARGET_BALLISTICA_BUILD = 21023 TARGET_BALLISTICA_VERSION = '1.7.20' _g_env_config: EnvConfig | None = None diff --git a/src/assets/ba_data/python/bascenev1/__init__.py b/src/assets/ba_data/python/bascenev1/__init__.py index fb01b078..63e212fc 100644 --- a/src/assets/ba_data/python/bascenev1/__init__.py +++ b/src/assets/ba_data/python/bascenev1/__init__.py @@ -36,6 +36,8 @@ from _babase import ( displaytimer, DisplayTimer, ) +from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec +from babase._appmode import AppMode from babase._error import NotFoundError, NodeNotFoundError, ContextError from babase._language import Lstr from babase._general import ( @@ -55,7 +57,6 @@ from babase._mgen.enums import ( InputType, ) - from _bascenev1 import ( get_foreground_host_session, get_foreground_host_activity, @@ -135,6 +136,7 @@ from _bascenev1 import ( ) +from bascenev1._appmode import SceneV1AppMode from bascenev1._session import Session from bascenev1._map import Map from bascenev1._coopsession import CoopSession @@ -200,11 +202,6 @@ from bascenev1._dependency import ( AssetPackage, ) -# if TYPE_CHECKING: -# from babase._app import App - -# app: App - __all__ = [ 'app', 'get_local_active_input_devices_count', @@ -383,6 +380,11 @@ __all__ = [ 'DisplayTimer', 'Time', 'BaseTime', + 'AppIntent', + 'AppIntentDefault', + 'AppIntentExec', + 'AppMode', + 'SceneV1AppMode', ] # Sanity check: we want to keep ballistica's dependencies and diff --git a/src/assets/ba_data/python/bascenev1/_appmode.py b/src/assets/ba_data/python/bascenev1/_appmode.py new file mode 100644 index 00000000..9988708a --- /dev/null +++ b/src/assets/ba_data/python/bascenev1/_appmode.py @@ -0,0 +1,36 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Provides AppMode functionality.""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from babase import AppMode, AppIntentExec, AppIntentDefault +import _bascenev1 + +if TYPE_CHECKING: + from babase import AppIntent + + +class SceneV1AppMode(AppMode): + """Our app-mode.""" + + @classmethod + def supports_intent(cls, intent: AppIntent) -> bool: + # We support default and exec intents currently. + return isinstance(intent, AppIntentExec | AppIntentDefault) + + def handle_intent(self, intent: AppIntent) -> None: + if isinstance(intent, AppIntentExec): + _bascenev1.handle_app_intent_exec(intent.code) + return + assert isinstance(intent, AppIntentDefault) + _bascenev1.handle_app_intent_default() + + def on_activate(self) -> None: + # Let the native layer do its thing. + _bascenev1.app_mode_activate() + + def on_deactivate(self) -> None: + # Let the native layer do its thing. + _bascenev1.app_mode_deactivate() diff --git a/src/assets/ba_data/python/bauiv1/__init__.py b/src/assets/ba_data/python/bauiv1/__init__.py index c92590ac..d24800d8 100644 --- a/src/assets/ba_data/python/bauiv1/__init__.py +++ b/src/assets/ba_data/python/bauiv1/__init__.py @@ -58,6 +58,8 @@ from _babase import ( ) from _babase import screenmessage +from babase._appintent import AppIntent, AppIntentDefault, AppIntentExec +from babase._appmode import AppMode from babase._general import Call, WeakCall, AppTime, DisplayTime from babase._language import Lstr from babase._plugin import PotentialPlugin, Plugin @@ -201,6 +203,10 @@ __all__ = [ 'displaytimer', 'DisplayTimer', 'uibounds', + 'AppIntent', + 'AppIntentDefault', + 'AppIntentExec', + 'AppMode', ] # Sanity check: we want to keep ballistica's dependencies and diff --git a/src/ballistica/base/assets/assets.cc b/src/ballistica/base/assets/assets.cc index 3d47201e..37c15917 100644 --- a/src/ballistica/base/assets/assets.cc +++ b/src/ballistica/base/assets/assets.cc @@ -1326,7 +1326,7 @@ void Assets::SetLanguageKeys( } // Let some subsystems know that language has changed. - g_base->app_mode->LanguageChanged(); + g_base->app_mode()->LanguageChanged(); g_base->ui->LanguageChanged(); g_base->graphics->LanguageChanged(); } diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index ce2864f4..9f0e172a 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -71,8 +71,8 @@ BaseFeatureSet::BaseFeatureSet() text_graphics{new TextGraphics()}, audio_server{new AudioServer()}, assets{new Assets()}, - app_mode{TempSV1CreateAppMode()}, - // app_mode{AppModeEmpty::GetSingleton()}, + // app_mode{TempSV1CreateAppMode()}, + app_mode_{AppModeEmpty::GetSingleton()}, stdio_console{g_buildconfig.enable_stdio_console() ? new StdioConsole() : nullptr} { // We're a singleton. If there's already one of us, something's wrong. @@ -202,6 +202,11 @@ void BaseFeatureSet::StartApp() { g_core->BootLog("start-app end"); } +void BaseFeatureSet::set_app_mode(AppMode* mode) { + assert(InLogicThread()); + app_mode_ = mode; +} + auto BaseFeatureSet::AppManagesEventLoop() -> bool { return app->ManagesEventLoop(); } diff --git a/src/ballistica/base/base.h b/src/ballistica/base/base.h index a047523d..65ded963 100644 --- a/src/ballistica/base/base.h +++ b/src/ballistica/base/base.h @@ -702,9 +702,11 @@ class BaseFeatureSet : public FeatureSetNativeComponent, UI* const ui; Utils* const utils; + auto* console() const { return console_; } + auto* app_mode() const { return app_mode_; } + void set_app_mode(AppMode* mode); + // Non-const bits (fixme: clean up access to these). - AppMode* app_mode; - auto* console() { return console_; } TouchInput* touch_input{}; private: @@ -713,6 +715,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent, void PrintContextForCallableLabel(const char* label); void PrintContextUnavailable(); + AppMode* app_mode_; Console* console_{}; std::string console_startup_messages_; bool called_start_app_{}; diff --git a/src/ballistica/base/graphics/graphics.cc b/src/ballistica/base/graphics/graphics.cc index d00e2a91..0e4af3ba 100644 --- a/src/ballistica/base/graphics/graphics.cc +++ b/src/ballistica/base/graphics/graphics.cc @@ -416,7 +416,7 @@ void Graphics::DrawMiscOverlays(RenderPass* pass) { } if (show_ping_) { - std::string ping_str = g_base->app_mode->GetPingString(); + std::string ping_str = g_base->app_mode()->GetPingString(); float ping{}; if (!ping_str.empty()) { if (ping_str != ping_string_) { @@ -452,7 +452,7 @@ void Graphics::DrawMiscOverlays(RenderPass* pass) { } if (show_net_info_) { - auto net_info_str{g_base->app_mode->GetNetworkDebugString()}; + auto net_info_str{g_base->app_mode()->GetNetworkDebugString()}; if (!net_info_str.empty()) { if (net_info_str != net_info_string_) { net_info_string_ = net_info_str; @@ -908,6 +908,7 @@ void Graphics::AddScreenMessage(const std::string& msg, const Vector3f& color, } void Graphics::Reset() { + assert(g_base->InLogicThread()); fade_ = 0; fade_start_ = 0; @@ -1075,7 +1076,7 @@ void Graphics::DrawWorld(FrameDef* frame_def) { // Draw all session contents (nodes, etc.) overlay_node_z_depth_ = -0.95f; - g_base->app_mode->DrawWorld(frame_def); + g_base->app_mode()->DrawWorld(frame_def); g_base->bg_dynamics->Draw(frame_def); // Lastly draw any blotches that have been building up. @@ -1131,7 +1132,7 @@ void Graphics::BuildAndPushFrameDef() { // wants to know. if (last_frame_def_graphics_quality_ != frame_def->quality()) { last_frame_def_graphics_quality_ = frame_def->quality(); - g_base->app_mode->GraphicsQualityChanged(frame_def->quality()); + g_base->app_mode()->GraphicsQualityChanged(frame_def->quality()); } ApplyCamera(frame_def); @@ -1142,7 +1143,7 @@ void Graphics::BuildAndPushFrameDef() { } else { // Ok, we're drawing a real frame. - bool session_fills_screen = g_base->app_mode->DoesWorldFillScreen(); + bool session_fills_screen = g_base->app_mode()->DoesWorldFillScreen(); frame_def->set_needs_clear(!session_fills_screen); DrawWorld(frame_def); diff --git a/src/ballistica/base/input/device/joystick_input.cc b/src/ballistica/base/input/device/joystick_input.cc index 1b7c9781..ed7e6e5e 100644 --- a/src/ballistica/base/input/device/joystick_input.cc +++ b/src/ballistica/base/input/device/joystick_input.cc @@ -995,8 +995,8 @@ void JoystickInput::HandleSDLEvent(const SDL_Event* e) { } else { // FIXME: Need a call we can make for this. bool do_party_button = false; - int party_size = g_base->app_mode->GetPartySize(); - if (party_size > 1 || g_base->app_mode->HasConnectionToHost() + int party_size = g_base->app_mode()->GetPartySize(); + if (party_size > 1 || g_base->app_mode()->HasConnectionToHost() || g_base->ui->root_ui()->always_draw_party_icon()) { do_party_button = true; } diff --git a/src/ballistica/base/input/input.cc b/src/ballistica/base/input/input.cc index 1efa8bd5..1e1762d0 100644 --- a/src/ballistica/base/input/input.cc +++ b/src/ballistica/base/input/input.cc @@ -266,7 +266,7 @@ void Input::AddInputDevice(InputDevice* device, bool standard_message) { // Let the current app-mode assign it a delegate. auto delegate = Object::CompleteDeferred( - g_base->app_mode->CreateInputDeviceDelegate(device)); + g_base->app_mode()->CreateInputDeviceDelegate(device)); device->set_delegate(delegate); delegate->set_input_device(device); @@ -980,12 +980,12 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { case SDLK_EQUALS: case SDLK_PLUS: - g_base->app_mode->ChangeGameSpeed(1); + g_base->app_mode()->ChangeGameSpeed(1); handled = true; break; case SDLK_MINUS: - g_base->app_mode->ChangeGameSpeed(-1); + g_base->app_mode()->ChangeGameSpeed(-1); handled = true; break; diff --git a/src/ballistica/base/logic/logic.cc b/src/ballistica/base/logic/logic.cc index 3e206943..4c359caa 100644 --- a/src/ballistica/base/logic/logic.cc +++ b/src/ballistica/base/logic/logic.cc @@ -63,7 +63,7 @@ void Logic::OnAppStart() { g_base->input->OnAppStart(); g_base->ui->OnAppStart(); g_core->platform->OnAppStart(); - g_base->app_mode->OnAppStart(); + g_base->app_mode()->OnAppStart(); if (g_base->HavePlus()) { g_base->Plus()->OnAppStart(); } @@ -91,7 +91,7 @@ void Logic::OnAppPause() { if (g_base->HavePlus()) { g_base->Plus()->OnAppPause(); } - g_base->app_mode->OnAppPause(); + g_base->app_mode()->OnAppPause(); g_core->platform->OnAppPause(); g_base->ui->OnAppPause(); g_base->input->OnAppPause(); @@ -108,7 +108,7 @@ void Logic::OnAppResume() { g_base->input->OnAppResume(); g_base->ui->OnAppResume(); g_core->platform->OnAppResume(); - g_base->app_mode->OnAppResume(); + g_base->app_mode()->OnAppResume(); if (g_base->HavePlus()) { g_base->Plus()->OnAppResume(); } @@ -127,7 +127,7 @@ void Logic::OnAppShutdown() { if (g_base->HavePlus()) { g_base->Plus()->OnAppShutdown(); } - g_base->app_mode->OnAppShutdown(); + g_base->app_mode()->OnAppShutdown(); g_core->platform->OnAppResume(); g_base->ui->OnAppShutdown(); g_base->input->OnAppShutdown(); @@ -152,7 +152,7 @@ void Logic::ApplyAppConfig() { g_base->input->ApplyAppConfig(); g_base->ui->ApplyAppConfig(); g_core->platform->ApplyAppConfig(); - g_base->app_mode->ApplyAppConfig(); + g_base->app_mode()->ApplyAppConfig(); if (g_base->HavePlus()) { g_base->Plus()->ApplyAppConfig(); } @@ -203,7 +203,7 @@ void Logic::OnInitialScreenCreated() { 1000 / 10, true, NewLambdaRunnable([this] { StepDisplayTime(); })); } // Let our initial app-mode know it has become active. - g_base->app_mode->OnActivate(); + g_base->app_mode()->OnActivate(); // Let the Python layer know what's up. It will probably flip to // 'Launching' state. @@ -224,6 +224,13 @@ void Logic::CompleteAppBootstrapping() { g_core->BootLog("app bootstrapping complete"); + // Reset our various subsystems to a default state. + g_base->ui->Reset(); + g_base->input->Reset(); + g_base->graphics->Reset(); + g_base->python->Reset(); + g_base->audio->Reset(); + // Let Python know we're done bootstrapping so it can flip the app // into the 'launching' state. g_base->python->objs() @@ -232,7 +239,7 @@ void Logic::CompleteAppBootstrapping() { app_bootstrapping_complete_ = true; // TODO(ericf): update this for the shiny new app-mode world. - if (explicit_bool(true)) { + if (explicit_bool(false)) { // If we were passed launch command args, run them. if (g_core->core_config().exec_command.has_value()) { bool success = PythonCommand(*g_core->core_config().exec_command, @@ -248,6 +255,8 @@ void Logic::CompleteAppBootstrapping() { if (!appmode->GetForegroundSession()) { appmode->RunMainMenu(); } + } else { + // Reset various subsystems } UpdatePendingWorkTimer(); @@ -267,7 +276,7 @@ void Logic::OnScreenSizeChange(float virtual_width, float virtual_height, g_base->input->OnScreenSizeChange(); g_base->ui->OnScreenSizeChange(); g_core->platform->OnScreenSizeChange(); - g_base->app_mode->OnScreenSizeChange(); + g_base->app_mode()->OnScreenSizeChange(); if (g_base->HavePlus()) { g_base->Plus()->OnScreenSizeChange(); } @@ -287,7 +296,7 @@ void Logic::StepDisplayTime() { g_base->input->StepDisplayTime(); g_base->ui->StepDisplayTime(); g_core->platform->StepDisplayTime(); - g_base->app_mode->StepDisplayTime(); + g_base->app_mode()->StepDisplayTime(); if (g_base->HavePlus()) { g_base->Plus()->StepDisplayTime(); } diff --git a/src/ballistica/base/networking/network_reader.cc b/src/ballistica/base/networking/network_reader.cc index 489ca4db..788c2251 100644 --- a/src/ballistica/base/networking/network_reader.cc +++ b/src/ballistica/base/networking/network_reader.cc @@ -234,7 +234,7 @@ auto NetworkReader::RunThread() -> int { memcpy(s_buffer.data(), buffer + 1, rresult2 - 1); s_buffer[rresult2 - 1] = 0; // terminate string std::string response = - g_base->app_mode->HandleJSONPing(s_buffer.data()); + g_base->app_mode()->HandleJSONPing(s_buffer.data()); if (!response.empty()) { std::vector msg(1 + response.size()); msg[0] = BA_PACKET_JSON_PONG; @@ -302,7 +302,7 @@ auto NetworkReader::RunThread() -> int { } case BA_PACKET_HOST_QUERY: { - g_base->app_mode->HandleGameQuery(buffer, rresult2, &from); + g_base->app_mode()->HandleGameQuery(buffer, rresult2, &from); // HandleGameQuery(buffer, rresult2, &from); break; @@ -338,8 +338,9 @@ void NetworkReader::PushIncomingUDPPacketCall(const std::vector& data, return; } - g_base->logic->event_loop()->PushCall( - [data, addr] { g_base->app_mode->HandleIncomingUDPPacket(data, addr); }); + g_base->logic->event_loop()->PushCall([data, addr] { + g_base->app_mode()->HandleIncomingUDPPacket(data, addr); + }); } void NetworkReader::OpenSockets() { diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index ae7c9dbf..121eb92c 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -239,7 +239,7 @@ static auto PyPushCall(PyObject* self, PyObject* args, PyObject* keywds) // Run this with an empty context by default, or foreground if // requested. ScopedSetContext ssc(other_thread_use_fg_context - ? g_base->app_mode->GetForegroundContext() + ? g_base->app_mode()->GetForegroundContext() : ContextRef(nullptr)); PythonRef(call_obj, PythonRef::kSteal).Call(); @@ -1243,6 +1243,28 @@ static PyMethodDef PyIsOSPlayingMusicDef = { "(Used to determine whether the game should avoid playing its own)", }; +// -------------------------------- exec_arg ----------------------------------- + +static auto PyExecArg(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + + if (g_core->core_config().exec_command.has_value()) { + return PyUnicode_FromString(g_core->core_config().exec_command->c_str()); + } + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyExecArgDef = { + "exec_arg", // name + (PyCFunction)PyExecArg, // method + METH_NOARGS, // flags + + "exec_arg() -> str | None\n" + "\n" + "(internal)\n", +}; + // ----------------------------------------------------------------------------- auto PythonMethodsApp::GetMethods() -> std::vector { @@ -1281,6 +1303,7 @@ auto PythonMethodsApp::GetMethods() -> std::vector { PyMacMusicAppGetPlaylistsDef, PyIsOSPlayingMusicDef, PyBootLogDef, + PyExecArgDef, }; } diff --git a/src/ballistica/base/support/classic_soft.h b/src/ballistica/base/support/classic_soft.h new file mode 100644 index 00000000..dd35cb4d --- /dev/null +++ b/src/ballistica/base/support/classic_soft.h @@ -0,0 +1,18 @@ +// Released under the MIT License. See LICENSE for details. + +#ifndef BALLISTICA_BASE_SUPPORT_CLASSIC_SOFT_H_ +#define BALLISTICA_BASE_SUPPORT_CLASSIC_SOFT_H_ + +namespace ballistica::base { + +/// 'Soft' interface to the classic feature-set. +/// Feature-sets listing classic as a soft requirement must limit their use of +/// it to these methods and should be prepared to handle the not-present +/// case. +class ClassicSoftInterface { + public: +}; + +} // namespace ballistica::base + +#endif // BALLISTICA_BASE_SUPPORT_CLASSIC_SOFT_H_ diff --git a/src/ballistica/base/support/stdio_console.cc b/src/ballistica/base/support/stdio_console.cc index 30084ca9..7c84d6e8 100644 --- a/src/ballistica/base/support/stdio_console.cc +++ b/src/ballistica/base/support/stdio_console.cc @@ -107,7 +107,7 @@ void StdioConsole::OnMainThreadStartApp() { void StdioConsole::PushCommand(const std::string& command) { g_base->logic->event_loop()->PushCall([command] { // These are always run in whichever context is 'visible'. - ScopedSetContext ssc(g_base->app_mode->GetForegroundContext()); + ScopedSetContext ssc(g_base->app_mode()->GetForegroundContext()); PythonCommand cmd(command, ""); if (!g_core->user_ran_commands) { g_core->user_ran_commands = true; diff --git a/src/ballistica/base/ui/console.cc b/src/ballistica/base/ui/console.cc index a1d58b2d..d204085a 100644 --- a/src/ballistica/base/ui/console.cc +++ b/src/ballistica/base/ui/console.cc @@ -146,7 +146,7 @@ void Console::PushCommand(const std::string& command) { assert(g_base); g_base->logic->event_loop()->PushCall([command] { // These are always run in whichever context is 'visible'. - ScopedSetContext ssc(g_base->app_mode->GetForegroundContext()); + ScopedSetContext ssc(g_base->app_mode()->GetForegroundContext()); PythonCommand cmd(command, ""); if (!g_core->user_ran_commands) { g_core->user_ran_commands = true; diff --git a/src/ballistica/scene_v1/python/methods/python_methods_networking.cc b/src/ballistica/scene_v1/python/methods/python_methods_networking.cc index c9230427..b254752b 100644 --- a/src/ballistica/scene_v1/python/methods/python_methods_networking.cc +++ b/src/ballistica/scene_v1/python/methods/python_methods_networking.cc @@ -660,7 +660,7 @@ static PyMethodDef PyEndHostScanningDef = { static auto PyHaveConnectedClients(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; - if (g_base->app_mode->HasConnectionToClients()) { + if (g_base->app_mode()->HasConnectionToClients()) { Py_RETURN_TRUE; } else { Py_RETURN_FALSE; diff --git a/src/ballistica/scene_v1/python/methods/python_methods_scene.cc b/src/ballistica/scene_v1/python/methods/python_methods_scene.cc index 2fdf91f3..90c69a9f 100644 --- a/src/ballistica/scene_v1/python/methods/python_methods_scene.cc +++ b/src/ballistica/scene_v1/python/methods/python_methods_scene.cc @@ -30,6 +30,7 @@ #include "ballistica/scene_v1/support/session_stream.h" #include "ballistica/shared/generic/json.h" #include "ballistica/shared/generic/utils.h" +#include "ballistica/shared/python/python_command.h" namespace ballistica::scene_v1 { @@ -1611,6 +1612,109 @@ static PyMethodDef PySetInternalMusicDef = { "(internal).", }; +// --------------------------- app_mode_activate ------------------------------- + +static auto PyAppModeActivate(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + BA_PRECONDITION(g_base->InLogicThread()); + g_base->set_app_mode(SceneV1AppMode::GetSingleton()); + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyAppModeActivateDef = { + "app_mode_activate", // name + (PyCFunction)PyAppModeActivate, // method + METH_NOARGS, // flags + + "app_mode_activate() -> None\n" + "\n" + "(internal)\n", +}; + +// -------------------------- app_mode_deactivate ------------------------------ + +static auto PyAppModeDeactivate(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + BA_PRECONDITION(g_base->InLogicThread()); + // Currently doing nothing. + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyAppModeDeactivateDef = { + "app_mode_deactivate", // name + (PyCFunction)PyAppModeDeactivate, // method + METH_NOARGS, // flags + + "app_mode_deactivate() -> None\n" + "\n" + "(internal)\n", +}; + +// ----------------------- handle_app_intent_default --------------------------- + +static auto PyHandleAppIntentDefault(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + BA_PRECONDITION(g_base->InLogicThread()); + auto* appmode = SceneV1AppMode::GetActiveOrThrow(); + appmode->RunMainMenu(); + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyHandleAppIntentDefaultDef = { + "handle_app_intent_default", // name + (PyCFunction)PyHandleAppIntentDefault, // method + METH_NOARGS, // flags + + "handle_app_intent_default() -> None\n" + "\n" + "(internal)\n", +}; + +// ------------------------ handle_app_intent_exec ----------------------------- + +static auto PyHandleAppIntentExec(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { + BA_PYTHON_TRY; + const char* command; + static const char* kwlist[] = {"command", nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "s", + const_cast(kwlist), &command)) { + return nullptr; + } + auto* appmode = SceneV1AppMode::GetActiveOrThrow(); + + // Run the command. + if (g_core->core_config().exec_command.has_value()) { + bool success = PythonCommand(*g_core->core_config().exec_command, + BA_BUILD_COMMAND_FILENAME) + .Exec(true, nullptr, nullptr); + if (!success) { + // FIXME: what should we do in this case? + // exit(1); + } + } + // If the stuff we just ran didn't result in a session, create a default + // one. + if (!appmode->GetForegroundSession()) { + appmode->RunMainMenu(); + } + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyHandleAppIntentExecDef = { + "handle_app_intent_exec", // name + (PyCFunction)PyHandleAppIntentExec, // method + METH_VARARGS | METH_KEYWORDS, // flags + + "handle_app_intent_exec(command: str) -> None\n" + "\n" + "(internal)", +}; + // ----------------------------------------------------------------------------- auto PythonMethodsScene::GetMethods() -> std::vector { @@ -1646,6 +1750,10 @@ auto PythonMethodsScene::GetMethods() -> std::vector { PyBaseTimeDef, PyBaseTimerDef, PyLsInputDevicesDef, + PyAppModeActivateDef, + PyAppModeDeactivateDef, + PyHandleAppIntentDefaultDef, + PyHandleAppIntentExecDef, }; } 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 c2fb1542..d3f1c799 100644 --- a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc +++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc @@ -315,7 +315,7 @@ auto SceneV1AppMode::GetActive() -> SceneV1AppMode* { // keep in mind that app-mode may change under them. // Otherwise return our singleton only if it is current. - if (g_base->app_mode == g_scene_v1_app_mode) { + if (g_base->app_mode() == g_scene_v1_app_mode) { return g_scene_v1_app_mode; } return nullptr; @@ -1389,7 +1389,7 @@ void SceneV1AppMode::HandleGameQuery(const char* buffer, size_t size, if (size == 5) { // If we're already in a party, don't advertise since they // wouldn't be able to join us anyway. - if (g_base->app_mode->HasConnectionToHost()) { + if (g_base->app_mode()->HasConnectionToHost()) { return; } diff --git a/src/ballistica/scene_v1/support/scene_v1_context.cc b/src/ballistica/scene_v1/support/scene_v1_context.cc index 8f1dc681..28f4f930 100644 --- a/src/ballistica/scene_v1/support/scene_v1_context.cc +++ b/src/ballistica/scene_v1/support/scene_v1_context.cc @@ -10,7 +10,7 @@ namespace ballistica::scene_v1 { auto ContextRefSceneV1::FromAppForegroundContext() -> ContextRefSceneV1 { - auto* c = g_base->app_mode->GetForegroundContext().Get(); + auto* c = g_base->app_mode()->GetForegroundContext().Get(); return ContextRefSceneV1(c); } diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 7acbaf8e..71d9a164 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 = 21022; +const int kEngineBuildNumber = 21023; const char* kEngineVersion = "1.7.20"; auto MonolithicMain(const core::CoreConfig& core_config) -> int { 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 f15a3192..59c170d5 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 @@ -2665,8 +2665,8 @@ static auto PyCanShowAd(PyObject* self, PyObject* args, PyObject* keywds) // them or whatnot). Also disallow ads if remote apps are connected; at least // on Android, ads pause our activity which disconnects the remote app. // (need to fix this). - if (g_base->app_mode->HasConnectionToHost() - || g_base->app_mode->HasConnectionToClients() + if (g_base->app_mode()->HasConnectionToHost() + || g_base->app_mode()->HasConnectionToClients() || g_base->input->HaveRemoteAppController()) { Py_RETURN_FALSE; } @@ -2873,8 +2873,8 @@ static auto PyIsPartyIconVisible(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { BA_PYTHON_TRY; bool party_button_active = - (g_base->app_mode->HasConnectionToClients() - || g_base->app_mode->HasConnectionToHost() + (g_base->app_mode()->HasConnectionToClients() + || g_base->app_mode()->HasConnectionToHost() || g_base->ui->root_ui()->always_draw_party_icon()); if (party_button_active) { Py_RETURN_TRUE; diff --git a/src/ballistica/ui_v1/support/root_ui.cc b/src/ballistica/ui_v1/support/root_ui.cc index d93bfbf1..e1362ca3 100644 --- a/src/ballistica/ui_v1/support/root_ui.cc +++ b/src/ballistica/ui_v1/support/root_ui.cc @@ -42,8 +42,9 @@ RootUI::~RootUI() = default; void RootUI::TogglePartyWindowKeyPress() { assert(g_base->InLogicThread()); - if (g_base->app_mode->GetPartySize() > 1 - || g_base->app_mode->HasConnectionToHost() || always_draw_party_icon()) { + if (g_base->app_mode()->GetPartySize() > 1 + || g_base->app_mode()->HasConnectionToHost() + || always_draw_party_icon()) { ActivatePartyIcon(); } } @@ -77,8 +78,8 @@ auto RootUI::HandleMouseButtonDown(float x, float y) -> bool { // floats over the top). Party button is to the left of menu button. if (explicit_bool(DO_OLD_MENU_PARTY_BUTTONS)) { bool party_button_active = (!party_window_open_ - && (g_base->app_mode->HasConnectionToClients() - || g_base->app_mode->HasConnectionToHost() + && (g_base->app_mode()->HasConnectionToClients() + || g_base->app_mode()->HasConnectionToHost() || always_draw_party_icon())); float party_button_left = menu_active ? 2 * menu_button_size_ : menu_button_size_; @@ -202,17 +203,17 @@ void RootUI::Draw(base::FrameDef* frame_def) { // To the left of the menu button, draw our connected-players indicator // (this probably shouldn't live here). bool draw_connected_players_icon = false; - int party_size = g_base->app_mode->GetPartySize(); - bool is_host = (!g_base->app_mode->HasConnectionToHost()); + int party_size = g_base->app_mode()->GetPartySize(); + bool is_host = (!g_base->app_mode()->HasConnectionToHost()); millisecs_t last_connection_to_client_join_time = - g_base->app_mode->LastClientJoinTime(); + g_base->app_mode()->LastClientJoinTime(); bool show_client_joined = (is_host && last_connection_to_client_join_time != 0 && real_time - last_connection_to_client_join_time < 5000); if (!party_window_open_ - && (party_size != 0 || g_base->app_mode->HasConnectionToHost() + && (party_size != 0 || g_base->app_mode()->HasConnectionToHost() || always_draw_party_icon_)) { draw_connected_players_icon = true; } @@ -221,7 +222,7 @@ void RootUI::Draw(base::FrameDef* frame_def) { // Flash and show a message if we're in the main menu instructing the // player to start a game. bool flash = false; - bool in_main_menu = g_base->app_mode->InMainMenu(); + bool in_main_menu = g_base->app_mode()->InMainMenu(); if (in_main_menu && party_size > 0 && show_client_joined) flash = true; diff --git a/src/ballistica/ui_v1/widget/root_widget.cc b/src/ballistica/ui_v1/widget/root_widget.cc index 72e59c2b..2fbee847 100644 --- a/src/ballistica/ui_v1/widget/root_widget.cc +++ b/src/ballistica/ui_v1/widget/root_widget.cc @@ -895,7 +895,7 @@ void RootWidget::UpdateForFocusedWindow() { void RootWidget::UpdateForFocusedWindow(Widget* widget) { // Take note if the current session is the main menu; we do a few things // differently there. - in_main_menu_ = g_base->app_mode->InMainMenu(); + in_main_menu_ = g_base->app_mode()->InMainMenu(); if (widget == nullptr) { toolbar_visibility_ = ToolbarVisibility::kInGame; diff --git a/tools/batools/project/_updater.py b/tools/batools/project/_updater.py index 3230dccc..353f0d3e 100755 --- a/tools/batools/project/_updater.py +++ b/tools/batools/project/_updater.py @@ -312,7 +312,10 @@ class ProjectUpdater: ) for i, change in enumerate(auto_changes): - print(f'{Clr.BLU}Correcting file: {change[0]}{Clr.RST}') + print( + f'{Clr.BLU}{Clr.BLD}Correcting' + f' {change[0]} line {change[1].line_number+1}{Clr.RST}' + ) with open( os.path.join(self.projroot, change[0]), encoding='utf-8' ) as infile: