tidying app classes

This commit is contained in:
Eric 2023-08-22 09:38:15 -07:00
parent 16ef618a93
commit bf94886aed
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
27 changed files with 257 additions and 323 deletions

88
.efrocachemap generated
View File

@ -4068,50 +4068,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": "60b5c6ab04b194345ff6257b70d69a5c", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "6d094a3cd65948896b4c447261e81704",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "b9c878e7396fd1426c2a99fffd600aab", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "430032b34e9b9f85dbf78ef48b72d132",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "6619693fb688c49e3fd87ef9c9c5211a", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "b61d8e726e0019cd4109aa5e9533867c",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a1f18844189de4fe40a56f345b33ef62", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "2c293043357d79e665bbbea2e5134cff",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "f04b028c0bdfbc84ce1d646977d0c06a", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "f793ef2f26fa13dad8d35e90c8c141d8",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "8e96e29356cc8157e5aa1cb30e597c78", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "5c301636ebf4f824a69d27a0beb11218",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "d4cc2f2ed1648c310aba235b30b43c79", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "8ec42bbe83ca4d0734f6e9697525cd7b",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "2c246e46c453d94e60be71f3f0d5eefe", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "a88db549342f7b10820e6028eb25b552",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "7b25f37a94583dfdf277d5d55d4c8ee2", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "d767a9da339452eca123eedc1a1613ab",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "2ce3e00ae6d5ff8e65697b68438e213e", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "fe23ddd3129d3ca1582a51e6ae6bd2f0",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "9a26cab66c09e657c0ac3233b1ecf0d7", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "da61ad34ea9852ee90216b384b9fa810",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "b20ad6abed43e7bec2de8fa04bff71e2", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "5bcafd99e07f74fb5354d8dc51f9dcd3",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "29999f55a9ccb252c621e714a1d15c9c", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "fbabe1c5a50b4fc288990855109c0f9a",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "3f75965547ef911e2ad85a7b1786eb43", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "1ee18569ef3e9ff79f89d4fe63b1ab48",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "7257acfca19121cc25be53e698c73d9c", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "6f5b8dabf7e99183804a325ede1801af",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8b45af314375a63d939a906d9f211bda", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "85bdeabd281d3b99f3a493e9ea68205f",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "736b5398e8396be443695e8b2bd572d9", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "352dee4c52b8f5a41374ba991365f2dd",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "56e73294a44f2d1f5bff3f4a9f973c2e", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "2b4255b6beaeb7792e3e0bb879d1549c",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "5630f2dd810cf19d1c309a579a0febde", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "b01cff4e97ac0e9abc4552068f6e2816",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "4c5506d6eb64252f0fb7275e13e3912c", "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "74ee01e9a88eb4ce1acdeef34d30dd49",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "85ba4e81a1f7ae2cff4b1355eb49904f", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "bf4e9fe0c457660b45d2e4546b8e32e4",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "498921f7eb2afd327d4b900cb70e31f9", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "de7dd11c6a017f9f28dd619b56463e95",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "85ba4e81a1f7ae2cff4b1355eb49904f", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "bf4e9fe0c457660b45d2e4546b8e32e4",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "498921f7eb2afd327d4b900cb70e31f9", "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "de7dd11c6a017f9f28dd619b56463e95",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "ded5f785236bf64e644ee20041ac8342", "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "ee204e8f8b8fc35d68166d0491255f87",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "c436a058b7204fa39f22eafc7ca7855f", "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "a79f838c7225f4bfb8d572a5caf79b91",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "ded5f785236bf64e644ee20041ac8342", "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "ee204e8f8b8fc35d68166d0491255f87",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "c436a058b7204fa39f22eafc7ca7855f", "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "a79f838c7225f4bfb8d572a5caf79b91",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "fe0ba4b21528a557c5a434b8f2eeda41", "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "c3f22a4373f4179fa694c5de0f68cbb7",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "7950a02c3d9a1088e9acd4c29bd3cb72", "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "437ad30ef4a8de4c19da41047ea721df",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "fe0ba4b21528a557c5a434b8f2eeda41", "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "c3f22a4373f4179fa694c5de0f68cbb7",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "7950a02c3d9a1088e9acd4c29bd3cb72", "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "437ad30ef4a8de4c19da41047ea721df",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "870d11d339fd1b3acf66cc601ff29c83", "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "c1fa4493a7afcc41c77a2ddf0697f030",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "0ab638b6602610bdaf432e3cc2464080", "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "b2f88f60a0efd2fd8e1dea74ca79e925",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "92394eb19387c363471ce134ac9e6a1b", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "acd5007aba0e74f5d3a3df6e7b3abab6",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "0ab638b6602610bdaf432e3cc2464080", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "b2f88f60a0efd2fd8e1dea74ca79e925",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "ac1e455123d1c71aa09a16f1e6c4ecad", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "77af3a22db2ea65aa1f7b6203363ff40",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "68323b0304b2158b09d1af9573fc6d85", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "695204948e2b06fd91e05ee3e6a66e0a",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "0f9b59502d1faf0b783420f78ee383f0", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "723e0bd1d1218d2b84f4647638cbdec7",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "e5df1b61f11ec0e2b7a1b28b22390574", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "8206655af59a053685329b87046ff58d",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "4d097132b48f21c218b7129ad4d5f9a4", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "c3ee54fd24061523dc56e983fef53b60",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "6dc492eb1c9bf2e9e9fb591ea81fa253", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "70f23230117e24af61cce73a597851ee",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "ad53f8041a683789e5f16d762a3d3b2c", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "322f179c3b4ebe52c4cd9da8206e499c",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "5d161b0abe97805381be809c0b5da828", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "6c17bf39253bb5bb3359786ae6f0246e",
"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": "f8cd3af311ac63147882590123b78318", "src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "eeddad968b176000e31c65be6206a2bc", "src/ballistica/base/mgen/pyembed/binding_base.inc": "eeddad968b176000e31c65be6206a2bc",

View File

@ -1,4 +1,4 @@
### 1.7.26 (build 21221, api 8, 2023-08-21) ### 1.7.26 (build 21232, api 8, 2023-08-22)
- Various general improvements to the pcommand (project command) system. - Various general improvements to the pcommand (project command) system.
- Modules containing pcommand functions are now named with an 's' - so - Modules containing pcommand functions are now named with an 's' - so

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 = 21221 TARGET_BALLISTICA_BUILD = 21232
TARGET_BALLISTICA_VERSION = '1.7.26' TARGET_BALLISTICA_VERSION = '1.7.26'

View File

