Initial work on AppExperiences

This commit is contained in:
Eric 2023-09-01 17:40:39 -07:00
parent 7b8a98bf98
commit 183c5abf60
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
22 changed files with 262 additions and 188 deletions

56
.efrocachemap generated
View File

@ -4064,26 +4064,26 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "95166586256dc94f679d3f15cf570ed8", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "ca001c9f2b4edf8d4918c1bcaf0cb561",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "820f8a69f86434ff6dab9cc23e675708", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "fcb58aae4535a761be49420f4e4a7711",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "70903ec2f476fbc5908cc52522f69e0c", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "8f453b8193ba06a3cc59374cdd2a1c09",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "442f2bdde3c176d9a4431c9fdcca892e", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "7c389cb83619516ce96118227668500e",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "5354821ede7348ba9bb90bee8fe5ccb7", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "2ffcc486e9ba8f0fd5279083a2e54aff",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "ab0cb59c874beb50e554d3d7c552565e", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "366bab5bff3ff9344e54bf7492f4d7c3",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "dec2c09bd136aa413b9b94729f7d6d2e", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "78277cc1bd40ba88fd1475f8755e0721",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "a5c43eb8befc810d693ec9c9f83fcba4", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "5f063e4d45e40ade22f1bad63658752e",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "56bf9b14517f0a579964b39218498fcf", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f7114c78146f22e89143218c9930b43e",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "d6a10e3a6bc1a07609598f962439c6c8", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "90c0ea0ce7a35d8f15b2a29bef704819",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "ab5cb01ed4a8a8db80ca65ab5cd0133b", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "e1b4932fd7f359404c979604a1e33444",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "231bd6e18145b06028e4f90fd7de106c", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "28ab454f71dfecd1986a245eb4a732e7",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "3cc88ed5471e9b7f2b84af4ee58a8061", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "7b1a4db9c3f36fd428cdafbc1459bc1b",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "4586629e8f114c1b5a22444ae26b5917", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "bc5a48ff49975901f212d18b1dbbb6f5",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "e1177b377c3e48d60057a9719bf9d0e3", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "7742db7c9555907fc7b2d5c89d48dfd0",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "518c529a7133d5ea41d730d12ae156c4", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "d81bc55d666f630a14de90f30be29fb4",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "41e7afae9395843c575c65593aab423c", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5ab68bfee69a6fe07b5b8a68938220e6",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "d95e4cc445b722f111e51b3d4b7bdde9", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "59a2b3cb2c1dde98c6aedf72aa9fdbfb",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "d75e3afcc1db962cd483f8b80a65c930", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "2199e800eb102d56197e11782b7adf12",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "2b1b27fc2ca79803d79796a7414db3a2", "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "3193300cf9927276893960698d8246f6",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b5a129d83796c9e7015ab5e319d2c22f", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b5a129d83796c9e7015ab5e319d2c22f",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5",
@ -4100,14 +4100,14 @@
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df", "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "d34c0a142e7d391a109a33ea3cc77c08", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "d34c0a142e7d391a109a33ea3cc77c08",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "aa15bc38411a8a4194b3711c9c6ebf56", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "13a3bb41dda8cfdb3ffdbc57a0390275",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "3539534ba129d95e99f89ddf79d14f28", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "042ff1129bab0def6bf90250b5c08a32",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "75e16f083a18621fe4502bc170e6a696", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "699ab8e6fd3e8517acac76cd5b40dfbc",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "29b9a8543b1612c0451eaaf90647b905", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "5b53317f8900cab59288be44b353dffc",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "4fb61373afaa6ce350601b245fcb1b7a", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "978ea0ba60c026efd45729cd48c0feba",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "d8e606cdfef72b6170721d87ee1d9b11", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "1fb524f11277090440cda18bfc3ee3a0",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "240b54fa0d4334591c0876a2b72a4663", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "91452d47e879acd0d7fdd06a44fbcfed",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "a7887447024ce5570da3f468bad5bbb4", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "7e9469f3cac9ab85c5e9d1256679604c",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318", "src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "ad347097a38e0d7ede9eb6dec6a80ee9", "src/ballistica/base/mgen/pyembed/binding_base.inc": "ad347097a38e0d7ede9eb6dec6a80ee9",

View File

@ -1,19 +1,19 @@
### 1.7.28 (build 21299, api 8, 2023-09-01) ### 1.7.28 (build 21303, api 8, 2023-09-01)
- Added some high level functionality for copying and deleting feature-sets to - Added some high level functionality for copying and deleting feature-sets to
the `tools/spinoff` tool. For example, to create your own `poo` feature-set, the `spinoff` tool. For example, to create your own `poo` feature-set based on
do `tools/spinoff fset-copy template_fs poo`. Then do `make update` and `make the existing `template_fs` one, do `tools/spinoff fset-copy template_fs poo`.
cmake` to build and run the app, and from within it you should be able to do Then do `make update` and `make cmake` to build and run the app, and from
`import bapoo` to get at your nice shiny poo feature-set. When you are done within it you should be able to do `import bapoo` to get at your nice shiny
playing, you can do `tools/spinoff fset-delete poo` to blow away any traces of poo feature-set. When you are done playing around, you can do `tools/spinoff
it. fset-delete poo` to blow away any traces of it.
- Public builds now properly reconstruct the CMakeLists.txt file for project - Public builds now properly reconstruct the CMakeLists.txt file for project
changes. changes.
- Efrocache now supports a starter-archive when building server builds. This - Efrocache now supports a starter-archive when building server builds. This
means that if you do something like `make clean; make means that if you do something like `make clean; make
prefab-server-release-build` you should only see a few file downloads prefab-server-release-build` you should see just a few file downloads
happening instead of hundreds or thousands which would happen before, which happening instead of the hundreds that would happen before, which should be
should be significantly faster & more efficient. significantly faster & more efficient.
### 1.7.27 (build 21282, api 8, 2023-08-30) ### 1.7.27 (build 21282, api 8, 2023-08-30)
@ -40,7 +40,8 @@
`build_number`, `device_name`, `config_file_path`, `version`, `debug_build`, `build_number`, `device_name`, `config_file_path`, `version`, `debug_build`,
`test_build`, `data_directory`, `python_directory_user`, `test_build`, `data_directory`, `python_directory_user`,
`python_directory_app`, `python_directory_app_site`, `api_version`, `on_tv`, `python_directory_app`, `python_directory_app_site`, `api_version`, `on_tv`,
`vr_mode`. `vr_mode`, `toolbar_test`, `arcade_mode`, `headless_mode`, `demo_mode`, and
`protocol_version`.
- Reverting the Android keyboard changes from 1.7.26, as I've received a few - Reverting the Android keyboard changes from 1.7.26, as I've received a few
reports of bluetooth game controllers now thinking they are keyboards. I'm reports of bluetooth game controllers now thinking they are keyboards. I'm
thinking I'll have to bite the bullet and implement something that asks the thinking I'll have to bite the bullet and implement something that asks the

