This commit is contained in:
Eric 2023-08-25 17:06:32 -07:00
parent 4cf54d4f85
commit 82aa76b29b
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
29 changed files with 273 additions and 234 deletions

92
.efrocachemap generated
View File

@ -4064,54 +4064,54 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "2ce43921f1917e4face2bd89de255c89",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "cfad36d58003989bd3fb758d72900914",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "11a8f7317dafd295f43e5b8573b21caa",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "dca6448b3d8f3acd46632f103d270b38",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "dcf48e1ee93b7a747b0c33efca58057a",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "b568da36b6a2ee329e31ff321df54810",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "b9e6b511c5fea84b0a81335fd3000592",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "ec94eec1e4c1084535c65e9923de87d6",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "d53c169951686dd96a85668def6dbbfc",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "b46e409cb87de94f6cf6f7d3e10e032f",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "4692a22e7ddaf15e34157325252b5fb3",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "d0ab73deaa89dd9fa1b75c65d463d293",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "3db746bb01e002fee667c70c6f57f928",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "10c06bd2c02a9b2ac488993e2ffc6700",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "01a466465933b84f3772e9d274ee9c40",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "3d3233606e03eb3b8ffe04169a5f2fce",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "c59aeaf8686b4008d0b11728a0523397",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "92dcffd261cfe8bf80806ebc765bb323",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "57639e6862d409cf39ad2fd609aafb20",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "fa7a86c50be523dcb7ff6aadb9d79d0e",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "d16860bc4d3ff0abae745e2fd5638149",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "620ed03a89ad8053656a466da6053676",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "d16860bc4d3ff0abae745e2fd5638149",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "620ed03a89ad8053656a466da6053676",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "642d380b38d5fd4a5f50a331579e6e9c",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "a767b4b9d20e61e0a6fb7289660959e9",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "642d380b38d5fd4a5f50a331579e6e9c",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "a767b4b9d20e61e0a6fb7289660959e9",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "fecd29b4aff2924b2403f36996cdca93",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "9aea5093bb559d64e28edc3c112b06b2",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "fecd29b4aff2924b2403f36996cdca93",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "9aea5093bb559d64e28edc3c112b06b2",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "8ce1ea0d1d64dac282100599159ad2a3",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "adf78a2ab5fb271d83e80fb72b68784f",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "864807f34140eed4dada8832878a1173",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "adf78a2ab5fb271d83e80fb72b68784f",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "8d271eb87a692db53219288d762a5194",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "3a2e88e7788e47cc86ac87df0ea46ce3",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "d0db9d64f7ff618cb6a62f59a70a00f0",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "308f59dbc2f3193b1adb321db9a09ff1",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "d66fe8cbfcd4d472ff3536c274f2a8d4",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "89d7812f28754a00ad797bb994c81846",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "ea8906f5c66ba5606bff77e8e57f3ade",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "d117fce0b21244310ce6771a29c237be",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "bb5e0df17efe96476c3c2b8c41c7979b",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "056115be35ac1afc1f62b58dcc8f217a",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cf2b052caaa06d512ef379687b3aff86",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "97b8d5f261f84b8047d43df1ca81777a",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "f304ee46675e7552c31e174d81199307",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "70b54361b557f5e8d4271a0c976b28b6",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "06c778dc4c2772acf6dbaf67eb7321c9",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "212a5c7206c1aa9a8427b61471e9e89b",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f3a1028602c7bbd3f8d62bd843af628d",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "9c4c6d1c50e225dc61cfbab4a82a37a6",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "4db4b600478f1767fdd0462a137912de",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "628bc102cf6ef17b38804c4c9baa5265",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "fb9d165ab24084e8beaa6d7c51c81a77",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "ae88283e96a9238aab7561d2afcd9a5f",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "e5fe958377b8dcf5d5203dbd17aaab72",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f22e8af184b19b4929162e901a056454",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5fa2cb24b9e78bddb1bf9efb197d0c51",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "d36f11acfa5e68f303d347c3895979d0",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "570a7e325c15ecebcc419d48a046dd24",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "3155f665993e5f850db5b87c9296abe7",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "d1bfae5e75824ba89338892bc0f84c6b",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b1466048e319c0d60139c46751f3eb79",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "d1bfae5e75824ba89338892bc0f84c6b",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "b1466048e319c0d60139c46751f3eb79",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "656176631037184b6e22b0b68c3cd1fa",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "12b633db4dad37bbb5a5c398db0c10dd",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "656176631037184b6e22b0b68c3cd1fa",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "12b633db4dad37bbb5a5c398db0c10dd",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "7e014214c6cb9ddaa0e95f5186ba9df6",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "45ddc559dd5ef563d2df5c5db9c9fbc0",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "7e014214c6cb9ddaa0e95f5186ba9df6",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "45ddc559dd5ef563d2df5c5db9c9fbc0",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "1a15bf7d809addab4992827da9d89895",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "a1e03b7d13482beab8852691b5698974",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "807e3629d9d4611cd93feb87face4e51",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "a1e03b7d13482beab8852691b5698974",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "7f7bc04993982b164f6e86ad6ce350ef",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "d24102dd35c29b6a77cdf3d9921695da",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "8edef08a22594617d2b4273e0e4cba40",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "cb6c0c6efad034b53fe1a8f930f2ce81",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "3ec4aadf128132116fc5479a46bd1f71",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "14c2cf0812e3022391caffd9409d0650",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "67c207425afc5023cea9740e3bd459c3",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "7219b9034f14c5b769818b80135ea61b",
"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": "ad5475b871dadc69bc2cf8ccac550878",
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "ef931ec35861d4ad37850b7278588922",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "ad347097a38e0d7ede9eb6dec6a80ee9",
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "077bf63282b23d1880287200c9f6035f",
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "3ceb412513963f0818ab39c58bf292e3",
"src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69",
"src/ballistica/core/mgen/pyembed/env.inc": "8be46e5818f360d10b7b0224a9e91d07",

