android polishing and bug fixes

This commit is contained in:
Eric 2023-12-01 16:26:13 -08:00
parent 0e15df6f59
commit 2a5e9768db
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
50 changed files with 657 additions and 452 deletions

88
.efrocachemap generated
View File

@ -4060,50 +4060,50 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "c2b80379179c8731be37581e52259377", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "84b6be9d4a7d8a544993006751c7f632",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "0a2257e46a20ae6453d888515a00f1a8", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "f87ec55a2f3732de69bd7ca56366ac5e",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "a8bf3602e161931f96c41989d9d4e630", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cc88073fd308bca9e681fa585cc8f534",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "15e5d036605366840182cf3f86d247ae", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a671348d891eb53c6882047f270c46ed",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "4212f5014bcf30f5ceb6e9ed00c1b443", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "667d5118d18ec5e9e822224868d24380",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a9ee1b8f07dc7466b4daf90a34991f3b", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "f49b0ac8dc49ef622c2da81de1134425",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "cbc9bf3f8ddce331912b6dbd8f1c6415", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "b38fb26bbcc45d9ac3990a4b4b2cec0a",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "6b4458aa454391fa0070099ef4cac711", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "3100977be9a4cb9271d1ee60d00afd87",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "fae27ee9108877bd75aaa222f02f239d", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1bd4a1960660c6f1b7c355de6d3d8658",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "37f2b4219ffe85aa1d28ab7df7fd4c44", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "3674cde02fbe0059d3f7281344970d16",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "3f8de90abf2069a0881f8d91f1ec78b2", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "384f40a3d4652323c218f1dfa650ec17",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "62a6ee7583ef9d83f9bb1601fce6ddaa", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "cdf0902eeacf4667ddae3b489fb9dd38",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "f495a8547ca8b824f80006603537d1cf", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "283aaa1498e719442445c6e24297d5f9",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "672c351fca7843d85a2be7aba13faf1f", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "7b0facb90fc8b2918cb124c0dcd360af",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "50f6d105ad4c2bb2df4f3d335b6d2cfa", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "981d0be875bb37e94840040ff41cf52e",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "44e1d633accc2410ca6d825e4c464f45", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f850af922341d32d237c249daef4383c",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5cce0ce595304313ac28c02e235f19d7", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "d3a3e7fb95fc7fbe586646134060d4e2",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "9433fc9da2f4b7255f2c7fa95868604a", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "f1a9d399fa0313ee35797eec3b6a465a",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "18eb1871a6db9bbe17f5e9678d3d492a", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "031e6fd40465f5b8d9f6390efcb7e82d",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "cf51dafe0553d06a44349d0911c21d71", "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "4c8f3095b6a700d1c9a73aabaee98e63",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "a27963487e346338e4c216bd4fbb9e2a", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "e3c41c240bb333fc53240f64d6e9583e",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "a27963487e346338e4c216bd4fbb9e2a", "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "e3c41c240bb333fc53240f64d6e9583e",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "2663c888aec894656bd8c49932bd7729", "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "0b0ec8be8c575beba2935fdc9aa03ce5",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "5e57d12a3cfcfbc47b0293c3cb9fdca9", "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "04c21c4226944f71230420f9b399d1e4",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "2663c888aec894656bd8c49932bd7729", "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "0b0ec8be8c575beba2935fdc9aa03ce5",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "5e57d12a3cfcfbc47b0293c3cb9fdca9", "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "04c21c4226944f71230420f9b399d1e4",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "0f7dbe6fb3e28a51904aa822b509da0f", "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "e80ffa41dcc78dbd75baf89fadb096b4",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "081b766945b52460a4f1afc01faa0652", "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "6514628d8ce1c046726de69fa0086613",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "0f7dbe6fb3e28a51904aa822b509da0f", "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "e80ffa41dcc78dbd75baf89fadb096b4",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "081b766945b52460a4f1afc01faa0652", "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "6514628d8ce1c046726de69fa0086613",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "ad609c63f68417d5211bbfb23ce4affe", "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "9f4f5c5043d66fa3bdeb16b0599b5de4",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "852fe46c736082611a831a618923c241", "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "36fbda7829ed5c2862c34feb09b03402", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "a3124c863c4b80de5acc20a7c9d49492",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "852fe46c736082611a831a618923c241", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "8ec03141da397548f8a06259222c14e3", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "9eba00c4b0b0e11a16d8396f26d34868",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9f20a365cc0ec1831e0e301a34198b0e", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "fb9041aadad56623332d3fa97684784e",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "37980dc34b9ad281cdab738f7053af26", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "34c36a14ff8a8d7661f923cc8f1149c9",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "148e0f3a1ee77607d0b93a636b253295", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "e87159408ee82988c641c963cb53c4e3",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "e20a2aa49741757075634f422dc5ac7a", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "569e1aac13b5bb8abd7df8e91ecc7554",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "fa7018f7c2ee8c952bff7879b92709cb", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "d85d2e4cbcaf1c5fe2a3c984d35a4ed8",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "cd38a41872e74b43de005375330e3cd0", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "5a33774c2533bfd695facb4a68767bb8",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "5bc4faf70f09f56e2ced27cafe2ad3e6", "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/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f", "src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",

View File

@ -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. - Continued work on the big 1.7.28 update.
- Got the Android version back up and running. There's been lots of cleanup and - 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 simplification on the Android layer, cleaning out years of cruft. This should

View File

@ -229,6 +229,15 @@ class App:
self.lang = LanguageSubsystem() self.lang = LanguageSubsystem()
self.plugins = PluginSubsystem() 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 @property
def aioloop(self) -> asyncio.AbstractEventLoop: def aioloop(self) -> asyncio.AbstractEventLoop:
"""The logic thread's asyncio event loop. """The logic thread's asyncio event loop.

View File

@ -4,6 +4,8 @@
from __future__ import annotations from __future__ import annotations
import time import time
import asyncio
import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import babase import babase
@ -31,6 +33,7 @@ class AdsSubsystem:
self.last_in_game_ad_remove_message_show_time: float | None = None self.last_in_game_ad_remove_message_show_time: float | None = None
self.last_ad_completion_time: float | None = None self.last_ad_completion_time: float | None = None
self.last_ad_was_short = False self.last_ad_was_short = False
self._fallback_task: asyncio.Task | None = None
def do_remove_in_game_ads_message(self) -> None: def do_remove_in_game_ads_message(self) -> None:
"""(internal)""" """(internal)"""
@ -181,36 +184,53 @@ class AdsSubsystem:
# If we're *still* cleared to show, actually tell the system to show. # If we're *still* cleared to show, actually tell the system to show.
if show: if show:
# As a safety-check, set up an object that will run # As a safety-check, we set up an object that will run the
# the completion callback if we've returned and sat for 10 seconds # completion callback if we've returned and sat for several
# (in case some random ad network doesn't properly deliver its # seconds (in case some random ad network doesn't properly
# completion callback). # deliver its completion callback).
class _Payload: class _Payload:
def __init__(self, pcall: Callable[[], Any]): def __init__(self, pcall: Callable[[], Any]):
self._call = pcall self._call = pcall
self._ran = False self._ran = False
def run(self, fallback: bool = False) -> None: 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 assert app.classic is not None
if not self._ran: if not self._ran:
if fallback: if fallback:
lanst = app.classic.ads.last_ad_network_set_time lanst = app.classic.ads.last_ad_network_set_time
print( logging.error(
'ERROR: relying on fallback ad-callback! ' 'Relying on fallback ad-callback! '
'last network: ' 'last network: %s (set %s seconds ago);'
+ app.classic.ads.last_ad_network ' purpose=%s.',
+ ' (set ' app.classic.ads.last_ad_network,
+ str(int(time.time() - lanst)) time.time() - lanst,
+ 's ago); purpose=' app.classic.ads.last_ad_purpose,
+ app.classic.ads.last_ad_purpose
) )
babase.pushcall(self._call) babase.pushcall(self._call)
self._ran = True self._ran = True
payload = _Payload(call) payload = _Payload(call)
# Set up our backup.
with babase.ContextRef.empty(): 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) self.show_ad('between_game', on_completion_call=payload.run)
else: else:
babase.pushcall(call) # Just run the callback without the ad. babase.pushcall(call) # Just run the callback without the ad.

