From 115fd5eac68d76a447a38c9d6e969e1b14967006 Mon Sep 17 00:00:00 2001 From: Eric Date: Fri, 8 Dec 2023 08:46:45 -0800 Subject: [PATCH] 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'