View File

@ -96,6 +96,7 @@
"ba_data/python/baclassic/osmusic.py", "ba_data/python/baclassic/osmusic.py",
"ba_data/python/bacommon/__init__.py", "ba_data/python/bacommon/__init__.py",
"ba_data/python/bacommon/__pycache__/__init__.cpython-311.opt-1.pyc", "ba_data/python/bacommon/__pycache__/__init__.cpython-311.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/app.cpython-311.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/assets.cpython-311.opt-1.pyc", "ba_data/python/bacommon/__pycache__/assets.cpython-311.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/bacloud.cpython-311.opt-1.pyc", "ba_data/python/bacommon/__pycache__/bacloud.cpython-311.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/build.cpython-311.opt-1.pyc", "ba_data/python/bacommon/__pycache__/build.cpython-311.opt-1.pyc",
@ -104,6 +105,7 @@
"ba_data/python/bacommon/__pycache__/net.cpython-311.opt-1.pyc", "ba_data/python/bacommon/__pycache__/net.cpython-311.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/servermanager.cpython-311.opt-1.pyc", "ba_data/python/bacommon/__pycache__/servermanager.cpython-311.opt-1.pyc",
"ba_data/python/bacommon/__pycache__/transfer.cpython-311.opt-1.pyc", "ba_data/python/bacommon/__pycache__/transfer.cpython-311.opt-1.pyc",
"ba_data/python/bacommon/app.py",
"ba_data/python/bacommon/assets.py", "ba_data/python/bacommon/assets.py",
"ba_data/python/bacommon/bacloud.py", "ba_data/python/bacommon/bacloud.py",
"ba_data/python/bacommon/build.py", "ba_data/python/bacommon/build.py",

View File

@ -712,6 +712,7 @@ $(eval $(call make-opt-pyc-target,$(element))))
SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \ SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \
$(BUILD_DIR)/ba_data/python/bacommon/__init__.py \ $(BUILD_DIR)/ba_data/python/bacommon/__init__.py \
$(BUILD_DIR)/ba_data/python/bacommon/app.py \
$(BUILD_DIR)/ba_data/python/bacommon/assets.py \ $(BUILD_DIR)/ba_data/python/bacommon/assets.py \
$(BUILD_DIR)/ba_data/python/bacommon/bacloud.py \ $(BUILD_DIR)/ba_data/python/bacommon/bacloud.py \
$(BUILD_DIR)/ba_data/python/bacommon/build.py \ $(BUILD_DIR)/ba_data/python/bacommon/build.py \
@ -746,6 +747,7 @@ SCRIPT_TARGETS_PY_PUBLIC_TOOLS = \
SCRIPT_TARGETS_PYC_PUBLIC_TOOLS = \ SCRIPT_TARGETS_PYC_PUBLIC_TOOLS = \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/__init__.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bacommon/__pycache__/__init__.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/app.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/assets.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bacommon/__pycache__/assets.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/bacloud.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bacommon/__pycache__/bacloud.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bacommon/__pycache__/build.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bacommon/__pycache__/build.cpython-311.opt-1.pyc \

View File

@ -64,7 +64,7 @@ class AccountV2Subsystem:
def set_primary_credentials(self, credentials: str | None) -> None: def set_primary_credentials(self, credentials: str | None) -> None:
"""Set credentials for the primary app account.""" """Set credentials for the primary app account."""
raise RuntimeError('This should be overridden.') raise NotImplementedError('This should be overridden.')
def have_primary_credentials(self) -> bool: def have_primary_credentials(self) -> bool:
"""Are credentials currently set for the primary app account? """Are credentials currently set for the primary app account?
@ -73,7 +73,7 @@ class AccountV2Subsystem:
only that they exist. If/when credentials are validated, the 'primary' only that they exist. If/when credentials are validated, the 'primary'
account handle will be set. account handle will be set.
""" """
raise RuntimeError('This should be overridden.') raise NotImplementedError('This should be overridden.')
@property @property
def primary(self) -> AccountV2Handle | None: def primary(self) -> AccountV2Handle | None:

View File