View File

@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be # Build number and version of the ballistica binary we expect to be
# using. # using.
TARGET_BALLISTICA_BUILD = 21639 TARGET_BALLISTICA_BUILD = 21652
TARGET_BALLISTICA_VERSION = '1.7.30' TARGET_BALLISTICA_VERSION = '1.7.30'

View File

@ -551,9 +551,11 @@ def show_offer() -> bool:
if bui.native_review_request_supported(): if bui.native_review_request_supported():
bui.native_review_request() bui.native_review_request()
else: else:
feedback.ask_for_rating() if app.ui_v1.available:
feedback.ask_for_rating()
else: else:
SpecialOfferWindow(app.classic.special_offer) if app.ui_v1.available:
SpecialOfferWindow(app.classic.special_offer)
app.classic.special_offer = None app.classic.special_offer = None
return True return True

View File

@ -25,138 +25,13 @@ void AppAdapter::OnMainThreadStartApp() {
} }
void AppAdapter::OnAppStart() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppStart() { assert(g_base->InLogicThread()); }
void AppAdapter::OnAppPause() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppSuspend() { assert(g_base->InLogicThread()); }
void AppAdapter::OnAppResume() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppUnsuspend() { assert(g_base->InLogicThread()); }
void AppAdapter::OnAppShutdown() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppShutdown() { assert(g_base->InLogicThread()); }
void AppAdapter::OnAppShutdownComplete() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppShutdownComplete() { assert(g_base->InLogicThread()); }
void AppAdapter::OnScreenSizeChange() { assert(g_base->InLogicThread()); } void AppAdapter::OnScreenSizeChange() { assert(g_base->InLogicThread()); }
void AppAdapter::DoApplyAppConfig() { 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() { void AppAdapter::RunMainThreadEventLoopToCompletion() {
FatalError("RunMainThreadEventLoopToCompletion is not implemented here."); FatalError("RunMainThreadEventLoopToCompletion is not implemented here.");
} }
@ -242,41 +117,6 @@ auto AppAdapter::GetGraphicsClientContext() -> GraphicsClientContext* {
auto AppAdapter::GetKeyRepeatDelay() -> float { return 0.3f; } auto AppAdapter::GetKeyRepeatDelay() -> float { return 0.3f; }
auto AppAdapter::GetKeyRepeatInterval() -> float { return 0.08f; } 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::DoClipboardIsSupported() -> bool { return false; }
auto AppAdapter::DoClipboardHasText() -> bool { auto AppAdapter::DoClipboardHasText() -> bool {
@ -311,4 +151,8 @@ void AppAdapter::NativeReviewRequest() {
void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); } void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); }
auto AppAdapter::ShouldSilenceAudioWhenInactive() -> bool const {
return false;
}
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -22,8 +22,8 @@ class AppAdapter {
// Logic thread callbacks. // Logic thread callbacks.
virtual void OnAppStart(); virtual void OnAppStart();
virtual void OnAppPause(); virtual void OnAppSuspend();
virtual void OnAppResume(); virtual void OnAppUnsuspend();
virtual void OnAppShutdown(); virtual void OnAppShutdown();
virtual void OnAppShutdownComplete(); virtual void OnAppShutdownComplete();
virtual void OnScreenSizeChange(); virtual void OnScreenSizeChange();
@ -88,9 +88,9 @@ class AppAdapter {
/// plugged in or unplugged/etc. Default implementation returns true. /// plugged in or unplugged/etc. Default implementation returns true.
virtual auto ShouldUseCursor() -> bool; virtual auto ShouldUseCursor() -> bool;
/// Return whether the app-adapter is having the OS show a cursor. /// Return whether the app-adapter is having the OS show a cursor. If this
/// If this returns false, the engine will take care of drawing a cursor /// returns false, the engine will take care of drawing a cursor when
/// when necessary. If true, SetHardwareCursorVisible will be called /// necessary. If true, SetHardwareCursorVisible will be called
/// periodically to inform the adapter what the cursor state should be. /// periodically to inform the adapter what the cursor state should be.
/// The default implementation returns false; /// The default implementation returns false;
virtual auto HasHardwareCursor() -> bool; virtual auto HasHardwareCursor() -> bool;
@ -106,21 +106,6 @@ class AppAdapter {
/// values. /// values.
virtual void CursorPositionForDraw(float* x, float* y); 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 /// Return whether this AppAdapter supports a 'fullscreen' toggle for its
/// display. This will affect whether that option is available in display /// display. This will affect whether that option is available in display
/// settings or via a hotkey. Must be called from the logic thread. /// 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. /// Return whether this AppAdapter supports max-fps controls for its display.
virtual auto SupportsMaxFPS() -> bool const; 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 /// Return whether this platform supports soft-quit. A soft quit is
/// when the app is reset/backgrounded/etc. but remains running in case /// when the app is reset/backgrounded/etc. but remains running in case
/// needed again. Generally this is the behavior on mobile apps. /// needed again. Generally this is the behavior on mobile apps.
@ -206,22 +197,6 @@ class AppAdapter {
virtual auto GetKeyRepeatDelay() -> float; virtual auto GetKeyRepeatDelay() -> float;
virtual auto GetKeyRepeatInterval() -> 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 /// Push a raw pointer Runnable to the platform's 'main' thread. The main
/// thread should call its RunAndLogErrors() method and then delete it. /// thread should call its RunAndLogErrors() method and then delete it.
virtual void DoPushMainThreadRunnable(Runnable* runnable) = 0; virtual void DoPushMainThreadRunnable(Runnable* runnable) = 0;
@ -239,25 +214,19 @@ class AppAdapter {
/// Asynchronously kick off a native review request. /// Asynchronously kick off a native review request.
void NativeReviewRequest(); void NativeReviewRequest();
protected:
virtual ~AppAdapter();
virtual auto DoClipboardIsSupported() -> bool; virtual auto DoClipboardIsSupported() -> bool;
virtual auto DoClipboardHasText() -> bool; virtual auto DoClipboardHasText() -> bool;
virtual void DoClipboardSetText(const std::string& text); virtual void DoClipboardSetText(const std::string& text);
virtual auto DoClipboardGetText() -> std::string; virtual auto DoClipboardGetText() -> std::string;
protected:
virtual ~AppAdapter();
/// Override to implement native review requests. Will be called in the /// Override to implement native review requests. Will be called in the
/// main thread. /// main thread.
virtual void DoNativeReviewRequest(); virtual void DoNativeReviewRequest();
private: private:
void OnAppSuspend_();
void OnAppUnsuspend_();
bool app_suspended_{};
bool have_clipboard_is_supported_{};
bool clipboard_is_supported_{};
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -66,7 +66,6 @@ class AppAdapterApple : public AppAdapter {
private: private:
class ScopedAllowGraphics_; class ScopedAllowGraphics_;
// void UpdateScreenSizes_();
void ReloadRenderer_(const GraphicsSettings* settings); void ReloadRenderer_(const GraphicsSettings* settings);
std::thread::id graphics_thread_{}; std::thread::id graphics_thread_{};

View File

@ -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 // 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 // change. We don't do this on other platforms where a maximized
// window is more distinctly different than a fullscreen one. // 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; fullscreen_ = true;
g_base->logic->event_loop()->PushCall([] { g_base->logic->event_loop()->PushCall([] {
g_base->python->objs() g_base->python->objs()
@ -497,18 +500,22 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
break; break;
case SDL_WINDOWEVENT_HIDDEN: { case SDL_WINDOWEVENT_HIDDEN: {
// Let's keep track of when we're hidden so we can stop drawing // We plug this into the app's overall 'Active' state so it can
// and sleep more. Theoretically we could put the app into a full // pause stuff or throttle down processing or whatever else.
// suspended state like we do on mobile (pausing event loops/etc.) if (!hidden_) {
// but that would be more involved; we'd need to ignore most SDL g_base->SetAppActive(false);
// events while sleeping (except for SDL_WINDOWEVENT_SHOWN) and }
// would need to rebuild our controller lists/etc when we resume. // Also note that we are *completely* hidden, so we can totally
// For now just gonna keep things simple and keep running. // stop drawing ('Inactive' app state does not imply this in and
// of itself).
hidden_ = true; hidden_ = true;
break; break;
} }
case SDL_WINDOWEVENT_SHOWN: { case SDL_WINDOWEVENT_SHOWN: {
if (hidden_) {
g_base->SetAppActive(true);
}
hidden_ = false; hidden_ = false;
break; break;
} }

View File

@ -14,8 +14,8 @@ void AppMode::OnActivate() {}
void AppMode::OnDeactivate() {} void AppMode::OnDeactivate() {}
void AppMode::OnAppStart() {} void AppMode::OnAppStart() {}
void AppMode::OnAppPause() {} void AppMode::OnAppSuspend() {}
void AppMode::OnAppResume() {} void AppMode::OnAppUnsuspend() {}
void AppMode::OnAppShutdown() {} void AppMode::OnAppShutdown() {}
void AppMode::OnAppShutdownComplete() {} void AppMode::OnAppShutdownComplete() {}

View File

@ -26,8 +26,8 @@ class AppMode {
/// Logic thread callbacks that run while the app-mode is active. /// Logic thread callbacks that run while the app-mode is active.
virtual void OnAppStart(); virtual void OnAppStart();
virtual void OnAppPause(); virtual void OnAppSuspend();
virtual void OnAppResume(); virtual void OnAppUnsuspend();
virtual void OnAppShutdown(); virtual void OnAppShutdown();
virtual void OnAppShutdownComplete(); virtual void OnAppShutdownComplete();
virtual void DoApplyAppConfig(); virtual void DoApplyAppConfig();

View File

@ -33,9 +33,9 @@ void Audio::Reset() {
void Audio::OnAppStart() { assert(g_base->InLogicThread()); } 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()); } void Audio::OnAppShutdown() { assert(g_base->InLogicThread()); }

View File

@ -21,8 +21,8 @@ class Audio {
void Reset(); void Reset();
virtual void OnAppStart(); virtual void OnAppStart();
virtual void OnAppPause(); virtual void OnAppSuspend();
virtual void OnAppResume(); virtual void OnAppUnsuspend();
virtual void OnAppShutdown(); virtual void OnAppShutdown();
virtual void OnAppShutdownComplete(); virtual void OnAppShutdownComplete();
virtual void DoApplyAppConfig(); virtual void DoApplyAppConfig();

View File

@ -4,6 +4,7 @@
#include <algorithm> #include <algorithm>
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/assets/assets.h" #include "ballistica/base/assets/assets.h"
#include "ballistica/base/assets/sound_asset.h" #include "ballistica/base/assets/sound_asset.h"
#include "ballistica/base/audio/al_sys.h" #include "ballistica/base/audio/al_sys.h"
@ -111,7 +112,7 @@ class AudioServer::ThreadSource_ : public Object {
std::unique_ptr<AudioSource> client_source_; std::unique_ptr<AudioSource> client_source_;
float fade_{1.0f}; float fade_{1.0f};
float gain_{1.0f}; float gain_{1.0f};
AudioServer* audio_thread_{}; AudioServer* audio_server_{};
bool valid_{}; bool valid_{};
const Object::Ref<SoundAsset>* source_sound_{}; const Object::Ref<SoundAsset>* source_sound_{};
int id_{}; int id_{};
@ -343,13 +344,13 @@ void AudioServer::SetSuspended_(bool suspend) {
try { try {
alcDevicePauseSOFT(device); alcDevicePauseSOFT(device);
} catch (const std::exception& e) { } catch (const std::exception& e) {
g_core->platform->DebugLog( g_core->platform->LowLevelDebugLog(
std::string("EXC pausing alcDevice: ") std::string("EXC pausing alcDevice: ")
+ g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " " + g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " "
+ e.what()); + e.what());
throw; throw;
} catch (...) { } catch (...) {
g_core->platform->DebugLog("UNKNOWN EXC pausing alcDevice"); g_core->platform->LowLevelDebugLog("UNKNOWN EXC pausing alcDevice");
throw; throw;
} }
#endif #endif
@ -381,13 +382,13 @@ void AudioServer::SetSuspended_(bool suspend) {
try { try {
alcDeviceResumeSOFT(device); alcDeviceResumeSOFT(device);
} catch (const std::exception& e) { } catch (const std::exception& e) {
g_core->platform->DebugLog( g_core->platform->LowLevelDebugLog(
std::string("EXC resuming alcDevice: ") std::string("EXC resuming alcDevice: ")
+ g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " " + g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " "
+ e.what()); + e.what());
throw; throw;
} catch (...) { } catch (...) {
g_core->platform->DebugLog("UNKNOWN EXC resuming alcDevice"); g_core->platform->LowLevelDebugLog("UNKNOWN EXC resuming alcDevice");
throw; throw;
} }
#endif #endif
@ -689,7 +690,7 @@ void AudioServer::Process_() {
assert(g_base->InAudioThread()); assert(g_base->InAudioThread());
millisecs_t real_time = g_core->GetAppTimeMillisecs(); 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_) { if (!suspended_ && !shutting_down_) {
// Do some loading... // Do some loading...
have_pending_loads_ = g_base->assets->RunPendingAudioLoads(); 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 #if BA_ENABLE_AUDIO
CHECK_AL_ERROR; CHECK_AL_ERROR;
#endif #endif
@ -792,9 +808,9 @@ void AudioServer::FadeSoundOut(uint32_t play_id, uint32_t time) {
// delete c; // delete c;
// } // }
AudioServer::ThreadSource_::ThreadSource_(AudioServer* audio_thread_in, AudioServer::ThreadSource_::ThreadSource_(AudioServer* audio_server_in,
int id_in, bool* valid_out) 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 #if BA_ENABLE_AUDIO
assert(g_core); assert(g_core);
assert(valid_out != nullptr); assert(valid_out != nullptr);
@ -839,10 +855,10 @@ AudioServer::ThreadSource_::~ThreadSource_() {
Stop(); Stop();
// Remove us from sources list. // Remove us from sources list.
for (auto i = audio_thread_->sources_.begin(); for (auto i = audio_server_->sources_.begin();
i != audio_thread_->sources_.end(); ++i) { i != audio_server_->sources_.end(); ++i) {
if (*i == this) { if (*i == this) {
audio_thread_->sources_.erase(i); audio_server_->sources_.erase(i);
break; break;
} }
} }
@ -1079,12 +1095,12 @@ void AudioServer::ThreadSource_::ExecPlay() {
looping_ = false; looping_ = false;
// Push us on the list of streaming sources if we're not on it. // 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) { if (i == this) {
throw Exception(); throw Exception();
} }
} }
audio_thread_->streaming_sources_.push_back(this); audio_server_->streaming_sources_.push_back(this);
// Make sure stereo sounds aren't positional. // Make sure stereo sounds aren't positional.
// This is default behavior on Mac/Win, but we enforce it for linux. // This is default behavior on Mac/Win, but we enforce it for linux.
@ -1162,10 +1178,10 @@ void AudioServer::ThreadSource_::ExecStop() {
if (streamer_.Exists()) { if (streamer_.Exists()) {
assert(is_streamed_); assert(is_streamed_);
streamer_->Stop(); streamer_->Stop();
for (auto i = audio_thread_->streaming_sources_.begin(); for (auto i = audio_server_->streaming_sources_.begin();
i != audio_thread_->streaming_sources_.end(); ++i) { i != audio_server_->streaming_sources_.end(); ++i) {
if (*i == this) { if (*i == this) {
audio_thread_->streaming_sources_.erase(i); audio_server_->streaming_sources_.erase(i);
break; break;
} }
} }
@ -1182,15 +1198,16 @@ void AudioServer::ThreadSource_::ExecStop() {
void AudioServer::ThreadSource_::UpdateVolume() { void AudioServer::ThreadSource_::UpdateVolume() {
#if BA_ENABLE_AUDIO #if BA_ENABLE_AUDIO
assert(g_base->InAudioThread()); assert(g_base->InAudioThread());
if (g_base->audio_server->suspended_ if (audio_server_->suspended_ || audio_server_->shutting_down_) {
|| g_base->audio_server->shutting_down_) {
return; return;
} }
float val = gain_ * fade_; float val = gain_ * fade_;
val *= audio_server_->app_active_volume_;
if (current_is_music()) { if (current_is_music()) {
val *= audio_thread_->music_volume_ / 7.0f; val *= audio_server_->music_volume_ / 7.0f;
} else { } else {
val *= audio_thread_->sound_volume_; val *= audio_server_->sound_volume_;
} }
alSourcef(source_, AL_GAIN, std::max(0.0f, val)); alSourcef(source_, AL_GAIN, std::max(0.0f, val));
CHECK_AL_ERROR; CHECK_AL_ERROR;
@ -1208,7 +1225,7 @@ void AudioServer::ThreadSource_::UpdatePitch() {
float val = 1.0f; float val = 1.0f;
if (current_is_music()) { if (current_is_music()) {
} else { } else {
val *= audio_thread_->sound_pitch_; val *= audio_server_->sound_pitch_;
} }
alSourcef(source_, AL_PITCH, val); alSourcef(source_, AL_PITCH, val);
CHECK_AL_ERROR; CHECK_AL_ERROR;

View File

@ -115,8 +115,10 @@ class AudioServer {
float sound_volume_{1.0f}; float sound_volume_{1.0f};
float sound_pitch_{1.0f}; float sound_pitch_{1.0f};
float music_volume_{1.0f}; float music_volume_{1.0f};
float app_active_volume_{1.0f};
bool have_pending_loads_{}; bool have_pending_loads_{};
bool app_active_{true};
bool suspended_{}; bool suspended_{};
bool shutdown_completed_{}; bool shutdown_completed_{};
bool shutting_down_{}; bool shutting_down_{};

View File

@ -250,6 +250,166 @@ void BaseFeatureSet::StartApp() {
g_core->LifecycleLog("start-app end (main thread)"); 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<EventLoop*> 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() { void BaseFeatureSet::OnAppShutdownComplete() {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
assert(g_core); assert(g_core);
@ -760,4 +920,57 @@ void BaseFeatureSet::PushMainThreadRunnable(Runnable* runnable) {
app_adapter->DoPushMainThreadRunnable(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<int>(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 } // namespace ballistica::base

View File

@ -609,6 +609,31 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// Start app systems in motion. /// Start app systems in motion.
void StartApp() override; 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 /// 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 /// can be safely called repeatedly. If 'confirm' is true, a confirmation
/// dialog will be presented if the environment and situation allows; /// dialog will be presented if the environment and situation allows;
@ -738,6 +763,22 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// reported by the Python layer. /// reported by the Python layer.
auto GetV2AccountID() -> std::optional<std::string>; auto GetV2AccountID() -> std::optional<std::string>;
/// 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. // Const subsystems.
AppAdapter* const app_adapter; AppAdapter* const app_adapter;
AppConfig* const app_config; AppConfig* const app_config;
@ -774,10 +815,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
// Non-const bits (fixme: clean up access to these). // Non-const bits (fixme: clean up access to these).
TouchInput* touch_input{}; TouchInput* touch_input{};
// auto return_value() const { return return_value_; } auto app_active() const { return app_active_; }
// void set_return_value(int val) { return_value_ = val; }
// auto GetReturnValue() const -> int override;
private: private:
BaseFeatureSet(); BaseFeatureSet();
@ -789,8 +827,12 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
AppMode* app_mode_; AppMode* app_mode_;
PlusSoftInterface* plus_soft_{}; PlusSoftInterface* plus_soft_{};
ClassicSoftInterface* classic_soft_{}; ClassicSoftInterface* classic_soft_{};
std::mutex shutdown_suppress_lock_; 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 shutdown_suppress_disallowed_{};
bool tried_importing_plus_{}; bool tried_importing_plus_{};
bool tried_importing_classic_{}; bool tried_importing_classic_{};
@ -803,7 +845,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
bool basn_log_behavior_{}; bool basn_log_behavior_{};
bool server_wrapper_managed_{}; bool server_wrapper_managed_{};
int shutdown_suppress_count_{}; int shutdown_suppress_count_{};
// int return_value_{};
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -83,12 +83,12 @@ Graphics::~Graphics() = default;
void Graphics::OnAppStart() { assert(g_base->InLogicThread()); } void Graphics::OnAppStart() { assert(g_base->InLogicThread()); }
void Graphics::OnAppPause() { void Graphics::OnAppSuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
SetGyroEnabled(false); SetGyroEnabled(false);
} }
void Graphics::OnAppResume() { void Graphics::OnAppUnsuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
g_base->graphics->SetGyroEnabled(true); g_base->graphics->SetGyroEnabled(true);
} }
@ -993,7 +993,10 @@ void Graphics::DrawFades(FrameDef* frame_def) {
// Guard against accidental fades that never fade back in. // Guard against accidental fades that never fade back in.
if (fade_ <= 0.0f && fade_out_) { if (fade_ <= 0.0f && fade_out_) {
millisecs_t faded_time = real_time - (fade_start_ + fade_time_); 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"); Log(LogLevel::kError, "FORCE-ENDING STUCK FADE");
fade_out_ = false; fade_out_ = false;
fade_ = 1.0f; fade_ = 1.0f;

View File

@ -56,8 +56,8 @@ class Graphics {
Graphics(); Graphics();
void OnAppStart(); void OnAppStart();
void OnAppPause(); void OnAppSuspend();
void OnAppResume(); void OnAppUnsuspend();
void OnAppShutdown(); void OnAppShutdown();
void OnAppShutdownComplete(); void OnAppShutdownComplete();
void OnScreenSizeChange(); void OnScreenSizeChange();

View File

@ -143,7 +143,7 @@ auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
// Spin and wait for a short bit for a frame_def to appear. // Spin and wait for a short bit for a frame_def to appear.
while (true) { while (true) {
// Stop waiting if we can't/shouldn't render anyway. // 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; return nullptr;
} }

View File

@ -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()); } void Input::OnAppShutdown() { assert(g_base->InLogicThread()); }

View File

@ -21,8 +21,8 @@ class Input {
Input(); Input();
void OnAppStart(); void OnAppStart();
void OnAppPause(); void OnAppSuspend();
void OnAppResume(); void OnAppUnsuspend();
void OnAppShutdown(); void OnAppShutdown();
void OnAppShutdownComplete(); void OnAppShutdownComplete();
void StepDisplayTime(); void StepDisplayTime();

View File

@ -48,9 +48,9 @@ void Logic::OnAppStart() {
// Stay informed when our event loop is pausing/unpausing. // Stay informed when our event loop is pausing/unpausing.
event_loop_->AddSuspendCallback( event_loop_->AddSuspendCallback(
NewLambdaRunnableUnmanaged([this] { OnAppPause(); })); NewLambdaRunnableUnmanaged([this] { OnAppSuspend(); }));
event_loop_->AddUnsuspendCallback( event_loop_->AddUnsuspendCallback(
NewLambdaRunnableUnmanaged([this] { OnAppResume(); })); NewLambdaRunnableUnmanaged([this] { OnAppUnsuspend(); }));
// Running in a specific order here and should try to stick to it in // Running in a specific order here and should try to stick to it in
// other OnAppXXX callbacks so any subsystem interdependencies behave // 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->InLogicThread());
assert(g_base->CurrentContext().IsEmpty()); assert(g_base->CurrentContext().IsEmpty());
// Note: keep these in opposite order of OnAppStart. // Note: keep these in opposite order of OnAppStart.
g_base->python->OnAppPause(); g_base->python->OnAppSuspend();
if (g_base->HavePlus()) { if (g_base->HavePlus()) {
g_base->plus()->OnAppPause(); g_base->plus()->OnAppSuspend();
} }
g_base->app_mode()->OnAppPause(); g_base->app_mode()->OnAppSuspend();
g_base->ui->OnAppPause(); g_base->ui->OnAppSuspend();
g_base->input->OnAppPause(); g_base->input->OnAppSuspend();
g_base->audio->OnAppPause(); g_base->audio->OnAppSuspend();
g_base->graphics->OnAppPause(); g_base->graphics->OnAppSuspend();
g_base->platform->OnAppPause(); g_base->platform->OnAppSuspend();
g_base->app_adapter->OnAppPause(); g_base->app_adapter->OnAppSuspend();
} }
void Logic::OnAppResume() { void Logic::OnAppUnsuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
assert(g_base->CurrentContext().IsEmpty()); assert(g_base->CurrentContext().IsEmpty());
// Note: keep these in the same order as OnAppStart. // Note: keep these in the same order as OnAppStart.
g_base->app_adapter->OnAppResume(); g_base->app_adapter->OnAppUnsuspend();
g_base->platform->OnAppResume(); g_base->platform->OnAppUnsuspend();
g_base->graphics->OnAppResume(); g_base->graphics->OnAppUnsuspend();
g_base->audio->OnAppResume(); g_base->audio->OnAppUnsuspend();
g_base->input->OnAppResume(); g_base->input->OnAppUnsuspend();
g_base->ui->OnAppResume(); g_base->ui->OnAppUnsuspend();
g_base->app_mode()->OnAppResume(); g_base->app_mode()->OnAppUnsuspend();
if (g_base->HavePlus()) { if (g_base->HavePlus()) {
g_base->plus()->OnAppResume(); g_base->plus()->OnAppUnsuspend();
} }
g_base->python->OnAppResume(); g_base->python->OnAppUnsuspend();
} }
void Logic::Shutdown() { void Logic::Shutdown() {

View File

@ -52,11 +52,11 @@ class Logic {
/// Called when our event-loop pauses. Informs Python and other /// Called when our event-loop pauses. Informs Python and other
/// subsystems. /// subsystems.
void OnAppPause(); void OnAppSuspend();
/// Called when our event-loop resumes. Informs Python and other /// Called when our event-loop resumes. Informs Python and other
/// subsystems. /// subsystems.
void OnAppResume(); void OnAppUnsuspend();
void OnAppShutdown(); void OnAppShutdown();
void OnAppShutdownComplete(); void OnAppShutdownComplete();

View File

@ -25,7 +25,7 @@ void NetworkReader::SetPort(int port) {
thread_ = new std::thread(RunThreadStatic_, this); thread_ = new std::thread(RunThreadStatic_, this);
} }
void NetworkReader::OnAppPause() { void NetworkReader::OnAppSuspend() {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
assert(!paused_); assert(!paused_);
{ {
@ -42,7 +42,7 @@ void NetworkReader::OnAppPause() {
} }
} }
void NetworkReader::OnAppResume() { void NetworkReader::OnAppUnsuspend() {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
assert(paused_); assert(paused_);

View File

@ -24,8 +24,8 @@ class NetworkReader {
public: public:
NetworkReader(); NetworkReader();
void SetPort(int port); void SetPort(int port);
void OnAppPause(); void OnAppSuspend();
void OnAppResume(); void OnAppUnsuspend();
auto port4() const { return port4_; } auto port4() const { return port4_; }
auto port6() const { return port6_; } auto port6() const { return port6_; }
auto sd_mutex() -> std::mutex& { return sd_mutex_; } auto sd_mutex() -> std::mutex& { return sd_mutex_; }

View File

@ -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<uint8_t>& buffer, void Networking::SendTo(const std::vector<uint8_t>& buffer,
const SockAddr& addr) { const SockAddr& addr) {

View File

@ -127,8 +127,8 @@ class Networking {
// Called on mobile platforms when going into the background, etc // Called on mobile platforms when going into the background, etc
// (when all networking should be shut down) // (when all networking should be shut down)
void OnAppPause(); void OnAppSuspend();
void OnAppResume(); void OnAppUnsuspend();
auto remote_server_accepting_connections() -> bool { auto remote_server_accepting_connections() -> bool {
return remote_server_accepting_connections_; return remote_server_accepting_connections_;

View File

@ -166,8 +166,8 @@ void BasePlatform::SetupInterruptHandling() {
} }
void BasePlatform::OnAppStart() { assert(g_base->InLogicThread()); } void BasePlatform::OnAppStart() { assert(g_base->InLogicThread()); }
void BasePlatform::OnAppPause() { assert(g_base->InLogicThread()); } void BasePlatform::OnAppSuspend() { assert(g_base->InLogicThread()); }
void BasePlatform::OnAppResume() { assert(g_base->InLogicThread()); } void BasePlatform::OnAppUnsuspend() { assert(g_base->InLogicThread()); }
void BasePlatform::OnAppShutdown() { assert(g_base->InLogicThread()); } void BasePlatform::OnAppShutdown() { assert(g_base->InLogicThread()); }
void BasePlatform::OnAppShutdownComplete() { assert(g_base->InLogicThread()); } void BasePlatform::OnAppShutdownComplete() { assert(g_base->InLogicThread()); }
void BasePlatform::OnScreenSizeChange() { assert(g_base->InLogicThread()); } void BasePlatform::OnScreenSizeChange() { assert(g_base->InLogicThread()); }

View File

@ -26,8 +26,8 @@ class BasePlatform {
// Logic thread callbacks. // Logic thread callbacks.
virtual void OnAppStart(); virtual void OnAppStart();
virtual void OnAppPause(); virtual void OnAppSuspend();
virtual void OnAppResume(); virtual void OnAppUnsuspend();
virtual void OnAppShutdown(); virtual void OnAppShutdown();
virtual void OnAppShutdownComplete(); virtual void OnAppShutdownComplete();
virtual void OnScreenSizeChange(); virtual void OnScreenSizeChange();

View File

@ -82,7 +82,7 @@ static const char* const scancode_names[SDL_NUM_SCANCODES] = {
"F12", "F12",
"PrintScreen", "PrintScreen",
"ScrollLock", "ScrollLock",
"OnAppPause", "Pause",
"Insert", "Insert",
"Home", "Home",
"PageUp", "PageUp",

View File

@ -149,12 +149,12 @@ void BasePython::OnAppStart() {
objs().Get(BasePython::ObjID::kAppOnNativeStartCall).Call(); objs().Get(BasePython::ObjID::kAppOnNativeStartCall).Call();
} }
void BasePython::OnAppPause() { void BasePython::OnAppSuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
objs().Get(BasePython::ObjID::kAppOnNativeSuspendCall).Call(); objs().Get(BasePython::ObjID::kAppOnNativeSuspendCall).Call();
} }
void BasePython::OnAppResume() { void BasePython::OnAppUnsuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
objs().Get(BasePython::ObjID::kAppOnNativeUnsuspendCall).Call(); objs().Get(BasePython::ObjID::kAppOnNativeUnsuspendCall).Call();
} }

View File

@ -15,8 +15,8 @@ class BasePython {
void OnMainThreadStartApp(); void OnMainThreadStartApp();
void OnAppStart(); void OnAppStart();
void OnAppPause(); void OnAppSuspend();
void OnAppResume(); void OnAppUnsuspend();
void OnAppShutdown(); void OnAppShutdown();
void OnAppShutdownComplete(); void OnAppShutdownComplete();
void DoApplyAppConfig(); void DoApplyAppConfig();

View File

@ -44,6 +44,27 @@ static PyMethodDef PyAppNameDef = {
"(internal)\n", "(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 ----------------------------------- // --------------------------------- run_app -----------------------------------
static auto PyRunApp(PyObject* self) -> PyObject* { static auto PyRunApp(PyObject* self) -> PyObject* {
@ -1650,6 +1671,7 @@ static PyMethodDef PyGraphicsShutdownIsCompleteDef = {
auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> { auto PythonMethodsApp::GetMethods() -> std::vector<PyMethodDef> {
return { return {
PyAppNameDef, PyAppNameDef,
PyAppIsActiveDef,
PyRunAppDef, PyRunAppDef,
PyAppNameUpperDef, PyAppNameUpperDef,
PyIsXCodeBuildDef, PyIsXCodeBuildDef,

View File

@ -129,7 +129,7 @@ static PyMethodDef PyHasTouchScreenDef = {
static auto PyClipboardIsSupported(PyObject* self) -> PyObject* { static auto PyClipboardIsSupported(PyObject* self) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
if (g_base->app_adapter->ClipboardIsSupported()) { if (g_base->ClipboardIsSupported()) {
Py_RETURN_TRUE; Py_RETURN_TRUE;
} }
Py_RETURN_FALSE; Py_RETURN_FALSE;
@ -155,7 +155,7 @@ static PyMethodDef PyClipboardIsSupportedDef = {
static auto PyClipboardHasText(PyObject* self) -> PyObject* { static auto PyClipboardHasText(PyObject* self) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
if (g_base->app_adapter->ClipboardHasText()) { if (g_base->ClipboardHasText()) {
Py_RETURN_TRUE; Py_RETURN_TRUE;
} }
Py_RETURN_FALSE; Py_RETURN_FALSE;
@ -188,7 +188,7 @@ static auto PyClipboardSetText(PyObject* self, PyObject* args, PyObject* keywds)
const_cast<char**>(kwlist), &value)) { const_cast<char**>(kwlist), &value)) {
return nullptr; return nullptr;
} }
g_base->app_adapter->ClipboardSetText(value); g_base->ClipboardSetText(value);
Py_RETURN_NONE; Py_RETURN_NONE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }
@ -212,7 +212,7 @@ static PyMethodDef PyClipboardSetTextDef = {
static auto PyClipboardGetText(PyObject* self) -> PyObject* { static auto PyClipboardGetText(PyObject* self) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
return PyUnicode_FromString(g_base->app_adapter->ClipboardGetText().c_str()); return PyUnicode_FromString(g_base->ClipboardGetText().c_str());
Py_RETURN_FALSE; Py_RETURN_FALSE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }

View File

@ -19,8 +19,8 @@ namespace ballistica::base {
class PlusSoftInterface { class PlusSoftInterface {
public: public:
virtual void OnAppStart() = 0; virtual void OnAppStart() = 0;
virtual void OnAppPause() = 0; virtual void OnAppSuspend() = 0;
virtual void OnAppResume() = 0; virtual void OnAppUnsuspend() = 0;
virtual void OnAppShutdown() = 0; virtual void OnAppShutdown() = 0;
virtual void OnAppShutdownComplete() = 0; virtual void OnAppShutdownComplete() = 0;
virtual void DoApplyAppConfig() = 0; virtual void DoApplyAppConfig() = 0;

View File

@ -1441,9 +1441,9 @@ void DevConsole::StepDisplayTime() {
auto DevConsole::PasteFromClipboard() -> bool { auto DevConsole::PasteFromClipboard() -> bool {
if (state_ != State_::kInactive) { if (state_ != State_::kInactive) {
if (python_terminal_visible_) { if (python_terminal_visible_) {
if (g_base->app_adapter->ClipboardIsSupported()) { if (g_base->ClipboardIsSupported()) {
if (g_base->app_adapter->ClipboardHasText()) { if (g_base->ClipboardHasText()) {
auto text = g_base->app_adapter->ClipboardGetText(); auto text = g_base->ClipboardGetText();
if (strstr(text.c_str(), "\n") || strstr(text.c_str(), "\r")) { if (strstr(text.c_str(), "\n") || strstr(text.c_str(), "\r")) {
g_base->audio->PlaySound( g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kErrorBeep)); g_base->assets->SysSound(SysSoundID::kErrorBeep));

View File

@ -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()); assert(g_base->InLogicThread());
SetUIInputDevice(nullptr); SetUIInputDevice(nullptr);
} }

View File

@ -32,8 +32,8 @@ class UI {
UI(); UI();
void OnAppStart(); void OnAppStart();
void OnAppPause(); void OnAppSuspend();
void OnAppResume(); void OnAppUnsuspend();
void OnAppShutdown(); void OnAppShutdown();
void OnAppShutdownComplete(); void OnAppShutdownComplete();
void DoApplyAppConfig(); void DoApplyAppConfig();

View File

@ -103,7 +103,9 @@ auto CorePlatform::Create() -> CorePlatform* {
return platform; 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()) {} CorePlatform::CorePlatform() : start_time_millisecs_(GetCurrentMillisecs()) {}
@ -1021,7 +1023,7 @@ auto CorePlatform::HavePermission(Permission p) -> bool {
void CorePlatform::SetDebugKey(const std::string& key, void CorePlatform::SetDebugKey(const std::string& key,
const std::string& value) {} const std::string& value) {}
void CorePlatform::HandleDebugLog(const std::string& msg) {} void CorePlatform::HandleLowLevelDebugLog(const std::string& msg) {}
auto CorePlatform::GetCurrentMillisecs() -> millisecs_t { auto CorePlatform::GetCurrentMillisecs() -> millisecs_t {
return std::chrono::time_point_cast<std::chrono::milliseconds>( return std::chrono::time_point_cast<std::chrono::milliseconds>(

View File

@ -54,9 +54,8 @@ class CorePlatform {
/// fopen() supporting UTF8 strings. /// fopen() supporting UTF8 strings.
virtual auto FOpen(const char* path, const char* mode) -> FILE*; virtual auto FOpen(const char* path, const char* mode) -> FILE*;
/// rename() supporting UTF8 strings. /// rename() supporting UTF8 strings. For cross-platform consistency, this
/// For cross-platform consistency, this should also remove any file that /// should also remove any file that exists at the target location first.
/// exists at the target location first.
virtual auto Rename(const char* oldname, const char* newname) -> int; virtual auto Rename(const char* oldname, const char* newname) -> int;
/// Simple cross-platform check for existence of a file. /// 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. /// This is expected to be lightweight as it may be called often.
virtual void SetDebugKey(const std::string& key, const std::string& value); 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 -------------------------------------------------------------- #pragma mark MISC --------------------------------------------------------------
/// Return a time measurement in milliseconds since launch. /// Return a time measurement in milliseconds since launch. It *should* be
/// It *should* be monotonic. /// monotonic. For most purposes, AppTime values are preferable since
/// For most purposes, AppTime values are preferable since their progression /// their progression pauses during app suspension and they are 100%
/// pauses during app suspension and they are 100% guaranteed to not go /// guaranteed to not go backwards.
/// backwards.
auto GetTicks() const -> millisecs_t; auto GetTicks() const -> millisecs_t;
/// Return a raw current milliseconds value. It *should* be monotonic. /// Return a raw current milliseconds value. It *should* be monotonic. It
/// It is relative to an undefined start point; only use it for time /// is relative to an undefined start point; only use it for time
/// differences. Generally the AppTime values are preferable since their /// differences. Generally the AppTime values are preferable since their
/// progression pauses during app suspension and they are 100% guaranteed /// progression pauses during app suspension and they are 100% guaranteed
/// to not go backwards. /// to not go backwards.
static auto GetCurrentMillisecs() -> millisecs_t; static auto GetCurrentMillisecs() -> millisecs_t;
/// Return a raw current microseconds value. It *should* be monotonic. /// Return a raw current microseconds value. It *should* be monotonic. It
/// It is relative to an undefined start point; only use it for time /// is relative to an undefined start point; only use it for time
/// differences. Generally the AppTime values are preferable since their /// differences. Generally the AppTime values are preferable since their
/// progression pauses during app suspension and they are 100% guaranteed /// progression pauses during app suspension and they are 100% guaranteed
/// to not go backwards. /// to not go backwards.
@ -378,14 +381,15 @@ class CorePlatform {
/// Is the OS currently playing music? (so we can avoid doing so). /// Is the OS currently playing music? (so we can avoid doing so).
virtual auto IsOSPlayingMusic() -> bool; 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); virtual void SetPlatformMiscReadVals(const std::string& vals);
/// Set the name of the current thread (for debugging). /// Set the name of the current thread (for debugging).
virtual void SetCurrentThreadName(const std::string& name); virtual void SetCurrentThreadName(const std::string& name);
// If display-resolution can be directly set on this platform, // If display-resolution can be directly set on this platform, return true
// return true and set the native full res here. Otherwise return false; // and set the native full res here. Otherwise return false;
virtual auto GetDisplayResolution(int* x, int* y) -> bool; virtual auto GetDisplayResolution(int* x, int* y) -> bool;
/// Are we being run from a terminal? (should we show prompts, etc?). /// Are we being run from a terminal? (should we show prompts, etc?).
@ -412,44 +416,39 @@ class CorePlatform {
/// device; something like "iPhone 12 Pro". /// device; something like "iPhone 12 Pro".
virtual auto DoGetDeviceDescription() -> std::string; virtual auto DoGetDeviceDescription() -> std::string;
/// Attempt to actually create a directory. /// Attempt to actually create a directory. Should *not* raise Exceptions
/// Should *not* raise Exceptions if it already exists or if quiet is true. /// if it already exists or if quiet is true.
virtual void DoMakeDir(const std::string& dir, bool quiet); virtual void DoMakeDir(const std::string& dir, bool quiet);
/// Attempt to actually get an abs path. This will only be called if /// Attempt to actually get an abs path. This will only be called if the
/// the path is valid and exists. /// path is valid and exists.
virtual auto DoAbsPath(const std::string& path, std::string* outpath) -> bool; virtual auto DoAbsPath(const std::string& path, std::string* outpath) -> bool;
/// Calc the user scripts dir path for this platform. /// Calc the user scripts dir path for this platform. This will be called
/// This will be called once and the path cached. /// once and the path cached.
virtual auto DoGetUserPythonDirectoryMonolithicDefault() virtual auto DoGetUserPythonDirectoryMonolithicDefault()
-> std::optional<std::string>; -> std::optional<std::string>;
/// Return the default config directory for this platform. /// Return the default config directory for this platform. This will be
/// This will be used as the config dir if not overridden via command /// used as the config dir if not overridden via command line options,
/// line options, etc. /// etc.
virtual auto DoGetConfigDirectoryMonolithicDefault() virtual auto DoGetConfigDirectoryMonolithicDefault()
-> std::optional<std::string>; -> std::optional<std::string>;
/// Return the default data directory for this platform. /// Return the default data directory for this platform. This will be used
/// This will be used as the data dir if not overridden by core-config, etc. /// as the data dir if not overridden by core-config, etc. This is the one
/// This is the one monolithic-default value that is not optional. /// monolithic-default value that is not optional.
virtual auto DoGetDataDirectoryMonolithicDefault() -> std::string; virtual auto DoGetDataDirectoryMonolithicDefault() -> std::string;
/// Return the default Volatile data dir for this platform. /// Return the default Volatile data dir for this platform. This will be
/// This will be used as the volatile-data-dir if not overridden via command /// used as the volatile-data-dir if not overridden via command line
/// line options/etc. /// options/etc.
virtual auto GetDefaultVolatileDataDirectory() -> std::string; virtual auto GetDefaultVolatileDataDirectory() -> std::string;
/// Generate a random UUID string. /// Generate a random UUID string.
virtual auto GenerateUUID() -> std::string; virtual auto GenerateUUID() -> std::string;
/// Print a log message to be included in crash logs or other debug virtual void HandleLowLevelDebugLog(const std::string& msg);
/// 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);
CorePlatform(); CorePlatform();
virtual ~CorePlatform(); virtual ~CorePlatform();

View File

@ -10,9 +10,9 @@
namespace ballistica::core { namespace ballistica::core {
void LowLevelPythonDebugLog(const char* msg) { static void PythonLowLevelDebugLog_(const char* msg) {
assert(g_core); assert(g_core);
g_core->platform->DebugLog(msg); g_core->platform->LowLevelDebugLog(msg);
} }
static void CheckPyInitStatus(const char* where, const PyStatus& status) { 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. // Install our low level logger in our custom Python builds.
#ifdef PY_HAVE_BALLISTICA_LOW_LEVEL_DEBUG_LOG #ifdef PY_HAVE_BALLISTICA_LOW_LEVEL_DEBUG_LOG
Py_BallisticaLowLevelDebugLog = LowLevelPythonDebugLog; Py_BallisticaLowLevelDebugLog = PythonLowLevelDebugLog_;
#endif #endif
// Flip on some extra runtime debugging options in debug builds. // Flip on some extra runtime debugging options in debug builds.

View File

@ -106,14 +106,14 @@ void SceneV1AppMode::OnAppShutdown() {
connections_->Shutdown(); connections_->Shutdown();
} }
void SceneV1AppMode::OnAppPause() { void SceneV1AppMode::OnAppSuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// App is going into background or whatnot. Kill any sockets/etc. // App is going into background or whatnot. Kill any sockets/etc.
EndHostScanning(); 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 // 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 // logic thread. This is generally not a good idea since it appears that even

View File

@ -148,8 +148,8 @@ class SceneV1AppMode : public base::AppMode {
auto IsPlayerBanned(const PlayerSpec& spec) -> bool; auto IsPlayerBanned(const PlayerSpec& spec) -> bool;
void BanPlayer(const PlayerSpec& spec, millisecs_t duration); void BanPlayer(const PlayerSpec& spec, millisecs_t duration);
void OnAppStart() override; void OnAppStart() override;
void OnAppPause() override; void OnAppSuspend() override;
void OnAppResume() override; void OnAppUnsuspend() override;
auto InClassicMainMenuSession() const -> bool override; auto InClassicMainMenuSession() const -> bool override;
auto CreateInputDeviceDelegate(base::InputDevice* device) auto CreateInputDeviceDelegate(base::InputDevice* device)
-> base::InputDeviceDelegate* override; -> base::InputDeviceDelegate* override;

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // 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 char* kEngineVersion = "1.7.30";
const int kEngineApiVersion = 8; const int kEngineApiVersion = 8;

View File

@ -38,7 +38,7 @@ void Logging::V1CloudLog(const std::string& msg) {
if (g_core) { if (g_core) {
// (ship to things like Crashlytics crash-logging) // (ship to things like Crashlytics crash-logging)
g_core->platform->DebugLog(msg); g_core->platform->LowLevelDebugLog(msg);
// Add to our complete v1-cloud-log. // Add to our complete v1-cloud-log.
std::scoped_lock lock(g_core->v1_cloud_log_mutex); std::scoped_lock lock(g_core->v1_cloud_log_mutex);

View File

@ -2416,9 +2416,9 @@ static auto PyShowAd(PyObject* self, PyObject* args, PyObject* keywds)
static_cast<bool>(pass_actually_showed)); static_cast<bool>(pass_actually_showed));
// In cases where we support ads, store our callback and kick one off. // In cases where we support ads, store our callback and kick one off.
// We'll then fire our callback once its done. // We'll then fire our callback once its done. If we *don't* support ads,
// If we *don't* support ads, just store our callback and then kick off // just store our callback and then kick off an ad-view-complete message
// an ad-view-complete message ourself so the event flow is similar.. // ourself so the event flow is similar..
if (g_core->platform->GetHasAds()) { if (g_core->platform->GetHasAds()) {
g_core->platform->ShowAd(purpose); g_core->platform->ShowAd(purpose);
} else { } else {

View File

@ -688,13 +688,14 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
// If we're doing inline editing, handle clipboard paste. // If we're doing inline editing, handle clipboard paste.
if (editable() && !ShouldUseStringEditor_() if (editable() && !ShouldUseStringEditor_()
&& m.type == base::WidgetMessage::Type::kPaste) { && m.type == base::WidgetMessage::Type::kPaste) {
if (g_base->app_adapter->ClipboardIsSupported()) { if (g_base->ClipboardIsSupported()) {
if (g_base->app_adapter->ClipboardHasText()) { if (g_base->ClipboardHasText()) {
// Just enter it char by char as if we had typed it... // 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 we're doing inline editing, handle some key events.
if (editable() && m.has_keysym && !ShouldUseStringEditor_()) { if (editable() && m.has_keysym && !ShouldUseStringEditor_()) {
last_carat_change_time_millisecs_ = last_carat_change_time_millisecs_ =

View File

@ -39,7 +39,7 @@ def build_openal(arch: str, mode: str) -> None:
if mode not in MODES: if mode not in MODES:
raise CleanError(f"Invalid mode '{mode}'.") raise CleanError(f"Invalid mode '{mode}'.")
enable_oboe = True # enable_oboe = True
# Get ndk path. # Get ndk path.
ndk_path = ( ndk_path = (
@ -51,9 +51,8 @@ def build_openal(arch: str, mode: str) -> None:
.stdout.decode() .stdout.decode()
.strip() .strip()
) )
# os.environ['NDK_ROOT'] = ndk_path
# Grab from git and build. # Grab OpenALSoft
builddir = _build_dir(arch, mode) builddir = _build_dir(arch, mode)
subprocess.run(['rm', '-rf', builddir], check=True) subprocess.run(['rm', '-rf', builddir], check=True)
subprocess.run(['mkdir', '-p', os.path.dirname(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) subprocess.run(['git', 'checkout', '1.23.1'], check=True, cwd=builddir)
if enable_oboe: # Grab Oboe
builddir_oboe = f'{builddir}_oboe' builddir_oboe = f'{builddir}_oboe'
subprocess.run(['rm', '-rf', builddir_oboe], check=True) subprocess.run(['rm', '-rf', builddir_oboe], check=True)
subprocess.run( subprocess.run(['mkdir', '-p', os.path.dirname(builddir_oboe)], check=True)
['mkdir', '-p', os.path.dirname(builddir_oboe)], check=True subprocess.run(
) [
subprocess.run( 'git',
[ 'clone',
'git', 'https://github.com/google/oboe',
'clone', builddir_oboe,
'https://github.com/google/oboe', ],
builddir_oboe, check=True,
], )
check=True, subprocess.run(['git', 'checkout', '1.8.0'], check=True, cwd=builddir_oboe)
)
subprocess.run(
['git', 'checkout', '1.8.0'], check=True, cwd=builddir_oboe
)
print(f'FULLY GOT {builddir_oboe}')
# One bit of filtering: by default, openalsoft sends all sorts of # One bit of filtering: by default, openalsoft sends all sorts of
# log messages to the android log. This is reasonable since its # 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: with open(loggingpath, 'w', encoding='utf-8') as outfile:
outfile.write(txt) 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 android_platform = 23
subprocess.run( subprocess.run(