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/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "95166586256dc94f679d3f15cf570ed8",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "820f8a69f86434ff6dab9cc23e675708",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "70903ec2f476fbc5908cc52522f69e0c",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "442f2bdde3c176d9a4431c9fdcca892e",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "5354821ede7348ba9bb90bee8fe5ccb7",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "ab0cb59c874beb50e554d3d7c552565e",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "dec2c09bd136aa413b9b94729f7d6d2e",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "a5c43eb8befc810d693ec9c9f83fcba4",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "56bf9b14517f0a579964b39218498fcf",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "d6a10e3a6bc1a07609598f962439c6c8",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "ab5cb01ed4a8a8db80ca65ab5cd0133b",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "231bd6e18145b06028e4f90fd7de106c",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "3cc88ed5471e9b7f2b84af4ee58a8061",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "4586629e8f114c1b5a22444ae26b5917",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "e1177b377c3e48d60057a9719bf9d0e3",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "518c529a7133d5ea41d730d12ae156c4",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "41e7afae9395843c575c65593aab423c",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "d95e4cc445b722f111e51b3d4b7bdde9",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "d75e3afcc1db962cd483f8b80a65c930",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "2b1b27fc2ca79803d79796a7414db3a2",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "ca001c9f2b4edf8d4918c1bcaf0cb561",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "fcb58aae4535a761be49420f4e4a7711",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "8f453b8193ba06a3cc59374cdd2a1c09",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "7c389cb83619516ce96118227668500e",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "2ffcc486e9ba8f0fd5279083a2e54aff",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "366bab5bff3ff9344e54bf7492f4d7c3",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "78277cc1bd40ba88fd1475f8755e0721",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "5f063e4d45e40ade22f1bad63658752e",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f7114c78146f22e89143218c9930b43e",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "90c0ea0ce7a35d8f15b2a29bef704819",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "e1b4932fd7f359404c979604a1e33444",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "28ab454f71dfecd1986a245eb4a732e7",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "7b1a4db9c3f36fd428cdafbc1459bc1b",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "bc5a48ff49975901f212d18b1dbbb6f5",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "7742db7c9555907fc7b2d5c89d48dfd0",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "d81bc55d666f630a14de90f30be29fb4",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5ab68bfee69a6fe07b5b8a68938220e6",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "59a2b3cb2c1dde98c6aedf72aa9fdbfb",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "2199e800eb102d56197e11782b7adf12",
"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/release/libballisticaplus.a": "b5a129d83796c9e7015ab5e319d2c22f",
"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_server/debug/libballisticaplus.a": "d34c0a142e7d391a109a33ea3cc77c08",
"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.pdb": "3539534ba129d95e99f89ddf79d14f28",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "75e16f083a18621fe4502bc170e6a696",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "29b9a8543b1612c0451eaaf90647b905",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "4fb61373afaa6ce350601b245fcb1b7a",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "d8e606cdfef72b6170721d87ee1d9b11",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "240b54fa0d4334591c0876a2b72a4663",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "a7887447024ce5570da3f468bad5bbb4",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "13a3bb41dda8cfdb3ffdbc57a0390275",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "042ff1129bab0def6bf90250b5c08a32",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "699ab8e6fd3e8517acac76cd5b40dfbc",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "5b53317f8900cab59288be44b353dffc",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "978ea0ba60c026efd45729cd48c0feba",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "1fb524f11277090440cda18bfc3ee3a0",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "91452d47e879acd0d7fdd06a44fbcfed",
"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/enums.py": "f8cd3af311ac63147882590123b78318",
"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
the `tools/spinoff` tool. For example, to create your own `poo` feature-set,
do `tools/spinoff fset-copy template_fs poo`. Then do `make update` and `make
cmake` to build and run the app, and from within it you should be able to do
`import bapoo` to get at your nice shiny poo feature-set. When you are done
playing, you can do `tools/spinoff fset-delete poo` to blow away any traces of
it.
the `spinoff` tool. For example, to create your own `poo` feature-set based on
the existing `template_fs` one, do `tools/spinoff fset-copy template_fs poo`.
Then do `make update` and `make cmake` to build and run the app, and from
within it you should be able to do `import bapoo` to get at your nice shiny
poo feature-set. When you are done playing around, you can do `tools/spinoff
fset-delete poo` to blow away any traces of it.
- Public builds now properly reconstruct the CMakeLists.txt file for project
changes.
- Efrocache now supports a starter-archive when building server builds. This
means that if you do something like `make clean; make
prefab-server-release-build` you should only see a few file downloads
happening instead of hundreds or thousands which would happen before, which
should be significantly faster & more efficient.
prefab-server-release-build` you should see just a few file downloads
happening instead of the hundreds that would happen before, which should be
significantly faster & more efficient.
### 1.7.27 (build 21282, api 8, 2023-08-30)
@ -40,7 +40,8 @@
`build_number`, `device_name`, `config_file_path`, `version`, `debug_build`,
`test_build`, `data_directory`, `python_directory_user`,
`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
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