@ -19,8 +19,7 @@
namespace ballistica::base { namespace ballistica::base {
App::App(EventLoop* event_loop) App::App() {
: event_loop_(event_loop), stress_test_(std::make_unique<StressTest>()) {
// We modify some app behavior when run under the server manager. // We modify some app behavior when run under the server manager.
auto* envval = getenv("BA_SERVER_WRAPPER_MANAGED"); auto* envval = getenv("BA_SERVER_WRAPPER_MANAGED");
server_wrapper_managed_ = (envval && strcmp(envval, "1") == 0); server_wrapper_managed_ = (envval && strcmp(envval, "1") == 0);
@ -40,6 +39,8 @@ void App::PostInit() {
g_core->platform->GetLegacyUserAgentString()); g_core->platform->GetLegacyUserAgentString());
} }
App::~App() = default;
void App::DoLogicThreadApplyAppConfig() { void App::DoLogicThreadApplyAppConfig() {
// Note: this gets called in the logic thread since that's where // Note: this gets called in the logic thread since that's where
// config reading happens. We should grab whatever values we need // config reading happens. We should grab whatever values we need
@ -87,7 +88,7 @@ void App::RunRenderUpkeepCycle() {
// Pump thread messages (we're being driven by frame-draw callbacks // Pump thread messages (we're being driven by frame-draw callbacks
// so this is the only place that it gets done at). // so this is the only place that it gets done at).
event_loop()->RunSingleCycle(); g_core->main_event_loop()->RunSingleCycle();
// Now do the general app event cycle for whoever needs to process things. // Now do the general app event cycle for whoever needs to process things.
RunEvents(); RunEvents();
@ -132,19 +133,17 @@ void App::LogicThreadShutdownComplete() {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
assert(g_core); assert(g_core);
done_ = true;
// Flag our own event loop to exit (or tell the OS to do so for its own). // Flag our own event loop to exit (or tell the OS to do so for its own).
if (ManagesEventLoop()) { if (ManagesEventLoop()) {
event_loop()->Quit(); g_core->main_event_loop()->Quit();
} else { } else {
g_core->platform->QuitApp(); g_core->platform->QuitApp();
} }
} }
void App::RunEvents() { void App::RunEvents() {
// there's probably a better place for this... // There's probably a better place for this.
stress_test_->Update(); g_base->stress_test()->Update();
// Give platforms a chance to pump/handle their own events. // Give platforms a chance to pump/handle their own events.
// //
@ -216,22 +215,6 @@ void App::OnAppResume_() {
} }
} }
auto App::GetProductPrice(const std::string& product) -> std::string {
std::scoped_lock lock(product_prices_mutex_);
auto i = product_prices_.find(product);
if (i == product_prices_.end()) {
return "";
} else {
return i->second;
}
}
void App::SetProductPrice(const std::string& product,
const std::string& price) {
std::scoped_lock lock(product_prices_mutex_);
product_prices_[product] = price;
}
void App::PauseApp() { void App::PauseApp() {
assert(g_core); assert(g_core);
assert(g_core->InMainThread()); assert(g_core->InMainThread());
@ -309,85 +292,37 @@ void App::PrimeMainThreadEventPump() {
// Pump events manually until a screen gets created. // Pump events manually until a screen gets created.
// At that point we use frame-draws to drive our event loop. // At that point we use frame-draws to drive our event loop.
while (!g_base->graphics_server->initial_screen_created()) { while (!g_base->graphics_server->initial_screen_created()) {
event_loop()->RunSingleCycle(); g_core->main_event_loop()->RunSingleCycle();
core::CorePlatform::SleepMillisecs(1); core::CorePlatform::SleepMillisecs(1);
} }
} }
#pragma mark Push-Calls #pragma mark Push-Calls
// FIXME - move this call to Platform.
void App::PushShowOnlineScoreUICall(const std::string& show,
const std::string& game,
const std::string& game_version) {
event_loop()->PushCall([show, game, game_version] {
assert(g_core->InMainThread());
g_core->platform->ShowOnlineScoreUI(show, game, game_version);
});
}
void App::PushPurchaseAckCall(const std::string& purchase, void App::PushPurchaseAckCall(const std::string& purchase,
const std::string& order_id) { const std::string& order_id) {
event_loop()->PushCall([purchase, order_id] { g_core->main_event_loop()->PushCall([purchase, order_id] {
g_base->platform->PurchaseAck(purchase, order_id); g_base->platform->PurchaseAck(purchase, order_id);
}); });
} }
void App::PushPurchaseCall(const std::string& item) { void App::PushPurchaseCall(const std::string& item) {
event_loop()->PushCall([item] { g_core->main_event_loop()->PushCall([item] {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
g_base->platform->Purchase(item); g_base->platform->Purchase(item);
}); });
} }
void App::PushRestorePurchasesCall() { void App::PushRestorePurchasesCall() {
event_loop()->PushCall([] { g_core->main_event_loop()->PushCall([] {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
g_base->platform->RestorePurchases(); g_base->platform->RestorePurchases();
}); });
} }
void App::PushOpenURLCall(const std::string& url) {
event_loop()->PushCall([url] { g_base->platform->OpenURL(url); });
}
void App::PushSubmitScoreCall(const std::string& game,
const std::string& game_version, int64_t score) {
event_loop()->PushCall([game, game_version, score] {
g_core->platform->SubmitScore(game, game_version, score);
});
}
void App::PushAchievementReportCall(const std::string& achievement) {
event_loop()->PushCall(
[achievement] { g_core->platform->ReportAchievement(achievement); });
}
void App::PushStringEditCall(const std::string& name, const std::string& value,
int max_chars) {
event_loop()->PushCall([name, value, max_chars] {
static millisecs_t last_edit_time = 0;
millisecs_t t = g_core->GetAppTimeMillisecs();
// Ignore if too close together.
// (in case second request comes in before first takes effect).
if (t - last_edit_time < 1000) {
return;
}
last_edit_time = t;
assert(g_core->InMainThread());
g_core->platform->EditText(name, value, max_chars);
});
}
void App::PushSetStressTestingCall(bool enable, int player_count) {
event_loop()->PushCall([this, enable, player_count] {
stress_test_->Set(enable, player_count);
});
}
void App::PushResetAchievementsCall() { void App::PushResetAchievementsCall() {
event_loop()->PushCall([] { g_core->platform->ResetAchievements(); }); g_core->main_event_loop()->PushCall(
[] { g_core->platform->ResetAchievements(); });
} }
void App::OnMainThreadStartApp() { void App::OnMainThreadStartApp() {
@ -416,11 +351,4 @@ void App::OnMainThreadStartApp() {
} }
} }
void App::PushCursorUpdate(bool vis) {
event_loop()->PushCall([vis] {
assert(g_core && g_core->InMainThread());
g_core->platform->SetHardwareCursorVisible(vis);
});
}
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -3,21 +3,18 @@
#ifndef BALLISTICA_BASE_APP_APP_H_ #ifndef BALLISTICA_BASE_APP_APP_H_
#define BALLISTICA_BASE_APP_APP_H_ #define BALLISTICA_BASE_APP_APP_H_
#include <memory>
#include <mutex>
#include <string> #include <string>
#include <unordered_map>
#include "ballistica/base/base.h" #include "ballistica/base/base.h"
#include "ballistica/base/support/stress_test.h"
namespace ballistica::base { namespace ballistica::base {
/// Encapsulates high level app behavior for regular apps, vr apps, /// Defines app behavior for a particular paradigm (regular gui, vr,
/// headless apps, etc. /// headless) and/or top level api (SDL, UIKit, etc.).
class App { class App {
public: public:
explicit App(EventLoop* event_loop); App();
virtual ~App();
/// Should be run after the instance is created and assigned. Any setup /// Should be run after the instance is created and assigned. Any setup
/// that may trigger virtual methods or lookups via global should go here. /// that may trigger virtual methods or lookups via global should go here.
@ -29,12 +26,7 @@ class App {
/// in our main thread. /// in our main thread.
void DoLogicThreadApplyAppConfig(); void DoLogicThreadApplyAppConfig();
/// Return whether this class runs its own event loop. If true, /// Return whether this class runs its own event loop.
/// MonolithicMain() will continuously ask the app for events until the
/// app is quit, at which point MonolithicMain() returns. If false,
/// MonolithicMain returns immediately and it is assumed that the OS
/// handles the app lifecycle and pushes events to the app via
/// callbacks/etc.
auto ManagesEventLoop() const -> bool; auto ManagesEventLoop() const -> bool;
/// Called for non-event-loop apps to give them an opportunity to ensure /// Called for non-event-loop apps to give them an opportunity to ensure
@ -85,13 +77,6 @@ class App {
/// Called by the graphics-server when drawing completes for a frame. /// Called by the graphics-server when drawing completes for a frame.
virtual void DidFinishRenderingFrame(FrameDef* frame); virtual void DidFinishRenderingFrame(FrameDef* frame);
/// Return the price of an IAP product as a human-readable string, or an
/// empty string if not found. FIXME: move this to platform.
auto GetProductPrice(const std::string& product) -> std::string;
void SetProductPrice(const std::string& product, const std::string& price);
auto done() const -> bool { return done_; }
/// Whether we're running under ballisticakit_server.py /// Whether we're running under ballisticakit_server.py
/// (affects some app behavior). /// (affects some app behavior).
auto server_wrapper_managed() const -> bool { auto server_wrapper_managed() const -> bool {
@ -102,26 +87,13 @@ class App {
// Deferred calls that can be made from other threads. // Deferred calls that can be made from other threads.
void PushCursorUpdate(bool vis);
void PushShowOnlineScoreUICall(const std::string& show,
const std::string& game,
const std::string& game_version);
void PushSubmitScoreCall(const std::string& game,
const std::string& game_version, int64_t score);
void PushAchievementReportCall(const std::string& achievement);
void PushOpenURLCall(const std::string& url);
void PushStringEditCall(const std::string& name, const std::string& value,
int max_chars);
void PushSetStressTestingCall(bool enable, int player_count);
void PushPurchaseCall(const std::string& item); void PushPurchaseCall(const std::string& item);
void PushRestorePurchasesCall(); void PushRestorePurchasesCall();
void PushResetAchievementsCall(); void PushResetAchievementsCall();
void PushPurchaseAckCall(const std::string& purchase, void PushPurchaseAckCall(const std::string& purchase,
const std::string& order_id); const std::string& order_id);
auto event_loop() const -> EventLoop* { return event_loop_; }
/// Called by the logic thread when all shutdown-related tasks are done /// Called by the logic thread when all shutdown-related tasks are done.
/// and it is safe to exit the main event loop.
void LogicThreadShutdownComplete(); void LogicThreadShutdownComplete();
void LogicThreadOnAppRunning(); void LogicThreadOnAppRunning();
@ -131,16 +103,11 @@ class App {
void UpdatePauseResume_(); void UpdatePauseResume_();
void OnAppPause_(); void OnAppPause_();
void OnAppResume_(); void OnAppResume_();
EventLoop* event_loop_{};
bool done_{};
bool server_wrapper_managed_{}; bool server_wrapper_managed_{};
bool sys_paused_app_{}; bool sys_paused_app_{};
bool actually_paused_{}; bool actually_paused_{};
std::unique_ptr<StressTest> stress_test_;
millisecs_t last_resize_draw_event_time_{}; millisecs_t last_resize_draw_event_time_{};
millisecs_t last_app_resume_time_{}; millisecs_t last_app_resume_time_{};
std::unordered_map<std::string, std::string> product_prices_;
std::mutex product_prices_mutex_;
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -9,14 +9,14 @@ namespace ballistica::base {
// We could technically use the vanilla App class here since we're not // We could technically use the vanilla App class here since we're not
// changing anything. // changing anything.
AppHeadless::AppHeadless(EventLoop* event_loop) : App(event_loop) { AppHeadless::AppHeadless() {
// Handle a few misc things like stress-test updates. // Handle a few misc things like stress-test updates.
// (SDL builds set up a similar timer so we need to also). // (SDL builds set up a similar timer so we need to also).
// This can probably go away at some point. // This can probably go away at some point.
this->event_loop()->NewTimer(10, true, NewLambdaRunnable([this] { g_core->main_event_loop()->NewTimer(10, true, NewLambdaRunnable([this] {
assert(g_base->app); assert(g_base->app);
g_base->app->RunEvents(); g_base->app->RunEvents();
})); }));
} }
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -11,7 +11,7 @@ namespace ballistica::base {
class AppHeadless : public App { class AppHeadless : public App {
public: public:
explicit AppHeadless(EventLoop* event_loop); AppHeadless();
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -30,15 +30,15 @@ void AppSDL::HandleSDLEvent(const SDL_Event& event) {
case SDL_JOYBALLMOTION: case SDL_JOYBALLMOTION:
case SDL_JOYHATMOTION: { case SDL_JOYHATMOTION: {
// It seems that joystick connection/disconnection callbacks can fire // It seems that joystick connection/disconnection callbacks can fire
// while there are still events for that joystick in the queue. // while there are still events for that joystick in the queue. So
// So take care to ignore events for no-longer-existing joysticks. // take care to ignore events for no-longer-existing joysticks.
assert(event.jaxis.which == event.jbutton.which assert(event.jaxis.which == event.jbutton.which
&& event.jaxis.which == event.jhat.which); && event.jaxis.which == event.jhat.which);
if (static_cast<size_t>(event.jbutton.which) >= sdl_joysticks_.size() if (static_cast<size_t>(event.jbutton.which) >= sdl_joysticks_.size()
|| sdl_joysticks_[event.jbutton.which] == nullptr) { || sdl_joysticks_[event.jbutton.which] == nullptr) {
return; return;
} }
JoystickInput* js = GetSDLJoyStickInput(&event); JoystickInput* js = GetSDLJoystickInput_(&event);
if (js) { if (js) {
if (g_base) { if (g_base) {
g_base->input->PushJoystickEvent(event, js); g_base->input->PushJoystickEvent(event, js);
@ -61,6 +61,7 @@ void AppSDL::HandleSDLEvent(const SDL_Event& event) {
} }
break; break;
} }
case SDL_MOUSEBUTTONUP: { case SDL_MOUSEBUTTONUP: {
const SDL_MouseButtonEvent* e = &event.button; const SDL_MouseButtonEvent* e = &event.button;
@ -72,6 +73,7 @@ void AppSDL::HandleSDLEvent(const SDL_Event& event) {
} }
break; break;
} }
case SDL_MOUSEMOTION: { case SDL_MOUSEMOTION: {
const SDL_MouseMotionEvent* e = &event.motion; const SDL_MouseMotionEvent* e = &event.motion;
@ -83,12 +85,14 @@ void AppSDL::HandleSDLEvent(const SDL_Event& event) {
} }
break; break;
} }
case SDL_KEYDOWN: { case SDL_KEYDOWN: {
if (g_base) { if (g_base) {
g_base->input->PushKeyPressEvent(event.key.keysym); g_base->input->PushKeyPressEvent(event.key.keysym);
} }
break; break;
} }
case SDL_KEYUP: { case SDL_KEYUP: {
if (g_base) { if (g_base) {
g_base->input->PushKeyReleaseEvent(event.key.keysym); g_base->input->PushKeyReleaseEvent(event.key.keysym);
@ -106,8 +110,7 @@ void AppSDL::HandleSDLEvent(const SDL_Event& event) {
int scroll_speed; int scroll_speed;
if (g_buildconfig.ostype_android()) { if (g_buildconfig.ostype_android()) {
scroll_speed = 1; scroll_speed = 1;
} else if (g_buildconfig } else if (g_buildconfig.ostype_macos()) {
.ostype_macos()) { // NOLINT(bugprone-branch-clone)
scroll_speed = 500; scroll_speed = 500;
} else { } else {
scroll_speed = 500; scroll_speed = 500;
@ -144,14 +147,16 @@ void AppSDL::HandleSDLEvent(const SDL_Event& event) {
// Is there a reason we need to ignore these on ios? // Is there a reason we need to ignore these on ios?
// do they even happen there? // do they even happen there?
// UPDATE: I think the even types are just not defined on our old iOS SDL. //
// UPDATE: I think the even types are just not defined on our old iOS
// SDL.
#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS && BA_ENABLE_SDL_JOYSTICKS #if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS && BA_ENABLE_SDL_JOYSTICKS
case SDL_JOYDEVICEREMOVED: case SDL_JOYDEVICEREMOVED:
// In this case we're passed the instance-id of the joystick. // In this case we're passed the instance-id of the joystick.
SDLJoystickDisconnected(event.jdevice.which); SDLJoystickDisconnected_(event.jdevice.which);
break; break;
case SDL_JOYDEVICEADDED: case SDL_JOYDEVICEADDED:
SDLJoystickConnected(event.jdevice.which); SDLJoystickConnected_(event.jdevice.which);
break; break;
#endif #endif
@ -161,8 +166,9 @@ void AppSDL::HandleSDLEvent(const SDL_Event& event) {
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD #if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD
case SDL_FULLSCREENSWITCH: case SDL_FULLSCREENSWITCH:
// Our custom hacked-up SDL informs *us* when our window enters or exits // Our custom hacked-up SDL informs *us* when our window enters or
// fullscreen. Let's commit this to our config so that we're in sync.. // exits fullscreen. Let's commit this to our config so that we're in
// sync..
g_base->python->objs().PushCall( g_base->python->objs().PushCall(
event.user.code ? BasePython::ObjID::kSetConfigFullscreenOnCall event.user.code ? BasePython::ObjID::kSetConfigFullscreenOnCall
: BasePython::ObjID::kSetConfigFullscreenOffCall); : BasePython::ObjID::kSetConfigFullscreenOffCall);
@ -243,8 +249,8 @@ auto FilterSDLEvent(const SDL_Event* event) -> int {
} }
return false; // We handled it; sdl doesn't need to keep it. return false; // We handled it; sdl doesn't need to keep it.
} else { } else {
// Otherwise just let SDL post it to the normal queue.. we process this // Otherwise just let SDL post it to the normal queue.. we process
// every now and then to pick these up. // this every now and then to pick these up.
return true; // sdl should keep this. return true; // sdl should keep this.
} }
} catch (const std::exception& e) { } catch (const std::exception& e) {
@ -266,8 +272,8 @@ void AppSDL::InitSDL() {
assert(g_core); assert(g_core);
if (g_buildconfig.ostype_macos()) { if (g_buildconfig.ostype_macos()) {
// We don't want sdl to translate command/option clicks to different mouse // We don't want sdl to translate command/option clicks to different
// buttons dernit. // mouse buttons dernit.
g_core->platform->SetEnv("SDL_HAS3BUTTONMOUSE", "1"); g_core->platform->SetEnv("SDL_HAS3BUTTONMOUSE", "1");
} }
@ -287,8 +293,8 @@ void AppSDL::InitSDL() {
// KILL THIS ONCE MAC SDL1.2 BUILD IS DEAD. // KILL THIS ONCE MAC SDL1.2 BUILD IS DEAD.
// Register our hotplug callbacks in our funky custom mac build. // Register our hotplug callbacks in our funky custom mac build.
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD #if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD
SDL_JoystickSetHotPlugCallbacks(AppSDL::SDLJoystickConnected, SDL_JoystickSetHotPlugCallbacks(AppSDL::SDLJoystickConnected_,
AppSDL::SDLJoystickDisconnected); AppSDL::SDLJoystickDisconnected_);
#endif #endif
} }
} }
@ -297,7 +303,7 @@ void AppSDL::InitSDL() {
// we don't want it. // we don't want it.
sdl_flags |= SDL_INIT_NOPARACHUTE; sdl_flags |= SDL_INIT_NOPARACHUTE;
// We want xinput on windows. // We may or may not want xinput on windows.
if (g_buildconfig.ostype_windows()) { if (g_buildconfig.ostype_windows()) {
if (!g_core->platform->GetLowLevelConfigValue("enablexinput", 1)) { if (!g_core->platform->GetLowLevelConfigValue("enablexinput", 1)) {
SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "0"); SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "0");
@ -322,13 +328,14 @@ void AppSDL::InitSDL() {
#endif #endif
} }
AppSDL::AppSDL(EventLoop* event_loop) : App(event_loop) { AppSDL::AppSDL() {
InitSDL(); InitSDL();
// If we're not running our own even loop, we set up a filter to intercept // If we're not running our own even loop, we set up a filter to intercept
// SDL events the moment they're generated and we process them immediately. // SDL events the moment they're generated and we process them
// This way we don't have to poll for events and can be purely callback-based, // immediately. This way we don't have to poll for events and can be
// which fits in nicely with most modern event models. // purely callback-based, which fits in nicely with most modern event
// models.
if (!ManagesEventLoop()) { if (!ManagesEventLoop()) {
#if BA_SDL2_BUILD #if BA_SDL2_BUILD
SDL_SetEventFilter(FilterSDL2Event, nullptr); SDL_SetEventFilter(FilterSDL2Event, nullptr);
@ -338,16 +345,36 @@ AppSDL::AppSDL(EventLoop* event_loop) : App(event_loop) {
} else { } else {
// Otherwise we do the standard old SDL polling stuff. // Otherwise we do the standard old SDL polling stuff.
// Set up a timer to chew through events every now and then. Polling isn't // Set up a timer to chew through events every now and then. Polling
// super elegant, but is necessary in SDL's case. (SDLWaitEvent() itself is // isn't super elegant, but is necessary in SDL's case. (SDLWaitEvent()
// pretty much a loop with SDL_PollEvents() followed by SDL_Delay(10) until // itself is pretty much a loop with SDL_PollEvents() followed by
// something is returned; In spirit, we're pretty much doing that same // SDL_Delay(10) until something is returned; In spirit, we're pretty
// thing, except that we're free to handle other matters concurrently // much doing that same thing, except that we're free to handle other
// instead of being locked in a delay call. // matters concurrently instead of being locked in a delay call.
this->event_loop()->NewTimer(10, true, NewLambdaRunnable([this] { g_core->main_event_loop()->NewTimer(10, true, NewLambdaRunnable([this] {
assert(g_base->app); assert(g_base->app);
g_base->app->RunEvents(); g_base->app->RunEvents();
})); }));
}
}
void AppSDL::OnMainThreadStartApp() {
App::OnMainThreadStartApp();
if (!g_core->HeadlessMode() && g_buildconfig.enable_sdl_joysticks()) {
// Add initial sdl joysticks. any added/removed after this will be
// handled via events. (it seems (on mac at least) even the initial ones
// are handled via events, so lets make sure we handle redundant
// joystick connections gracefully.
if (explicit_bool(true)) {
int joystick_count = SDL_NumJoysticks();
for (int i = 0; i < joystick_count; i++) {
AppSDL::SDLJoystickConnected_(i);
}
// We want events from joysticks.
SDL_JoystickEventState(SDL_ENABLE);
}
} }
} }
@ -356,17 +383,17 @@ void AppSDL::RunEvents() {
// Now run all pending SDL events until we run out or we're told to quit. // Now run all pending SDL events until we run out or we're told to quit.
SDL_Event event; SDL_Event event;
while (SDL_PollEvent(&event) && (!done())) { while (SDL_PollEvent(&event) && (!g_core->main_event_loop()->done())) {
HandleSDLEvent(event); HandleSDLEvent(event);
} }
} }
void AppSDL::DidFinishRenderingFrame(FrameDef* frame) { void AppSDL::DidFinishRenderingFrame(FrameDef* frame) {
App::DidFinishRenderingFrame(frame); App::DidFinishRenderingFrame(frame);
SwapBuffers(); SwapBuffers_();
} }
void AppSDL::DoSwap() { void AppSDL::DoSwap_() {
assert(g_base->InGraphicsThread()); assert(g_base->InGraphicsThread());
if (g_buildconfig.debug_build()) { if (g_buildconfig.debug_build()) {
@ -390,11 +417,11 @@ void AppSDL::DoSwap() {
if (last_swap_time_ != 0) { if (last_swap_time_ != 0) {
millisecs_t diff2 = cur_time - last_swap_time_; millisecs_t diff2 = cur_time - last_swap_time_;
if (auto_vsync_) { if (auto_vsync_) {
UpdateAutoVSync(static_cast<int>(diff2)); UpdateAutoVSync_(static_cast<int>(diff2));
} }
// If we drop to a super-crappy FPS lets take some countermeasures // If we drop to a super-crappy FPS lets take some countermeasures such
// such as telling BG-dynamics to kill off some stuff. // as telling BG-dynamics to kill off some stuff.
if (diff2 >= 1000 / 20) { if (diff2 >= 1000 / 20) {
too_slow_frame_count_++; too_slow_frame_count_++;
} else { } else {
@ -405,23 +432,22 @@ void AppSDL::DoSwap() {
if (too_slow_frame_count_ > 10) { if (too_slow_frame_count_ > 10) {
too_slow_frame_count_ = 0; too_slow_frame_count_ = 0;
// A common cause of slowness is excessive smoke and bg stuff; // A common cause of slowness is excessive smoke and bg stuff; lets
// lets tell the bg dynamics thread to tone it down. // tell the bg dynamics thread to tone it down.
g_base->bg_dynamics->TooSlow(); g_base->bg_dynamics->TooSlow();
} }
} }
last_swap_time_ = cur_time; last_swap_time_ = cur_time;
} }
void AppSDL::SwapBuffers() { void AppSDL::SwapBuffers_() {
swap_start_time_ = g_core->GetAppTimeMillisecs(); swap_start_time_ = g_core->GetAppTimeMillisecs();
assert(event_loop()->ThreadIsCurrent()); assert(g_core->main_event_loop()->ThreadIsCurrent());
DoSwap(); DoSwap_();
// FIXME: Move this somewhere reasonable. Not here. // FIXME: Move this somewhere reasonable. Not here. On mac/ios we wanna
// On mac/ios we wanna delay our game-center login until we've drawn a few // delay our game-center login until we've drawn a few frames, so lets do
// frames, so lets do that here. // that here. ...hmm; why is that? I don't remember. Should revisit.
// ...hmm; why is that? I don't remember. Should revisit.
if (g_buildconfig.use_game_center()) { if (g_buildconfig.use_game_center()) {
static int f_count = 0; static int f_count = 0;
f_count++; f_count++;
@ -431,18 +457,18 @@ void AppSDL::SwapBuffers() {
} }
} }
void AppSDL::UpdateAutoVSync(int diff) { void AppSDL::UpdateAutoVSync_(int diff) {
assert(auto_vsync_); assert(auto_vsync_);
// If we're currently vsyncing, watch for slow frames. // If we're currently vsyncing, watch for slow frames.
if (vsync_enabled_) { if (vsync_enabled_) {
bool should_disable{}; bool should_disable{};
// Note (March 2023): Currently mac opengl vsync seems broken on recent OSs. // Note (March 2023): Currently mac opengl vsync seems broken on recent
// See discussions: https://github.com/libsdl-org/SDL/issues/4918 // OSs. See discussions: https://github.com/libsdl-org/SDL/issues/4918
// Since Mac compositor generally avoids tearing anyway, just gonna // Since Mac compositor generally avoids tearing anyway, just gonna have
// have 'auto' mode disable vsync for now. Explicit enable is still // 'auto' mode disable vsync for now. Explicit enable is still available
// available for odd cases where it still may be beneficial. // for odd cases where it still may be beneficial.
if (g_buildconfig.ostype_macos()) { if (g_buildconfig.ostype_macos()) {
should_disable = true; should_disable = true;
} else { } else {
@ -454,12 +480,12 @@ void AppSDL::UpdateAutoVSync(int diff) {
smoothing * average_vsync_fps_ + (1.0f - smoothing) * this_fps; smoothing * average_vsync_fps_ + (1.0f - smoothing) * this_fps;
} }
// FIXME: should not be assuming a 60fps framerate these days. // FIXME: should not be assuming a 60fps framerate these days. If
// If framerate drops significantly below 60, flip vsync off to get a // framerate drops significantly below 60, flip vsync off to get a
// better framerate (but *only* if we're pretty sure we can hit 60 with // better framerate (but *only* if we're pretty sure we can hit 60
// it on; otherwise if we're on a 30hz monitor we'll get into a cycle of // with it on; otherwise if we're on a 30hz monitor we'll get into a
// flipping it off and on repeatedly since we slow down a lot with it on // cycle of flipping it off and on repeatedly since we slow down a lot
// and then speed up a lot with it off). // with it on and then speed up a lot with it off).
if (diff >= 1000 / 40 && average_vsync_fps_ > 55.0f) { if (diff >= 1000 / 40 && average_vsync_fps_ > 55.0f) {
vsync_bad_frame_count_++; vsync_bad_frame_count_++;
} else { } else {
@ -479,8 +505,8 @@ void AppSDL::UpdateAutoVSync(int diff) {
if (g_buildconfig.ostype_macos()) { if (g_buildconfig.ostype_macos()) {
should_enable = false; should_enable = false;
} else { } else {
// Vsync is currently off.. watch for framerate staying consistently high // Vsync is currently off; watch for framerate staying consistently
// and then turn it on again. // high and then turn it on again.
if (diff <= 1000 / 50) { if (diff <= 1000 / 50) {
vsync_good_frame_count_++; vsync_good_frame_count_++;
} else { } else {
@ -510,34 +536,14 @@ void AppSDL::SetAutoVSync(bool enable) {
} }
} }
void AppSDL::OnMainThreadStartApp() { void AppSDL::SDLJoystickConnected_(int device_index) {
App::OnMainThreadStartApp();
if (!g_core->HeadlessMode() && g_buildconfig.enable_sdl_joysticks()) {
// Add initial sdl joysticks. any added/removed after this will be handled
// via events. (it seems (on mac at least) even the initial ones are handled
// via events, so lets make sure we handle redundant joystick connections
// gracefully.
if (explicit_bool(true)) {
int joystick_count = SDL_NumJoysticks();
for (int i = 0; i < joystick_count; i++) {
AppSDL::SDLJoystickConnected(i);
}
// We want events from joysticks.
SDL_JoystickEventState(SDL_ENABLE);
}
}
}
void AppSDL::SDLJoystickConnected(int device_index) {
assert(g_core && g_core->InMainThread()); assert(g_core && g_core->InMainThread());
// We add all existing inputs when bootstrapping is complete; we should // We add all existing inputs when bootstrapping is complete; we should
// never be getting these before that happens. // never be getting these before that happens.
if (!g_base) { if (!g_base) {
Log(LogLevel::kError, Log(LogLevel::kError,
"Unexpected SDLJoystickConnected early in boot sequence."); "Unexpected SDLJoystickConnected_ early in boot sequence.");
return; return;
} }
@ -549,24 +555,24 @@ void AppSDL::SDLJoystickConnected(int device_index) {
auto* j = Object::NewDeferred<JoystickInput>(device_index); auto* j = Object::NewDeferred<JoystickInput>(device_index);
if (g_buildconfig.sdl2_build() && g_buildconfig.enable_sdl_joysticks()) { if (g_buildconfig.sdl2_build() && g_buildconfig.enable_sdl_joysticks()) {
int instance_id = SDL_JoystickInstanceID(j->sdl_joystick()); int instance_id = SDL_JoystickInstanceID(j->sdl_joystick());
get()->AddSDLInputDevice(j, instance_id); Get()->AddSDLInputDevice_(j, instance_id);
} else { } else {
get()->AddSDLInputDevice(j, device_index); Get()->AddSDLInputDevice_(j, device_index);
} }
} }
} }
void AppSDL::SDLJoystickDisconnected(int index) { void AppSDL::SDLJoystickDisconnected_(int index) {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
assert(index >= 0); assert(index >= 0);
get()->RemoveSDLInputDevice(index); Get()->RemoveSDLInputDevice_(index);
} }
void AppSDL::SetInitialScreenDimensions(const Vector2f& dimensions) { void AppSDL::SetInitialScreenDimensions(const Vector2f& dimensions) {
screen_dimensions_ = dimensions; screen_dimensions_ = dimensions;
} }
void AppSDL::AddSDLInputDevice(JoystickInput* input, int index) { void AppSDL::AddSDLInputDevice_(JoystickInput* input, int index) {
assert(g_base && g_base->input != nullptr); assert(g_base && g_base->input != nullptr);
assert(input != nullptr); assert(input != nullptr);
assert(g_core->InMainThread()); assert(g_core->InMainThread());
@ -581,10 +587,10 @@ void AppSDL::AddSDLInputDevice(JoystickInput* input, int index) {
g_base->input->PushAddInputDeviceCall(input, true); g_base->input->PushAddInputDeviceCall(input, true);
} }
void AppSDL::RemoveSDLInputDevice(int index) { void AppSDL::RemoveSDLInputDevice_(int index) {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
assert(index >= 0); assert(index >= 0);
JoystickInput* j = GetSDLJoyStickInput(index); JoystickInput* j = GetSDLJoystickInput_(index);
assert(j); assert(j);
if (static_cast_check_fit<int>(sdl_joysticks_.size()) > index) { if (static_cast_check_fit<int>(sdl_joysticks_.size()) > index) {
sdl_joysticks_[index] = nullptr; sdl_joysticks_[index] = nullptr;
@ -596,7 +602,7 @@ void AppSDL::RemoveSDLInputDevice(int index) {
g_base->input->PushRemoveInputDeviceCall(j, true); g_base->input->PushRemoveInputDeviceCall(j, true);
} }
auto AppSDL::GetSDLJoyStickInput(const SDL_Event* e) const -> JoystickInput* { auto AppSDL::GetSDLJoystickInput_(const SDL_Event* e) const -> JoystickInput* {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
int joy_id; int joy_id;
@ -618,10 +624,10 @@ auto AppSDL::GetSDLJoyStickInput(const SDL_Event* e) const -> JoystickInput* {
default: default:
return nullptr; return nullptr;
} }
return GetSDLJoyStickInput(joy_id); return GetSDLJoystickInput_(joy_id);
} }
auto AppSDL::GetSDLJoyStickInput(int sdl_joystick_id) const -> JoystickInput* { auto AppSDL::GetSDLJoystickInput_(int sdl_joystick_id) const -> JoystickInput* {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
for (auto sdl_joystick : sdl_joysticks_) { for (auto sdl_joystick : sdl_joysticks_) {
if ((sdl_joystick != nullptr) && (*sdl_joystick).sdl_joystick_id() >= 0 if ((sdl_joystick != nullptr) && (*sdl_joystick).sdl_joystick_id() >= 0

View File

@ -15,17 +15,15 @@ namespace ballistica::base {
class AppSDL : public App { class AppSDL : public App {
public: public:
static void InitSDL(); static void InitSDL();
explicit AppSDL(EventLoop* event_loop); AppSDL();
void HandleSDLEvent(const SDL_Event& event); void HandleSDLEvent(const SDL_Event& event);
void RunEvents() override; void RunEvents() override;
void DidFinishRenderingFrame(FrameDef* frame) override; void DidFinishRenderingFrame(FrameDef* frame) override;
void SetAutoVSync(bool enable); void SetAutoVSync(bool enable);
static void SDLJoystickConnected(int index);
static void SDLJoystickDisconnected(int index);
void OnMainThreadStartApp() override; void OnMainThreadStartApp() override;
/// Return g_base->app as a AppSDL. (assumes it actually is one). /// Return g_base->app as an AppSDL. (assumes it actually is one).
static AppSDL* get() { static AppSDL* Get() {
assert(g_base && g_base->app != nullptr); assert(g_base && g_base->app != nullptr);
assert(dynamic_cast<AppSDL*>(g_base->app) assert(dynamic_cast<AppSDL*>(g_base->app)
== static_cast<AppSDL*>(g_base->app)); == static_cast<AppSDL*>(g_base->app));
@ -34,17 +32,17 @@ class AppSDL : public App {
void SetInitialScreenDimensions(const Vector2f& dimensions); void SetInitialScreenDimensions(const Vector2f& dimensions);
private: private:
// Given an sdl joystick ID, returns our ballistica input for it. // Given an sdl joystick ID, returns our Ballistica input for it.
auto GetSDLJoyStickInput(int sdl_joystick_id) const -> JoystickInput*; auto GetSDLJoystickInput_(int sdl_joystick_id) const -> JoystickInput*;
// The same but using sdl events. // The same but using sdl events.
auto GetSDLJoyStickInput(const SDL_Event* e) const -> JoystickInput*; auto GetSDLJoystickInput_(const SDL_Event* e) const -> JoystickInput*;
static void SDLJoystickConnected_(int index);
void DoSwap(); static void SDLJoystickDisconnected_(int index);
void SwapBuffers(); void DoSwap_();
void UpdateAutoVSync(int diff); void SwapBuffers_();
void AddSDLInputDevice(JoystickInput* input, int index); void UpdateAutoVSync_(int diff);
void RemoveSDLInputDevice(int index); void AddSDLInputDevice_(JoystickInput* input, int index);
void RemoveSDLInputDevice_(int index);
millisecs_t last_swap_time_{}; millisecs_t last_swap_time_{};
millisecs_t swap_start_time_{}; millisecs_t swap_start_time_{};
int too_slow_frame_count_{}; int too_slow_frame_count_{};
@ -54,8 +52,7 @@ class AppSDL : public App {
int vsync_good_frame_count_{}; int vsync_good_frame_count_{};
int vsync_bad_frame_count_{}; int vsync_bad_frame_count_{};
std::vector<JoystickInput*> sdl_joysticks_; std::vector<JoystickInput*> sdl_joysticks_;
/// This is in points, not pixels.
/// This is in points; not pixels.
Vector2f screen_dimensions_{1.0f, 1.0f}; Vector2f screen_dimensions_{1.0f, 1.0f};
}; };

View File

@ -11,10 +11,10 @@
namespace ballistica::base { namespace ballistica::base {
AppVR::AppVR(EventLoop* event_loop) : App(event_loop) {} AppVR::AppVR() {}
void AppVR::PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state) { void AppVR::PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state) {
event_loop()->PushCall([this, state] { g_core->main_event_loop()->PushCall([this, state] {
// Convert this to a full hands state, adding in some simple elbow // Convert this to a full hands state, adding in some simple elbow
// positioning of our own and left/right. // positioning of our own and left/right.
VRHandsState s; VRHandsState s;

View File

@ -27,7 +27,7 @@ class AppVR : public App {
return static_cast<AppVR*>(g_base->app); return static_cast<AppVR*>(g_base->app);
} }
explicit AppVR(EventLoop* event_loop); AppVR();
void PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state); void PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state);
void VRSetDrawDimensions(int w, int h); void VRSetDrawDimensions(int w, int h);
void VRPreDraw(); void VRPreDraw();

View File

@ -25,6 +25,7 @@
#include "ballistica/base/support/huffman.h" #include "ballistica/base/support/huffman.h"
#include "ballistica/base/support/plus_soft.h" #include "ballistica/base/support/plus_soft.h"
#include "ballistica/base/support/stdio_console.h" #include "ballistica/base/support/stdio_console.h"
#include "ballistica/base/support/stress_test.h"
#include "ballistica/base/ui/console.h" #include "ballistica/base/ui/console.h"
#include "ballistica/base/ui/ui.h" #include "ballistica/base/ui/ui.h"
#include "ballistica/core/platform/core_platform.h" #include "ballistica/core/platform/core_platform.h"
@ -47,6 +48,7 @@ BaseFeatureSet::BaseFeatureSet()
utils{new Utils()}, utils{new Utils()},
logic{new Logic()}, logic{new Logic()},
huffman{new Huffman()}, huffman{new Huffman()},
stress_test_{new StressTest()},
ui{new UI()}, ui{new UI()},
networking{new Networking()}, networking{new Networking()},
app{BasePlatform::CreateApp()}, app{BasePlatform::CreateApp()},
@ -174,7 +176,7 @@ void BaseFeatureSet::StartApp() {
g_core->LifecycleLog("start-app begin (main thread)"); g_core->LifecycleLog("start-app begin (main thread)");
LogVersionInfo(); LogVersionInfo_();
// The logic thread (or maybe other things) need to run Python as // The logic thread (or maybe other things) need to run Python as
// we're bringing them up, so let it go for the duration of this call. // we're bringing them up, so let it go for the duration of this call.
@ -222,7 +224,7 @@ void BaseFeatureSet::StartApp() {
g_core->LifecycleLog("start-app end (main thread)"); g_core->LifecycleLog("start-app end (main thread)");
} }
void BaseFeatureSet::LogVersionInfo() { void BaseFeatureSet::LogVersionInfo_() {
char buffer[256]; char buffer[256];
if (g_buildconfig.headless_build()) { if (g_buildconfig.headless_build()) {
snprintf(buffer, sizeof(buffer), "BallisticaKit Headless %s build %d.", snprintf(buffer, sizeof(buffer), "BallisticaKit Headless %s build %d.",
@ -635,24 +637,24 @@ std::string BaseFeatureSet::DoGetContextBaseString() {
} }
void BaseFeatureSet::DoPrintContextAuto() { void BaseFeatureSet::DoPrintContextAuto() {
if (!InLogicThread()) { if (!InLogicThread()) {
PrintContextNonLogicThread(); PrintContextNonLogicThread_();
} else if (const char* label = Python::ScopedCallLabel::current_label()) { } else if (const char* label = Python::ScopedCallLabel::current_label()) {
PrintContextForCallableLabel(label); PrintContextForCallableLabel_(label);
} else if (PythonCommand* cmd = PythonCommand::current_command()) { } else if (PythonCommand* cmd = PythonCommand::current_command()) {
cmd->PrintContext(); cmd->PrintContext();
} else if (PythonContextCall* call = PythonContextCall::current_call()) { } else if (PythonContextCall* call = PythonContextCall::current_call()) {
call->PrintContext(); call->PrintContext();
} else { } else {
PrintContextUnavailable(); PrintContextUnavailable_();
} }
} }
void BaseFeatureSet::PrintContextNonLogicThread() { void BaseFeatureSet::PrintContextNonLogicThread_() {
std::string s = std::string( std::string s = std::string(
" root call: <not in logic thread; context_ref unavailable>"); " root call: <not in logic thread; context_ref unavailable>");
PySys_WriteStderr("%s\n", s.c_str()); PySys_WriteStderr("%s\n", s.c_str());
} }
void BaseFeatureSet::PrintContextForCallableLabel(const char* label) { void BaseFeatureSet::PrintContextForCallableLabel_(const char* label) {
assert(InLogicThread()); assert(InLogicThread());
assert(label); assert(label);
std::string s = std::string(" root call: ") + label + "\n"; std::string s = std::string(" root call: ") + label + "\n";
@ -660,7 +662,7 @@ void BaseFeatureSet::PrintContextForCallableLabel(const char* label) {
PySys_WriteStderr("%s\n", s.c_str()); PySys_WriteStderr("%s\n", s.c_str());
} }
void BaseFeatureSet::PrintContextUnavailable() { void BaseFeatureSet::PrintContextUnavailable_() {
// (no logic-thread-check here; can be called early or from other threads) // (no logic-thread-check here; can be called early or from other threads)
std::string s = std::string(" root call: <unavailable>\n"); std::string s = std::string(" root call: <unavailable>\n");
s += Python::GetContextBaseString(); s += Python::GetContextBaseString();

View File

@ -719,6 +719,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
auto* console() const { return console_; } auto* console() const { return console_; }
auto* app_mode() const { return app_mode_; } auto* app_mode() const { return app_mode_; }
auto* stress_test() const { return stress_test_; }
void set_app_mode(AppMode* mode); void set_app_mode(AppMode* mode);
// Non-const bits (fixme: clean up access to these). // Non-const bits (fixme: clean up access to these).
@ -726,16 +727,17 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
private: private:
BaseFeatureSet(); BaseFeatureSet();
void LogVersionInfo(); void LogVersionInfo_();
void PrintContextNonLogicThread(); void PrintContextNonLogicThread_();
void PrintContextForCallableLabel(const char* label); void PrintContextForCallableLabel_(const char* label);
void PrintContextUnavailable(); void PrintContextUnavailable_();
AppMode* app_mode_; AppMode* app_mode_;
Console* console_{}; Console* console_{};
PlusSoftInterface* plus_soft_{}; PlusSoftInterface* plus_soft_{};
ClassicSoftInterface* classic_soft_{}; ClassicSoftInterface* classic_soft_{};
UIV1SoftInterface* ui_v1_soft_{}; UIV1SoftInterface* ui_v1_soft_{};
StressTest* stress_test_;
std::string console_startup_messages_; std::string console_startup_messages_;
bool tried_importing_plus_{}; bool tried_importing_plus_{};

View File

@ -149,7 +149,7 @@ GLContext::GLContext(int target_res_x, int target_res_y, bool fullscreen)
// devices. // devices.
int win_size_x, win_size_y; int win_size_x, win_size_y;
SDL_GetWindowSize(sdl_window_, &win_size_x, &win_size_y); SDL_GetWindowSize(sdl_window_, &win_size_x, &win_size_y);
AppSDL::get()->SetInitialScreenDimensions(Vector2f( AppSDL::Get()->SetInitialScreenDimensions(Vector2f(
static_cast<float>(win_size_x), static_cast<float>(win_size_y))); static_cast<float>(win_size_x), static_cast<float>(win_size_y)));
#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID #if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
res_x_ = win_size_x; res_x_ = win_size_x;
@ -188,7 +188,7 @@ GLContext::GLContext(int target_res_x, int target_res_y, bool fullscreen)
} }
res_x_ = surface_->w; res_x_ = surface_->w;
res_y_ = surface_->h; res_y_ = surface_->h;
AppSDL::get()->SetInitialScreenDimensions(Vector2f(res_x_, res_y_)); AppSDL::Get()->SetInitialScreenDimensions(Vector2f(res_x_, res_y_));
SDL_WM_SetCaption("BallisticaKit", "BallisticaKit"); SDL_WM_SetCaption("BallisticaKit", "BallisticaKit");
#elif BA_OSTYPE_ANDROID #elif BA_OSTYPE_ANDROID
// On Android the Java layer creates a GL setup before even calling us. // On Android the Java layer creates a GL setup before even calling us.

View File

@ -1476,7 +1476,10 @@ void Graphics::DrawCursor(RenderPass* pass, millisecs_t real_time) {
|| real_time - last_cursor_visibility_event_time_ > 2000) { || real_time - last_cursor_visibility_event_time_ > 2000) {
hardware_cursor_visible_ = new_cursor_visibility; hardware_cursor_visible_ = new_cursor_visibility;
last_cursor_visibility_event_time_ = real_time; last_cursor_visibility_event_time_ = real_time;
g_base->app->PushCursorUpdate(hardware_cursor_visible_); g_core->main_event_loop()->PushCall([this] {
assert(g_core && g_core->InMainThread());
g_core->platform->SetHardwareCursorVisible(hardware_cursor_visible_);
});
} }
} else { } else {
// Draw software cursor. // Draw software cursor.

View File

@ -307,7 +307,7 @@ JoystickInput::~JoystickInput() {
#if BA_ENABLE_SDL_JOYSTICKS #if BA_ENABLE_SDL_JOYSTICKS
assert(g_base->app); assert(g_base->app);
auto joystick = sdl_joystick_; auto joystick = sdl_joystick_;
g_base->app->event_loop()->PushCall( g_core->main_event_loop()->PushCall(
[joystick] { SDL_JoystickClose(joystick); }); [joystick] { SDL_JoystickClose(joystick); });
sdl_joystick_ = nullptr; sdl_joystick_ = nullptr;
#else #else

View File

@ -137,7 +137,7 @@ void Logic::OnAppShutdown() {
// FIXME: Should add a mechanism where we give the above subsystems // FIXME: Should add a mechanism where we give the above subsystems
// a short bit of time to complete shutdown if they need it. // a short bit of time to complete shutdown if they need it.
// For now just completing instantly. // For now just completing instantly.
g_base->app->event_loop()->PushCall( g_core->main_event_loop()->PushCall(
[] { g_base->app->LogicThreadShutdownComplete(); }); [] { g_base->app->LogicThreadShutdownComplete(); });
} }

View File

@ -20,7 +20,7 @@ void Networking::DoApplyAppConfig() {
// Grab network settings from config and kick them over to the main // Grab network settings from config and kick them over to the main
// thread to be applied. // thread to be applied.
int port = g_base->app_config->Resolve(AppConfig::IntID::kPort); int port = g_base->app_config->Resolve(AppConfig::IntID::kPort);
g_base->app->event_loop()->PushCall([port] { g_core->main_event_loop()->PushCall([port] {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
g_base->network_reader->SetPort(port); g_base->network_reader->SetPort(port);
}); });

View File

@ -119,20 +119,20 @@ auto BasePlatform::CreateApp() -> App* {
App* app{}; App* app{};
#if BA_HEADLESS_BUILD #if BA_HEADLESS_BUILD
app = new AppHeadless(g_core->main_event_loop()); app = new AppHeadless();
#elif BA_RIFT_BUILD #elif BA_RIFT_BUILD
// Rift build can spin up in either VR or regular mode. // Rift build can spin up in either VR or regular mode.
if (g_core->vr_mode) { if (g_core->vr_mode) {
app = new AppVR(g_core->main_event_loop()); app = new AppVR();
} else { } else {
app = new AppSDL(g_core->main_event_loop()); app = new AppSDL();
} }
#elif BA_CARDBOARD_BUILD #elif BA_CARDBOARD_BUILD
app = new AppVR(g_core->main_event_loop()); app = new AppVR();
#elif BA_SDL_BUILD #elif BA_SDL_BUILD
app = new AppSDL(g_core->main_event_loop()); app = new AppSDL();
#else #else
app = new App(g_core->main_event_loop()); app = new App();
#endif #endif
assert(app); assert(app);

View File

@ -9,6 +9,7 @@
#include "ballistica/base/logic/logic.h" #include "ballistica/base/logic/logic.h"
#include "ballistica/base/python/base_python.h" #include "ballistica/base/python/base_python.h"
#include "ballistica/base/python/support/python_context_call_runnable.h" #include "ballistica/base/python/support/python_context_call_runnable.h"
#include "ballistica/base/support/stress_test.h"
#include "ballistica/base/ui/ui.h" #include "ballistica/base/ui/ui.h"
#include "ballistica/core/platform/core_platform.h" #include "ballistica/core/platform/core_platform.h"
#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/foundation/event_loop.h"
@ -796,13 +797,14 @@ static PyMethodDef PyEnvDef = {
static auto PySetStressTesting(PyObject* self, PyObject* args) -> PyObject* { static auto PySetStressTesting(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY; BA_PYTHON_TRY;
int testing; int enable;
int player_count; int player_count;
if (!PyArg_ParseTuple(args, "pi", &testing, &player_count)) { if (!PyArg_ParseTuple(args, "pi", &enable, &player_count)) {
return nullptr; return nullptr;
} }
g_base->app->PushSetStressTestingCall(static_cast<bool>(testing), g_core->main_event_loop()->PushCall([enable, player_count] {
player_count); g_base->stress_test()->Set(enable, player_count);
});
Py_RETURN_NONE; Py_RETURN_NONE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }

View File

@ -42,6 +42,9 @@ class PlusSoftInterface {
const std::vector<std::string>& friends) = 0; const std::vector<std::string>& friends) = 0;
virtual void DispatchRemoteAchievementList( virtual void DispatchRemoteAchievementList(
const std::set<std::string>& achs) = 0; const std::set<std::string>& achs) = 0;
virtual void SetProductPrice(const std::string& product,
const std::string& price) = 0;
virtual void PushAnalyticsCall(const std::string& type, int increment) = 0; virtual void PushAnalyticsCall(const std::string& type, int increment) = 0;
virtual void PushPurchaseTransactionCall(const std::string& item, virtual void PushPurchaseTransactionCall(const std::string& item,
const std::string& receipt, const std::string& receipt,

View File

@ -178,7 +178,7 @@ auto CoreConfig::ForEnvVars() -> CoreConfig {
return cfg; return cfg;
} }
auto CoreConfig::ForArgsAndEnvVars(int argc, char** argv) -> CoreConfig { auto CoreConfig::ForArgsAndEnvVars(size_t argc, char** argv) -> CoreConfig {
CoreConfig cfg{}; CoreConfig cfg{};
// Apply env-vars first. We want explicit args to override these. // Apply env-vars first. We want explicit args to override these.

View File

@ -14,7 +14,7 @@ namespace ballistica::core {
/// when initing the core feature-set. /// when initing the core feature-set.
class CoreConfig { class CoreConfig {
public: public:
static auto ForArgsAndEnvVars(int argc, char** argv) -> CoreConfig; static auto ForArgsAndEnvVars(size_t argc, char** argv) -> CoreConfig;
static auto ForEnvVars() -> CoreConfig; static auto ForEnvVars() -> CoreConfig;

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 = 21221; const int kEngineBuildNumber = 21232;
const char* kEngineVersion = "1.7.26"; const char* kEngineVersion = "1.7.26";
#if BA_MONOLITHIC_BUILD #if BA_MONOLITHIC_BUILD

View File

@ -100,6 +100,10 @@ class EventLoop {
static auto GetStillPausingThreads() -> std::vector<EventLoop*>; static auto GetStillPausingThreads() -> std::vector<EventLoop*>;
auto paused() { return paused_; } auto paused() { return paused_; }
auto done() -> bool {
assert(source_ == ThreadSource::kWrapMain);
return done_;
}
private: private:
struct ThreadMessage_ { struct ThreadMessage_ {
@ -117,7 +121,7 @@ class EventLoop {
auto CheckPushRunnableSafety_() -> bool; auto CheckPushRunnableSafety_() -> bool;
void SetInternalThreadName_(const std::string& name); void SetInternalThreadName_(const std::string& name);
void WaitForNextEvent_(bool single_cycle); void WaitForNextEvent_(bool single_cycle);
void LoopUpkeep_(bool once); void LoopUpkeep_(bool single_cycle);
void LogThreadMessageTally_( void LogThreadMessageTally_(
std::vector<std::pair<LogLevel, std::string>>* log_entries); std::vector<std::pair<LogLevel, std::string>>* log_entries);
void PushLocalRunnable_(Runnable* runnable, bool* completion_flag); void PushLocalRunnable_(Runnable* runnable, bool* completion_flag);

View File

@ -6,10 +6,12 @@
#include "ballistica/base/app_mode/app_mode.h" #include "ballistica/base/app_mode/app_mode.h"
#include "ballistica/base/assets/sound_asset.h" #include "ballistica/base/assets/sound_asset.h"
#include "ballistica/base/input/input.h" #include "ballistica/base/input/input.h"
#include "ballistica/base/platform/base_platform.h"
#include "ballistica/base/python/base_python.h" #include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/plus_soft.h" #include "ballistica/base/support/plus_soft.h"
#include "ballistica/base/ui/console.h" #include "ballistica/base/ui/console.h"
#include "ballistica/base/ui/ui.h" #include "ballistica/base/ui/ui.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/ui_v1/python/class/python_class_ui_mesh.h" #include "ballistica/ui_v1/python/class/python_class_ui_mesh.h"
#include "ballistica/ui_v1/python/class/python_class_ui_sound.h" #include "ballistica/ui_v1/python/class/python_class_ui_sound.h"
#include "ballistica/ui_v1/python/class/python_class_ui_texture.h" #include "ballistica/ui_v1/python/class/python_class_ui_texture.h"
@ -2366,7 +2368,10 @@ static auto PyShowOnlineScoreUI(PyObject* self, PyObject* args,
if (game_version_obj != Py_None) { if (game_version_obj != Py_None) {
game_version = Python::GetPyString(game_version_obj); game_version = Python::GetPyString(game_version_obj);
} }
g_base->app->PushShowOnlineScoreUICall(show, game, game_version); g_core->main_event_loop()->PushCall([show, game, game_version] {
assert(g_core->InMainThread());
g_core->platform->ShowOnlineScoreUI(show, game, game_version);
});
Py_RETURN_NONE; Py_RETURN_NONE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }
@ -2726,7 +2731,8 @@ static auto PyOpenURL(PyObject* self, PyObject* args, PyObject* keywds)
if (force_internal) { if (force_internal) {
g_base->ui->ShowURL(address); g_base->ui->ShowURL(address);
} else { } else {
g_base->app->PushOpenURLCall(address); g_core->main_event_loop()->PushCall(
[address] { g_base->platform->OpenURL(address); });
} }
Py_RETURN_NONE; Py_RETURN_NONE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;

View File

@ -13,6 +13,7 @@
#include "ballistica/base/python/support/python_context_call.h" #include "ballistica/base/python/support/python_context_call.h"
#include "ballistica/base/ui/ui.h" #include "ballistica/base/ui/ui.h"
#include "ballistica/core/core.h" #include "ballistica/core/core.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/generic/utils.h" #include "ballistica/shared/generic/utils.h"
#include "ballistica/shared/python/python.h" #include "ballistica/shared/python/python.h"
#include "ballistica/ui_v1/python/ui_v1_python.h" #include "ballistica/ui_v1/python/ui_v1_python.h"
@ -559,7 +560,20 @@ void TextWidget::BringUpEditDialog() {
use_internal_dialog = false; use_internal_dialog = false;
// store ourself as the current text-widget and kick off an edit // store ourself as the current text-widget and kick off an edit
android_string_edit_widget_ = this; android_string_edit_widget_ = this;
g_base->app->PushStringEditCall(description_, text_raw_, max_chars_); g_core->main_event_loop()->PushCall(
[name = description_, value = text_raw_, max_chars = max_chars_] {
static millisecs_t last_edit_time = 0;
millisecs_t t = g_core->GetAppTimeMillisecs();
// Ignore if too close together.
// (in case second request comes in before first takes effect).
if (t - last_edit_time < 1000) {
return;
}
last_edit_time = t;
assert(g_core->InMainThread());
g_core->platform->EditText(name, value, max_chars);
});
} }
} }
} }