View File

@ -1,4 +1,4 @@
### 1.7.26 (build 21250, api 8, 2023-08-23)
### 1.7.26 (build 21256, api 8, 2023-08-25)
- Android should now be better at detecting hardware keyboards (you will see
'Configure Keyboard' and 'Configure Keyboard P2' buttons under
@ -24,14 +24,19 @@
functionality, but rather adapt the app to a particular paradigm or api (VR,
Headless, SDL GUI, etc.). Also am trying to move any functionality out of
those classes that does not fit that definition.
- Started cleaning up the app exit process. This will allow the app to
- Started cleaning up the app shutdown process. This will allow the app to
gracefully run tasks such as syncing account data to the cloud or disk or
properly closing the audio system when shutting down. It also means there
should be more consistent use of the 'Quit?' confirm window. Please holler if
you see any odd behavior when trying to quit the app.
- Unix TERM signal now triggers graceful app shutdown.
- Added `ba.app.add_shutdown_task()` to register coroutines to be run as part of
shutdown.
- Removed `babase.app.iircade_mode`. RIP iiRcade :(.
- Changed `AppState.INITIAL` to `AppState.NOT_RUNNING`, added a
`AppState.NATIVE_BOOTSTRAPPING`, and changed `AppState.LAUNCHING` to
`AppState.INITING`. These better describe what the app is actually doing while
in those states.
### 1.7.25 (build 21211, api 8, 2023-08-03)

View File

@ -128,7 +128,7 @@ class AccountV2Subsystem:
# Ok; no workspace to worry about; carry on.
if not self._initial_sign_in_completed:
self._initial_sign_in_completed = True
_babase.app.on_initial_sign_in_completed()
_babase.app.on_initial_sign_in_complete()
def on_active_logins_changed(self, logins: dict[LoginType, str]) -> None:
"""Should be called when logins for the active account change."""
@ -163,7 +163,7 @@ class AccountV2Subsystem:
"""
if not self._initial_sign_in_completed:
self._initial_sign_in_completed = True
_babase.app.on_initial_sign_in_completed()
_babase.app.on_initial_sign_in_complete()
@staticmethod
def _hashstr(val: str) -> str:
@ -409,7 +409,7 @@ class AccountV2Subsystem:
def _on_set_active_workspace_completed(self) -> None:
if not self._initial_sign_in_completed:
self._initial_sign_in_completed = True
_babase.app.on_initial_sign_in_completed()
_babase.app.on_initial_sign_in_complete()
class AccountV2Handle:

View File

@ -4,8 +4,8 @@
from __future__ import annotations
import os
from enum import Enum
import logging
from enum import Enum
from typing import TYPE_CHECKING
from concurrent.futures import ThreadPoolExecutor
from functools import cached_property
@ -58,31 +58,41 @@ class App:
health_monitor: AppHealthMonitor
# How long we allow shutdown tasks to run before killing them.
# Currently the entire app hard-exits if shutdown takes 10 seconds,
# so we need to keep it under that.
SHUTDOWN_TASK_TIMEOUT_SECONDS = 5
class State(Enum):
"""High level state the app can be in."""
# Waiting on the native layer to finish spinning up; our launch
# process here has not yet started.
BOOTSTRAPPING = 0
# The app has not yet begun starting and should not be used in
# any way.
NOT_RUNNING = 'not_running'
# Our app subsystems are being inited but should not yet
# interact.
LAUNCHING = 1
# The native layer is spinning up its machinery (screens,
# renderers, etc.). Nothing should happen in the Python layer
# until this completes.
NATIVE_BOOTSTRAPPING = 'native_bootstrapping'
# App subsystems are inited and interacting, but the app has not
# yet embarked on a high level course of action. It is doing
# initial account logins, workspace & asset downloads, etc. in
# order to prepare for this.
LOADING = 2
# Python app subsystems are being inited but should not yet
# interact or do any work.
INITING = 'initing'
# Python app subsystems are inited and interacting, but the app
# has not yet embarked on a high level course of action. It is
# doing initial account logins, workspace & asset downloads,
# etc.
LOADING = 'loading'
# All pieces are in place and the app is now doing its thing.
RUNNING = 3
RUNNING = 'running'
# The app is backgrounded or otherwise suspended.
PAUSED = 4
PAUSED = 'paused'
# The app is shutting down.
SHUTTING_DOWN = 5
SHUTTING_DOWN = 'shutting_down'
@property
def aioloop(self) -> asyncio.AbstractEventLoop:
@ -236,19 +246,20 @@ class App:
if os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') == '1':
return
self.state = self.State.BOOTSTRAPPING
self.state = self.State.NOT_RUNNING
self._subsystems: list[AppSubsystem] = []
self._native_bootstrapped = False
self._native_bootstrapping_completed = False
self._init_completed = False
self._meta_scan_completed = False
self._native_start_called = False
self._native_paused = False
self._native_shutdown_called = False
self._launch_completed = False
self._initial_sign_in_completed = False
self._meta_scan_completed = False
self._called_on_app_launching = False
self._called_on_app_loading = False
self._called_on_app_running = False
self._called_on_initing = False
self._called_on_loading = False
self._called_on_running = False
self._subsystem_registration_ended = False
self._pending_apply_app_config = False
@ -484,13 +495,13 @@ class App:
def run(self) -> None:
"""Run the app to completion.
Note that this only works on platforms where ballistica
Note that this only works on platforms where Ballistica
manages its own event loop.
"""
_babase.run_app()
def _on_app_launching(self) -> None:
"""Called when the app enters the launching state.
def _on_initing(self) -> None:
"""Called when the app enters the initing state.
Here we can put together subsystems and other pieces for the
app, but most things should not be doing any work yet.
@ -503,7 +514,7 @@ class App:
assert _babase.in_logic_thread()
_env.on_app_launching()
_env.on_app_state_initing()
self._aioloop = _asyncio.setup_asyncio()
self.health_monitor = AppHealthMonitor()
@ -531,28 +542,25 @@ class App:
# __FEATURESET_APP_SUBSYSTEM_CREATE_END__
self._launch_completed = True
# We're a pretty short-lived state. This should flip us to
# 'loading'.
self._init_completed = True
self._update_state()
def _on_app_loading(self) -> None:
"""Called when the app enters the loading state.
def _on_loading(self) -> None:
"""Called when we enter the loading state.
At this point, all built-in pieces of the app should be in place
and can start doing 'work'. Though at a high level, the goal of
the app at this point is only to sign in to initial accounts,
download workspaces, and otherwise prepare itself to really
'run'.
and can start talking to each other and doing work. Though at a
high level, the goal of the app at this point is only to sign in
to initial accounts, download workspaces, and otherwise prepare
itself to really 'run'.
"""
from babase._apputils import log_dumped_app_state
assert _babase.in_logic_thread()
# Get meta-system scanning built-in stuff in the bg.
self.meta.start_scan(scan_complete_cb=self._on_meta_scan_complete)
# If any traceback dumps happened last run, log and clear them.
log_dumped_app_state()
# Inform all app subsystems in the same order they were inited.
# Operate on a copy here because subsystems can still be added
# at this point.
@ -568,7 +576,7 @@ class App:
# is not present, however, we just do it ourself so we can
# proceed on to the running state.
if self.plus is None:
_babase.pushcall(self.on_initial_sign_in_completed)
_babase.pushcall(self.on_initial_sign_in_complete)
def _on_meta_scan_complete(self) -> None:
"""Called when meta-scan is done doing its thing."""
@ -581,8 +589,8 @@ class App:
self._meta_scan_completed = True
self._update_state()
def _on_app_running(self) -> None:
"""Called when the app enters the running state.
def _on_running(self) -> None:
"""Called when we enter the running state.
At this point, all workspaces, initial accounts, etc. are in place
and we can actually get started doing whatever we're gonna do.
@ -704,51 +712,61 @@ class App:
# pylint: disable=too-many-branches
assert _babase.in_logic_thread()
# Can't shut down until we've got our asyncio machinery spun up.
if self._native_shutdown_called and self._aioloop is not None:
# Shutdown trumps all. Though we can't shut down until init is
# completed since we need our asyncio stuff to exist for the
# shutdown process.
if self._native_shutdown_called and self._init_completed:
# Entering shutdown state:
if self.state is not self.State.SHUTTING_DOWN:
self.state = self.State.SHUTTING_DOWN
self._on_app_shutdown()
self._on_shutting_down()
elif self._native_paused:
# Entering paused state:
if self.state is not self.State.PAUSED:
self.state = self.State.PAUSED
self._on_app_pause()
self._on_pause()
else:
# Leaving paused state:
if self.state is self.State.PAUSED:
self._on_app_resume()
self._on_resume()
# Handle initially entering or returning to other states.
if self._initial_sign_in_completed and self._meta_scan_completed:
if self.state != self.State.RUNNING:
self.state = self.State.RUNNING
_babase.lifecyclelog('app state running')
if not self._called_on_app_running:
self._called_on_app_running = True
self._on_app_running()
elif self._launch_completed:
if not self._called_on_running:
self._called_on_running = True
self._on_running()
elif self._init_completed:
if self.state is not self.State.LOADING:
self.state = self.State.LOADING
_babase.lifecyclelog('app state loading')
if not self._called_on_app_loading:
self._called_on_app_loading = True
self._on_app_loading()
if not self._called_on_loading:
self._called_on_loading = True
self._on_loading()
elif self._native_bootstrapping_completed:
if self.state is not self.State.INITING:
self.state = self.State.INITING
_babase.lifecyclelog('app state initing')
if not self._called_on_initing:
self._called_on_initing = True
self._on_initing()
else:
# Only thing left is launching. We shouldn't be getting
# called before at least that is complete.
assert self._native_bootstrapped
if self.state is not self.State.LAUNCHING:
self.state = self.State.LAUNCHING
_babase.lifecyclelog('app state launching')
if not self._called_on_app_launching:
self._called_on_app_launching = True
self._on_app_launching()
# Only possibility left is app-start. We shouldn't be
# getting called before at least that happens.
assert self._native_start_called
assert self.state is self.State.NOT_RUNNING
if bool(True):
self.state = self.State.NATIVE_BOOTSTRAPPING
def add_shutdown_task(self, coro: Coroutine[None, None, None]) -> None:
"""Add a task to be run on app shutdown."""
"""Add a task to be run on app shutdown.
Note that tasks will be killed after
App.SHUTDOWN_TASK_TIMEOUT_SECONDS if they are still running.
"""
if self.state is self.State.SHUTTING_DOWN:
raise RuntimeError(
'Cannot add shutdown tasks with state SHUTTING_DOWN.'
@ -780,15 +798,22 @@ class App:
task = asyncio.create_task(coro)
try:
await asyncio.wait_for(task, 5.0)
await asyncio.wait_for(task, self.SHUTDOWN_TASK_TIMEOUT_SECONDS)
except Exception:
logging.exception('Error in shutdown task.')
def on_native_bootstrapped(self) -> None:
def on_native_start(self) -> None:
"""Called by the native layer when the app is being started."""
assert _babase.in_logic_thread()
assert not self._native_start_called
self._native_start_called = True
self._update_state()
def on_native_bootstrapping_complete(self) -> None:
"""Called by the native layer once its ready to rock."""
assert _babase.in_logic_thread()
assert not self._native_bootstrapped
self._native_bootstrapped = True
assert not self._native_bootstrapping_completed
self._native_bootstrapping_completed = True
self._update_state()
def on_native_pause(self) -> None:
@ -811,7 +836,7 @@ class App:
self._native_shutdown_called = True
self._update_state()
def _on_app_pause(self) -> None:
def _on_pause(self) -> None:
"""Called when the app goes to a paused state."""
assert _babase.in_logic_thread()
@ -824,7 +849,7 @@ class App:
'Error in on_app_pause for subsystem %s.', subsystem
)
def _on_app_resume(self) -> None:
def _on_resume(self) -> None:
"""Called when resuming."""
assert _babase.in_logic_thread()
self.fg_state += 1
@ -838,7 +863,7 @@ class App:
'Error in on_app_resume for subsystem %s.', subsystem
)
def _on_app_shutdown(self) -> None:
def _on_shutting_down(self) -> None:
"""(internal)"""
assert _babase.in_logic_thread()
@ -882,7 +907,7 @@ class App:
except ImportError:
pass
def on_initial_sign_in_completed(self) -> None:
def on_initial_sign_in_complete(self) -> None:
"""Called when initial sign-in (or lack thereof) completes.
This normally gets called by the plus subsystem. The

View File

@ -378,6 +378,10 @@ class AppHealthMonitor(AppSubsystem):
self._response = False
self._first_check = True
def on_app_loading(self) -> None:
# If any traceback dumps happened last run, log and clear them.
log_dumped_app_state()
def _app_monitor_thread_main(self) -> None:
try:
self._monitor_app()

View File

@ -149,14 +149,15 @@ def on_main_thread_start_app() -> None:
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
def on_app_launching() -> None:
"""Called when the app reaches the launching state."""
def on_app_state_initing() -> None:
"""Called when the app reaches the initing state."""
import _babase
import baenv
assert _babase.in_logic_thread()
# Let the user know if the app Python dir is a 'user' one.
# Let the user know if the app Python dir is a 'user' one. This is a
# risky thing to be doing so don't let them forget they're doing it.
envconfig = baenv.get_config()
if envconfig.is_user_app_python_dir:
_babase.screenmessage(
@ -192,12 +193,13 @@ def _feed_logs_to_babase(log_handler: LogHandler) -> None:
# cache. This will feed the engine any logs that happened between
# baenv.configure() and now.
# FIXME: while this works for now, the downside is that these
# FIXME: while this setup works for now, the downside is that these
# callbacks fire in a bg thread so certain things like android
# logging will be delayed compared to code that uses native logging
# calls directly. Perhaps we should add some sort of 'immediate'
# callback option to better handle such cases (similar to the
# immediate echofile stderr print that already occurs).
# logging will be delayed relative to code that uses native logging
# calls directly. Ideally we should add some sort of 'immediate'
# callback option to better handle such cases (analogous to the
# immediate echofile stderr print that LogHandler already
# supports).
log_handler.add_callback(_on_log, feed_existing_logs=True)

View File

@ -81,7 +81,7 @@ class PluginSubsystem(AppSubsystem):
config_changed = True
found_new = True
# If we're *not* auto-enabling, just let the user know if we
# If we're *not* auto-enabling, simply let the user know if we
# found new ones.
if found_new and not auto_enable_new_plugins:
_babase.screenmessage(
@ -131,10 +131,10 @@ class PluginSubsystem(AppSubsystem):
disappeared_plugs.add(class_path)
continue
# If plugins disappeared, let the user know gently and remove them
# from the config so we'll again let the user know if they later
# reappear. This makes it much smoother to switch between users
# or workspaces.
# If plugins disappeared, let the user know gently and remove
# them from the config so we'll again let the user know if they
# later reappear. This makes it much smoother to switch between
# users or workspaces.
if disappeared_plugs:
_babase.getsimplesound('shieldDown').play()
_babase.screenmessage(
@ -217,7 +217,7 @@ class PluginSpec:
key. Remember to commit the app-config after making any changes.
The 'attempted_load' attr will be True if the engine has attempted
to load the plugin. If 'attempted_load' is True for a plugin-spec
to load the plugin. If 'attempted_load' is True for a PluginSpec
but the 'plugin' attr is None, it means there was an error loading
the plugin. If a plugin's api-version does not match the running
app, if a new plugin is detected with auto-enable-plugins disabled,
@ -249,7 +249,7 @@ class PluginSpec:
plugstate['enabled'] = val
def attempt_load_if_enabled(self) -> Plugin | None:
"""Possibly load the plugin and report errors."""
"""Possibly load the plugin and log any errors."""
from babase._general import getclass
from babase._language import Lstr
@ -308,8 +308,8 @@ class Plugin:
Category: **App Classes**
Plugins are discoverable by the meta-tag system
and the user can select which ones they want to activate.
Active plugins are then called at specific times as the
and the user can select which ones they want to enable.
Enabled plugins are then called at specific times as the
app is running in order to modify its behavior in some way.
"""

View File

@ -4,8 +4,9 @@
from __future__ import annotations
import copy
import threading
import logging
import weakref
import threading
from enum import Enum
from typing import TYPE_CHECKING
@ -36,7 +37,9 @@ class MasterServerV1CallThread(threading.Thread):
callback: MasterServerCallback | None,
response_type: MasterServerResponseType,
):
super().__init__()
# Set daemon=True so long-running requests don't keep us from
# quitting the app.
super().__init__(daemon=True)
self._request = request
self._request_type = request_type
if not isinstance(response_type, MasterServerResponseType):
@ -76,6 +79,16 @@ class MasterServerV1CallThread(threading.Thread):
from efro.error import is_urllib_communication_error
# If the app is going down, this is a no-op. Trying to avoid the
# rare odd crash I see from (presumably) SSL stuff getting used
# while the app is being torn down.
if babase.app.state is babase.app.State.SHUTTING_DOWN:
logging.warning(
'MasterServerV1CallThread.run() during app'
' shutdown is a no-op.'
)
return
plus = babase.app.plus
assert plus is not None
response_data: Any = None

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 = 21250
TARGET_BALLISTICA_BUILD = 21256
TARGET_BALLISTICA_VERSION = '1.7.26'