View File

@ -96,6 +96,7 @@
"ba_data/python/baclassic/osmusic.py",
"ba_data/python/bacommon/__init__.py",
"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__/bacloud.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__/servermanager.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/bacloud.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 = \
$(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/bacloud.py \
$(BUILD_DIR)/ba_data/python/bacommon/build.py \
@ -746,6 +747,7 @@ SCRIPT_TARGETS_PY_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__/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__/bacloud.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:
"""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:
"""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'
account handle will be set.
"""
raise RuntimeError('This should be overridden.')
raise NotImplementedError('This should be overridden.')
@property
def primary(self) -> AccountV2Handle | None:

View File

@ -112,7 +112,9 @@ class App:
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
# __GOOD_PLACE_FOR_CUSTOM_SPINOFF_LOGIC__
@ -127,10 +129,10 @@ class App:
import babase
if bascenev1.SceneV1AppMode.supports_intent(intent):
if bascenev1.SceneV1AppMode.can_handle_intent(intent):
return bascenev1.SceneV1AppMode
if babase.EmptyAppMode.supports_intent(intent):
if babase.EmptyAppMode.can_handle_intent(intent):
return babase.EmptyAppMode
raise RuntimeError(f'No handler found for intent {type(intent)}.')
@ -144,19 +146,13 @@ class App:
the single shared instance.
"""
# Hack for docs-generation.
if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1':
return
self.env: babase.Env = _babase.Env()
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
# processing. It should also be passed to any additional asyncio
# loops we create so that everything shares the same single set
@ -193,6 +189,7 @@ class App:
self._pending_intent: AppIntent | None = None
self._intent: AppIntent | None = None
self._mode: AppMode | None = None
self._mode_selector: babase.AppModeSelector | None = None
self._shutdown_task: asyncio.Task[None] | None = None
self._shutdown_tasks: list[Coroutine[None, None, None]] = [
self._wait_for_shutdown_suppressions()
@ -201,6 +198,7 @@ class App:
def postinit(self) -> None:
"""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':
return
@ -231,6 +229,31 @@ class App:
assert self._config is not None
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__
# This section generated by batools.appmodule; do not edit.
@ -303,8 +326,8 @@ class App:
def run(self) -> None:
"""Run the app to completion.
Note that this only works on platforms where Ballistica
manages its own event loop.
Note that this only works on builds where Ballistica manages
its own event loop.
"""
_babase.run_app()
@ -423,7 +446,7 @@ class App:
self._update_state()
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()
try:
# Ask the selector what app-mode to use for this intent.
@ -431,15 +454,27 @@ class App:
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):
# NOTE: Since intents are somewhat high level things, should
# we do some universal thing like a screenmessage saying
# 'The app cannot handle that request' on failure?
if modetype is None:
raise RuntimeError(
f'Intent {intent} is not supported by AppMode class'
f' {modetype}'
f'No app-mode found to handle app-intent'
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()
_babase.pushcall(
tpartial(self._apply_intent, intent, mode),
@ -480,6 +515,9 @@ class App:
# Hmm; what should we do in this case?...
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:
_babase.on_initial_app_mode_set()
@ -599,7 +637,7 @@ class App:
# Set a default app-mode-selector. Plugins can then override
# 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
# registered. Operate on a copy here because subsystems can

View File

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

View File

@ -6,6 +6,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from bacommon.app import AppExperience
from babase._appintent import AppIntent
@ -17,16 +18,33 @@ class AppMode:
"""
@classmethod
def supports_intent(cls, intent: AppIntent) -> bool:
"""Return whether our mode can handle the provided intent."""
del intent
def get_app_experience(cls) -> AppExperience:
"""Return the overall experience provided by this mode."""
raise NotImplementedError('AppMode subclasses must override this.')
# Say no to everything by default. Let's make mode explicitly
# lay out everything they *do* support.
return False
@classmethod
def can_handle_intent(cls, intent: AppIntent) -> bool:
"""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:
"""Handle an intent."""
raise NotImplementedError('AppMode subclasses must override this.')
def on_activate(self) -> None:
"""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
determine which AppMode to use to handle the intent. Plugins 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.
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.
"""
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 bacommon.app import AppExperience
import _babase
from babase._appmode import AppMode
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."""
@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.
return isinstance(intent, AppIntentExec | AppIntentDefault)
@ -30,8 +36,8 @@ class EmptyAppMode(AppMode):
def on_activate(self) -> None:
# Let the native layer do its thing.
_babase.empty_app_mode_activate()
_babase.on_empty_app_mode_activate()
def on_deactivate(self) -> None:
# 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
# using.
TARGET_BALLISTICA_BUILD = 21299
TARGET_BALLISTICA_BUILD = 21303
TARGET_BALLISTICA_VERSION = '1.7.28'

View File

@ -5,7 +5,9 @@ from __future__ import annotations
from typing import TYPE_CHECKING
from bacommon.app import AppExperience
from babase import AppMode, AppIntentExec, AppIntentDefault
import _bascenev1
if TYPE_CHECKING:
@ -16,7 +18,11 @@ class SceneV1AppMode(AppMode):
"""Our app-mode."""
@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.
return isinstance(intent, AppIntentExec | AppIntentDefault)
@ -29,8 +35,8 @@ class SceneV1AppMode(AppMode):
def on_activate(self) -> None:
# Let the native layer do its thing.
_bascenev1.app_mode_activate()
_bascenev1.on_app_mode_activate()
def on_deactivate(self) -> None:
# 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."""
assert babase.app.classic is not None
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

View File

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

View File

@ -991,12 +991,13 @@ void Graphics::ClearFrameDefDeleteList() {
}
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.
// (otherwise, overlapping fades can cause things to get lost)
if (fade_end_call_.Exists()) {
if (g_buildconfig.debug_build()) {
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_.Clear();
@ -1089,7 +1090,7 @@ void Graphics::ApplyCamera(FrameDef* frame_def) {
void Graphics::DrawWorld(FrameDef* frame_def) {
assert(!g_core->HeadlessMode());
// Draw all session contents (nodes, etc.)
// Draw the world.
overlay_node_z_depth_ = -0.95f;
g_base->app_mode()->DrawWorld(frame_def);
g_base->bg_dynamics->Draw(frame_def);
@ -1120,9 +1121,6 @@ void Graphics::BuildAndPushFrameDef() {
// layer is fully bootstrapped.
BA_PRECONDITION_FATAL(g_base->logic->app_bootstrapping_complete());
// This should no longer be necessary..
WaitForRendererToExist();
millisecs_t app_time_millisecs = g_core->GetAppTimeMillisecs();
// 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_);
last_create_frame_def_time_ = display_time_millisecs;
// This probably should not be here. Though I guess we get the most up-to-date
// values possible this way. But it should probably live in g_input.
// This probably should not be here. Though I guess we get the most
// up-to-date values possible this way. But it should probably live in
// g_input.
UpdateGyro(app_time_millisecs, elapsed);
FrameDef* frame_def = GetEmptyFrameDef();
@ -1184,9 +1183,9 @@ void Graphics::BuildAndPushFrameDef() {
// Draw our light/shadow images to the screen if desired.
DrawDebugBuffers(overlay_pass);
// In high-quality modes we draw a screen-quad as a catch-all for blitting
// the world buffer to the screen (other nodes can add their own blitters
// such as distortion shapes which will have priority).
// In high-quality modes we draw a screen-quad as a catch-all for
// blitting the world buffer to the screen (other nodes can add their
// own blitters such as distortion shapes which will have priority).
if (frame_def->quality() >= GraphicsQuality::kHigh) {
PostProcessComponent c(frame_def->blit_pass());
c.DrawScreenQuad();
@ -1195,8 +1194,8 @@ void Graphics::BuildAndPushFrameDef() {
DrawFades(frame_def, app_time_millisecs);
// Sanity test: If we're in VR, the only reason we should have stuff in the
// flat overlay pass is if there's windows present (we want to avoid
// Sanity test: If we're in VR, the only reason we should have stuff in
// the flat overlay pass is if there's windows present (we want to avoid
// drawing/blitting the 2d UI buffer during gameplay for efficiency).
if (g_core->IsVRMode()) {
if (frame_def->GetOverlayFlatPass()->HasDrawCommands()) {
@ -1219,9 +1218,9 @@ void Graphics::BuildAndPushFrameDef() {
frame_def->Finalize();
// Include all mesh-data loads and unloads that have accumulated up to this
// point the graphics thread will have to handle these before rendering the
// frame_def.
// Include all mesh-data loads and unloads that have accumulated up to
// this point the graphics thread will have to handle these before
// rendering the frame_def.
frame_def->set_mesh_data_creates(mesh_data_creates_);
mesh_data_creates_.clear();
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_));
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.
int count = g_base->assets->GetGraphicalPendingLoadCount();
if (count <= 0) {
@ -1570,8 +1569,8 @@ void Graphics::AddMeshDataCreate(MeshData* d) {
assert(g_base->graphics);
// 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
// the frame_def.
// next frame_def to have the graphics thread load before it processes the
// frame_def.
mesh_data_creates_.push_back(d);
}
@ -1580,8 +1579,8 @@ void Graphics::AddMeshDataDestroy(MeshData* d) {
assert(g_base->graphics);
// 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
// the frame_def.
// next frame_def to have the graphics thread kill before it processes the
// frame_def.
mesh_data_destroys_.push_back(d);
}
@ -1635,24 +1634,6 @@ void Graphics::ToggleDebugDraw() {
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,
double* deltaval, double* outval) -> bool {
return false;
@ -1737,20 +1718,17 @@ void Graphics::DoDrawBlotch(std::vector<uint16_t>* indices,
}
void Graphics::DrawRadialMeter(MeshIndexedSimpleFull* m, float amt) {
// FIXME - we're updating this every frame so we should use pure dynamic data;
// not a mix of static and dynamic.
// FIXME - we're updating this every frame so we should use pure dynamic
// data; not a mix of static and dynamic.
if (amt >= 0.999f) {
// clang-format off
uint16_t indices[] = {0, 1, 2, 1, 3, 2};
VertexSimpleFull vertices[] = {
{-1, -1, 0, 0, 65535},
{1, -1, 0, 65535, 65535},
{-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->SetData(Object::New<MeshBuffer<VertexSimpleFull>>(4, vertices));

View File

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

View File

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

View File

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

View File

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

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kEngineBuildNumber = 21299;
const int kEngineBuildNumber = 21303;
const char* kEngineVersion = "1.7.28";
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:
contents += (
'if bascenev1.SceneV1AppMode.supports_intent(intent):\n'
'if bascenev1.SceneV1AppMode.can_handle_intent(intent):\n'
' return bascenev1.SceneV1AppMode\n\n'
)
if 'base' in fsets:
contents += (
'if babase.EmptyAppMode.supports_intent(intent):\n'
'if babase.EmptyAppMode.can_handle_intent(intent):\n'
' return babase.EmptyAppMode\n\n'
)
contents += (