work on app-modes and app-intents

This commit is contained in:
Eric 2023-05-26 18:42:17 -07:00
parent 4e21714961
commit 28da551aa2
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
40 changed files with 607 additions and 111 deletions

View File

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

View File

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

View File

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

View File

@ -399,6 +399,7 @@
<ClInclude Include="..\..\src\ballistica\base\python\support\python_context_call.h" />
<ClInclude Include="..\..\src\ballistica\base\python\support\python_context_call_runnable.h" />
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h" />
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\context.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\context.h" />
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />

View File

@ -631,6 +631,9 @@
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\context.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>

View File

@ -394,6 +394,7 @@
<ClInclude Include="..\..\src\ballistica\base\python\support\python_context_call.h" />
<ClInclude Include="..\..\src\ballistica\base\python\support\python_context_call_runnable.h" />
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h" />
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\support\context.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\context.h" />
<ClCompile Include="..\..\src\ballistica\base\support\huffman.cc" />

View File

@ -631,6 +631,9 @@
<ClInclude Include="..\..\src\ballistica\base\support\app_timer.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClInclude Include="..\..\src\ballistica\base\support\classic_soft.h">
<Filter>ballistica\base\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\support\context.cc">
<Filter>ballistica\base\support</Filter>
</ClCompile>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<PyMethodDef> {
@ -1281,6 +1303,7 @@ auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
PyMacMusicAppGetPlaylistsDef,
PyIsOSPlayingMusicDef,
PyBootLogDef,
PyExecArgDef,
};
}

View File

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

View File

@ -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, "<stdin>");
if (!g_core->user_ran_commands) {
g_core->user_ran_commands = true;

View File

@ -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, "<console>");
if (!g_core->user_ran_commands) {
g_core->user_ran_commands = true;

View File

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

View File

@ -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<char**>(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<PyMethodDef> {
@ -1646,6 +1750,10 @@ auto PythonMethodsScene::GetMethods() -> std::vector<PyMethodDef> {
PyBaseTimeDef,
PyBaseTimerDef,
PyLsInputDevicesDef,
PyAppModeActivateDef,
PyAppModeDeactivateDef,
PyHandleAppIntentDefaultDef,
PyHandleAppIntentExecDef,
};
}

View File

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

View File

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

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 = 21022;
const int kEngineBuildNumber = 21023;
const char* kEngineVersion = "1.7.20";
auto MonolithicMain(const core::CoreConfig& core_config) -> int {

View File

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

View File

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

View File

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

View File

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