From 0e15df6f59824ee3601253eeb500ab644b673c0b Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 30 Nov 2023 14:14:45 -0800 Subject: [PATCH 01/10] hopefully finally fixed multi-ui bug for real this time --- .efrocachemap | 58 ++++++------- CHANGELOG.md | 15 +++- .../ba_data/python/baclassic/_appdelegate.py | 3 +- .../ba_data/python/baclassic/_subsystem.py | 3 +- src/assets/ba_data/python/baenv.py | 2 +- .../ba_data/python/bascenev1lib/mainmenu.py | 26 ++++-- src/assets/ba_data/python/bauiv1/_hooks.py | 17 ++++ .../ba_data/python/bauiv1/_subsystem.py | 60 +++++++++++-- .../python/bauiv1lib/account/settings.py | 18 +++- .../ba_data/python/bauiv1lib/coop/browser.py | 21 ++++- .../ba_data/python/bauiv1lib/creditslist.py | 7 +- src/assets/ba_data/python/bauiv1lib/debug.py | 7 +- .../python/bauiv1lib/gather/__init__.py | 14 ++- .../ba_data/python/bauiv1lib/getcurrency.py | 9 +- src/assets/ba_data/python/bauiv1lib/helpui.py | 7 +- src/assets/ba_data/python/bauiv1lib/kiosk.py | 8 +- .../python/bauiv1lib/league/rankwindow.py | 7 +- .../ba_data/python/bauiv1lib/mainmenu.py | 72 +++++++++++++--- src/assets/ba_data/python/bauiv1lib/play.py | 34 ++++++-- .../python/bauiv1lib/playlist/browser.py | 14 ++- .../bauiv1lib/playlist/customizebrowser.py | 7 +- .../ba_data/python/bauiv1lib/playlist/edit.py | 14 ++- .../bauiv1lib/playlist/editcontroller.py | 18 ++-- .../python/bauiv1lib/playlist/editgame.py | 7 +- .../python/bauiv1lib/playlist/mapselect.py | 14 ++- .../ba_data/python/bauiv1lib/playoptions.py | 9 +- .../python/bauiv1lib/profile/browser.py | 21 ++++- .../ba_data/python/bauiv1lib/profile/edit.py | 22 ++++- .../ba_data/python/bauiv1lib/promocode.py | 14 ++- .../python/bauiv1lib/settings/advanced.py | 43 ++++++++-- .../python/bauiv1lib/settings/allsettings.py | 35 ++++++-- .../python/bauiv1lib/settings/audio.py | 14 ++- .../python/bauiv1lib/settings/controls.py | 42 +++++++-- .../python/bauiv1lib/settings/gamepad.py | 14 ++- .../bauiv1lib/settings/gamepadselect.py | 19 ++++- .../python/bauiv1lib/settings/graphics.py | 7 +- .../python/bauiv1lib/settings/keyboard.py | 14 ++- .../python/bauiv1lib/settings/nettesting.py | 15 +++- .../python/bauiv1lib/settings/plugins.py | 14 ++- .../bauiv1lib/settings/pluginsettings.py | 7 +- .../python/bauiv1lib/settings/remoteapp.py | 7 +- .../python/bauiv1lib/settings/testing.py | 8 +- .../python/bauiv1lib/settings/touchscreen.py | 7 +- .../python/bauiv1lib/soundtrack/browser.py | 14 ++- .../python/bauiv1lib/soundtrack/edit.py | 25 +++++- .../bauiv1lib/soundtrack/entrytypeselect.py | 21 ++++- .../ba_data/python/bauiv1lib/store/browser.py | 18 +++- src/assets/ba_data/python/bauiv1lib/watch.py | 7 +- src/ballistica/base/graphics/graphics.cc | 4 +- .../base/python/methods/python_methods_app.cc | 2 +- .../python/support/python_context_call.cc | 38 --------- .../base/python/support/python_context_call.h | 30 ------- .../class/python_class_session_player.cc | 5 +- src/ballistica/shared/ballistica.cc | 2 +- .../ui_v1/python/class/python_class_widget.cc | 76 +++++++++++++++-- .../ui_v1/python/class/python_class_widget.h | 3 + .../python/methods/python_methods_ui_v1.cc | 12 +-- src/ballistica/ui_v1/python/ui_v1_python.cc | 4 +- src/ballistica/ui_v1/python/ui_v1_python.h | 1 + src/ballistica/ui_v1/widget/button_widget.cc | 2 +- .../ui_v1/widget/check_box_widget.cc | 7 +- .../ui_v1/widget/container_widget.cc | 85 +++++++++++++------ .../ui_v1/widget/container_widget.h | 23 +++-- src/ballistica/ui_v1/widget/text_widget.cc | 4 +- src/ballistica/ui_v1/widget/widget.cc | 4 +- src/ballistica/ui_v1/widget/widget.h | 60 ++++++------- src/meta/bauiv1meta/pyembed/binding_ui_v1.py | 1 + 67 files changed, 914 insertions(+), 308 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index a48f4efd..a21fb9a3 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4060,26 +4060,26 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "9fe23e06319e4e256b9fa88814a14afa", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "4306acae21ce88235f9d1589086866e7", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "75e4f7d3a3df67dedd079ec3f4441094", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "bd5eda13f239b81886ac80596d6ade73", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "0805235a92dd91f96d43ea54575eecac", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "07589a61b11cbc5fca0bbc8b7fc1c955", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f28629761060c8152168b6792b71adae", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "1cfd1a33474cdb31834994f626385ed0", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "d50879a92d9d344c376f6f196d78d1be", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "78cd0edf2698f197f2acd80ca364fae7", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "d656f47118ebc3af57c40423cb258bc8", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "75df540b27779342a7c696e1bdbe593f", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "76f0dfacaa9ea67e45e8ccf3bb3bc1c6", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "2acc754bed825a9265e0621dc09899e0", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "62c2b6190de8784ea8750ea50e6a2304", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "e57358fd9a948a8ce82a54cdd5c766fc", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "d36e3303e13049eae5e7ec19861d300e", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "46971a2ca1e3021e52ea5d0f4938d2ff", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "19ebd36613cf62c4bd50e70b93371368", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "49ef5905b6e9e1a9caaed3d1c1da4ea5", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "c2b80379179c8731be37581e52259377", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "0a2257e46a20ae6453d888515a00f1a8", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "a8bf3602e161931f96c41989d9d4e630", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "15e5d036605366840182cf3f86d247ae", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "4212f5014bcf30f5ceb6e9ed00c1b443", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a9ee1b8f07dc7466b4daf90a34991f3b", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "cbc9bf3f8ddce331912b6dbd8f1c6415", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "6b4458aa454391fa0070099ef4cac711", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "fae27ee9108877bd75aaa222f02f239d", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "37f2b4219ffe85aa1d28ab7df7fd4c44", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "3f8de90abf2069a0881f8d91f1ec78b2", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "62a6ee7583ef9d83f9bb1601fce6ddaa", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "f495a8547ca8b824f80006603537d1cf", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "672c351fca7843d85a2be7aba13faf1f", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "50f6d105ad4c2bb2df4f3d335b6d2cfa", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "44e1d633accc2410ca6d825e4c464f45", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5cce0ce595304313ac28c02e235f19d7", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "9433fc9da2f4b7255f2c7fa95868604a", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "18eb1871a6db9bbe17f5e9678d3d492a", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "cf51dafe0553d06a44349d0911c21d71", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "a27963487e346338e4c216bd4fbb9e2a", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c", @@ -4096,14 +4096,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "852fe46c736082611a831a618923c241", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "36fbda7829ed5c2862c34feb09b03402", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "852fe46c736082611a831a618923c241", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "38b4b5b85a9bafdb76222d0f0c962b06", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9a8af3d217bcb0bacfaed4c30dd5f42e", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6d10ca306f60d66efb4942636e4955d6", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "1c65d36e4420ed79380dc8c041c94a8b", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "9b1b72f3d41c89a6b06288be63e8f40a", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "e0f2eb8ea024bc88e999b9dc16317fd4", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "42be1225757328f432d91de950444ba0", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "05bc2832cc0c9fba308668fc1a6d3b0f", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "8ec03141da397548f8a06259222c14e3", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9f20a365cc0ec1831e0e301a34198b0e", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "37980dc34b9ad281cdab738f7053af26", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "148e0f3a1ee77607d0b93a636b253295", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "e20a2aa49741757075634f422dc5ac7a", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "fa7018f7c2ee8c952bff7879b92709cb", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "cd38a41872e74b43de005375330e3cd0", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "5bc4faf70f09f56e2ced27cafe2ad3e6", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f", @@ -4114,5 +4114,5 @@ "src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5", "src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "d80f970053099b3044204bfe29ddefce", "src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f", - "src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "8f4c2070174bdc2fbf735180394d7b3a" + "src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "f5f054050d2b2fcd3763a4833fb32269" } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f3ac4a8..fd6cae22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.30 (build 21636, api 8, 2023-11-30) +### 1.7.30 (build 21639, api 8, 2023-11-30) - Continued work on the big 1.7.28 update. - Got the Android version back up and running. There's been lots of cleanup and simplification on the Android layer, cleaning out years of cruft. This should @@ -21,9 +21,16 @@ builds when tv-border was on (Thanks for the heads-up Loup(Dliwk's fan)!). - (build 21631) Fixes an issue where '^^^^^^^^^^^^^' lines in stack traces could get chopped into tiny bits each on their own line in the dev console. -- Fixed a longstanding issue where multiple key presses simultaneously could - cause multiple windows to pop up where only one is expected. Please holler if - you still see this problem happening anywhere. +- Hopefully finally fixed a longstanding issue where obscure cases such as + multiple key presses simultaneously could cause multiple main menu windows to + pop up. Please holler if you still see this problem happening anywhere. Also + added a few related safety checks and warnings to help ensure UI code is free + from such problems going forward. To make sure your custom UIs are behaving + well in this system, do the following two things: 1) any time you call + `set_main_menu_window()`, pass your existing main menu window root widget as + `from_window`. 2) In any call that can lead to you switching the main menu + window, check if your root widget is dead or transitioning out first and abort + if it is. See any window in `ui_v1_lib` for examples. ### 1.7.29 (build 21619, api 8, 2023-11-21) diff --git a/src/assets/ba_data/python/baclassic/_appdelegate.py b/src/assets/ba_data/python/baclassic/_appdelegate.py index 4595bd63..1dd1a6b0 100644 --- a/src/assets/ba_data/python/baclassic/_appdelegate.py +++ b/src/assets/ba_data/python/baclassic/_appdelegate.py @@ -41,5 +41,6 @@ class AppDelegate: sessiontype, settings, completion_call=completion_call, - ).get_root_widget() + ).get_root_widget(), + from_window=False, # Disable check since we don't know. ) diff --git a/src/assets/ba_data/python/baclassic/_subsystem.py b/src/assets/ba_data/python/baclassic/_subsystem.py index f8aba3bc..6746251c 100644 --- a/src/assets/ba_data/python/baclassic/_subsystem.py +++ b/src/assets/ba_data/python/baclassic/_subsystem.py @@ -800,5 +800,6 @@ class ClassicSubsystem(babase.AppSubsystem): bauiv1.getsound('swish').play() babase.app.ui_v1.set_main_menu_window( - MainMenuWindow().get_root_widget() + MainMenuWindow().get_root_widget(), + from_window=False, # Disable check here. ) diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 82c27c87..4e411438 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21636 +TARGET_BALLISTICA_BUILD = 21639 TARGET_BALLISTICA_VERSION = '1.7.30' diff --git a/src/assets/ba_data/python/bascenev1lib/mainmenu.py b/src/assets/ba_data/python/bascenev1lib/mainmenu.py index 88b85249..6092f0ce 100644 --- a/src/assets/ba_data/python/bascenev1lib/mainmenu.py +++ b/src/assets/ba_data/python/bascenev1lib/mainmenu.py @@ -317,7 +317,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): from bauiv1lib.kiosk import KioskWindow bs.app.ui_v1.set_main_menu_window( - KioskWindow().get_root_widget() + KioskWindow().get_root_widget(), + from_window=False, # Disable check here. ) # ..or in normal cases go back to the main menu else: @@ -326,14 +327,16 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): from bauiv1lib.gather import GatherWindow bs.app.ui_v1.set_main_menu_window( - GatherWindow(transition=None).get_root_widget() + GatherWindow(transition=None).get_root_widget(), + from_window=False, # Disable check here. ) elif main_menu_location == 'Watch': # pylint: disable=cyclic-import from bauiv1lib.watch import WatchWindow bs.app.ui_v1.set_main_menu_window( - WatchWindow(transition=None).get_root_widget() + WatchWindow(transition=None).get_root_widget(), + from_window=False, # Disable check here. ) elif main_menu_location == 'Team Game Select': # pylint: disable=cyclic-import @@ -344,7 +347,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): bs.app.ui_v1.set_main_menu_window( PlaylistBrowserWindow( sessiontype=bs.DualTeamSession, transition=None - ).get_root_widget() + ).get_root_widget(), + from_window=False, # Disable check here. ) elif main_menu_location == 'Free-for-All Game Select': # pylint: disable=cyclic-import @@ -356,28 +360,34 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]): PlaylistBrowserWindow( sessiontype=bs.FreeForAllSession, transition=None, - ).get_root_widget() + ).get_root_widget(), + from_window=False, # Disable check here. ) elif main_menu_location == 'Coop Select': # pylint: disable=cyclic-import from bauiv1lib.coop.browser import CoopBrowserWindow bs.app.ui_v1.set_main_menu_window( - CoopBrowserWindow(transition=None).get_root_widget() + CoopBrowserWindow( + transition=None + ).get_root_widget(), + from_window=False, # Disable check here. ) elif main_menu_location == 'Benchmarks & Stress Tests': # pylint: disable=cyclic-import from bauiv1lib.debug import DebugWindow bs.app.ui_v1.set_main_menu_window( - DebugWindow(transition=None).get_root_widget() + DebugWindow(transition=None).get_root_widget(), + from_window=False, # Disable check here. ) else: # pylint: disable=cyclic-import from bauiv1lib.mainmenu import MainMenuWindow bs.app.ui_v1.set_main_menu_window( - MainMenuWindow(transition=None).get_root_widget() + MainMenuWindow(transition=None).get_root_widget(), + from_window=None, ) # attempt to show any pending offers immediately. diff --git a/src/assets/ba_data/python/bauiv1/_hooks.py b/src/assets/ba_data/python/bauiv1/_hooks.py index c7b6072a..30903564 100644 --- a/src/assets/ba_data/python/bauiv1/_hooks.py +++ b/src/assets/ba_data/python/bauiv1/_hooks.py @@ -6,6 +6,7 @@ from __future__ import annotations import logging +import inspect from typing import TYPE_CHECKING import _bauiv1 @@ -87,3 +88,19 @@ def show_url_window(address: str) -> None: return app.classic.show_url_window(address) + + +def double_transition_out_warning() -> None: + """Called if a widget is set to transition out twice.""" + caller_frame = inspect.stack()[1] + caller_filename = caller_frame.filename + caller_line_number = caller_frame.lineno + logging.warning( + 'ContainerWidget was set to transition out twice;' + ' this often implies buggy code (%s line %s).\n' + ' Generally you should check the value of' + ' _root_widget.transitioning_out and only kick off transitions' + ' when that is False.', + caller_filename, + caller_line_number, + ) diff --git a/src/assets/ba_data/python/bauiv1/_subsystem.py b/src/assets/ba_data/python/bauiv1/_subsystem.py index 83d88d87..b2645faf 100644 --- a/src/assets/ba_data/python/bauiv1/_subsystem.py +++ b/src/assets/ba_data/python/bauiv1/_subsystem.py @@ -5,6 +5,7 @@ from __future__ import annotations import logging +import inspect from typing import TYPE_CHECKING import babase @@ -116,21 +117,69 @@ class UIV1Subsystem(babase.AppSubsystem): # FIXME: Can probably kill this if we do immediate UI death checks. self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True) - def set_main_menu_window(self, window: bauiv1.Widget) -> None: - """Set the current 'main' window, replacing any existing.""" + def set_main_menu_window( + self, + window: bauiv1.Widget, + from_window: bauiv1.Widget | None | bool = True, + ) -> None: + """Set the current 'main' window, replacing any existing. + + If 'from_window' is passed as a bauiv1.Widget or None, a warning + will be issued if it that value does not match the current main + window. This can help clean up flawed code that can lead to bad + UI states. A value of False will disable the check. + """ + existing = self._main_menu_window - from inspect import currentframe, getframeinfo + + try: + if isinstance(from_window, bool): + # For default val True we warn that the arg wasn't + # passed. False can be explicitly passed to disable this + # check. + if from_window is True: + caller_frame = inspect.stack()[1] + caller_filename = caller_frame.filename + caller_line_number = caller_frame.lineno + logging.warning( + 'set_main_menu_window() should be passed a' + " 'from_window' value to help ensure proper UI behavior" + ' (%s line %i).', + caller_filename, + caller_line_number, + ) + else: + # For everything else, warn if what they passed wasn't + # the previous main menu widget. + if from_window is not existing: + caller_frame = inspect.stack()[1] + caller_filename = caller_frame.filename + caller_line_number = caller_frame.lineno + logging.warning( + "set_main_menu_window() was passed 'from_window' %s" + ' but existing main-menu-window is %s. (%s line %i).', + from_window, + existing, + caller_filename, + caller_line_number, + ) + except Exception: + # Prevent any bugs in these checks from causing problems. + logging.exception('Error checking from_window') + + # Once the above code leads to us fixing all leftover window bugs + # at the source, we can kill the code below. # Let's grab the location where we were called from to report # if we have to force-kill the existing window (which normally # should not happen). frameline = None try: - frame = currentframe() + frame = inspect.currentframe() if frame is not None: frame = frame.f_back if frame is not None: - frameinfo = getframeinfo(frame) + frameinfo = inspect.getframeinfo(frame) frameline = f'{frameinfo.filename} {frameinfo.lineno}' except Exception: logging.exception('Error calcing line for set_main_menu_window') @@ -167,6 +216,7 @@ class UIV1Subsystem(babase.AppSubsystem): ) else: self._main_menu_window.delete() + self._main_menu_window = None def add_main_menu_close_callback(self, call: Callable[[], Any]) -> None: """(internal)""" diff --git a/src/assets/ba_data/python/bauiv1lib/account/settings.py b/src/assets/ba_data/python/bauiv1lib/account/settings.py index 8c29aee5..63937ef6 100644 --- a/src/assets/ba_data/python/bauiv1lib/account/settings.py +++ b/src/assets/ba_data/python/bauiv1lib/account/settings.py @@ -1507,9 +1507,18 @@ class AccountSettingsWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.profile.browser import ProfileBrowserWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') - ProfileBrowserWindow(origin_widget=self._player_profiles_button) + bui.app.ui_v1.set_main_menu_window( + ProfileBrowserWindow( + origin_widget=self._player_profiles_button + ).get_root_widget(), + from_window=self._root_widget, + ) def _cancel_sign_in_press(self) -> None: # If we're waiting on an adapter to give us credentials, abort. @@ -1670,6 +1679,10 @@ class AccountSettingsWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.mainmenu import MainMenuWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out @@ -1678,7 +1691,8 @@ class AccountSettingsWindow(bui.Window): if not self._modal: assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget() + MainMenuWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _save_state(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/coop/browser.py b/src/assets/ba_data/python/bauiv1lib/coop/browser.py index 20760381..256e6771 100644 --- a/src/assets/ba_data/python/bauiv1lib/coop/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/coop/browser.py @@ -1019,6 +1019,10 @@ class CoopBrowserWindow(bui.Window): from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.league.rankwindow import LeagueRankWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + plus = bui.app.plus assert plus is not None @@ -1032,7 +1036,8 @@ class CoopBrowserWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( LeagueRankWindow( origin_widget=self._league_rank_button.get_button() - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _switch_to_score( @@ -1043,6 +1048,10 @@ class CoopBrowserWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.account import show_sign_in_prompt + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + plus = bui.app.plus assert plus is not None @@ -1058,7 +1067,8 @@ class CoopBrowserWindow(bui.Window): origin_widget=self._store_button.get_button(), show_tab=show_tab, back_location='CoopBrowserWindow', - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def is_tourney_data_up_to_date(self) -> bool: @@ -1218,6 +1228,10 @@ class CoopBrowserWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.play import PlayWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + # If something is selected, store it. self._save_state() bui.containerwidget( @@ -1225,7 +1239,8 @@ class CoopBrowserWindow(bui.Window): ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - PlayWindow(transition='in_left').get_root_widget() + PlayWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _save_state(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/creditslist.py b/src/assets/ba_data/python/bauiv1lib/creditslist.py index e6a0769f..c087dfbf 100644 --- a/src/assets/ba_data/python/bauiv1lib/creditslist.py +++ b/src/assets/ba_data/python/bauiv1lib/creditslist.py @@ -359,10 +359,15 @@ class CreditsListWindow(bui.Window): def _back(self) -> None: from bauiv1lib.mainmenu import MainMenuWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget() + MainMenuWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/debug.py b/src/assets/ba_data/python/bauiv1lib/debug.py index 7cd5e5d2..b397610f 100644 --- a/src/assets/ba_data/python/bauiv1lib/debug.py +++ b/src/assets/ba_data/python/bauiv1lib/debug.py @@ -379,8 +379,13 @@ class DebugWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.advanced import AdvancedSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget() + AdvancedSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/gather/__init__.py b/src/assets/ba_data/python/bauiv1lib/gather/__init__.py index 3b5f02d6..8fc2e841 100644 --- a/src/assets/ba_data/python/bauiv1lib/gather/__init__.py +++ b/src/assets/ba_data/python/bauiv1lib/gather/__init__.py @@ -270,12 +270,17 @@ class GatherWindow(bui.Window): """Called by the private-hosting tab to select a playlist.""" from bauiv1lib.play import PlayWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.selecting_private_party_playlist = True bui.app.ui_v1.set_main_menu_window( - PlayWindow(origin_widget=origin_widget).get_root_widget() + PlayWindow(origin_widget=origin_widget).get_root_widget(), + from_window=self._root_widget, ) def _set_tab(self, tab_id: TabID) -> None: @@ -383,11 +388,16 @@ class GatherWindow(bui.Window): def _back(self) -> None: from bauiv1lib.mainmenu import MainMenuWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget() + MainMenuWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/getcurrency.py b/src/assets/ba_data/python/bauiv1lib/getcurrency.py index a87ec18f..78ab9af1 100644 --- a/src/assets/ba_data/python/bauiv1lib/getcurrency.py +++ b/src/assets/ba_data/python/bauiv1lib/getcurrency.py @@ -732,8 +732,13 @@ class GetCurrencyWindow(bui.Window): def _back(self) -> None: from bauiv1lib.store import browser + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + if self._transitioning_out: return + bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) @@ -745,7 +750,9 @@ class GetCurrencyWindow(bui.Window): ).get_root_widget() if not self._from_modal_store: assert bui.app.classic is not None - bui.app.ui_v1.set_main_menu_window(window) + bui.app.ui_v1.set_main_menu_window( + window, from_window=self._root_widget + ) self._transitioning_out = True diff --git a/src/assets/ba_data/python/bauiv1lib/helpui.py b/src/assets/ba_data/python/bauiv1lib/helpui.py index 6a7ed437..e2fc64ea 100644 --- a/src/assets/ba_data/python/bauiv1lib/helpui.py +++ b/src/assets/ba_data/python/bauiv1lib/helpui.py @@ -645,11 +645,16 @@ class HelpWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.mainmenu import MainMenuWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) if self._main_menu: assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget() + MainMenuWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/kiosk.py b/src/assets/ba_data/python/bauiv1lib/kiosk.py index 377a86d0..ab1ca87e 100644 --- a/src/assets/ba_data/python/bauiv1lib/kiosk.py +++ b/src/assets/ba_data/python/bauiv1lib/kiosk.py @@ -501,9 +501,15 @@ class KioskWindow(bui.Window): def _do_full_menu(self) -> None: from bauiv1lib.mainmenu import MainMenuWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + assert bui.app.classic is not None self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') bui.app.classic.did_menu_intro = True # prevent delayed transition-in - bui.app.ui_v1.set_main_menu_window(MainMenuWindow().get_root_widget()) + bui.app.ui_v1.set_main_menu_window( + MainMenuWindow().get_root_widget(), from_window=self._root_widget + ) diff --git a/src/assets/ba_data/python/bauiv1lib/league/rankwindow.py b/src/assets/ba_data/python/bauiv1lib/league/rankwindow.py index d108cbb3..725bb318 100644 --- a/src/assets/ba_data/python/bauiv1lib/league/rankwindow.py +++ b/src/assets/ba_data/python/bauiv1lib/league/rankwindow.py @@ -1142,6 +1142,10 @@ class LeagueRankWindow(bui.Window): def _back(self) -> None: from bauiv1lib.coop.browser import CoopBrowserWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out @@ -1149,5 +1153,6 @@ class LeagueRankWindow(bui.Window): if not self._modal: assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - CoopBrowserWindow(transition='in_left').get_root_widget() + CoopBrowserWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py index 7e6ca3d4..16f318ae 100644 --- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py +++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py @@ -1038,6 +1038,10 @@ class MainMenuWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.confirm import QuitWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + # Note: Normally we should go through bui.quit(confirm=True) but # invoking the window directly lets us scale it up from the # button. @@ -1047,24 +1051,34 @@ class MainMenuWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.kiosk import KioskWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - KioskWindow(transition='in_left').get_root_widget() + KioskWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _show_account_window(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.account.settings import AccountSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( AccountSettingsWindow( origin_widget=self._account_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _on_store_pressed(self) -> None: @@ -1072,6 +1086,10 @@ class MainMenuWindow(bui.Window): from bauiv1lib.store.browser import StoreBrowserWindow from bauiv1lib.account import show_sign_in_prompt + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + plus = bui.app.plus assert plus is not None @@ -1084,7 +1102,8 @@ class MainMenuWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( StoreBrowserWindow( origin_widget=self._store_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _is_benchmark(self) -> bool: @@ -1149,8 +1168,11 @@ class MainMenuWindow(bui.Window): def _end_game(self) -> None: assert bui.app.classic is not None - if not self._root_widget: + + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: return + bui.containerwidget(edit=self._root_widget, transition='out_left') bui.app.classic.return_to_main_menu_session_gracefully(reset_ui=False) @@ -1166,39 +1188,54 @@ class MainMenuWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.creditslist import CreditsListWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( CreditsListWindow( origin_widget=self._credits_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _howtoplay(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.helpui import HelpWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( HelpWindow( main_menu=True, origin_widget=self._how_to_play_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _settings(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings.allsettings import AllSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( AllSettingsWindow( origin_widget=self._settings_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _resume_and_call(self, call: Callable[[], Any]) -> None: @@ -1281,35 +1318,50 @@ class MainMenuWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.gather import GatherWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - GatherWindow(origin_widget=self._gather_button).get_root_widget() + GatherWindow(origin_widget=self._gather_button).get_root_widget(), + from_window=self._root_widget, ) def _watch_press(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.watch import WatchWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - WatchWindow(origin_widget=self._watch_button).get_root_widget() + WatchWindow(origin_widget=self._watch_button).get_root_widget(), + from_window=self._root_widget, ) def _play_press(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.play import PlayWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.selecting_private_party_playlist = False bui.app.ui_v1.set_main_menu_window( - PlayWindow(origin_widget=self._start_button).get_root_widget() + PlayWindow(origin_widget=self._start_button).get_root_widget(), + from_window=self._root_widget, ) def _resume(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/play.py b/src/assets/ba_data/python/bauiv1lib/play.py index ef1ca896..f06a9d59 100644 --- a/src/assets/ba_data/python/bauiv1lib/play.py +++ b/src/assets/ba_data/python/bauiv1lib/play.py @@ -521,13 +521,19 @@ class PlayWindow(bui.Window): def _back(self) -> None: # pylint: disable=cyclic-import + + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + if self._is_main_menu: from bauiv1lib.mainmenu import MainMenuWindow self._save_state() assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget() + MainMenuWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) bui.containerwidget( edit=self._root_widget, transition=self._transition_out @@ -538,7 +544,8 @@ class PlayWindow(bui.Window): self._save_state() assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - GatherWindow(transition='in_left').get_root_widget() + GatherWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) bui.containerwidget( edit=self._root_widget, transition=self._transition_out @@ -549,6 +556,10 @@ class PlayWindow(bui.Window): from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.coop.browser import CoopBrowserWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + plus = bui.app.plus assert plus is not None @@ -559,26 +570,38 @@ class PlayWindow(bui.Window): bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - CoopBrowserWindow(origin_widget=self._coop_button).get_root_widget() + CoopBrowserWindow( + origin_widget=self._coop_button + ).get_root_widget(), + from_window=self._root_widget, ) def _team_tourney(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.playlist.browser import PlaylistBrowserWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( PlaylistBrowserWindow( origin_widget=self._teams_button, sessiontype=bs.DualTeamSession - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _free_for_all(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.playlist.browser import PlaylistBrowserWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None @@ -586,7 +609,8 @@ class PlayWindow(bui.Window): PlaylistBrowserWindow( origin_widget=self._free_for_all_button, sessiontype=bs.FreeForAllSession, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _draw_dude( diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/browser.py b/src/assets/ba_data/python/bauiv1lib/playlist/browser.py index 287496c2..8a61e91c 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/browser.py @@ -684,6 +684,10 @@ class PlaylistBrowserWindow(bui.Window): PlaylistCustomizeBrowserWindow, ) + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None @@ -691,13 +695,18 @@ class PlaylistBrowserWindow(bui.Window): PlaylistCustomizeBrowserWindow( origin_widget=self._customize_button, sessiontype=self._sessiontype, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _on_back_press(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.play import PlayWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + # Store our selected playlist if that's changed. if self._selected_playlist is not None: prev_sel = bui.app.config.get( @@ -716,7 +725,8 @@ class PlaylistBrowserWindow(bui.Window): ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - PlayWindow(transition='in_left').get_root_widget() + PlayWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _save_state(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py b/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py index 789e7f2e..f0a433b5 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py @@ -323,6 +323,10 @@ class PlaylistCustomizeBrowserWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.playlist import browser + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + if self._selected_playlist_name is not None: cfg = bui.app.config cfg[ @@ -337,7 +341,8 @@ class PlaylistCustomizeBrowserWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( browser.PlaylistBrowserWindow( transition='in_left', sessiontype=self._sessiontype - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _select(self, name: str, index: int) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/edit.py b/src/assets/ba_data/python/bauiv1lib/playlist/edit.py index 1b971514..07f7adae 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/edit.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/edit.py @@ -283,6 +283,10 @@ class PlaylistEditWindow(bui.Window): PlaylistCustomizeBrowserWindow, ) + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.getsound('powerdown01').play() bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None @@ -293,7 +297,8 @@ class PlaylistEditWindow(bui.Window): select_playlist=( self._editcontroller.get_existing_playlist_name() ), - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _add(self) -> None: @@ -315,6 +320,10 @@ class PlaylistEditWindow(bui.Window): PlaylistCustomizeBrowserWindow, ) + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + plus = bui.app.plus assert plus is not None @@ -380,7 +389,8 @@ class PlaylistEditWindow(bui.Window): transition='in_left', sessiontype=self._editcontroller.get_session_type(), select_playlist=new_name, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _save_press_with_sound(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/editcontroller.py b/src/assets/ba_data/python/bauiv1lib/playlist/editcontroller.py index 26e53b27..7ed9a92a 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/editcontroller.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/editcontroller.py @@ -92,7 +92,8 @@ class PlaylistEditController: bui.app.ui_v1.set_main_menu_window( PlaylistEditWindow( editcontroller=self, transition=transition - ).get_root_widget() + ).get_root_widget(), + from_window=False, # Disable this check. ) def get_config_name(self) -> str: @@ -150,7 +151,8 @@ class PlaylistEditController: assert bui.app.classic is not None bui.app.ui_v1.clear_main_menu_window(transition='out_left') bui.app.ui_v1.set_main_menu_window( - PlaylistAddGameWindow(editcontroller=self).get_root_widget() + PlaylistAddGameWindow(editcontroller=self).get_root_widget(), + from_window=None, ) def edit_game_pressed(self) -> None: @@ -175,7 +177,8 @@ class PlaylistEditController: bui.app.ui_v1.set_main_menu_window( PlaylistEditWindow( editcontroller=self, transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=None, ) def _show_edit_ui( @@ -205,7 +208,8 @@ class PlaylistEditController: bui.app.ui_v1.set_main_menu_window( PlaylistEditWindow( editcontroller=self, transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=None, ) # Otherwise we were adding; go back to the add type choice list. @@ -214,7 +218,8 @@ class PlaylistEditController: bui.app.ui_v1.set_main_menu_window( PlaylistAddGameWindow( editcontroller=self, transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=None, ) else: # Make sure type is in there. @@ -236,5 +241,6 @@ class PlaylistEditController: bui.app.ui_v1.set_main_menu_window( PlaylistEditWindow( editcontroller=self, transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=None, ) diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py b/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py index 4e7c3d84..cb951624 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py @@ -514,6 +514,10 @@ class PlaylistEditGameWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.playlist.mapselect import PlaylistMapSelectWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + # Replace ourself with the map-select UI. bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None @@ -524,7 +528,8 @@ class PlaylistEditGameWindow(bui.Window): copy.deepcopy(self._getconfig()), self._edit_info, self._completion_call, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _choice_inc( diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py b/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py index c86854d6..4d351379 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py @@ -273,6 +273,10 @@ class PlaylistMapSelectWindow(bui.Window): def _select(self, map_name: str) -> None: from bauiv1lib.playlist.editgame import PlaylistEditGameWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._config['settings']['map'] = map_name bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None @@ -285,7 +289,8 @@ class PlaylistMapSelectWindow(bui.Window): default_selection='map', transition='in_left', edit_info=self._edit_info, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _select_with_delay(self, map_name: str) -> None: @@ -296,6 +301,10 @@ class PlaylistMapSelectWindow(bui.Window): def _cancel(self) -> None: from bauiv1lib.playlist.editgame import PlaylistEditGameWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( @@ -307,5 +316,6 @@ class PlaylistMapSelectWindow(bui.Window): default_selection='map', transition='in_left', edit_info=self._edit_info, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/playoptions.py b/src/assets/ba_data/python/bauiv1lib/playoptions.py index fdc79e74..12fd4e3e 100644 --- a/src/assets/ba_data/python/bauiv1lib/playoptions.py +++ b/src/assets/ba_data/python/bauiv1lib/playoptions.py @@ -140,7 +140,6 @@ class PlayOptionsWindow(PopupWindow): if show_shuffle_check_box: self._height += 40 - # Creates our _root_widget. uiscale = bui.app.ui_v1.uiscale scale = ( 1.69 @@ -149,6 +148,7 @@ class PlayOptionsWindow(PopupWindow): if uiscale is bui.UIScale.MEDIUM else 0.85 ) + # Creates our _root_widget. super().__init__( position=scale_origin, size=(self._width, self._height), scale=scale ) @@ -448,6 +448,10 @@ class PlayOptionsWindow(PopupWindow): self._transition_out() def _on_ok_press(self) -> None: + # no-op if our underlying widget is dead or on its way out. + if not self.root_widget or self.root_widget.transitioning_out: + return + # Disallow if our playlist has disappeared. if not self._does_target_playlist_exist(): return @@ -479,7 +483,8 @@ class PlayOptionsWindow(PopupWindow): bui.getsound('gunCocking').play() assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - GatherWindow(transition='in_right').get_root_widget() + GatherWindow(transition='in_right').get_root_widget(), + from_window=self.root_widget, ) self._transition_out(transition='out_left') if self._delegate is not None: diff --git a/src/assets/ba_data/python/bauiv1lib/profile/browser.py b/src/assets/ba_data/python/bauiv1lib/profile/browser.py index 0fc51e14..2f47251e 100644 --- a/src/assets/ba_data/python/bauiv1lib/profile/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/profile/browser.py @@ -212,6 +212,10 @@ class ProfileBrowserWindow(bui.Window): from bauiv1lib.profile.edit import EditProfileWindow from bauiv1lib.purchase import PurchaseWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + plus = bui.app.plus assert plus is not None @@ -252,7 +256,8 @@ class ProfileBrowserWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( EditProfileWindow( existing_profile=None, in_main_menu=self._in_main_menu - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _delete_profile(self) -> None: @@ -301,6 +306,10 @@ class ProfileBrowserWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.profile.edit import EditProfileWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + if self._selected_profile is None: bui.getsound('error').play() bui.screenmessage( @@ -313,7 +322,8 @@ class ProfileBrowserWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( EditProfileWindow( self._selected_profile, in_main_menu=self._in_main_menu - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _select(self, name: str, index: int) -> None: @@ -324,6 +334,10 @@ class ProfileBrowserWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.account.settings import AccountSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + assert bui.app.classic is not None self._save_state() @@ -333,7 +347,8 @@ class ProfileBrowserWindow(bui.Window): if self._in_main_menu: assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - AccountSettingsWindow(transition='in_left').get_root_widget() + AccountSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) # If we're being called up standalone, handle pause/resume ourself. diff --git a/src/assets/ba_data/python/bauiv1lib/profile/edit.py b/src/assets/ba_data/python/bauiv1lib/profile/edit.py index 5c9c899d..b216d1f9 100644 --- a/src/assets/ba_data/python/bauiv1lib/profile/edit.py +++ b/src/assets/ba_data/python/bauiv1lib/profile/edit.py @@ -18,12 +18,18 @@ class EditProfileWindow(bui.Window): # FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION. def reload_window(self) -> None: """Transitions out and recreates ourself.""" + + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( EditProfileWindow( self.getname(), self._in_main_menu - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def __init__( @@ -653,6 +659,10 @@ class EditProfileWindow(bui.Window): def _cancel(self) -> None: from bauiv1lib.profile.browser import ProfileBrowserWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( @@ -660,7 +670,8 @@ class EditProfileWindow(bui.Window): 'in_left', selected_profile=self._existing_profile, in_main_menu=self._in_main_menu, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _set_color(self, color: tuple[float, float, float]) -> None: @@ -759,6 +770,10 @@ class EditProfileWindow(bui.Window): """Save has been selected.""" from bauiv1lib.profile.browser import ProfileBrowserWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return False + plus = bui.app.plus assert plus is not None @@ -808,6 +823,7 @@ class EditProfileWindow(bui.Window): 'in_left', selected_profile=new_name, in_main_menu=self._in_main_menu, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) return True diff --git a/src/assets/ba_data/python/bauiv1lib/promocode.py b/src/assets/ba_data/python/bauiv1lib/promocode.py index 3e7f035c..3cf745b5 100644 --- a/src/assets/ba_data/python/bauiv1lib/promocode.py +++ b/src/assets/ba_data/python/bauiv1lib/promocode.py @@ -142,13 +142,18 @@ class PromoCodeWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.advanced import AdvancedSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) if not self._modal: assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget() + AdvancedSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _activate_enter_button(self) -> None: @@ -158,6 +163,10 @@ class PromoCodeWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.advanced import AdvancedSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + plus = bui.app.plus assert plus is not None @@ -167,7 +176,8 @@ class PromoCodeWindow(bui.Window): if not self._modal: assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget() + AdvancedSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) plus.add_v1_account_transaction( { diff --git a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py index e7724815..0db77056 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py @@ -682,11 +682,16 @@ class AdvancedSettingsWindow(bui.Window): def _on_vr_test_press(self) -> None: from bauiv1lib.settings.vrtesting import VRTestingWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - VRTestingWindow(transition='in_right').get_root_widget() + VRTestingWindow(transition='in_right').get_root_widget(), + from_window=self._root_widget, ) def _on_net_test_press(self) -> None: @@ -694,6 +699,10 @@ class AdvancedSettingsWindow(bui.Window): assert plus is not None from bauiv1lib.settings.nettesting import NetTestingWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + # Net-testing requires a signed in v1 account. if plus.get_v1_account_state() != 'signed_in': bui.screenmessage( @@ -706,7 +715,8 @@ class AdvancedSettingsWindow(bui.Window): bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - NetTestingWindow(transition='in_right').get_root_widget() + NetTestingWindow(transition='in_right').get_root_widget(), + from_window=self._root_widget, ) def _on_friend_promo_code_press(self) -> None: @@ -724,17 +734,26 @@ class AdvancedSettingsWindow(bui.Window): def _on_plugins_button_press(self) -> None: from bauiv1lib.settings.plugins import PluginWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - PluginWindow(origin_widget=self._plugins_button).get_root_widget() + PluginWindow(origin_widget=self._plugins_button).get_root_widget(), + from_window=self._root_widget, ) def _on_promo_code_press(self) -> None: from bauiv1lib.promocode import PromoCodeWindow from bauiv1lib.account import show_sign_in_prompt + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + plus = bui.app.plus assert plus is not None @@ -742,23 +761,30 @@ class AdvancedSettingsWindow(bui.Window): if plus.get_v1_account_state() != 'signed_in': show_sign_in_prompt() return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( PromoCodeWindow( origin_widget=self._promo_code_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _on_benchmark_press(self) -> None: from bauiv1lib.debug import DebugWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - DebugWindow(transition='in_right').get_root_widget() + DebugWindow(transition='in_right').get_root_widget(), + from_window=self._root_widget, ) def _save_state(self) -> None: @@ -908,11 +934,16 @@ class AdvancedSettingsWindow(bui.Window): def _do_back(self) -> None: from bauiv1lib.settings.allsettings import AllSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - AllSettingsWindow(transition='in_left').get_root_widget() + AllSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py b/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py index d6d18b5f..75e0f633 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py @@ -235,65 +235,90 @@ class AllSettingsWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.mainmenu import MainMenuWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget() + MainMenuWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _do_controllers(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings.controls import ControlsSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( ControlsSettingsWindow( origin_widget=self._controllers_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _do_graphics(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings.graphics import GraphicsSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( GraphicsSettingsWindow( origin_widget=self._graphics_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _do_audio(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings.audio import AudioSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( AudioSettingsWindow( origin_widget=self._audio_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _do_advanced(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings.advanced import AdvancedSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( AdvancedSettingsWindow( origin_widget=self._advanced_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _save_state(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/settings/audio.py b/src/assets/ba_data/python/bauiv1lib/settings/audio.py index 643e23fa..fc39b719 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/audio.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/audio.py @@ -237,6 +237,10 @@ class AudioSettingsWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.soundtrack import browser as stb + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + # We require disk access for soundtracks; # if we don't have it, request it. if not bui.have_permission(bui.Permission.STORAGE): @@ -256,13 +260,18 @@ class AudioSettingsWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( stb.SoundtrackBrowserWindow( origin_widget=self._soundtrack_button - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _back(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings import allsettings + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out @@ -271,7 +280,8 @@ class AudioSettingsWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( allsettings.AllSettingsWindow( transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _save_state(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/settings/controls.py b/src/assets/ba_data/python/bauiv1lib/settings/controls.py index 168df15c..108657f5 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/controls.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/controls.py @@ -367,59 +367,84 @@ class ControlsSettingsWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.keyboard import ConfigKeyboardWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( ConfigKeyboardWindow( bs.getinputdevice('Keyboard', '#1') - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _config_keyboard2(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings.keyboard import ConfigKeyboardWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( ConfigKeyboardWindow( bs.getinputdevice('Keyboard', '#2') - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _do_mobile_devices(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - RemoteAppSettingsWindow().get_root_widget() + RemoteAppSettingsWindow().get_root_widget(), + from_window=self._root_widget, ) def _do_gamepads(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings.gamepadselect import GamepadSelectWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - GamepadSelectWindow().get_root_widget() + GamepadSelectWindow().get_root_widget(), + from_window=self._root_widget, ) def _do_touchscreen(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - TouchscreenSettingsWindow().get_root_widget() + TouchscreenSettingsWindow().get_root_widget(), + from_window=self._root_widget, ) def _save_state(self) -> None: @@ -466,11 +491,16 @@ class ControlsSettingsWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.allsettings import AllSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - AllSettingsWindow(transition='in_left').get_root_widget() + AllSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py b/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py index 3c67b23f..a63847d4 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py @@ -795,19 +795,28 @@ class GamepadSettingsWindow(bui.Window): def _cancel(self) -> None: from bauiv1lib.settings.controls import ControlsSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) if self._is_main_menu: assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - ControlsSettingsWindow(transition='in_left').get_root_widget() + ControlsSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _save(self) -> None: classic = bui.app.classic assert classic is not None + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) @@ -852,7 +861,8 @@ class GamepadSettingsWindow(bui.Window): assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - ControlsSettingsWindow(transition='in_left').get_root_widget() + ControlsSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/gamepadselect.py b/src/assets/ba_data/python/bauiv1lib/settings/gamepadselect.py index b576d422..d0dba0ac 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/gamepadselect.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/gamepadselect.py @@ -33,7 +33,8 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None: assert isinstance(device, bs.InputDevice) if device.allows_configuring: bui.app.ui_v1.set_main_menu_window( - gamepad.GamepadSettingsWindow(device).get_root_widget() + gamepad.GamepadSettingsWindow(device).get_root_widget(), + from_window=None, ) else: width = 700 @@ -51,7 +52,7 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None: size=(width, height), transition='in_right', ) - bui.app.ui_v1.set_main_menu_window(dlg) + bui.app.ui_v1.set_main_menu_window(dlg, from_window=None) if device.allows_configuring_in_system_settings: msg = bui.Lstr( @@ -81,12 +82,17 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None: def _ok() -> None: from bauiv1lib.settings import controls + # no-op if our underlying widget is dead or on its way out. + if not dlg or dlg.transitioning_out: + return + bui.containerwidget(edit=dlg, transition='out_right') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( controls.ControlsSettingsWindow( transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=dlg, ) bui.buttonwidget( @@ -191,11 +197,16 @@ class GamepadSelectWindow(bui.Window): def _back(self) -> None: from bauiv1lib.settings import controls + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bs.release_gamepad_input() bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( controls.ControlsSettingsWindow( transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/graphics.py b/src/assets/ba_data/python/bauiv1lib/settings/graphics.py index 7e20a537..f441826c 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/graphics.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/graphics.py @@ -436,6 +436,10 @@ class GraphicsSettingsWindow(bui.Window): def _back(self) -> None: from bauiv1lib.settings import allsettings + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + # Applying max-fps takes a few moments. Apply if it hasn't been # yet. self._apply_max_fps() @@ -447,7 +451,8 @@ class GraphicsSettingsWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( allsettings.AllSettingsWindow( transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _set_quality(self, quality: str) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/settings/keyboard.py b/src/assets/ba_data/python/bauiv1lib/settings/keyboard.py index 60aab600..1e564378 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/keyboard.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/keyboard.py @@ -271,15 +271,24 @@ class ConfigKeyboardWindow(bui.Window): def _cancel(self) -> None: from bauiv1lib.settings.controls import ControlsSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - ControlsSettingsWindow(transition='in_left').get_root_widget() + ControlsSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _save(self) -> None: from bauiv1lib.settings.controls import ControlsSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + assert bui.app.classic is not None bui.containerwidget(edit=self._root_widget, transition='out_right') bui.getsound('gunCocking').play() @@ -314,7 +323,8 @@ class ConfigKeyboardWindow(bui.Window): ) bui.app.config.apply_and_commit() bui.app.ui_v1.set_main_menu_window( - ControlsSettingsWindow(transition='in_left').get_root_widget() + ControlsSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/nettesting.py b/src/assets/ba_data/python/bauiv1lib/settings/nettesting.py index dd295108..e4e6e996 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/nettesting.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/nettesting.py @@ -135,8 +135,14 @@ class NetTestingWindow(bui.Window): def _show_val_testing(self) -> None: assert bui.app.classic is not None + + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.app.ui_v1.set_main_menu_window( - NetValTestingWindow().get_root_widget() + NetValTestingWindow().get_root_widget(), + from_window=self._root_widget, ) bui.containerwidget(edit=self._root_widget, transition='out_left') @@ -144,9 +150,14 @@ class NetTestingWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.advanced import AdvancedSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget() + AdvancedSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) bui.containerwidget(edit=self._root_widget, transition='out_right') diff --git a/src/assets/ba_data/python/bauiv1lib/settings/plugins.py b/src/assets/ba_data/python/bauiv1lib/settings/plugins.py index 5f22847b..c2327bbe 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/plugins.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/plugins.py @@ -212,11 +212,16 @@ class PluginWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.pluginsettings import PluginSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - PluginSettingsWindow(transition='in_right').get_root_widget() + PluginSettingsWindow(transition='in_right').get_root_widget(), + from_window=self._root_widget, ) def _show_category_options(self) -> None: @@ -412,11 +417,16 @@ class PluginWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.advanced import AdvancedSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - AdvancedSettingsWindow(transition='in_left').get_root_widget() + AdvancedSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/pluginsettings.py b/src/assets/ba_data/python/bauiv1lib/settings/pluginsettings.py index 03e9e9e8..1474f5cb 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/pluginsettings.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/pluginsettings.py @@ -161,10 +161,15 @@ class PluginSettingsWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.plugins import PluginWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - PluginWindow(transition='in_left').get_root_widget() + PluginWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/remoteapp.py b/src/assets/ba_data/python/bauiv1lib/settings/remoteapp.py index 03b1c611..3542f992 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/remoteapp.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/remoteapp.py @@ -138,10 +138,15 @@ class RemoteAppSettingsWindow(bui.Window): def _back(self) -> None: from bauiv1lib.settings import controls + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( controls.ControlsSettingsWindow( transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/testing.py b/src/assets/ba_data/python/bauiv1lib/settings/testing.py index c4bcd58a..30f11e28 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/testing.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/testing.py @@ -217,6 +217,10 @@ class TestingWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings.advanced import AdvancedSettingsWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_right') backwin = ( self._back_call() @@ -224,4 +228,6 @@ class TestingWindow(bui.Window): else AdvancedSettingsWindow(transition='in_left') ) assert bui.app.classic is not None - bui.app.ui_v1.set_main_menu_window(backwin.get_root_widget()) + bui.app.ui_v1.set_main_menu_window( + backwin.get_root_widget(), from_window=self._root_widget + ) diff --git a/src/assets/ba_data/python/bauiv1lib/settings/touchscreen.py b/src/assets/ba_data/python/bauiv1lib/settings/touchscreen.py index 61041bbd..d77a16a2 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/touchscreen.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/touchscreen.py @@ -276,11 +276,16 @@ class TouchscreenSettingsWindow(bui.Window): def _back(self) -> None: from bauiv1lib.settings import controls + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_right') assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( controls.ControlsSettingsWindow( transition='in_left' - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) bs.set_touchscreen_editing(False) diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py index 021942e6..a3b561b4 100644 --- a/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py @@ -394,13 +394,18 @@ class SoundtrackBrowserWindow(bui.Window): # pylint: disable=cyclic-import from bauiv1lib.settings import audio + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - audio.AudioSettingsWindow(transition='in_left').get_root_widget() + audio.AudioSettingsWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _edit_soundtrack_with_sound(self) -> None: @@ -421,6 +426,10 @@ class SoundtrackBrowserWindow(bui.Window): from bauiv1lib.purchase import PurchaseWindow from bauiv1lib.soundtrack.edit import SoundtrackEditWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + if ( bui.app.classic is not None and not bui.app.classic.accounts.have_pro_options() @@ -443,7 +452,8 @@ class SoundtrackBrowserWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( SoundtrackEditWindow( existing_soundtrack=self._selected_soundtrack - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr: diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py index 0e1088b4..8c3887eb 100644 --- a/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py +++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py @@ -351,7 +351,8 @@ class SoundtrackEditWindow(bui.Window): soundtrack[musictype] = entry bui.app.ui_v1.set_main_menu_window( - cls(state, transition='in_left').get_root_widget() + cls(state, transition='in_left').get_root_widget(), + from_window=False, # Disable check here. ) def _get_entry( @@ -359,6 +360,11 @@ class SoundtrackEditWindow(bui.Window): ) -> None: assert bui.app.classic is not None music = bui.app.classic.music + + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + if selection_target_name != '': selection_target_name = "'" + selection_target_name + "'" state = { @@ -375,7 +381,8 @@ class SoundtrackEditWindow(bui.Window): entry, selection_target_name, ) - .get_root_widget() + .get_root_widget(), + from_window=self._root_widget, ) def _test(self, song_type: bs.MusicType) -> None: @@ -422,6 +429,10 @@ class SoundtrackEditWindow(bui.Window): def _cancel(self) -> None: from bauiv1lib.soundtrack import browser as stb + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + assert bui.app.classic is not None music = bui.app.classic.music @@ -429,12 +440,17 @@ class SoundtrackEditWindow(bui.Window): music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR) bui.containerwidget(edit=self._root_widget, transition='out_right') bui.app.ui_v1.set_main_menu_window( - stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget() + stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _do_it(self) -> None: from bauiv1lib.soundtrack import browser as stb + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + assert bui.app.classic is not None music = bui.app.classic.music cfg = bui.app.config @@ -483,7 +499,8 @@ class SoundtrackEditWindow(bui.Window): ) bui.app.ui_v1.set_main_menu_window( - stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget() + stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) def _do_it_with_sound(self) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py index 2adaa721..583855a8 100644 --- a/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py +++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py @@ -166,6 +166,10 @@ class SoundtrackEntryTypeSelectWindow(bui.Window): MacMusicAppPlaylistSelectWindow, ) + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_left') current_playlist_entry: str | None @@ -181,7 +185,8 @@ class SoundtrackEntryTypeSelectWindow(bui.Window): bui.app.ui_v1.set_main_menu_window( MacMusicAppPlaylistSelectWindow( self._callback, current_playlist_entry, self._current_entry - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _on_music_file_press(self) -> None: @@ -189,6 +194,10 @@ class SoundtrackEntryTypeSelectWindow(bui.Window): from baclassic.osmusic import OSMusicPlayer from bauiv1lib.fileselector import FileSelectorWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_left') base_path = android_get_external_files_dir() assert bui.app.classic is not None @@ -201,13 +210,18 @@ class SoundtrackEntryTypeSelectWindow(bui.Window): OSMusicPlayer.get_valid_music_file_extensions() ), allow_folders=False, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _on_music_folder_press(self) -> None: from bauiv1lib.fileselector import FileSelectorWindow from babase import android_get_external_files_dir + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + bui.containerwidget(edit=self._root_widget, transition='out_left') base_path = android_get_external_files_dir() assert bui.app.classic is not None @@ -218,7 +232,8 @@ class SoundtrackEntryTypeSelectWindow(bui.Window): show_base_path=False, valid_file_extensions=[], allow_folders=True, - ).get_root_widget() + ).get_root_widget(), + from_window=self._root_widget, ) def _music_file_selector_cb(self, result: str | None) -> None: diff --git a/src/assets/ba_data/python/bauiv1lib/store/browser.py b/src/assets/ba_data/python/bauiv1lib/store/browser.py index 03af613a..201643f3 100644 --- a/src/assets/ba_data/python/bauiv1lib/store/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/store/browser.py @@ -1329,6 +1329,10 @@ class StoreBrowserWindow(bui.Window): from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.getcurrency import GetCurrencyWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + plus = bui.app.plus assert plus is not None @@ -1343,13 +1347,19 @@ class StoreBrowserWindow(bui.Window): ).get_root_widget() if not self._modal: assert bui.app.classic is not None - bui.app.ui_v1.set_main_menu_window(window) + bui.app.ui_v1.set_main_menu_window( + window, from_window=self._root_widget + ) def _back(self) -> None: # pylint: disable=cyclic-import from bauiv1lib.coop.browser import CoopBrowserWindow from bauiv1lib.mainmenu import MainMenuWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out @@ -1358,11 +1368,13 @@ class StoreBrowserWindow(bui.Window): assert bui.app.classic is not None if self._back_location == 'CoopBrowserWindow': bui.app.ui_v1.set_main_menu_window( - CoopBrowserWindow(transition='in_left').get_root_widget() + CoopBrowserWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) else: bui.app.ui_v1.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget() + MainMenuWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) if self._on_close_call is not None: self._on_close_call() diff --git a/src/assets/ba_data/python/bauiv1lib/watch.py b/src/assets/ba_data/python/bauiv1lib/watch.py index 1bdd4fb3..c2e50e4c 100644 --- a/src/assets/ba_data/python/bauiv1lib/watch.py +++ b/src/assets/ba_data/python/bauiv1lib/watch.py @@ -663,11 +663,16 @@ class WatchWindow(bui.Window): def _back(self) -> None: from bauiv1lib.mainmenu import MainMenuWindow + # no-op if our underlying widget is dead or on its way out. + if not self._root_widget or self._root_widget.transitioning_out: + return + self._save_state() bui.containerwidget( edit=self._root_widget, transition=self._transition_out ) assert bui.app.classic is not None bui.app.ui_v1.set_main_menu_window( - MainMenuWindow(transition='in_left').get_root_widget() + MainMenuWindow(transition='in_left').get_root_widget(), + from_window=self._root_widget, ) diff --git a/src/ballistica/base/graphics/graphics.cc b/src/ballistica/base/graphics/graphics.cc index c5d920d3..f6802563 100644 --- a/src/ballistica/base/graphics/graphics.cc +++ b/src/ballistica/base/graphics/graphics.cc @@ -615,7 +615,7 @@ void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) { Log(LogLevel::kWarning, "2 fades overlapping; running first fade-end-call early."); } - fade_end_call_->ScheduleOnce(); + fade_end_call_->Schedule(); fade_end_call_.Clear(); } set_fade_start_on_next_draw_ = true; @@ -1021,7 +1021,7 @@ void Graphics::DrawFades(FrameDef* frame_def) { } else { fade_ = 0; if (!was_done && fade_end_call_.Exists()) { - fade_end_call_->ScheduleOnce(); + fade_end_call_->Schedule(); fade_end_call_.Clear(); } } diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index 8815316c..44055fd7 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -279,7 +279,7 @@ static auto PyPushCall(PyObject* self, PyObject* args, PyObject* keywds) if (!g_base->InLogicThread()) { throw Exception("You must use from_other_thread mode."); } - Object::New(call_obj)->ScheduleOnce(); + Object::New(call_obj)->Schedule(); } Py_RETURN_NONE; BA_PYTHON_CATCH; diff --git a/src/ballistica/base/python/support/python_context_call.cc b/src/ballistica/base/python/support/python_context_call.cc index 4be03012..a37ddffb 100644 --- a/src/ballistica/base/python/support/python_context_call.cc +++ b/src/ballistica/base/python/support/python_context_call.cc @@ -125,83 +125,45 @@ void PythonContextCall::Schedule() { Object::Ref ref(this); assert(base::g_base); - schedule_count_++; base::g_base->logic->event_loop()->PushCall([ref] { assert(ref.Exists()); - ref->schedule_count_--; - assert(ref->schedule_count_ >= 0); ref->Run(); }); } -void PythonContextCall::ScheduleOnce() { - if (schedule_count_ > 0) { - return; - } - Schedule(); -} void PythonContextCall::Schedule(const PythonRef& args) { // Since we're mucking with Object::Refs, need to limit to logic thread. BA_PRECONDITION(g_base->InLogicThread()); Object::Ref ref(this); assert(base::g_base); - schedule_count_++; base::g_base->logic->event_loop()->PushCall([ref, args] { assert(ref.Exists()); - ref->schedule_count_--; - assert(ref->schedule_count_ >= 0); ref->Run(args); }); } -void PythonContextCall::ScheduleOnce(const PythonRef& args) { - if (schedule_count_ > 0) { - return; - } - Schedule(args); -} void PythonContextCall::ScheduleWeak() { // Since we're mucking with Object::WeakRefs, need to limit to logic thread. BA_PRECONDITION(g_base->InLogicThread()); Object::WeakRef ref(this); assert(base::g_base); - schedule_count_++; base::g_base->logic->event_loop()->PushCall([ref] { if (auto* call = ref.Get()) { - call->schedule_count_--; - assert(call->schedule_count_ >= 0); call->Run(); } }); } -void PythonContextCall::ScheduleWeakOnce() { - if (schedule_count_ > 0) { - return; - } - ScheduleWeak(); -} - void PythonContextCall::ScheduleWeak(const PythonRef& args) { // Since we're mucking with Object::WeakRefs, need to limit to logic thread. BA_PRECONDITION(g_base->InLogicThread()); Object::WeakRef ref(this); assert(base::g_base); - schedule_count_++; base::g_base->logic->event_loop()->PushCall([ref, args] { if (auto* call = ref.Get()) { - call->schedule_count_--; - assert(call->schedule_count_ >= 0); call->Run(args); } }); } -void PythonContextCall::ScheduleWeakOnce(const PythonRef& args) { - if (schedule_count_ > 0) { - return; - } - ScheduleWeak(args); -} - } // namespace ballistica::base diff --git a/src/ballistica/base/python/support/python_context_call.h b/src/ballistica/base/python/support/python_context_call.h index 1c99d3bc..1c7d5d01 100644 --- a/src/ballistica/base/python/support/python_context_call.h +++ b/src/ballistica/base/python/support/python_context_call.h @@ -42,56 +42,26 @@ class PythonContextCall : public Object { /// context_ref-call is guaranteed to exist until run. void Schedule(); - /// Schedule only if this instance is not already scheduled. Generally a - /// good idea unless you know you need multiple runs scheduled. Avoids - /// problems such as UIs expecting to be activated only once getting - /// activated twice due to two simultenous key presses. - void ScheduleOnce(); - /// Run in an upcoming cycle of the logic thread with provided args. Must /// be called from the logic thread. This form creates a strong-reference /// so the context_ref-call is guaranteed to exist until run. void Schedule(const PythonRef& args); - /// Schedule only if this instance is not already scheduled. Generally a - /// good idea unless you know you need multiple runs scheduled. Avoids - /// problems such as UIs expecting to be activated only once getting - /// activated twice due to two simultenous key presses. - void ScheduleOnce(const PythonRef& args); - /// Run in an upcoming cycle of the logic thread. Must be called from the /// logic thread. This form creates a weak-reference and is a no-op if the /// context_ref-call is destroyed before its scheduled run. void ScheduleWeak(); - /// Schedule weakly only if this instance is not already scheduled. - /// Generally a good idea unless you know you need multiple runs - /// scheduled. Avoids problems such as UIs expecting to be activated only - /// once getting activated twice due to two simultenous key presses. - void ScheduleWeakOnce(); - /// Run in an upcoming cycle of the logic thread with provided args. Must /// be called from the logic thread. This form creates a weak-reference /// and is a no-op if the context_ref-call is destroyed before its /// scheduled run. void ScheduleWeak(const PythonRef& args); - /// Schedule weakly only if this instance is not already scheduled. - /// Generally a good idea unless you know you need multiple runs - /// scheduled. Avoids problems such as UIs expecting to be activated only - /// once getting activated twice due to two simultenous key presses. - void ScheduleWeakOnce(const PythonRef& args); - - auto IsScheduled() const { - assert(g_base->InLogicThread()); - return schedule_count_ > 0; - } - private: void GetTrace(); // we try to grab basic trace info int line_{}; - int schedule_count_{}; bool dead_{}; std::string file_loc_; PythonRef object_; diff --git a/src/ballistica/scene_v1/python/class/python_class_session_player.cc b/src/ballistica/scene_v1/python/class/python_class_session_player.cc index 76b7c1c8..5431aff9 100644 --- a/src/ballistica/scene_v1/python/class/python_class_session_player.cc +++ b/src/ballistica/scene_v1/python/class/python_class_session_player.cc @@ -208,7 +208,7 @@ auto PythonClassSessionPlayer::tp_getattro(PythonClassSessionPlayer* self, PyObject* attr) -> PyObject* { BA_PYTHON_TRY; - assert(g_base->InLogicThread()); + BA_PRECONDITION(g_base->InLogicThread()); // Assuming this will always be a str? assert(PyUnicode_Check(attr)); @@ -327,6 +327,9 @@ auto PythonClassSessionPlayer::tp_setattro(PythonClassSessionPlayer* self, PyObject* attr, PyObject* val) -> int { BA_PYTHON_TRY; + + BA_PRECONDITION(g_base->InLogicThread()); + // Assuming this will always be a str? assert(PyUnicode_Check(attr)); const char* s = PyUnicode_AsUTF8(attr); diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 38e7096c..bb2af3c5 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21636; +const int kEngineBuildNumber = 21639; const char* kEngineVersion = "1.7.30"; const int kEngineApiVersion = 8; diff --git a/src/ballistica/ui_v1/python/class/python_class_widget.cc b/src/ballistica/ui_v1/python/class/python_class_widget.cc index 7a31bb13..57853ee0 100644 --- a/src/ballistica/ui_v1/python/class/python_class_widget.cc +++ b/src/ballistica/ui_v1/python/class/python_class_widget.cc @@ -17,6 +17,12 @@ auto PythonClassWidget::nb_bool(PythonClassWidget* self) -> int { PyNumberMethods PythonClassWidget::as_number_; +// Attrs we expose through our custom getattr/setattr. +#define ATTR_TRANSITIONING_OUT "transitioning_out" + +// The set we expose via dir(). +static const char* extra_dir_attrs[] = {ATTR_TRANSITIONING_OUT, nullptr}; + auto PythonClassWidget::type_name() -> const char* { return "Widget"; } void PythonClassWidget::SetupType(PyTypeObject* cls) { @@ -24,6 +30,9 @@ void PythonClassWidget::SetupType(PyTypeObject* cls) { // Fully qualified type path we will be exposed as: cls->tp_name = "bauiv1.Widget"; cls->tp_basicsize = sizeof(PythonClassWidget); + + // clang-format off + cls->tp_doc = "Internal type for low level UI elements; buttons, windows, etc.\n" "\n" @@ -31,11 +40,22 @@ void PythonClassWidget::SetupType(PyTypeObject* cls) { "\n" "This class represents a weak reference to a widget object\n" "in the internal C++ layer. Currently, functions such as\n" - "babase.buttonwidget() must be used to instantiate or edit these."; + "babase.buttonwidget() must be used to instantiate or edit these.\n" + "Attributes:\n" + " " ATTR_TRANSITIONING_OUT " (bool):\n" + " Whether this widget is in the process of dying (read only).\n" + "\n" + " It can be useful to check this on a window's root widget to\n" + " prevent multiple window actions from firing simultaneously,\n" + " potentially leaving the UI in a broken state.\n"; + + // clang-format on + cls->tp_new = tp_new; cls->tp_dealloc = (destructor)tp_dealloc; cls->tp_repr = (reprfunc)tp_repr; cls->tp_methods = tp_methods; + cls->tp_getattro = (getattrofunc)tp_getattro; // we provide number methods only for bool functionality memset(&as_number_, 0, sizeof(as_number_)); @@ -44,7 +64,7 @@ void PythonClassWidget::SetupType(PyTypeObject* cls) { } auto PythonClassWidget::Create(Widget* widget) -> PyObject* { - // Make sure we only have one python ref per widget. + // Make sure we only have one Python ref per Widget. if (widget) { assert(!widget->has_py_ref()); } @@ -62,10 +82,56 @@ auto PythonClassWidget::Create(Widget* widget) -> PyObject* { auto PythonClassWidget::GetWidget() const -> Widget* { Widget* w = widget_->Get(); - if (!w) throw Exception("Invalid widget"); + if (!w) { + throw Exception("Invalid Widget", PyExcType::kReference); + } return w; } +auto PythonClassWidget::tp_getattro(PythonClassWidget* self, PyObject* attr) + -> PyObject* { + BA_PYTHON_TRY; + + BA_PRECONDITION(g_base->InLogicThread()); + + // Assuming this will always be a str? + assert(PyUnicode_Check(attr)); + + const char* s = PyUnicode_AsUTF8(attr); + if (!strcmp(s, ATTR_TRANSITIONING_OUT)) { + Widget* w = self->widget_->Get(); + if (!w) { + throw Exception("Invalid Widget", PyExcType::kReference); + } + if (w->IsTransitioningOut()) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; + } + + // Fall back to generic behavior. + PyObject* val; + val = PyObject_GenericGetAttr(reinterpret_cast(self), attr); + return val; + BA_PYTHON_CATCH; +} + +auto PythonClassWidget::tp_setattro(PythonClassWidget* self, PyObject* attr, + PyObject* val) -> int { + BA_PYTHON_TRY; + + BA_PRECONDITION(g_base->InLogicThread()); + + // Assuming this will always be a str? + assert(PyUnicode_Check(attr)); + const char* s = PyUnicode_AsUTF8(attr); + + throw Exception("Attr '" + std::string(PyUnicode_AsUTF8(attr)) + + "' is not settable on SessionPlayer objects.", + PyExcType::kAttribute); + BA_PYTHON_INT_CATCH; +} + auto PythonClassWidget::tp_repr(PythonClassWidget* self) -> PyObject* { BA_PYTHON_TRY; Widget* w = self->widget_->Get(); @@ -96,8 +162,8 @@ auto PythonClassWidget::tp_new(PyTypeObject* type, PyObject* args, void PythonClassWidget::tp_dealloc(PythonClassWidget* self) { BA_PYTHON_TRY; - // these have to be destructed in the logic thread - send them along to it if - // need be + // these have to be destructed in the logic thread - send them along to it + // if need be if (!g_base->InLogicThread()) { Object::WeakRef* w = self->widget_; g_base->logic->event_loop()->PushCall([w] { delete w; }); diff --git a/src/ballistica/ui_v1/python/class/python_class_widget.h b/src/ballistica/ui_v1/python/class/python_class_widget.h index 012612da..b4e98409 100644 --- a/src/ballistica/ui_v1/python/class/python_class_widget.h +++ b/src/ballistica/ui_v1/python/class/python_class_widget.h @@ -26,6 +26,9 @@ class PythonClassWidget : public PythonClass { static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds) -> PyObject*; static void tp_dealloc(PythonClassWidget* self); + static auto tp_getattro(PythonClassWidget* self, PyObject* attr) -> PyObject*; + static auto tp_setattro(PythonClassWidget* self, PyObject* attr, + PyObject* val) -> int; static auto Exists(PythonClassWidget* self) -> PyObject*; static auto GetWidgetType(PythonClassWidget* self) -> PyObject*; static auto Activate(PythonClassWidget* self) -> PyObject*; diff --git a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc index ce6f13dd..b071343e 100644 --- a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc +++ b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc @@ -1219,17 +1219,17 @@ static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds) if (transition_obj != Py_None) { std::string t = Python::GetPyString(transition_obj); if (t == "in_left") - widget->SetTransition(ContainerWidget::TRANSITION_IN_LEFT); + widget->SetTransition(ContainerWidget::TransitionType::kInLeft); else if (t == "in_right") - widget->SetTransition(ContainerWidget::TRANSITION_IN_RIGHT); + widget->SetTransition(ContainerWidget::TransitionType::kInRight); else if (t == "out_left") - widget->SetTransition(ContainerWidget::TRANSITION_OUT_LEFT); + widget->SetTransition(ContainerWidget::TransitionType::kOutLeft); else if (t == "out_right") - widget->SetTransition(ContainerWidget::TRANSITION_OUT_RIGHT); + widget->SetTransition(ContainerWidget::TransitionType::kOutRight); else if (t == "in_scale") - widget->SetTransition(ContainerWidget::TRANSITION_IN_SCALE); + widget->SetTransition(ContainerWidget::TransitionType::kInScale); else if (t == "out_scale") - widget->SetTransition(ContainerWidget::TRANSITION_OUT_SCALE); + widget->SetTransition(ContainerWidget::TransitionType::kOutScale); } if (cancel_button_obj != Py_None) { diff --git a/src/ballistica/ui_v1/python/ui_v1_python.cc b/src/ballistica/ui_v1/python/ui_v1_python.cc index f4157594..f12321b0 100644 --- a/src/ballistica/ui_v1/python/ui_v1_python.cc +++ b/src/ballistica/ui_v1/python/ui_v1_python.cc @@ -115,7 +115,7 @@ void UIV1Python::InvokeStringEditor(PyObject* string_edit_adapter_instance) { PythonRef::kSteal); Object::New( objs().Get(ObjID::kOnScreenKeyboardClass)) - ->ScheduleOnce(args); + ->Schedule(args); } void UIV1Python::LaunchStringEditOld(TextWidget* w) { @@ -131,7 +131,7 @@ void UIV1Python::LaunchStringEditOld(TextWidget* w) { PythonRef::kSteal); Object::New( objs().Get(ObjID::kOnScreenKeyboardClass)) - ->ScheduleOnce(args); + ->Schedule(args); } void UIV1Python::InvokeQuitWindow(QuitType quit_type) { diff --git a/src/ballistica/ui_v1/python/ui_v1_python.h b/src/ballistica/ui_v1/python/ui_v1_python.h index 8e10e9a1..734cd476 100644 --- a/src/ballistica/ui_v1/python/ui_v1_python.h +++ b/src/ballistica/ui_v1/python/ui_v1_python.h @@ -39,6 +39,7 @@ class UIV1Python { kQuitWindowCall, kDeviceMenuPressCall, kShowURLWindowCall, + kDoubleTransitionOutWarningCall, kTextWidgetStringEditAdapterClass, kLast // Sentinel; must be at end. }; diff --git a/src/ballistica/ui_v1/widget/button_widget.cc b/src/ballistica/ui_v1/widget/button_widget.cc index 7b824db1..29c6db6a 100644 --- a/src/ballistica/ui_v1/widget/button_widget.cc +++ b/src/ballistica/ui_v1/widget/button_widget.cc @@ -562,7 +562,7 @@ void ButtonWidget::DoActivate(bool is_repeat) { if (auto* call = on_activate_call_.Get()) { // Call this in the next cycle (don't want to risk mucking with UI from // within a UI loop.) - call->ScheduleWeakOnce(); + call->ScheduleWeak(); return; } } diff --git a/src/ballistica/ui_v1/widget/check_box_widget.cc b/src/ballistica/ui_v1/widget/check_box_widget.cc index f252870a..a6e47b3e 100644 --- a/src/ballistica/ui_v1/widget/check_box_widget.cc +++ b/src/ballistica/ui_v1/widget/check_box_widget.cc @@ -247,7 +247,7 @@ void CheckBoxWidget::Activate() { // Call this in the next cycle (don't want to risk mucking with UI from // within a UI loop) - call->ScheduleWeakOnce(args); + call->ScheduleWeak(args); } } @@ -271,12 +271,13 @@ auto CheckBoxWidget::HandleMessage(const base::WidgetMessage& m) -> bool { float x = m.fval1; float y = m.fval2; bool claimed = (m.fval3 > 0.0f); - if (claimed) + if (claimed) { mouse_over_ = false; - else + } else { mouse_over_ = ((x >= (-left_overlap)) && (x < (width_ + right_overlap)) && (y >= (-bottom_overlap)) && (y < (height_ + top_overlap))); + } return mouse_over_; } case base::WidgetMessage::Type::kMouseDown: { diff --git a/src/ballistica/ui_v1/widget/container_widget.cc b/src/ballistica/ui_v1/widget/container_widget.cc index e47bbb17..4f205dbd 100644 --- a/src/ballistica/ui_v1/widget/container_widget.cc +++ b/src/ballistica/ui_v1/widget/container_widget.cc @@ -11,6 +11,7 @@ #include "ballistica/shared/generic/utils.h" #include "ballistica/shared/math/random.h" #include "ballistica/shared/python/python.h" +#include "ballistica/ui_v1/python/ui_v1_python.h" #include "ballistica/ui_v1/widget/button_widget.h" #include "ballistica/ui_v1/widget/root_widget.h" #include "ballistica/ui_v1/widget/stack_widget.h" @@ -347,7 +348,7 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool { // Call this in the next cycle (don't wanna risk mucking with UI from // within a UI loop). - call->ScheduleWeakOnce(); + call->ScheduleWeak(); } else { OnCancelCustom(); } @@ -631,7 +632,7 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool { if (!claimed && on_outside_click_call_.Exists()) { // Call this in the next cycle (don't wanna risk mucking with UI from // within a UI loop). - on_outside_click_call_->ScheduleWeakOnce(); + on_outside_click_call_->ScheduleWeak(); } // Always claim if they want. @@ -793,7 +794,7 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { bg_dirty_ = true; if (!draw_transparent) { - if (transition_type_ == TRANSITION_IN_SCALE) { + if (transition_type_ == TransitionType::kInScale) { if (net_time - dynamics_update_time_millisecs_ > 1000) dynamics_update_time_millisecs_ = net_time - 1000; while (net_time - dynamics_update_time_millisecs_ > 5) { @@ -808,7 +809,7 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { transitioning_ = false; } } - } else if (transition_type_ == TRANSITION_OUT_SCALE) { + } else if (transition_type_ == TransitionType::kOutScale) { if (net_time - dynamics_update_time_millisecs_ > 1000) dynamics_update_time_millisecs_ = net_time - 1000; while (net_time - dynamics_update_time_millisecs_ > 5) { @@ -908,8 +909,8 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) { // If we're scaling in or out, update our transition offset // (so we can zoom from a point somewhere else on screen). - if (transition_type_ == TRANSITION_IN_SCALE - || transition_type_ == TRANSITION_OUT_SCALE) { + if (transition_type_ == TransitionType::kInScale + || transition_type_ == TransitionType::kOutScale) { // Add a fudge factor since our scale point isn't exactly in our center. // :-( float xdiff = scale_origin_stack_offset_x_ - stack_offset_x() @@ -1071,7 +1072,7 @@ void ContainerWidget::Activate() { if (auto* call = on_activate_call_.Get()) { // Call this in the next cycle (don't wanna risk mucking with UI from within // a UI loop). - call->ScheduleWeakOnce(); + call->ScheduleWeak(); } } @@ -1156,8 +1157,23 @@ void ContainerWidget::SetStartButton(ButtonWidget* button) { button->set_icon_type(ButtonWidget::IconType::kStart); } +static auto _IsTransitionOut(ContainerWidget::TransitionType type) { + switch (type) { + case ContainerWidget::TransitionType::kUnset: + case ContainerWidget::TransitionType::kInLeft: + case ContainerWidget::TransitionType::kInRight: + case ContainerWidget::TransitionType::kInScale: + return false; + case ContainerWidget::TransitionType::kOutLeft: + case ContainerWidget::TransitionType::kOutRight: + case ContainerWidget::TransitionType::kOutScale: + return true; + } +} + void ContainerWidget::SetTransition(TransitionType t) { BA_DEBUG_UI_READ_LOCK; + assert(g_base->InLogicThread()); bg_dirty_ = glow_dirty_ = true; ContainerWidget* parent = parent_widget(); @@ -1167,17 +1183,26 @@ void ContainerWidget::SetTransition(TransitionType t) { parent->CheckLayout(); auto display_time_millisecs = static_cast(g_base->logic->display_time() * 1000.0); + + // Warn if setting out-transition twice. This likely means a window is + // switching to another window twice which can leave the UI broken. + if (_IsTransitionOut(transition_type_) && _IsTransitionOut(t)) { + g_ui_v1->python->objs() + .Get(UIV1Python::ObjID::kDoubleTransitionOutWarningCall) + .Call(); + } + transition_type_ = t; // Scale transitions are simpler. - if (t == TRANSITION_IN_SCALE) { + if (t == TransitionType::kInScale) { transition_start_time_ = display_time_millisecs; dynamics_update_time_millisecs_ = display_time_millisecs; transitioning_ = true; transitioning_out_ = false; transition_scale_ = 0.0f; d_transition_scale_ = 0.0f; - } else if (t == TRANSITION_OUT_SCALE) { + } else if (t == TransitionType::kOutScale) { transition_start_time_ = display_time_millisecs; dynamics_update_time_millisecs_ = display_time_millisecs; transitioning_ = true; @@ -1195,7 +1220,7 @@ void ContainerWidget::SetTransition(TransitionType t) { // In case we're mid-transition, this avoids hitches. float y_offs = 2.0f; - if (t == TRANSITION_IN_LEFT) { + if (t == TransitionType::kInLeft) { transition_start_time_ = display_time_millisecs; transition_start_offset_ = screen_min_x - width_ - 100; transition_offset_x_smoothed_ = transition_start_offset_; @@ -1204,7 +1229,7 @@ void ContainerWidget::SetTransition(TransitionType t) { transitioning_ = true; dynamics_update_time_millisecs_ = display_time_millisecs; transitioning_out_ = false; - } else if (t == TRANSITION_IN_RIGHT) { + } else if (t == TransitionType::kInRight) { transition_start_time_ = display_time_millisecs; transition_start_offset_ = screen_max_x + 100; transition_offset_x_smoothed_ = transition_start_offset_; @@ -1213,7 +1238,7 @@ void ContainerWidget::SetTransition(TransitionType t) { transitioning_ = true; dynamics_update_time_millisecs_ = display_time_millisecs; transitioning_out_ = false; - } else if (t == TRANSITION_OUT_LEFT) { + } else if (t == TransitionType::kOutLeft) { transition_start_time_ = display_time_millisecs; transition_start_offset_ = transition_offset_x_; transition_target_offset_ = -2.0f * (screen_max_x - screen_min_x); @@ -1223,7 +1248,7 @@ void ContainerWidget::SetTransition(TransitionType t) { dynamics_update_time_millisecs_ = display_time_millisecs; transitioning_out_ = true; ignore_input_ = true; - } else if (t == TRANSITION_OUT_RIGHT) { + } else if (t == TransitionType::kOutRight) { transition_start_time_ = display_time_millisecs; transition_start_offset_ = transition_offset_x_; transition_target_offset_ = 2.0f * (screen_max_x - screen_min_x); @@ -1279,8 +1304,9 @@ void ContainerWidget::DeleteWidget(Widget* w) { assert(found); - // Special case: if we're the overlay stack and we've deleted our last widget, - // try to reselect whatever was last selected before the overlay stack. + // Special case: if we're the overlay stack and we've deleted our last + // widget, try to reselect whatever was last selected before the overlay + // stack. if (is_overlay_window_stack_) { if (widgets_.empty()) { // Eww this logic should be in some sort of controller. @@ -1298,8 +1324,9 @@ void ContainerWidget::DeleteWidget(Widget* w) { if ((**i).IsSelectable()) { // A change on the main or overlay window stack changes the global // selection (unless its on the main window stack and there's already - // something on the overlay stack) in all other cases we just shift our - // direct selected child (which may not affect the global selection). + // something on the overlay stack) in all other cases we just shift + // our direct selected child (which may not affect the global + // selection). if (is_window_stack_ && (is_overlay_window_stack_ || !g_ui_v1->root_widget() @@ -1322,8 +1349,8 @@ void ContainerWidget::DeleteWidget(Widget* w) { } auto ContainerWidget::GetTopmostToolbarInfluencingWidget() -> Widget* { - // Look for the first window that is accepting input (filters out windows that - // are transitioning out) and also set to affect the toolbar state. + // Look for the first window that is accepting input (filters out windows + // that are transitioning out) and also set to affect the toolbar state. for (auto w = widgets_.rbegin(); w != widgets_.rend(); ++w) { if ((**w).IsAcceptingInput() && (**w).toolbar_visibility() != ToolbarVisibility::kInherit) { @@ -1438,9 +1465,8 @@ void ContainerWidget::SetSelected(bool s, SelectionCause cause) { } } } else { - // if we're being deselected and we have a selected child, tell them they're - // deselected - // if (selected_widget_) { + // if we're being deselected and we have a selected child, tell them + // they're deselected if (selected_widget_) { // } } } @@ -1583,8 +1609,8 @@ void ContainerWidget::SelectDownWidget() { selected_widget_->GetCenter(&our_x, &our_y); w = GetClosestDownWidget(our_x, our_y, selected_widget_); if (!w) { - // If we found no viable children and we're under the main window stack, - // see if we should pass focus to a toolbar widget. + // If we found no viable children and we're under the main window + // stack, see if we should pass focus to a toolbar widget. if (IsInMainStack()) { float x = our_x; float y = our_y; @@ -1712,7 +1738,8 @@ void ContainerWidget::SelectLeftWidget() { float our_x, our_y; selected_widget_->GetCenter(&our_x, &our_y); w = GetClosestLeftWidget(our_x, our_y, selected_widget_); - // When we find no viable targets for an autoselect widget we do nothing. + // When we find no viable targets for an autoselect widget we do + // nothing. if (!w) { return; } @@ -1843,8 +1870,8 @@ void ContainerWidget::SelectNextWidget() { return; } else if (selected_widget_ == nullptr) { // NOLINT(bugprone-branch-clone) - // We've got no selection and we've scanned the whole list to no avail, - // fail. + // We've got no selection and we've scanned the whole list to no + // avail, fail. PrintExitListInstructions(old_last_prev_next_time); return; } else if (selection_loops()) { @@ -1997,4 +2024,8 @@ void ContainerWidget::OnLanguageChange() { } } +auto ContainerWidget::IsTransitioningOut() const -> bool { + return transitioning_out_; +} + } // namespace ballistica::ui_v1 diff --git a/src/ballistica/ui_v1/widget/container_widget.h b/src/ballistica/ui_v1/widget/container_widget.h index 30946207..5f55bd7e 100644 --- a/src/ballistica/ui_v1/widget/container_widget.h +++ b/src/ballistica/ui_v1/widget/container_widget.h @@ -14,20 +14,21 @@ namespace ballistica::ui_v1 { // Base class for widgets that contain other widgets. class ContainerWidget : public Widget { public: - explicit ContainerWidget(float width = 0, float height = 0); + explicit ContainerWidget(float width = 0.0f, float height = 0.0f); ~ContainerWidget() override; void Draw(base::RenderPass* pass, bool transparent) override; auto HandleMessage(const base::WidgetMessage& m) -> bool override; - enum TransitionType { - TRANSITION_OUT_LEFT, - TRANSITION_OUT_RIGHT, - TRANSITION_IN_LEFT, - TRANSITION_IN_RIGHT, - TRANSITION_IN_SCALE, - TRANSITION_OUT_SCALE + enum class TransitionType { + kUnset, + kOutLeft, + kOutRight, + kInLeft, + kInRight, + kInScale, + kOutScale }; void SetTransition(TransitionType t); @@ -49,6 +50,7 @@ class ContainerWidget : public Widget { width_ = w; MarkForUpdate(); } + virtual void SetHeight(float h) { bg_dirty_ = glow_dirty_ = true; height_ = h; @@ -67,6 +69,7 @@ class ContainerWidget : public Widget { CheckLayout(); return width_; } + auto GetHeight() -> float override { CheckLayout(); return height_; @@ -76,8 +79,8 @@ class ContainerWidget : public Widget { auto HasKeySelectableChild() const -> bool; - void set_is_window_stack(bool a) { is_window_stack_ = a; } auto is_window_stack() const -> bool { return is_window_stack_; } + void set_is_window_stack(bool a) { is_window_stack_ = a; } auto GetChildCount() const -> int { assert(g_base->InLogicThread()); @@ -167,6 +170,8 @@ class ContainerWidget : public Widget { // if the topmost one is transitioning out, etc.) auto GetTopmostToolbarInfluencingWidget() -> Widget*; + auto IsTransitioningOut() const -> bool override; + protected: virtual void OnCancelCustom() {} void set_single_depth_root(bool s) { single_depth_root_ = s; } diff --git a/src/ballistica/ui_v1/widget/text_widget.cc b/src/ballistica/ui_v1/widget/text_widget.cc index 687c1689..67474f50 100644 --- a/src/ballistica/ui_v1/widget/text_widget.cc +++ b/src/ballistica/ui_v1/widget/text_widget.cc @@ -592,7 +592,7 @@ void TextWidget::Activate() { if (auto* call = on_activate_call_.Get()) { // Call this in the next cycle (don't wanna risk mucking with UI from // within a UI loop). - call->ScheduleWeakOnce(); + call->ScheduleWeak(); } // Bring up an editor if applicable. @@ -720,7 +720,7 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool { claimed = true; // Call this in the next cycle (don't wanna risk mucking with UI // from within a UI loop) - call->ScheduleWeakOnce(); + call->ScheduleWeak(); } } break; diff --git a/src/ballistica/ui_v1/widget/widget.cc b/src/ballistica/ui_v1/widget/widget.cc index 257d2198..c94910a7 100644 --- a/src/ballistica/ui_v1/widget/widget.cc +++ b/src/ballistica/ui_v1/widget/widget.cc @@ -88,7 +88,7 @@ void Widget::SetSelected(bool s, SelectionCause cause) { if (selected_ && on_select_call_.Exists()) { // Call this in the next cycle (don't wanna risk mucking // with UI from within a UI loop). - on_select_call_->ScheduleWeakOnce(); + on_select_call_->ScheduleWeak(); } } @@ -238,4 +238,6 @@ auto Widget::IsAcceptingInput() const -> bool { return true; } void Widget::Activate() {} +auto Widget::IsTransitioningOut() const -> bool { return false; } + } // namespace ballistica::ui_v1 diff --git a/src/ballistica/ui_v1/widget/widget.h b/src/ballistica/ui_v1/widget/widget.h index 56e7e18b..e6953c99 100644 --- a/src/ballistica/ui_v1/widget/widget.h +++ b/src/ballistica/ui_v1/widget/widget.h @@ -46,7 +46,8 @@ class Widget : public Object { // Whether the widget (or its children) is selectable in any way. virtual auto IsSelectable() -> bool; - // Whether the widget can be selected by default with direction/tab presses. + // Whether the widget can be selected by default with direction/tab + // presses. virtual auto IsSelectableViaKeys() -> bool; // Is the widget currently accepting input? @@ -83,8 +84,8 @@ class Widget : public Object { // If this widget is in a container, return it. auto parent_widget() const -> ContainerWidget* { return parent_widget_; } - // Return the container_widget containing this widget, or the owner-widget if - // there is no parent. + // Return the container_widget containing this widget, or the owner-widget + // if there is no parent. auto GetOwnerWidget() const -> Widget*; auto down_widget() const -> Widget* { return down_widget_.Get(); } @@ -116,25 +117,23 @@ class Widget : public Object { // redirecting them to transient per-window stuff). void set_neighbors_locked(bool locked) { neighbors_locked_ = locked; } - // Widgets normally draw with a local depth range of 0-1. - // It can be useful to limit drawing to a subsection of that region however - // (for manually resolving overlap issues with widgets at the same depth, - // etc). + // Widgets normally draw with a local depth range of 0-1. It can be useful + // to limit drawing to a subsection of that region however (for manually + // resolving overlap issues with widgets at the same depth, etc). void SetDepthRange(float minDepth, float maxDepth); auto depth_range_min() const -> float { return depth_range_min_; } auto depth_range_max() const -> float { return depth_range_max_; } - // For use by ContainerWidgets. - // (we probably should just this functionality to all widgets) + // For use by ContainerWidgets (we probably should just add this + // functionality to all widgets). void set_parent_widget(ContainerWidget* c) { parent_widget_ = c; } auto IsInMainStack() const -> bool; auto IsInOverlayStack() const -> bool; - // For use when embedding widgets inside others manually. - // This will allow proper selection states/etc to trickle down to the - // lowest-level child. + // For use when embedding widgets inside others manually. This will allow + // proper selection states/etc to trickle down to the lowest-level child. void set_owner_widget(Widget* o) { owner_widget_ = o; } virtual auto GetWidgetTypeName() -> std::string { return "widget"; } virtual auto HasChildren() const -> bool { return false; } @@ -167,22 +166,25 @@ class Widget : public Object { void ScreenPointToWidget(float* x, float* y) const; void WidgetPointToScreen(float* x, float* y) const; - // Draw-control parents are used to give one widget some basic visual control - // over others, allowing them to inherit things like draw-brightness and tilt - // shift (for cases such as images drawn over buttons). - // Ideally we'd probably want to extend the parent mechanism for this, but - // this works for now. + // Draw-control parents are used to give one widget some basic visual + // control over others, allowing them to inherit things like + // draw-brightness and tilt shift (for cases such as images drawn over + // buttons). Ideally we'd probably want to extend the parent mechanism for + // this, but this works for now. auto draw_control_parent() const -> Widget* { return draw_control_parent_.Get(); } void set_draw_control_parent(Widget* w) { draw_control_parent_ = w; } - // Can be used to ask link-parents how bright to draw. - // Note: make sure the value returned here does not get changed when draw() - // is run, since parts of draw-controlled children may query this before - // draw() and parts after. (and they need to line up visually) + // Can be used to ask link-parents how bright to draw. Note: make sure the + // value returned here does not get changed when draw() is run, since + // parts of draw-controlled children may query this before draw() and + // parts after. (and they need to line up visually) virtual auto GetDrawBrightness(millisecs_t current_time) const -> float; + /// Is this widget in the process of transitioning out before dying? + virtual auto IsTransitioningOut() const -> bool; + // Extra buffer added around widgets when they are centered-on. void set_show_buffer_top(float b) { show_buffer_top_ = b; } void set_show_buffer_bottom(float b) { show_buffer_bottom_ = b; } @@ -206,8 +208,8 @@ class Widget : public Object { virtual void OnLanguageChange() {} - // Primitive janktastic child culling for use by containers. - // (should really implement something more proper...) + // Primitive janktastic child culling for use by containers (should really + // implement something more proper). auto simple_culling_v() const -> float { return simple_culling_v_; } auto simple_culling_h() const -> float { return simple_culling_h_; } auto simple_culling_bottom() const -> float { return simple_culling_bottom_; } @@ -224,14 +226,17 @@ class Widget : public Object { private: auto GetPyWidget(bool new_ref) -> PyObject*; virtual void SetSelected(bool s, SelectionCause cause); + bool selected_{}; + bool visible_in_container_{true}; + bool neighbors_locked_{}; + bool auto_select_{}; + ToolbarVisibility toolbar_visibility_{ToolbarVisibility::kMenuMinimalNoBack}; float simple_culling_h_{-1.0f}; float simple_culling_v_{-1.0f}; float simple_culling_left_{}; float simple_culling_right_{}; float simple_culling_bottom_{}; float simple_culling_top_{}; - ToolbarVisibility toolbar_visibility_{ToolbarVisibility::kMenuMinimalNoBack}; - PyObject* py_ref_{}; float show_buffer_top_{20.0f}; float show_buffer_bottom_{20.0f}; float show_buffer_left_{20.0f}; @@ -241,12 +246,9 @@ class Widget : public Object { Object::WeakRef up_widget_; Object::WeakRef left_widget_; Object::WeakRef right_widget_; - bool neighbors_locked_{}; - bool auto_select_{}; ContainerWidget* parent_widget_{}; + PyObject* py_ref_{}; Widget* owner_widget_{}; - bool selected_{}; - bool visible_in_container_{true}; float tx_{}; float ty_{}; float stack_offset_x_{}; diff --git a/src/meta/bauiv1meta/pyembed/binding_ui_v1.py b/src/meta/bauiv1meta/pyembed/binding_ui_v1.py index 015d75ea..dc7f8c13 100644 --- a/src/meta/bauiv1meta/pyembed/binding_ui_v1.py +++ b/src/meta/bauiv1meta/pyembed/binding_ui_v1.py @@ -22,5 +22,6 @@ values = [ _hooks.quit_window, # kQuitWindowCall _hooks.device_menu_press, # kDeviceMenuPressCall _hooks.show_url_window, # kShowURLWindowCall + _hooks.double_transition_out_warning, # kDoubleTransitionOutWarningCall TextWidgetStringEditAdapter, # kTextWidgetStringEditAdapterClass ] From 2a5e9768dbd5d187d53a34bf1c2a956447bc43ba Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 1 Dec 2023 16:26:13 -0800 Subject: [PATCH 02/10] android polishing and bug fixes --- .efrocachemap | 88 ++++---- CHANGELOG.md | 2 +- src/assets/ba_data/python/babase/_app.py | 9 + src/assets/ba_data/python/baclassic/_ads.py | 48 ++-- src/assets/ba_data/python/baenv.py | 2 +- .../ba_data/python/bauiv1lib/specialoffer.py | 6 +- .../base/app_adapter/app_adapter.cc | 168 +------------- src/ballistica/base/app_adapter/app_adapter.h | 59 ++--- .../base/app_adapter/app_adapter_apple.h | 1 - .../base/app_adapter/app_adapter_sdl.cc | 21 +- src/ballistica/base/app_mode/app_mode.cc | 4 +- src/ballistica/base/app_mode/app_mode.h | 4 +- src/ballistica/base/audio/audio.cc | 4 +- src/ballistica/base/audio/audio.h | 4 +- src/ballistica/base/audio/audio_server.cc | 59 +++-- src/ballistica/base/audio/audio_server.h | 2 + src/ballistica/base/base.cc | 213 ++++++++++++++++++ src/ballistica/base/base.h | 53 ++++- src/ballistica/base/graphics/graphics.cc | 9 +- src/ballistica/base/graphics/graphics.h | 4 +- .../base/graphics/graphics_server.cc | 2 +- src/ballistica/base/input/input.cc | 4 +- src/ballistica/base/input/input.h | 4 +- src/ballistica/base/logic/logic.cc | 44 ++-- src/ballistica/base/logic/logic.h | 4 +- .../base/networking/network_reader.cc | 4 +- .../base/networking/network_reader.h | 4 +- src/ballistica/base/networking/networking.cc | 4 +- src/ballistica/base/networking/networking.h | 4 +- src/ballistica/base/platform/base_platform.cc | 4 +- src/ballistica/base/platform/base_platform.h | 4 +- .../base/platform/support/min_sdl_key_names.h | 2 +- src/ballistica/base/python/base_python.cc | 4 +- src/ballistica/base/python/base_python.h | 4 +- .../base/python/methods/python_methods_app.cc | 22 ++ .../python/methods/python_methods_misc.cc | 8 +- src/ballistica/base/support/plus_soft.h | 4 +- src/ballistica/base/ui/dev_console.cc | 6 +- src/ballistica/base/ui/ui.cc | 4 +- src/ballistica/base/ui/ui.h | 4 +- src/ballistica/core/platform/core_platform.cc | 6 +- src/ballistica/core/platform/core_platform.h | 73 +++--- src/ballistica/core/python/core_python.cc | 6 +- .../scene_v1/support/scene_v1_app_mode.cc | 4 +- .../scene_v1/support/scene_v1_app_mode.h | 4 +- src/ballistica/shared/ballistica.cc | 2 +- src/ballistica/shared/foundation/logging.cc | 2 +- .../python/methods/python_methods_ui_v1.cc | 6 +- src/ballistica/ui_v1/widget/text_widget.cc | 7 +- tools/efrotools/openalbuild.py | 99 ++++++-- 50 files changed, 657 insertions(+), 452 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index a21fb9a3..8b2edc16 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4060,50 +4060,50 @@ "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": "c2b80379179c8731be37581e52259377", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "0a2257e46a20ae6453d888515a00f1a8", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "a8bf3602e161931f96c41989d9d4e630", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "15e5d036605366840182cf3f86d247ae", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "4212f5014bcf30f5ceb6e9ed00c1b443", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a9ee1b8f07dc7466b4daf90a34991f3b", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "cbc9bf3f8ddce331912b6dbd8f1c6415", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "6b4458aa454391fa0070099ef4cac711", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "fae27ee9108877bd75aaa222f02f239d", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "37f2b4219ffe85aa1d28ab7df7fd4c44", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "3f8de90abf2069a0881f8d91f1ec78b2", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "62a6ee7583ef9d83f9bb1601fce6ddaa", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "f495a8547ca8b824f80006603537d1cf", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "672c351fca7843d85a2be7aba13faf1f", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "50f6d105ad4c2bb2df4f3d335b6d2cfa", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "44e1d633accc2410ca6d825e4c464f45", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5cce0ce595304313ac28c02e235f19d7", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "9433fc9da2f4b7255f2c7fa95868604a", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "18eb1871a6db9bbe17f5e9678d3d492a", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "cf51dafe0553d06a44349d0911c21d71", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "a27963487e346338e4c216bd4fbb9e2a", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "a27963487e346338e4c216bd4fbb9e2a", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "2663c888aec894656bd8c49932bd7729", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "5e57d12a3cfcfbc47b0293c3cb9fdca9", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "2663c888aec894656bd8c49932bd7729", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "5e57d12a3cfcfbc47b0293c3cb9fdca9", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "0f7dbe6fb3e28a51904aa822b509da0f", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "081b766945b52460a4f1afc01faa0652", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "0f7dbe6fb3e28a51904aa822b509da0f", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "081b766945b52460a4f1afc01faa0652", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "ad609c63f68417d5211bbfb23ce4affe", - "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "852fe46c736082611a831a618923c241", - "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "36fbda7829ed5c2862c34feb09b03402", - "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "852fe46c736082611a831a618923c241", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "8ec03141da397548f8a06259222c14e3", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9f20a365cc0ec1831e0e301a34198b0e", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "37980dc34b9ad281cdab738f7053af26", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "148e0f3a1ee77607d0b93a636b253295", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "e20a2aa49741757075634f422dc5ac7a", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "fa7018f7c2ee8c952bff7879b92709cb", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "cd38a41872e74b43de005375330e3cd0", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "5bc4faf70f09f56e2ced27cafe2ad3e6", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "84b6be9d4a7d8a544993006751c7f632", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "f87ec55a2f3732de69bd7ca56366ac5e", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cc88073fd308bca9e681fa585cc8f534", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a671348d891eb53c6882047f270c46ed", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "667d5118d18ec5e9e822224868d24380", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "f49b0ac8dc49ef622c2da81de1134425", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "b38fb26bbcc45d9ac3990a4b4b2cec0a", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "3100977be9a4cb9271d1ee60d00afd87", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1bd4a1960660c6f1b7c355de6d3d8658", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "3674cde02fbe0059d3f7281344970d16", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "384f40a3d4652323c218f1dfa650ec17", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "cdf0902eeacf4667ddae3b489fb9dd38", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "283aaa1498e719442445c6e24297d5f9", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "7b0facb90fc8b2918cb124c0dcd360af", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "981d0be875bb37e94840040ff41cf52e", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f850af922341d32d237c249daef4383c", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "d3a3e7fb95fc7fbe586646134060d4e2", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "f1a9d399fa0313ee35797eec3b6a465a", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "031e6fd40465f5b8d9f6390efcb7e82d", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "4c8f3095b6a700d1c9a73aabaee98e63", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "e3c41c240bb333fc53240f64d6e9583e", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "e3c41c240bb333fc53240f64d6e9583e", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "0b0ec8be8c575beba2935fdc9aa03ce5", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "04c21c4226944f71230420f9b399d1e4", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "0b0ec8be8c575beba2935fdc9aa03ce5", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "04c21c4226944f71230420f9b399d1e4", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "e80ffa41dcc78dbd75baf89fadb096b4", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "6514628d8ce1c046726de69fa0086613", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "e80ffa41dcc78dbd75baf89fadb096b4", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "6514628d8ce1c046726de69fa0086613", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "9f4f5c5043d66fa3bdeb16b0599b5de4", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "a3124c863c4b80de5acc20a7c9d49492", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "9eba00c4b0b0e11a16d8396f26d34868", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "fb9041aadad56623332d3fa97684784e", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "34c36a14ff8a8d7661f923cc8f1149c9", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "e87159408ee82988c641c963cb53c4e3", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "569e1aac13b5bb8abd7df8e91ecc7554", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "d85d2e4cbcaf1c5fe2a3c984d35a4ed8", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "5a33774c2533bfd695facb4a68767bb8", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "87890c51eb8a6eab5e1847bf19e1c1ba", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f", diff --git a/CHANGELOG.md b/CHANGELOG.md index fd6cae22..b86b15f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.30 (build 21639, api 8, 2023-11-30) +### 1.7.30 (build 21652, api 8, 2023-12-01) - Continued work on the big 1.7.28 update. - Got the Android version back up and running. There's been lots of cleanup and simplification on the Android layer, cleaning out years of cruft. This should diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index b014d677..07bacfa6 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -229,6 +229,15 @@ class App: self.lang = LanguageSubsystem() self.plugins = PluginSubsystem() + @property + def active(self) -> bool: + """Whether the app is currently front and center. + + This will be False when the app is hidden, other activities + are covering it, etc. (depending on the platform). + """ + return _babase.app_is_active() + @property def aioloop(self) -> asyncio.AbstractEventLoop: """The logic thread's asyncio event loop. diff --git a/src/assets/ba_data/python/baclassic/_ads.py b/src/assets/ba_data/python/baclassic/_ads.py index 0617a6ea..0b520d22 100644 --- a/src/assets/ba_data/python/baclassic/_ads.py +++ b/src/assets/ba_data/python/baclassic/_ads.py @@ -4,6 +4,8 @@ from __future__ import annotations import time +import asyncio +import logging from typing import TYPE_CHECKING import babase @@ -31,6 +33,7 @@ class AdsSubsystem: self.last_in_game_ad_remove_message_show_time: float | None = None self.last_ad_completion_time: float | None = None self.last_ad_was_short = False + self._fallback_task: asyncio.Task | None = None def do_remove_in_game_ads_message(self) -> None: """(internal)""" @@ -181,36 +184,53 @@ class AdsSubsystem: # If we're *still* cleared to show, actually tell the system to show. if show: - # As a safety-check, set up an object that will run - # the completion callback if we've returned and sat for 10 seconds - # (in case some random ad network doesn't properly deliver its - # completion callback). + # As a safety-check, we set up an object that will run the + # completion callback if we've returned and sat for several + # seconds (in case some random ad network doesn't properly + # deliver its completion callback). class _Payload: def __init__(self, pcall: Callable[[], Any]): self._call = pcall self._ran = False def run(self, fallback: bool = False) -> None: - """Run fallback call (and issue a warning about it).""" + """Run the payload.""" assert app.classic is not None if not self._ran: if fallback: lanst = app.classic.ads.last_ad_network_set_time - print( - 'ERROR: relying on fallback ad-callback! ' - 'last network: ' - + app.classic.ads.last_ad_network - + ' (set ' - + str(int(time.time() - lanst)) - + 's ago); purpose=' - + app.classic.ads.last_ad_purpose + logging.error( + 'Relying on fallback ad-callback! ' + 'last network: %s (set %s seconds ago);' + ' purpose=%s.', + app.classic.ads.last_ad_network, + time.time() - lanst, + app.classic.ads.last_ad_purpose, ) babase.pushcall(self._call) self._ran = True payload = _Payload(call) + + # Set up our backup. with babase.ContextRef.empty(): - babase.apptimer(5.0, lambda: payload.run(fallback=True)) + # Note to self: Previously this was a simple 5 second + # timer because the app got totally suspended while ads + # were showing (which delayed the timer), but these days + # the app may continue to run, so we need to be more + # careful and only fire the fallback after we see that + # the app has been front-and-center for several seconds. + async def add_fallback_task() -> None: + activesecs = 5 + while activesecs > 0: + if babase.app.active: + activesecs -= 1 + await asyncio.sleep(1.0) + payload.run(fallback=True) + + _fallback_task = babase.app.aioloop.create_task( + add_fallback_task() + ) self.show_ad('between_game', on_completion_call=payload.run) else: babase.pushcall(call) # Just run the callback without the ad. diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 4e411438..253fb286 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21639 +TARGET_BALLISTICA_BUILD = 21652 TARGET_BALLISTICA_VERSION = '1.7.30' diff --git a/src/assets/ba_data/python/bauiv1lib/specialoffer.py b/src/assets/ba_data/python/bauiv1lib/specialoffer.py index af6621a8..6e4a463b 100644 --- a/src/assets/ba_data/python/bauiv1lib/specialoffer.py +++ b/src/assets/ba_data/python/bauiv1lib/specialoffer.py @@ -551,9 +551,11 @@ def show_offer() -> bool: if bui.native_review_request_supported(): bui.native_review_request() else: - feedback.ask_for_rating() + if app.ui_v1.available: + feedback.ask_for_rating() else: - SpecialOfferWindow(app.classic.special_offer) + if app.ui_v1.available: + SpecialOfferWindow(app.classic.special_offer) app.classic.special_offer = None return True diff --git a/src/ballistica/base/app_adapter/app_adapter.cc b/src/ballistica/base/app_adapter/app_adapter.cc index e140e1a2..fd409b94 100644 --- a/src/ballistica/base/app_adapter/app_adapter.cc +++ b/src/ballistica/base/app_adapter/app_adapter.cc @@ -25,138 +25,13 @@ void AppAdapter::OnMainThreadStartApp() { } void AppAdapter::OnAppStart() { assert(g_base->InLogicThread()); } -void AppAdapter::OnAppPause() { assert(g_base->InLogicThread()); } -void AppAdapter::OnAppResume() { assert(g_base->InLogicThread()); } +void AppAdapter::OnAppSuspend() { assert(g_base->InLogicThread()); } +void AppAdapter::OnAppUnsuspend() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppShutdown() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppShutdownComplete() { assert(g_base->InLogicThread()); } void AppAdapter::OnScreenSizeChange() { assert(g_base->InLogicThread()); } void AppAdapter::DoApplyAppConfig() { assert(g_base->InLogicThread()); } -void AppAdapter::OnAppSuspend_() { - assert(g_core->InMainThread()); - - // IMPORTANT: Any pause related stuff that event-loop-threads need to do - // should be done from their registered pause-callbacks. If we instead - // push runnables to them from here they may or may not be called before - // their event-loop is actually paused. - - // Pause all event loops. - EventLoop::SetEventLoopsSuspended(true); - - if (g_base->network_reader) { - g_base->network_reader->OnAppPause(); - } - g_base->networking->OnAppPause(); -} - -void AppAdapter::OnAppUnsuspend_() { - assert(g_core->InMainThread()); - - // Spin all event-loops back up. - EventLoop::SetEventLoopsSuspended(false); - - // Run resumes that expect to happen in the main thread. - g_base->network_reader->OnAppResume(); - g_base->networking->OnAppResume(); - - // When resuming from a suspended state, we may want to pause whatever - // game was running when we last were active. - // - // TODO(efro): we should make this smarter so it doesn't happen if we're - // in a network game or something that we can't pause; bringing up the - // menu doesn't really accomplish anything there. - // - // In general this probably should be handled at a higher level. - // if (g_core->should_pause_active_game) { - // g_core->should_pause_active_game = false; - - // // If we've been completely backgrounded, send a menu-press command to - // // the game; this will bring up a pause menu if we're in the game/etc. - // if (!g_base->ui->MainMenuVisible()) { - // g_base->ui->PushMainMenuPressCall(nullptr); - // } - // } -} - -void AppAdapter::SuspendApp() { - assert(g_core); - assert(g_core->InMainThread()); - - if (app_suspended_) { - Log(LogLevel::kWarning, - "AppAdapter::SuspendApp() called with app already suspended."); - return; - } - - millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()}; - - // Apple mentioned 5 seconds to run stuff once backgrounded or they bring - // down the hammer. Let's aim to stay under 2. - millisecs_t max_duration{2000}; - - g_core->platform->DebugLog( - "SuspendApp@" - + std::to_string(core::CorePlatform::GetCurrentMillisecs())); - app_suspended_ = true; - OnAppSuspend_(); - - // We assume that the OS will completely suspend our process the moment we - // return from this call (though this is not technically true on all - // platforms). So we want to spin and wait for threads to actually process - // the pause message. - size_t running_thread_count{}; - while (std::abs(core::CorePlatform::GetCurrentMillisecs() - start_time) - < max_duration) { - // If/when we get to a point with no threads waiting to be paused, we're - // good to go. - auto threads{EventLoop::GetStillSuspendingEventLoops()}; - running_thread_count = threads.size(); - if (running_thread_count == 0) { - if (g_buildconfig.debug_build()) { - Log(LogLevel::kDebug, - "SuspendApp() completed in " - + std::to_string(core::CorePlatform::GetCurrentMillisecs() - - start_time) - + "ms."); - } - return; - } - } - - // If we made it here, we timed out. Complain. - Log(LogLevel::kError, - std::string("SuspendApp() took too long; ") - + std::to_string(running_thread_count) - + " threads not yet paused after " - + std::to_string(core::CorePlatform::GetCurrentMillisecs() - - start_time) - + " ms."); -} - -void AppAdapter::UnsuspendApp() { - assert(g_core); - assert(g_core->InMainThread()); - - if (!app_suspended_) { - Log(LogLevel::kWarning, - "AppAdapter::UnsuspendApp() called with app not in suspendedstate."); - return; - } - millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()}; - g_core->platform->DebugLog( - "UnsuspendApp@" - + std::to_string(core::CorePlatform::GetCurrentMillisecs())); - app_suspended_ = false; - OnAppUnsuspend_(); - if (g_buildconfig.debug_build()) { - Log(LogLevel::kDebug, - "UnsuspendApp() completed in " - + std::to_string(core::CorePlatform::GetCurrentMillisecs() - - start_time) - + "ms."); - } -} - void AppAdapter::RunMainThreadEventLoopToCompletion() { FatalError("RunMainThreadEventLoopToCompletion is not implemented here."); } @@ -242,41 +117,6 @@ auto AppAdapter::GetGraphicsClientContext() -> GraphicsClientContext* { auto AppAdapter::GetKeyRepeatDelay() -> float { return 0.3f; } auto AppAdapter::GetKeyRepeatInterval() -> float { return 0.08f; } -auto AppAdapter::ClipboardIsSupported() -> bool { - // We only call our actual virtual function once. - if (!have_clipboard_is_supported_) { - clipboard_is_supported_ = DoClipboardIsSupported(); - have_clipboard_is_supported_ = true; - } - return clipboard_is_supported_; -} - -auto AppAdapter::ClipboardHasText() -> bool { - // If subplatform says they don't support clipboards, don't even ask. - if (!ClipboardIsSupported()) { - return false; - } - return DoClipboardHasText(); -} - -void AppAdapter::ClipboardSetText(const std::string& text) { - // If subplatform says they don't support clipboards, this is an error. - if (!ClipboardIsSupported()) { - throw Exception("ClipboardSetText called with no clipboard support.", - PyExcType::kRuntime); - } - DoClipboardSetText(text); -} - -auto AppAdapter::ClipboardGetText() -> std::string { - // If subplatform says they don't support clipboards, this is an error. - if (!ClipboardIsSupported()) { - throw Exception("ClipboardGetText called with no clipboard support.", - PyExcType::kRuntime); - } - return DoClipboardGetText(); -} - auto AppAdapter::DoClipboardIsSupported() -> bool { return false; } auto AppAdapter::DoClipboardHasText() -> bool { @@ -311,4 +151,8 @@ void AppAdapter::NativeReviewRequest() { void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); } +auto AppAdapter::ShouldSilenceAudioWhenInactive() -> bool const { + return false; +} + } // namespace ballistica::base diff --git a/src/ballistica/base/app_adapter/app_adapter.h b/src/ballistica/base/app_adapter/app_adapter.h index f62ae114..a73b74b6 100644 --- a/src/ballistica/base/app_adapter/app_adapter.h +++ b/src/ballistica/base/app_adapter/app_adapter.h @@ -22,8 +22,8 @@ class AppAdapter { // Logic thread callbacks. virtual void OnAppStart(); - virtual void OnAppPause(); - virtual void OnAppResume(); + virtual void OnAppSuspend(); + virtual void OnAppUnsuspend(); virtual void OnAppShutdown(); virtual void OnAppShutdownComplete(); virtual void OnScreenSizeChange(); @@ -88,9 +88,9 @@ class AppAdapter { /// plugged in or unplugged/etc. Default implementation returns true. virtual auto ShouldUseCursor() -> bool; - /// Return whether the app-adapter is having the OS show a cursor. - /// If this returns false, the engine will take care of drawing a cursor - /// when necessary. If true, SetHardwareCursorVisible will be called + /// Return whether the app-adapter is having the OS show a cursor. If this + /// returns false, the engine will take care of drawing a cursor when + /// necessary. If true, SetHardwareCursorVisible will be called /// periodically to inform the adapter what the cursor state should be. /// The default implementation returns false; virtual auto HasHardwareCursor() -> bool; @@ -106,21 +106,6 @@ class AppAdapter { /// values. virtual void CursorPositionForDraw(float* x, float* y); - /// Put the app into a suspended state. Should be called from the main - /// thread. Pauses work, closes network sockets, etc. May correspond to - /// being backgrounded on mobile, being minimized on desktop, etc. It is - /// assumed that, as soon as this call returns, all work is finished and - /// all threads can be suspended by the OS without any negative side - /// effects. - void SuspendApp(); - - /// Return the app to a running state from a suspended one. Can correspond - /// to foregrounding on mobile, unminimizing on desktop, etc. Spins - /// threads back up, re-opens network sockets, etc. - void UnsuspendApp(); - - auto app_suspended() const { return app_suspended_; } - /// Return whether this AppAdapter supports a 'fullscreen' toggle for its /// display. This will affect whether that option is available in display /// settings or via a hotkey. Must be called from the logic thread. @@ -150,6 +135,12 @@ class AppAdapter { /// Return whether this AppAdapter supports max-fps controls for its display. virtual auto SupportsMaxFPS() -> bool const; + /// Return whether audio should be silenced when the app is inactive. + /// On Desktop systems it is generally normal to continue to hear things + /// even if their windows are hidden, but on mobile we probably want to + /// silence our audio when phone calls, ads, etc. pop up over it. + virtual auto ShouldSilenceAudioWhenInactive() -> bool const; + /// Return whether this platform supports soft-quit. A soft quit is /// when the app is reset/backgrounded/etc. but remains running in case /// needed again. Generally this is the behavior on mobile apps. @@ -206,22 +197,6 @@ class AppAdapter { virtual auto GetKeyRepeatDelay() -> float; virtual auto GetKeyRepeatInterval() -> float; - /// Return whether clipboard operations are supported at all. This gets - /// called when determining whether to display clipboard related UI - /// elements/etc. - auto ClipboardIsSupported() -> bool; - - /// Return whether there is currently text on the clipboard. - auto ClipboardHasText() -> bool; - - /// Set current clipboard text. Raises an Exception if clipboard is - /// unsupported. - void ClipboardSetText(const std::string& text); - - /// Return current text from the clipboard. Raises an Exception if - /// clipboard is unsupported or if there's no text on the clipboard. - auto ClipboardGetText() -> std::string; - /// Push a raw pointer Runnable to the platform's 'main' thread. The main /// thread should call its RunAndLogErrors() method and then delete it. virtual void DoPushMainThreadRunnable(Runnable* runnable) = 0; @@ -239,25 +214,19 @@ class AppAdapter { /// Asynchronously kick off a native review request. void NativeReviewRequest(); - protected: - virtual ~AppAdapter(); - virtual auto DoClipboardIsSupported() -> bool; virtual auto DoClipboardHasText() -> bool; virtual void DoClipboardSetText(const std::string& text); virtual auto DoClipboardGetText() -> std::string; + protected: + virtual ~AppAdapter(); + /// Override to implement native review requests. Will be called in the /// main thread. virtual void DoNativeReviewRequest(); private: - void OnAppSuspend_(); - void OnAppUnsuspend_(); - - bool app_suspended_{}; - bool have_clipboard_is_supported_{}; - bool clipboard_is_supported_{}; }; } // namespace ballistica::base diff --git a/src/ballistica/base/app_adapter/app_adapter_apple.h b/src/ballistica/base/app_adapter/app_adapter_apple.h index fa0ee058..6a51b51e 100644 --- a/src/ballistica/base/app_adapter/app_adapter_apple.h +++ b/src/ballistica/base/app_adapter/app_adapter_apple.h @@ -66,7 +66,6 @@ class AppAdapterApple : public AppAdapter { private: class ScopedAllowGraphics_; - // void UpdateScreenSizes_(); void ReloadRenderer_(const GraphicsSettings* settings); std::thread::id graphics_thread_{}; diff --git a/src/ballistica/base/app_adapter/app_adapter_sdl.cc b/src/ballistica/base/app_adapter/app_adapter_sdl.cc index 13b2b698..5bc2ed08 100644 --- a/src/ballistica/base/app_adapter/app_adapter_sdl.cc +++ b/src/ballistica/base/app_adapter/app_adapter_sdl.cc @@ -471,6 +471,9 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) { // it to the config so that UIs can poll for it and pick up the // change. We don't do this on other platforms where a maximized // window is more distinctly different than a fullscreen one. + // Though I guess some Linux window managers have a fullscreen + // function so theoretically we should there. Le sigh. Maybe SDL + // 3 will tidy up this situation. fullscreen_ = true; g_base->logic->event_loop()->PushCall([] { g_base->python->objs() @@ -497,18 +500,22 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) { break; case SDL_WINDOWEVENT_HIDDEN: { - // Let's keep track of when we're hidden so we can stop drawing - // and sleep more. Theoretically we could put the app into a full - // suspended state like we do on mobile (pausing event loops/etc.) - // but that would be more involved; we'd need to ignore most SDL - // events while sleeping (except for SDL_WINDOWEVENT_SHOWN) and - // would need to rebuild our controller lists/etc when we resume. - // For now just gonna keep things simple and keep running. + // We plug this into the app's overall 'Active' state so it can + // pause stuff or throttle down processing or whatever else. + if (!hidden_) { + g_base->SetAppActive(false); + } + // Also note that we are *completely* hidden, so we can totally + // stop drawing ('Inactive' app state does not imply this in and + // of itself). hidden_ = true; break; } case SDL_WINDOWEVENT_SHOWN: { + if (hidden_) { + g_base->SetAppActive(true); + } hidden_ = false; break; } diff --git a/src/ballistica/base/app_mode/app_mode.cc b/src/ballistica/base/app_mode/app_mode.cc index e87ab5a6..4f1650fc 100644 --- a/src/ballistica/base/app_mode/app_mode.cc +++ b/src/ballistica/base/app_mode/app_mode.cc @@ -14,8 +14,8 @@ void AppMode::OnActivate() {} void AppMode::OnDeactivate() {} void AppMode::OnAppStart() {} -void AppMode::OnAppPause() {} -void AppMode::OnAppResume() {} +void AppMode::OnAppSuspend() {} +void AppMode::OnAppUnsuspend() {} void AppMode::OnAppShutdown() {} void AppMode::OnAppShutdownComplete() {} diff --git a/src/ballistica/base/app_mode/app_mode.h b/src/ballistica/base/app_mode/app_mode.h index 5a12fa0d..7f981b5a 100644 --- a/src/ballistica/base/app_mode/app_mode.h +++ b/src/ballistica/base/app_mode/app_mode.h @@ -26,8 +26,8 @@ class AppMode { /// Logic thread callbacks that run while the app-mode is active. virtual void OnAppStart(); - virtual void OnAppPause(); - virtual void OnAppResume(); + virtual void OnAppSuspend(); + virtual void OnAppUnsuspend(); virtual void OnAppShutdown(); virtual void OnAppShutdownComplete(); virtual void DoApplyAppConfig(); diff --git a/src/ballistica/base/audio/audio.cc b/src/ballistica/base/audio/audio.cc index dc1823f2..6e039ead 100644 --- a/src/ballistica/base/audio/audio.cc +++ b/src/ballistica/base/audio/audio.cc @@ -33,9 +33,9 @@ void Audio::Reset() { void Audio::OnAppStart() { assert(g_base->InLogicThread()); } -void Audio::OnAppPause() { assert(g_base->InLogicThread()); } +void Audio::OnAppSuspend() { assert(g_base->InLogicThread()); } -void Audio::OnAppResume() { assert(g_base->InLogicThread()); } +void Audio::OnAppUnsuspend() { assert(g_base->InLogicThread()); } void Audio::OnAppShutdown() { assert(g_base->InLogicThread()); } diff --git a/src/ballistica/base/audio/audio.h b/src/ballistica/base/audio/audio.h index 9c87daec..af0d646d 100644 --- a/src/ballistica/base/audio/audio.h +++ b/src/ballistica/base/audio/audio.h @@ -21,8 +21,8 @@ class Audio { void Reset(); virtual void OnAppStart(); - virtual void OnAppPause(); - virtual void OnAppResume(); + virtual void OnAppSuspend(); + virtual void OnAppUnsuspend(); virtual void OnAppShutdown(); virtual void OnAppShutdownComplete(); virtual void DoApplyAppConfig(); diff --git a/src/ballistica/base/audio/audio_server.cc b/src/ballistica/base/audio/audio_server.cc index fade988f..1d83cd0f 100644 --- a/src/ballistica/base/audio/audio_server.cc +++ b/src/ballistica/base/audio/audio_server.cc @@ -4,6 +4,7 @@ #include +#include "ballistica/base/app_adapter/app_adapter.h" #include "ballistica/base/assets/assets.h" #include "ballistica/base/assets/sound_asset.h" #include "ballistica/base/audio/al_sys.h" @@ -111,7 +112,7 @@ class AudioServer::ThreadSource_ : public Object { std::unique_ptr client_source_; float fade_{1.0f}; float gain_{1.0f}; - AudioServer* audio_thread_{}; + AudioServer* audio_server_{}; bool valid_{}; const Object::Ref* source_sound_{}; int id_{}; @@ -343,13 +344,13 @@ void AudioServer::SetSuspended_(bool suspend) { try { alcDevicePauseSOFT(device); } catch (const std::exception& e) { - g_core->platform->DebugLog( + g_core->platform->LowLevelDebugLog( std::string("EXC pausing alcDevice: ") + g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " " + e.what()); throw; } catch (...) { - g_core->platform->DebugLog("UNKNOWN EXC pausing alcDevice"); + g_core->platform->LowLevelDebugLog("UNKNOWN EXC pausing alcDevice"); throw; } #endif @@ -381,13 +382,13 @@ void AudioServer::SetSuspended_(bool suspend) { try { alcDeviceResumeSOFT(device); } catch (const std::exception& e) { - g_core->platform->DebugLog( + g_core->platform->LowLevelDebugLog( std::string("EXC resuming alcDevice: ") + g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " " + e.what()); throw; } catch (...) { - g_core->platform->DebugLog("UNKNOWN EXC resuming alcDevice"); + g_core->platform->LowLevelDebugLog("UNKNOWN EXC resuming alcDevice"); throw; } #endif @@ -689,7 +690,7 @@ void AudioServer::Process_() { assert(g_base->InAudioThread()); millisecs_t real_time = g_core->GetAppTimeMillisecs(); - // If we're suspended we don't do nothin'. + // Only do real work if we're in normal running mode. if (!suspended_ && !shutting_down_) { // Do some loading... have_pending_loads_ = g_base->assets->RunPendingAudioLoads(); @@ -711,6 +712,21 @@ void AudioServer::Process_() { } } + // If the app has switched active/inactive state, update + // our volumes (we may silence our audio in these cases). + auto app_active = g_base->app_active(); + if (app_active != app_active_) { + app_active_ = app_active; + if (g_base->app_adapter->ShouldSilenceAudioWhenInactive()) { + app_active_volume_ = app_active ? 1.0f : 0.0f; + } else { + app_active_volume_ = 1.0f; + } + for (auto&& i : sources_) { + i->UpdateVolume(); + } + } + #if BA_ENABLE_AUDIO CHECK_AL_ERROR; #endif @@ -792,9 +808,9 @@ void AudioServer::FadeSoundOut(uint32_t play_id, uint32_t time) { // delete c; // } -AudioServer::ThreadSource_::ThreadSource_(AudioServer* audio_thread_in, +AudioServer::ThreadSource_::ThreadSource_(AudioServer* audio_server_in, int id_in, bool* valid_out) - : id_(id_in), audio_thread_(audio_thread_in) { + : id_(id_in), audio_server_(audio_server_in) { #if BA_ENABLE_AUDIO assert(g_core); assert(valid_out != nullptr); @@ -839,10 +855,10 @@ AudioServer::ThreadSource_::~ThreadSource_() { Stop(); // Remove us from sources list. - for (auto i = audio_thread_->sources_.begin(); - i != audio_thread_->sources_.end(); ++i) { + for (auto i = audio_server_->sources_.begin(); + i != audio_server_->sources_.end(); ++i) { if (*i == this) { - audio_thread_->sources_.erase(i); + audio_server_->sources_.erase(i); break; } } @@ -1079,12 +1095,12 @@ void AudioServer::ThreadSource_::ExecPlay() { looping_ = false; // Push us on the list of streaming sources if we're not on it. - for (auto&& i : audio_thread_->streaming_sources_) { + for (auto&& i : audio_server_->streaming_sources_) { if (i == this) { throw Exception(); } } - audio_thread_->streaming_sources_.push_back(this); + audio_server_->streaming_sources_.push_back(this); // Make sure stereo sounds aren't positional. // This is default behavior on Mac/Win, but we enforce it for linux. @@ -1162,10 +1178,10 @@ void AudioServer::ThreadSource_::ExecStop() { if (streamer_.Exists()) { assert(is_streamed_); streamer_->Stop(); - for (auto i = audio_thread_->streaming_sources_.begin(); - i != audio_thread_->streaming_sources_.end(); ++i) { + for (auto i = audio_server_->streaming_sources_.begin(); + i != audio_server_->streaming_sources_.end(); ++i) { if (*i == this) { - audio_thread_->streaming_sources_.erase(i); + audio_server_->streaming_sources_.erase(i); break; } } @@ -1182,15 +1198,16 @@ void AudioServer::ThreadSource_::ExecStop() { void AudioServer::ThreadSource_::UpdateVolume() { #if BA_ENABLE_AUDIO assert(g_base->InAudioThread()); - if (g_base->audio_server->suspended_ - || g_base->audio_server->shutting_down_) { + if (audio_server_->suspended_ || audio_server_->shutting_down_) { return; } float val = gain_ * fade_; + val *= audio_server_->app_active_volume_; + if (current_is_music()) { - val *= audio_thread_->music_volume_ / 7.0f; + val *= audio_server_->music_volume_ / 7.0f; } else { - val *= audio_thread_->sound_volume_; + val *= audio_server_->sound_volume_; } alSourcef(source_, AL_GAIN, std::max(0.0f, val)); CHECK_AL_ERROR; @@ -1208,7 +1225,7 @@ void AudioServer::ThreadSource_::UpdatePitch() { float val = 1.0f; if (current_is_music()) { } else { - val *= audio_thread_->sound_pitch_; + val *= audio_server_->sound_pitch_; } alSourcef(source_, AL_PITCH, val); CHECK_AL_ERROR; diff --git a/src/ballistica/base/audio/audio_server.h b/src/ballistica/base/audio/audio_server.h index 610b53bd..e1d658d3 100644 --- a/src/ballistica/base/audio/audio_server.h +++ b/src/ballistica/base/audio/audio_server.h @@ -115,8 +115,10 @@ class AudioServer { float sound_volume_{1.0f}; float sound_pitch_{1.0f}; float music_volume_{1.0f}; + float app_active_volume_{1.0f}; bool have_pending_loads_{}; + bool app_active_{true}; bool suspended_{}; bool shutdown_completed_{}; bool shutting_down_{}; diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index dc696467..45dadaf3 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -250,6 +250,166 @@ void BaseFeatureSet::StartApp() { g_core->LifecycleLog("start-app end (main thread)"); } +void BaseFeatureSet::SuspendApp() { + assert(g_core); + assert(g_core->InMainThread()); + + if (app_suspended_) { + Log(LogLevel::kWarning, + "AppAdapter::SuspendApp() called with app already suspended."); + return; + } + + millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()}; + + // Apple mentioned 5 seconds to run stuff once backgrounded or they bring + // down the hammer. Let's aim to stay under 2. + millisecs_t max_duration{2000}; + + g_core->platform->LowLevelDebugLog( + "SuspendApp@" + + std::to_string(core::CorePlatform::GetCurrentMillisecs())); + app_suspended_ = true; + + // IMPORTANT: Any pause related stuff that event-loop-threads need to do + // should be done from their registered pause-callbacks. If we instead + // push runnables to them from here they may or may not be called before + // their event-loop is actually paused. + + // Pause all event loops. + EventLoop::SetEventLoopsSuspended(true); + + if (g_base->network_reader) { + g_base->network_reader->OnAppSuspend(); + } + g_base->networking->OnAppSuspend(); + + // We assume that the OS will completely suspend our process the moment we + // return from this call (though this is not technically true on all + // platforms). So we want to spin here and give our various event loop + // threads time to park themselves. + std::vector running_loops; + do { + // If/when we get to a point with no threads waiting to be paused, we're + // good to go. + // auto loops{EventLoop::GetStillSuspendingEventLoops()}; + running_loops = EventLoop::GetStillSuspendingEventLoops(); + // running_loop_count = loops.size(); + if (running_loops.empty()) { + if (g_buildconfig.debug_build()) { + Log(LogLevel::kDebug, + "SuspendApp() completed in " + + std::to_string(core::CorePlatform::GetCurrentMillisecs() + - start_time) + + "ms."); + } + return; + } + } while (std::abs(core::CorePlatform::GetCurrentMillisecs() - start_time) + < max_duration); + + // If we made it here, we timed out. Complain. + std::string msg = + std::string("SuspendApp() took too long; ") + + std::to_string(running_loops.size()) + + " event-loops not yet suspended after " + + std::to_string(core::CorePlatform::GetCurrentMillisecs() - start_time) + + " ms: ("; + bool first = true; + for (auto* loop : running_loops) { + if (!first) { + msg += ", "; + } + // Note: not adding a default here so compiler complains if we + // add/change something. + switch (loop->identifier()) { + case EventLoopID::kInvalid: + msg += "invalid"; + break; + case EventLoopID::kLogic: + msg += "logic"; + break; + case EventLoopID::kAssets: + msg += "assets"; + break; + case EventLoopID::kFileOut: + msg += "fileout"; + break; + case EventLoopID::kMain: + msg += "main"; + break; + case EventLoopID::kAudio: + msg += "audio"; + break; + case EventLoopID::kNetworkWrite: + msg += "networkwrite"; + break; + case EventLoopID::kSuicide: + msg += "suicide"; + break; + case EventLoopID::kStdin: + msg += "stdin"; + break; + case EventLoopID::kBGDynamics: + msg += "bgdynamics"; + break; + } + first = false; + } + msg += ")."; + + Log(LogLevel::kError, msg); +} + +void BaseFeatureSet::UnsuspendApp() { + assert(g_core); + assert(g_core->InMainThread()); + + if (!app_suspended_) { + Log(LogLevel::kWarning, + "AppAdapter::UnsuspendApp() called with app not in suspendedstate."); + return; + } + millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()}; + g_core->platform->LowLevelDebugLog( + "UnsuspendApp@" + + std::to_string(core::CorePlatform::GetCurrentMillisecs())); + app_suspended_ = false; + + // Spin all event-loops back up. + EventLoop::SetEventLoopsSuspended(false); + + // Run resumes that expect to happen in the main thread. + g_base->network_reader->OnAppUnsuspend(); + g_base->networking->OnAppUnsuspend(); + + // When resuming from a suspended state, we may want to pause whatever + // game was running when we last were active. + // + // TODO(efro): we should make this smarter so it doesn't happen if we're + // in a network game or something that we can't pause; bringing up the + // menu doesn't really accomplish anything there. + // + // In general this probably should be handled at a higher level. + // if (g_core->should_pause_active_game) { + // g_core->should_pause_active_game = false; + + // // If we've been completely backgrounded, send a menu-press command to + // // the game; this will bring up a pause menu if we're in the game/etc. + // if (!g_base->ui->MainMenuVisible()) { + // g_base->ui->PushMainMenuPressCall(nullptr); + // } + // } + + if (g_buildconfig.debug_build()) { + Log(LogLevel::kDebug, + "UnsuspendApp() completed in " + + std::to_string(core::CorePlatform::GetCurrentMillisecs() + - start_time) + + "ms."); + } +} + void BaseFeatureSet::OnAppShutdownComplete() { assert(g_core->InMainThread()); assert(g_core); @@ -760,4 +920,57 @@ void BaseFeatureSet::PushMainThreadRunnable(Runnable* runnable) { app_adapter->DoPushMainThreadRunnable(runnable); } +auto BaseFeatureSet::ClipboardIsSupported() -> bool { + // We only call our actual virtual function once. + if (!have_clipboard_is_supported_) { + clipboard_is_supported_ = app_adapter->DoClipboardIsSupported(); + have_clipboard_is_supported_ = true; + } + return clipboard_is_supported_; +} + +auto BaseFeatureSet::ClipboardHasText() -> bool { + // If subplatform says they don't support clipboards, don't even ask. + if (!ClipboardIsSupported()) { + return false; + } + return app_adapter->DoClipboardHasText(); +} + +void BaseFeatureSet::ClipboardSetText(const std::string& text) { + // If subplatform says they don't support clipboards, this is an error. + if (!ClipboardIsSupported()) { + throw Exception("ClipboardSetText called with no clipboard support.", + PyExcType::kRuntime); + } + app_adapter->DoClipboardSetText(text); +} + +auto BaseFeatureSet::ClipboardGetText() -> std::string { + // If subplatform says they don't support clipboards, this is an error. + if (!ClipboardIsSupported()) { + throw Exception("ClipboardGetText called with no clipboard support.", + PyExcType::kRuntime); + } + return app_adapter->DoClipboardGetText(); +} + +void BaseFeatureSet::SetAppActive(bool active) { + assert(InMainThread()); + g_core->platform->LowLevelDebugLog( + "SetAppActive(" + std::to_string(active) + ")@" + + std::to_string(core::CorePlatform::GetCurrentMillisecs())); + + printf("APP ACTIVE %d\n", static_cast(active)); + + // Issue a gentle warning if they are feeding us the same state twice in a + // row; might imply faulty logic. + if (app_active_set_ && app_active_ == active) { + Log(LogLevel::kWarning, "SetAppActive called with state " + + std::to_string(active) + " twice in a row."); + } + app_active_set_ = true; + app_active_ = active; +} + } // namespace ballistica::base diff --git a/src/ballistica/base/base.h b/src/ballistica/base/base.h index a14ddee1..5bea0e93 100644 --- a/src/ballistica/base/base.h +++ b/src/ballistica/base/base.h @@ -609,6 +609,31 @@ class BaseFeatureSet : public FeatureSetNativeComponent, /// Start app systems in motion. void StartApp() override; + /// Set the app's active state. Should be called from the main thread. + /// Generally called by the AppAdapter. Being inactive means the app + /// experience is not front and center and thus it may want to throttle + /// down its rendering rate, pause single play gameplay, etc. This does + /// not, however, cause any extreme action such as halting event loops; + /// use Suspend/Resume for that. And note that the app may still be + /// visible while inactive, so it should not *completely* stop + /// drawing/etc. + void SetAppActive(bool active); + + /// Put the app into a suspended state. Should be called from the main + /// thread. Generally called by the AppAdapter. Suspends event loops, + /// closes network sockets, etc. Generally corresponds to being + /// backgrounded on mobile platforms. It is assumed that, as soon as this + /// call returns, all engine work is finished and all threads can be + /// immediately suspended by the OS without any problems. + void SuspendApp(); + + /// Return the app to a running state from a suspended one. Can correspond + /// to foregrounding on mobile, unminimizing on desktop, etc. Spins + /// threads back up, re-opens network sockets, etc. + void UnsuspendApp(); + + auto app_suspended() const { return app_suspended_; } + /// Issue a high level app quit request. Can be called from any thread and /// can be safely called repeatedly. If 'confirm' is true, a confirmation /// dialog will be presented if the environment and situation allows; @@ -738,6 +763,22 @@ class BaseFeatureSet : public FeatureSetNativeComponent, /// reported by the Python layer. auto GetV2AccountID() -> std::optional; + /// Return whether clipboard operations are supported at all. This gets + /// called when determining whether to display clipboard related UI + /// elements/etc. + auto ClipboardIsSupported() -> bool; + + /// Return whether there is currently text on the clipboard. + auto ClipboardHasText() -> bool; + + /// Set current clipboard text. Raises an Exception if clipboard is + /// unsupported. + void ClipboardSetText(const std::string& text); + + /// Return current text from the clipboard. Raises an Exception if + /// clipboard is unsupported or if there's no text on the clipboard. + auto ClipboardGetText() -> std::string; + // Const subsystems. AppAdapter* const app_adapter; AppConfig* const app_config; @@ -774,10 +815,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent, // Non-const bits (fixme: clean up access to these). TouchInput* touch_input{}; - // auto return_value() const { return return_value_; } - // void set_return_value(int val) { return_value_ = val; } - - // auto GetReturnValue() const -> int override; + auto app_active() const { return app_active_; } private: BaseFeatureSet(); @@ -789,8 +827,12 @@ class BaseFeatureSet : public FeatureSetNativeComponent, AppMode* app_mode_; PlusSoftInterface* plus_soft_{}; ClassicSoftInterface* classic_soft_{}; - std::mutex shutdown_suppress_lock_; + bool have_clipboard_is_supported_{}; + bool clipboard_is_supported_{}; + bool app_active_set_{}; + bool app_active_{true}; + bool app_suspended_{}; bool shutdown_suppress_disallowed_{}; bool tried_importing_plus_{}; bool tried_importing_classic_{}; @@ -803,7 +845,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent, bool basn_log_behavior_{}; bool server_wrapper_managed_{}; int shutdown_suppress_count_{}; - // int return_value_{}; }; } // namespace ballistica::base diff --git a/src/ballistica/base/graphics/graphics.cc b/src/ballistica/base/graphics/graphics.cc index f6802563..53871876 100644 --- a/src/ballistica/base/graphics/graphics.cc +++ b/src/ballistica/base/graphics/graphics.cc @@ -83,12 +83,12 @@ Graphics::~Graphics() = default; void Graphics::OnAppStart() { assert(g_base->InLogicThread()); } -void Graphics::OnAppPause() { +void Graphics::OnAppSuspend() { assert(g_base->InLogicThread()); SetGyroEnabled(false); } -void Graphics::OnAppResume() { +void Graphics::OnAppUnsuspend() { assert(g_base->InLogicThread()); g_base->graphics->SetGyroEnabled(true); } @@ -993,7 +993,10 @@ void Graphics::DrawFades(FrameDef* frame_def) { // Guard against accidental fades that never fade back in. if (fade_ <= 0.0f && fade_out_) { millisecs_t faded_time = real_time - (fade_start_ + fade_time_); - if (faded_time > 15000) { + + // TEMP HACK - don't trigger this while inactive. + // Need to make overall fade logic smarter. + if (faded_time > 15000 && g_base->app_active()) { Log(LogLevel::kError, "FORCE-ENDING STUCK FADE"); fade_out_ = false; fade_ = 1.0f; diff --git a/src/ballistica/base/graphics/graphics.h b/src/ballistica/base/graphics/graphics.h index c56ae98f..93ef8310 100644 --- a/src/ballistica/base/graphics/graphics.h +++ b/src/ballistica/base/graphics/graphics.h @@ -56,8 +56,8 @@ class Graphics { Graphics(); void OnAppStart(); - void OnAppPause(); - void OnAppResume(); + void OnAppSuspend(); + void OnAppUnsuspend(); void OnAppShutdown(); void OnAppShutdownComplete(); void OnScreenSizeChange(); diff --git a/src/ballistica/base/graphics/graphics_server.cc b/src/ballistica/base/graphics/graphics_server.cc index 4e076291..63220332 100644 --- a/src/ballistica/base/graphics/graphics_server.cc +++ b/src/ballistica/base/graphics/graphics_server.cc @@ -143,7 +143,7 @@ auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* { // Spin and wait for a short bit for a frame_def to appear. while (true) { // Stop waiting if we can't/shouldn't render anyway. - if (!renderer_ || shutting_down_ || g_base->app_adapter->app_suspended()) { + if (!renderer_ || shutting_down_ || g_base->app_suspended()) { return nullptr; } diff --git a/src/ballistica/base/input/input.cc b/src/ballistica/base/input/input.cc index e741a7f3..05268697 100644 --- a/src/ballistica/base/input/input.cc +++ b/src/ballistica/base/input/input.cc @@ -516,9 +516,9 @@ void Input::OnAppStart() { } } -void Input::OnAppPause() { assert(g_base->InLogicThread()); } +void Input::OnAppSuspend() { assert(g_base->InLogicThread()); } -void Input::OnAppResume() { assert(g_base->InLogicThread()); } +void Input::OnAppUnsuspend() { assert(g_base->InLogicThread()); } void Input::OnAppShutdown() { assert(g_base->InLogicThread()); } diff --git a/src/ballistica/base/input/input.h b/src/ballistica/base/input/input.h index e4819753..a20a619d 100644 --- a/src/ballistica/base/input/input.h +++ b/src/ballistica/base/input/input.h @@ -21,8 +21,8 @@ class Input { Input(); void OnAppStart(); - void OnAppPause(); - void OnAppResume(); + void OnAppSuspend(); + void OnAppUnsuspend(); void OnAppShutdown(); void OnAppShutdownComplete(); void StepDisplayTime(); diff --git a/src/ballistica/base/logic/logic.cc b/src/ballistica/base/logic/logic.cc index 909a8ddd..13dfef5b 100644 --- a/src/ballistica/base/logic/logic.cc +++ b/src/ballistica/base/logic/logic.cc @@ -48,9 +48,9 @@ void Logic::OnAppStart() { // Stay informed when our event loop is pausing/unpausing. event_loop_->AddSuspendCallback( - NewLambdaRunnableUnmanaged([this] { OnAppPause(); })); + NewLambdaRunnableUnmanaged([this] { OnAppSuspend(); })); event_loop_->AddUnsuspendCallback( - NewLambdaRunnableUnmanaged([this] { OnAppResume(); })); + NewLambdaRunnableUnmanaged([this] { OnAppUnsuspend(); })); // Running in a specific order here and should try to stick to it in // other OnAppXXX callbacks so any subsystem interdependencies behave @@ -179,40 +179,40 @@ void Logic::OnInitialAppModeSet() { } } -void Logic::OnAppPause() { +void Logic::OnAppSuspend() { assert(g_base->InLogicThread()); assert(g_base->CurrentContext().IsEmpty()); // Note: keep these in opposite order of OnAppStart. - g_base->python->OnAppPause(); + g_base->python->OnAppSuspend(); if (g_base->HavePlus()) { - g_base->plus()->OnAppPause(); + g_base->plus()->OnAppSuspend(); } - g_base->app_mode()->OnAppPause(); - g_base->ui->OnAppPause(); - g_base->input->OnAppPause(); - g_base->audio->OnAppPause(); - g_base->graphics->OnAppPause(); - g_base->platform->OnAppPause(); - g_base->app_adapter->OnAppPause(); + g_base->app_mode()->OnAppSuspend(); + g_base->ui->OnAppSuspend(); + g_base->input->OnAppSuspend(); + g_base->audio->OnAppSuspend(); + g_base->graphics->OnAppSuspend(); + g_base->platform->OnAppSuspend(); + g_base->app_adapter->OnAppSuspend(); } -void Logic::OnAppResume() { +void Logic::OnAppUnsuspend() { assert(g_base->InLogicThread()); assert(g_base->CurrentContext().IsEmpty()); // Note: keep these in the same order as OnAppStart. - g_base->app_adapter->OnAppResume(); - g_base->platform->OnAppResume(); - g_base->graphics->OnAppResume(); - g_base->audio->OnAppResume(); - g_base->input->OnAppResume(); - g_base->ui->OnAppResume(); - g_base->app_mode()->OnAppResume(); + g_base->app_adapter->OnAppUnsuspend(); + g_base->platform->OnAppUnsuspend(); + g_base->graphics->OnAppUnsuspend(); + g_base->audio->OnAppUnsuspend(); + g_base->input->OnAppUnsuspend(); + g_base->ui->OnAppUnsuspend(); + g_base->app_mode()->OnAppUnsuspend(); if (g_base->HavePlus()) { - g_base->plus()->OnAppResume(); + g_base->plus()->OnAppUnsuspend(); } - g_base->python->OnAppResume(); + g_base->python->OnAppUnsuspend(); } void Logic::Shutdown() { diff --git a/src/ballistica/base/logic/logic.h b/src/ballistica/base/logic/logic.h index 06e29f69..32872e63 100644 --- a/src/ballistica/base/logic/logic.h +++ b/src/ballistica/base/logic/logic.h @@ -52,11 +52,11 @@ class Logic { /// Called when our event-loop pauses. Informs Python and other /// subsystems. - void OnAppPause(); + void OnAppSuspend(); /// Called when our event-loop resumes. Informs Python and other /// subsystems. - void OnAppResume(); + void OnAppUnsuspend(); void OnAppShutdown(); void OnAppShutdownComplete(); diff --git a/src/ballistica/base/networking/network_reader.cc b/src/ballistica/base/networking/network_reader.cc index 7c3e0392..fee5e162 100644 --- a/src/ballistica/base/networking/network_reader.cc +++ b/src/ballistica/base/networking/network_reader.cc @@ -25,7 +25,7 @@ void NetworkReader::SetPort(int port) { thread_ = new std::thread(RunThreadStatic_, this); } -void NetworkReader::OnAppPause() { +void NetworkReader::OnAppSuspend() { assert(g_core->InMainThread()); assert(!paused_); { @@ -42,7 +42,7 @@ void NetworkReader::OnAppPause() { } } -void NetworkReader::OnAppResume() { +void NetworkReader::OnAppUnsuspend() { assert(g_core->InMainThread()); assert(paused_); diff --git a/src/ballistica/base/networking/network_reader.h b/src/ballistica/base/networking/network_reader.h index d0494161..7b1798c6 100644 --- a/src/ballistica/base/networking/network_reader.h +++ b/src/ballistica/base/networking/network_reader.h @@ -24,8 +24,8 @@ class NetworkReader { public: NetworkReader(); void SetPort(int port); - void OnAppPause(); - void OnAppResume(); + void OnAppSuspend(); + void OnAppUnsuspend(); auto port4() const { return port4_; } auto port6() const { return port6_; } auto sd_mutex() -> std::mutex& { return sd_mutex_; } diff --git a/src/ballistica/base/networking/networking.cc b/src/ballistica/base/networking/networking.cc index cf672073..696eb7a1 100644 --- a/src/ballistica/base/networking/networking.cc +++ b/src/ballistica/base/networking/networking.cc @@ -31,9 +31,9 @@ void Networking::DoApplyAppConfig() { } } -void Networking::OnAppPause() {} +void Networking::OnAppSuspend() {} -void Networking::OnAppResume() {} +void Networking::OnAppUnsuspend() {} void Networking::SendTo(const std::vector& buffer, const SockAddr& addr) { diff --git a/src/ballistica/base/networking/networking.h b/src/ballistica/base/networking/networking.h index 70aea41c..833b732f 100644 --- a/src/ballistica/base/networking/networking.h +++ b/src/ballistica/base/networking/networking.h @@ -127,8 +127,8 @@ class Networking { // Called on mobile platforms when going into the background, etc // (when all networking should be shut down) - void OnAppPause(); - void OnAppResume(); + void OnAppSuspend(); + void OnAppUnsuspend(); auto remote_server_accepting_connections() -> bool { return remote_server_accepting_connections_; diff --git a/src/ballistica/base/platform/base_platform.cc b/src/ballistica/base/platform/base_platform.cc index 0af9a65b..0a1d4949 100644 --- a/src/ballistica/base/platform/base_platform.cc +++ b/src/ballistica/base/platform/base_platform.cc @@ -166,8 +166,8 @@ void BasePlatform::SetupInterruptHandling() { } void BasePlatform::OnAppStart() { assert(g_base->InLogicThread()); } -void BasePlatform::OnAppPause() { assert(g_base->InLogicThread()); } -void BasePlatform::OnAppResume() { assert(g_base->InLogicThread()); } +void BasePlatform::OnAppSuspend() { assert(g_base->InLogicThread()); } +void BasePlatform::OnAppUnsuspend() { assert(g_base->InLogicThread()); } void BasePlatform::OnAppShutdown() { assert(g_base->InLogicThread()); } void BasePlatform::OnAppShutdownComplete() { assert(g_base->InLogicThread()); } void BasePlatform::OnScreenSizeChange() { assert(g_base->InLogicThread()); } diff --git a/src/ballistica/base/platform/base_platform.h b/src/ballistica/base/platform/base_platform.h index 3609e9cb..b38716ff 100644 --- a/src/ballistica/base/platform/base_platform.h +++ b/src/ballistica/base/platform/base_platform.h @@ -26,8 +26,8 @@ class BasePlatform { // Logic thread callbacks. virtual void OnAppStart(); - virtual void OnAppPause(); - virtual void OnAppResume(); + virtual void OnAppSuspend(); + virtual void OnAppUnsuspend(); virtual void OnAppShutdown(); virtual void OnAppShutdownComplete(); virtual void OnScreenSizeChange(); diff --git a/src/ballistica/base/platform/support/min_sdl_key_names.h b/src/ballistica/base/platform/support/min_sdl_key_names.h index 57eae886..64c57704 100644 --- a/src/ballistica/base/platform/support/min_sdl_key_names.h +++ b/src/ballistica/base/platform/support/min_sdl_key_names.h @@ -82,7 +82,7 @@ static const char* const scancode_names[SDL_NUM_SCANCODES] = { "F12", "PrintScreen", "ScrollLock", - "OnAppPause", + "Pause", "Insert", "Home", "PageUp", diff --git a/src/ballistica/base/python/base_python.cc b/src/ballistica/base/python/base_python.cc index 78545b12..24b9c182 100644 --- a/src/ballistica/base/python/base_python.cc +++ b/src/ballistica/base/python/base_python.cc @@ -149,12 +149,12 @@ void BasePython::OnAppStart() { objs().Get(BasePython::ObjID::kAppOnNativeStartCall).Call(); } -void BasePython::OnAppPause() { +void BasePython::OnAppSuspend() { assert(g_base->InLogicThread()); objs().Get(BasePython::ObjID::kAppOnNativeSuspendCall).Call(); } -void BasePython::OnAppResume() { +void BasePython::OnAppUnsuspend() { assert(g_base->InLogicThread()); objs().Get(BasePython::ObjID::kAppOnNativeUnsuspendCall).Call(); } diff --git a/src/ballistica/base/python/base_python.h b/src/ballistica/base/python/base_python.h index 5646cabe..870da85a 100644 --- a/src/ballistica/base/python/base_python.h +++ b/src/ballistica/base/python/base_python.h @@ -15,8 +15,8 @@ class BasePython { void OnMainThreadStartApp(); void OnAppStart(); - void OnAppPause(); - void OnAppResume(); + void OnAppSuspend(); + void OnAppUnsuspend(); void OnAppShutdown(); void OnAppShutdownComplete(); void DoApplyAppConfig(); diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index 44055fd7..3ce2f238 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -44,6 +44,27 @@ static PyMethodDef PyAppNameDef = { "(internal)\n", }; +// ------------------------------ app_is_active -------------------------------- + +static auto PyAppIsActive(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + + if (g_base->app_active()) { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyAppIsActiveDef = { + "app_is_active", // name + (PyCFunction)PyAppIsActive, // method + METH_NOARGS, // flags + + "app_is_active() -> bool\n" + "\n" + "(internal)\n", +}; // --------------------------------- run_app ----------------------------------- static auto PyRunApp(PyObject* self) -> PyObject* { @@ -1650,6 +1671,7 @@ static PyMethodDef PyGraphicsShutdownIsCompleteDef = { auto PythonMethodsApp::GetMethods() -> std::vector { return { PyAppNameDef, + PyAppIsActiveDef, PyRunAppDef, PyAppNameUpperDef, PyIsXCodeBuildDef, diff --git a/src/ballistica/base/python/methods/python_methods_misc.cc b/src/ballistica/base/python/methods/python_methods_misc.cc index d8e59e45..7b25475d 100644 --- a/src/ballistica/base/python/methods/python_methods_misc.cc +++ b/src/ballistica/base/python/methods/python_methods_misc.cc @@ -129,7 +129,7 @@ static PyMethodDef PyHasTouchScreenDef = { static auto PyClipboardIsSupported(PyObject* self) -> PyObject* { BA_PYTHON_TRY; - if (g_base->app_adapter->ClipboardIsSupported()) { + if (g_base->ClipboardIsSupported()) { Py_RETURN_TRUE; } Py_RETURN_FALSE; @@ -155,7 +155,7 @@ static PyMethodDef PyClipboardIsSupportedDef = { static auto PyClipboardHasText(PyObject* self) -> PyObject* { BA_PYTHON_TRY; - if (g_base->app_adapter->ClipboardHasText()) { + if (g_base->ClipboardHasText()) { Py_RETURN_TRUE; } Py_RETURN_FALSE; @@ -188,7 +188,7 @@ static auto PyClipboardSetText(PyObject* self, PyObject* args, PyObject* keywds) const_cast(kwlist), &value)) { return nullptr; } - g_base->app_adapter->ClipboardSetText(value); + g_base->ClipboardSetText(value); Py_RETURN_NONE; BA_PYTHON_CATCH; } @@ -212,7 +212,7 @@ static PyMethodDef PyClipboardSetTextDef = { static auto PyClipboardGetText(PyObject* self) -> PyObject* { BA_PYTHON_TRY; - return PyUnicode_FromString(g_base->app_adapter->ClipboardGetText().c_str()); + return PyUnicode_FromString(g_base->ClipboardGetText().c_str()); Py_RETURN_FALSE; BA_PYTHON_CATCH; } diff --git a/src/ballistica/base/support/plus_soft.h b/src/ballistica/base/support/plus_soft.h index 00994d3b..32ce3684 100644 --- a/src/ballistica/base/support/plus_soft.h +++ b/src/ballistica/base/support/plus_soft.h @@ -19,8 +19,8 @@ namespace ballistica::base { class PlusSoftInterface { public: virtual void OnAppStart() = 0; - virtual void OnAppPause() = 0; - virtual void OnAppResume() = 0; + virtual void OnAppSuspend() = 0; + virtual void OnAppUnsuspend() = 0; virtual void OnAppShutdown() = 0; virtual void OnAppShutdownComplete() = 0; virtual void DoApplyAppConfig() = 0; diff --git a/src/ballistica/base/ui/dev_console.cc b/src/ballistica/base/ui/dev_console.cc index 7f31eae3..7360a9a0 100644 --- a/src/ballistica/base/ui/dev_console.cc +++ b/src/ballistica/base/ui/dev_console.cc @@ -1441,9 +1441,9 @@ void DevConsole::StepDisplayTime() { auto DevConsole::PasteFromClipboard() -> bool { if (state_ != State_::kInactive) { if (python_terminal_visible_) { - if (g_base->app_adapter->ClipboardIsSupported()) { - if (g_base->app_adapter->ClipboardHasText()) { - auto text = g_base->app_adapter->ClipboardGetText(); + if (g_base->ClipboardIsSupported()) { + if (g_base->ClipboardHasText()) { + auto text = g_base->ClipboardGetText(); if (strstr(text.c_str(), "\n") || strstr(text.c_str(), "\r")) { g_base->audio->PlaySound( g_base->assets->SysSound(SysSoundID::kErrorBeep)); diff --git a/src/ballistica/base/ui/ui.cc b/src/ballistica/base/ui/ui.cc index dda9bd0d..96671198 100644 --- a/src/ballistica/base/ui/ui.cc +++ b/src/ballistica/base/ui/ui.cc @@ -81,9 +81,9 @@ void UI::OnAppStart() { } } -void UI::OnAppPause() { assert(g_base->InLogicThread()); } +void UI::OnAppSuspend() { assert(g_base->InLogicThread()); } -void UI::OnAppResume() { +void UI::OnAppUnsuspend() { assert(g_base->InLogicThread()); SetUIInputDevice(nullptr); } diff --git a/src/ballistica/base/ui/ui.h b/src/ballistica/base/ui/ui.h index 7ed5dd92..2bbf0917 100644 --- a/src/ballistica/base/ui/ui.h +++ b/src/ballistica/base/ui/ui.h @@ -32,8 +32,8 @@ class UI { UI(); void OnAppStart(); - void OnAppPause(); - void OnAppResume(); + void OnAppSuspend(); + void OnAppUnsuspend(); void OnAppShutdown(); void OnAppShutdownComplete(); void DoApplyAppConfig(); diff --git a/src/ballistica/core/platform/core_platform.cc b/src/ballistica/core/platform/core_platform.cc index 0c31a5d7..0abf2ad0 100644 --- a/src/ballistica/core/platform/core_platform.cc +++ b/src/ballistica/core/platform/core_platform.cc @@ -103,7 +103,9 @@ auto CorePlatform::Create() -> CorePlatform* { return platform; } -void CorePlatform::DebugLog(const std::string& msg) { HandleDebugLog(msg); } +void CorePlatform::LowLevelDebugLog(const std::string& msg) { + HandleLowLevelDebugLog(msg); +} CorePlatform::CorePlatform() : start_time_millisecs_(GetCurrentMillisecs()) {} @@ -1021,7 +1023,7 @@ auto CorePlatform::HavePermission(Permission p) -> bool { void CorePlatform::SetDebugKey(const std::string& key, const std::string& value) {} -void CorePlatform::HandleDebugLog(const std::string& msg) {} +void CorePlatform::HandleLowLevelDebugLog(const std::string& msg) {} auto CorePlatform::GetCurrentMillisecs() -> millisecs_t { return std::chrono::time_point_cast( diff --git a/src/ballistica/core/platform/core_platform.h b/src/ballistica/core/platform/core_platform.h index 1d1e1e73..6bcd0627 100644 --- a/src/ballistica/core/platform/core_platform.h +++ b/src/ballistica/core/platform/core_platform.h @@ -54,9 +54,8 @@ class CorePlatform { /// fopen() supporting UTF8 strings. virtual auto FOpen(const char* path, const char* mode) -> FILE*; - /// rename() supporting UTF8 strings. - /// For cross-platform consistency, this should also remove any file that - /// exists at the target location first. + /// rename() supporting UTF8 strings. For cross-platform consistency, this + /// should also remove any file that exists at the target location first. virtual auto Rename(const char* oldname, const char* newname) -> int; /// Simple cross-platform check for existence of a file. @@ -332,26 +331,30 @@ class CorePlatform { /// This is expected to be lightweight as it may be called often. virtual void SetDebugKey(const std::string& key, const std::string& value); - void DebugLog(const std::string& msg); + /// Print a log message to be included in crash logs or other debug + /// mechanisms (example: Crashlytics). V1-cloud-log messages get forwarded + /// to here as well. It can be useful to call this directly to report extra + /// details that may help in debugging, as these calls are not considered + /// 'noteworthy' or presented to the user as standard Log() calls are. + void LowLevelDebugLog(const std::string& msg); #pragma mark MISC -------------------------------------------------------------- - /// Return a time measurement in milliseconds since launch. - /// It *should* be monotonic. - /// For most purposes, AppTime values are preferable since their progression - /// pauses during app suspension and they are 100% guaranteed to not go - /// backwards. + /// Return a time measurement in milliseconds since launch. It *should* be + /// monotonic. For most purposes, AppTime values are preferable since + /// their progression pauses during app suspension and they are 100% + /// guaranteed to not go backwards. auto GetTicks() const -> millisecs_t; - /// Return a raw current milliseconds value. It *should* be monotonic. - /// It is relative to an undefined start point; only use it for time + /// Return a raw current milliseconds value. It *should* be monotonic. It + /// is relative to an undefined start point; only use it for time /// differences. Generally the AppTime values are preferable since their /// progression pauses during app suspension and they are 100% guaranteed /// to not go backwards. static auto GetCurrentMillisecs() -> millisecs_t; - /// Return a raw current microseconds value. It *should* be monotonic. - /// It is relative to an undefined start point; only use it for time + /// Return a raw current microseconds value. It *should* be monotonic. It + /// is relative to an undefined start point; only use it for time /// differences. Generally the AppTime values are preferable since their /// progression pauses during app suspension and they are 100% guaranteed /// to not go backwards. @@ -378,14 +381,15 @@ class CorePlatform { /// Is the OS currently playing music? (so we can avoid doing so). virtual auto IsOSPlayingMusic() -> bool; - /// Pass platform-specific misc-read-vals along to the OS (as a json string). + /// Pass platform-specific misc-read-vals along to the OS (as a json + /// string). virtual void SetPlatformMiscReadVals(const std::string& vals); /// Set the name of the current thread (for debugging). virtual void SetCurrentThreadName(const std::string& name); - // If display-resolution can be directly set on this platform, - // return true and set the native full res here. Otherwise return false; + // If display-resolution can be directly set on this platform, return true + // and set the native full res here. Otherwise return false; virtual auto GetDisplayResolution(int* x, int* y) -> bool; /// Are we being run from a terminal? (should we show prompts, etc?). @@ -412,44 +416,39 @@ class CorePlatform { /// device; something like "iPhone 12 Pro". virtual auto DoGetDeviceDescription() -> std::string; - /// Attempt to actually create a directory. - /// Should *not* raise Exceptions if it already exists or if quiet is true. + /// Attempt to actually create a directory. Should *not* raise Exceptions + /// if it already exists or if quiet is true. virtual void DoMakeDir(const std::string& dir, bool quiet); - /// Attempt to actually get an abs path. This will only be called if - /// the path is valid and exists. + /// Attempt to actually get an abs path. This will only be called if the + /// path is valid and exists. virtual auto DoAbsPath(const std::string& path, std::string* outpath) -> bool; - /// Calc the user scripts dir path for this platform. - /// This will be called once and the path cached. + /// Calc the user scripts dir path for this platform. This will be called + /// once and the path cached. virtual auto DoGetUserPythonDirectoryMonolithicDefault() -> std::optional; - /// Return the default config directory for this platform. - /// This will be used as the config dir if not overridden via command - /// line options, etc. + /// Return the default config directory for this platform. This will be + /// used as the config dir if not overridden via command line options, + /// etc. virtual auto DoGetConfigDirectoryMonolithicDefault() -> std::optional; - /// Return the default data directory for this platform. - /// This will be used as the data dir if not overridden by core-config, etc. - /// This is the one monolithic-default value that is not optional. + /// Return the default data directory for this platform. This will be used + /// as the data dir if not overridden by core-config, etc. This is the one + /// monolithic-default value that is not optional. virtual auto DoGetDataDirectoryMonolithicDefault() -> std::string; - /// Return the default Volatile data dir for this platform. - /// This will be used as the volatile-data-dir if not overridden via command - /// line options/etc. + /// Return the default Volatile data dir for this platform. This will be + /// used as the volatile-data-dir if not overridden via command line + /// options/etc. virtual auto GetDefaultVolatileDataDirectory() -> std::string; /// Generate a random UUID string. virtual auto GenerateUUID() -> std::string; - /// Print a log message to be included in crash logs or other debug - /// mechanisms (example: Crashlytics). V1-cloud-log messages get forwarded - /// to here as well. It can be useful to call this directly to report extra - /// details that may help in debugging, as these calls are not considered - /// 'noteworthy' or presented to the user as standard Log() calls are. - virtual void HandleDebugLog(const std::string& msg); + virtual void HandleLowLevelDebugLog(const std::string& msg); CorePlatform(); virtual ~CorePlatform(); diff --git a/src/ballistica/core/python/core_python.cc b/src/ballistica/core/python/core_python.cc index dcebb4a6..3c981e77 100644 --- a/src/ballistica/core/python/core_python.cc +++ b/src/ballistica/core/python/core_python.cc @@ -10,9 +10,9 @@ namespace ballistica::core { -void LowLevelPythonDebugLog(const char* msg) { +static void PythonLowLevelDebugLog_(const char* msg) { assert(g_core); - g_core->platform->DebugLog(msg); + g_core->platform->LowLevelDebugLog(msg); } static void CheckPyInitStatus(const char* where, const PyStatus& status) { @@ -28,7 +28,7 @@ void CorePython::InitPython() { // Install our low level logger in our custom Python builds. #ifdef PY_HAVE_BALLISTICA_LOW_LEVEL_DEBUG_LOG - Py_BallisticaLowLevelDebugLog = LowLevelPythonDebugLog; + Py_BallisticaLowLevelDebugLog = PythonLowLevelDebugLog_; #endif // Flip on some extra runtime debugging options in debug builds. diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc index 5daf266c..72cfdbd2 100644 --- a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc +++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc @@ -106,14 +106,14 @@ void SceneV1AppMode::OnAppShutdown() { connections_->Shutdown(); } -void SceneV1AppMode::OnAppPause() { +void SceneV1AppMode::OnAppSuspend() { assert(g_base->InLogicThread()); // App is going into background or whatnot. Kill any sockets/etc. EndHostScanning(); } -void SceneV1AppMode::OnAppResume() { assert(g_base->InLogicThread()); } +void SceneV1AppMode::OnAppUnsuspend() { assert(g_base->InLogicThread()); } // Note: for now we're making our host-scan network calls directly from the // logic thread. This is generally not a good idea since it appears that even diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.h b/src/ballistica/scene_v1/support/scene_v1_app_mode.h index 0d9bd6f7..a3310535 100644 --- a/src/ballistica/scene_v1/support/scene_v1_app_mode.h +++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.h @@ -148,8 +148,8 @@ class SceneV1AppMode : public base::AppMode { auto IsPlayerBanned(const PlayerSpec& spec) -> bool; void BanPlayer(const PlayerSpec& spec, millisecs_t duration); void OnAppStart() override; - void OnAppPause() override; - void OnAppResume() override; + void OnAppSuspend() override; + void OnAppUnsuspend() override; auto InClassicMainMenuSession() const -> bool override; auto CreateInputDeviceDelegate(base::InputDevice* device) -> base::InputDeviceDelegate* override; diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index bb2af3c5..22310ad3 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21639; +const int kEngineBuildNumber = 21652; const char* kEngineVersion = "1.7.30"; const int kEngineApiVersion = 8; diff --git a/src/ballistica/shared/foundation/logging.cc b/src/ballistica/shared/foundation/logging.cc index 9e84b713..64ccfc74 100644 --- a/src/ballistica/shared/foundation/logging.cc +++ b/src/ballistica/shared/foundation/logging.cc @@ -38,7 +38,7 @@ void Logging::V1CloudLog(const std::string& msg) { if (g_core) { // (ship to things like Crashlytics crash-logging) - g_core->platform->DebugLog(msg); + g_core->platform->LowLevelDebugLog(msg); // Add to our complete v1-cloud-log. std::scoped_lock lock(g_core->v1_cloud_log_mutex); diff --git a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc index b071343e..0fd6279f 100644 --- a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc +++ b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc @@ -2416,9 +2416,9 @@ static auto PyShowAd(PyObject* self, PyObject* args, PyObject* keywds) static_cast(pass_actually_showed)); // In cases where we support ads, store our callback and kick one off. - // We'll then fire our callback once its done. - // If we *don't* support ads, just store our callback and then kick off - // an ad-view-complete message ourself so the event flow is similar.. + // We'll then fire our callback once its done. If we *don't* support ads, + // just store our callback and then kick off an ad-view-complete message + // ourself so the event flow is similar.. if (g_core->platform->GetHasAds()) { g_core->platform->ShowAd(purpose); } else { diff --git a/src/ballistica/ui_v1/widget/text_widget.cc b/src/ballistica/ui_v1/widget/text_widget.cc index 67474f50..c6c393a7 100644 --- a/src/ballistica/ui_v1/widget/text_widget.cc +++ b/src/ballistica/ui_v1/widget/text_widget.cc @@ -688,13 +688,14 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool { // If we're doing inline editing, handle clipboard paste. if (editable() && !ShouldUseStringEditor_() && m.type == base::WidgetMessage::Type::kPaste) { - if (g_base->app_adapter->ClipboardIsSupported()) { - if (g_base->app_adapter->ClipboardHasText()) { + if (g_base->ClipboardIsSupported()) { + if (g_base->ClipboardHasText()) { // Just enter it char by char as if we had typed it... - AddCharsToText_(g_base->app_adapter->ClipboardGetText()); + AddCharsToText_(g_base->ClipboardGetText()); } } } + // If we're doing inline editing, handle some key events. if (editable() && m.has_keysym && !ShouldUseStringEditor_()) { last_carat_change_time_millisecs_ = diff --git a/tools/efrotools/openalbuild.py b/tools/efrotools/openalbuild.py index 7d00ed80..9edeaf79 100644 --- a/tools/efrotools/openalbuild.py +++ b/tools/efrotools/openalbuild.py @@ -39,7 +39,7 @@ def build_openal(arch: str, mode: str) -> None: if mode not in MODES: raise CleanError(f"Invalid mode '{mode}'.") - enable_oboe = True + # enable_oboe = True # Get ndk path. ndk_path = ( @@ -51,9 +51,8 @@ def build_openal(arch: str, mode: str) -> None: .stdout.decode() .strip() ) - # os.environ['NDK_ROOT'] = ndk_path - # Grab from git and build. + # Grab OpenALSoft builddir = _build_dir(arch, mode) subprocess.run(['rm', '-rf', builddir], check=True) subprocess.run(['mkdir', '-p', os.path.dirname(builddir)], check=True) @@ -63,25 +62,20 @@ def build_openal(arch: str, mode: str) -> None: ) subprocess.run(['git', 'checkout', '1.23.1'], check=True, cwd=builddir) - if enable_oboe: - builddir_oboe = f'{builddir}_oboe' - subprocess.run(['rm', '-rf', builddir_oboe], check=True) - subprocess.run( - ['mkdir', '-p', os.path.dirname(builddir_oboe)], check=True - ) - subprocess.run( - [ - 'git', - 'clone', - 'https://github.com/google/oboe', - builddir_oboe, - ], - check=True, - ) - subprocess.run( - ['git', 'checkout', '1.8.0'], check=True, cwd=builddir_oboe - ) - print(f'FULLY GOT {builddir_oboe}') + # Grab Oboe + builddir_oboe = f'{builddir}_oboe' + subprocess.run(['rm', '-rf', builddir_oboe], check=True) + subprocess.run(['mkdir', '-p', os.path.dirname(builddir_oboe)], check=True) + subprocess.run( + [ + 'git', + 'clone', + 'https://github.com/google/oboe', + builddir_oboe, + ], + check=True, + ) + subprocess.run(['git', 'checkout', '1.8.0'], check=True, cwd=builddir_oboe) # One bit of filtering: by default, openalsoft sends all sorts of # log messages to the android log. This is reasonable since its @@ -106,6 +100,67 @@ def build_openal(arch: str, mode: str) -> None: with open(loggingpath, 'w', encoding='utf-8') as outfile: outfile.write(txt) + # Add a function to set a logging function so we can gather info + # on AL fatal errors/etc. + # fpath = f'{builddir}/alc/alc.cpp' + # with open(fpath, encoding='utf-8') as infile: + # txt = infile.read() + # txt = replace_exact( + # txt, + # 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device)\n', + # ( + # 'void (*alcDebugLogger)(const char*) = nullptr;\n' + # '\n' + # 'ALC_API void ALC_APIENTRY' + # ' alcSetDebugLogger(void (*fn)(const char*)) {\n' + # ' alcDebugLogger = fn;\n' + # '}\n' + # '\n' + # 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device)\n' + # ), + # ) + # with open(fpath, 'w', encoding='utf-8') as outfile: + # outfile.write(txt) + + # fpath = f'{builddir}/include/AL/alc.h' + # with open(fpath, encoding='utf-8') as infile: + # txt = infile.read() + # txt = replace_exact( + # txt, + # 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device);\n', + # 'ALC_API ALCenum ALC_APIENTRY alcGetError(ALCdevice *device);\n' + # 'ALC_API void ALC_APIENTRY alcSetDebugLogger(' + # 'void (*fn)(const char*));\n', + # ) + # with open(fpath, 'w', encoding='utf-8') as outfile: + # outfile.write(txt) + + fpath = f'{builddir}/core/except.h' + with open(fpath, encoding='utf-8') as infile: + txt = infile.read() + txt = replace_exact( + txt, + '#define END_API_FUNC catch(...) { std::terminate(); }\n', + '#define END_API_FUNC\n', + ) + txt = replace_exact( + txt, '#define START_API_FUNC try\n', '#define START_API_FUNC\n' + ) + # txt = replace_exact( + # txt, + # '#define END_API_FUNC catch(...) { std::terminate(); }\n', + # 'extern void (*alcDebugLogger)(const char*);\n' + # '\n' + # '#define END_API_FUNC catch(...) { \\\n' + # ' if (alcDebugLogger != nullptr) { \\\n' + # ' alcDebugLogger("UNKNOWN OpenALSoft fatal exception."); \\\n' + # ' } \\\n' + # ' std::terminate(); \\\n' + # '}\n' + # ) + with open(fpath, 'w', encoding='utf-8') as outfile: + outfile.write(txt) + android_platform = 23 subprocess.run( From 1c2cb0aecb1e529a361cc5919162fbc5db24b527 Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 2 Dec 2023 09:11:15 -0800 Subject: [PATCH 03/10] bug fixes --- .efrocachemap | 56 +++++++++---------- CHANGELOG.md | 2 +- src/assets/ba_data/python/baenv.py | 2 +- src/ballistica/shared/ballistica.cc | 2 +- .../ui_v1/widget/container_widget.cc | 10 +++- 5 files changed, 39 insertions(+), 33 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index 8b2edc16..0e663c7d 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4060,26 +4060,26 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "84b6be9d4a7d8a544993006751c7f632", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "f87ec55a2f3732de69bd7ca56366ac5e", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cc88073fd308bca9e681fa585cc8f534", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a671348d891eb53c6882047f270c46ed", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "667d5118d18ec5e9e822224868d24380", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "f49b0ac8dc49ef622c2da81de1134425", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "b38fb26bbcc45d9ac3990a4b4b2cec0a", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "3100977be9a4cb9271d1ee60d00afd87", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1bd4a1960660c6f1b7c355de6d3d8658", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "3674cde02fbe0059d3f7281344970d16", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "384f40a3d4652323c218f1dfa650ec17", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "cdf0902eeacf4667ddae3b489fb9dd38", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "283aaa1498e719442445c6e24297d5f9", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "7b0facb90fc8b2918cb124c0dcd360af", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "981d0be875bb37e94840040ff41cf52e", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f850af922341d32d237c249daef4383c", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "d3a3e7fb95fc7fbe586646134060d4e2", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "f1a9d399fa0313ee35797eec3b6a465a", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "031e6fd40465f5b8d9f6390efcb7e82d", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "4c8f3095b6a700d1c9a73aabaee98e63", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "dc1e98f8253f6e61583becda21c73162", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "c4e955cd85bc493fc03af74ce1062c66", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "29c7e4a4accdb87c354e6181780654d4", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "1e254a7d528cbaa33cf5bce8e8c4fea7", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "5b6792f0cadb52d82e483981b72def92", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "d5bdea21bb4fe28e55de0dae42b05c09", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "271c8808879bc09181f02c95787f6818", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "40fe07bb55d239e1eabdc0976b46112d", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "ae0f96fc3364de8f6721d12e775e0ab2", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "e2650e5017b27e84c398b894c768dee6", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "1a5d42f10fdd26ff5e81b07afa67c66e", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "940c21f7293968fe88023e33607d36f2", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "31f56d98b67c8894a2dfe73f4d4ed4c8", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "7b316468903c66cab07b0d8ae38b7bb7", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "42cd00bfbe63f750e9a064900582df48", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "045c0b16bf8ff0be028cefe5cfae1ee6", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "588711c1bb3cf986506b0a6a3d00fed9", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "a626549c279d83295e117835cc15050e", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "207ea740413855ede15da4ba1f304cf3", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "8516e63b6129b872ca59cdf5fade1c45", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "e3c41c240bb333fc53240f64d6e9583e", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587", @@ -4096,14 +4096,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "a3124c863c4b80de5acc20a7c9d49492", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "9eba00c4b0b0e11a16d8396f26d34868", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "fb9041aadad56623332d3fa97684784e", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "34c36a14ff8a8d7661f923cc8f1149c9", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "e87159408ee82988c641c963cb53c4e3", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "569e1aac13b5bb8abd7df8e91ecc7554", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "d85d2e4cbcaf1c5fe2a3c984d35a4ed8", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "5a33774c2533bfd695facb4a68767bb8", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "87890c51eb8a6eab5e1847bf19e1c1ba", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "155cfc2d5a62f02ab4490b24afe241e2", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9c5b8303df63e5bdde524ae4ce654fa8", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6895c7a2714c09e065ba6ac1f3860501", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "a0a6f9d1afaacc9c762da68df3f9178c", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "0ce476258657733e63c21be416d35574", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "e23b83abb5efcb2566e003be2c6a4b6e", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "364dd18a7b830ec1600827734c8ac0e6", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "ab27a9c76a3706803283e09a43ecd92b", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f", diff --git a/CHANGELOG.md b/CHANGELOG.md index b86b15f7..6d150ebe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.30 (build 21652, api 8, 2023-12-01) +### 1.7.30 (build 21657, api 8, 2023-12-02) - Continued work on the big 1.7.28 update. - Got the Android version back up and running. There's been lots of cleanup and simplification on the Android layer, cleaning out years of cruft. This should diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 253fb286..e03ef1cb 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21652 +TARGET_BALLISTICA_BUILD = 21657 TARGET_BALLISTICA_VERSION = '1.7.30' diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 22310ad3..29976311 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21652; +const int kEngineBuildNumber = 21657; const char* kEngineVersion = "1.7.30"; const int kEngineApiVersion = 8; diff --git a/src/ballistica/ui_v1/widget/container_widget.cc b/src/ballistica/ui_v1/widget/container_widget.cc index 4f205dbd..651c9866 100644 --- a/src/ballistica/ui_v1/widget/container_widget.cc +++ b/src/ballistica/ui_v1/widget/container_widget.cc @@ -1158,17 +1158,23 @@ void ContainerWidget::SetStartButton(ButtonWidget* button) { } static auto _IsTransitionOut(ContainerWidget::TransitionType type) { + // Note: framing this without a 'default:' so we get compiler warnings + // when enums are added/removed. + bool val = false; switch (type) { case ContainerWidget::TransitionType::kUnset: case ContainerWidget::TransitionType::kInLeft: case ContainerWidget::TransitionType::kInRight: case ContainerWidget::TransitionType::kInScale: - return false; + val = false; + break; case ContainerWidget::TransitionType::kOutLeft: case ContainerWidget::TransitionType::kOutRight: case ContainerWidget::TransitionType::kOutScale: - return true; + val = true; + break; } + return val; } void ContainerWidget::SetTransition(TransitionType t) { From 8a8f38f5bca403ad7c5f8db7754e0874ede5548a Mon Sep 17 00:00:00 2001 From: Era <100019405+EraOSBeta@users.noreply.github.com> Date: Tue, 5 Dec 2023 09:37:10 +0330 Subject: [PATCH 04/10] Added CD --- .github/workflows/cd.yml | 178 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 .github/workflows/cd.yml diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml new file mode 100644 index 00000000..ebeea7ef --- /dev/null +++ b/.github/workflows/cd.yml @@ -0,0 +1,178 @@ +name: CD + +on: + # Run on pushes and pull-requests + push: + pull_request: + +jobs: + make_linux_x86_64_gui_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-gui-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: linux_x86_64_gui_(debug) + path: build/prefab/full/linux_x86_64_gui + make_linux_x86_64_server_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-server-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: linux_x86_64_server_(debug) + path: build/prefab/full/linux_x86_64_server + make_linux_arm64_gui_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-linux-arm64-gui-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: linux_arm64_gui_(debug) + path: build/prefab/full/linux_arm64_gui + make_linux_arm64_server_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-linux-arm64-server-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: linux_arm64_server_(debug) + path: build/prefab/full/linux_arm64_server + make_mac_x86_64_gui_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-mac-x86-64-gui-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: mac_x86_64_gui_(debug) + path: build/prefab/full/mac_x86_64_gui + make_mac_x86_64_server_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-mac-x86-64-server-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: mac_x86_64_server_(debug) + path: build/prefab/full/mac_x86_64_server + make_mac_arm64_gui_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-mac-arm64-gui-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: mac_arm64_gui_(debug) + path: build/prefab/full/mac_arm64_gui + make_mac_arm64_server_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-mac-arm64-server-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: mac_arm64_server_(debug) + path: build/prefab/full/mac_arm64_server + make_windows_x86_gui_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-windows-x86-gui-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: windows_x86_gui_(debug) + path: build/prefab/full/windows_x86_gui + make_windows_x86_server_debug_build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + - name: Install pip requirements + run: tools/pcommand install_pip_reqs + - name: Make the build + run: make prefab-windows-x86-server-debug-build + - name: Upload the build + uses: actions/upload-artifact@v3 + with: + name: windows_x86_server_(debug) + path: build/prefab/full/windows_x86_server From a54614e3f97a2deefbbb47e131f0a9eb77b8add0 Mon Sep 17 00:00:00 2001 From: Era <100019405+EraOSBeta@users.noreply.github.com> Date: Tue, 5 Dec 2023 10:22:59 +0330 Subject: [PATCH 05/10] CI will kill me one day --- config/spinoffconfig.py | 1 + 1 file changed, 1 insertion(+) diff --git a/config/spinoffconfig.py b/config/spinoffconfig.py index 21b06551..5ed00b2e 100644 --- a/config/spinoffconfig.py +++ b/config/spinoffconfig.py @@ -195,6 +195,7 @@ ctx.filter_file_names = { '.projectile', '.editorconfig', 'ci.yml', + 'cd.yml', 'LICENSE', 'cloudtool', 'bacloud', From 5ef968f25bb63c159b16b34eec009da0fcb175c5 Mon Sep 17 00:00:00 2001 From: Era <100019405+EraOSBeta@users.noreply.github.com> Date: Tue, 5 Dec 2023 10:25:18 +0330 Subject: [PATCH 06/10] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4550a942..20fddc2a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ height="50" alt="logo"> ***-ica***: collection of things relating to a specific theme. -[![](https://github.com/efroemling/ballistica/actions/workflows/ci.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/ci.yml) +[![](https://github.com/efroemling/ballistica/actions/workflows/ci.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/ci.yml) [![](https://github.com/efroemling/ballistica/actions/workflows/cd.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/cd.yml) The Ballistica project is the foundation for [BombSquad](https://www.froemling.net/apps/bombsquad) and potentially other @@ -52,7 +52,7 @@ want to keep that spirit alive as the Ballistica project moves forward. Whether this means making it easier to share mods, organize tournaments, join up with friends, teach each other some Python, or whatever else. Life is short; let's play some games. Or make them. Maybe both. - + ### Frequently Asked Questions * **Q: What's with this name? Is it BombSquad or Ballistica?** @@ -86,4 +86,4 @@ Playstation / My Toaster??** for more details or the [Ballistica Downloads](https://ballistica.net/downloads) page for early test builds on some platforms. - + From 115fd5eac68d76a447a38c9d6e969e1b14967006 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 8 Dec 2023 08:46:45 -0800 Subject: [PATCH 07/10] v1.7.30 --- .efrocachemap | 88 +++---- CHANGELOG.md | 16 +- .../ba_data/python/babase/_accountv2.py | 14 +- src/assets/ba_data/python/babase/_login.py | 4 +- src/assets/ba_data/python/baenv.py | 2 +- .../python/bascenev1lib/activity/coopscore.py | 2 +- .../ba_data/python/bauiv1/_subsystem.py | 6 +- .../ba_data/python/bauiv1lib/playoptions.py | 5 +- .../python/bauiv1lib/profile/browser.py | 4 +- .../base/app_adapter/app_adapter.cc | 4 +- src/ballistica/base/app_adapter/app_adapter.h | 13 +- src/ballistica/base/audio/al_sys.h | 3 +- src/ballistica/base/audio/audio_server.cc | 232 ++++++++++++------ src/ballistica/base/audio/audio_server.h | 9 +- src/ballistica/base/base.cc | 15 +- .../base/graphics/text/text_group.h | 2 +- src/ballistica/base/input/input.cc | 47 ++-- .../base/input/support/remote_app_server.cc | 13 +- .../base/networking/network_reader.cc | 6 +- .../python/methods/python_methods_misc.cc | 27 ++ src/ballistica/base/ui/ui.cc | 3 - src/ballistica/core/core.h | 4 +- src/ballistica/shared/ballistica.cc | 142 +++++++++-- src/ballistica/shared/ballistica.h | 5 + .../shared/foundation/event_loop.cc | 27 +- src/ballistica/shared/foundation/event_loop.h | 10 +- .../shared/foundation/fatal_error.cc | 35 ++- .../shared/foundation/fatal_error.h | 23 +- src/ballistica/shared/generic/runnable.cc | 14 +- tools/batools/pcommands.py | 1 - tools/efrotools/openalbuild.py | 7 +- 31 files changed, 536 insertions(+), 247 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index 0e663c7d..c94e9ecf 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4060,50 +4060,50 @@ "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": "dc1e98f8253f6e61583becda21c73162", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "c4e955cd85bc493fc03af74ce1062c66", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "29c7e4a4accdb87c354e6181780654d4", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "1e254a7d528cbaa33cf5bce8e8c4fea7", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "5b6792f0cadb52d82e483981b72def92", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "d5bdea21bb4fe28e55de0dae42b05c09", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "271c8808879bc09181f02c95787f6818", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "40fe07bb55d239e1eabdc0976b46112d", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "ae0f96fc3364de8f6721d12e775e0ab2", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "e2650e5017b27e84c398b894c768dee6", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "1a5d42f10fdd26ff5e81b07afa67c66e", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "940c21f7293968fe88023e33607d36f2", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "31f56d98b67c8894a2dfe73f4d4ed4c8", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "7b316468903c66cab07b0d8ae38b7bb7", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "42cd00bfbe63f750e9a064900582df48", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "045c0b16bf8ff0be028cefe5cfae1ee6", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "588711c1bb3cf986506b0a6a3d00fed9", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "a626549c279d83295e117835cc15050e", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "207ea740413855ede15da4ba1f304cf3", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "8516e63b6129b872ca59cdf5fade1c45", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "e3c41c240bb333fc53240f64d6e9583e", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "e3c41c240bb333fc53240f64d6e9583e", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "0b0ec8be8c575beba2935fdc9aa03ce5", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "04c21c4226944f71230420f9b399d1e4", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "0b0ec8be8c575beba2935fdc9aa03ce5", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "04c21c4226944f71230420f9b399d1e4", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "e80ffa41dcc78dbd75baf89fadb096b4", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "6514628d8ce1c046726de69fa0086613", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "e80ffa41dcc78dbd75baf89fadb096b4", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "6514628d8ce1c046726de69fa0086613", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "9f4f5c5043d66fa3bdeb16b0599b5de4", - "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc", - "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "a3124c863c4b80de5acc20a7c9d49492", - "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "155cfc2d5a62f02ab4490b24afe241e2", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9c5b8303df63e5bdde524ae4ce654fa8", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6895c7a2714c09e065ba6ac1f3860501", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "a0a6f9d1afaacc9c762da68df3f9178c", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "0ce476258657733e63c21be416d35574", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "e23b83abb5efcb2566e003be2c6a4b6e", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "364dd18a7b830ec1600827734c8ac0e6", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "ab27a9c76a3706803283e09a43ecd92b", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "be3e38d0503d272d22a9226323803564", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "feea24dbb10aec67e1aa4d6c1d0928b5", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "ac035cdcd1134ed3eefb50f2b6fee259", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "aa71c8cf97c379aa2cc93b3edb050167", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "afaf8cc0bd7e93439366a43a80debdbc", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "f787ba6f598297a6b43668949af0c8e2", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f8317a770d865a1380bfef13011ae1cc", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "39afba54e9822911d8bfdf53602fcc8d", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "2fe24a441e96e882ce29cd55c886ee8e", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "4d068a5426f7e8814438a295f34d5356", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "e315d748000555f280fd6a0e9575f799", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "753a957f642173a1568725ff0b36bfce", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "833be86309d39cfb7f273c4c72ffdafc", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "49ad9d968f4d615323a840f036caa36f", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "5795639a921b758c08a14ad09a8883d9", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "db31fcbdeeb8f5ab794a1f9f1dfe7ede", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "8aa9aa6b833d64d2e4a3689f6ce134fb", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "7f6119e33678b934ac1c5f8002d8d6bc", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "639a01a7dcfd2f6db3023b610ad44d4a", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e925ad3d39bb366f78c6e0f9da22d0cb", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "4f5d3cafbea651078c1721684b61033a", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "7436c575aee1f9d112d41f18e2ae6b22", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "4f5d3cafbea651078c1721684b61033a", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "7436c575aee1f9d112d41f18e2ae6b22", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "3ffcb35eb71566fefb7d9ad2589b37b4", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "ce9de33efccb9aa1e301fe4f11e6b1c1", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "3ffcb35eb71566fefb7d9ad2589b37b4", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "ce9de33efccb9aa1e301fe4f11e6b1c1", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "9d674b5e8a8357b9462a65359611ea45", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "e6d1c0b9bf27c34968e512c5db8e7de5", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "9d674b5e8a8357b9462a65359611ea45", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "e6d1c0b9bf27c34968e512c5db8e7de5", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "910b5e39fe4ba5bb849505c578efe3ec", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "2d9e14f7cfe50b1dc51e5e9eae05b5fd", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "b83a67eeaed0fc99bf995767a8150e5d", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "2d9e14f7cfe50b1dc51e5e9eae05b5fd", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "8c6063999e8d77daf8bf8a33d045c737", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "e60f8d06f584bac8e54d920776a4d57d", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "89b61adb57d6a53740f302d01e107587", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "f347b87f6ee2167546edec41b750245c", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ed74c7fd0a854650675aa92366c2be3f", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "ac5046013922b1db4689c6ba653791c1", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "52c36bf974594558a33f7c6caa07b81e", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "27e287e1826677d1b4a480820e5e2f3a", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f", diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d150ebe..12c13b0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.30 (build 21657, api 8, 2023-12-02) +### 1.7.30 (build 21693, api 8, 2023-12-08) - Continued work on the big 1.7.28 update. - Got the Android version back up and running. There's been lots of cleanup and simplification on the Android layer, cleaning out years of cruft. This should @@ -15,8 +15,14 @@ - Bundled Android Python has been bumped to version 3.11.6. - Android app suspend behavior has been revamped. The app should stay running more often and be quicker to respond when dialogs or other activities - temporarily pop up in front of it. Please holler if you run into strange side + temporarily pop up in front of it. This also allows it to continue playing + music over other activities such as Google Play Games + Achievements/Leaderboards screens. Please holler if you run into strange side effects such as the app continuing to play audio when it should not be. +- Modernized the Android fullscreen setup code when running in Android 11 or + newer. The game should now use the whole screen area, including the area + around notches or camera cutouts. Please holler if you are seeing any problems + related to this. - (build 21626) Fixed a bug where click/tap locations were incorrect on some builds when tv-border was on (Thanks for the heads-up Loup(Dliwk's fan)!). - (build 21631) Fixes an issue where '^^^^^^^^^^^^^' lines in stack traces could @@ -31,6 +37,12 @@ `from_window`. 2) In any call that can lead to you switching the main menu window, check if your root widget is dead or transitioning out first and abort if it is. See any window in `ui_v1_lib` for examples. +- (build 21691) Fixed a bug causing touches to not register in some cases on + newer Android devices. (Huge thanks to JESWIN A J for helping me track that + down!). +- Temporarily removed the pause-the-game-when-backgrounded behavior for locally + hosted games, mainly due to the code being hacky. Will try to restore this + functionality in a cleaner way soon. ### 1.7.29 (build 21619, api 8, 2023-11-21) diff --git a/src/assets/ba_data/python/babase/_accountv2.py b/src/assets/ba_data/python/babase/_accountv2.py index f9617fcb..5d5d6633 100644 --- a/src/assets/ba_data/python/babase/_accountv2.py +++ b/src/assets/ba_data/python/babase/_accountv2.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: from babase._login import LoginAdapter, LoginInfo -DEBUG_LOG = False +DEBUG_LOG = _babase.temp_testing() class AccountV2Subsystem: @@ -186,9 +186,10 @@ class AccountV2Subsystem: cfgkey = 'ImplicitLoginStates' cfgdict = _babase.app.config.setdefault(cfgkey, {}) - # Store which (if any) adapter is currently implicitly signed in. - # Making the assumption there will only ever be one implicit - # adapter at a time; may need to update this if that changes. + # Store which (if any) adapter is currently implicitly signed + # in. Making the assumption there will only ever be one implicit + # adapter at a time; may need to revisit this logic if that + # changes. prev_state = cfgdict.get(login_type.value) if state is None: self._implicit_signed_in_adapter = None @@ -296,9 +297,8 @@ class AccountV2Subsystem: # Consider this an 'explicit' sign in because the # implicit-login state change presumably was triggered # by some user action (signing in, signing out, or - # switching accounts via the back-end). - # NOTE: should test case where we don't have - # connectivity here. + # switching accounts via the back-end). NOTE: should + # test case where we don't have connectivity here. if plus.cloud.is_connected(): if DEBUG_LOG: logging.debug( diff --git a/src/assets/ba_data/python/babase/_login.py b/src/assets/ba_data/python/babase/_login.py index e8884034..cb7ebbd5 100644 --- a/src/assets/ba_data/python/babase/_login.py +++ b/src/assets/ba_data/python/babase/_login.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: from typing import Callable -DEBUG_LOG = False +DEBUG_LOG = _babase.temp_testing() @dataclass @@ -145,7 +145,7 @@ class LoginAdapter: is actually being used by the app. It should therefore register unlocked achievements, leaderboard scores, allow viewing native UIs, etc. When not active it should ignore everything and behave - as if logged out, even if it technically is still logged in. + as if signed out, even if it technically is still signed in. """ assert _babase.in_logic_thread() del active # Unused. diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index e03ef1cb..3971d9c9 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21657 +TARGET_BALLISTICA_BUILD = 21693 TARGET_BALLISTICA_VERSION = '1.7.30' diff --git a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py index 47e8d2ef..2aff578f 100644 --- a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py +++ b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py @@ -190,7 +190,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]): super().__del__() # If our UI is still up, kill it. - if self._root_ui: + if self._root_ui and not self._root_ui.transitioning_out: with bui.ContextRef.empty(): bui.containerwidget(edit=self._root_ui, transition='out_left') diff --git a/src/assets/ba_data/python/bauiv1/_subsystem.py b/src/assets/ba_data/python/bauiv1/_subsystem.py index b2645faf..3c6b1e77 100644 --- a/src/assets/ba_data/python/bauiv1/_subsystem.py +++ b/src/assets/ba_data/python/bauiv1/_subsystem.py @@ -209,8 +209,12 @@ class UIV1Subsystem(babase.AppSubsystem): def clear_main_menu_window(self, transition: str | None = None) -> None: """Clear any existing 'main' window with the provided transition.""" + assert transition is None or not transition.endswith('_in') if self._main_menu_window: - if transition is not None: + if ( + transition is not None + and not self._main_menu_window.transitioning_out + ): _bauiv1.containerwidget( edit=self._main_menu_window, transition=transition ) diff --git a/src/assets/ba_data/python/bauiv1lib/playoptions.py b/src/assets/ba_data/python/bauiv1lib/playoptions.py index 12fd4e3e..ea58e4d6 100644 --- a/src/assets/ba_data/python/bauiv1lib/playoptions.py +++ b/src/assets/ba_data/python/bauiv1lib/playoptions.py @@ -482,9 +482,12 @@ class PlayOptionsWindow(PopupWindow): cfg['Private Party Host Session Type'] = typename bui.getsound('gunCocking').play() assert bui.app.classic is not None + # Note: this is a wonky situation where we aren't actually + # the main window but we set it on behalf of the main window + # that popped us up. bui.app.ui_v1.set_main_menu_window( GatherWindow(transition='in_right').get_root_widget(), - from_window=self.root_widget, + from_window=False, # Disable this test. ) self._transition_out(transition='out_left') if self._delegate is not None: diff --git a/src/assets/ba_data/python/bauiv1lib/profile/browser.py b/src/assets/ba_data/python/bauiv1lib/profile/browser.py index 2f47251e..38cbbd3c 100644 --- a/src/assets/ba_data/python/bauiv1lib/profile/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/profile/browser.py @@ -257,7 +257,7 @@ class ProfileBrowserWindow(bui.Window): EditProfileWindow( existing_profile=None, in_main_menu=self._in_main_menu ).get_root_widget(), - from_window=self._root_widget, + from_window=self._root_widget if self._in_main_menu else False, ) def _delete_profile(self) -> None: @@ -323,7 +323,7 @@ class ProfileBrowserWindow(bui.Window): EditProfileWindow( self._selected_profile, in_main_menu=self._in_main_menu ).get_root_widget(), - from_window=self._root_widget, + from_window=self._root_widget if self._in_main_menu else False, ) def _select(self, name: str, index: int) -> None: diff --git a/src/ballistica/base/app_adapter/app_adapter.cc b/src/ballistica/base/app_adapter/app_adapter.cc index fd409b94..2c288168 100644 --- a/src/ballistica/base/app_adapter/app_adapter.cc +++ b/src/ballistica/base/app_adapter/app_adapter.cc @@ -151,8 +151,6 @@ void AppAdapter::NativeReviewRequest() { void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); } -auto AppAdapter::ShouldSilenceAudioWhenInactive() -> bool const { - return false; -} +auto AppAdapter::ShouldSilenceAudioForInactive() -> bool const { return false; } } // namespace ballistica::base diff --git a/src/ballistica/base/app_adapter/app_adapter.h b/src/ballistica/base/app_adapter/app_adapter.h index a73b74b6..8db58c7e 100644 --- a/src/ballistica/base/app_adapter/app_adapter.h +++ b/src/ballistica/base/app_adapter/app_adapter.h @@ -135,11 +135,14 @@ class AppAdapter { /// Return whether this AppAdapter supports max-fps controls for its display. virtual auto SupportsMaxFPS() -> bool const; - /// Return whether audio should be silenced when the app is inactive. - /// On Desktop systems it is generally normal to continue to hear things - /// even if their windows are hidden, but on mobile we probably want to - /// silence our audio when phone calls, ads, etc. pop up over it. - virtual auto ShouldSilenceAudioWhenInactive() -> bool const; + /// Return whether audio should be silenced when the app goes inactive. On + /// Desktop systems it is generally normal to continue to hear things even + /// if their windows are hidden, but on mobile we probably want to silence + /// our audio when phone calls, ads, etc. pop up over it. Note that this + /// is called each time the app goes inactive, so the adapter may choose + /// to selectively silence audio depending on what caused the inactive + /// switch. + virtual auto ShouldSilenceAudioForInactive() -> bool const; /// Return whether this platform supports soft-quit. A soft quit is /// when the app is reset/backgrounded/etc. but remains running in case diff --git a/src/ballistica/base/audio/al_sys.h b/src/ballistica/base/audio/al_sys.h index 475af2db..53b5ef07 100644 --- a/src/ballistica/base/audio/al_sys.h +++ b/src/ballistica/base/audio/al_sys.h @@ -17,7 +17,8 @@ #include #endif -#if BA_OSTYPE_ANDROID +#if BA_OPENAL_IS_SOFT +#define AL_ALEXT_PROTOTYPES #include #endif diff --git a/src/ballistica/base/audio/audio_server.cc b/src/ballistica/base/audio/audio_server.cc index 1d83cd0f..90e8e4ee 100644 --- a/src/ballistica/base/audio/audio_server.cc +++ b/src/ballistica/base/audio/audio_server.cc @@ -28,9 +28,12 @@ namespace ballistica::base { extern std::string g_rift_audio_device_name; #endif -#if BA_OSTYPE_ANDROID +#if BA_OPENAL_IS_SOFT LPALCDEVICEPAUSESOFT alcDevicePauseSOFT{}; LPALCDEVICERESUMESOFT alcDeviceResumeSOFT{}; +LPALCRESETDEVICESOFT alcResetDeviceSOFT{}; +LPALEVENTCALLBACKSOFT alEventCallbackSOFT{}; +LPALEVENTCONTROLSOFT alEventControlSOFT{}; #endif const int kAudioProcessIntervalNormal{500 * 1000}; @@ -108,29 +111,24 @@ class AudioServer::ThreadSource_ : public Object { } private: - bool looping_{}; - std::unique_ptr client_source_; - float fade_{1.0f}; - float gain_{1.0f}; - AudioServer* audio_server_{}; - bool valid_{}; - const Object::Ref* source_sound_{}; int id_{}; - uint32_t play_count_{}; + bool looping_{}; + bool valid_{}; bool is_actually_playing_{}; bool want_to_play_{}; -#if BA_ENABLE_AUDIO - ALuint source_{}; -#endif bool is_streamed_{}; - /// Whether we should be designated as "music" next time we play. bool is_music_{}; - /// Whether currently playing as music. bool current_is_music_{}; - + uint32_t play_count_{}; + float fade_{1.0f}; + float gain_{1.0f}; + std::unique_ptr client_source_; + AudioServer* audio_server_{}; + const Object::Ref* source_sound_{}; #if BA_ENABLE_AUDIO + ALuint source_{}; Object::Ref streamer_; #endif }; // ThreadSource @@ -156,6 +154,22 @@ void AudioServer::OnMainThreadStartApp() { event_loop_->PushCallSynchronous([this] { OnAppStartInThread_(); }); } +#if BA_OPENAL_IS_SOFT +static void ALEventCallback_(ALenum eventType, ALuint object, ALuint param, + ALsizei length, const ALchar* message, + ALvoid* userParam) noexcept { + if (eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT) { + if (g_base->audio_server) { + g_base->audio_server->event_loop()->PushCall( + [] { g_base->audio_server->OnDeviceDisconnected(); }); + } + } else { + Log(LogLevel::kWarning, "Got unexpected OpenAL callback event " + + std::to_string(static_cast(eventType))); + } +} +#endif // BA_OPENAL_IS_SOFT + void AudioServer::OnAppStartInThread_() { assert(g_base->InAudioThread()); @@ -168,7 +182,7 @@ void AudioServer::OnAppStartInThread_() { // Bring up OpenAL stuff. { - const char* al_device_name = nullptr; + const char* al_device_name{}; // On the rift build in vr mode we need to make sure we open the rift audio // device. @@ -212,21 +226,42 @@ void AudioServer::OnAppStartInThread_() { "connected?"); } impl_->alc_context = alcCreateContext(device, nullptr); - BA_PRECONDITION(impl_->alc_context); - BA_PRECONDITION(alcMakeContextCurrent(impl_->alc_context)); + if (!impl_->alc_context) { + FatalError( + "Unable to init audio. Do you have speakers/headphones/etc. " + "connected?"); + } + BA_PRECONDITION_FATAL(impl_->alc_context); + BA_PRECONDITION_FATAL(alcMakeContextCurrent(impl_->alc_context)); CHECK_AL_ERROR; -#if BA_OSTYPE_ANDROID - if (alcIsExtensionPresent(device, "ALC_SOFT_pause_device")) { - alcDevicePauseSOFT = reinterpret_cast( - alcGetProcAddress(device, "alcDevicePauseSOFT")); - BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr); - alcDeviceResumeSOFT = reinterpret_cast( - alcGetProcAddress(device, "alcDeviceResumeSOFT")); - BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr); - } else { - FatalError("ALC_SOFT pause/resume functionality not found."); - } +#if BA_OPENAL_IS_SOFT + // Currently assuming the pause/resume and reset extensions are present. + // if (alcIsExtensionPresent(device, "ALC_SOFT_pause_device")) { + alcDevicePauseSOFT = reinterpret_cast( + alcGetProcAddress(device, "alcDevicePauseSOFT")); + BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr); + alcDeviceResumeSOFT = reinterpret_cast( + alcGetProcAddress(device, "alcDeviceResumeSOFT")); + BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr); + alcResetDeviceSOFT = reinterpret_cast( + alcGetProcAddress(device, "alcResetDeviceSOFT")); + BA_PRECONDITION_FATAL(alcResetDeviceSOFT != nullptr); + alEventCallbackSOFT = reinterpret_cast( + alcGetProcAddress(device, "alEventCallbackSOFT")); + BA_PRECONDITION_FATAL(alEventCallbackSOFT != nullptr); + alEventControlSOFT = reinterpret_cast( + alcGetProcAddress(device, "alEventControlSOFT")); + BA_PRECONDITION_FATAL(alEventControlSOFT != nullptr); + + // Ask to be notified when a device is disconnected. + alEventCallbackSOFT(ALEventCallback_, nullptr); + CHECK_AL_ERROR; + ALenum types[] = {AL_EVENT_TYPE_DISCONNECTED_SOFT}; + alEventControlSOFT(1, types, AL_TRUE); + // } else { + // FatalError("ALC_SOFT pause/resume functionality not found."); + // } #endif } @@ -260,6 +295,7 @@ void AudioServer::OnAppStartInThread_() { // Now make available any stopped sources (should be all of them). UpdateAvailableSources_(); + last_started_playing_time_ = g_core->GetAppTimeSeconds(); #endif // BA_ENABLE_AUDIO } @@ -335,23 +371,27 @@ void AudioServer::SetSuspended_(bool suspend) { #endif // Pause OpenALSoft. -#if BA_OSTYPE_ANDROID +#if BA_OPENAL_IS_SOFT BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr); BA_PRECONDITION_FATAL(impl_ != nullptr && impl_->alc_context != nullptr); auto* device = alcGetContextsDevice(impl_->alc_context); BA_PRECONDITION_FATAL(device != nullptr); try { + g_core->platform->LowLevelDebugLog( + "Calling alcDevicePauseSOFT at " + + std::to_string(g_core->GetAppTimeSeconds())); alcDevicePauseSOFT(device); } catch (const std::exception& e) { - g_core->platform->LowLevelDebugLog( - std::string("EXC pausing alcDevice: ") - + g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " " - + e.what()); - throw; + Log(LogLevel::kError, + "Error in alcDevicePauseSOFT at time " + + std::to_string(g_core->GetAppTimeSeconds()) + + "( playing since " + + std::to_string(last_started_playing_time_) + + "): " + g_core->platform->DemangleCXXSymbol(typeid(e).name()) + + " " + e.what()); } catch (...) { - g_core->platform->LowLevelDebugLog("UNKNOWN EXC pausing alcDevice"); - throw; + Log(LogLevel::kError, "Unknown error in alcDevicePauseSOFT"); } #endif @@ -373,25 +413,28 @@ void AudioServer::SetSuspended_(bool suspend) { #endif #endif -// On android lets tell openal-soft to stop processing. -#if BA_OSTYPE_ANDROID +// With OpenALSoft lets tell openal-soft to resume processing. +#if BA_OPENAL_IS_SOFT BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr); BA_PRECONDITION_FATAL(impl_ != nullptr && impl_->alc_context != nullptr); auto* device = alcGetContextsDevice(impl_->alc_context); BA_PRECONDITION_FATAL(device != nullptr); try { + g_core->platform->LowLevelDebugLog( + "Calling alcDeviceResumeSOFT at " + + std::to_string(g_core->GetAppTimeSeconds())); alcDeviceResumeSOFT(device); } catch (const std::exception& e) { - g_core->platform->LowLevelDebugLog( - std::string("EXC resuming alcDevice: ") - + g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " " - + e.what()); - throw; + Log(LogLevel::kError, + "Error in alcDeviceResumeSOFT at time " + + std::to_string(g_core->GetAppTimeSeconds()) + ": " + + g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " " + + e.what()); } catch (...) { - g_core->platform->LowLevelDebugLog("UNKNOWN EXC resuming alcDevice"); - throw; + Log(LogLevel::kError, "Unknown error in alcDeviceResumeSOFT"); } #endif + last_started_playing_time_ = g_core->GetAppTimeSeconds(); suspended_ = false; #if BA_ENABLE_AUDIO CHECK_AL_ERROR; @@ -478,8 +521,8 @@ void AudioServer::PushSourcePlayCall(uint32_t play_id, // Let's take this opportunity to pass on newly available sources. // This way the more things clients are playing, the more - // tight our source availability checking gets (instead of solely relying on - // our periodic process() calls). + // tight our source availability checking gets (instead of solely relying + // on our periodic process() calls). UpdateAvailableSources_(); }); } @@ -686,12 +729,58 @@ void AudioServer::UpdateMusicPlayState_() { } } +void AudioServer::ProcessDeviceDisconnects_(seconds_t real_time_seconds) { +#if BA_OPENAL_IS_SOFT + // If our device has been disconnected, try to reconnect it + // periodically. + auto* device = alcGetContextsDevice(impl_->alc_context); + BA_PRECONDITION_FATAL(device != nullptr); + ALCint connected{-1}; + alcGetIntegerv(device, ALC_CONNECTED, sizeof(connected), &connected); + CHECK_AL_ERROR; + if (connected == 0 && real_time_seconds - last_reset_attempt_time_ > 10.0) { + Log(LogLevel::kInfo, "OpenAL device disconnected; resetting..."); + last_reset_attempt_time_ = real_time_seconds; + BA_PRECONDITION_FATAL(alcResetDeviceSOFT != nullptr); + alcResetDeviceSOFT(device, nullptr); + CHECK_AL_ERROR; + + // Make noise if this ever fails to bring the device back. + ALCint connected{-1}; + alcGetIntegerv(device, ALC_CONNECTED, sizeof(connected), &connected); + CHECK_AL_ERROR; + + // If we were successful, don't require a wait for the next reset. + // (otherwise plugging in headphones and then unplugging will stay quiet + // for 10 seconds). + if (connected == 1) { + last_reset_attempt_time_ = -999.0; + } + + if (connected == 0 && !reported_reset_fail_) { + reported_reset_fail_ = true; + Log(LogLevel::kError, "alcResetDeviceSOFT failed to reconnect device."); + } + } +#endif // BA_OPENAL_IS_SOFT +} + +void AudioServer::OnDeviceDisconnected() { + assert(g_base->InAudioThread()); + // All we do here is run an explicit Process_. This only saves us a half + // second or so over letting the timer do it, but hey we'll take it. + Process_(); +} + void AudioServer::Process_() { assert(g_base->InAudioThread()); - millisecs_t real_time = g_core->GetAppTimeMillisecs(); + seconds_t real_time_seconds = g_core->GetAppTimeSeconds(); + millisecs_t real_time_millisecs = real_time_seconds * 1000; // Only do real work if we're in normal running mode. if (!suspended_ && !shutting_down_) { + ProcessDeviceDisconnects_(real_time_seconds); + // Do some loading... have_pending_loads_ = g_base->assets->RunPendingAudioLoads(); @@ -699,29 +788,28 @@ void AudioServer::Process_() { UpdateAvailableSources_(); // Update our fading sound volumes. - if (real_time - last_sound_fade_process_time_ > 50) { + if (real_time_millisecs - last_sound_fade_process_time_ > 50) { ProcessSoundFades_(); - last_sound_fade_process_time_ = real_time; + last_sound_fade_process_time_ = real_time_millisecs; } // Update streaming sources. - if (real_time - last_stream_process_time_ > 100) { - last_stream_process_time_ = real_time; + if (real_time_millisecs - last_stream_process_time_ > 100) { + last_stream_process_time_ = real_time_millisecs; for (auto&& i : streaming_sources_) { i->Update(); } } - // If the app has switched active/inactive state, update - // our volumes (we may silence our audio in these cases). + // If the app has switched active/inactive state, update our volumes (we + // may silence our audio in these cases). auto app_active = g_base->app_active(); if (app_active != app_active_) { app_active_ = app_active; - if (g_base->app_adapter->ShouldSilenceAudioWhenInactive()) { - app_active_volume_ = app_active ? 1.0f : 0.0f; - } else { - app_active_volume_ = 1.0f; - } + app_active_volume_ = + (!app_active && g_base->app_adapter->ShouldSilenceAudioForInactive()) + ? 0.0f + : 1.0f; for (auto&& i : sources_) { i->UpdateVolume(); } @@ -797,7 +885,8 @@ void AudioServer::ProcessSoundFades_() { } void AudioServer::FadeSoundOut(uint32_t play_id, uint32_t time) { - // Pop a new node on the list (this won't overwrite the old if there is one). + // Pop a new node on the list (this won't overwrite the old if there is + // one). sound_fade_nodes_.insert( std::make_pair(play_id, SoundFadeNode_(play_id, time, true))); } @@ -882,8 +971,8 @@ void AudioServer::ThreadSource_::UpdateAvailability() { assert(g_base->InAudioThread()); - // If it's waiting to be picked up by a client or has pending client commands, - // skip. + // If it's waiting to be picked up by a client or has pending client + // commands, skip. if (!client_source_->TryLock(6)) { return; } @@ -895,10 +984,9 @@ void AudioServer::ThreadSource_::UpdateAvailability() { } // We consider ourselves busy if there's an active looping play command - // (regardless of its actual physical play state - music could be turned off, - // stuttering, etc.). - // If it's non-looping, we check its play state and snatch it if it's not - // playing. + // (regardless of its actual physical play state - music could be turned + // off, stuttering, etc.). If it's non-looping, we check its play state and + // snatch it if it's not playing. bool busy; if (looping_ || (is_streamed_ && streamer_.Exists() && streamer_->loops())) { busy = want_to_play_; @@ -1244,16 +1332,6 @@ void AudioServer::PushSetSoundPitchCall(float val) { event_loop()->PushCall([this, val] { SetSoundPitch_(val); }); } -// void AudioServer::PushSetSuspendedCall(bool suspend) { -// event_loop()->PushCall([this, suspend] { -// if (g_buildconfig.ostype_android()) { -// Log(LogLevel::kError, "Shouldn't be getting SetSuspendedCall on -// android."); -// } -// SetSuspended_(suspend); -// }); -// } - void AudioServer::PushComponentUnloadCall( const std::vector*>& components) { event_loop()->PushCall([components] { diff --git a/src/ballistica/base/audio/audio_server.h b/src/ballistica/base/audio/audio_server.h index e1d658d3..d9919c0a 100644 --- a/src/ballistica/base/audio/audio_server.h +++ b/src/ballistica/base/audio/audio_server.h @@ -67,6 +67,8 @@ class AudioServer { auto event_loop() const -> EventLoop* { return event_loop_; } + void OnDeviceDisconnected(); + private: class ThreadSource_; struct Impl_; @@ -90,6 +92,7 @@ class AudioServer { void Reset_(); void Process_(); + void ProcessDeviceDisconnects_(seconds_t real_time_seconds); /// Send a component to the audio thread to delete. // void DeleteAssetComponent_(Asset* c); @@ -122,7 +125,11 @@ class AudioServer { bool suspended_{}; bool shutdown_completed_{}; bool shutting_down_{}; + bool reported_reset_fail_{}; + int al_source_count_{}; + seconds_t last_reset_attempt_time_{-999.0}; seconds_t shutdown_start_time_{}; + seconds_t last_started_playing_time_{}; millisecs_t last_sound_fade_process_time_{}; /// Indexed list of sources. @@ -146,8 +153,6 @@ class AudioServer { // Our list of sound media components to delete via the main thread. std::vector*> sound_ref_delete_list_; - - int al_source_count_{}; }; } // namespace ballistica::base diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index 45dadaf3..45d6c2c7 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -198,6 +198,8 @@ void BaseFeatureSet::StartApp() { BA_PRECONDITION(g_core->InMainThread()); BA_PRECONDITION(g_base); + auto start_time = g_core->GetAppTimeSeconds(); + // Currently limiting this to once per process. BA_PRECONDITION(!called_start_app_); called_start_app_ = true; @@ -248,6 +250,17 @@ void BaseFeatureSet::StartApp() { } g_core->LifecycleLog("start-app end (main thread)"); + + // Make some noise if this takes more than a few seconds. If we pass 5 + // seconds or so we start to trigger App-Not-Responding reports which + // isn't good. + auto duration = g_core->GetAppTimeSeconds() - start_time; + if (duration > 3.0) { + char buffer[128]; + snprintf(buffer, sizeof(buffer), + "StartApp() took too long (%.2lf seconds).", duration); + Log(LogLevel::kWarning, buffer); + } } void BaseFeatureSet::SuspendApp() { @@ -890,8 +903,6 @@ void BaseFeatureSet::ShutdownSuppressDisallow() { shutdown_suppress_disallowed_ = true; } -// auto BaseFeatureSet::GetReturnValue() const -> int { return return_value(); } - void BaseFeatureSet::QuitApp(bool confirm, QuitType quit_type) { // If they want a confirm dialog and we're able to present one, do that. if (confirm && !g_core->HeadlessMode() && !g_base->input->IsInputLocked() diff --git a/src/ballistica/base/graphics/text/text_group.h b/src/ballistica/base/graphics/text/text_group.h index 86f9121b..7a65a8ee 100644 --- a/src/ballistica/base/graphics/text/text_group.h +++ b/src/ballistica/base/graphics/text/text_group.h @@ -87,7 +87,7 @@ class TextGroup : public Object { Object::Ref os_texture_; std::vector> entries_; std::string text_; - bool big_; + bool big_{}; }; } // namespace ballistica::base diff --git a/src/ballistica/base/input/input.cc b/src/ballistica/base/input/input.cc index 05268697..939e1c59 100644 --- a/src/ballistica/base/input/input.cc +++ b/src/ballistica/base/input/input.cc @@ -155,27 +155,24 @@ void Input::AnnounceConnects_() { if (first_print && g_core->GetAppTimeSeconds() < 3.0) { first_print = false; - // Disabling this completely on Android for now; we often get large - // numbers of devices there that aren't actually devices. - bool do_print_initial_counts{!g_buildconfig.ostype_android()}; - // If there's been several connected, just give a number. - if (explicit_bool(do_print_initial_counts)) { - if (newly_connected_controllers_.size() > 1) { - std::string s = - g_base->assets->GetResourceString("controllersDetectedText"); - Utils::StringReplaceOne( - &s, "${COUNT}", - std::to_string(newly_connected_controllers_.size())); - ScreenMessage(s); - } else { - ScreenMessage( - g_base->assets->GetResourceString("controllerDetectedText")); - } + if (newly_connected_controllers_.size() > 1) { + std::string s = + g_base->assets->GetResourceString("controllersDetectedText"); + Utils::StringReplaceOne( + &s, "${COUNT}", std::to_string(newly_connected_controllers_.size())); + ScreenMessage(s); + } else { + ScreenMessage( + g_base->assets->GetResourceString("controllerDetectedText")); } + } else { // If there's been several connected, just give a number. if (newly_connected_controllers_.size() > 1) { + for (auto&& s : newly_connected_controllers_) { + Log(LogLevel::kInfo, "GOT CONTROLLER " + s); + } std::string s = g_base->assets->GetResourceString("controllersConnectedText"); Utils::StringReplaceOne( @@ -193,7 +190,6 @@ void Input::AnnounceConnects_() { g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kGunCock)); } } - newly_connected_controllers_.clear(); } @@ -222,6 +218,14 @@ void Input::AnnounceDisconnects_() { void Input::ShowStandardInputDeviceConnectedMessage_(InputDevice* j) { assert(g_base->InLogicThread()); + + // On Android we never show messages for initial input-devices; we often + // get large numbers of strange virtual devices that aren't actually + // controllers so this is more confusing than helpful. + if (g_buildconfig.ostype_android() && g_core->GetAppTimeSeconds() < 3.0) { + return; + } + std::string suffix; suffix += j->GetPersistentIdentifier(); suffix += j->GetDeviceExtraDescription(); @@ -1239,7 +1243,14 @@ void Input::HandleSmoothMouseScroll_(const Vector2f& velocity, bool momentum) { } void Input::PushMouseMotionEvent(const Vector2f& position) { - assert(g_base->logic->event_loop()); + auto* loop = g_base->logic->event_loop(); + assert(loop); + + // Don't overload it with events if it's stuck. + if (!loop->CheckPushSafety()) { + return; + } + g_base->logic->event_loop()->PushCall( [this, position] { HandleMouseMotion_(position); }); } diff --git a/src/ballistica/base/input/support/remote_app_server.cc b/src/ballistica/base/input/support/remote_app_server.cc index d5f78b95..268cb848 100644 --- a/src/ballistica/base/input/support/remote_app_server.cc +++ b/src/ballistica/base/input/support/remote_app_server.cc @@ -376,8 +376,10 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr, Vector3f(1, 1, 1)); }); g_base->logic->event_loop()->PushCall([] { - g_base->audio->PlaySound( - g_base->assets->SysSound(SysSoundID::kGunCock)); + if (g_base->assets->asset_loads_allowed()) { + g_base->audio->PlaySound( + g_base->assets->SysSound(SysSoundID::kGunCock)); + } }); } clients_[i].in_use = true; @@ -426,9 +428,12 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr, }); g_base->logic->event_loop()->PushCall([] { - g_base->audio->PlaySound( - g_base->assets->SysSound(SysSoundID::kGunCock)); + if (g_base->assets->asset_loads_allowed()) { + g_base->audio->PlaySound( + g_base->assets->SysSound(SysSoundID::kGunCock)); + } }); + std::string utf8 = Utils::GetValidUTF8(clients_[i].display_name, "rsgc1"); clients_[i].joystick_ = Object::NewDeferred( -1, // not an sdl joystick diff --git a/src/ballistica/base/networking/network_reader.cc b/src/ballistica/base/networking/network_reader.cc index fee5e162..0c44e9e1 100644 --- a/src/ballistica/base/networking/network_reader.cc +++ b/src/ballistica/base/networking/network_reader.cc @@ -33,12 +33,10 @@ void NetworkReader::OnAppSuspend() { paused_ = true; } - // Ok now attempt to send a quick ping to ourself to wake us up so we can kill - // our socket. + // It's possible that we get suspended before port is set, so this could + // still be -1. if (port4_ != -1) { PokeSelf_(); - } else { - Log(LogLevel::kError, "NetworkReader port is -1 on pause"); } } diff --git a/src/ballistica/base/python/methods/python_methods_misc.cc b/src/ballistica/base/python/methods/python_methods_misc.cc index 7b25475d..caa96c5f 100644 --- a/src/ballistica/base/python/methods/python_methods_misc.cc +++ b/src/ballistica/base/python/methods/python_methods_misc.cc @@ -1788,6 +1788,32 @@ static PyMethodDef PyNativeReviewRequestDef = { "\n" "(internal)", }; + +// ------------------------------- temp_testing -------------------------------- + +static auto PyTempTesting(PyObject* self) -> PyObject* { + BA_PYTHON_TRY; + + std::string devstr = g_core->platform->GetDeviceName() + " " + + g_core->platform->GetOSVersionString(); + if (devstr == "samsung SM-N950F 7.1.1") { + Py_RETURN_TRUE; + } + Py_RETURN_FALSE; + + BA_PYTHON_CATCH; +} + +static PyMethodDef PyTempTestingDef = { + "temp_testing", // name + (PyCFunction)PyTempTesting, // method + METH_NOARGS, // flags + + "temp_testing() -> bool\n" + "\n" + "(internal)", +}; + // ----------------------------------------------------------------------------- auto PythonMethodsMisc::GetMethods() -> std::vector { @@ -1856,6 +1882,7 @@ auto PythonMethodsMisc::GetMethods() -> std::vector { PyUsingGameCenterDef, PyNativeReviewRequestSupportedDef, PyNativeReviewRequestDef, + PyTempTestingDef, }; } diff --git a/src/ballistica/base/ui/ui.cc b/src/ballistica/base/ui/ui.cc index 96671198..8d306e6d 100644 --- a/src/ballistica/base/ui/ui.cc +++ b/src/ballistica/base/ui/ui.cc @@ -236,9 +236,6 @@ void UI::MainMenuPress_(InputDevice* device) { assert(g_base->InLogicThread()); if (auto* ui_delegate = g_base->ui->delegate()) { ui_delegate->DoHandleDeviceMenuPress(device); - } else { - Log(LogLevel::kWarning, - "UI::MainMenuPress called without ui_v1 present; unexpected."); } } diff --git a/src/ballistica/core/core.h b/src/ballistica/core/core.h index 4c0cb1ca..be60774f 100644 --- a/src/ballistica/core/core.h +++ b/src/ballistica/core/core.h @@ -158,10 +158,10 @@ class CoreFeatureSet { bool v1_cloud_log_full{}; int master_server_source{}; std::vector suspendable_event_loops; - std::mutex v1_cloud_log_mutex; - std::string v1_cloud_log; std::mutex thread_name_map_mutex; std::unordered_map thread_name_map; + std::mutex v1_cloud_log_mutex; + std::string v1_cloud_log; #if BA_DEBUG_BUILD std::mutex object_list_mutex; diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 29976311..c8c8d46d 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21657; +const int kEngineBuildNumber = 21693; const char* kEngineVersion = "1.7.30"; const int kEngineApiVersion = 8; @@ -53,6 +53,8 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { core::BaseSoftInterface* l_base{}; try { + auto time1 = core::CorePlatform::GetCurrentMillisecs(); + // Even at the absolute start of execution we should be able to // reasonably log errors. Set env var BA_CRASH_TEST=1 to test this. if (const char* crashenv = getenv("BA_CRASH_TEST")) { @@ -66,6 +68,8 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { // import it first thing even if we don't explicitly use it. l_core = core::CoreFeatureSet::Import(&core_config); + auto time2 = core::CorePlatform::GetCurrentMillisecs(); + // If a command was passed, simply run it and exit. We want to act // simply as a Python interpreter in that case; we don't do any // environment setup (aside from the bits core does automatically such @@ -90,6 +94,8 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { // those modules get loaded from in the first place. l_core->python->MonolithicModeBaEnvConfigure(); + auto time3 = core::CorePlatform::GetCurrentMillisecs(); + // We need the base feature-set to run a full app but we don't have a hard // dependency to it. Let's see if it's available. l_base = l_core->SoftImportBase(); @@ -97,6 +103,8 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { FatalError("Base module unavailable; can't run app."); } + auto time4 = core::CorePlatform::GetCurrentMillisecs(); + // ------------------------------------------------------------------------- // Phase 2: "The pieces are moving." // ------------------------------------------------------------------------- @@ -113,6 +121,23 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { // until the app exits (or we return from this function and let the // environment do that part). + // Make noise if it takes us too long to get to this point. + auto time5 = core::CorePlatform::GetCurrentMillisecs(); + auto total_duration = time5 - time1; + if (total_duration > 5000) { + auto core_import_duration = time2 - time1; + auto env_config_duration = time3 - time2; + auto base_import_duration = time4 - time3; + auto start_app_duration = time5 - time4; + Log(LogLevel::kWarning, + "MonolithicMain took too long (" + std::to_string(total_duration) + + " ms; " + std::to_string(core_import_duration) + + " core-import, " + std::to_string(env_config_duration) + + " env-config, " + std::to_string(base_import_duration) + + " base-import, " + std::to_string(start_app_duration) + + " start-app)."); + } + if (l_base->AppManagesMainThreadEventLoop()) { // In environments where we control the event loop, do that. l_base->RunAppToCompletion(); @@ -130,20 +155,20 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { std::string error_msg = std::string("Unhandled exception in MonolithicMain(): ") + exc.what(); - // Let the user and/or master-server know we're dying. + // Let the user and/or master-server know what killed us. FatalError::ReportFatalError(error_msg, true); - // Exiting the app via an exception leads to crash reports on various - // platforms. If it seems we're not on an official live build then we'd - // rather just exit cleanly with an error code and avoid polluting crash - // report logs with reports from dev builds. + // Exiting the app via an exception tends to lead to crash reports. If + // it seems we're not on an official live build then we'd rather just + // exit cleanly with an error code and avoid polluting crash report logs + // with reports from dev builds. bool try_to_exit_cleanly = !(l_base && l_base->IsUnmodifiedBlessedBuild()); - // If this is true it means the app is handling things (showing a fatal - // error dialog, etc.) and it's out of our hands. + // If this returns true, it means the app is handling things (showing a + // fatal error dialog, etc.) and it's out of our hands. bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true); - // Do the default thing if it's not been handled. + // If it's not been handled, take the app down ourself. if (!handled) { if (try_to_exit_cleanly) { exit(1); @@ -155,22 +180,95 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int { return 0; } +// A way to do the same as above except in an incremental manner. This can +// be used to avoid app-not-responding reports on slow devices by +// interleaving engine init steps with other event processing. +class IncrementalInitRunner_ { + public: + explicit IncrementalInitRunner_(const core::CoreConfig* config) + : config_(*config) {} + auto Process() -> bool { + if (zombie_) { + return false; + } + try { + switch (step_) { + case 0: + core_ = core::CoreFeatureSet::Import(&config_); + step_++; + return false; + case 1: + core_->python->MonolithicModeBaEnvConfigure(); + step_++; + return false; + case 2: + base_ = core_->SoftImportBase(); + if (!base_) { + FatalError("Base module unavailable; can't run app."); + } + step_++; + return false; + case 3: + base_->StartApp(); + Python::PermanentlyReleaseGIL(); + step_++; + return false; + default: + return true; + } + } catch (const std::exception& exc) { + std::string error_msg = + std::string("Unhandled exception in MonolithicMain(): ") + exc.what(); + + // Let the user and/or master-server know what killed us. + FatalError::ReportFatalError(error_msg, true); + + // Exiting the app via an exception tends to lead to crash reports. If + // it seems we're not on an official live build then we'd rather just + // exit cleanly with an error code and avoid polluting crash report logs + // with reports from dev builds. + bool try_to_exit_cleanly = !(base_ && base_->IsUnmodifiedBlessedBuild()); + + // If this returns true, it means the app is handling things (showing a + // fatal error dialog, etc.) and it's out of our hands. + bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true); + + // If it's not been handled, take the app down ourself. + if (!handled) { + if (try_to_exit_cleanly) { + exit(1); + } else { + throw; // Crash report here we come! + } + } + // Just go into vegetable mode so hopefully the handler can do its + // thing. + zombie_ = true; + return false; + } + } + + private: + int step_{}; + bool zombie_{}; + core::CoreConfig config_; + core::CoreFeatureSet* core_{}; + core::BaseSoftInterface* base_{}; +}; + +static IncrementalInitRunner_* g_incremental_init_runner_{}; + +auto MonolithicMainIncremental(const core::CoreConfig* config) -> bool { + if (g_incremental_init_runner_ == nullptr) { + g_incremental_init_runner_ = new IncrementalInitRunner_(config); + } + return g_incremental_init_runner_->Process(); +} + #endif // BA_MONOLITHIC_BUILD void FatalError(const std::string& message) { - // Let the user and/or master-server know we're dying. - FatalError::ReportFatalError(message, false); - - // Exiting the app via an exception leads to crash reports on various - // platforms. If it seems we're not on an official live build then we'd - // rather just exit cleanly with an error code and avoid polluting crash - // report logs with reports from dev builds. - bool try_to_exit_cleanly = - !(core::g_base_soft && core::g_base_soft->IsUnmodifiedBlessedBuild()); - bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, false); - if (!handled) { - throw Exception("A fatal error occurred."); - } + FatalError::DoFatalError(message); } void Log(LogLevel level, const std::string& msg) { Logging::Log(level, msg); } diff --git a/src/ballistica/shared/ballistica.h b/src/ballistica/shared/ballistica.h index 83734735..17831283 100644 --- a/src/ballistica/shared/ballistica.h +++ b/src/ballistica/shared/ballistica.h @@ -68,6 +68,11 @@ class CoreConfig; /// Entry point for standard monolithic builds. Handles all initing and /// running. auto MonolithicMain(const core::CoreConfig& config) -> int; + +/// Special alternate version of MonolithicMain which breaks its work into +/// pieces; used to reduce app-not-responding reports from slow Android +/// devices. Call this repeatedly until it returns true; +auto MonolithicMainIncremental(const core::CoreConfig* config) -> bool; #endif // BA_MONOLITHIC_BUILD // Print a momentary message on the screen. diff --git a/src/ballistica/shared/foundation/event_loop.cc b/src/ballistica/shared/foundation/event_loop.cc index 24c5ca92..7ed704a6 100644 --- a/src/ballistica/shared/foundation/event_loop.cc +++ b/src/ballistica/shared/foundation/event_loop.cc @@ -342,47 +342,46 @@ void EventLoop::GetThreadMessages_(std::list* messages) { void EventLoop::BootstrapThread_() { assert(!bootstrapped_); thread_id_ = std::this_thread::get_id(); - const char* name; const char* id_string; switch (identifier_) { case EventLoopID::kLogic: - name = "logic"; + name_ = "logic"; id_string = "ballistica logic"; break; case EventLoopID::kStdin: - name = "stdin"; + name_ = "stdin"; id_string = "ballistica stdin"; break; case EventLoopID::kAssets: - name = "assets"; + name_ = "assets"; id_string = "ballistica assets"; break; case EventLoopID::kFileOut: - name = "fileout"; + name_ = "fileout"; id_string = "ballistica file-out"; break; case EventLoopID::kMain: - name = "main"; + name_ = "main"; id_string = "ballistica main"; break; case EventLoopID::kAudio: - name = "audio"; + name_ = "audio"; id_string = "ballistica audio"; break; case EventLoopID::kBGDynamics: - name = "bgdynamics"; + name_ = "bgdynamics"; id_string = "ballistica bg-dynamics"; break; case EventLoopID::kNetworkWrite: - name = "networkwrite"; + name_ = "networkwrite"; id_string = "ballistica network-write"; break; default: throw Exception(); } - assert(name && id_string); - SetInternalThreadName_(name); + assert(!name_.empty() && id_string); + SetInternalThreadName_(name_); // Note: we currently don't do this for our main thread because it // changes the process name we see in top/etc. Should look into that. @@ -552,8 +551,7 @@ void EventLoop::PushThreadMessage_(const ThreadMessage_& t) { if (!sent_error) { sent_error = true; log_entries.emplace_back( - LogLevel::kError, - "ThreadMessage list > 1000 in thread: " + CurrentThreadName()); + LogLevel::kError, "ThreadMessage list > 1000 in thread: " + name_); LogThreadMessageTally_(&log_entries); } @@ -561,8 +559,7 @@ void EventLoop::PushThreadMessage_(const ThreadMessage_& t) { // Prevent runaway mem usage if the list gets out of control. if (thread_messages_.size() > 10000) { - FatalError("ThreadMessage list > 10000 in thread: " - + CurrentThreadName()); + FatalError("ThreadMessage list > 10000 in thread: " + name_); } // Unlock thread-message list and inform thread that there's something diff --git a/src/ballistica/shared/foundation/event_loop.h b/src/ballistica/shared/foundation/event_loop.h index 6b68b51d..186c52de 100644 --- a/src/ballistica/shared/foundation/event_loop.h +++ b/src/ballistica/shared/foundation/event_loop.h @@ -97,6 +97,8 @@ class EventLoop { auto suspended() { return suspended_; } auto done() -> bool { return done_; } + auto name() const { return name_; } + private: struct ThreadMessage_ { enum class Type { kShutdown = 999, kRunnable, kSuspend, kUnsuspend }; @@ -149,13 +151,6 @@ class EventLoop { void BootstrapThread_(); - // void LoopUpkeep_(bool single_cycle); - - // FIXME: Should generalize this to some sort of PlatformThreadData class. -#if BA_XCODE_BUILD - // void* auto_release_pool_{}; -#endif - EventLoopID identifier_{EventLoopID::kInvalid}; ThreadSource source_{}; bool bootstrapped_{}; @@ -173,6 +168,7 @@ class EventLoop { std::mutex thread_message_mutex_; std::mutex client_listener_mutex_; std::list> data_to_client_; + std::string name_; PyThreadState* py_thread_state_{}; TimerList timers_; }; diff --git a/src/ballistica/shared/foundation/fatal_error.cc b/src/ballistica/shared/foundation/fatal_error.cc index 967e6a30..be39d384 100644 --- a/src/ballistica/shared/foundation/fatal_error.cc +++ b/src/ballistica/shared/foundation/fatal_error.cc @@ -15,15 +15,31 @@ namespace ballistica { using core::g_base_soft; using core::g_core; +bool FatalError::reported_{}; + +void FatalError::DoFatalError(const std::string& message) { + // Let the user and/or master-server know we're dying. + ReportFatalError(message, false); + + // In some cases we prefer to cleanly exit the app with an error code + // in a way that won't wind up as a crash report; this avoids polluting + // our crash reports list with stuff from dev builds. + bool try_to_exit_cleanly = + !(core::g_base_soft && core::g_base_soft->IsUnmodifiedBlessedBuild()); + bool handled = HandleFatalError(try_to_exit_cleanly, false); + if (!handled) { + abort(); + } +} + void FatalError::ReportFatalError(const std::string& message, bool in_top_level_exception_handler) { - // We want to report the first fatal error that happens; if further ones - // happen they are probably red herrings. - static bool ran = false; - if (ran) { + // We want to report only the first fatal error that happens; if further + // ones happen they are likely red herrings triggered by the first. + if (reported_) { return; } - ran = true; + reported_ = true; // Our main goal here varies based off whether we are an unmodified // blessed build. If we are, our main goal is to communicate as much info @@ -139,8 +155,9 @@ void FatalError::DoBlockingFatalErrorDialog(const std::string& message) { bool* startedptr{&started}; bool* finishedptr{&finished}; - // If our thread is holding the GIL, release it to give the main - // thread a better chance to get to the point of displaying the fatal error. + // If our thread is holding the GIL, release it to give the main thread + // a better chance of getting to the point of displaying the fatal + // error. if (Python::HaveGIL()) { Python::PermanentlyReleaseGIL(); } @@ -152,7 +169,7 @@ void FatalError::DoBlockingFatalErrorDialog(const std::string& message) { })); // Wait a short amount of time for the main thread to take action. - // There's a chance that it can't (if threads are paused, if it is + // There's a chance that it can't (if threads are suspended, if it is // blocked on a synchronous call to another thread, etc.) so if we don't // see something happening soon, just give up on showing a dialog. auto starttime = core::CorePlatform::GetCurrentMillisecs(); @@ -192,7 +209,7 @@ auto FatalError::HandleFatalError(bool exit_cleanly, } // Otherwise its up to who called us (they might let the caught exception - // bubble up) + // bubble up). return false; } diff --git a/src/ballistica/shared/foundation/fatal_error.h b/src/ballistica/shared/foundation/fatal_error.h index e98fa651..f2f16dae 100644 --- a/src/ballistica/shared/foundation/fatal_error.h +++ b/src/ballistica/shared/foundation/fatal_error.h @@ -9,24 +9,31 @@ namespace ballistica { class FatalError { public: + /// Complete high level level fatal error call; does both reporting and + /// handling. ballistica::FatalError() simply calls this. + static void DoFatalError(const std::string& message); + /// Report a fatal error to the master-server/user/etc. Note that reporting /// only happens for the first invocation of this call; additional calls - /// are no-ops. + /// are no-ops. This is because the process of tearing down the app may + /// trigger additional errors which are red herrings. static void ReportFatalError(const std::string& message, bool in_top_level_exception_handler); - /// Handle a fatal error. This can involve calling exit(), abort(), setting - /// up an asynchronous quit, etc. Returns true if the fatal-error has been - /// handled; otherwise it is up to the caller (this should only be the case - /// when in_top_level_exception_handler is true). - /// Unlike ReportFatalError, the logic in this call can be invoked repeatedly - /// and should be prepared for that possibility in the case of recursive - /// fatal errors/etc. + /// Handle a fatal error. This can involve calling exit(), abort(), + /// setting up an asynchronous quit, etc. Returns true if the fatal-error + /// has been handled; otherwise it is up to the caller (this should only + /// be the case when in_top_level_exception_handler is true). + /// + /// Unlike ReportFatalError, the logic in this call can be invoked + /// repeatedly and should be prepared for that possibility in the case of + /// recursive fatal errors/etc. static auto HandleFatalError(bool clean_exit, bool in_top_level_exception_handler) -> bool; private: static void DoBlockingFatalErrorDialog(const std::string& message); + static bool reported_; }; } // namespace ballistica diff --git a/src/ballistica/shared/generic/runnable.cc b/src/ballistica/shared/generic/runnable.cc index 3f7faa91..f5ebb16f 100644 --- a/src/ballistica/shared/generic/runnable.cc +++ b/src/ballistica/shared/generic/runnable.cc @@ -2,8 +2,13 @@ #include "ballistica/shared/generic/runnable.h" +#include "ballistica/core/core.h" +#include "ballistica/core/platform/core_platform.h" + namespace ballistica { +using core::g_core; + auto Runnable::GetThreadOwnership() const -> Object::ThreadOwnership { return ThreadOwnership::kNextReferencing; } @@ -12,7 +17,14 @@ void Runnable::RunAndLogErrors() { try { Run(); } catch (const std::exception& exc) { - Log(LogLevel::kError, std::string("Error in Runnable: ") + exc.what()); + std::string type_name; + if (g_core != nullptr) { + type_name = g_core->platform->DemangleCXXSymbol(typeid(exc).name()); + } else { + type_name = ""; + } + Log(LogLevel::kError, + std::string("Error in Runnable: " + type_name + ": ") + exc.what()); } } diff --git a/tools/batools/pcommands.py b/tools/batools/pcommands.py index 3e5031f8..8fa3e42e 100644 --- a/tools/batools/pcommands.py +++ b/tools/batools/pcommands.py @@ -718,7 +718,6 @@ def logcat() -> None: raise CleanError('Expected 2 args') adb = sys.argv[2] plat = sys.argv[3] - print('plat is', plat) # My amazon tablet chokes on the color format. if plat == 'amazon': diff --git a/tools/efrotools/openalbuild.py b/tools/efrotools/openalbuild.py index 9edeaf79..8ccb2b4a 100644 --- a/tools/efrotools/openalbuild.py +++ b/tools/efrotools/openalbuild.py @@ -60,7 +60,12 @@ def build_openal(arch: str, mode: str) -> None: ['git', 'clone', 'https://github.com/kcat/openal-soft.git', builddir], check=True, ) - subprocess.run(['git', 'checkout', '1.23.1'], check=True, cwd=builddir) + # subprocess.run(['git', 'checkout', '1.23.1'], check=True, cwd=builddir) + subprocess.run( + ['git', 'checkout', '5b5b948516f7340810ebbfdd5e46eb40f85d2e56'], + check=True, + cwd=builddir, + ) # Grab Oboe builddir_oboe = f'{builddir}_oboe' From c6f0ecf17e36fdc8a3f7afc26d86684c01dae83d Mon Sep 17 00:00:00 2001 From: Eric Date: Sat, 9 Dec 2023 11:45:23 -0800 Subject: [PATCH 08/10] version bump --- .efrocachemap | 56 ++++++++++++++--------------- CHANGELOG.md | 4 ++- src/assets/ba_data/python/baenv.py | 4 +-- src/ballistica/shared/ballistica.cc | 4 +-- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index c94e9ecf..4b8f1691 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4060,26 +4060,26 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "be3e38d0503d272d22a9226323803564", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "feea24dbb10aec67e1aa4d6c1d0928b5", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "ac035cdcd1134ed3eefb50f2b6fee259", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "aa71c8cf97c379aa2cc93b3edb050167", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "afaf8cc0bd7e93439366a43a80debdbc", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "f787ba6f598297a6b43668949af0c8e2", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f8317a770d865a1380bfef13011ae1cc", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "39afba54e9822911d8bfdf53602fcc8d", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "2fe24a441e96e882ce29cd55c886ee8e", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "4d068a5426f7e8814438a295f34d5356", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "e315d748000555f280fd6a0e9575f799", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "753a957f642173a1568725ff0b36bfce", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "833be86309d39cfb7f273c4c72ffdafc", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "49ad9d968f4d615323a840f036caa36f", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "5795639a921b758c08a14ad09a8883d9", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "db31fcbdeeb8f5ab794a1f9f1dfe7ede", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "8aa9aa6b833d64d2e4a3689f6ce134fb", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "7f6119e33678b934ac1c5f8002d8d6bc", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "639a01a7dcfd2f6db3023b610ad44d4a", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e925ad3d39bb366f78c6e0f9da22d0cb", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "8561660678904509458b80bb4b62d8ea", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "f2e7de4723ce8fb6984a5bbd9ac93aa6", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "af1ade2cc275dacc7e1793383d766f3d", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "bb806faa941b6c3c7834383264cf8571", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "648bfaca6487ff13cd27a31e317878f0", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "38501876d5009ecaa936994ec6fbb3a2", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f7d354aded26684974b4d0fbb8725762", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "f6ceb8135fe4fb9aa7fc13fb1b835077", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "402cb0d5a0abecfa9e578f3801b15e59", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "4eae3e6ade00be0224839bfc16351d2c", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c6dd9b6cf876db50aa8d0cc0cc80efa4", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "79d19536ed7de918c947c5095286b8ce", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "409140e2c39941dbc66088417399d5ea", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "1a25fd7e3c5b94a294ce769083b71751", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "7bfbb50681473702f286a50e56277b93", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "37ce7d3b865c0b58161c2d4ccdb54256", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "728dfc083916d7199dee1e75ad12e9fc", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "fbf59de05d7e121d9c5dd164939228dc", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "1eb456e1b1c4d215912809950d938c1a", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "65f507699fed78fcfa12e3e63af2bf8b", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "4f5d3cafbea651078c1721684b61033a", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "7436c575aee1f9d112d41f18e2ae6b22", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "4f5d3cafbea651078c1721684b61033a", @@ -4096,14 +4096,14 @@ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "2d9e14f7cfe50b1dc51e5e9eae05b5fd", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "b83a67eeaed0fc99bf995767a8150e5d", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "2d9e14f7cfe50b1dc51e5e9eae05b5fd", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "8c6063999e8d77daf8bf8a33d045c737", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "e60f8d06f584bac8e54d920776a4d57d", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "89b61adb57d6a53740f302d01e107587", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "f347b87f6ee2167546edec41b750245c", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ed74c7fd0a854650675aa92366c2be3f", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "ac5046013922b1db4689c6ba653791c1", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "52c36bf974594558a33f7c6caa07b81e", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "27e287e1826677d1b4a480820e5e2f3a", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "f6a855e83d7816e73d9859ff9e508190", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "6cd203dce2718e0eded672c83c51506a", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a1f81d6527bba562c3626e520ab8aa2d", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "8b6a8b19f73b67413612185aa2d9ca5d", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "2534af31cf9f8a86ddb7c92b0184f4b2", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "0cf7fb12f97a3f8176462ec5d1ddb992", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4afbbfa78e25e932d9392a1bd6bacc52", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "a0ba1ed33b7a83543b51f24a96941dfa", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f", diff --git a/CHANGELOG.md b/CHANGELOG.md index 12c13b0a..4423000a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,6 @@ -### 1.7.30 (build 21693, api 8, 2023-12-08) +### 1.7.31 (build 21700, api 8, 2023-12-09) + +### 1.7.30 (build 21697, api 8, 2023-12-08) - Continued work on the big 1.7.28 update. - Got the Android version back up and running. There's been lots of cleanup and simplification on the Android layer, cleaning out years of cruft. This should diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 3971d9c9..ba47b57e 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,8 +52,8 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21693 -TARGET_BALLISTICA_VERSION = '1.7.30' +TARGET_BALLISTICA_BUILD = 21700 +TARGET_BALLISTICA_VERSION = '1.7.31' @dataclass diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index c8c8d46d..2dec51aa 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,8 +39,8 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21693; -const char* kEngineVersion = "1.7.30"; +const int kEngineBuildNumber = 21700; +const char* kEngineVersion = "1.7.31"; const int kEngineApiVersion = 8; #if BA_MONOLITHIC_BUILD From 5cf8065c401beb6ff8687470643e0d2489878a50 Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 13 Dec 2023 22:18:42 -0800 Subject: [PATCH 09/10] added bascenev1.get_connection_to_host_info_2() --- .efrocachemap | 100 +++++++-------- .idea/ballisticakit.iml | 1 - CHANGELOG.md | 6 +- ballisticakit-cmake/.idea/misc.xml | 1 - config/spinoffconfig.py | 1 - src/assets/.asset_manifest_public.json | 6 +- src/assets/Makefile | 6 +- src/assets/ba_data/python/babase/__init__.py | 2 - .../ba_data/python/babase/_accountv2.py | 2 +- src/assets/ba_data/python/babase/_appmode.py | 1 + src/assets/ba_data/python/babase/_apputils.py | 11 +- src/assets/ba_data/python/babase/_login.py | 2 +- src/assets/ba_data/python/babase/_meta.py | 35 +++--- src/assets/ba_data/python/baclassic/_ads.py | 4 +- src/assets/ba_data/python/baenv.py | 2 +- .../ba_data/python/baplus/_subsystem.py | 15 +++ .../ba_data/python/bascenev1/__init__.py | 4 + src/assets/ba_data/python/bascenev1/_net.py | 24 ++++ src/assets/ba_data/python/bauiv1/__init__.py | 8 +- .../python/{babase => bauiv1}/_keyboard.py | 0 .../ba_data/python/bauiv1/onscreenkeyboard.py | 17 ++- .../ba_data/python/bauiv1lib/coop/browser.py | 4 +- .../python/bauiv1lib/coop/tournamentbutton.py | 4 +- .../ba_data/python/bauiv1lib/getcurrency.py | 4 +- .../bauiv1lib/keyboard/englishkeyboard.py | 8 +- src/assets/ba_data/python/bauiv1lib/party.py | 10 +- .../python/bauiv1lib/tournamententry.py | 5 +- src/ballistica/base/base.cc | 6 + src/ballistica/base/networking/networking.cc | 2 +- src/ballistica/base/platform/base_platform.cc | 4 +- src/ballistica/core/core.cc | 66 ++++++++-- src/ballistica/core/core.h | 11 +- .../platform/apple/core_platform_apple.cc | 2 +- src/ballistica/core/platform/core_platform.cc | 8 +- .../connection/connection_to_host_udp.cc | 25 ++-- .../connection/connection_to_host_udp.h | 14 ++- .../methods/python_methods_networking.cc | 65 +++++++++- .../scene_v1/python/scene_v1_python.h | 1 + .../scene_v1/support/client_session_net.h | 13 +- src/ballistica/shared/ballistica.cc | 4 +- .../shared/foundation/event_loop.cc | 65 +--------- src/ballistica/shared/foundation/event_loop.h | 5 - .../shared/foundation/fatal_error.cc | 20 +-- src/ballistica/shared/networking/sockaddr.cc | 38 +++++- src/ballistica/shared/networking/sockaddr.h | 49 +++++--- src/ballistica/shared/python/python.cc | 4 +- src/ballistica/shared/python/python.h | 9 +- src/ballistica/shared/python/python_ref.h | 9 +- .../python/methods/python_methods_ui_v1.cc | 87 ------------- .../bascenev1meta/pyembed/binding_scene_v1.py | 2 + tools/bacommon/app.py | 119 ++++++++++++++++-- tools/batools/dummymodule.py | 6 + tools/batools/project/_updater.py | 1 - tools/efrotools/openalbuild.py | 2 +- 54 files changed, 542 insertions(+), 378 deletions(-) create mode 100644 src/assets/ba_data/python/bascenev1/_net.py rename src/assets/ba_data/python/{babase => bauiv1}/_keyboard.py (100%) diff --git a/.efrocachemap b/.efrocachemap index 4b8f1691..375613f0 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -421,18 +421,18 @@ "build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55", - "build/assets/ba_data/data/langdata.json": "c6f94f9c1dc833c537d16672d9018b94", + "build/assets/ba_data/data/langdata.json": "4a35cc51d1021fa7e525123bcf99043c", "build/assets/ba_data/data/languages/arabic.json": "00ba700de6c672a56658a6bd1ad27523", "build/assets/ba_data/data/languages/belarussian.json": "7fe38341815ca6ff4d95224196e7a67e", "build/assets/ba_data/data/languages/chinese.json": "5761468d25f2bd4e79921826cebd572b", "build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078", "build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa", - "build/assets/ba_data/data/languages/czech.json": "93c5fe0d884d95435da6c675f64e30e0", + "build/assets/ba_data/data/languages/czech.json": "cd21ad8c6b8e9ed700284cf1e1aecbf8", "build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e", "build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e", "build/assets/ba_data/data/languages/english.json": "bd43b77b1ccca059573acbde148b4767", "build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880", - "build/assets/ba_data/data/languages/filipino.json": "afbda3adf14555e1567ee63c32e340e7", + "build/assets/ba_data/data/languages/filipino.json": "0f5ad7c06db70027b116dfd9324bdf67", "build/assets/ba_data/data/languages/french.json": "49ff6d211537b8003b8241438dca661d", "build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad", "build/assets/ba_data/data/languages/gibberish.json": "9aae526303a22372fe9b4cf1781520ef", @@ -445,12 +445,12 @@ "build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38", "build/assets/ba_data/data/languages/persian.json": "d742f4a6d3c3555031102b21abdcbb5b", "build/assets/ba_data/data/languages/polish.json": "b9a58b70ed5e99d8b7fa2392b2eb0cda", - "build/assets/ba_data/data/languages/portuguese.json": "556af4e8170356ad239412e1743e20d5", + "build/assets/ba_data/data/languages/portuguese.json": "e3adc6c04486d21e84019a0b03ce11b1", "build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826", "build/assets/ba_data/data/languages/russian.json": "e120993371f52edd2d99f2236188933c", "build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef", - "build/assets/ba_data/data/languages/spanish.json": "80ea58bd3295a0252b7fdac9154aa22f", + "build/assets/ba_data/data/languages/spanish.json": "1d14210b4eefb48130608bd0495b7900", "build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac", "build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c", "build/assets/ba_data/data/languages/thai.json": "77755219bbf5fb7eea0d6b226684f403", @@ -4060,50 +4060,50 @@ "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": "8561660678904509458b80bb4b62d8ea", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "f2e7de4723ce8fb6984a5bbd9ac93aa6", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "af1ade2cc275dacc7e1793383d766f3d", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "bb806faa941b6c3c7834383264cf8571", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "648bfaca6487ff13cd27a31e317878f0", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "38501876d5009ecaa936994ec6fbb3a2", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f7d354aded26684974b4d0fbb8725762", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "f6ceb8135fe4fb9aa7fc13fb1b835077", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "402cb0d5a0abecfa9e578f3801b15e59", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "4eae3e6ade00be0224839bfc16351d2c", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "c6dd9b6cf876db50aa8d0cc0cc80efa4", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "79d19536ed7de918c947c5095286b8ce", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "409140e2c39941dbc66088417399d5ea", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "1a25fd7e3c5b94a294ce769083b71751", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "7bfbb50681473702f286a50e56277b93", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "37ce7d3b865c0b58161c2d4ccdb54256", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "728dfc083916d7199dee1e75ad12e9fc", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "fbf59de05d7e121d9c5dd164939228dc", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "1eb456e1b1c4d215912809950d938c1a", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "65f507699fed78fcfa12e3e63af2bf8b", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "4f5d3cafbea651078c1721684b61033a", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "7436c575aee1f9d112d41f18e2ae6b22", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "4f5d3cafbea651078c1721684b61033a", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "7436c575aee1f9d112d41f18e2ae6b22", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "3ffcb35eb71566fefb7d9ad2589b37b4", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "ce9de33efccb9aa1e301fe4f11e6b1c1", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "3ffcb35eb71566fefb7d9ad2589b37b4", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "ce9de33efccb9aa1e301fe4f11e6b1c1", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "9d674b5e8a8357b9462a65359611ea45", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "e6d1c0b9bf27c34968e512c5db8e7de5", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "9d674b5e8a8357b9462a65359611ea45", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "e6d1c0b9bf27c34968e512c5db8e7de5", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "910b5e39fe4ba5bb849505c578efe3ec", - "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "2d9e14f7cfe50b1dc51e5e9eae05b5fd", - "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "b83a67eeaed0fc99bf995767a8150e5d", - "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "2d9e14f7cfe50b1dc51e5e9eae05b5fd", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "f6a855e83d7816e73d9859ff9e508190", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "6cd203dce2718e0eded672c83c51506a", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a1f81d6527bba562c3626e520ab8aa2d", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "8b6a8b19f73b67413612185aa2d9ca5d", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "2534af31cf9f8a86ddb7c92b0184f4b2", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "0cf7fb12f97a3f8176462ec5d1ddb992", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "4afbbfa78e25e932d9392a1bd6bacc52", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "a0ba1ed33b7a83543b51f24a96941dfa", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "6135aeb242afaf9d1114810a67c89cec", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "bbbbb14d42ed6eb0c5eb56867b7fb870", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cd28f9cc4652736a31c677fc4e5dbaf1", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "239c608cc52c0320210e56ad6abe57a5", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e76d67cacf1393d33796d6b6b1bf1413", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a7eaa8dc4d859ef7a735483b04ccec4a", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "7a2eef42da34a35ddcc2fd7c66843b1b", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "694599ac6a967b2ed383b27bf8093e5b", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "c91cbab6a07affa22e0612210f8b807c", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "d460f7a3909f92d5dbf752e4521a9fbc", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0a0abfe75bc987e7b65a3cfa106e8353", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "8f21405b29f2b2ab01323d711492cca0", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "96dc73e819f41f99a1b2dbb45f79d551", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c79ac51cd2deabb1c2d0acddeaf81c30", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "f06ec14e8c3106be9df91af7da621dc9", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f389f9a7b1afc81f76787722340cfa9c", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "c7dab78aac11cb1430d8456d5d48107a", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "67e29852dfee2e63e179cfebf608ef26", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "9778f8faf91c9993fbf3015bd4554a87", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "73477bd15b9e3834314fd878c9e108d4", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "fb9b8443c1b4cccad749df7d6328220f", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "384fb7fd55ad5a6cdbb662da1ec402ab", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "fb9b8443c1b4cccad749df7d6328220f", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "384fb7fd55ad5a6cdbb662da1ec402ab", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "bc7d0811bcd87156ebf5292a38a1c350", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "bb32f45054b6999300bf8b41d6a4b402", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "bc7d0811bcd87156ebf5292a38a1c350", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "bb32f45054b6999300bf8b41d6a4b402", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "8d9a1505bf397f4902baabed7c1cf438", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "f4d9c115e22dd81e36d1c5baeac8d848", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "8d9a1505bf397f4902baabed7c1cf438", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "f4d9c115e22dd81e36d1c5baeac8d848", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "fb72c92ec6ec0e1c8f4ced32abd86505", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "131aab20cfe77fe89c3f452a855f1e68", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "ee10cdc9f9a861e2be0f1a208c0ca0fe", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "131aab20cfe77fe89c3f452a855f1e68", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "678fabc6dfd6f401ee8942d088ee9181", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "e092d2aed8464a61a623d79ca25308d8", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6b658f49be396ad645c5e57464739a3b", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "9d79a56403a6d806ff131a7de664dfa7", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "e831a26d2c28e862d51e24393d158c99", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "46fe1c89bcc75c781729ec9e5491c610", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "9c6278d7df3ce4db2ffe7794a0fd35b7", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "110c35a17b462864075800756b5e541a", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f", @@ -4112,7 +4112,7 @@ "src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69", "src/ballistica/core/mgen/pyembed/env.inc": "8be46e5818f360d10b7b0224a9e91d07", "src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5", - "src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "d80f970053099b3044204bfe29ddefce", + "src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "c25b263f2a31fb5ebe057db07d144879", "src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f", "src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "f5f054050d2b2fcd3763a4833fb32269" } \ No newline at end of file diff --git a/.idea/ballisticakit.iml b/.idea/ballisticakit.iml index 4c2844c3..6d3ca9f0 100644 --- a/.idea/ballisticakit.iml +++ b/.idea/ballisticakit.iml @@ -21,7 +21,6 @@ - diff --git a/CHANGELOG.md b/CHANGELOG.md index 4423000a..34c408fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ -### 1.7.31 (build 21700, api 8, 2023-12-09) - +### 1.7.31 (build 21707, api 8, 2023-12-13) +- added `bascenev1.get_connection_to_host_info_2()` which is an improved + type-safe version of `bascenev1.get_connection_to_host_info()`. + ### 1.7.30 (build 21697, api 8, 2023-12-08) - Continued work on the big 1.7.28 update. - Got the Android version back up and running. There's been lots of cleanup and diff --git a/ballisticakit-cmake/.idea/misc.xml b/ballisticakit-cmake/.idea/misc.xml index 17581663..f01f08b8 100644 --- a/ballisticakit-cmake/.idea/misc.xml +++ b/ballisticakit-cmake/.idea/misc.xml @@ -14,7 +14,6 @@ - diff --git a/config/spinoffconfig.py b/config/spinoffconfig.py index 21b06551..ece093bd 100644 --- a/config/spinoffconfig.py +++ b/config/spinoffconfig.py @@ -152,7 +152,6 @@ ctx.filter_dirs = { 'ballisticakit-cmake', 'ballisticakit-xcode/BallisticaKit.xcodeproj', 'ballisticakit-ios.xcodeproj', - 'ballisticakit-mac.xcodeproj', 'config', 'src/assets/pdoc', } diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json index 55998cf7..8a62973f 100644 --- a/src/assets/.asset_manifest_public.json +++ b/src/assets/.asset_manifest_public.json @@ -20,7 +20,6 @@ "ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc", - "ba_data/python/babase/__pycache__/_keyboard.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_language.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_login.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_math.cpython-311.opt-1.pyc", @@ -50,7 +49,6 @@ "ba_data/python/babase/_error.py", "ba_data/python/babase/_general.py", "ba_data/python/babase/_hooks.py", - "ba_data/python/babase/_keyboard.py", "ba_data/python/babase/_language.py", "ba_data/python/babase/_login.py", "ba_data/python/babase/_math.py", @@ -152,6 +150,7 @@ "ba_data/python/bascenev1/__pycache__/_messages.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_multiteamsession.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_music.cpython-311.opt-1.pyc", + "ba_data/python/bascenev1/__pycache__/_net.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_nodeactor.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_player.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_playlist.cpython-311.opt-1.pyc", @@ -186,6 +185,7 @@ "ba_data/python/bascenev1/_messages.py", "ba_data/python/bascenev1/_multiteamsession.py", "ba_data/python/bascenev1/_music.py", + "ba_data/python/bascenev1/_net.py", "ba_data/python/bascenev1/_nodeactor.py", "ba_data/python/bascenev1/_player.py", "ba_data/python/bascenev1/_playlist.py", @@ -352,10 +352,12 @@ "ba_data/python/bauiv1/__init__.py", "ba_data/python/bauiv1/__pycache__/__init__.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/__pycache__/_hooks.cpython-311.opt-1.pyc", + "ba_data/python/bauiv1/__pycache__/_keyboard.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/__pycache__/_subsystem.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/__pycache__/_uitypes.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/_hooks.py", + "ba_data/python/bauiv1/_keyboard.py", "ba_data/python/bauiv1/_subsystem.py", "ba_data/python/bauiv1/_uitypes.py", "ba_data/python/bauiv1/onscreenkeyboard.py", diff --git a/src/assets/Makefile b/src/assets/Makefile index a52dda49..1ee71109 100644 --- a/src/assets/Makefile +++ b/src/assets/Makefile @@ -178,7 +178,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/babase/_error.py \ $(BUILD_DIR)/ba_data/python/babase/_general.py \ $(BUILD_DIR)/ba_data/python/babase/_hooks.py \ - $(BUILD_DIR)/ba_data/python/babase/_keyboard.py \ $(BUILD_DIR)/ba_data/python/babase/_language.py \ $(BUILD_DIR)/ba_data/python/babase/_login.py \ $(BUILD_DIR)/ba_data/python/babase/_math.py \ @@ -237,6 +236,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bascenev1/_messages.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_multiteamsession.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_music.py \ + $(BUILD_DIR)/ba_data/python/bascenev1/_net.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_nodeactor.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_player.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_playlist.py \ @@ -326,6 +326,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \ $(BUILD_DIR)/ba_data/python/batemplatefs/_subsystem.py \ $(BUILD_DIR)/ba_data/python/bauiv1/__init__.py \ $(BUILD_DIR)/ba_data/python/bauiv1/_hooks.py \ + $(BUILD_DIR)/ba_data/python/bauiv1/_keyboard.py \ $(BUILD_DIR)/ba_data/python/bauiv1/_subsystem.py \ $(BUILD_DIR)/ba_data/python/bauiv1/_uitypes.py \ $(BUILD_DIR)/ba_data/python/bauiv1/onscreenkeyboard.py \ @@ -452,7 +453,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc \ - $(BUILD_DIR)/ba_data/python/babase/__pycache__/_keyboard.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_language.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_login.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_math.cpython-311.opt-1.pyc \ @@ -511,6 +511,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_messages.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_multiteamsession.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_music.cpython-311.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_net.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_nodeactor.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_player.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_playlist.cpython-311.opt-1.pyc \ @@ -600,6 +601,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \ $(BUILD_DIR)/ba_data/python/batemplatefs/__pycache__/_subsystem.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/__init__.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_hooks.cpython-311.opt-1.pyc \ + $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_keyboard.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_subsystem.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_uitypes.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-311.opt-1.pyc \ diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py index b2a12abe..91b05f35 100644 --- a/src/assets/ba_data/python/babase/__init__.py +++ b/src/assets/ba_data/python/babase/__init__.py @@ -154,7 +154,6 @@ from babase._general import ( getclass, get_type_name, ) -from babase._keyboard import Keyboard from babase._language import Lstr, LanguageSubsystem from babase._login import LoginAdapter, LoginInfo @@ -261,7 +260,6 @@ __all__ = [ 'is_point_in_box', 'is_running_on_fire_tv', 'is_xcode_build', - 'Keyboard', 'LanguageSubsystem', 'lock_all_input', 'LoginAdapter', diff --git a/src/assets/ba_data/python/babase/_accountv2.py b/src/assets/ba_data/python/babase/_accountv2.py index 5d5d6633..37914bce 100644 --- a/src/assets/ba_data/python/babase/_accountv2.py +++ b/src/assets/ba_data/python/babase/_accountv2.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: from babase._login import LoginAdapter, LoginInfo -DEBUG_LOG = _babase.temp_testing() +DEBUG_LOG = False class AccountV2Subsystem: diff --git a/src/assets/ba_data/python/babase/_appmode.py b/src/assets/ba_data/python/babase/_appmode.py index fcaa77a9..1da43144 100644 --- a/src/assets/ba_data/python/babase/_appmode.py +++ b/src/assets/ba_data/python/babase/_appmode.py @@ -31,6 +31,7 @@ class AppMode: AppExperience associated with the AppMode must be supported by the current app and runtime environment. """ + # FIXME: check AppExperience. return cls._supports_intent(intent) @classmethod diff --git a/src/assets/ba_data/python/babase/_apputils.py b/src/assets/ba_data/python/babase/_apputils.py index 655bc8b3..59d76481 100644 --- a/src/assets/ba_data/python/babase/_apputils.py +++ b/src/assets/ba_data/python/babase/_apputils.py @@ -325,7 +325,7 @@ def dump_app_state( ) -def log_dumped_app_state() -> None: +def log_dumped_app_state(from_previous_run: bool = False) -> None: """If an app-state dump exists, log it and clear it. No-op otherwise.""" try: @@ -352,8 +352,13 @@ def log_dumped_app_state() -> None: metadata = dataclass_from_json(DumpedAppStateMetadata, appstatedata) + header = ( + 'Found app state dump from previous app run' + if from_previous_run + else 'App state dump' + ) out += ( - f'App state dump:\nReason: {metadata.reason}\n' + f'{header}:\nReason: {metadata.reason}\n' f'Time: {metadata.app_time:.2f}' ) tbpath = os.path.join( @@ -383,7 +388,7 @@ class AppHealthMonitor(AppSubsystem): def on_app_loading(self) -> None: # If any traceback dumps happened last run, log and clear them. - log_dumped_app_state() + log_dumped_app_state(from_previous_run=True) def _app_monitor_thread_main(self) -> None: _babase.set_thread_name('ballistica app-monitor') diff --git a/src/assets/ba_data/python/babase/_login.py b/src/assets/ba_data/python/babase/_login.py index cb7ebbd5..39cfa0ed 100644 --- a/src/assets/ba_data/python/babase/_login.py +++ b/src/assets/ba_data/python/babase/_login.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: from typing import Callable -DEBUG_LOG = _babase.temp_testing() +DEBUG_LOG = False @dataclass diff --git a/src/assets/ba_data/python/babase/_meta.py b/src/assets/ba_data/python/babase/_meta.py index 5e2a0ec5..76f88b76 100644 --- a/src/assets/ba_data/python/babase/_meta.py +++ b/src/assets/ba_data/python/babase/_meta.py @@ -24,6 +24,8 @@ if TYPE_CHECKING: # instead of these or to make the meta system aware of arbitrary classes. EXPORT_CLASS_NAME_SHORTCUTS: dict[str, str] = { 'plugin': 'babase.Plugin', + # DEPRECATED as of 12/2023. Currently am warning if finding these + # but should take this out eventually. 'keyboard': 'babase.Keyboard', } @@ -414,30 +416,27 @@ class DirectoryScan: if export_class_name is not None: classname = modulename + '.' + export_class_name - # Since we'll soon have multiple versions of 'game' - # classes we need to migrate people to using base - # class names for them. - if exporttypestr == 'game': + # Migrating away from the 'keyboard' name shortcut + # since it's specific to bauiv1; warn if we find it. + if exporttypestr == 'keyboard': logging.warning( "metascan: %s:%d: '# ba_meta export" - " game' tag should be replaced by '# ba_meta" - " export bascenev1.GameActivity'.", + " keyboard' tag should be replaced by '# ba_meta" + " export bauiv1.Keyboard'.", subpath, lindex + 1, ) self.results.announce_errors_occurred = True - else: - # If export type is one of our shortcuts, sub in the - # actual class path. Otherwise assume its a classpath - # itself. - exporttype = EXPORT_CLASS_NAME_SHORTCUTS.get( - exporttypestr - ) - if exporttype is None: - exporttype = exporttypestr - self.results.exports.setdefault(exporttype, []).append( - classname - ) + + # If export type is one of our shortcuts, sub in the + # actual class path. Otherwise assume its a classpath + # itself. + exporttype = EXPORT_CLASS_NAME_SHORTCUTS.get(exporttypestr) + if exporttype is None: + exporttype = exporttypestr + self.results.exports.setdefault(exporttype, []).append( + classname + ) def _get_export_class_name( self, subpath: Path, lines: list[str], lindex: int diff --git a/src/assets/ba_data/python/baclassic/_ads.py b/src/assets/ba_data/python/baclassic/_ads.py index 0b520d22..fdb34de4 100644 --- a/src/assets/ba_data/python/baclassic/_ads.py +++ b/src/assets/ba_data/python/baclassic/_ads.py @@ -97,7 +97,7 @@ class AdsSubsystem: show = True # No ads without net-connections, etc. - if not bauiv1.can_show_ad(): + if not plus.can_show_ad(): show = False if classic.accounts.have_pro(): show = False # Pro disables interstitials. @@ -135,7 +135,7 @@ class AdsSubsystem: # ad-show-threshold and see if we should *actually* show # (we reach our threshold faster the longer we've been # playing). - base = 'ads' if bauiv1.has_video_ads() else 'ads2' + base = 'ads' if plus.has_video_ads() else 'ads2' min_lc = plus.get_v1_account_misc_read_val(base + '.minLC', 0.0) max_lc = plus.get_v1_account_misc_read_val(base + '.maxLC', 5.0) min_lc_scale = plus.get_v1_account_misc_read_val( diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index ba47b57e..35416b81 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21700 +TARGET_BALLISTICA_BUILD = 21707 TARGET_BALLISTICA_VERSION = '1.7.31' diff --git a/src/assets/ba_data/python/baplus/_subsystem.py b/src/assets/ba_data/python/baplus/_subsystem.py index 97a9cdb5..0b77c20f 100644 --- a/src/assets/ba_data/python/baplus/_subsystem.py +++ b/src/assets/ba_data/python/baplus/_subsystem.py @@ -249,3 +249,18 @@ class PlusSubsystem(AppSubsystem): ) -> None: """(internal)""" return _baplus.tournament_query(callback, args) + + @staticmethod + def have_incentivized_ad() -> bool: + """Is an incentivized ad available?""" + return _baplus.have_incentivized_ad() + + @staticmethod + def has_video_ads() -> bool: + """Are video ads available?""" + return _baplus.has_video_ads() + + @staticmethod + def can_show_ad() -> bool: + """Can we show an ad?""" + return _baplus.can_show_ad() diff --git a/src/assets/ba_data/python/bascenev1/__init__.py b/src/assets/ba_data/python/bascenev1/__init__.py index 605b363c..e6cc6a5a 100644 --- a/src/assets/ba_data/python/bascenev1/__init__.py +++ b/src/assets/ba_data/python/bascenev1/__init__.py @@ -78,6 +78,7 @@ from _bascenev1 import ( end_host_scanning, get_chat_messages, get_connection_to_host_info, + get_connection_to_host_info_2, get_foreground_host_activity, get_foreground_host_session, get_game_port, @@ -202,6 +203,7 @@ from bascenev1._multiteamsession import ( DEFAULT_TEAM_NAMES, ) from bascenev1._music import MusicType, setmusic +from bascenev1._net import HostInfo from bascenev1._nodeactor import NodeActor from bascenev1._powerup import get_default_powerup_distribution from bascenev1._profile import ( @@ -303,6 +305,7 @@ __all__ = [ 'GameTip', 'get_chat_messages', 'get_connection_to_host_info', + 'get_connection_to_host_info_2', 'get_default_free_for_all_playlist', 'get_default_teams_playlist', 'get_default_powerup_distribution', @@ -338,6 +341,7 @@ __all__ = [ 'have_connected_clients', 'have_touchscreen_input', 'HitMessage', + 'HostInfo', 'host_scan_cycle', 'ImpactDamageMessage', 'increment_analytics_count', diff --git a/src/assets/ba_data/python/bascenev1/_net.py b/src/assets/ba_data/python/bascenev1/_net.py new file mode 100644 index 00000000..279c329d --- /dev/null +++ b/src/assets/ba_data/python/bascenev1/_net.py @@ -0,0 +1,24 @@ +# Released under the MIT License. See LICENSE for details. +# +"""Functionality related to net play.""" +from __future__ import annotations + +from typing import TYPE_CHECKING +from dataclasses import dataclass + +if TYPE_CHECKING: + pass + + +@dataclass +class HostInfo: + """Info about a host.""" + + name: str + build_number: int + + # Note this can be None for non-ip hosts such as bluetooth. + address: str | None + + # Note this can be None for non-ip hosts such as bluetooth. + port: int | None diff --git a/src/assets/ba_data/python/bauiv1/__init__.py b/src/assets/ba_data/python/bauiv1/__init__.py index fa97bb6f..5e738646 100644 --- a/src/assets/ba_data/python/bauiv1/__init__.py +++ b/src/assets/ba_data/python/bauiv1/__init__.py @@ -62,7 +62,6 @@ from babase import ( is_browser_likely_available, is_running_on_fire_tv, is_xcode_build, - Keyboard, lock_all_input, LoginAdapter, LoginInfo, @@ -94,7 +93,6 @@ from babase import ( from _bauiv1 import ( buttonwidget, - can_show_ad, checkboxwidget, columnwidget, containerwidget, @@ -103,8 +101,6 @@ from _bauiv1 import ( getmesh, getsound, gettexture, - has_video_ads, - have_incentivized_ad, hscrollwidget, imagewidget, is_party_icon_visible, @@ -125,6 +121,7 @@ from _bauiv1 import ( Widget, widget, ) +from bauiv1._keyboard import Keyboard from bauiv1._uitypes import Window, uicleanupcheck from bauiv1._subsystem import UIV1Subsystem @@ -144,7 +141,6 @@ __all__ = [ 'AppTimer', 'buttonwidget', 'Call', - 'can_show_ad', 'fullscreen_control_available', 'fullscreen_control_get', 'fullscreen_control_key_shortcut', @@ -178,8 +174,6 @@ __all__ = [ 'getmesh', 'getsound', 'gettexture', - 'has_video_ads', - 'have_incentivized_ad', 'have_permission', 'hscrollwidget', 'imagewidget', diff --git a/src/assets/ba_data/python/babase/_keyboard.py b/src/assets/ba_data/python/bauiv1/_keyboard.py similarity index 100% rename from src/assets/ba_data/python/babase/_keyboard.py rename to src/assets/ba_data/python/bauiv1/_keyboard.py diff --git a/src/assets/ba_data/python/bauiv1/onscreenkeyboard.py b/src/assets/ba_data/python/bauiv1/onscreenkeyboard.py index 7dc42b0e..425a78e6 100644 --- a/src/assets/ba_data/python/bauiv1/onscreenkeyboard.py +++ b/src/assets/ba_data/python/bauiv1/onscreenkeyboard.py @@ -12,6 +12,7 @@ from typing import TYPE_CHECKING import babase import _bauiv1 +from bauiv1._keyboard import Keyboard from bauiv1._uitypes import Window if TYPE_CHECKING: @@ -252,9 +253,7 @@ class OnScreenKeyboardWindow(Window): # Show change instructions only if we have more than one # keyboard option. keyboards = ( - babase.app.meta.scanresults.exports_of_class( - babase.Keyboard - ) + babase.app.meta.scanresults.exports_of_class(Keyboard) if babase.app.meta.scanresults is not None else [] ) @@ -286,10 +285,10 @@ class OnScreenKeyboardWindow(Window): def _get_keyboard(self) -> bui.Keyboard: assert babase.app.meta.scanresults is not None - classname = babase.app.meta.scanresults.exports_of_class( - babase.Keyboard - )[self._keyboard_index] - kbclass = babase.getclass(classname, babase.Keyboard) + classname = babase.app.meta.scanresults.exports_of_class(Keyboard)[ + self._keyboard_index + ] + kbclass = babase.getclass(classname, Keyboard) return kbclass() def _refresh(self) -> None: @@ -384,9 +383,7 @@ class OnScreenKeyboardWindow(Window): def _next_keyboard(self) -> None: assert babase.app.meta.scanresults is not None - kbexports = babase.app.meta.scanresults.exports_of_class( - babase.Keyboard - ) + kbexports = babase.app.meta.scanresults.exports_of_class(Keyboard) self._keyboard_index = (self._keyboard_index + 1) % len(kbexports) self._load_keyboard() diff --git a/src/assets/ba_data/python/bauiv1lib/coop/browser.py b/src/assets/ba_data/python/bauiv1lib/coop/browser.py index 256e6771..c46c552d 100644 --- a/src/assets/ba_data/python/bauiv1lib/coop/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/coop/browser.py @@ -415,7 +415,7 @@ class CoopBrowserWindow(bui.Window): ) # Decrement time on our tournament buttons. - ads_enabled = bui.have_incentivized_ad() + ads_enabled = plus.have_incentivized_ad() for tbtn in self._tournament_buttons: tbtn.time_remaining = max(0, tbtn.time_remaining - 1) if tbtn.time_remaining_value_text is not None: @@ -430,7 +430,7 @@ class CoopBrowserWindow(bui.Window): ) # Also adjust the ad icon visibility. - if tbtn.allow_ads and bui.has_video_ads(): + if tbtn.allow_ads and plus.has_video_ads(): bui.imagewidget( edit=tbtn.entry_fee_ad_image, opacity=1.0 if ads_enabled else 0.25, diff --git a/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py b/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py index 72d604e6..4b766379 100644 --- a/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py +++ b/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py @@ -638,8 +638,8 @@ class TournamentButton: # Now, if this fee allows ads and we support video ads, show # the 'or ad' version. - if allow_ads and bui.has_video_ads(): - ads_enabled = bui.have_incentivized_ad() + if allow_ads and plus.has_video_ads(): + ads_enabled = plus.have_incentivized_ad() bui.imagewidget( edit=self.entry_fee_ad_image, opacity=1.0 if ads_enabled else 0.25, diff --git a/src/assets/ba_data/python/bauiv1lib/getcurrency.py b/src/assets/ba_data/python/bauiv1lib/getcurrency.py index 78ab9af1..6e355d42 100644 --- a/src/assets/ba_data/python/bauiv1lib/getcurrency.py +++ b/src/assets/ba_data/python/bauiv1lib/getcurrency.py @@ -334,7 +334,7 @@ class GetCurrencyWindow(bui.Window): tex_scale=1.2, ) # 19.99-ish - self._enable_ad_button = bui.has_video_ads() + self._enable_ad_button = plus.has_video_ads() h = self._width * 0.5 + 110.0 v = self._height - b_size[1] - 115.0 @@ -561,7 +561,7 @@ class GetCurrencyWindow(bui.Window): next_reward_ad_time ) now = datetime.datetime.utcnow() - if bui.have_incentivized_ad() and ( + if plus.have_incentivized_ad() and ( next_reward_ad_time is None or next_reward_ad_time <= now ): self._ad_button_greyed = False diff --git a/src/assets/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py b/src/assets/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py index ae94d938..3de74f87 100644 --- a/src/assets/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py +++ b/src/assets/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py @@ -9,7 +9,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -import babase +import bauiv1 as bui if TYPE_CHECKING: from typing import Iterable @@ -33,15 +33,15 @@ def split(chars: Iterable[str], maxlen: int) -> list[list[str]]: def generate_emojis(maxlen: int) -> list[list[str]]: - """Generates a lot of UTF8 emojis prepared for babase.Keyboard pages""" + """Generates a lot of UTF8 emojis prepared for bui.Keyboard pages""" all_emojis = split([chr(i) for i in range(0x1F601, 0x1F650)], maxlen) all_emojis += split([chr(i) for i in range(0x2702, 0x27B1)], maxlen) all_emojis += split([chr(i) for i in range(0x1F680, 0x1F6C1)], maxlen) return all_emojis -# ba_meta export keyboard -class EnglishKeyboard(babase.Keyboard): +# ba_meta export bauiv1.Keyboard +class EnglishKeyboard(bui.Keyboard): """Default English keyboard.""" name = 'English' diff --git a/src/assets/ba_data/python/bauiv1lib/party.py b/src/assets/ba_data/python/bauiv1lib/party.py index 5920c2d8..f9250334 100644 --- a/src/assets/ba_data/python/bauiv1lib/party.py +++ b/src/assets/ba_data/python/bauiv1lib/party.py @@ -92,9 +92,10 @@ class PartyWindow(bui.Window): iconscale=1.2, ) - info = bs.get_connection_to_host_info() - if info.get('name', '') != '': - title = bui.Lstr(value=info['name']) + info = bs.get_connection_to_host_info_2() + + if info is not None and info.name != '': + title = bui.Lstr(value=info.name) else: title = bui.Lstr(resource=self._r + '.titleText') @@ -481,7 +482,8 @@ class PartyWindow(bui.Window): kick_str = bui.Lstr(resource='kickText') else: # kick-votes appeared in build 14248 - if bs.get_connection_to_host_info().get('build_number', 0) < 14248: + info = bs.get_connection_to_host_info_2() + if info is None or info.build_number < 14248: return kick_str = bui.Lstr(resource='kickVoteText') assert bui.app.classic is not None diff --git a/src/assets/ba_data/python/bauiv1lib/tournamententry.py b/src/assets/ba_data/python/bauiv1lib/tournamententry.py index fcba3e99..d00c37dd 100644 --- a/src/assets/ba_data/python/bauiv1lib/tournamententry.py +++ b/src/assets/ba_data/python/bauiv1lib/tournamententry.py @@ -34,6 +34,7 @@ class TournamentEntryWindow(PopupWindow): # pylint: disable=too-many-statements assert bui.app.classic is not None + assert bui.app.plus bui.set_analytics_screen('Tournament Entry Window') self._tournament_id = tournament_id @@ -100,7 +101,7 @@ class TournamentEntryWindow(PopupWindow): self._launched = False # Show the ad button only if we support ads *and* it has a level 1 fee. - self._do_ad_btn = bui.has_video_ads() and self._allow_ads + self._do_ad_btn = bui.app.plus.has_video_ads() and self._allow_ads x_offs = 0 if self._do_ad_btn else 85 @@ -477,7 +478,7 @@ class TournamentEntryWindow(PopupWindow): ) if self._do_ad_btn: - enabled = bui.have_incentivized_ad() + enabled = plus.have_incentivized_ad() have_ad_tries_remaining = ( self._tournament_info['adTriesRemaining'] is not None and self._tournament_info['adTriesRemaining'] > 0 diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index 45d6c2c7..a174c575 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -195,6 +195,12 @@ void BaseFeatureSet::OnAssetsAvailable() { } void BaseFeatureSet::StartApp() { + // { + // // TEST - recreate the ID python dumps in its thread tracebacks. + // auto val = PyThread_get_thread_ident(); + // printf("MAIN THREAD IS %#018lx\n", val); + // } + BA_PRECONDITION(g_core->InMainThread()); BA_PRECONDITION(g_base); diff --git a/src/ballistica/base/networking/networking.cc b/src/ballistica/base/networking/networking.cc index 696eb7a1..3b83f698 100644 --- a/src/ballistica/base/networking/networking.cc +++ b/src/ballistica/base/networking/networking.cc @@ -50,7 +50,7 @@ void Networking::SendTo(const std::vector& buffer, if (sd != -1) { sendto(sd, (const char*)&buffer[0], static_cast_check_fit(buffer.size()), 0, - addr.GetSockAddr(), addr.GetSockAddrLen()); + addr.AsSockAddr(), addr.GetSockAddrLen()); } } diff --git a/src/ballistica/base/platform/base_platform.cc b/src/ballistica/base/platform/base_platform.cc index 0a1d4949..f05d2b26 100644 --- a/src/ballistica/base/platform/base_platform.cc +++ b/src/ballistica/base/platform/base_platform.cc @@ -58,8 +58,8 @@ auto BasePlatform::GetPublicDeviceUUID() -> std::string { // We used to plug version in directly here, but that caused uuids to // shuffle too rapidly during periods of rapid development. This // keeps it more constant. - // __last_rand_uuid_component_shuffle_date__ 2023 6 15 - auto rand_uuid_component{"JVRWZ82D4WMBO110OA0IFJV7JKMQV8W3"}; + // __last_rand_uuid_component_shuffle_date__ 2023 12 13 + auto rand_uuid_component{"7YM96RZHN6ZCPZGTQONULZO1JU5NMMC7"}; inputs.emplace_back(rand_uuid_component); auto gil{Python::ScopedInterpreterLock()}; diff --git a/src/ballistica/core/core.cc b/src/ballistica/core/core.cc index 002a4388..f7bdc1ca 100644 --- a/src/ballistica/core/core.cc +++ b/src/ballistica/core/core.cc @@ -368,12 +368,64 @@ void CoreFeatureSet::StartSuicideTimer(const std::string& action, } } -// auto CoreFeatureSet::InMainThread() -> bool { -// return std::this_thread::get_id() == main_thread_id; -// // if (main_event_loop_) { -// // return main_event_loop_->ThreadIsCurrent(); -// // } -// // return false; -// } +void CoreFeatureSet::RegisterThread(const std::string& name) { + { + std::scoped_lock lock(thread_info_map_mutex_); + + // Should be registering each thread just once. + assert(thread_info_map_.find(std::this_thread::get_id()) + == thread_info_map_.end()); + thread_info_map_[std::this_thread::get_id()] = name; + } + + // Also set the name at the OS leve when possible. Prepend 'ballistica' + // since there's generally lots of other random threads in the mix. + // + // Note that we currently don't do this for our main thread because (on + // Linux at least) that changes the process name we see in top/etc. On + // other platforms we could reconsider, but its generally clear what the + // main thread is anyway in most scenarios. + if (!InMainThread()) { + g_core->platform->SetCurrentThreadName("ballistica " + name); + } +} + +void CoreFeatureSet::UnregisterThread() { + std::scoped_lock lock(thread_info_map_mutex_); + auto i = thread_info_map_.find(std::this_thread::get_id()); + assert(i != thread_info_map_.end()); + if (i != thread_info_map_.end()) { + thread_info_map_.erase(i); + } +} + +auto CoreFeatureSet::CurrentThreadName() -> std::string { + if (g_core == nullptr) { + return "unknown(not-yet-inited)"; + } + { + std::scoped_lock lock(g_core->thread_info_map_mutex_); + auto i = g_core->thread_info_map_.find(std::this_thread::get_id()); + if (i != g_core->thread_info_map_.end()) { + return i->second; + } + } + + // Ask pthread for the thread name if we don't have one. + // FIXME - move this to platform. +#if BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS || BA_OSTYPE_LINUX + std::string name = "unknown (sys-name="; + char buffer[256]; + int result = pthread_getname_np(pthread_self(), buffer, sizeof(buffer)); + if (result == 0) { + name += std::string("\"") + buffer + "\")"; + } else { + name += ""; + } + return name; +#else + return "unknown"; +#endif +} } // namespace ballistica::core diff --git a/src/ballistica/core/core.h b/src/ballistica/core/core.h index be60774f..d31d0e8d 100644 --- a/src/ballistica/core/core.h +++ b/src/ballistica/core/core.h @@ -144,6 +144,12 @@ class CoreFeatureSet { return using_custom_app_python_dir_; } + /// Register various info about the current thread. + void RegisterThread(const std::string& name); + + /// Should be called by a thread before it exits. + void UnregisterThread(); + // Subsystems. CorePython* const python; CorePlatform* const platform; @@ -158,8 +164,6 @@ class CoreFeatureSet { bool v1_cloud_log_full{}; int master_server_source{}; std::vector suspendable_event_loops; - std::mutex thread_name_map_mutex; - std::unordered_map thread_name_map; std::mutex v1_cloud_log_mutex; std::string v1_cloud_log; @@ -173,6 +177,7 @@ class CoreFeatureSet { auto vr_mode() const { return vr_mode_; } auto event_loops_suspended() const { return event_loops_suspended_; } void set_event_loops_suspended(bool val) { event_loops_suspended_ = val; } + static auto CurrentThreadName() -> std::string; private: explicit CoreFeatureSet(CoreConfig config); @@ -204,6 +209,8 @@ class CoreFeatureSet { std::optional ba_env_user_python_dir_; std::optional ba_env_site_python_dir_; std::string ba_env_data_dir_; + std::mutex thread_info_map_mutex_; + std::unordered_map thread_info_map_; }; } // namespace ballistica::core diff --git a/src/ballistica/core/platform/apple/core_platform_apple.cc b/src/ballistica/core/platform/apple/core_platform_apple.cc index aa9f72d0..df8281f6 100644 --- a/src/ballistica/core/platform/apple/core_platform_apple.cc +++ b/src/ballistica/core/platform/apple/core_platform_apple.cc @@ -466,7 +466,7 @@ auto CorePlatformApple::CanShowBlockingFatalErrorDialog() -> bool { if (g_buildconfig.xcode_build() && g_buildconfig.ostype_macos()) { return true; } - return false; + return CorePlatform::CanShowBlockingFatalErrorDialog(); } void CorePlatformApple::BlockingFatalErrorDialog(const std::string& message) { diff --git a/src/ballistica/core/platform/core_platform.cc b/src/ballistica/core/platform/core_platform.cc index 0abf2ad0..274c0996 100644 --- a/src/ballistica/core/platform/core_platform.cc +++ b/src/ballistica/core/platform/core_platform.cc @@ -826,11 +826,9 @@ auto CorePlatform::MacMusicAppGetPlaylists() -> std::list { } void CorePlatform::SetCurrentThreadName(const std::string& name) { - // Currently we leave the main thread alone, otherwise we show up as - // "BallisticaMainThread" under "top" on linux (should check other platforms). - if (g_core->InMainThread()) { - return; - } + // We should never be doing this for the main thread. + BA_PRECONDITION_FATAL(!g_core->InMainThread()); + #if BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS pthread_setname_np(name.c_str()); #elif BA_OSTYPE_LINUX || BA_OSTYPE_ANDROID diff --git a/src/ballistica/scene_v1/connection/connection_to_host_udp.cc b/src/ballistica/scene_v1/connection/connection_to_host_udp.cc index 97bf9271..3ae741eb 100644 --- a/src/ballistica/scene_v1/connection/connection_to_host_udp.cc +++ b/src/ballistica/scene_v1/connection/connection_to_host_udp.cc @@ -18,7 +18,7 @@ auto ConnectionToHostUDP::SwitchProtocol() -> bool { // Need a new request id so we ignore further responses to our previous // requests. - GetRequestID(); + GetRequestID_(); return true; } return false; @@ -32,7 +32,7 @@ ConnectionToHostUDP::ConnectionToHostUDP(const SockAddr& addr) did_die_(false), last_host_response_time_millisecs_( static_cast(g_base->logic->display_time() * 1000.0)) { - GetRequestID(); + GetRequestID_(); if (auto* appmode = SceneV1AppMode::GetActiveOrWarn()) { if (appmode->connections()->GetPrintUDPConnectProgress()) { ScreenMessage(g_base->assets->GetResourceString("connectingToPartyText")); @@ -46,11 +46,11 @@ ConnectionToHostUDP::~ConnectionToHostUDP() { set_connection_dying(true); } -void ConnectionToHostUDP::GetRequestID() { +void ConnectionToHostUDP::GetRequestID_() { // We store a unique-ish request ID to minimize the chance that data for - // previous connections/etc will muck with us. - // Try to start this value at something that won't be common in packets to - // minimize chance of garbage packets causing trouble. + // previous connections/etc will muck with us. Try to start this value at + // something that won't be common in packets to minimize chance of garbage + // packets causing trouble. static auto next_request_id = static_cast(71 + (rand() % 151)); // NOLINT request_id_ = next_request_id++; @@ -95,13 +95,14 @@ void ConnectionToHostUDP::Update() { {1, 0, 0}); } - // Die immediately in this case; no use trying to wait for a disconnect-ack - // since we've already given up hope of hearing from them. + // Die immediately in this case; no use trying to wait for a + // disconnect-ack since we've already given up hope of hearing from + // them. Die(); return; } else if (errored()) { - // If we've errored, keep sending disconnect-requests periodically. - // Once we get a response (or time out in the above code) we'll die. + // If we've errored, keep sending disconnect-requests periodically. Once + // we get a response (or time out in the above code) we'll die. if (current_time_millisecs - last_disconnect_request_time_ > 1000) { last_disconnect_request_time_ = current_time_millisecs; @@ -189,8 +190,8 @@ void ConnectionToHostUDP::Error(const std::string& msg) { auto ConnectionToHostUDP::GetAsUDP() -> ConnectionToHostUDP* { return this; } void ConnectionToHostUDP::RequestDisconnect() { - // Mark us as errored so all future communication results in more disconnect - // requests. + // Mark us as errored so all future communication results in more + // disconnect requests. set_errored(true); if (client_id_ != -1) { SendDisconnectRequest(); diff --git a/src/ballistica/scene_v1/connection/connection_to_host_udp.h b/src/ballistica/scene_v1/connection/connection_to_host_udp.h index b085b759..58cb4a04 100644 --- a/src/ballistica/scene_v1/connection/connection_to_host_udp.h +++ b/src/ballistica/scene_v1/connection/connection_to_host_udp.h @@ -23,8 +23,8 @@ class ConnectionToHostUDP : public ConnectionToHost { void set_client_id(int val) { client_id_ = val; } auto client_id() const -> int { return client_id_; } - // Attempt connecting via a different protocol. If none are left to try, - // returns false. + /// Attempt connecting via a different protocol. If none are left to try, + /// returns false. auto SwitchProtocol() -> bool; void RequestDisconnect() override; @@ -32,16 +32,18 @@ class ConnectionToHostUDP : public ConnectionToHost { void Error(const std::string& error_msg) override; void Die(); void SendDisconnectRequest(); + const auto& addr() const { return *addr_; } private: - void GetRequestID(); - uint8_t request_id_{}; - std::unique_ptr addr_; + void GetRequestID_(); + bool did_die_{}; + uint8_t request_id_{}; + int client_id_{}; millisecs_t last_client_id_request_time_{}; millisecs_t last_disconnect_request_time_{}; - int client_id_{}; millisecs_t last_host_response_time_millisecs_{}; + std::unique_ptr addr_; }; } // namespace ballistica::scene_v1 diff --git a/src/ballistica/scene_v1/python/methods/python_methods_networking.cc b/src/ballistica/scene_v1/python/methods/python_methods_networking.cc index b254752b..72b824d1 100644 --- a/src/ballistica/scene_v1/python/methods/python_methods_networking.cc +++ b/src/ballistica/scene_v1/python/methods/python_methods_networking.cc @@ -5,13 +5,17 @@ #include "ballistica/base/assets/assets.h" #include "ballistica/base/networking/network_reader.h" #include "ballistica/base/python/base_python.h" +#include "ballistica/core/python/core_python.h" #include "ballistica/scene_v1/connection/connection_set.h" #include "ballistica/scene_v1/connection/connection_to_client.h" #include "ballistica/scene_v1/connection/connection_to_host.h" +#include "ballistica/scene_v1/connection/connection_to_host_udp.h" +#include "ballistica/scene_v1/python/scene_v1_python.h" #include "ballistica/scene_v1/support/scene_v1_app_mode.h" #include "ballistica/shared/math/vector3f.h" #include "ballistica/shared/networking/sockaddr.h" #include "ballistica/shared/python/python.h" +#include "ballistica/shared/python/python_ref.h" #include "ballistica/shared/python/python_sys.h" namespace ballistica::scene_v1 { @@ -20,8 +24,7 @@ namespace ballistica::scene_v1 { #pragma clang diagnostic push #pragma ide diagnostic ignored "hicpp-signed-bitwise" -// ------------------------- get_public_party_enabled -// --------------------------- +// ----------------------- get_public_party_enabled --------------------------- static auto PyGetPublicPartyEnabled(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* { @@ -411,7 +414,10 @@ static auto PyGetConnectionToHostInfo(PyObject* self, PyObject* args, const_cast(kwlist))) { return nullptr; } - // Error if we're not in our app-mode. + BA_LOG_ONCE(LogLevel::kWarning, + "bascenev1.get_connection_to_host_info() is deprecated; use " + "bascenev1.get_connection_to_host_info_2()."); + BA_PRECONDITION(g_base->InLogicThread()); auto* appmode = SceneV1AppMode::GetActiveOrThrow(); ConnectionToHost* hc = appmode->connections()->connection_to_host(); @@ -435,6 +441,57 @@ static PyMethodDef PyGetConnectionToHostInfoDef = { "(internal)", }; +// --------------------- get_connection_to_host_info_2 ------------------------- + +static auto PyGetConnectionToHostInfo2(PyObject* self, PyObject* args, + PyObject* keywds) -> PyObject* { + BA_PYTHON_TRY; + static const char* kwlist[] = {nullptr}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "", + const_cast(kwlist))) { + return nullptr; + } + BA_PRECONDITION(g_base->InLogicThread()); + auto* appmode = SceneV1AppMode::GetActiveOrThrow(); + + ConnectionToHost* hc = appmode->connections()->connection_to_host(); + if (hc) { + PythonRef addr_obj; + PythonRef port_obj; + if (ConnectionToHostUDP* hcu = dynamic_cast(hc)) { + addr_obj.Steal(PyUnicode_FromString(hcu->addr().AddressString().c_str())); + port_obj.Steal(PyLong_FromLong(hcu->addr().Port())); + } else { + addr_obj.Acquire(Py_None); + port_obj.Acquire(Py_None); + } + auto args = + g_core->python->objs().Get(core::CorePython::ObjID::kEmptyTuple); + auto keywds = PythonRef::Stolen(Py_BuildValue( + "{sssisOsO}", "name", hc->party_name().c_str(), "build_number", + hc->build_number(), "address", addr_obj.Get(), "port", port_obj.Get())); + auto result = g_scene_v1->python->objs() + .Get(SceneV1Python::ObjID::kHostInfoClass) + .Call(args, keywds); + if (!result.Exists()) { + throw Exception("Failed to instantiate HostInfo.", PyExcType::kRuntime); + } + return result.HandOver(); + } + Py_RETURN_NONE; + BA_PYTHON_CATCH; +} + +static PyMethodDef PyGetConnectionToHostInfo2Def = { + "get_connection_to_host_info_2", // name + (PyCFunction)PyGetConnectionToHostInfo2, // method + METH_VARARGS | METH_KEYWORDS, // flags + + "get_connection_to_host_info_2() -> bascenev1.HostInfo | None\n" + "\n" + "Return info about the host we are currently connected to.", +}; + // --------------------------- disconnect_from_host ---------------------------- static auto PyDisconnectFromHost(PyObject* self, PyObject* args, @@ -701,6 +758,7 @@ static auto PyChatMessage(PyObject* self, PyObject* args, PyObject* keywds) &clients_obj, &sender_override_obj)) { return nullptr; } + BA_PRECONDITION(g_base->InLogicThread()); auto* appmode = SceneV1AppMode::GetActiveOrThrow(); message = g_base->python->GetPyLString(message_obj); @@ -775,6 +833,7 @@ auto PythonMethodsNetworking::GetMethods() -> std::vector { PyDisconnectClientDef, PyGetClientPublicDeviceUUIDDef, PyGetConnectionToHostInfoDef, + PyGetConnectionToHostInfo2Def, PyClientInfoQueryResponseDef, PyConnectToPartyDef, PySetAuthenticateClientsDef, diff --git a/src/ballistica/scene_v1/python/scene_v1_python.h b/src/ballistica/scene_v1/python/scene_v1_python.h index 404e72f7..afa67832 100644 --- a/src/ballistica/scene_v1/python/scene_v1_python.h +++ b/src/ballistica/scene_v1/python/scene_v1_python.h @@ -91,6 +91,7 @@ class SceneV1Python { kGetPlayerIconCall, kFilterChatMessageCall, kHandleLocalChatMessageCall, + kHostInfoClass, kLast // Sentinel; must be at end. }; diff --git a/src/ballistica/scene_v1/support/client_session_net.h b/src/ballistica/scene_v1/support/client_session_net.h index 075dda57..7a6a213f 100644 --- a/src/ballistica/scene_v1/support/client_session_net.h +++ b/src/ballistica/scene_v1/support/client_session_net.h @@ -37,21 +37,16 @@ class ClientSessionNet : public ClientSession { auto GetBucketNum() -> int; bool writing_replay_{}; + int delay_sample_counter_{}; + float max_delay_smoothed_{}; + float last_bucket_max_delay_{}; + float current_delay_{}; millisecs_t base_time_received_{}; millisecs_t last_base_time_receive_time_{}; millisecs_t leading_base_time_received_{}; millisecs_t leading_base_time_receive_time_{}; Object::WeakRef connection_to_host_; std::vector buckets_{5}; - - // float bucket_max_smoothed_{}; - // float bucket_min_smoothed_{}; - float max_delay_smoothed_{}; - float last_bucket_max_delay_{}; - float current_delay_{}; - - int delay_sample_counter_{}; - // int adjust_counter_{}; }; } // namespace ballistica::scene_v1 diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 2dec51aa..27f3b924 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21700; +const int kEngineBuildNumber = 21707; const char* kEngineVersion = "1.7.31"; const int kEngineApiVersion = 8; @@ -290,7 +290,7 @@ void ScreenMessage(const std::string& msg) { auto CurrentThreadName() -> std::string { // Currently just ask event-loop for this but perhaps should be talking // more directly to the OS/etc. to cover more cases. - return EventLoop::CurrentThreadName(); + return core::CoreFeatureSet::CurrentThreadName(); } } // namespace ballistica diff --git a/src/ballistica/shared/foundation/event_loop.cc b/src/ballistica/shared/foundation/event_loop.cc index 7ed704a6..b3711495 100644 --- a/src/ballistica/shared/foundation/event_loop.cc +++ b/src/ballistica/shared/foundation/event_loop.cc @@ -98,21 +98,6 @@ EventLoop::EventLoop(EventLoopID identifier_in, ThreadSource source) } } -void EventLoop::SetInternalThreadName_(const std::string& name) { - assert(g_core); - std::scoped_lock lock(g_core->thread_name_map_mutex); - g_core->thread_name_map[std::this_thread::get_id()] = name; -} - -void EventLoop::ClearCurrentThreadName() { - assert(g_core); - std::scoped_lock lock(g_core->thread_name_map_mutex); - auto i = g_core->thread_name_map.find(std::this_thread::get_id()); - if (i != g_core->thread_name_map.end()) { - g_core->thread_name_map.erase(i); - } -} - // These are all exactly the same; its just a way to try and clarify // in stack traces which thread is running in case it is not otherwise // evident. @@ -341,53 +326,40 @@ void EventLoop::GetThreadMessages_(std::list* messages) { void EventLoop::BootstrapThread_() { assert(!bootstrapped_); + assert(g_core); thread_id_ = std::this_thread::get_id(); const char* id_string; switch (identifier_) { case EventLoopID::kLogic: name_ = "logic"; - id_string = "ballistica logic"; break; case EventLoopID::kStdin: name_ = "stdin"; - id_string = "ballistica stdin"; break; case EventLoopID::kAssets: name_ = "assets"; - id_string = "ballistica assets"; break; case EventLoopID::kFileOut: name_ = "fileout"; - id_string = "ballistica file-out"; break; case EventLoopID::kMain: name_ = "main"; - id_string = "ballistica main"; break; case EventLoopID::kAudio: name_ = "audio"; - id_string = "ballistica audio"; break; case EventLoopID::kBGDynamics: name_ = "bgdynamics"; - id_string = "ballistica bg-dynamics"; break; case EventLoopID::kNetworkWrite: name_ = "networkwrite"; - id_string = "ballistica network-write"; break; default: throw Exception(); } - assert(!name_.empty() && id_string); - SetInternalThreadName_(name_); - - // Note: we currently don't do this for our main thread because it - // changes the process name we see in top/etc. Should look into that. - if (identifier_ != EventLoopID::kMain) { - g_core->platform->SetCurrentThreadName(id_string); - } + assert(!name_.empty()); + g_core->RegisterThread(name_); bootstrapped_ = true; } @@ -410,7 +382,7 @@ auto EventLoop::ThreadMain_() -> int { RunToCompletion(); - ClearCurrentThreadName(); + g_core->UnregisterThread(); return 0; } catch (const std::exception& e) { auto error_msg = std::string("Unhandled exception in ") @@ -622,35 +594,6 @@ void EventLoop::DeleteTimer(int id) { timers_.DeleteTimer(id); } -auto EventLoop::CurrentThreadName() -> std::string { - if (g_core == nullptr) { - return "unknown(not-yet-inited)"; - } - { - std::scoped_lock lock(g_core->thread_name_map_mutex); - auto i = g_core->thread_name_map.find(std::this_thread::get_id()); - if (i != g_core->thread_name_map.end()) { - return i->second; - } - } - - // Ask pthread for the thread name if we don't have one. - // FIXME - move this to platform. -#if BA_OSTYPE_MACOS || BA_OSTYPE_IOS_TVOS || BA_OSTYPE_LINUX - std::string name = "unknown (sys-name="; - char buffer[256]; - int result = pthread_getname_np(pthread_self(), buffer, sizeof(buffer)); - if (result == 0) { - name += std::string("\"") + buffer + "\")"; - } else { - name += ""; - } - return name; -#else - return "unknown"; -#endif -} - void EventLoop::RunPendingRunnables_() { // Pull all runnables off the list first (its possible for one of these // runnables to add more) and then process them. diff --git a/src/ballistica/shared/foundation/event_loop.h b/src/ballistica/shared/foundation/event_loop.h index 186c52de..8bb761a6 100644 --- a/src/ballistica/shared/foundation/event_loop.h +++ b/src/ballistica/shared/foundation/event_loop.h @@ -25,10 +25,6 @@ class EventLoop { ThreadSource source = ThreadSource::kCreate); virtual ~EventLoop(); - void ClearCurrentThreadName(); - - static auto CurrentThreadName() -> std::string; - static void SetEventLoopsSuspended(bool enable); static auto AreEventLoopsSuspended() -> bool; @@ -113,7 +109,6 @@ class EventLoop { : type(type), runnable(runnable), completion_flag{completion_flag} {} }; auto CheckPushRunnableSafety_() -> bool; - void SetInternalThreadName_(const std::string& name); void WaitForNextEvent_(bool single_cycle); void LogThreadMessageTally_( std::vector>* log_entries); diff --git a/src/ballistica/shared/foundation/fatal_error.cc b/src/ballistica/shared/foundation/fatal_error.cc index be39d384..4bed5b6a 100644 --- a/src/ballistica/shared/foundation/fatal_error.cc +++ b/src/ballistica/shared/foundation/fatal_error.cc @@ -91,12 +91,16 @@ void FatalError::ReportFatalError(const std::string& message, if (trace) { std::string tracestr = trace->FormatForDisplay(); if (!tracestr.empty()) { - logmsg += ("\nCPP-STACK-TRACE-BEGIN:\n" + tracestr - + "\nCPP-STACK-TRACE-END"); + logmsg += + (("\n----------------------- BALLISTICA-NATIVE-STACK-TRACE-BEGIN " + "--------------------\n") + + tracestr + + ("\n----------------------- BALLISTICA-NATIVE-STACK-TRACE-END " + "----------------------")); } delete trace; } else { - logmsg += "\n(CPP-STACK-TRACE-UNAVAILABLE)"; + logmsg += "\n(BALLISTICA-NATIVE-STACK-TRACE-UNAVAILABLE)"; } } } @@ -155,12 +159,10 @@ void FatalError::DoBlockingFatalErrorDialog(const std::string& message) { bool* startedptr{&started}; bool* finishedptr{&finished}; - // If our thread is holding the GIL, release it to give the main thread - // a better chance of getting to the point of displaying the fatal - // error. - if (Python::HaveGIL()) { - Python::PermanentlyReleaseGIL(); - } + // If our thread is holding the GIL, release it while we spin; otherwise + // we can wind up in deadlock if the main thread wants it. + Python::ScopedInterpreterLockRelease gil_release; + g_base_soft->PushMainThreadRunnable( NewLambdaRunnableUnmanaged([message, startedptr, finishedptr] { *startedptr = true; diff --git a/src/ballistica/shared/networking/sockaddr.cc b/src/ballistica/shared/networking/sockaddr.cc index 68fdf993..6dd5b190 100644 --- a/src/ballistica/shared/networking/sockaddr.cc +++ b/src/ballistica/shared/networking/sockaddr.cc @@ -14,22 +14,48 @@ SockAddr::SockAddr(const std::string& addr, int port) { if (result == 1) { auto* a = reinterpret_cast(&addr_); a->sin_family = AF_INET; - a->sin_port = htons(port); // NOLINT + a->sin_port = htons(port); a->sin_addr = addr_out; return; } else { - struct in6_addr addr6Out {}; - result = inet_pton(AF_INET6, addr.c_str(), &addr6Out); + struct in6_addr addr6_out {}; + result = inet_pton(AF_INET6, addr.c_str(), &addr6_out); if (result == 1) { auto* a = reinterpret_cast(&addr_); a->sin6_family = AF_INET6; - a->sin6_port = htons(port); // NOLINT - a->sin6_addr = addr6Out; + a->sin6_port = htons(port); + a->sin6_addr = addr6_out; return; } } } - throw Exception("Invalid address: '" + addr + "'."); + throw Exception("Invalid address: '" + addr + "'.", PyExcType::kValue); +} + +auto SockAddr::AddressString() const -> std::string { + if (IsV6()) { + char ip_str[INET6_ADDRSTRLEN]; + if (inet_ntop(AF_INET6, &(AsSockAddrIn6()->sin6_addr), ip_str, + INET6_ADDRSTRLEN) + == nullptr) { + throw Exception("inet_ntop failed for v6 addr", PyExcType::kValue); + } + return ip_str; + } + char ip_str[INET_ADDRSTRLEN]; + if (inet_ntop(AF_INET, &(AsSockAddrIn()->sin_addr), ip_str, INET_ADDRSTRLEN) + == nullptr) { + throw Exception("inet_ntop failed for v4 addr", PyExcType::kValue); + } + return ip_str; +} + +auto SockAddr::Port() const -> int { + if (IsV6()) { + return ntohs(AsSockAddrIn6()->sin6_port); + } else { + return ntohs(AsSockAddrIn()->sin_port); + } } } // namespace ballistica diff --git a/src/ballistica/shared/networking/sockaddr.h b/src/ballistica/shared/networking/sockaddr.h index f372bf70..3e880c56 100644 --- a/src/ballistica/shared/networking/sockaddr.h +++ b/src/ballistica/shared/networking/sockaddr.h @@ -15,16 +15,33 @@ class SockAddr { public: SockAddr() { memset(&addr_, 0, sizeof(addr_)); } - // Creates from an ipv4 or ipv6 address string; - // throws an exception on error. + // Creates from an ipv4 or ipv6 address string; throws an exception on + // error. SockAddr(const std::string& addr, int port); + explicit SockAddr(const sockaddr_storage& addr_in) { addr_ = addr_in; assert(addr_.ss_family == AF_INET || addr_.ss_family == AF_INET6); } - auto GetSockAddr() const -> const sockaddr* { + + auto AsSockAddr() const -> const sockaddr* { return reinterpret_cast(&addr_); } + + auto AsSockAddrIn() const -> const sockaddr_in* { + assert(!IsV6()); + return reinterpret_cast(&addr_); + } + + auto AsSockAddrIn6() const -> const sockaddr_in6* { + assert(IsV6()); + return reinterpret_cast(&addr_); + } + + auto AddressString() const -> std::string; + + auto Port() const -> int; + auto GetSockAddrLen() const -> socklen_t { switch (addr_.ss_family) { case AF_INET: @@ -32,9 +49,10 @@ class SockAddr { case AF_INET6: return sizeof(sockaddr_in6); default: - throw Exception(); + throw Exception(PyExcType::kValue); } } + auto IsV6() const -> bool { switch (addr_.ss_family) { case AF_INET: @@ -45,25 +63,22 @@ class SockAddr { throw Exception(); } } + auto operator==(const SockAddr& other) const -> bool { if (addr_.ss_family != other.addr_.ss_family) return false; if (addr_.ss_family == AF_INET) { - return (reinterpret_cast(addr_).sin_addr.s_addr - == reinterpret_cast(other.addr_) - .sin_addr.s_addr) - && (reinterpret_cast(addr_).sin_port - == reinterpret_cast(other.addr_).sin_port); + auto* a1 = AsSockAddrIn(); + auto* a2 = other.AsSockAddrIn(); + return !memcmp(&(a1->sin_addr), &(a2->sin_addr), sizeof(in_addr)) + && a1->sin_port == a2->sin_port; } if (addr_.ss_family == AF_INET6) { - return !memcmp(&(reinterpret_cast(addr_).sin6_addr), - &(reinterpret_cast(other.addr_) - .sin6_addr), - sizeof(in6_addr)) - && (reinterpret_cast(addr_).sin6_port - == reinterpret_cast(other.addr_) - .sin6_port); + auto* a1 = AsSockAddrIn6(); + auto* a2 = other.AsSockAddrIn6(); + return !memcmp(&(a1->sin6_addr), &(a2->sin6_addr), sizeof(in6_addr)) + && a1->sin6_port == a2->sin6_port; } - throw Exception(); + throw Exception(PyExcType::kValue); } private: diff --git a/src/ballistica/shared/python/python.cc b/src/ballistica/shared/python/python.cc index 567a9831..0068077b 100644 --- a/src/ballistica/shared/python/python.cc +++ b/src/ballistica/shared/python/python.cc @@ -416,9 +416,7 @@ class Python::ScopedInterpreterLock::Impl { }; Python::ScopedInterpreterLock::ScopedInterpreterLock() - : impl_{new Python::ScopedInterpreterLock::Impl()} -// impl_{std::make_unique()} -{} + : impl_{new Python::ScopedInterpreterLock::Impl()} {} Python::ScopedInterpreterLock::~ScopedInterpreterLock() { delete impl_; } diff --git a/src/ballistica/shared/python/python.h b/src/ballistica/shared/python/python.h index 2a94c9e3..4d2e1615 100644 --- a/src/ballistica/shared/python/python.h +++ b/src/ballistica/shared/python/python.h @@ -41,7 +41,8 @@ class Python { /// Use this to protect Python code that may be run in cases where we /// don't hold the Global Interpreter Lock (GIL). (Basically anything - /// outside of the logic thread). + /// outside of the logic thread). This will release and then restore + /// the GIL if it is held initially; otherwise it is a no-op. class ScopedInterpreterLock { public: ScopedInterpreterLock(); @@ -49,9 +50,6 @@ class Python { private: class Impl; - // Note: should use unique_ptr for this, but build fails on raspberry pi - // (gcc 8.3.0). Works on Ubuntu 9.3 so should try again later. - // std::unique_ptr impl_{}; Impl* impl_{}; }; @@ -64,9 +62,6 @@ class Python { private: class Impl; - // Note: should use unique_ptr for this, but build fails on raspberry pi - // (gcc 8.3.0). Works on Ubuntu 9.3 so should try again later. - // std::unique_ptr impl_{}; Impl* impl_{}; }; diff --git a/src/ballistica/shared/python/python_ref.h b/src/ballistica/shared/python/python_ref.h index 141aeebf..b2455d26 100644 --- a/src/ballistica/shared/python/python_ref.h +++ b/src/ballistica/shared/python/python_ref.h @@ -118,7 +118,9 @@ class PythonRef { /// Release the held reference (if one is held). void Release(); - /// Clear the ref without decrementing its count and return the raw PyObject* + /// Clear the ref without decrementing its count and return the raw + /// PyObject*. Useful for functions that are expected to return a new + /// Python ref. auto HandOver() -> PyObject* { assert(obj_); PyObject* obj = obj_; @@ -151,8 +153,9 @@ class PythonRef { /// Throws Exception if an error occurs. auto DictGetItem(const char* name) const -> PythonRef; - /// The equivalent of calling Python str() on the contained PyObject. - /// Gracefully handles invalid refs. + /// The equivalent of calling Python str() on the contained PyObject, and + /// gracefully handles invalid refs. To throw exceptions on invalid refs, + /// use ValueAsString(); auto Str() const -> std::string; /// The equivalent of calling repr() on the contained PyObject. diff --git a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc index 0fd6279f..8c0aefe5 100644 --- a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc +++ b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc @@ -2589,90 +2589,6 @@ static PyMethodDef PyGetSpecialWidgetDef = { "(internal)", }; -// -------------------------- have_incentivized_ad ----------------------------- - -// returns an extra hash value that can be incorporated into security checks; -// this contains things like whether console commands have been run, etc. -static auto PyHaveIncentivizedAd(PyObject* self, PyObject* args, - PyObject* keywds) -> PyObject* { - BA_PYTHON_TRY; - static const char* kwlist[] = {nullptr}; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "", - const_cast(kwlist))) { - return nullptr; - } - if (g_core->have_incentivized_ad) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } - BA_PYTHON_CATCH; -} - -static PyMethodDef PyHaveIncentivizedAdDef = { - "have_incentivized_ad", // name - (PyCFunction)PyHaveIncentivizedAd, // method - METH_VARARGS | METH_KEYWORDS, // flags - - "have_incentivized_ad() -> bool\n" - "\n" - "(internal)", -}; - -// ----------------------------- can_show_ad ----------------------------------- - -// this returns whether it makes sense to show an currently -static auto PyCanShowAd(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { - BA_PYTHON_TRY; - - BA_PRECONDITION(g_base->InLogicThread()); - // if we've got any network connections, no ads. - // (don't want to make someone on the other end wait or risk disconnecting - // 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() - || g_base->input->HaveRemoteAppController()) { - Py_RETURN_FALSE; - } - Py_RETURN_TRUE; // all systems go.. - BA_PYTHON_CATCH; -} - -static PyMethodDef PyCanShowAdDef = { - "can_show_ad", // name - (PyCFunction)PyCanShowAd, // method - METH_VARARGS | METH_KEYWORDS, // flags - "can_show_ad() -> bool\n" - "\n" - "(internal)", -}; - -// ---------------------------- has_video_ads ---------------------------------- - -static auto PyHasVideoAds(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { - BA_PYTHON_TRY; - if (g_core->platform->GetHasVideoAds()) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } - BA_PYTHON_CATCH; -} - -static PyMethodDef PyHasVideoAdsDef = { - "has_video_ads", // name - (PyCFunction)PyHasVideoAds, // method - METH_VARARGS | METH_KEYWORDS, // flags - - "has_video_ads() -> bool\n" - "\n" - "(internal)", -}; - // ------------------------------ back_press ----------------------------------- static auto PyBackPress(PyObject* self, PyObject* args, PyObject* keywds) @@ -2893,9 +2809,6 @@ auto PythonMethodsUIV1::GetMethods() -> std::vector { PyOpenFileExternallyDef, PyOpenURLDef, PyBackPressDef, - PyHasVideoAdsDef, - PyCanShowAdDef, - PyHaveIncentivizedAdDef, PyGetSpecialWidgetDef, PySetPartyWindowOpenDef, PySetPartyIconAlwaysVisibleDef, diff --git a/src/meta/bascenev1meta/pyembed/binding_scene_v1.py b/src/meta/bascenev1meta/pyembed/binding_scene_v1.py index c4a19066..4757e713 100644 --- a/src/meta/bascenev1meta/pyembed/binding_scene_v1.py +++ b/src/meta/bascenev1meta/pyembed/binding_scene_v1.py @@ -10,6 +10,7 @@ from bascenev1._player import Player from bascenev1._dependency import AssetPackage from bascenev1._activity import Activity from bascenev1._session import Session +from bascenev1._net import HostInfo import _bascenev1 # The C++ layer looks for this variable: @@ -30,4 +31,5 @@ values = [ AssetPackage, # kAssetPackageClass Activity, # kActivityClass Session, # kSceneV1SessionClass + HostInfo, # kHostInfoClass ] diff --git a/tools/bacommon/app.py b/tools/bacommon/app.py index d1b50fda..2b8abdc6 100644 --- a/tools/bacommon/app.py +++ b/tools/bacommon/app.py @@ -5,22 +5,49 @@ from __future__ import annotations from enum import Enum -from typing import TYPE_CHECKING +from dataclasses import dataclass +from typing import TYPE_CHECKING, Annotated + +from efro.dataclassio import ioprepped, IOAttrs if TYPE_CHECKING: pass -class AppExperience(Enum): - """Overall experience that can be provided by a Ballistica app. +class AppInterfaceIdiom(Enum): + """A general form-factor or way of experiencing a Ballistica app. - This corresponds generally, but not exactly, to distinct apps built - with Ballistica. However, a single app may support multiple experiences, - or there may be multiple apps targeting one experience. Cloud components - such as leagues are generally associated with an AppExperience. + Note that it is possible for a running app to switch idioms (for + instance if a mobile device or computer is connected to a TV). """ - # A special experience category that is supported everywhere. Used + PHONE = 'phone' + TABLET = 'tablet' + DESKTOP = 'desktop' + TV = 'tv' + XR = 'xr' + + +class AppExperience(Enum): + """A particular experience that can be provided by a Ballistica app. + + This is one metric used to isolate different playerbases from + eachother where there might be no technical barriers doing so. + For example, a casual one-hand-playable phone game and an augmented + reality tabletop game may both use the same scene-versions and + networking-protocols and whatnot, but it would make no sense to + allow players of one join servers for the other. AppExperience can + be used to keep these player bases separate. + + Generally a single Ballistica app targets a single AppExperience. + This is not a technical requirement, however. A single app may + support multiple experiences, or there may be multiple apps + targeting one experience. Cloud components such as leagues are + generally associated with an AppExperience so that they are only + visible to client apps designed for that play style. + """ + + # An experience that is supported everywhere. Used # for the default empty AppMode when starting the app, etc. EMPTY = 'empty' @@ -33,3 +60,79 @@ class AppExperience(Enum): # touch-screen allowing a mobile device to be used as a game # controller. REMOTE = 'remote' + + +class AppArchitecture(Enum): + """Processor architecture the App is running on.""" + + ARM = 'arm' + ARM64 = 'arm64' + X86 = 'x86' + X86_64 = 'x86_64' + + +class AppPlatform(Enum): + """Overall platform a Ballistica build can be targeting. + + Each distinct flavor of an app has a unique combination + of AppPlatform and AppVariant. Generally platform describes + a set of hardware, while variant describes a destination or + purpose for the build. + """ + + MAC = 'mac' + WINDOWS = 'windows' + LINUX = 'linux' + ANDROID = 'android' + IOS = 'ios' + TVOS = 'tvos' + + +class AppVariant(Enum): + """A unique Ballistica build type within a single platform. + + Each distinct flavor of an app has a unique combination + of AppPlatform and AppVariant. Generally platform describes + a set of hardware, while variant describes a destination or + purpose for the build. + """ + + # Default builds. + GENERIC = 'generic' + + # Builds intended for public testing (may have some extra checks + # or logging enabled). + TEST = 'test' + + # Various stores. + AMAZON_APPSTORE = 'amazon_appstore' + GOOGLE_PLAY = 'google_play' + APP_STORE = 'app_store' + WINDOWS_STORE = 'windows_store' + STEAM = 'steam' + META = 'meta' + EPIC_GAMES_STORE = 'epic_games_store' + + # Other. + ARCADE = 'arcade' + DEMO = 'demo' + + +@ioprepped +@dataclass +class AppInstanceInfo: + """General info about an individual running app.""" + + name = Annotated[str, IOAttrs('n')] + version = Annotated[str, IOAttrs('v')] + build = Annotated[int, IOAttrs('b')] + + platform = Annotated[AppPlatform, IOAttrs('p')] + variant = Annotated[AppVariant, IOAttrs('va')] + architecture = Annotated[AppArchitecture, IOAttrs('a')] + os_version = Annotated[str | None, IOAttrs('o')] + + interface_idiom: Annotated[AppInterfaceIdiom, IOAttrs('i')] + locale: Annotated[str, IOAttrs('l')] + + device: Annotated[str | None, IOAttrs('d')] diff --git a/tools/batools/dummymodule.py b/tools/batools/dummymodule.py index 02bd6746..7fbf618e 100755 --- a/tools/batools/dummymodule.py +++ b/tools/batools/dummymodule.py @@ -216,6 +216,12 @@ def _writefuncs( 'import bascenev1 # pylint: disable=cyclic-import\n' 'return bascenev1.Time(0.0)' ) + elif returns == 'bascenev1.HostInfo | None': + returnstr = ( + 'import bascenev1 # pylint: disable=cyclic-import\n' + 'return bascenev1.HostInfo(\'dummyname\', -1,' + ' \'dummy_addr\', -1)' + ) elif returns == 'babase.DisplayTime': returnstr = ( 'import babase # pylint: disable=cyclic-import\n' diff --git a/tools/batools/project/_updater.py b/tools/batools/project/_updater.py index db616708..5d820d92 100755 --- a/tools/batools/project/_updater.py +++ b/tools/batools/project/_updater.py @@ -426,7 +426,6 @@ class ProjectUpdater: # from batools.xcode import update_xcode_project for projpath in [ - # 'ballisticakit-mac.xcodeproj/project.pbxproj', # 'ballisticakit-ios.xcodeproj/project.pbxproj', 'ballisticakit-xcode/BallisticaKit.xcodeproj/project.pbxproj', ]: diff --git a/tools/efrotools/openalbuild.py b/tools/efrotools/openalbuild.py index 8ccb2b4a..a53555b3 100644 --- a/tools/efrotools/openalbuild.py +++ b/tools/efrotools/openalbuild.py @@ -62,7 +62,7 @@ def build_openal(arch: str, mode: str) -> None: ) # subprocess.run(['git', 'checkout', '1.23.1'], check=True, cwd=builddir) subprocess.run( - ['git', 'checkout', '5b5b948516f7340810ebbfdd5e46eb40f85d2e56'], + ['git', 'checkout', 'b81a270f6c1e795ca70d7684e0ccf35a19f247e2'], check=True, cwd=builddir, ) From b4017bcefd0d0524e6cae38486bf63953791b048 Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 14 Dec 2023 10:22:47 -0800 Subject: [PATCH 10/10] Got about section cleaned up for new discord button --- .efrocachemap | 10 +- CHANGELOG.md | 4 +- .../ba_data/python/bauiv1lib/discord.py | 6 +- .../python/bauiv1lib/gather/abouttab.py | 142 +++++++++++------- 4 files changed, 103 insertions(+), 59 deletions(-) diff --git a/.efrocachemap b/.efrocachemap index 375613f0..b656e348 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -421,21 +421,21 @@ "build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55", - "build/assets/ba_data/data/langdata.json": "4a35cc51d1021fa7e525123bcf99043c", + "build/assets/ba_data/data/langdata.json": "f200cdf431b9494d8b96cdd47e950dd1", "build/assets/ba_data/data/languages/arabic.json": "00ba700de6c672a56658a6bd1ad27523", - "build/assets/ba_data/data/languages/belarussian.json": "7fe38341815ca6ff4d95224196e7a67e", + "build/assets/ba_data/data/languages/belarussian.json": "40883823367f04c5a2403a96525bfcc3", "build/assets/ba_data/data/languages/chinese.json": "5761468d25f2bd4e79921826cebd572b", "build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078", "build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa", "build/assets/ba_data/data/languages/czech.json": "cd21ad8c6b8e9ed700284cf1e1aecbf8", "build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e", "build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e", - "build/assets/ba_data/data/languages/english.json": "bd43b77b1ccca059573acbde148b4767", + "build/assets/ba_data/data/languages/english.json": "6a3fab4fb8b2879e00ed9877709bf504", "build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880", - "build/assets/ba_data/data/languages/filipino.json": "0f5ad7c06db70027b116dfd9324bdf67", + "build/assets/ba_data/data/languages/filipino.json": "6f4051ce78861a4666f4978d6f9a0ed2", "build/assets/ba_data/data/languages/french.json": "49ff6d211537b8003b8241438dca661d", "build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad", - "build/assets/ba_data/data/languages/gibberish.json": "9aae526303a22372fe9b4cf1781520ef", + "build/assets/ba_data/data/languages/gibberish.json": "25fcb5130fae56985bee175aa19f86a2", "build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3", "build/assets/ba_data/data/languages/hindi.json": "8848f6b0caec0fcf9d85bc6e683809ec", "build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e", diff --git a/CHANGELOG.md b/CHANGELOG.md index 34c408fd..96ff9ebd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ ### 1.7.31 (build 21707, api 8, 2023-12-13) -- added `bascenev1.get_connection_to_host_info_2()` which is an improved +- Added `bascenev1.get_connection_to_host_info_2()` which is an improved type-safe version of `bascenev1.get_connection_to_host_info()`. +- There is now a link to the official Discord server in the About section + (thanks EraOSBeta!). ### 1.7.30 (build 21697, api 8, 2023-12-08) - Continued work on the big 1.7.28 update. diff --git a/src/assets/ba_data/python/bauiv1lib/discord.py b/src/assets/ba_data/python/bauiv1lib/discord.py index 6021d273..2e2eb520 100644 --- a/src/assets/ba_data/python/bauiv1lib/discord.py +++ b/src/assets/ba_data/python/bauiv1lib/discord.py @@ -73,6 +73,7 @@ class DiscordWindow(bui.Window): edit=self._root_widget, cancel_button=self._back_button ) + # Do we need to translate 'Discord'? Or is that always the name? self._title_text = bui.textwidget( parent=self._root_widget, position=(0, self._height - 52), @@ -91,6 +92,9 @@ class DiscordWindow(bui.Window): texture=bui.gettexture('discordServer'), ) + # Hmm should we translate this? The discord server is mostly + # English so being able to read this might be a good screening + # process?.. bui.textwidget( parent=self._root_widget, position=(self._width / 2 - 60, self._height - 100), @@ -110,7 +114,7 @@ class DiscordWindow(bui.Window): position=(self._width / 2 - 30, 20), size=(self._width / 2 - 60, 60), autoselect=True, - label='Join The Discord', + label=bui.Lstr(resource='discordJoinText'), text_scale=1.0, on_activate_call=bui.Call( bui.open_url, 'https://ballistica.net/discord' diff --git a/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py b/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py index 840ab400..b61ed339 100644 --- a/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py +++ b/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py @@ -16,10 +16,6 @@ if TYPE_CHECKING: class AboutGatherTab(GatherTab): """The about tab in the gather UI""" - def __init__(self, window: GatherWindow) -> None: - super().__init__(window) - self._container: bui.Widget | None = None - def on_activate( self, parent_widget: bui.Widget, @@ -34,6 +30,40 @@ class AboutGatherTab(GatherTab): plus = bui.app.plus assert plus is not None + try_tickets = plus.get_v1_account_misc_read_val( + 'friendTryTickets', None + ) + + show_message = True + # Squish message as needed to get things to fit nicely at + # various scales. + uiscale = bui.app.ui_v1.uiscale + message_height = ( + 210 + if uiscale is bui.UIScale.SMALL + else 305 + if uiscale is bui.UIScale.MEDIUM + else 370 + ) + # Let's not talk about sharing in vr-mode; its tricky to fit more + # than one head in a VR-headset. + show_message_extra = not bui.app.env.vr + message_extra_height = 60 + show_invite = try_tickets is not None + invite_height = 80 + show_discord = True + discord_height = 80 + + c_height = 0 + if show_message: + c_height += message_height + if show_message_extra: + c_height += message_extra_height + if show_invite: + c_height += invite_height + if show_discord: + c_height += discord_height + party_button_label = bui.charstr(bui.SpecialChar.TOP_BUTTON) message = bui.Lstr( resource='gatherWindow.aboutDescriptionText', @@ -43,9 +73,7 @@ class AboutGatherTab(GatherTab): ], ) - # Let's not talk about sharing in vr-mode; its tricky to fit more - # than one head in a VR-headset ;-) - if not bui.app.env.vr: + if show_message_extra: message = bui.Lstr( value='${A}\n\n${B}', subs=[ @@ -59,46 +87,52 @@ class AboutGatherTab(GatherTab): ), ], ) - string_height = 400 - include_invite = True - include_discord = False # Need to fix spacing on small first. - msc_scale = 1.1 - c_height_2 = min(region_height, string_height * msc_scale + 100) - try_tickets = plus.get_v1_account_misc_read_val( - 'friendTryTickets', None - ) - if try_tickets is None: - include_invite = False - self._container = bui.containerwidget( + scroll_widget = bui.scrollwidget( parent=parent_widget, + position=(region_left, region_bottom), + size=(region_width, region_height), + highlight=False, + border_opacity=0, + ) + msc_scale = 1.1 + + container = bui.containerwidget( + parent=scroll_widget, position=( region_left, - region_bottom + (region_height - c_height_2) * 0.5, + region_bottom + (region_height - c_height) * 0.5, ), - size=(region_width, c_height_2), + size=(region_width, c_height), background=False, - selectable=include_invite or include_discord, + selectable=show_invite or show_discord, ) - bui.widget(edit=self._container, up_widget=tab_button) + # Allows escaping if we select the container somehow (though + # shouldn't be possible when buttons are present). + bui.widget(edit=container, up_widget=tab_button) - bui.textwidget( - parent=self._container, - position=(region_width * 0.5, c_height_2 * 0.58), - color=(0.6, 1.0, 0.6), - scale=msc_scale, - size=(0, 0), - maxwidth=region_width * 0.9, - max_height=c_height_2 * 0.7, - h_align='center', - v_align='center', - text=message, - ) - - if include_invite: + y = c_height - 30 + if show_message: bui.textwidget( - parent=self._container, - position=(region_width * 0.57, 35), + parent=container, + position=(region_width * 0.5, y), + color=(0.6, 1.0, 0.6), + scale=msc_scale, + size=(0, 0), + maxwidth=region_width * 0.9, + max_height=message_height, + h_align='center', + v_align='top', + text=message, + ) + y -= message_height + if show_message_extra: + y -= message_extra_height + + if show_invite: + bui.textwidget( + parent=container, + position=(region_width * 0.57, y), color=(0, 1, 0), scale=0.6, size=(0, 0), @@ -112,8 +146,8 @@ class AboutGatherTab(GatherTab): ), ) invite_button = bui.buttonwidget( - parent=self._container, - position=(region_width * 0.59, 10), + parent=container, + position=(region_width * 0.59, y - 25), size=(230, 50), color=(0.54, 0.42, 0.56), textcolor=(0, 1, 0), @@ -125,13 +159,14 @@ class AboutGatherTab(GatherTab): on_activate_call=bui.WeakCall(self._invite_to_try_press), up_widget=tab_button, ) + y -= invite_height else: invite_button = None - if include_discord: + if show_discord: bui.textwidget( - parent=self._container, - position=(region_width * 0.57, 15 if include_invite else 75), + parent=container, + position=(region_width * 0.57, y), color=(0.6, 0.6, 1), scale=0.6, size=(0, 0), @@ -139,26 +174,29 @@ class AboutGatherTab(GatherTab): h_align='right', v_align='center', flatness=1.0, - text=( - 'Want to look for new people to play with?\n' - 'Join our Discord and find new friends!' - ), + text=bui.Lstr(resource='discordFriendsText'), ) - bui.buttonwidget( - parent=self._container, - position=(region_width * 0.59, -10 if include_invite else 50), + discord_button = bui.buttonwidget( + parent=container, + position=(region_width * 0.59, y - 25), size=(230, 50), color=(0.54, 0.42, 0.56), textcolor=(0.6, 0.6, 1), - label='Join The Discord', + label=bui.Lstr(resource='discordJoinText'), autoselect=True, on_activate_call=bui.WeakCall(self._join_the_discord_press), up_widget=( invite_button if invite_button is not None else tab_button ), ) + y -= discord_height + else: + discord_button = None - return self._container + if discord_button is not None: + pass + + return scroll_widget def _invite_to_try_press(self) -> None: from bauiv1lib.account import show_sign_in_prompt