@ -112,7 +112,9 @@ class App:
project. project.
""" """
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode]: def app_mode_for_intent(
self, intent: AppIntent
) -> type[AppMode] | None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
# __GOOD_PLACE_FOR_CUSTOM_SPINOFF_LOGIC__ # __GOOD_PLACE_FOR_CUSTOM_SPINOFF_LOGIC__
@ -127,10 +129,10 @@ class App:
import babase import babase
if bascenev1.SceneV1AppMode.supports_intent(intent): if bascenev1.SceneV1AppMode.can_handle_intent(intent):
return bascenev1.SceneV1AppMode return bascenev1.SceneV1AppMode
if babase.EmptyAppMode.supports_intent(intent): if babase.EmptyAppMode.can_handle_intent(intent):
return babase.EmptyAppMode return babase.EmptyAppMode
raise RuntimeError(f'No handler found for intent {type(intent)}.') raise RuntimeError(f'No handler found for intent {type(intent)}.')
@ -144,19 +146,13 @@ class App:
the single shared instance. the single shared instance.
""" """
# Hack for docs-generation.
if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1': if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1':
return return
self.env: babase.Env = _babase.Env() self.env: babase.Env = _babase.Env()
self.state = self.State.NOT_RUNNING self.state = self.State.NOT_RUNNING
# 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: babase.AppModeSelector | None = None
# Default executor which can be used for misc background # Default executor which can be used for misc background
# processing. It should also be passed to any additional asyncio # processing. It should also be passed to any additional asyncio
# loops we create so that everything shares the same single set # loops we create so that everything shares the same single set
@ -193,6 +189,7 @@ class App:
self._pending_intent: AppIntent | None = None self._pending_intent: AppIntent | None = None
self._intent: AppIntent | None = None self._intent: AppIntent | None = None
self._mode: AppMode | None = None self._mode: AppMode | None = None
self._mode_selector: babase.AppModeSelector | None = None
self._shutdown_task: asyncio.Task[None] | None = None self._shutdown_task: asyncio.Task[None] | None = None
self._shutdown_tasks: list[Coroutine[None, None, None]] = [ self._shutdown_tasks: list[Coroutine[None, None, None]] = [
self._wait_for_shutdown_suppressions() self._wait_for_shutdown_suppressions()
@ -201,6 +198,7 @@ class App:
def postinit(self) -> None: def postinit(self) -> None:
"""Called after we've been inited and assigned to babase.app.""" """Called after we've been inited and assigned to babase.app."""
# Hack for docs-generation.
if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1': if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1':
return return
@ -231,6 +229,31 @@ class App:
assert self._config is not None assert self._config is not None
return self._config return self._config
@property
def mode_selector(self) -> babase.AppModeSelector:
"""Controls which app-modes are used for handling given intents.
Plugins can override this to change high level app behavior and
spinoff projects can change the default implementation for the
same effect.
"""
if self._mode_selector is None:
raise RuntimeError(
'mode_selector cannot be used until the app reaches'
' the running state.'
)
return self._mode_selector
@mode_selector.setter
def mode_selector(self, selector: babase.AppModeSelector) -> None:
# Don't allow overriding this until after we've initially set it.
if self._mode_selector is None:
raise RuntimeError(
'mode_selector cannot be used until the app reaches'
' the running state.'
)
self._mode_selector = selector
# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__ # __FEATURESET_APP_SUBSYSTEM_PROPERTIES_BEGIN__
# This section generated by batools.appmodule; do not edit. # This section generated by batools.appmodule; do not edit.
@ -303,8 +326,8 @@ class App:
def run(self) -> None: def run(self) -> None:
"""Run the app to completion. """Run the app to completion.
Note that this only works on platforms where Ballistica Note that this only works on builds where Ballistica manages
manages its own event loop. its own event loop.
""" """
_babase.run_app() _babase.run_app()
@ -423,7 +446,7 @@ class App:
self._update_state() self._update_state()
def _set_intent(self, intent: AppIntent) -> None: def _set_intent(self, intent: AppIntent) -> None:
# This should be running in a bg thread. # This should be happening in a bg thread.
assert not _babase.in_logic_thread() assert not _babase.in_logic_thread()
try: try:
# Ask the selector what app-mode to use for this intent. # Ask the selector what app-mode to use for this intent.
@ -431,15 +454,27 @@ class App:
raise RuntimeError('No AppModeSelector set.') raise RuntimeError('No AppModeSelector set.')
modetype = self.mode_selector.app_mode_for_intent(intent) modetype = self.mode_selector.app_mode_for_intent(intent)
# Make sure the app-mode they return *actually* supports the # NOTE: Since intents are somewhat high level things, should
# intent. # we do some universal thing like a screenmessage saying
if not modetype.supports_intent(intent): # 'The app cannot handle that request' on failure?
if modetype is None:
raise RuntimeError( raise RuntimeError(
f'Intent {intent} is not supported by AppMode class' f'No app-mode found to handle app-intent'
f' {modetype}' f' type {type(intent)}.'
) )
# Kick back to the logic thread to apply. # Make sure the app-mode the selector gave us *actually*
# supports the intent.
if not modetype.can_handle_intent(intent):
raise RuntimeError(
f'Intent {intent} cannot be handled by AppMode type'
f' {modetype} (selector {self.mode_selector}'
f' incorrectly thinks that it can be).'
)
# Ok; seems legit. Now instantiate the mode if necessary and
# kick back to the logic thread to apply.
mode = modetype() mode = modetype()
_babase.pushcall( _babase.pushcall(
tpartial(self._apply_intent, intent, mode), tpartial(self._apply_intent, intent, mode),
@ -480,6 +515,9 @@ class App:
# Hmm; what should we do in this case?... # Hmm; what should we do in this case?...
logging.exception('Error activating app-mode %s.', mode) logging.exception('Error activating app-mode %s.', mode)
# Let the world know when we first have an app-mode; certain
# app stuff such as input processing can proceed at that
# point.
if is_initial_mode: if is_initial_mode:
_babase.on_initial_app_mode_set() _babase.on_initial_app_mode_set()
@ -599,7 +637,7 @@ class App:
# Set a default app-mode-selector. Plugins can then override # Set a default app-mode-selector. Plugins can then override
# this if they want in the on_app_running callback below. # this if they want in the on_app_running callback below.
self.mode_selector = self.DefaultAppModeSelector() self._mode_selector = self.DefaultAppModeSelector()
# Inform all app subsystems in the same order they were # Inform all app subsystems in the same order they were
# registered. Operate on a copy here because subsystems can # registered. Operate on a copy here because subsystems can

View File

@ -3,6 +3,7 @@
"""Provides the AppConfig class.""" """Provides the AppConfig class."""
from __future__ import annotations from __future__ import annotations
import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import _babase import _babase
@ -120,33 +121,24 @@ def read_app_config() -> tuple[AppConfig, bool]:
config = AppConfig() config = AppConfig()
config_file_healthy = True config_file_healthy = True
except Exception as exc: except Exception:
print( logging.exception(
( "Error reading config file at time %.3f: '%s'.",
'error reading config file at time ' _babase.apptime(),
+ str(_babase.apptime()) config_file_path,
+ ': \''
+ config_file_path
+ '\':\n'
),
exc,
) )
# Whenever this happens lets back up the broken one just in case it # Whenever this happens lets back up the broken one just in case it
# gets overwritten accidentally. # gets overwritten accidentally.
print( logging.info(
( "Backing up current config file to '%s.broken'", config_file_path
'backing up current config file to \''
+ config_file_path
+ ".broken\'"
)
) )
try: try:
import shutil import shutil
shutil.copyfile(config_file_path, config_file_path + '.broken') shutil.copyfile(config_file_path, config_file_path + '.broken')
except Exception as exc2: except Exception:
print('EXC copying broken config:', exc2) logging.exception('Error copying broken config.')
config = AppConfig() config = AppConfig()
# Now attempt to read one of our 'prev' backup copies. # Now attempt to read one of our 'prev' backup copies.
@ -159,9 +151,9 @@ def read_app_config() -> tuple[AppConfig, bool]:
else: else:
config = AppConfig() config = AppConfig()
config_file_healthy = True config_file_healthy = True
print('successfully read backup config.') logging.info('Successfully read backup config.')
except Exception as exc2: except Exception:
print('EXC reading prev backup config:', exc2) logging.exception('Error reading prev backup config.')
return config, config_file_healthy return config, config_file_healthy
@ -176,7 +168,7 @@ def commit_app_config(force: bool = False) -> None:
assert plus is not None assert plus is not None
if not _babase.app.config_file_healthy and not force: if not _babase.app.config_file_healthy and not force:
print( logging.warning(
'Current config file is broken; ' 'Current config file is broken; '
'skipping write to avoid losing settings.' 'skipping write to avoid losing settings.'
) )

View File

@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
if TYPE_CHECKING: if TYPE_CHECKING:
from bacommon.app import AppExperience
from babase._appintent import AppIntent from babase._appintent import AppIntent
@ -17,16 +18,33 @@ class AppMode:
""" """
@classmethod @classmethod
def supports_intent(cls, intent: AppIntent) -> bool: def get_app_experience(cls) -> AppExperience:
"""Return whether our mode can handle the provided intent.""" """Return the overall experience provided by this mode."""
del intent raise NotImplementedError('AppMode subclasses must override this.')
# Say no to everything by default. Let's make mode explicitly @classmethod
# lay out everything they *do* support. def can_handle_intent(cls, intent: AppIntent) -> bool:
return False """Return whether this mode can handle the provided intent.
For this to return True, the AppMode must claim to support the
provided intent (via its _supports_intent() method) AND the
AppExperience associated with the AppMode must be supported by
the current app and runtime environment.
"""
return cls._supports_intent(intent)
@classmethod
def _supports_intent(cls, intent: AppIntent) -> bool:
"""Return whether our mode can handle the provided intent.
AppModes should override this to define what they can handle.
Note that AppExperience does not have to be considered here; that
is handled automatically by the can_handle_intent() call."""
raise NotImplementedError('AppMode subclasses must override this.')
def handle_intent(self, intent: AppIntent) -> None: def handle_intent(self, intent: AppIntent) -> None:
"""Handle an intent.""" """Handle an intent."""
raise NotImplementedError('AppMode subclasses must override this.')
def on_activate(self) -> None: def on_activate(self) -> None:
"""Called when the mode is being activated.""" """Called when the mode is being activated."""

View File

@ -18,15 +18,15 @@ class AppModeSelector:
The app calls an instance of this class when passed an AppIntent to The app calls an instance of this class when passed an AppIntent to
determine which AppMode to use to handle the intent. Plugins or determine which AppMode to use to handle the intent. Plugins or
spinoff projects can modify high level app behavior by replacing or spinoff projects can modify high level app behavior by replacing or
modifying this. modifying the app's mode-selector.
""" """
def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode]: def app_mode_for_intent(self, intent: AppIntent) -> type[AppMode] | None:
"""Given an AppIntent, return the AppMode that should handle it. """Given an AppIntent, return the AppMode that should handle it.
If None is returned, the AppIntent will be ignored. If None is returned, the AppIntent will be ignored.
This is called in a background thread, so avoid any calls This may be called in a background thread, so avoid any calls
limited to logic thread use/etc. limited to logic thread use/etc.
""" """
raise RuntimeError('app_mode_for_intent() should be overridden.') raise NotImplementedError('app_mode_for_intent() should be overridden.')

View File

@ -5,6 +5,8 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from bacommon.app import AppExperience
import _babase import _babase
from babase._appmode import AppMode from babase._appmode import AppMode
from babase._appintent import AppIntentExec, AppIntentDefault from babase._appintent import AppIntentExec, AppIntentDefault
@ -17,7 +19,11 @@ class EmptyAppMode(AppMode):
"""An empty app mode that can be used as a fallback/etc.""" """An empty app mode that can be used as a fallback/etc."""
@classmethod @classmethod
def supports_intent(cls, intent: AppIntent) -> bool: def get_app_experience(cls) -> AppExperience:
return AppExperience.EMPTY
@classmethod
def _supports_intent(cls, intent: AppIntent) -> bool:
# We support default and exec intents currently. # We support default and exec intents currently.
return isinstance(intent, AppIntentExec | AppIntentDefault) return isinstance(intent, AppIntentExec | AppIntentDefault)
@ -30,8 +36,8 @@ class EmptyAppMode(AppMode):
def on_activate(self) -> None: def on_activate(self) -> None:
# Let the native layer do its thing. # Let the native layer do its thing.
_babase.empty_app_mode_activate() _babase.on_empty_app_mode_activate()
def on_deactivate(self) -> None: def on_deactivate(self) -> None:
# Let the native layer do its thing. # Let the native layer do its thing.
_babase.empty_app_mode_deactivate() _babase.on_empty_app_mode_deactivate()

View File

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

View File

@ -5,7 +5,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from bacommon.app import AppExperience
from babase import AppMode, AppIntentExec, AppIntentDefault from babase import AppMode, AppIntentExec, AppIntentDefault
import _bascenev1 import _bascenev1
if TYPE_CHECKING: if TYPE_CHECKING:
@ -16,7 +18,11 @@ class SceneV1AppMode(AppMode):
"""Our app-mode.""" """Our app-mode."""
@classmethod @classmethod
def supports_intent(cls, intent: AppIntent) -> bool: def get_app_experience(cls) -> AppExperience:
return AppExperience.MELEE
@classmethod
def _supports_intent(cls, intent: AppIntent) -> bool:
# We support default and exec intents currently. # We support default and exec intents currently.
return isinstance(intent, AppIntentExec | AppIntentDefault) return isinstance(intent, AppIntentExec | AppIntentDefault)
@ -29,8 +35,8 @@ class SceneV1AppMode(AppMode):
def on_activate(self) -> None: def on_activate(self) -> None:
# Let the native layer do its thing. # Let the native layer do its thing.
_bascenev1.app_mode_activate() _bascenev1.on_app_mode_activate()
def on_deactivate(self) -> None: def on_deactivate(self) -> None:
# Let the native layer do its thing. # Let the native layer do its thing.
_bascenev1.app_mode_deactivate() _bascenev1.on_app_mode_deactivate()

View File

@ -371,5 +371,5 @@ def register_map(maptype: type[Map]) -> None:
"""Register a map class with the game.""" """Register a map class with the game."""
assert babase.app.classic is not None assert babase.app.classic is not None
if maptype.name in babase.app.classic.maps: if maptype.name in babase.app.classic.maps:
raise RuntimeError('map "' + maptype.name + '" already registered') raise RuntimeError(f'Map "{maptype.name}" is already registered.')
babase.app.classic.maps[maptype.name] = maptype babase.app.classic.maps[maptype.name] = maptype

View File

@ -1,12 +1,10 @@
# Released under the MIT License. See LICENSE for details. # Released under the MIT License. See LICENSE for details.
# #
"""Snippets of code for use by the c++ layer.""" """Snippets of code for use by the native layer."""
# (most of these are self-explanatory)
# pylint: disable=missing-function-docstring
from __future__ import annotations from __future__ import annotations
def hello_world() -> None: def hello_world() -> None:
"""The usual example."""
print('HELLO WORLD FROM TemplateFs!') print('HELLO WORLD FROM TemplateFs!')

View File

@ -991,12 +991,13 @@ void Graphics::ClearFrameDefDeleteList() {
} }
void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) { void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) {
BA_PRECONDITION(g_base->InLogicThread());
// If there's an ourstanding fade-end command, go ahead and run it. // If there's an ourstanding fade-end command, go ahead and run it.
// (otherwise, overlapping fades can cause things to get lost) // (otherwise, overlapping fades can cause things to get lost)
if (fade_end_call_.Exists()) { if (fade_end_call_.Exists()) {
if (g_buildconfig.debug_build()) { if (g_buildconfig.debug_build()) {
Log(LogLevel::kWarning, Log(LogLevel::kWarning,
"2 fades overlapping; running first fade-end-call early"); "2 fades overlapping; running first fade-end-call early.");
} }
fade_end_call_->Schedule(); fade_end_call_->Schedule();
fade_end_call_.Clear(); fade_end_call_.Clear();
@ -1089,7 +1090,7 @@ void Graphics::ApplyCamera(FrameDef* frame_def) {
void Graphics::DrawWorld(FrameDef* frame_def) { void Graphics::DrawWorld(FrameDef* frame_def) {
assert(!g_core->HeadlessMode()); assert(!g_core->HeadlessMode());
// Draw all session contents (nodes, etc.) // Draw the world.
overlay_node_z_depth_ = -0.95f; 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); g_base->bg_dynamics->Draw(frame_def);
@ -1120,9 +1121,6 @@ void Graphics::BuildAndPushFrameDef() {
// layer is fully bootstrapped. // layer is fully bootstrapped.
BA_PRECONDITION_FATAL(g_base->logic->app_bootstrapping_complete()); BA_PRECONDITION_FATAL(g_base->logic->app_bootstrapping_complete());
// This should no longer be necessary..
WaitForRendererToExist();
millisecs_t app_time_millisecs = g_core->GetAppTimeMillisecs(); millisecs_t app_time_millisecs = g_core->GetAppTimeMillisecs();
// Store how much time this frame_def represents. // Store how much time this frame_def represents.
@ -1132,8 +1130,9 @@ void Graphics::BuildAndPushFrameDef() {
millisecs_t{50}, display_time_millisecs - last_create_frame_def_time_); millisecs_t{50}, display_time_millisecs - last_create_frame_def_time_);
last_create_frame_def_time_ = display_time_millisecs; last_create_frame_def_time_ = display_time_millisecs;
// This probably should not be here. Though I guess we get the most up-to-date // This probably should not be here. Though I guess we get the most
// values possible this way. But it should probably live in g_input. // up-to-date values possible this way. But it should probably live in
// g_input.
UpdateGyro(app_time_millisecs, elapsed); UpdateGyro(app_time_millisecs, elapsed);
FrameDef* frame_def = GetEmptyFrameDef(); FrameDef* frame_def = GetEmptyFrameDef();
@ -1184,9 +1183,9 @@ void Graphics::BuildAndPushFrameDef() {
// Draw our light/shadow images to the screen if desired. // Draw our light/shadow images to the screen if desired.
DrawDebugBuffers(overlay_pass); DrawDebugBuffers(overlay_pass);
// In high-quality modes we draw a screen-quad as a catch-all for blitting // In high-quality modes we draw a screen-quad as a catch-all for
// the world buffer to the screen (other nodes can add their own blitters // blitting the world buffer to the screen (other nodes can add their
// such as distortion shapes which will have priority). // own blitters such as distortion shapes which will have priority).
if (frame_def->quality() >= GraphicsQuality::kHigh) { if (frame_def->quality() >= GraphicsQuality::kHigh) {
PostProcessComponent c(frame_def->blit_pass()); PostProcessComponent c(frame_def->blit_pass());
c.DrawScreenQuad(); c.DrawScreenQuad();
@ -1195,8 +1194,8 @@ void Graphics::BuildAndPushFrameDef() {
DrawFades(frame_def, app_time_millisecs); DrawFades(frame_def, app_time_millisecs);
// Sanity test: If we're in VR, the only reason we should have stuff in the // Sanity test: If we're in VR, the only reason we should have stuff in
// flat overlay pass is if there's windows present (we want to avoid // the flat overlay pass is if there's windows present (we want to avoid
// drawing/blitting the 2d UI buffer during gameplay for efficiency). // drawing/blitting the 2d UI buffer during gameplay for efficiency).
if (g_core->IsVRMode()) { if (g_core->IsVRMode()) {
if (frame_def->GetOverlayFlatPass()->HasDrawCommands()) { if (frame_def->GetOverlayFlatPass()->HasDrawCommands()) {
@ -1219,9 +1218,9 @@ void Graphics::BuildAndPushFrameDef() {
frame_def->Finalize(); frame_def->Finalize();
// Include all mesh-data loads and unloads that have accumulated up to this // Include all mesh-data loads and unloads that have accumulated up to
// point the graphics thread will have to handle these before rendering the // this point the graphics thread will have to handle these before
// frame_def. // rendering the frame_def.
frame_def->set_mesh_data_creates(mesh_data_creates_); frame_def->set_mesh_data_creates(mesh_data_creates_);
mesh_data_creates_.clear(); mesh_data_creates_.clear();
frame_def->set_mesh_data_destroys(mesh_data_destroys_); frame_def->set_mesh_data_destroys(mesh_data_destroys_);
@ -1327,7 +1326,7 @@ void Graphics::UpdateAndDrawProgressBar(FrameDef* frame_def,
/ static_cast<float>(progress_bar_loads_)); / static_cast<float>(progress_bar_loads_));
DrawProgressBar(pass, 1.0f); DrawProgressBar(pass, 1.0f);
// If we were drawing a progress bar, see if everything is now loaded.. if // If we were drawing a progress bar, see if everything is now loaded. If
// so, start rendering normally next frame. // so, start rendering normally next frame.
int count = g_base->assets->GetGraphicalPendingLoadCount(); int count = g_base->assets->GetGraphicalPendingLoadCount();
if (count <= 0) { if (count <= 0) {
@ -1570,8 +1569,8 @@ void Graphics::AddMeshDataCreate(MeshData* d) {
assert(g_base->graphics); assert(g_base->graphics);
// Add this to our list of new-mesh-datas. We'll include this with our // Add this to our list of new-mesh-datas. We'll include this with our
// next frame_def to have the graphics thread load before it processes // next frame_def to have the graphics thread load before it processes the
// the frame_def. // frame_def.
mesh_data_creates_.push_back(d); mesh_data_creates_.push_back(d);
} }
@ -1580,8 +1579,8 @@ void Graphics::AddMeshDataDestroy(MeshData* d) {
assert(g_base->graphics); assert(g_base->graphics);
// Add this to our list of delete-mesh-datas; we'll include this with our // Add this to our list of delete-mesh-datas; we'll include this with our
// next frame_def to have the graphics thread kill before it processes // next frame_def to have the graphics thread kill before it processes the
// the frame_def. // frame_def.
mesh_data_destroys_.push_back(d); mesh_data_destroys_.push_back(d);
} }
@ -1635,24 +1634,6 @@ void Graphics::ToggleDebugDraw() {
void Graphics::ReleaseFadeEndCommand() { fade_end_call_.Clear(); } void Graphics::ReleaseFadeEndCommand() { fade_end_call_.Clear(); }
void Graphics::WaitForRendererToExist() {
// Conceivably we could hit this point before our graphics thread has created
// the renderer. In that case lets wait a moment.
int sleep_count = 0;
while (g_base->graphics_server == nullptr
|| g_base->graphics_server->renderer() == nullptr) {
BA_LOG_ONCE(
LogLevel::kWarning,
"BuildAndPushFrameDef() called before renderer is up; spinning...");
core::CorePlatform::SleepMillisecs(100);
sleep_count++;
if (sleep_count > 100) {
throw Exception(
"Aborting waiting for renderer to come up in BuildAndPushFrameDef()");
}
}
}
auto Graphics::ValueTest(const std::string& arg, double* absval, auto Graphics::ValueTest(const std::string& arg, double* absval,
double* deltaval, double* outval) -> bool { double* deltaval, double* outval) -> bool {
return false; return false;
@ -1737,20 +1718,17 @@ void Graphics::DoDrawBlotch(std::vector<uint16_t>* indices,
} }
void Graphics::DrawRadialMeter(MeshIndexedSimpleFull* m, float amt) { void Graphics::DrawRadialMeter(MeshIndexedSimpleFull* m, float amt) {
// FIXME - we're updating this every frame so we should use pure dynamic data; // FIXME - we're updating this every frame so we should use pure dynamic
// not a mix of static and dynamic. // data; not a mix of static and dynamic.
if (amt >= 0.999f) { if (amt >= 0.999f) {
// clang-format off
uint16_t indices[] = {0, 1, 2, 1, 3, 2}; uint16_t indices[] = {0, 1, 2, 1, 3, 2};
VertexSimpleFull vertices[] = { VertexSimpleFull vertices[] = {
{-1, -1, 0, 0, 65535}, {-1, -1, 0, 0, 65535},
{1, -1, 0, 65535, 65535}, {1, -1, 0, 65535, 65535},
{-1, 1, 0, 0, 0}, {-1, 1, 0, 0, 0},
{1, 1, 0, 65535, 0, {1, 1, 0, 65535, 0},
}
}; };
// clang-format on
m->SetIndexData(Object::New<MeshIndexBuffer16>(6, indices)); m->SetIndexData(Object::New<MeshIndexBuffer16>(6, indices));
m->SetData(Object::New<MeshBuffer<VertexSimpleFull>>(4, vertices)); m->SetData(Object::New<MeshBuffer<VertexSimpleFull>>(4, vertices));

View File

@ -322,7 +322,6 @@ class Graphics {
void DrawCursor(RenderPass* pass, millisecs_t real_time); void DrawCursor(RenderPass* pass, millisecs_t real_time);
void DrawFades(FrameDef* frame_def, millisecs_t real_time); void DrawFades(FrameDef* frame_def, millisecs_t real_time);
void DrawDebugBuffers(RenderPass* pass); void DrawDebugBuffers(RenderPass* pass);
void WaitForRendererToExist();
void UpdateAndDrawProgressBar(FrameDef* frame_def, millisecs_t real_time); void UpdateAndDrawProgressBar(FrameDef* frame_def, millisecs_t real_time);
void DoDrawBlotch(std::vector<uint16_t>* indices, void DoDrawBlotch(std::vector<uint16_t>* indices,
@ -344,7 +343,7 @@ class Graphics {
std::vector<MeshData*> mesh_data_creates_; std::vector<MeshData*> mesh_data_creates_;
std::vector<MeshData*> mesh_data_destroys_; std::vector<MeshData*> mesh_data_destroys_;
bool has_supports_high_quality_graphics_value_{}; bool has_supports_high_quality_graphics_value_{};
bool supports_high_quality_graphics_ = false; bool supports_high_quality_graphics_{};
millisecs_t last_create_frame_def_time_{}; millisecs_t last_create_frame_def_time_{};
Vector3f shadow_offset_{0.0f, 0.0f, 0.0f}; Vector3f shadow_offset_{0.0f, 0.0f, 0.0f};
Vector2f shadow_scale_{1.0f, 1.0f}; Vector2f shadow_scale_{1.0f, 1.0f};

View File

@ -1370,9 +1370,9 @@ static PyMethodDef PyUserAgentStringDef = {
"(internal)\n", "(internal)\n",
}; };
// ----------------------- empty_app_mode_activate ----------------------------- // --------------------- on_empty_app_mode_activate ----------------------------
static auto PyEmptyAppModeActivate(PyObject* self) -> PyObject* { static auto PyOnEmptyAppModeActivate(PyObject* self) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread()); BA_PRECONDITION(g_base->InLogicThread());
g_base->set_app_mode(AppModeEmpty::GetSingleton()); g_base->set_app_mode(AppModeEmpty::GetSingleton());
@ -1382,19 +1382,19 @@ static auto PyEmptyAppModeActivate(PyObject* self) -> PyObject* {
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }
static PyMethodDef PyEmptyAppModeActivateDef = { static PyMethodDef PyOnEmptyAppModeActivateDef = {
"empty_app_mode_activate", // name "on_empty_app_mode_activate", // name
(PyCFunction)PyEmptyAppModeActivate, // method (PyCFunction)PyOnEmptyAppModeActivate, // method
METH_NOARGS, // flags METH_NOARGS, // flags
"empty_app_mode_activate() -> None\n" "on_empty_app_mode_activate() -> None\n"
"\n" "\n"
"(internal)\n", "(internal)\n",
}; };
// ----------------------- empty_app_mode_deactivate --------------------------- // --------------------- on_empty_app_mode_deactivate --------------------------
static auto PyEmptyAppModeDeactivate(PyObject* self) -> PyObject* { static auto PyOnEmptyAppModeDeactivate(PyObject* self) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread()); BA_PRECONDITION(g_base->InLogicThread());
// Currently doing nothing. // Currently doing nothing.
@ -1402,12 +1402,12 @@ static auto PyEmptyAppModeDeactivate(PyObject* self) -> PyObject* {
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }
static PyMethodDef PyEmptyAppModeDeactivateDef = { static PyMethodDef PyOnEmptyAppModeDeactivateDef = {
"empty_app_mode_deactivate", // name "on_empty_app_mode_deactivate", // name
(PyCFunction)PyEmptyAppModeDeactivate, // method (PyCFunction)PyOnEmptyAppModeDeactivate, // method
METH_NOARGS, // flags METH_NOARGS, // flags
"empty_app_mode_deactivate() -> None\n" "on_empty_app_mode_deactivate() -> None\n"
"\n" "\n"
"(internal)\n", "(internal)\n",
}; };
@ -1598,8 +1598,8 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
PyOnInitialAppModeSetDef, PyOnInitialAppModeSetDef,
PyReachedEndOfBaBaseDef, PyReachedEndOfBaBaseDef,
PyUserAgentStringDef, PyUserAgentStringDef,
PyEmptyAppModeActivateDef, PyOnEmptyAppModeActivateDef,
PyEmptyAppModeDeactivateDef, PyOnEmptyAppModeDeactivateDef,
PyEmptyAppModeHandleIntentDefaultDef, PyEmptyAppModeHandleIntentDefaultDef,
PyEmptyAppModeHandleIntentExecDef, PyEmptyAppModeHandleIntentExecDef,
PyGetImmediateReturnCodeDef, PyGetImmediateReturnCodeDef,

View File

@ -525,8 +525,7 @@ static auto PyFadeScreen(PyObject* self, PyObject* args, PyObject* keywds)
-> PyObject* { -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
// This can only be called in the UI context. int fade{};
int fade{0};
float time{0.25f}; float time{0.25f};
PyObject* endcall = nullptr; PyObject* endcall = nullptr;
static const char* kwlist[] = {"to", "time", "endcall", nullptr}; static const char* kwlist[] = {"to", "time", "endcall", nullptr};

View File

@ -1614,9 +1614,9 @@ static PyMethodDef PySetInternalMusicDef = {
"(internal).", "(internal).",
}; };
// --------------------------- app_mode_activate ------------------------------- // -------------------------- on_app_mode_activate -----------------------------
static auto PyAppModeActivate(PyObject* self) -> PyObject* { static auto PyOnAppModeActivate(PyObject* self) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread()); BA_PRECONDITION(g_base->InLogicThread());
g_base->set_app_mode(SceneV1AppMode::GetSingleton()); g_base->set_app_mode(SceneV1AppMode::GetSingleton());
@ -1624,19 +1624,19 @@ static auto PyAppModeActivate(PyObject* self) -> PyObject* {
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }
static PyMethodDef PyAppModeActivateDef = { static PyMethodDef PyOnAppModeActivateDef = {
"app_mode_activate", // name "on_app_mode_activate", // name
(PyCFunction)PyAppModeActivate, // method (PyCFunction)PyOnAppModeActivate, // method
METH_NOARGS, // flags METH_NOARGS, // flags
"app_mode_activate() -> None\n" "on_app_mode_activate() -> None\n"
"\n" "\n"
"(internal)\n", "(internal)\n",
}; };
// -------------------------- app_mode_deactivate ------------------------------ // ------------------------- on_app_mode_deactivate ----------------------------
static auto PyAppModeDeactivate(PyObject* self) -> PyObject* { static auto PyOnAppModeDeactivate(PyObject* self) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread()); BA_PRECONDITION(g_base->InLogicThread());
// Currently doing nothing. // Currently doing nothing.
@ -1644,12 +1644,12 @@ static auto PyAppModeDeactivate(PyObject* self) -> PyObject* {
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }
static PyMethodDef PyAppModeDeactivateDef = { static PyMethodDef PyOnAppModeDeactivateDef = {
"app_mode_deactivate", // name "on_app_mode_deactivate", // name
(PyCFunction)PyAppModeDeactivate, // method (PyCFunction)PyOnAppModeDeactivate, // method
METH_NOARGS, // flags METH_NOARGS, // flags
"app_mode_deactivate() -> None\n" "on_app_mode_deactivate() -> None\n"
"\n" "\n"
"(internal)\n", "(internal)\n",
}; };
@ -1771,8 +1771,8 @@ auto PythonMethodsScene::GetMethods() -> std::vector<PyMethodDef> {
PyBaseTimeDef, PyBaseTimeDef,
PyBaseTimerDef, PyBaseTimerDef,
PyLsInputDevicesDef, PyLsInputDevicesDef,
PyAppModeActivateDef, PyOnAppModeActivateDef,
PyAppModeDeactivateDef, PyOnAppModeDeactivateDef,
PyHandleAppIntentDefaultDef, PyHandleAppIntentDefaultDef,
PyHandleAppIntentExecDef, PyHandleAppIntentExecDef,
PyProtocolVersionDef, PyProtocolVersionDef,

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // These are set automatically via script; don't modify them here.
const int kEngineBuildNumber = 21299; const int kEngineBuildNumber = 21303;
const char* kEngineVersion = "1.7.28"; const char* kEngineVersion = "1.7.28";
const int kEngineApiVersion = 8; const int kEngineApiVersion = 8;

35
tools/bacommon/app.py Normal file
View File

@ -0,0 +1,35 @@
# Released under the MIT License. See LICENSE for details.
#
"""Common high level values/functionality related to apps."""
from __future__ import annotations
from enum import Enum
from typing import TYPE_CHECKING
if TYPE_CHECKING:
pass
class AppExperience(Enum):
"""Overall experience that can be provided by a Ballistica app.
This corresponds generally, but not exactly, to distinct apps built
with Ballistica. However, a single app may support multiple experiences,
or there may be multiple apps targeting one experience. Cloud components
such as leagues are generally associated with an AppExperience.
"""
# A special experience category that is supported everywhere. Used
# for the default empty AppMode when starting the app, etc.
EMPTY = 'empty'
# The traditional BombSquad experience: multiple players using
# controllers in a single arena small enough for all action to be
# viewed on a single screen.
MELEE = 'melee'
# The traditional BombSquad Remote experience; buttons on a
# touch-screen allowing a mobile device to be used as a game
# controller.
REMOTE = 'remote'

View File

@ -166,12 +166,12 @@ def generate_app_module(
if 'scene_v1' in fsets: if 'scene_v1' in fsets:
contents += ( contents += (
'if bascenev1.SceneV1AppMode.supports_intent(intent):\n' 'if bascenev1.SceneV1AppMode.can_handle_intent(intent):\n'
' return bascenev1.SceneV1AppMode\n\n' ' return bascenev1.SceneV1AppMode\n\n'
) )
if 'base' in fsets: if 'base' in fsets:
contents += ( contents += (
'if babase.EmptyAppMode.supports_intent(intent):\n' 'if babase.EmptyAppMode.can_handle_intent(intent):\n'
' return babase.EmptyAppMode\n\n' ' return babase.EmptyAppMode\n\n'
) )
contents += ( contents += (