View File

@ -199,7 +199,11 @@ class QuitWindow:
)
bui.lock_all_input()
# Unlock and fade back in shortly.. just in case something goes wrong
# (or on android where quit just backs out of our activity and
# we may come back)
bui.apptimer(0.3, bui.unlock_all_input)
# Unlock and fade back in shortly. Just in case something goes
# wrong (or on Android where quit just backs out of our activity
# and we may come back after).
def _come_back() -> None:
bui.unlock_all_input()
bui.fade_screen(True, time=0.1)
bui.apptimer(0.3, _come_back)

View File

@ -1406,11 +1406,11 @@ def _check_merch_availability_in_bg_thread() -> None:
time.sleep(1.1934) # A bit randomized to avoid aliasing.
# Slight hack; start checking merch availability in the bg
# (but only if it looks like we're part of a running app; don't want to
# do this during docs generation/etc.)
# Slight hack; start checking merch availability in the bg (but only if
# it looks like we've been imported for use in a running app; don't want
# to do this during docs generation/etc.)
if (
os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') != '1'
and bui.app.state is not bui.app.State.BOOTSTRAPPING
and bui.app.state is not bui.app.State.NOT_RUNNING
):
Thread(target=_check_merch_availability_in_bg_thread, daemon=True).start()

View File

