This commit is contained in:
Eric 2023-12-08 08:46:45 -08:00
parent 1c2cb0aecb
commit 115fd5eac6
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
31 changed files with 536 additions and 247 deletions

88
.efrocachemap generated
View File

@ -4060,50 +4060,50 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "dc1e98f8253f6e61583becda21c73162",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "c4e955cd85bc493fc03af74ce1062c66",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "29c7e4a4accdb87c354e6181780654d4",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "1e254a7d528cbaa33cf5bce8e8c4fea7",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "5b6792f0cadb52d82e483981b72def92",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "d5bdea21bb4fe28e55de0dae42b05c09",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "271c8808879bc09181f02c95787f6818",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "40fe07bb55d239e1eabdc0976b46112d",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "ae0f96fc3364de8f6721d12e775e0ab2",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "e2650e5017b27e84c398b894c768dee6",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "1a5d42f10fdd26ff5e81b07afa67c66e",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "940c21f7293968fe88023e33607d36f2",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "31f56d98b67c8894a2dfe73f4d4ed4c8",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "7b316468903c66cab07b0d8ae38b7bb7",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "42cd00bfbe63f750e9a064900582df48",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "045c0b16bf8ff0be028cefe5cfae1ee6",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "588711c1bb3cf986506b0a6a3d00fed9",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "a626549c279d83295e117835cc15050e",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "207ea740413855ede15da4ba1f304cf3",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "8516e63b6129b872ca59cdf5fade1c45",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "e3c41c240bb333fc53240f64d6e9583e",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "add3863bc3c332a1196db0673fde5587",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "e3c41c240bb333fc53240f64d6e9583e",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "0b0ec8be8c575beba2935fdc9aa03ce5",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "04c21c4226944f71230420f9b399d1e4",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "0b0ec8be8c575beba2935fdc9aa03ce5",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "04c21c4226944f71230420f9b399d1e4",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "e80ffa41dcc78dbd75baf89fadb096b4",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "6514628d8ce1c046726de69fa0086613",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "e80ffa41dcc78dbd75baf89fadb096b4",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "6514628d8ce1c046726de69fa0086613",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "9f4f5c5043d66fa3bdeb16b0599b5de4",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "a3124c863c4b80de5acc20a7c9d49492",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "ddcb6a381ef5eaef6b1650cd94c498fc",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "155cfc2d5a62f02ab4490b24afe241e2",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9c5b8303df63e5bdde524ae4ce654fa8",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6895c7a2714c09e065ba6ac1f3860501",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "a0a6f9d1afaacc9c762da68df3f9178c",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "0ce476258657733e63c21be416d35574",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "e23b83abb5efcb2566e003be2c6a4b6e",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "364dd18a7b830ec1600827734c8ac0e6",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "ab27a9c76a3706803283e09a43ecd92b",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "be3e38d0503d272d22a9226323803564",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "feea24dbb10aec67e1aa4d6c1d0928b5",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "ac035cdcd1134ed3eefb50f2b6fee259",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "aa71c8cf97c379aa2cc93b3edb050167",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "afaf8cc0bd7e93439366a43a80debdbc",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "f787ba6f598297a6b43668949af0c8e2",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f8317a770d865a1380bfef13011ae1cc",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "39afba54e9822911d8bfdf53602fcc8d",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "2fe24a441e96e882ce29cd55c886ee8e",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "4d068a5426f7e8814438a295f34d5356",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "e315d748000555f280fd6a0e9575f799",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "753a957f642173a1568725ff0b36bfce",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "833be86309d39cfb7f273c4c72ffdafc",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "49ad9d968f4d615323a840f036caa36f",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "5795639a921b758c08a14ad09a8883d9",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "db31fcbdeeb8f5ab794a1f9f1dfe7ede",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "8aa9aa6b833d64d2e4a3689f6ce134fb",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "7f6119e33678b934ac1c5f8002d8d6bc",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "639a01a7dcfd2f6db3023b610ad44d4a",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e925ad3d39bb366f78c6e0f9da22d0cb",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "4f5d3cafbea651078c1721684b61033a",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "7436c575aee1f9d112d41f18e2ae6b22",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "4f5d3cafbea651078c1721684b61033a",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "7436c575aee1f9d112d41f18e2ae6b22",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "3ffcb35eb71566fefb7d9ad2589b37b4",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "ce9de33efccb9aa1e301fe4f11e6b1c1",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "3ffcb35eb71566fefb7d9ad2589b37b4",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "ce9de33efccb9aa1e301fe4f11e6b1c1",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "9d674b5e8a8357b9462a65359611ea45",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "e6d1c0b9bf27c34968e512c5db8e7de5",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "9d674b5e8a8357b9462a65359611ea45",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "e6d1c0b9bf27c34968e512c5db8e7de5",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "910b5e39fe4ba5bb849505c578efe3ec",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "2d9e14f7cfe50b1dc51e5e9eae05b5fd",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "b83a67eeaed0fc99bf995767a8150e5d",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "2d9e14f7cfe50b1dc51e5e9eae05b5fd",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "8c6063999e8d77daf8bf8a33d045c737",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "e60f8d06f584bac8e54d920776a4d57d",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "89b61adb57d6a53740f302d01e107587",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "f347b87f6ee2167546edec41b750245c",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ed74c7fd0a854650675aa92366c2be3f",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "ac5046013922b1db4689c6ba653791c1",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "52c36bf974594558a33f7c6caa07b81e",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "27e287e1826677d1b4a480820e5e2f3a",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",

View File

