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(