@ -140,7 +140,6 @@ void AppAdapter::OnAppPause_() {
g_base->network_reader->OnAppPause();
}
g_base->networking->OnAppPause();
g_core->platform->OnAppPause();
}
void AppAdapter::OnAppResume_() {
@ -151,7 +150,6 @@ void AppAdapter::OnAppResume_() {
EventLoop::SetEventLoopsPaused(false);
// Run resumes that expect to happen in the main thread.
g_core->platform->OnAppResume();
g_base->network_reader->OnAppResume();
g_base->networking->OnAppResume();

View File

@ -75,12 +75,12 @@ BaseFeatureSet::BaseFeatureSet()
}
void BaseFeatureSet::OnModuleExec(PyObject* module) {
// Ok, our feature-set's Python module is getting imported. Like any
// normal Python module, we take this opportunity to import/create the
// Ok, our feature-set's Python module is getting imported. Just like a
// pure Python module would, we take this opportunity to import/create the
// stuff we use.
// Importing core should always be the first thing we do. Various
// ballistica functionality will fail if this has not been done.
// Ballistica functionality will fail if this has not been done.
assert(g_core == nullptr);
g_core = core::CoreFeatureSet::Import();
@ -106,17 +106,18 @@ void BaseFeatureSet::OnModuleExec(PyObject* module) {
g_base->python->AddPythonClasses(module);
// Store our C++ front-end with our Python module. This is what allows
// others to 'import' our C++ front end.
// other C++ code to 'import' our C++ front end and talk to us directly.
g_base->StoreOnPythonModule(module);
// Import all the Python stuff we use.
g_base->python->ImportPythonObjs();
// Run some sanity checks, wire up our log handler, etc.
auto result = g_base->python->objs()
.Get(BasePython::ObjID::kOnNativeModuleImportCall)
.Call();
if (!result.Exists()) {
bool success = g_base->python->objs()
.Get(BasePython::ObjID::kEnvOnNativeModuleImportCall)
.Call()
.Exists();
if (!success) {
FatalError("babase._env.on_native_module_import() call failed.");
}

View File

@ -62,7 +62,6 @@ void Logic::OnAppStart() {
g_base->audio->OnAppStart();
g_base->input->OnAppStart();
g_base->ui->OnAppStart();
g_core->platform->OnAppStart();
g_base->app_mode()->OnAppStart();
if (g_base->HavePlus()) {
g_base->plus()->OnAppStart();
@ -80,7 +79,7 @@ void Logic::OnInitialScreenCreated() {
// business logic.
// Let the Python layer know the native layer is now fully functional.
// This will probably result in the Python layer flipping to the LAUNCHING
// This will probably result in the Python layer flipping to the INITING
// state.
CompleteAppBootstrapping();
@ -109,7 +108,7 @@ void Logic::CompleteAppBootstrapping() {
assert(!app_bootstrapping_complete_);
app_bootstrapping_complete_ = true;
g_core->LifecycleLog("app bootstrapping complete");
g_core->LifecycleLog("app native bootstrapping complete");
// Let the assets system know it can start loading stuff now that
// we have a screen and thus know texture formats/etc.
@ -143,7 +142,7 @@ void Logic::CompleteAppBootstrapping() {
// Let Python know we're done bootstrapping so it can flip the app
// into the 'launching' state.
g_base->python->objs()
.Get(BasePython::ObjID::kAppOnNativeBootstrappedCall)
.Get(BasePython::ObjID::kAppOnNativeBootstrappingCompleteCall)
.Call();
UpdatePendingWorkTimer();
@ -181,7 +180,6 @@ void Logic::OnAppPause() {
g_base->plus()->OnAppPause();
}
g_base->app_mode()->OnAppPause();
g_core->platform->OnAppPause();
g_base->ui->OnAppPause();
g_base->input->OnAppPause();
g_base->audio->OnAppPause();
@ -197,7 +195,6 @@ void Logic::OnAppResume() {
g_base->audio->OnAppResume();
g_base->input->OnAppResume();
g_base->ui->OnAppResume();
g_core->platform->OnAppResume();
g_base->app_mode()->OnAppResume();
if (g_base->HavePlus()) {
g_base->plus()->OnAppResume();
@ -234,7 +231,6 @@ void Logic::OnAppShutdown() {
g_base->plus()->OnAppShutdown();
}
g_base->app_mode()->OnAppShutdown();
g_core->platform->OnAppShutdown();
g_base->ui->OnAppShutdown();
g_base->input->OnAppShutdown();
g_base->audio->OnAppShutdown();
@ -269,7 +265,6 @@ void Logic::DoApplyAppConfig() {
g_base->audio->DoApplyAppConfig();
g_base->input->DoApplyAppConfig();
g_base->ui->DoApplyAppConfig();
g_core->platform->DoApplyAppConfig();
g_base->app_mode()->DoApplyAppConfig();
if (g_base->HavePlus()) {
g_base->plus()->DoApplyAppConfig();
@ -585,15 +580,20 @@ void Logic::UpdatePendingWorkTimer() {
void Logic::HandleInterruptSignal() {
assert(g_base->InLogicThread());
// Interrupt signals are 'gentle' requests to shut down.
// Special case; when running under the server-wrapper, we completely
// ignore interrupt signals (the wrapper acts on them).
if (g_base->server_wrapper_managed()) {
return;
}
Shutdown();
}
// Go with a low level process shutdown here. In situations where we're
// getting interrupt signals I don't think we'd ever want high level
// 'soft' quits.
void Logic::HandleTerminateSignal() {
// Interrupt signals are slightly more stern requests to shut down.
// We always respond to these.
assert(g_base->InLogicThread());
Shutdown();
}

View File

@ -69,7 +69,9 @@ class Logic {
return app_bootstrapping_complete_;
}
void NotifyOfPendingAssetLoads();
void HandleInterruptSignal();
void HandleTerminateSignal();
auto NewAppTimer(millisecs_t length, bool repeat,
const Object::Ref<Runnable>& runnable) -> int;

View File

@ -257,11 +257,21 @@ void BasePlatform::DoOpenURL(const std::string& url) {
#if !BA_OSTYPE_WINDOWS
static void HandleSIGINT(int s) {
if (g_base->logic) {
if (g_base && g_base->logic->event_loop()) {
g_base->logic->event_loop()->PushCall(
[] { g_base->logic->HandleInterruptSignal(); });
} else {
Log(LogLevel::kError, "SigInt handler called before g_logic exists.");
Log(LogLevel::kError,
"SigInt handler called before g_base->logic->event_loop exists.");
}
}
static void HandleSIGTERM(int s) {
if (g_base && g_base->logic->event_loop()) {
g_base->logic->event_loop()->PushCall(
[] { g_base->logic->HandleTerminateSignal(); });
} else {
Log(LogLevel::kError,
"SigInt handler called before g_base->logic->event_loop exists.");
}
}
#endif
@ -271,11 +281,20 @@ void BasePlatform::SetupInterruptHandling() {
#if BA_OSTYPE_WINDOWS
throw Exception();
#else
struct sigaction handler {};
handler.sa_handler = HandleSIGINT;
sigemptyset(&handler.sa_mask);
handler.sa_flags = 0;
sigaction(SIGINT, &handler, nullptr);
{
struct sigaction handler {};
handler.sa_handler = HandleSIGINT;
sigemptyset(&handler.sa_mask);
handler.sa_flags = 0;
sigaction(SIGINT, &handler, nullptr);
}
{
struct sigaction handler {};
handler.sa_handler = HandleSIGTERM;
sigemptyset(&handler.sa_mask);
handler.sa_flags = 0;
sigaction(SIGTERM, &handler, nullptr);
}
#endif
}

View File

@ -142,7 +142,10 @@ void BasePython::OnMainThreadStartApp() {
}
}
void BasePython::OnAppStart() { assert(g_base->InLogicThread()); }
void BasePython::OnAppStart() {
assert(g_base->InLogicThread());
objs().Get(BasePython::ObjID::kAppOnNativeStartCall).Call();
}
void BasePython::OnAppPause() {
assert(g_base->InLogicThread());

View File

@ -35,7 +35,7 @@ class BasePython {
kCallClass,
kGarbageCollectSessionEndCall,
kConfig,
kAppOnNativeBootstrappedCall,
kAppOnNativeBootstrappingCompleteCall,
kResetToMainMenuCall,
kSetConfigFullscreenOnCall,
kSetConfigFullscreenOffCall,
@ -69,10 +69,11 @@ class BasePython {
kAppReadConfigCall,
kUIRemotePressCall,
kRemoveInGameAdsMessageCall,
kAppOnNativeStartCall,
kAppOnNativePauseCall,
kAppOnNativeResumeCall,
kQuitCall,
kAppOnNativeShutdownCall,
kQuitCall,
kShowPostPurchaseMessageCall,
kContextError,
kNotFoundError,
@ -98,7 +99,7 @@ class BasePython {
kLoginAdapterGetSignInTokenResponseCall,
kPreEnv,
kOpenURLWithWebBrowserModuleCall,
kOnNativeModuleImportCall,
kEnvOnNativeModuleImportCall,
kOnMainThreadStartAppCall,
kAppPushApplyAppConfigCall,
kLast // Sentinel; must be at end.

View File

@ -516,6 +516,9 @@ static auto PyQuit(PyObject* self, PyObject* args, PyObject* keywds)
return nullptr;
}
// Log(LogLevel::kDebug,
// "QUIT soft=" + std::to_string(soft) + " back=" + std::to_string(back));
// FIXME this should all just go through platform and/or app-adapter.
if (g_buildconfig.ostype_ios_tvos()) {

View File

@ -32,8 +32,8 @@ void ClassicFeatureSet::OnModuleExec(PyObject* module) {
assert(g_classic == nullptr);
g_classic = new ClassicFeatureSet();
// Store our C++ front-end with our Python module.
// This is what allows others to 'import' our C++ front end.
// Store our C++ front-end with our Python module. This is what allows
// other C++ code to 'import' our C++ front end and talk to us directly.
g_classic->StoreOnPythonModule(module);
// Import any Python stuff we use into objs_.

View File

@ -644,22 +644,6 @@ auto CorePlatform::GetTextTextureData(void* tex) -> uint8_t* {
throw Exception();
}
void CorePlatform::OnAppStart() {
assert(g_base_soft && g_base_soft->InLogicThread());
}
void CorePlatform::OnAppPause() {
assert(g_base_soft && g_base_soft->InLogicThread());
}
void CorePlatform::OnAppResume() {
assert(g_base_soft && g_base_soft->InLogicThread());
}
void CorePlatform::OnAppShutdown() {
assert(g_base_soft && g_base_soft->InLogicThread());
}
void CorePlatform::OnScreenSizeChange() {
assert(g_base_soft && g_base_soft->InLogicThread());
}
@ -700,8 +684,6 @@ void CorePlatform::AndroidSetResString(const std::string& res) {
throw Exception();
}
void CorePlatform::DoApplyAppConfig() {}
void CorePlatform::AndroidSynthesizeBackPress() {
Log(LogLevel::kError, "AndroidSynthesizeBackPress() unimplemented");
}

View File

@ -51,11 +51,6 @@ class CorePlatform {
virtual void WillExitMain(bool errored);
virtual void OnAppStart();
virtual void OnAppPause();
virtual void OnAppResume();
virtual void OnAppShutdown();
virtual void DoApplyAppConfig();
virtual void OnScreenSizeChange();
virtual void StepDisplayTime();

View File

@ -50,8 +50,8 @@ void SceneV1FeatureSet::OnModuleExec(PyObject* module) {
assert(g_scene_v1 == nullptr);
g_scene_v1 = new SceneV1FeatureSet();
// Store our C++ front-end with our Python module.
// This is what allows others to 'import' our C++ front end.
// Store our C++ front-end with our Python module. This is what allows
// other C++ code to 'import' our C++ front end and talk to us directly.
g_scene_v1->StoreOnPythonModule(module);
// Define our classes.

View File

@ -1376,10 +1376,7 @@ void SceneV1AppMode::HandleQuitOnIdle() {
idle_exiting_ = true;
Log(LogLevel::kInfo, "Quitting due to reaching idle-exit-minutes.");
g_base->logic->event_loop()->PushCall([] {
assert(g_base->InLogicThread());
g_base->python->objs().Get(base::BasePython::ObjID::kQuitCall).Call();
});
g_base->logic->event_loop()->PushCall([] { g_base->logic->Shutdown(); });
}
}
}

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 = 21250;
const int kEngineBuildNumber = 21256;
const char* kEngineVersion = "1.7.26";
#if BA_MONOLITHIC_BUILD

View File

@ -86,16 +86,8 @@ EventLoop::EventLoop(EventLoopID identifier_in, ThreadSource source)
// Block until the thread is bootstrapped.
// (maybe not necessary, but let's be cautious in case we'd
// try to use things like thread_id before they're known).
if (identifier_ == EventLoopID::kLogic) {
g_core->LifecycleLog("logic thread bootstrap wait begin");
}
client_listener_cv_.wait(lock, [this] { return bootstrapped_; });
if (identifier_ == EventLoopID::kLogic) {
g_core->LifecycleLog("logic thread bootstrap wait end");
}
break;
}
case ThreadSource::kWrapMain: {
@ -758,20 +750,12 @@ void EventLoop::PushRunnableSynchronous(Runnable* runnable) {
PushCrossThreadRunnable_(runnable, &complete);
}
if (identifier_ == EventLoopID::kLogic) {
g_core->LifecycleLog("logic thread sync run push begin");
}
// Now listen until our completion flag gets set.
client_listener_cv_.wait(lock, [complete_ptr] {
// Go back to sleep on spurious wakeups
// (if we're not actually complete yet).
return *complete_ptr;
});
if (identifier_ == EventLoopID::kLogic) {
g_core->LifecycleLog("logic thread sync run push end");
}
}
auto EventLoop::CheckPushSafety() -> bool {

View File

@ -23,8 +23,8 @@ void TemplateFsFeatureSet::OnModuleExec(PyObject* module) {
// Create our feature-set's C++ front-end.
g_template_fs = new TemplateFsFeatureSet();
// Store our C++ front-end on our Python module.
// This is what allows others to 'import' our C++ front end.
// Store our C++ front-end with our Python module. This is what allows
// other C++ code to 'import' our C++ front end and talk to us directly.
g_template_fs->StoreOnPythonModule(module);
// Import any Python stuff we use into objs_.

View File

@ -78,6 +78,6 @@ values = [
_hooks.implicit_sign_out, # kImplicitSignOutCall
_hooks.login_adapter_get_sign_in_token_response, # kLoginAdapterGetSignInTokenResponseCall
_hooks.open_url_with_webbrowser_module, # kOpenURLWithWebBrowserModuleCall
_env.on_native_module_import, # kOnNativeModuleImportCall
_env.on_native_module_import, # kEnvOnNativeModuleImportCall
_env.on_main_thread_start_app, # kOnMainThreadStartAppCall
]

View File

@ -12,7 +12,8 @@ values = [
app.lang.get_resource, # kGetResourceCall
app.lang.translate, # kTranslateCall
app.push_apply_app_config, # kAppPushApplyAppConfigCall
app.on_native_bootstrapped, # kAppOnNativeBootstrappedCall
app.on_native_start, # kAppOnNativeStartCall
app.on_native_bootstrapping_complete, # kAppOnNativeBootstrappingCompleteCall
app.on_native_pause, # kAppOnNativePauseCall
app.on_native_resume, # kAppOnNativeResumeCall
app.on_native_shutdown, # kAppOnNativeShutdownCall