@ -1,4 +1,4 @@
### 1.7.30 (build 21657, api 8, 2023-12-02)
### 1.7.30 (build 21693, api 8, 2023-12-08)
- Continued work on the big 1.7.28 update.
- Got the Android version back up and running. There's been lots of cleanup and
simplification on the Android layer, cleaning out years of cruft. This should
@ -15,8 +15,14 @@
- Bundled Android Python has been bumped to version 3.11.6.
- Android app suspend behavior has been revamped. The app should stay running
more often and be quicker to respond when dialogs or other activities
temporarily pop up in front of it. Please holler if you run into strange side
temporarily pop up in front of it. This also allows it to continue playing
music over other activities such as Google Play Games
Achievements/Leaderboards screens. Please holler if you run into strange side
effects such as the app continuing to play audio when it should not be.
- Modernized the Android fullscreen setup code when running in Android 11 or
newer. The game should now use the whole screen area, including the area
around notches or camera cutouts. Please holler if you are seeing any problems
related to this.
- (build 21626) Fixed a bug where click/tap locations were incorrect on some
builds when tv-border was on (Thanks for the heads-up Loup(Dliwk's fan)!).
- (build 21631) Fixes an issue where '^^^^^^^^^^^^^' lines in stack traces could
@ -31,6 +37,12 @@
`from_window`. 2) In any call that can lead to you switching the main menu
window, check if your root widget is dead or transitioning out first and abort
if it is. See any window in `ui_v1_lib` for examples.
- (build 21691) Fixed a bug causing touches to not register in some cases on
newer Android devices. (Huge thanks to JESWIN A J for helping me track that
down!).
- Temporarily removed the pause-the-game-when-backgrounded behavior for locally
hosted games, mainly due to the code being hacky. Will try to restore this
functionality in a cleaner way soon.
### 1.7.29 (build 21619, api 8, 2023-11-21)

View File

@ -19,7 +19,7 @@ if TYPE_CHECKING:
from babase._login import LoginAdapter, LoginInfo
DEBUG_LOG = False
DEBUG_LOG = _babase.temp_testing()
class AccountV2Subsystem:
@ -186,9 +186,10 @@ class AccountV2Subsystem:
cfgkey = 'ImplicitLoginStates'
cfgdict = _babase.app.config.setdefault(cfgkey, {})
# Store which (if any) adapter is currently implicitly signed in.
# Making the assumption there will only ever be one implicit
# adapter at a time; may need to update this if that changes.
# Store which (if any) adapter is currently implicitly signed
# in. Making the assumption there will only ever be one implicit
# adapter at a time; may need to revisit this logic if that
# changes.
prev_state = cfgdict.get(login_type.value)
if state is None:
self._implicit_signed_in_adapter = None
@ -296,9 +297,8 @@ class AccountV2Subsystem:
# Consider this an 'explicit' sign in because the
# implicit-login state change presumably was triggered
# by some user action (signing in, signing out, or
# switching accounts via the back-end).
# NOTE: should test case where we don't have
# connectivity here.
# switching accounts via the back-end). NOTE: should
# test case where we don't have connectivity here.
if plus.cloud.is_connected():
if DEBUG_LOG:
logging.debug(

View File

@ -17,7 +17,7 @@ if TYPE_CHECKING:
from typing import Callable
DEBUG_LOG = False
DEBUG_LOG = _babase.temp_testing()
@dataclass
@ -145,7 +145,7 @@ class LoginAdapter:
is actually being used by the app. It should therefore register
unlocked achievements, leaderboard scores, allow viewing native
UIs, etc. When not active it should ignore everything and behave
as if logged out, even if it technically is still logged in.
as if signed out, even if it technically is still signed in.
"""
assert _babase.in_logic_thread()
del active # Unused.

View File

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

View File

@ -190,7 +190,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
super().__del__()
# If our UI is still up, kill it.
if self._root_ui:
if self._root_ui and not self._root_ui.transitioning_out:
with bui.ContextRef.empty():
bui.containerwidget(edit=self._root_ui, transition='out_left')

View File

@ -209,8 +209,12 @@ class UIV1Subsystem(babase.AppSubsystem):
def clear_main_menu_window(self, transition: str | None = None) -> None:
"""Clear any existing 'main' window with the provided transition."""
assert transition is None or not transition.endswith('_in')
if self._main_menu_window:
if transition is not None:
if (
transition is not None
and not self._main_menu_window.transitioning_out
):
_bauiv1.containerwidget(
edit=self._main_menu_window, transition=transition
)

View File

@ -482,9 +482,12 @@ class PlayOptionsWindow(PopupWindow):
cfg['Private Party Host Session Type'] = typename
bui.getsound('gunCocking').play()
assert bui.app.classic is not None
# Note: this is a wonky situation where we aren't actually
# the main window but we set it on behalf of the main window
# that popped us up.
bui.app.ui_v1.set_main_menu_window(
GatherWindow(transition='in_right').get_root_widget(),
from_window=self.root_widget,
from_window=False, # Disable this test.
)
self._transition_out(transition='out_left')
if self._delegate is not None:

View File

@ -257,7 +257,7 @@ class ProfileBrowserWindow(bui.Window):
EditProfileWindow(
existing_profile=None, in_main_menu=self._in_main_menu
).get_root_widget(),
from_window=self._root_widget,
from_window=self._root_widget if self._in_main_menu else False,
)
def _delete_profile(self) -> None:
@ -323,7 +323,7 @@ class ProfileBrowserWindow(bui.Window):
EditProfileWindow(
self._selected_profile, in_main_menu=self._in_main_menu
).get_root_widget(),
from_window=self._root_widget,
from_window=self._root_widget if self._in_main_menu else False,
)
def _select(self, name: str, index: int) -> None:

View File

@ -151,8 +151,6 @@ void AppAdapter::NativeReviewRequest() {
void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); }
auto AppAdapter::ShouldSilenceAudioWhenInactive() -> bool const {
return false;
}
auto AppAdapter::ShouldSilenceAudioForInactive() -> bool const { return false; }
} // namespace ballistica::base

View File

@ -135,11 +135,14 @@ class AppAdapter {
/// Return whether this AppAdapter supports max-fps controls for its display.
virtual auto SupportsMaxFPS() -> bool const;
/// Return whether audio should be silenced when the app is inactive.
/// On Desktop systems it is generally normal to continue to hear things
/// even if their windows are hidden, but on mobile we probably want to
/// silence our audio when phone calls, ads, etc. pop up over it.
virtual auto ShouldSilenceAudioWhenInactive() -> bool const;
/// Return whether audio should be silenced when the app goes inactive. On
/// Desktop systems it is generally normal to continue to hear things even
/// if their windows are hidden, but on mobile we probably want to silence
/// our audio when phone calls, ads, etc. pop up over it. Note that this
/// is called each time the app goes inactive, so the adapter may choose
/// to selectively silence audio depending on what caused the inactive
/// switch.
virtual auto ShouldSilenceAudioForInactive() -> bool const;
/// Return whether this platform supports soft-quit. A soft quit is
/// when the app is reset/backgrounded/etc. but remains running in case

View File

@ -17,7 +17,8 @@
#include <alc.h>
#endif
#if BA_OSTYPE_ANDROID
#if BA_OPENAL_IS_SOFT
#define AL_ALEXT_PROTOTYPES
#include <alext.h>
#endif

View File

@ -28,9 +28,12 @@ namespace ballistica::base {
extern std::string g_rift_audio_device_name;
#endif
#if BA_OSTYPE_ANDROID
#if BA_OPENAL_IS_SOFT
LPALCDEVICEPAUSESOFT alcDevicePauseSOFT{};
LPALCDEVICERESUMESOFT alcDeviceResumeSOFT{};
LPALCRESETDEVICESOFT alcResetDeviceSOFT{};
LPALEVENTCALLBACKSOFT alEventCallbackSOFT{};
LPALEVENTCONTROLSOFT alEventControlSOFT{};
#endif
const int kAudioProcessIntervalNormal{500 * 1000};
@ -108,29 +111,24 @@ class AudioServer::ThreadSource_ : public Object {
}
private:
bool looping_{};
std::unique_ptr<AudioSource> client_source_;
float fade_{1.0f};
float gain_{1.0f};
AudioServer* audio_server_{};
bool valid_{};
const Object::Ref<SoundAsset>* source_sound_{};
int id_{};
uint32_t play_count_{};
bool looping_{};
bool valid_{};
bool is_actually_playing_{};
bool want_to_play_{};
#if BA_ENABLE_AUDIO
ALuint source_{};
#endif
bool is_streamed_{};
/// Whether we should be designated as "music" next time we play.
bool is_music_{};
/// Whether currently playing as music.
bool current_is_music_{};
uint32_t play_count_{};
float fade_{1.0f};
float gain_{1.0f};
std::unique_ptr<AudioSource> client_source_;
AudioServer* audio_server_{};
const Object::Ref<SoundAsset>* source_sound_{};
#if BA_ENABLE_AUDIO
ALuint source_{};
Object::Ref<AudioStreamer> streamer_;
#endif
}; // ThreadSource
@ -156,6 +154,22 @@ void AudioServer::OnMainThreadStartApp() {
event_loop_->PushCallSynchronous([this] { OnAppStartInThread_(); });
}
#if BA_OPENAL_IS_SOFT
static void ALEventCallback_(ALenum eventType, ALuint object, ALuint param,
ALsizei length, const ALchar* message,
ALvoid* userParam) noexcept {
if (eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT) {
if (g_base->audio_server) {
g_base->audio_server->event_loop()->PushCall(
[] { g_base->audio_server->OnDeviceDisconnected(); });
}
} else {
Log(LogLevel::kWarning, "Got unexpected OpenAL callback event "
+ std::to_string(static_cast<int>(eventType)));
}
}
#endif // BA_OPENAL_IS_SOFT
void AudioServer::OnAppStartInThread_() {
assert(g_base->InAudioThread());
@ -168,7 +182,7 @@ void AudioServer::OnAppStartInThread_() {
// Bring up OpenAL stuff.
{
const char* al_device_name = nullptr;
const char* al_device_name{};
// On the rift build in vr mode we need to make sure we open the rift audio
// device.
@ -212,21 +226,42 @@ void AudioServer::OnAppStartInThread_() {
"connected?");
}
impl_->alc_context = alcCreateContext(device, nullptr);
BA_PRECONDITION(impl_->alc_context);
BA_PRECONDITION(alcMakeContextCurrent(impl_->alc_context));
if (!impl_->alc_context) {
FatalError(
"Unable to init audio. Do you have speakers/headphones/etc. "
"connected?");
}
BA_PRECONDITION_FATAL(impl_->alc_context);
BA_PRECONDITION_FATAL(alcMakeContextCurrent(impl_->alc_context));
CHECK_AL_ERROR;
#if BA_OSTYPE_ANDROID
if (alcIsExtensionPresent(device, "ALC_SOFT_pause_device")) {
alcDevicePauseSOFT = reinterpret_cast<LPALCDEVICEPAUSESOFT>(
alcGetProcAddress(device, "alcDevicePauseSOFT"));
BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr);
alcDeviceResumeSOFT = reinterpret_cast<LPALCDEVICERESUMESOFT>(
alcGetProcAddress(device, "alcDeviceResumeSOFT"));
BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr);
} else {
FatalError("ALC_SOFT pause/resume functionality not found.");
}
#if BA_OPENAL_IS_SOFT
// Currently assuming the pause/resume and reset extensions are present.
// if (alcIsExtensionPresent(device, "ALC_SOFT_pause_device")) {
alcDevicePauseSOFT = reinterpret_cast<LPALCDEVICEPAUSESOFT>(
alcGetProcAddress(device, "alcDevicePauseSOFT"));
BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr);
alcDeviceResumeSOFT = reinterpret_cast<LPALCDEVICERESUMESOFT>(
alcGetProcAddress(device, "alcDeviceResumeSOFT"));
BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr);
alcResetDeviceSOFT = reinterpret_cast<LPALCRESETDEVICESOFT>(
alcGetProcAddress(device, "alcResetDeviceSOFT"));
BA_PRECONDITION_FATAL(alcResetDeviceSOFT != nullptr);
alEventCallbackSOFT = reinterpret_cast<LPALEVENTCALLBACKSOFT>(
alcGetProcAddress(device, "alEventCallbackSOFT"));
BA_PRECONDITION_FATAL(alEventCallbackSOFT != nullptr);
alEventControlSOFT = reinterpret_cast<LPALEVENTCONTROLSOFT>(
alcGetProcAddress(device, "alEventControlSOFT"));
BA_PRECONDITION_FATAL(alEventControlSOFT != nullptr);
// Ask to be notified when a device is disconnected.
alEventCallbackSOFT(ALEventCallback_, nullptr);
CHECK_AL_ERROR;
ALenum types[] = {AL_EVENT_TYPE_DISCONNECTED_SOFT};
alEventControlSOFT(1, types, AL_TRUE);
// } else {
// FatalError("ALC_SOFT pause/resume functionality not found.");
// }
#endif
}
@ -260,6 +295,7 @@ void AudioServer::OnAppStartInThread_() {
// Now make available any stopped sources (should be all of them).
UpdateAvailableSources_();
last_started_playing_time_ = g_core->GetAppTimeSeconds();
#endif // BA_ENABLE_AUDIO
}
@ -335,23 +371,27 @@ void AudioServer::SetSuspended_(bool suspend) {
#endif
// Pause OpenALSoft.
#if BA_OSTYPE_ANDROID
#if BA_OPENAL_IS_SOFT
BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr);
BA_PRECONDITION_FATAL(impl_ != nullptr && impl_->alc_context != nullptr);
auto* device = alcGetContextsDevice(impl_->alc_context);
BA_PRECONDITION_FATAL(device != nullptr);
try {
g_core->platform->LowLevelDebugLog(
"Calling alcDevicePauseSOFT at "
+ std::to_string(g_core->GetAppTimeSeconds()));
alcDevicePauseSOFT(device);
} catch (const std::exception& e) {
g_core->platform->LowLevelDebugLog(
std::string("EXC pausing alcDevice: ")
+ g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " "
+ e.what());
throw;
Log(LogLevel::kError,
"Error in alcDevicePauseSOFT at time "
+ std::to_string(g_core->GetAppTimeSeconds())
+ "( playing since "
+ std::to_string(last_started_playing_time_)
+ "): " + g_core->platform->DemangleCXXSymbol(typeid(e).name())
+ " " + e.what());
} catch (...) {
g_core->platform->LowLevelDebugLog("UNKNOWN EXC pausing alcDevice");
throw;
Log(LogLevel::kError, "Unknown error in alcDevicePauseSOFT");
}
#endif
@ -373,25 +413,28 @@ void AudioServer::SetSuspended_(bool suspend) {
#endif
#endif
// On android lets tell openal-soft to stop processing.
#if BA_OSTYPE_ANDROID
// With OpenALSoft lets tell openal-soft to resume processing.
#if BA_OPENAL_IS_SOFT
BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr);
BA_PRECONDITION_FATAL(impl_ != nullptr && impl_->alc_context != nullptr);
auto* device = alcGetContextsDevice(impl_->alc_context);
BA_PRECONDITION_FATAL(device != nullptr);
try {
g_core->platform->LowLevelDebugLog(
"Calling alcDeviceResumeSOFT at "
+ std::to_string(g_core->GetAppTimeSeconds()));
alcDeviceResumeSOFT(device);
} catch (const std::exception& e) {
g_core->platform->LowLevelDebugLog(
std::string("EXC resuming alcDevice: ")
+ g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " "
+ e.what());
throw;
Log(LogLevel::kError,
"Error in alcDeviceResumeSOFT at time "
+ std::to_string(g_core->GetAppTimeSeconds()) + ": "
+ g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " "
+ e.what());
} catch (...) {
g_core->platform->LowLevelDebugLog("UNKNOWN EXC resuming alcDevice");
throw;
Log(LogLevel::kError, "Unknown error in alcDeviceResumeSOFT");
}
#endif
last_started_playing_time_ = g_core->GetAppTimeSeconds();
suspended_ = false;
#if BA_ENABLE_AUDIO
CHECK_AL_ERROR;
@ -478,8 +521,8 @@ void AudioServer::PushSourcePlayCall(uint32_t play_id,
// Let's take this opportunity to pass on newly available sources.
// This way the more things clients are playing, the more
// tight our source availability checking gets (instead of solely relying on
// our periodic process() calls).
// tight our source availability checking gets (instead of solely relying
// on our periodic process() calls).
UpdateAvailableSources_();
});
}
@ -686,12 +729,58 @@ void AudioServer::UpdateMusicPlayState_() {
}
}
void AudioServer::ProcessDeviceDisconnects_(seconds_t real_time_seconds) {
#if BA_OPENAL_IS_SOFT
// If our device has been disconnected, try to reconnect it
// periodically.
auto* device = alcGetContextsDevice(impl_->alc_context);
BA_PRECONDITION_FATAL(device != nullptr);
ALCint connected{-1};
alcGetIntegerv(device, ALC_CONNECTED, sizeof(connected), &connected);
CHECK_AL_ERROR;
if (connected == 0 && real_time_seconds - last_reset_attempt_time_ > 10.0) {
Log(LogLevel::kInfo, "OpenAL device disconnected; resetting...");
last_reset_attempt_time_ = real_time_seconds;
BA_PRECONDITION_FATAL(alcResetDeviceSOFT != nullptr);
alcResetDeviceSOFT(device, nullptr);
CHECK_AL_ERROR;
// Make noise if this ever fails to bring the device back.
ALCint connected{-1};
alcGetIntegerv(device, ALC_CONNECTED, sizeof(connected), &connected);
CHECK_AL_ERROR;
// If we were successful, don't require a wait for the next reset.
// (otherwise plugging in headphones and then unplugging will stay quiet
// for 10 seconds).
if (connected == 1) {
last_reset_attempt_time_ = -999.0;
}
if (connected == 0 && !reported_reset_fail_) {
reported_reset_fail_ = true;
Log(LogLevel::kError, "alcResetDeviceSOFT failed to reconnect device.");
}
}
#endif // BA_OPENAL_IS_SOFT
}
void AudioServer::OnDeviceDisconnected() {
assert(g_base->InAudioThread());
// All we do here is run an explicit Process_. This only saves us a half
// second or so over letting the timer do it, but hey we'll take it.
Process_();
}
void AudioServer::Process_() {
assert(g_base->InAudioThread());
millisecs_t real_time = g_core->GetAppTimeMillisecs();
seconds_t real_time_seconds = g_core->GetAppTimeSeconds();
millisecs_t real_time_millisecs = real_time_seconds * 1000;
// Only do real work if we're in normal running mode.
if (!suspended_ && !shutting_down_) {
ProcessDeviceDisconnects_(real_time_seconds);
// Do some loading...
have_pending_loads_ = g_base->assets->RunPendingAudioLoads();
@ -699,29 +788,28 @@ void AudioServer::Process_() {
UpdateAvailableSources_();
// Update our fading sound volumes.
if (real_time - last_sound_fade_process_time_ > 50) {
if (real_time_millisecs - last_sound_fade_process_time_ > 50) {
ProcessSoundFades_();
last_sound_fade_process_time_ = real_time;
last_sound_fade_process_time_ = real_time_millisecs;
}
// Update streaming sources.
if (real_time - last_stream_process_time_ > 100) {
last_stream_process_time_ = real_time;
if (real_time_millisecs - last_stream_process_time_ > 100) {
last_stream_process_time_ = real_time_millisecs;
for (auto&& i : streaming_sources_) {
i->Update();
}
}
// If the app has switched active/inactive state, update
// our volumes (we may silence our audio in these cases).
// If the app has switched active/inactive state, update our volumes (we
// may silence our audio in these cases).
auto app_active = g_base->app_active();
if (app_active != app_active_) {
app_active_ = app_active;
if (g_base->app_adapter->ShouldSilenceAudioWhenInactive()) {
app_active_volume_ = app_active ? 1.0f : 0.0f;
} else {
app_active_volume_ = 1.0f;
}
app_active_volume_ =
(!app_active && g_base->app_adapter->ShouldSilenceAudioForInactive())
? 0.0f
: 1.0f;
for (auto&& i : sources_) {
i->UpdateVolume();
}
@ -797,7 +885,8 @@ void AudioServer::ProcessSoundFades_() {
}
void AudioServer::FadeSoundOut(uint32_t play_id, uint32_t time) {
// Pop a new node on the list (this won't overwrite the old if there is one).
// Pop a new node on the list (this won't overwrite the old if there is
// one).
sound_fade_nodes_.insert(
std::make_pair(play_id, SoundFadeNode_(play_id, time, true)));
}
@ -882,8 +971,8 @@ void AudioServer::ThreadSource_::UpdateAvailability() {
assert(g_base->InAudioThread());
// If it's waiting to be picked up by a client or has pending client commands,
// skip.
// If it's waiting to be picked up by a client or has pending client
// commands, skip.
if (!client_source_->TryLock(6)) {
return;
}
@ -895,10 +984,9 @@ void AudioServer::ThreadSource_::UpdateAvailability() {
}
// We consider ourselves busy if there's an active looping play command
// (regardless of its actual physical play state - music could be turned off,
// stuttering, etc.).
// If it's non-looping, we check its play state and snatch it if it's not
// playing.
// (regardless of its actual physical play state - music could be turned
// off, stuttering, etc.). If it's non-looping, we check its play state and
// snatch it if it's not playing.
bool busy;
if (looping_ || (is_streamed_ && streamer_.Exists() && streamer_->loops())) {
busy = want_to_play_;
@ -1244,16 +1332,6 @@ void AudioServer::PushSetSoundPitchCall(float val) {
event_loop()->PushCall([this, val] { SetSoundPitch_(val); });
}
// void AudioServer::PushSetSuspendedCall(bool suspend) {
// event_loop()->PushCall([this, suspend] {
// if (g_buildconfig.ostype_android()) {
// Log(LogLevel::kError, "Shouldn't be getting SetSuspendedCall on
// android.");
// }
// SetSuspended_(suspend);
// });
// }
void AudioServer::PushComponentUnloadCall(
const std::vector<Object::Ref<Asset>*>& components) {
event_loop()->PushCall([components] {

View File

@ -67,6 +67,8 @@ class AudioServer {
auto event_loop() const -> EventLoop* { return event_loop_; }
void OnDeviceDisconnected();
private:
class ThreadSource_;
struct Impl_;
@ -90,6 +92,7 @@ class AudioServer {
void Reset_();
void Process_();
void ProcessDeviceDisconnects_(seconds_t real_time_seconds);
/// Send a component to the audio thread to delete.
// void DeleteAssetComponent_(Asset* c);
@ -122,7 +125,11 @@ class AudioServer {
bool suspended_{};
bool shutdown_completed_{};
bool shutting_down_{};
bool reported_reset_fail_{};
int al_source_count_{};
seconds_t last_reset_attempt_time_{-999.0};
seconds_t shutdown_start_time_{};
seconds_t last_started_playing_time_{};
millisecs_t last_sound_fade_process_time_{};
/// Indexed list of sources.
@ -146,8 +153,6 @@ class AudioServer {
// Our list of sound media components to delete via the main thread.
std::vector<const Object::Ref<SoundAsset>*> sound_ref_delete_list_;
int al_source_count_{};
};
} // namespace ballistica::base

View File

@ -198,6 +198,8 @@ void BaseFeatureSet::StartApp() {
BA_PRECONDITION(g_core->InMainThread());
BA_PRECONDITION(g_base);
auto start_time = g_core->GetAppTimeSeconds();
// Currently limiting this to once per process.
BA_PRECONDITION(!called_start_app_);
called_start_app_ = true;
@ -248,6 +250,17 @@ void BaseFeatureSet::StartApp() {
}
g_core->LifecycleLog("start-app end (main thread)");
// Make some noise if this takes more than a few seconds. If we pass 5
// seconds or so we start to trigger App-Not-Responding reports which
// isn't good.
auto duration = g_core->GetAppTimeSeconds() - start_time;
if (duration > 3.0) {
char buffer[128];
snprintf(buffer, sizeof(buffer),
"StartApp() took too long (%.2lf seconds).", duration);
Log(LogLevel::kWarning, buffer);
}
}
void BaseFeatureSet::SuspendApp() {
@ -890,8 +903,6 @@ void BaseFeatureSet::ShutdownSuppressDisallow() {
shutdown_suppress_disallowed_ = true;
}
// auto BaseFeatureSet::GetReturnValue() const -> int { return return_value(); }
void BaseFeatureSet::QuitApp(bool confirm, QuitType quit_type) {
// If they want a confirm dialog and we're able to present one, do that.
if (confirm && !g_core->HeadlessMode() && !g_base->input->IsInputLocked()

View File

@ -87,7 +87,7 @@ class TextGroup : public Object {
Object::Ref<TextureAsset> os_texture_;
std::vector<std::unique_ptr<TextMeshEntry>> entries_;
std::string text_;
bool big_;
bool big_{};
};
} // namespace ballistica::base

View File

@ -155,27 +155,24 @@ void Input::AnnounceConnects_() {
if (first_print && g_core->GetAppTimeSeconds() < 3.0) {
first_print = false;
// Disabling this completely on Android for now; we often get large
// numbers of devices there that aren't actually devices.
bool do_print_initial_counts{!g_buildconfig.ostype_android()};
// If there's been several connected, just give a number.
if (explicit_bool(do_print_initial_counts)) {
if (newly_connected_controllers_.size() > 1) {
std::string s =
g_base->assets->GetResourceString("controllersDetectedText");
Utils::StringReplaceOne(
&s, "${COUNT}",
std::to_string(newly_connected_controllers_.size()));
ScreenMessage(s);
} else {
ScreenMessage(
g_base->assets->GetResourceString("controllerDetectedText"));
}
if (newly_connected_controllers_.size() > 1) {
std::string s =
g_base->assets->GetResourceString("controllersDetectedText");
Utils::StringReplaceOne(
&s, "${COUNT}", std::to_string(newly_connected_controllers_.size()));
ScreenMessage(s);
} else {
ScreenMessage(
g_base->assets->GetResourceString("controllerDetectedText"));
}
} else {
// If there's been several connected, just give a number.
if (newly_connected_controllers_.size() > 1) {
for (auto&& s : newly_connected_controllers_) {
Log(LogLevel::kInfo, "GOT CONTROLLER " + s);
}
std::string s =
g_base->assets->GetResourceString("controllersConnectedText");
Utils::StringReplaceOne(
@ -193,7 +190,6 @@ void Input::AnnounceConnects_() {
g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kGunCock));
}
}
newly_connected_controllers_.clear();
}
@ -222,6 +218,14 @@ void Input::AnnounceDisconnects_() {
void Input::ShowStandardInputDeviceConnectedMessage_(InputDevice* j) {
assert(g_base->InLogicThread());
// On Android we never show messages for initial input-devices; we often
// get large numbers of strange virtual devices that aren't actually
// controllers so this is more confusing than helpful.
if (g_buildconfig.ostype_android() && g_core->GetAppTimeSeconds() < 3.0) {
return;
}
std::string suffix;
suffix += j->GetPersistentIdentifier();
suffix += j->GetDeviceExtraDescription();
@ -1239,7 +1243,14 @@ void Input::HandleSmoothMouseScroll_(const Vector2f& velocity, bool momentum) {
}
void Input::PushMouseMotionEvent(const Vector2f& position) {
assert(g_base->logic->event_loop());
auto* loop = g_base->logic->event_loop();
assert(loop);
// Don't overload it with events if it's stuck.
if (!loop->CheckPushSafety()) {
return;
}
g_base->logic->event_loop()->PushCall(
[this, position] { HandleMouseMotion_(position); });
}

View File

@ -376,8 +376,10 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
Vector3f(1, 1, 1));
});
g_base->logic->event_loop()->PushCall([] {
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kGunCock));
if (g_base->assets->asset_loads_allowed()) {
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kGunCock));
}
});
}
clients_[i].in_use = true;
@ -426,9 +428,12 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
});
g_base->logic->event_loop()->PushCall([] {
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kGunCock));
if (g_base->assets->asset_loads_allowed()) {
g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kGunCock));
}
});
std::string utf8 = Utils::GetValidUTF8(clients_[i].display_name, "rsgc1");
clients_[i].joystick_ = Object::NewDeferred<JoystickInput>(
-1, // not an sdl joystick

View File

@ -33,12 +33,10 @@ void NetworkReader::OnAppSuspend() {
paused_ = true;
}
// Ok now attempt to send a quick ping to ourself to wake us up so we can kill
// our socket.
// It's possible that we get suspended before port is set, so this could
// still be -1.
if (port4_ != -1) {
PokeSelf_();
} else {
Log(LogLevel::kError, "NetworkReader port is -1 on pause");
}
}

View File

@ -1788,6 +1788,32 @@ static PyMethodDef PyNativeReviewRequestDef = {
"\n"
"(internal)",
};
// ------------------------------- temp_testing --------------------------------
static auto PyTempTesting(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
std::string devstr = g_core->platform->GetDeviceName() + " "
+ g_core->platform->GetOSVersionString();
if (devstr == "samsung SM-N950F 7.1.1") {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyTempTestingDef = {
"temp_testing", // name
(PyCFunction)PyTempTesting, // method
METH_NOARGS, // flags
"temp_testing() -> bool\n"
"\n"
"(internal)",
};
// -----------------------------------------------------------------------------
auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
@ -1856,6 +1882,7 @@ auto PythonMethodsMisc::GetMethods() -> std::vector<PyMethodDef> {
PyUsingGameCenterDef,
PyNativeReviewRequestSupportedDef,
PyNativeReviewRequestDef,
PyTempTestingDef,
};
}

View File

@ -236,9 +236,6 @@ void UI::MainMenuPress_(InputDevice* device) {
assert(g_base->InLogicThread());
if (auto* ui_delegate = g_base->ui->delegate()) {
ui_delegate->DoHandleDeviceMenuPress(device);
} else {
Log(LogLevel::kWarning,
"UI::MainMenuPress called without ui_v1 present; unexpected.");
}
}

View File

@ -158,10 +158,10 @@ class CoreFeatureSet {
bool v1_cloud_log_full{};
int master_server_source{};
std::vector<EventLoop*> suspendable_event_loops;
std::mutex v1_cloud_log_mutex;
std::string v1_cloud_log;
std::mutex thread_name_map_mutex;
std::unordered_map<std::thread::id, std::string> thread_name_map;
std::mutex v1_cloud_log_mutex;
std::string v1_cloud_log;
#if BA_DEBUG_BUILD
std::mutex object_list_mutex;

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kEngineBuildNumber = 21657;
const int kEngineBuildNumber = 21693;
const char* kEngineVersion = "1.7.30";
const int kEngineApiVersion = 8;
@ -53,6 +53,8 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int {
core::BaseSoftInterface* l_base{};
try {
auto time1 = core::CorePlatform::GetCurrentMillisecs();
// Even at the absolute start of execution we should be able to
// reasonably log errors. Set env var BA_CRASH_TEST=1 to test this.
if (const char* crashenv = getenv("BA_CRASH_TEST")) {
@ -66,6 +68,8 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int {
// import it first thing even if we don't explicitly use it.
l_core = core::CoreFeatureSet::Import(&core_config);
auto time2 = core::CorePlatform::GetCurrentMillisecs();
// If a command was passed, simply run it and exit. We want to act
// simply as a Python interpreter in that case; we don't do any
// environment setup (aside from the bits core does automatically such
@ -90,6 +94,8 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int {
// those modules get loaded from in the first place.
l_core->python->MonolithicModeBaEnvConfigure();
auto time3 = core::CorePlatform::GetCurrentMillisecs();
// We need the base feature-set to run a full app but we don't have a hard
// dependency to it. Let's see if it's available.
l_base = l_core->SoftImportBase();
@ -97,6 +103,8 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int {
FatalError("Base module unavailable; can't run app.");
}
auto time4 = core::CorePlatform::GetCurrentMillisecs();
// -------------------------------------------------------------------------
// Phase 2: "The pieces are moving."
// -------------------------------------------------------------------------
@ -113,6 +121,23 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int {
// until the app exits (or we return from this function and let the
// environment do that part).
// Make noise if it takes us too long to get to this point.
auto time5 = core::CorePlatform::GetCurrentMillisecs();
auto total_duration = time5 - time1;
if (total_duration > 5000) {
auto core_import_duration = time2 - time1;
auto env_config_duration = time3 - time2;
auto base_import_duration = time4 - time3;
auto start_app_duration = time5 - time4;
Log(LogLevel::kWarning,
"MonolithicMain took too long (" + std::to_string(total_duration)
+ " ms; " + std::to_string(core_import_duration)
+ " core-import, " + std::to_string(env_config_duration)
+ " env-config, " + std::to_string(base_import_duration)
+ " base-import, " + std::to_string(start_app_duration)
+ " start-app).");
}
if (l_base->AppManagesMainThreadEventLoop()) {
// In environments where we control the event loop, do that.
l_base->RunAppToCompletion();
@ -130,20 +155,20 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int {
std::string error_msg =
std::string("Unhandled exception in MonolithicMain(): ") + exc.what();
// Let the user and/or master-server know we're dying.
// Let the user and/or master-server know what killed us.
FatalError::ReportFatalError(error_msg, true);
// Exiting the app via an exception leads to crash reports on various
// platforms. If it seems we're not on an official live build then we'd
// rather just exit cleanly with an error code and avoid polluting crash
// report logs with reports from dev builds.
// Exiting the app via an exception tends to lead to crash reports. If
// it seems we're not on an official live build then we'd rather just
// exit cleanly with an error code and avoid polluting crash report logs
// with reports from dev builds.
bool try_to_exit_cleanly = !(l_base && l_base->IsUnmodifiedBlessedBuild());
// If this is true it means the app is handling things (showing a fatal
// error dialog, etc.) and it's out of our hands.
// If this returns true, it means the app is handling things (showing a
// fatal error dialog, etc.) and it's out of our hands.
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
// Do the default thing if it's not been handled.
// If it's not been handled, take the app down ourself.
if (!handled) {
if (try_to_exit_cleanly) {
exit(1);
@ -155,22 +180,95 @@ auto MonolithicMain(const core::CoreConfig& core_config) -> int {
return 0;
}
// A way to do the same as above except in an incremental manner. This can
// be used to avoid app-not-responding reports on slow devices by
// interleaving engine init steps with other event processing.
class IncrementalInitRunner_ {
public:
explicit IncrementalInitRunner_(const core::CoreConfig* config)
: config_(*config) {}
auto Process() -> bool {
if (zombie_) {
return false;
}
try {
switch (step_) {
case 0:
core_ = core::CoreFeatureSet::Import(&config_);
step_++;
return false;
case 1:
core_->python->MonolithicModeBaEnvConfigure();
step_++;
return false;
case 2:
base_ = core_->SoftImportBase();
if (!base_) {
FatalError("Base module unavailable; can't run app.");
}
step_++;
return false;
case 3:
base_->StartApp();
Python::PermanentlyReleaseGIL();
step_++;
return false;
default:
return true;
}
} catch (const std::exception& exc) {
std::string error_msg =
std::string("Unhandled exception in MonolithicMain(): ") + exc.what();
// Let the user and/or master-server know what killed us.
FatalError::ReportFatalError(error_msg, true);
// Exiting the app via an exception tends to lead to crash reports. If
// it seems we're not on an official live build then we'd rather just
// exit cleanly with an error code and avoid polluting crash report logs
// with reports from dev builds.
bool try_to_exit_cleanly = !(base_ && base_->IsUnmodifiedBlessedBuild());
// If this returns true, it means the app is handling things (showing a
// fatal error dialog, etc.) and it's out of our hands.
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, true);
// If it's not been handled, take the app down ourself.
if (!handled) {
if (try_to_exit_cleanly) {
exit(1);
} else {
throw; // Crash report here we come!
}
}
// Just go into vegetable mode so hopefully the handler can do its
// thing.
zombie_ = true;
return false;
}
}
private:
int step_{};
bool zombie_{};
core::CoreConfig config_;
core::CoreFeatureSet* core_{};
core::BaseSoftInterface* base_{};
};
static IncrementalInitRunner_* g_incremental_init_runner_{};
auto MonolithicMainIncremental(const core::CoreConfig* config) -> bool {
if (g_incremental_init_runner_ == nullptr) {
g_incremental_init_runner_ = new IncrementalInitRunner_(config);
}
return g_incremental_init_runner_->Process();
}
#endif // BA_MONOLITHIC_BUILD
void FatalError(const std::string& message) {
// Let the user and/or master-server know we're dying.
FatalError::ReportFatalError(message, false);
// Exiting the app via an exception leads to crash reports on various
// platforms. If it seems we're not on an official live build then we'd
// rather just exit cleanly with an error code and avoid polluting crash
// report logs with reports from dev builds.
bool try_to_exit_cleanly =
!(core::g_base_soft && core::g_base_soft->IsUnmodifiedBlessedBuild());
bool handled = FatalError::HandleFatalError(try_to_exit_cleanly, false);
if (!handled) {
throw Exception("A fatal error occurred.");
}
FatalError::DoFatalError(message);
}
void Log(LogLevel level, const std::string& msg) { Logging::Log(level, msg); }

View File

@ -68,6 +68,11 @@ class CoreConfig;
/// Entry point for standard monolithic builds. Handles all initing and
/// running.
auto MonolithicMain(const core::CoreConfig& config) -> int;
/// Special alternate version of MonolithicMain which breaks its work into
/// pieces; used to reduce app-not-responding reports from slow Android
/// devices. Call this repeatedly until it returns true;
auto MonolithicMainIncremental(const core::CoreConfig* config) -> bool;
#endif // BA_MONOLITHIC_BUILD
// Print a momentary message on the screen.

View File

@ -342,47 +342,46 @@ void EventLoop::GetThreadMessages_(std::list<ThreadMessage_>* messages) {
void EventLoop::BootstrapThread_() {
assert(!bootstrapped_);
thread_id_ = std::this_thread::get_id();
const char* name;
const char* id_string;
switch (identifier_) {
case EventLoopID::kLogic:
name = "logic";
name_ = "logic";
id_string = "ballistica logic";
break;
case EventLoopID::kStdin:
name = "stdin";
name_ = "stdin";
id_string = "ballistica stdin";
break;
case EventLoopID::kAssets:
name = "assets";
name_ = "assets";
id_string = "ballistica assets";
break;
case EventLoopID::kFileOut:
name = "fileout";
name_ = "fileout";
id_string = "ballistica file-out";
break;
case EventLoopID::kMain:
name = "main";
name_ = "main";
id_string = "ballistica main";
break;
case EventLoopID::kAudio:
name = "audio";
name_ = "audio";
id_string = "ballistica audio";
break;
case EventLoopID::kBGDynamics:
name = "bgdynamics";
name_ = "bgdynamics";
id_string = "ballistica bg-dynamics";
break;
case EventLoopID::kNetworkWrite:
name = "networkwrite";
name_ = "networkwrite";
id_string = "ballistica network-write";
break;
default:
throw Exception();
}
assert(name && id_string);
SetInternalThreadName_(name);
assert(!name_.empty() && id_string);
SetInternalThreadName_(name_);
// Note: we currently don't do this for our main thread because it
// changes the process name we see in top/etc. Should look into that.
@ -552,8 +551,7 @@ void EventLoop::PushThreadMessage_(const ThreadMessage_& t) {
if (!sent_error) {
sent_error = true;
log_entries.emplace_back(
LogLevel::kError,
"ThreadMessage list > 1000 in thread: " + CurrentThreadName());
LogLevel::kError, "ThreadMessage list > 1000 in thread: " + name_);
LogThreadMessageTally_(&log_entries);
}
@ -561,8 +559,7 @@ void EventLoop::PushThreadMessage_(const ThreadMessage_& t) {
// Prevent runaway mem usage if the list gets out of control.
if (thread_messages_.size() > 10000) {
FatalError("ThreadMessage list > 10000 in thread: "
+ CurrentThreadName());
FatalError("ThreadMessage list > 10000 in thread: " + name_);
}
// Unlock thread-message list and inform thread that there's something

View File

@ -97,6 +97,8 @@ class EventLoop {
auto suspended() { return suspended_; }
auto done() -> bool { return done_; }
auto name() const { return name_; }
private:
struct ThreadMessage_ {
enum class Type { kShutdown = 999, kRunnable, kSuspend, kUnsuspend };
@ -149,13 +151,6 @@ class EventLoop {
void BootstrapThread_();
// void LoopUpkeep_(bool single_cycle);
// FIXME: Should generalize this to some sort of PlatformThreadData class.
#if BA_XCODE_BUILD
// void* auto_release_pool_{};
#endif
EventLoopID identifier_{EventLoopID::kInvalid};
ThreadSource source_{};
bool bootstrapped_{};
@ -173,6 +168,7 @@ class EventLoop {
std::mutex thread_message_mutex_;
std::mutex client_listener_mutex_;
std::list<std::vector<char>> data_to_client_;
std::string name_;
PyThreadState* py_thread_state_{};
TimerList timers_;
};

View File

@ -15,15 +15,31 @@ namespace ballistica {
using core::g_base_soft;
using core::g_core;
bool FatalError::reported_{};
void FatalError::DoFatalError(const std::string& message) {
// Let the user and/or master-server know we're dying.
ReportFatalError(message, false);
// In some cases we prefer to cleanly exit the app with an error code
// in a way that won't wind up as a crash report; this avoids polluting
// our crash reports list with stuff from dev builds.
bool try_to_exit_cleanly =
!(core::g_base_soft && core::g_base_soft->IsUnmodifiedBlessedBuild());
bool handled = HandleFatalError(try_to_exit_cleanly, false);
if (!handled) {
abort();
}
}
void FatalError::ReportFatalError(const std::string& message,
bool in_top_level_exception_handler) {
// We want to report the first fatal error that happens; if further ones
// happen they are probably red herrings.
static bool ran = false;
if (ran) {
// We want to report only the first fatal error that happens; if further
// ones happen they are likely red herrings triggered by the first.
if (reported_) {
return;
}
ran = true;
reported_ = true;
// Our main goal here varies based off whether we are an unmodified
// blessed build. If we are, our main goal is to communicate as much info
@ -139,8 +155,9 @@ void FatalError::DoBlockingFatalErrorDialog(const std::string& message) {
bool* startedptr{&started};
bool* finishedptr{&finished};
// If our thread is holding the GIL, release it to give the main
// thread a better chance to get to the point of displaying the fatal error.
// If our thread is holding the GIL, release it to give the main thread
// a better chance of getting to the point of displaying the fatal
// error.
if (Python::HaveGIL()) {
Python::PermanentlyReleaseGIL();
}
@ -152,7 +169,7 @@ void FatalError::DoBlockingFatalErrorDialog(const std::string& message) {
}));
// Wait a short amount of time for the main thread to take action.
// There's a chance that it can't (if threads are paused, if it is
// There's a chance that it can't (if threads are suspended, if it is
// blocked on a synchronous call to another thread, etc.) so if we don't
// see something happening soon, just give up on showing a dialog.
auto starttime = core::CorePlatform::GetCurrentMillisecs();
@ -192,7 +209,7 @@ auto FatalError::HandleFatalError(bool exit_cleanly,
}
// Otherwise its up to who called us (they might let the caught exception
// bubble up)
// bubble up).
return false;
}

View File

@ -9,24 +9,31 @@ namespace ballistica {
class FatalError {
public:
/// Complete high level level fatal error call; does both reporting and
/// handling. ballistica::FatalError() simply calls this.
static void DoFatalError(const std::string& message);
/// Report a fatal error to the master-server/user/etc. Note that reporting
/// only happens for the first invocation of this call; additional calls
/// are no-ops.
/// are no-ops. This is because the process of tearing down the app may
/// trigger additional errors which are red herrings.
static void ReportFatalError(const std::string& message,
bool in_top_level_exception_handler);
/// Handle a fatal error. This can involve calling exit(), abort(), setting
/// up an asynchronous quit, etc. Returns true if the fatal-error has been
/// handled; otherwise it is up to the caller (this should only be the case
/// when in_top_level_exception_handler is true).
/// Unlike ReportFatalError, the logic in this call can be invoked repeatedly
/// and should be prepared for that possibility in the case of recursive
/// fatal errors/etc.
/// Handle a fatal error. This can involve calling exit(), abort(),
/// setting up an asynchronous quit, etc. Returns true if the fatal-error
/// has been handled; otherwise it is up to the caller (this should only
/// be the case when in_top_level_exception_handler is true).
///
/// Unlike ReportFatalError, the logic in this call can be invoked
/// repeatedly and should be prepared for that possibility in the case of
/// recursive fatal errors/etc.
static auto HandleFatalError(bool clean_exit,
bool in_top_level_exception_handler) -> bool;
private:
static void DoBlockingFatalErrorDialog(const std::string& message);
static bool reported_;
};
} // namespace ballistica

View File

@ -2,8 +2,13 @@
#include "ballistica/shared/generic/runnable.h"
#include "ballistica/core/core.h"
#include "ballistica/core/platform/core_platform.h"
namespace ballistica {
using core::g_core;
auto Runnable::GetThreadOwnership() const -> Object::ThreadOwnership {
return ThreadOwnership::kNextReferencing;
}
@ -12,7 +17,14 @@ void Runnable::RunAndLogErrors() {
try {
Run();
} catch (const std::exception& exc) {
Log(LogLevel::kError, std::string("Error in Runnable: ") + exc.what());
std::string type_name;
if (g_core != nullptr) {
type_name = g_core->platform->DemangleCXXSymbol(typeid(exc).name());
} else {
type_name = "<type unavailable>";
}
Log(LogLevel::kError,
std::string("Error in Runnable: " + type_name + ": ") + exc.what());
}
}

View File

@ -718,7 +718,6 @@ def logcat() -> None:
raise CleanError('Expected 2 args')
adb = sys.argv[2]
plat = sys.argv[3]
print('plat is', plat)
# My amazon tablet chokes on the color format.
if plat == 'amazon':

View File

@ -60,7 +60,12 @@ def build_openal(arch: str, mode: str) -> None:
['git', 'clone', 'https://github.com/kcat/openal-soft.git', builddir],
check=True,
)
subprocess.run(['git', 'checkout', '1.23.1'], check=True, cwd=builddir)
# subprocess.run(['git', 'checkout', '1.23.1'], check=True, cwd=builddir)
subprocess.run(
['git', 'checkout', '5b5b948516f7340810ebbfdd5e46eb40f85d2e56'],
check=True,
cwd=builddir,
)
# Grab Oboe
builddir_oboe = f'{builddir}_oboe'