mirror of
https://github.com/RYDE-WORK/ballistica.git
synced 2026-01-28 01:43:22 +08:00
More C++
This commit is contained in:
parent
87b6716d59
commit
0809dbcbfb
@ -3934,14 +3934,14 @@
|
||||
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
|
||||
"build/prefab/linux-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ac/96/c3b9934061393fe09cc90ff24b8d",
|
||||
"build/prefab/linux-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/38/2b/5641b3b40846f74f232771ac0457",
|
||||
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/e3/43/5df3f99b46aa9b6aff2db87fabb4",
|
||||
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/b1/06/b993362a65a5760ddfb10f2c59b9",
|
||||
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/4f/99/2234c89a65d7e5d8463556d9df5e",
|
||||
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e4/96/f289ec31cdada6f41de5dce382bd",
|
||||
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/62/8d/6f2752d858b097c318920ff09cf5",
|
||||
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/77/1d/7986d62c92d9f34ca85396168923",
|
||||
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/a3/da/d5f88e92926543f61bd5dd80321e",
|
||||
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/60/8e/d7efd5e7d7a94439453909527d4e",
|
||||
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d3/b3/8124ea626db8dbd7b1744c335c7c",
|
||||
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/27/f7/87a5d3b8648352ac40398f86dc8e"
|
||||
"build/prefab/linux/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/d0/21/cb738075207c3dab9f2389455a69",
|
||||
"build/prefab/linux/release/ballisticacore": "https://files.ballistica.net/cache/ba1/cd/44/d59a1f7d6ee05b58e80ebc3469bf",
|
||||
"build/prefab/mac-server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/b4/56/6d1f390d348ddf65937af3374d76",
|
||||
"build/prefab/mac-server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/31/b9/176ba76e48c23d064b6583963a0e",
|
||||
"build/prefab/mac/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/37/18/56c6d3b8788362ceb97005b6b3b6",
|
||||
"build/prefab/mac/release/ballisticacore": "https://files.ballistica.net/cache/ba1/a6/bd/da7954fe559403d021b15df72967",
|
||||
"build/prefab/windows-server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/97/10/138625f676793653620a84cd712b",
|
||||
"build/prefab/windows-server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/ef/a8/4b532f37f74929b834ffd6c2ac5d",
|
||||
"build/prefab/windows/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/3f/6f/c49674367960a82b19a45641daa6",
|
||||
"build/prefab/windows/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/df/a3/90e19f2ef6b2edd17d0f936245f2"
|
||||
}
|
||||
483
src/ballistica/app/app.cc
Normal file
483
src/ballistica/app/app.cc
Normal file
@ -0,0 +1,483 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/app/app.h"
|
||||
|
||||
#include "ballistica/core/thread.h"
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
#include "ballistica/input/device/touch_input.h"
|
||||
#include "ballistica/input/input.h"
|
||||
#include "ballistica/networking/network_reader.h"
|
||||
#include "ballistica/networking/networking.h"
|
||||
#include "ballistica/networking/telnet_server.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
App::App(Thread* thread) : Module("app", thread) {
|
||||
assert(g_app == nullptr);
|
||||
g_app = this;
|
||||
|
||||
// So anyone who needs to use the 'main' thread id can get at it...
|
||||
Thread::UpdateMainThreadID();
|
||||
|
||||
// We modify some app behavior when run under the server manager.
|
||||
auto* envval = getenv("BA_SERVER_WRAPPER_MANAGED");
|
||||
server_wrapper_managed_ = (envval && strcmp(envval, "1") == 0);
|
||||
}
|
||||
|
||||
void App::PostInit() {
|
||||
// If we've got a nice themed hardware cursor, show it.
|
||||
// Otherwise hide the hardware cursor; we'll draw it in software.
|
||||
// (need to run this in postinit because SDL/etc may not be inited yet
|
||||
// as of App::App().
|
||||
g_platform->SetHardwareCursorVisible(g_buildconfig.hardware_cursor());
|
||||
}
|
||||
|
||||
App::~App() = default;
|
||||
|
||||
auto App::UsesEventLoop() const -> bool {
|
||||
// We have 2 redundant values for essentially the same thing;
|
||||
// should get rid of IsEventPushMode() once we've created
|
||||
// App subclasses for our various platforms.
|
||||
return !g_platform->IsEventPushMode();
|
||||
}
|
||||
|
||||
void App::PushInterruptSignalSetupCall() {
|
||||
g_platform->SetupInterruptHandling();
|
||||
}
|
||||
|
||||
void App::RunRenderUpkeepCycle() {
|
||||
// This should only be used in cases where the OS is handling the event loop.
|
||||
assert(!UsesEventLoop());
|
||||
if (UsesEventLoop()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pump thread messages (we're being driven by frame-draw callbacks
|
||||
// so this is the only place that it gets done at).
|
||||
thread()->RunEventLoop(true); // Single pass only.
|
||||
|
||||
// Now do the general app event cycle for whoever needs to process things.
|
||||
RunEvents();
|
||||
}
|
||||
|
||||
void App::RebuildLostGLContext() {
|
||||
assert(InMainThread());
|
||||
assert(g_graphics_server);
|
||||
if (g_graphics_server) {
|
||||
g_graphics_server->RebuildLostContext();
|
||||
}
|
||||
}
|
||||
|
||||
void App::DrawFrame(bool during_resize) {
|
||||
assert(InMainThread());
|
||||
|
||||
// Its possible to receive frames before we're ready to draw.
|
||||
if (!g_graphics_server || !g_graphics_server->renderer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
millisecs_t starttime = GetRealTime();
|
||||
|
||||
// A resize-draw event means that we're drawing due to a window resize.
|
||||
// In this case we ignore regular draw events for a short while
|
||||
// afterwards which makes resizing smoother.
|
||||
// FIXME: should figure out the *correct* way to handle this;
|
||||
// I believe the underlying cause here is some sort of context contention
|
||||
// across threads.
|
||||
if (during_resize) {
|
||||
last_resize_draw_event_time_ = starttime;
|
||||
} else {
|
||||
if (starttime - last_resize_draw_event_time_ < (1000 / 30)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
g_graphics_server->TryRender();
|
||||
RunRenderUpkeepCycle();
|
||||
}
|
||||
|
||||
void App::SetScreenResolution(float width, float height) {
|
||||
assert(InMainThread());
|
||||
if (!HeadlessMode()) {
|
||||
g_graphics_server->VideoResize(width, height);
|
||||
}
|
||||
}
|
||||
|
||||
void App::PushShutdownCompleteCall() {
|
||||
PushCall([this] { ShutdownComplete(); });
|
||||
}
|
||||
|
||||
void App::ShutdownComplete() {
|
||||
assert(g_platform);
|
||||
|
||||
// Need to call our cleanup stuff that would otherwise get called in main.
|
||||
g_platform->FinalCleanup();
|
||||
|
||||
done_ = true;
|
||||
|
||||
// Kill our own event loop (or tell the OS to kill its).
|
||||
if (UsesEventLoop()) {
|
||||
thread()->Quit();
|
||||
} else {
|
||||
g_platform->QuitApp();
|
||||
}
|
||||
}
|
||||
|
||||
void App::RunEvents() {
|
||||
if (!HeadlessMode()) {
|
||||
// there's probably a better place for this...
|
||||
UpdateStressTesting();
|
||||
}
|
||||
|
||||
// Give platforms a chance to pump/handle their own events.
|
||||
// FIXME: now that we have app class overrides, platform should really
|
||||
// not be doing event handling. (need to fix rift build).
|
||||
g_platform->RunEvents();
|
||||
}
|
||||
|
||||
void App::UpdatePauseResume() {
|
||||
if (actually_paused_) {
|
||||
// Unpause if no one wants pause.
|
||||
if (!sys_paused_app_ && !user_paused_app_) {
|
||||
OnResume();
|
||||
actually_paused_ = false;
|
||||
}
|
||||
} else {
|
||||
// Pause if anyone wants.
|
||||
if (sys_paused_app_ || user_paused_app_) {
|
||||
OnPause();
|
||||
actually_paused_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App::OnPause() {
|
||||
assert(InMainThread());
|
||||
|
||||
// Avoid reading gyro values for a short time to avoid hitches when restored.
|
||||
g_graphics->SetGyroEnabled(false);
|
||||
|
||||
// IMPORTANT: Any on-pause related stuff that threads need to do must
|
||||
// must be done from their HandleThreadPause(). If we push runnables to them
|
||||
// they may or may not be called before the thread is actually paused.
|
||||
|
||||
Thread::SetThreadsPaused(true);
|
||||
|
||||
assert(g_networking);
|
||||
g_networking->Pause();
|
||||
|
||||
assert(g_network_reader);
|
||||
if (g_network_reader) {
|
||||
g_network_reader->Pause();
|
||||
}
|
||||
|
||||
if (g_app_globals->telnet_server) {
|
||||
g_app_globals->telnet_server->Pause();
|
||||
}
|
||||
|
||||
g_platform->OnAppPause();
|
||||
}
|
||||
|
||||
void App::OnResume() {
|
||||
assert(InMainThread());
|
||||
last_app_resume_time_ = GetRealTime();
|
||||
Thread::SetThreadsPaused(false);
|
||||
|
||||
g_platform->OnAppResume();
|
||||
g_networking->Resume();
|
||||
g_network_reader->Resume();
|
||||
|
||||
if (g_app_globals->telnet_server) {
|
||||
g_app_globals->telnet_server->Resume();
|
||||
}
|
||||
|
||||
// Also let the Python layer do what it needs to
|
||||
// (starting/stopping music, etc).
|
||||
g_python->PushObjCall(Python::ObjID::kHandleAppResumeCall);
|
||||
g_game->PushOnAppResumeCall();
|
||||
|
||||
g_graphics->SetGyroEnabled(true);
|
||||
|
||||
// When resuming from a paused state, we may want to
|
||||
// pause whatever game was running when we last were active.
|
||||
// TODO(efro): we should make this smarter so it doesn't happen if
|
||||
// we're in a network game or something that we can't pause;
|
||||
// bringing up the menu doesn't really accomplish anything there.
|
||||
if (g_app_globals->should_pause) {
|
||||
g_app_globals->should_pause = false;
|
||||
|
||||
// If we've been completely backgrounded,
|
||||
// send a menu-press command to the game; this will
|
||||
// bring up a pause menu if we're in the game/etc.
|
||||
g_game->PushMainMenuPressCall(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
auto App::GetProductPrice(const std::string& product) -> std::string {
|
||||
std::lock_guard<std::mutex> 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::lock_guard<std::mutex> lock(product_prices_mutex_);
|
||||
product_prices_[product] = price;
|
||||
}
|
||||
|
||||
void App::PauseApp() {
|
||||
assert(InMainThread());
|
||||
Platform::DebugLog("PauseApp@"
|
||||
+ std::to_string(Platform::GetCurrentMilliseconds()));
|
||||
assert(!sys_paused_app_);
|
||||
sys_paused_app_ = true;
|
||||
UpdatePauseResume();
|
||||
}
|
||||
|
||||
void App::ResumeApp() {
|
||||
assert(InMainThread());
|
||||
Platform::DebugLog("ResumeApp@"
|
||||
+ std::to_string(Platform::GetCurrentMilliseconds()));
|
||||
assert(sys_paused_app_);
|
||||
sys_paused_app_ = false;
|
||||
UpdatePauseResume();
|
||||
}
|
||||
|
||||
void App::DidFinishRenderingFrame(FrameDef* frame) {}
|
||||
|
||||
void App::PrimeEventPump() {
|
||||
assert(!UsesEventLoop());
|
||||
|
||||
// Pump events manually until a screen gets created.
|
||||
// At that point we use frame-draws to drive our event loop.
|
||||
while (!g_graphics_server->initial_screen_created()) {
|
||||
g_main_thread->RunEventLoop(true);
|
||||
Platform::SleepMS(1);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Push-Calls
|
||||
|
||||
void App::PushShowOnlineScoreUICall(const std::string& show,
|
||||
const std::string& game,
|
||||
const std::string& game_version) {
|
||||
PushCall([this, show, game, game_version] {
|
||||
assert(InMainThread());
|
||||
g_platform->ShowOnlineScoreUI(show, game, game_version);
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushNetworkSetupCall(int port, int telnet_port, bool enable_telnet,
|
||||
const std::string& telnet_password) {
|
||||
PushCall([this, port, telnet_port, enable_telnet, telnet_password] {
|
||||
assert(InMainThread());
|
||||
// Kick these off if they don't exist.
|
||||
// (do we want to support changing ports on existing ones?)
|
||||
if (g_network_reader == nullptr) {
|
||||
new NetworkReader(port);
|
||||
}
|
||||
if (g_app_globals->telnet_server == nullptr && enable_telnet) {
|
||||
new TelnetServer(telnet_port);
|
||||
assert(g_app_globals->telnet_server);
|
||||
if (telnet_password.empty()) {
|
||||
g_app_globals->telnet_server->SetPassword(nullptr);
|
||||
} else {
|
||||
g_app_globals->telnet_server->SetPassword(telnet_password.c_str());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushPurchaseAckCall(const std::string& purchase,
|
||||
const std::string& order_id) {
|
||||
PushCall([this, purchase, order_id] {
|
||||
g_platform->PurchaseAck(purchase, order_id);
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushGetScoresToBeatCall(const std::string& level,
|
||||
const std::string& config,
|
||||
void* py_callback) {
|
||||
PushCall([this, level, config, py_callback] {
|
||||
assert(InMainThread());
|
||||
g_platform->GetScoresToBeat(level, config, py_callback);
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushPurchaseCall(const std::string& item) {
|
||||
PushCall([this, item] {
|
||||
assert(InMainThread());
|
||||
g_platform->Purchase(item);
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushRestorePurchasesCall() {
|
||||
PushCall([this] {
|
||||
assert(InMainThread());
|
||||
g_platform->RestorePurchases();
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushOpenURLCall(const std::string& url) {
|
||||
PushCall([this, url] { g_platform->OpenURL(url); });
|
||||
}
|
||||
|
||||
void App::PushGetFriendScoresCall(const std::string& game,
|
||||
const std::string& game_version, void* data) {
|
||||
PushCall([this, game, game_version, data] {
|
||||
g_platform->GetFriendScores(game, game_version, data);
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushSubmitScoreCall(const std::string& game,
|
||||
const std::string& game_version, int64_t score) {
|
||||
PushCall([this, game, game_version, score] {
|
||||
g_platform->SubmitScore(game, game_version, score);
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushAchievementReportCall(const std::string& achievement) {
|
||||
PushCall([this, achievement] { g_platform->ReportAchievement(achievement); });
|
||||
}
|
||||
|
||||
void App::PushStringEditCall(const std::string& name, const std::string& value,
|
||||
int max_chars) {
|
||||
PushCall([this, name, value, max_chars] {
|
||||
static millisecs_t last_edit_time = 0;
|
||||
millisecs_t t = GetRealTime();
|
||||
|
||||
// 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(InMainThread());
|
||||
g_platform->EditText(name, value, max_chars);
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushSetStressTestingCall(bool enable, int player_count) {
|
||||
PushCall([this, enable, player_count] {
|
||||
bool was_stress_testing = stress_testing_;
|
||||
stress_testing_ = enable;
|
||||
stress_test_player_count_ = player_count;
|
||||
|
||||
// If we're turning on, reset our intervals and things.
|
||||
if (!was_stress_testing && stress_testing_) {
|
||||
// So our first sample is 1 interval from now...
|
||||
last_stress_test_update_time_ = GetRealTime();
|
||||
// Reset our frames-rendered tally.
|
||||
if (g_graphics && g_graphics_server->renderer()) {
|
||||
last_total_frames_rendered_ =
|
||||
g_graphics_server->renderer()->total_frames_rendered();
|
||||
} else {
|
||||
// Assume zero if there's no graphics yet.
|
||||
last_total_frames_rendered_ = 0;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void App::PushResetAchievementsCall() {
|
||||
PushCall([this] { g_platform->ResetAchievements(); });
|
||||
}
|
||||
|
||||
void App::OnBootstrapComplete() {
|
||||
assert(InMainThread());
|
||||
assert(g_input);
|
||||
|
||||
if (!HeadlessMode()) {
|
||||
// On desktop systems we just assume keyboard input exists and add it
|
||||
// immediately.
|
||||
if (g_platform->IsRunningOnDesktop()) {
|
||||
g_input->PushCreateKeyboardInputDevices();
|
||||
}
|
||||
|
||||
// On non-tv, non-desktop, non-vr systems, create a touchscreen input.
|
||||
if (!g_platform->IsRunningOnTV() && !IsVRMode()
|
||||
&& !g_platform->IsRunningOnDesktop()) {
|
||||
g_input->CreateTouchInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void App::PushCursorUpdate(bool vis) {
|
||||
PushCall([this, vis] {
|
||||
assert(InMainThread());
|
||||
g_platform->SetHardwareCursorVisible(vis);
|
||||
});
|
||||
}
|
||||
|
||||
void App::UpdateStressTesting() {
|
||||
// Handle a little misc stuff here.
|
||||
// If we're currently running stress-tests, update that stuff.
|
||||
if (stress_testing_ && g_input) {
|
||||
// Update our fake inputs to make our dudes run around.
|
||||
g_input->ProcessStressTesting(stress_test_player_count_);
|
||||
|
||||
// Every 10 seconds update our stress-test stats.
|
||||
millisecs_t t = GetRealTime();
|
||||
if (t - last_stress_test_update_time_ >= 10000) {
|
||||
if (stress_test_stats_file_ == nullptr) {
|
||||
assert(g_platform);
|
||||
std::string f_name =
|
||||
g_platform->GetUserPythonDirectory() + "/stress_test_stats.csv";
|
||||
stress_test_stats_file_ = g_platform->FOpen(f_name.c_str(), "wb");
|
||||
if (stress_test_stats_file_ != nullptr) {
|
||||
fprintf(stress_test_stats_file_,
|
||||
"time,averageFps,nodes,models,collideModels,textures,sounds,"
|
||||
"pssMem,sharedDirtyMem,privateDirtyMem\n");
|
||||
fflush(stress_test_stats_file_);
|
||||
if (g_buildconfig.ostype_android()) {
|
||||
// On android, let the OS know we've added or removed a file
|
||||
// (limit to android or we'll get an unimplemented warning)
|
||||
g_platform->AndroidRefreshFile(f_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stress_test_stats_file_ != nullptr) {
|
||||
// See how many frames we've rendered this past interval.
|
||||
int total_frames_rendered;
|
||||
if (g_graphics && g_graphics_server->renderer()) {
|
||||
total_frames_rendered =
|
||||
g_graphics_server->renderer()->total_frames_rendered();
|
||||
} else {
|
||||
total_frames_rendered = last_total_frames_rendered_;
|
||||
}
|
||||
float avg =
|
||||
static_cast<float>(total_frames_rendered
|
||||
- last_total_frames_rendered_)
|
||||
/ (static_cast<float>(t - last_stress_test_update_time_) / 1000.0f);
|
||||
last_total_frames_rendered_ = total_frames_rendered;
|
||||
uint32_t model_count = 0;
|
||||
uint32_t collide_model_count = 0;
|
||||
uint32_t texture_count = 0;
|
||||
uint32_t sound_count = 0;
|
||||
uint32_t node_count = 0;
|
||||
if (g_media) {
|
||||
model_count = g_media->total_model_count();
|
||||
collide_model_count = g_media->total_collide_model_count();
|
||||
texture_count = g_media->total_texture_count();
|
||||
sound_count = g_media->total_sound_count();
|
||||
}
|
||||
assert(g_game);
|
||||
std::string mem_usage = g_platform->GetMemUsageInfo();
|
||||
fprintf(stress_test_stats_file_, "%d,%.1f,%d,%d,%d,%d,%d,%s\n",
|
||||
static_cast<int>(GetRealTime()), avg, node_count, model_count,
|
||||
collide_model_count, texture_count, sound_count,
|
||||
mem_usage.c_str());
|
||||
fflush(stress_test_stats_file_);
|
||||
}
|
||||
last_stress_test_update_time_ = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
148
src/ballistica/app/app.h
Normal file
148
src/ballistica/app/app.h
Normal file
@ -0,0 +1,148 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_APP_APP_H_
|
||||
#define BALLISTICA_APP_APP_H_
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "ballistica/core/module.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
/// Our high level app interface module.
|
||||
/// It runs in the main thread and is what platform wrappers
|
||||
/// should primarily interact with.
|
||||
class App : public Module {
|
||||
public:
|
||||
explicit App(Thread* thread);
|
||||
~App() override;
|
||||
|
||||
/// This gets run after the constructor completes.
|
||||
/// Any setup that may trigger a virtual method/etc. should go here.
|
||||
void PostInit();
|
||||
|
||||
/// Return whether this class runs its own event loop.
|
||||
/// If true, BallisticaMain() will continuously ask the app for events
|
||||
/// until the app is quit, at which point BallisticaMain() returns.
|
||||
/// If false, BallisticaMain returns immediately and it is assumed
|
||||
/// that the OS handles the app lifecycle and pushes events to the app
|
||||
/// via callbacks/etc.
|
||||
auto UsesEventLoop() const -> bool;
|
||||
|
||||
/// Called for non-event-loop apps to give them an opportunity to
|
||||
/// ensure they are self-sustaining. For instance, an app relying on
|
||||
/// frame-draws for its main thread event processing may need to
|
||||
/// manually pump events until frame rendering begins.
|
||||
virtual void PrimeEventPump();
|
||||
|
||||
/// Handle any pending OS events.
|
||||
/// On normal graphical builds this is triggered by RunRenderUpkeepCycle();
|
||||
/// timer intervals for headless builds, etc.
|
||||
/// Should process any pending OS events, etc.
|
||||
virtual void RunEvents();
|
||||
|
||||
// These should be called by the window, view-controller, sdl,
|
||||
// or whatever is driving the app. They must be called from the main thread.
|
||||
|
||||
/// Should be called on mobile when the app is backgrounded.
|
||||
/// Pauses threads, closes network sockets, etc.
|
||||
void PauseApp();
|
||||
|
||||
auto paused() const -> bool { return actually_paused_; }
|
||||
|
||||
/// Should be called on mobile when the app is foregrounded.
|
||||
/// Spins threads back up, re-opens network sockets, etc.
|
||||
void ResumeApp();
|
||||
|
||||
/// The last time the app was resumed (uses GetRealTime() value).
|
||||
auto last_app_resume_time() const -> millisecs_t {
|
||||
return last_app_resume_time_;
|
||||
}
|
||||
|
||||
/// Should be called when the window/screen resolution changes.
|
||||
void SetScreenResolution(float width, float height);
|
||||
|
||||
/// Should be called if the platform detects the GL context was lost.
|
||||
void RebuildLostGLContext();
|
||||
|
||||
/// Attempt to draw a frame.
|
||||
void DrawFrame(bool during_resize = false);
|
||||
|
||||
/// Used on platforms where our main thread event processing is driven by
|
||||
/// frame-draw commands given to us. This should be called after drawing
|
||||
/// a frame in order to bring game state up to date and process OS events.
|
||||
void RunRenderUpkeepCycle();
|
||||
|
||||
/// Called by the graphics-server when drawing completes for a 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 ballisticacore_server.py
|
||||
/// (affects some app behavior).
|
||||
auto server_wrapper_managed() const -> bool {
|
||||
return server_wrapper_managed_;
|
||||
}
|
||||
|
||||
virtual void OnBootstrapComplete();
|
||||
|
||||
// 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 PushGetFriendScoresCall(const std::string& game,
|
||||
const std::string& game_version, void* data);
|
||||
void PushSubmitScoreCall(const std::string& game,
|
||||
const std::string& game_version, int64_t score);
|
||||
void PushAchievementReportCall(const std::string& achievement);
|
||||
void PushGetScoresToBeatCall(const std::string& level,
|
||||
const std::string& config, void* py_callback);
|
||||
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 PushRestorePurchasesCall();
|
||||
void PushResetAchievementsCall();
|
||||
void PushPurchaseAckCall(const std::string& purchase,
|
||||
const std::string& order_id);
|
||||
void PushNetworkSetupCall(int port, int telnet_port, bool enable_telnet,
|
||||
const std::string& telnet_password);
|
||||
void PushShutdownCompleteCall();
|
||||
void PushInterruptSignalSetupCall();
|
||||
|
||||
private:
|
||||
void UpdateStressTesting();
|
||||
void UpdatePauseResume();
|
||||
void OnPause();
|
||||
void OnResume();
|
||||
void ShutdownComplete();
|
||||
bool done_{};
|
||||
bool server_wrapper_managed_{};
|
||||
bool sys_paused_app_{};
|
||||
bool user_paused_app_{};
|
||||
bool actually_paused_{};
|
||||
millisecs_t last_resize_draw_event_time_{};
|
||||
millisecs_t last_app_resume_time_{};
|
||||
std::map<std::string, std::string> product_prices_;
|
||||
std::mutex product_prices_mutex_;
|
||||
FILE* stress_test_stats_file_{};
|
||||
millisecs_t last_stress_test_update_time_{};
|
||||
int last_total_frames_rendered_{};
|
||||
bool stress_testing_{};
|
||||
int stress_test_player_count_{8};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_APP_APP_H_
|
||||
245
src/ballistica/app/app_config.cc
Normal file
245
src/ballistica/app/app_config.cc
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/app/app_config.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
#include "ballistica/python/python.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void AppConfig::Init() { new AppConfig(); }
|
||||
|
||||
auto AppConfig::Entry::FloatValue() const -> float {
|
||||
throw Exception("not a float entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::StringValue() const -> std::string {
|
||||
throw Exception("not a string entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::IntValue() const -> int {
|
||||
throw Exception("not an int entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::BoolValue() const -> bool {
|
||||
throw Exception("not a bool entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultFloatValue() const -> float {
|
||||
throw Exception("not a float entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultStringValue() const -> std::string {
|
||||
throw Exception("not a string entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultIntValue() const -> int {
|
||||
throw Exception("not an int entry");
|
||||
}
|
||||
|
||||
auto AppConfig::Entry::DefaultBoolValue() const -> bool {
|
||||
throw Exception("not a bool entry");
|
||||
}
|
||||
|
||||
class AppConfig::StringEntry : public AppConfig::Entry {
|
||||
public:
|
||||
StringEntry() = default;
|
||||
StringEntry(const char* name, std::string default_value)
|
||||
: Entry(name), default_value_(std::move(default_value)) {}
|
||||
auto GetType() const -> Type override { return Type::kString; }
|
||||
auto Resolve() const -> std::string {
|
||||
return g_python->GetRawConfigValue(name().c_str(), default_value_.c_str());
|
||||
}
|
||||
auto StringValue() const -> std::string override { return Resolve(); }
|
||||
auto DefaultStringValue() const -> std::string override {
|
||||
return default_value_;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string default_value_;
|
||||
};
|
||||
|
||||
class AppConfig::FloatEntry : public AppConfig::Entry {
|
||||
public:
|
||||
FloatEntry() = default;
|
||||
FloatEntry(const char* name, float default_value)
|
||||
: Entry(name), default_value_(default_value) {}
|
||||
auto GetType() const -> Type override { return Type::kFloat; }
|
||||
auto Resolve() const -> float {
|
||||
return g_python->GetRawConfigValue(name().c_str(), default_value_);
|
||||
}
|
||||
auto FloatValue() const -> float override { return Resolve(); }
|
||||
auto DefaultFloatValue() const -> float override { return default_value_; }
|
||||
|
||||
private:
|
||||
float default_value_{};
|
||||
};
|
||||
|
||||
class AppConfig::IntEntry : public AppConfig::Entry {
|
||||
public:
|
||||
IntEntry() = default;
|
||||
IntEntry(const char* name, int default_value)
|
||||
: Entry(name), default_value_(default_value) {}
|
||||
auto GetType() const -> Type override { return Type::kInt; }
|
||||
auto Resolve() const -> int {
|
||||
return g_python->GetRawConfigValue(name().c_str(), default_value_);
|
||||
}
|
||||
auto IntValue() const -> int override { return Resolve(); }
|
||||
auto DefaultIntValue() const -> int override { return default_value_; }
|
||||
|
||||
private:
|
||||
int default_value_{};
|
||||
};
|
||||
|
||||
class AppConfig::BoolEntry : public AppConfig::Entry {
|
||||
public:
|
||||
BoolEntry() = default;
|
||||
BoolEntry(const char* name, bool default_value)
|
||||
: Entry(name), default_value_(default_value) {}
|
||||
auto GetType() const -> Type override { return Type::kBool; }
|
||||
auto Resolve() const -> bool {
|
||||
return g_python->GetRawConfigValue(name().c_str(), default_value_);
|
||||
}
|
||||
auto BoolValue() const -> bool override { return Resolve(); }
|
||||
auto DefaultBoolValue() const -> bool override { return default_value_; }
|
||||
|
||||
private:
|
||||
bool default_value_{};
|
||||
};
|
||||
|
||||
AppConfig::AppConfig() {
|
||||
// (We're a singleton).
|
||||
assert(g_app_config == nullptr);
|
||||
g_app_config = this;
|
||||
SetupEntries();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void AppConfig::CompleteMap(const T& entry_map) {
|
||||
for (auto&& i : entry_map) {
|
||||
assert(entries_by_name_.find(i.second.name()) == entries_by_name_.end());
|
||||
assert(i.first < decltype(i.first)::kLast);
|
||||
entries_by_name_[i.second.name()] = &i.second;
|
||||
}
|
||||
|
||||
// Make sure all values have entries.
|
||||
#if BA_DEBUG_BUILD
|
||||
int last = static_cast<int>(decltype(entry_map.begin()->first)::kLast); // ew
|
||||
for (int j = 0; j < last; ++j) {
|
||||
auto i2 =
|
||||
entry_map.find(static_cast<decltype(entry_map.begin()->first)>(j));
|
||||
if (i2 == entry_map.end()) {
|
||||
throw Exception("Missing appconfig entry " + std::to_string(j));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void AppConfig::SetupEntries() {
|
||||
// Register all our typed entries.
|
||||
float_entries_[FloatID::kScreenGamma] = FloatEntry("Screen Gamma", 1.0F);
|
||||
float_entries_[FloatID::kScreenPixelScale] =
|
||||
FloatEntry("Screen Pixel Scale", 1.0F);
|
||||
float_entries_[FloatID::kTouchControlsScale] =
|
||||
FloatEntry("Touch Controls Scale", 1.0F);
|
||||
float_entries_[FloatID::kTouchControlsScaleMovement] =
|
||||
FloatEntry("Touch Controls Scale Movement", 1.0F);
|
||||
float_entries_[FloatID::kTouchControlsScaleActions] =
|
||||
FloatEntry("Touch Controls Scale Actions", 1.0F);
|
||||
float_entries_[FloatID::kSoundVolume] = FloatEntry("Sound Volume", 1.0F);
|
||||
float_entries_[FloatID::kMusicVolume] = FloatEntry("Music Volume", 1.0F);
|
||||
|
||||
// Note: keep this synced with the defaults in MainActivity.java.
|
||||
float gvrrts_default = g_platform->IsRunningOnDaydream() ? 1.0F : 0.5F;
|
||||
float_entries_[FloatID::kGoogleVRRenderTargetScale] =
|
||||
FloatEntry("GVR Render Target Scale", gvrrts_default);
|
||||
|
||||
string_entries_[StringID::kResolutionAndroid] =
|
||||
StringEntry("Resolution (Android)", "Auto");
|
||||
string_entries_[StringID::kTouchActionControlType] =
|
||||
StringEntry("Touch Action Control Type", "buttons");
|
||||
string_entries_[StringID::kTouchMovementControlType] =
|
||||
StringEntry("Touch Movement Control Type", "swipe");
|
||||
string_entries_[StringID::kGraphicsQuality] =
|
||||
StringEntry("Graphics Quality", "Auto");
|
||||
string_entries_[StringID::kTextureQuality] =
|
||||
StringEntry("Texture Quality", "Auto");
|
||||
string_entries_[StringID::kVerticalSync] =
|
||||
StringEntry("Vertical Sync", "Auto");
|
||||
string_entries_[StringID::kVRHeadRelativeAudio] =
|
||||
StringEntry("VR Head Relative Audio", "Auto");
|
||||
string_entries_[StringID::kMacControllerSubsystem] =
|
||||
StringEntry("Mac Controller Subsystem", "Classic");
|
||||
string_entries_[StringID::kTelnetPassword] =
|
||||
StringEntry("Telnet Password", "changeme");
|
||||
|
||||
int_entries_[IntID::kPort] = IntEntry("Port", kDefaultPort);
|
||||
int_entries_[IntID::kTelnetPort] =
|
||||
IntEntry("Telnet Port", kDefaultTelnetPort);
|
||||
|
||||
bool_entries_[BoolID::kTouchControlsSwipeHidden] =
|
||||
BoolEntry("Touch Controls Swipe Hidden", false);
|
||||
bool_entries_[BoolID::kFullscreen] = BoolEntry("Fullscreen", false);
|
||||
bool_entries_[BoolID::kKickIdlePlayers] =
|
||||
BoolEntry("Kick Idle Players", false);
|
||||
bool_entries_[BoolID::kAlwaysUseInternalKeyboard] =
|
||||
BoolEntry("Always Use Internal Keyboard", false);
|
||||
bool_entries_[BoolID::kShowFPS] = BoolEntry("Show FPS", false);
|
||||
bool_entries_[BoolID::kTVBorder] =
|
||||
BoolEntry("TV Border", g_platform->IsRunningOnTV());
|
||||
bool_entries_[BoolID::kKeyboardP2Enabled] =
|
||||
BoolEntry("Keyboard P2 Enabled", false);
|
||||
bool_entries_[BoolID::kEnablePackageMods] =
|
||||
BoolEntry("Enable Package Mods", false);
|
||||
bool_entries_[BoolID::kChatMuted] = BoolEntry("Chat Muted", false);
|
||||
bool_entries_[BoolID::kEnableRemoteApp] =
|
||||
BoolEntry("Enable Remote App", true);
|
||||
bool_entries_[BoolID::kEnableTelnet] = BoolEntry("Enable Telnet", true);
|
||||
bool_entries_[BoolID::kDisableCameraShake] =
|
||||
BoolEntry("Disable Camera Shake", false);
|
||||
bool_entries_[BoolID::kDisableCameraGyro] =
|
||||
BoolEntry("Disable Camera Gyro", false);
|
||||
|
||||
// Now add everything to our name map and make sure all is kosher.
|
||||
CompleteMap(float_entries_);
|
||||
CompleteMap(int_entries_);
|
||||
CompleteMap(string_entries_);
|
||||
CompleteMap(bool_entries_);
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(FloatID id) -> float {
|
||||
auto i = float_entries_.find(id);
|
||||
if (i == float_entries_.end()) {
|
||||
throw Exception("Invalid config entry");
|
||||
}
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(StringID id) -> std::string {
|
||||
auto i = string_entries_.find(id);
|
||||
if (i == string_entries_.end()) {
|
||||
throw Exception("Invalid config entry");
|
||||
}
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(BoolID id) -> bool {
|
||||
auto i = bool_entries_.find(id);
|
||||
if (i == bool_entries_.end()) {
|
||||
throw Exception("Invalid config entry");
|
||||
}
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
auto AppConfig::Resolve(IntID id) -> int {
|
||||
auto i = int_entries_.find(id);
|
||||
if (i == int_entries_.end()) {
|
||||
throw Exception("Invalid config entry");
|
||||
}
|
||||
return i->second.Resolve();
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
130
src/ballistica/app/app_config.h
Normal file
130
src/ballistica/app/app_config.h
Normal file
@ -0,0 +1,130 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_APP_APP_CONFIG_H_
|
||||
#define BALLISTICA_APP_APP_CONFIG_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// This class wrangles user config values for the app.
|
||||
// The underlying config data currently lives in the Python layer,
|
||||
// so at the moment these calls are only usable from the game thread,
|
||||
// but that may change in the future.
|
||||
class AppConfig {
|
||||
public:
|
||||
// Our official config values:
|
||||
|
||||
enum class FloatID {
|
||||
kScreenGamma,
|
||||
kScreenPixelScale,
|
||||
kTouchControlsScale,
|
||||
kTouchControlsScaleMovement,
|
||||
kTouchControlsScaleActions,
|
||||
kSoundVolume,
|
||||
kMusicVolume,
|
||||
kGoogleVRRenderTargetScale,
|
||||
kLast // Sentinel.
|
||||
};
|
||||
|
||||
enum class StringID {
|
||||
kResolutionAndroid,
|
||||
kTouchActionControlType,
|
||||
kTouchMovementControlType,
|
||||
kGraphicsQuality,
|
||||
kTextureQuality,
|
||||
kVerticalSync,
|
||||
kVRHeadRelativeAudio,
|
||||
kMacControllerSubsystem,
|
||||
kTelnetPassword,
|
||||
kLast // Sentinel.
|
||||
};
|
||||
|
||||
enum class IntID {
|
||||
kPort,
|
||||
kTelnetPort,
|
||||
kLast // Sentinel.
|
||||
};
|
||||
|
||||
enum class BoolID {
|
||||
kTouchControlsSwipeHidden,
|
||||
kFullscreen,
|
||||
kKickIdlePlayers,
|
||||
kAlwaysUseInternalKeyboard,
|
||||
kShowFPS,
|
||||
kTVBorder,
|
||||
kKeyboardP2Enabled,
|
||||
kEnablePackageMods,
|
||||
kChatMuted,
|
||||
kEnableRemoteApp,
|
||||
kEnableTelnet,
|
||||
kDisableCameraShake,
|
||||
kDisableCameraGyro,
|
||||
kLast // Sentinel.
|
||||
};
|
||||
|
||||
class Entry {
|
||||
public:
|
||||
enum class Type { kString, kInt, kFloat, kBool };
|
||||
Entry() = default;
|
||||
explicit Entry(const char* name) : name_(name) {}
|
||||
virtual auto GetType() const -> Type = 0;
|
||||
auto name() const -> const std::string& { return name_; }
|
||||
virtual auto FloatValue() const -> float;
|
||||
virtual auto StringValue() const -> std::string;
|
||||
virtual auto IntValue() const -> int;
|
||||
virtual auto BoolValue() const -> bool;
|
||||
virtual auto DefaultFloatValue() const -> float;
|
||||
virtual auto DefaultStringValue() const -> std::string;
|
||||
virtual auto DefaultIntValue() const -> int;
|
||||
virtual auto DefaultBoolValue() const -> bool;
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
};
|
||||
|
||||
static void Init();
|
||||
AppConfig();
|
||||
|
||||
// Given specific ids, returns resolved values (fastest access).
|
||||
auto Resolve(FloatID id) -> float;
|
||||
auto Resolve(StringID id) -> std::string;
|
||||
auto Resolve(IntID id) -> int;
|
||||
auto Resolve(BoolID id) -> bool;
|
||||
|
||||
// Given a name, returns an entry (or nullptr).
|
||||
// You should check the entry's type and request
|
||||
// the corresponding typed resolved value from it.
|
||||
auto GetEntry(const std::string& name) -> const Entry* {
|
||||
auto i = entries_by_name_.find(name);
|
||||
if (i == entries_by_name_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return i->second;
|
||||
}
|
||||
|
||||
auto entries_by_name() const -> const std::map<std::string, const Entry*>& {
|
||||
return entries_by_name_;
|
||||
}
|
||||
|
||||
private:
|
||||
class StringEntry;
|
||||
class FloatEntry;
|
||||
class IntEntry;
|
||||
class BoolEntry;
|
||||
template <typename T>
|
||||
void CompleteMap(const T& entry_map);
|
||||
void SetupEntries();
|
||||
std::map<std::string, const Entry*> entries_by_name_;
|
||||
std::map<FloatID, FloatEntry> float_entries_;
|
||||
std::map<IntID, IntEntry> int_entries_;
|
||||
std::map<StringID, StringEntry> string_entries_;
|
||||
std::map<BoolID, BoolEntry> bool_entries_;
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_APP_APP_CONFIG_H_
|
||||
12
src/ballistica/app/app_globals.cc
Normal file
12
src/ballistica/app/app_globals.cc
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/app/app_globals.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
AppGlobals::AppGlobals(int argc_in, char** argv_in)
|
||||
: argc{argc_in},
|
||||
argv{argv_in},
|
||||
main_thread_id{std::this_thread::get_id()} {}
|
||||
|
||||
} // namespace ballistica
|
||||
99
src/ballistica/app/app_globals.h
Normal file
99
src/ballistica/app/app_globals.h
Normal file
@ -0,0 +1,99 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_APP_APP_GLOBALS_H_
|
||||
#define BALLISTICA_APP_APP_GLOBALS_H_
|
||||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/networking/master_server_config.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// The first thing the engine does is allocate an instance of this as g_globals.
|
||||
// As much as possible, previously static/global values should be moved to here,
|
||||
// ideally as a temporary measure until they can be placed as non-static members
|
||||
// in the proper classes.
|
||||
// Any use of non-trivial global/static values such as class instances should be
|
||||
// avoided since it can introduce ambiguities during init and teardown.
|
||||
// For more explanation, see the 'Static and Global Variables' section in the
|
||||
// Google C++ Style Guide.
|
||||
class AppGlobals {
|
||||
public:
|
||||
AppGlobals(int argc, char** argv);
|
||||
|
||||
/// Program argument count (on applicable platforms).
|
||||
int argc{};
|
||||
|
||||
/// Program argument values (on applicable platforms).
|
||||
char** argv{};
|
||||
|
||||
std::map<std::string, NodeType*> node_types;
|
||||
std::map<int, NodeType*> node_types_by_id;
|
||||
std::map<std::string, NodeMessageType> node_message_types;
|
||||
std::vector<std::string> node_message_formats;
|
||||
std::string calced_blessing_hash;
|
||||
bool have_mods{};
|
||||
bool replay_open{};
|
||||
std::vector<Thread*> pausable_threads;
|
||||
TouchInput* touch_input{};
|
||||
std::string console_startup_messages;
|
||||
std::mutex log_mutex;
|
||||
std::string log;
|
||||
bool put_log{};
|
||||
bool log_full{};
|
||||
int master_server_source{1};
|
||||
int session_count{};
|
||||
bool shutting_down{};
|
||||
bool have_incentivized_ad{true};
|
||||
bool should_pause{};
|
||||
TelnetServer* telnet_server{};
|
||||
Console* console{};
|
||||
bool reset_vr_orientation{};
|
||||
bool user_ran_commands{};
|
||||
UIScale ui_scale{UIScale::kLarge};
|
||||
AccountType account_type{AccountType::kInvalid};
|
||||
bool remote_server_accepting_connections{true};
|
||||
std::string game_commands;
|
||||
std::string user_agent_string{"BA_USER_AGENT_UNSET (" BA_PLATFORM_STRING ")"};
|
||||
int return_value{};
|
||||
bool is_stdin_a_terminal{true};
|
||||
std::thread::id main_thread_id{};
|
||||
bool is_bootstrapped{};
|
||||
bool args_handled{};
|
||||
std::string user_config_dir;
|
||||
bool started_suicide{};
|
||||
|
||||
// Netplay testing.
|
||||
int buffer_time{1000 / 30};
|
||||
|
||||
// How often we send dynamics sync packets.
|
||||
int dynamics_sync_time{500};
|
||||
|
||||
// How many steps we sample for each bucket.
|
||||
int delay_samples{20};
|
||||
|
||||
bool vr_mode{g_buildconfig.vr_build()};
|
||||
// Temp dirty way to do some shutdown stuff (FIXME: move to an App method).
|
||||
void (*temp_cleanup_callback)() = nullptr;
|
||||
millisecs_t real_time{};
|
||||
millisecs_t last_real_time_ticks{};
|
||||
std::mutex real_time_mutex;
|
||||
std::mutex thread_name_map_mutex;
|
||||
std::map<std::thread::id, std::string> thread_name_map;
|
||||
std::string master_server_addr{BA_MASTER_SERVER_DEFAULT_ADDR};
|
||||
std::string master_server_fallback_addr{BA_MASTER_SERVER_FALLBACK_ADDR};
|
||||
#if BA_DEBUG_BUILD
|
||||
std::mutex object_list_mutex;
|
||||
Object* object_list_first{};
|
||||
int object_count{0};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_APP_APP_GLOBALS_H_
|
||||
21
src/ballistica/app/headless_app.cc
Normal file
21
src/ballistica/app/headless_app.cc
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
#if BA_HEADLESS_BUILD
|
||||
|
||||
#include "ballistica/app/headless_app.h"
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// We could technically use the vanilla App class here since we're not
|
||||
// changing anything.
|
||||
HeadlessApp::HeadlessApp(Thread* thread) : App(thread) {
|
||||
// NewThreadTimer(10, true, NewLambdaRunnable([this] {
|
||||
// assert(g_app);
|
||||
// g_app->RunEvents();
|
||||
// }));
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BA_HEADLESS_BUILD
|
||||
20
src/ballistica/app/headless_app.h
Normal file
20
src/ballistica/app/headless_app.h
Normal file
@ -0,0 +1,20 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_APP_HEADLESS_APP_H_
|
||||
#define BALLISTICA_APP_HEADLESS_APP_H_
|
||||
#if BA_HEADLESS_BUILD
|
||||
|
||||
#include "ballistica/app/app.h"
|
||||
#include "ballistica/core/thread.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class HeadlessApp : public App {
|
||||
public:
|
||||
explicit HeadlessApp(Thread* thread);
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BA_HEADLESS_BUILD
|
||||
#endif // BALLISTICA_APP_HEADLESS_APP_H_
|
||||
101
src/ballistica/app/stress_test.cc
Normal file
101
src/ballistica/app/stress_test.cc
Normal file
@ -0,0 +1,101 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
|
||||
#include "ballistica/app/stress_test.h"
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
#include "ballistica/input/input.h"
|
||||
#include "ballistica/platform/platform.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
void StressTest::Update() {
|
||||
assert(InMainThread());
|
||||
|
||||
// Handle a little misc stuff here.
|
||||
// If we're currently running stress-tests, update that stuff.
|
||||
if (stress_testing_ && g_input) {
|
||||
// Update our fake inputs to make our dudes run around.
|
||||
g_input->ProcessStressTesting(stress_test_player_count_);
|
||||
|
||||
// Every 10 seconds update our stress-test stats.
|
||||
millisecs_t t = GetRealTime();
|
||||
if (t - last_stress_test_update_time_ >= 10000) {
|
||||
if (stress_test_stats_file_ == nullptr) {
|
||||
assert(g_platform);
|
||||
std::string f_name =
|
||||
g_platform->GetUserPythonDirectory() + "/stress_test_stats.csv";
|
||||
stress_test_stats_file_ = g_platform->FOpen(f_name.c_str(), "wb");
|
||||
if (stress_test_stats_file_ != nullptr) {
|
||||
fprintf(stress_test_stats_file_,
|
||||
"time,averageFps,nodes,models,collide_models,textures,sounds,"
|
||||
"pssMem,sharedDirtyMem,privateDirtyMem\n");
|
||||
fflush(stress_test_stats_file_);
|
||||
if (g_buildconfig.ostype_android()) {
|
||||
// On android, let the OS know we've added or removed a file
|
||||
// (limit to android or we'll get an unimplemented warning).
|
||||
g_platform->AndroidRefreshFile(f_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stress_test_stats_file_ != nullptr) {
|
||||
// See how many frames we've rendered this past interval.
|
||||
int total_frames_rendered;
|
||||
if (g_graphics_server && g_graphics_server->renderer()) {
|
||||
total_frames_rendered =
|
||||
g_graphics_server->renderer()->total_frames_rendered();
|
||||
} else {
|
||||
total_frames_rendered = last_total_frames_rendered_;
|
||||
}
|
||||
float avg =
|
||||
static_cast<float>(total_frames_rendered
|
||||
- last_total_frames_rendered_)
|
||||
/ (static_cast<float>(t - last_stress_test_update_time_) / 1000.0f);
|
||||
last_total_frames_rendered_ = total_frames_rendered;
|
||||
uint32_t model_count = 0;
|
||||
uint32_t collide_model_count = 0;
|
||||
uint32_t texture_count = 0;
|
||||
uint32_t sound_count = 0;
|
||||
uint32_t node_count = 0;
|
||||
if (g_media) {
|
||||
model_count = g_media->total_model_count();
|
||||
collide_model_count = g_media->total_collide_model_count();
|
||||
texture_count = g_media->total_texture_count();
|
||||
sound_count = g_media->total_sound_count();
|
||||
}
|
||||
assert(g_game);
|
||||
std::string mem_usage = g_platform->GetMemUsageInfo();
|
||||
fprintf(stress_test_stats_file_, "%d,%.1f,%d,%d,%d,%d,%d,%s\n",
|
||||
static_cast_check_fit<int>(GetRealTime()), avg, node_count,
|
||||
model_count, collide_model_count, texture_count, sound_count,
|
||||
mem_usage.c_str());
|
||||
fflush(stress_test_stats_file_);
|
||||
}
|
||||
last_stress_test_update_time_ = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void StressTest::Set(bool enable, int player_count) {
|
||||
assert(InMainThread());
|
||||
bool was_stress_testing = stress_testing_;
|
||||
stress_testing_ = enable;
|
||||
stress_test_player_count_ = player_count;
|
||||
|
||||
// If we're turning on, reset our intervals and things.
|
||||
if (!was_stress_testing && stress_testing_) {
|
||||
// So our first sample is 1 interval from now.
|
||||
last_stress_test_update_time_ = GetRealTime();
|
||||
|
||||
// Reset our frames-rendered tally.
|
||||
if (g_graphics_server && g_graphics_server->renderer()) {
|
||||
last_total_frames_rendered_ =
|
||||
g_graphics_server->renderer()->total_frames_rendered();
|
||||
} else {
|
||||
// Assume zero if there's no graphics yet.
|
||||
last_total_frames_rendered_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace ballistica
|
||||
29
src/ballistica/app/stress_test.h
Normal file
29
src/ballistica/app/stress_test.h
Normal file
@ -0,0 +1,29 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_APP_STRESS_TEST_H_
|
||||
#define BALLISTICA_APP_STRESS_TEST_H_
|
||||
|
||||
#include "ballistica/ballistica.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
// FIXME: This is not wired up; I just moved things here from App.
|
||||
class StressTest {
|
||||
public:
|
||||
// This used to be a SetStressTesting() call in App.
|
||||
void Set(bool enable, int player_count);
|
||||
|
||||
// This used to get run from RunEvents() in App.
|
||||
void Update();
|
||||
|
||||
private:
|
||||
FILE* stress_test_stats_file_{};
|
||||
millisecs_t last_stress_test_update_time_{};
|
||||
bool stress_testing_{};
|
||||
int stress_test_player_count_{8};
|
||||
int last_total_frames_rendered_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BALLISTICA_APP_STRESS_TEST_H_
|
||||
110
src/ballistica/app/vr_app.cc
Normal file
110
src/ballistica/app/vr_app.cc
Normal file
@ -0,0 +1,110 @@
|
||||
// Copyright (c) 2011-2020 Eric Froemling
|
||||
#if BA_VR_BUILD
|
||||
|
||||
#include "ballistica/app/vr_app.h"
|
||||
|
||||
#include "ballistica/game/game.h"
|
||||
#include "ballistica/graphics/graphics_server.h"
|
||||
#include "ballistica/graphics/renderer.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
VRApp::VRApp(Thread* thread) : App(thread) {}
|
||||
|
||||
void VRApp::PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state) {
|
||||
PushCall([this, state] {
|
||||
// Convert this to a full hands state, adding in some simple elbow
|
||||
// positioning of our own and left/right.
|
||||
VRHandsState s;
|
||||
s.l.tx = -0.2f;
|
||||
s.l.ty = -0.2f;
|
||||
s.l.tz = -0.3f;
|
||||
|
||||
// Hmm; for now lets always assign this as right hand even when its in
|
||||
// left-handed mode to keep things simple on the back-end. Can change later
|
||||
// if there's a downside to that.
|
||||
s.r.type = VRHandType::kDaydreamRemote;
|
||||
s.r.tx = 0.2f;
|
||||
s.r.ty = -0.2f;
|
||||
s.r.tz = -0.3f;
|
||||
s.r.yaw = state.r0;
|
||||
s.r.pitch = state.r1;
|
||||
s.r.roll = state.r2;
|
||||
VRSetHands(s);
|
||||
});
|
||||
}
|
||||
|
||||
void VRApp::VRSetDrawDimensions(int w, int h) {
|
||||
g_graphics_server->VideoResize(w, h);
|
||||
}
|
||||
|
||||
void VRApp::VRPreDraw() {
|
||||
if (!g_graphics_server || !g_graphics_server->renderer()) {
|
||||
return;
|
||||
}
|
||||
assert(InMainThread());
|
||||
if (FrameDef* frame_def = g_graphics_server->GetRenderFrameDef()) {
|
||||
// Note: this could be part of PreprocessRenderFrameDef but
|
||||
// the non-vr path needs it to be separate since preprocess doesn't
|
||||
// happen sometimes. Should probably clean that up.
|
||||
g_graphics_server->RunFrameDefMeshUpdates(frame_def);
|
||||
|
||||
// store this for the duration of this frame
|
||||
vr_render_frame_def_ = frame_def;
|
||||
g_graphics_server->PreprocessRenderFrameDef(frame_def);
|
||||
}
|
||||
}
|
||||
|
||||
void VRApp::VRPostDraw() {
|
||||
assert(InMainThread());
|
||||
if (!g_graphics_server || !g_graphics_server->renderer()) {
|
||||
return;
|
||||
}
|
||||
if (vr_render_frame_def_) {
|
||||
g_graphics_server->FinishRenderFrameDef(vr_render_frame_def_);
|
||||
vr_render_frame_def_ = nullptr;
|
||||
}
|
||||
RunRenderUpkeepCycle();
|
||||
}
|
||||
|
||||
void VRApp::VRSetHead(float tx, float ty, float tz, float yaw, float pitch,
|
||||
float roll) {
|
||||
assert(InMainThread());
|
||||
Renderer* renderer = g_graphics_server->renderer();
|
||||
if (renderer == nullptr) return;
|
||||
renderer->VRSetHead(tx, ty, tz, yaw, pitch, roll);
|
||||
}
|
||||
|
||||
void VRApp::VRSetHands(const VRHandsState& state) {
|
||||
assert(InMainThread());
|
||||
|
||||
// Pass this along to the renderer (in this same thread) for drawing
|
||||
// (so hands can be drawn at their absolute most up-to-date positions, etc).
|
||||
Renderer* renderer = g_graphics_server->renderer();
|
||||
if (renderer == nullptr) return;
|
||||
renderer->VRSetHands(state);
|
||||
|
||||
// ALSO ship it off to the game/ui thread to actually handle input from it.
|
||||
g_game->PushVRHandsState(state);
|
||||
}
|
||||
|
||||
void VRApp::VRDrawEye(int eye, float yaw, float pitch, float roll, float tan_l,
|
||||
float tan_r, float tan_b, float tan_t, float eye_x,
|
||||
float eye_y, float eye_z, int viewport_x,
|
||||
int viewport_y) {
|
||||
if (!g_graphics_server || !g_graphics_server->renderer()) {
|
||||
return;
|
||||
}
|
||||
assert(InMainThread());
|
||||
if (vr_render_frame_def_) {
|
||||
// set up VR eye stuff...
|
||||
Renderer* renderer = g_graphics_server->renderer();
|
||||
renderer->VRSetEye(eye, yaw, pitch, roll, tan_l, tan_r, tan_b, tan_t, eye_x,
|
||||
eye_y, eye_z, viewport_x, viewport_y);
|
||||
g_graphics_server->DrawRenderFrameDef(vr_render_frame_def_);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BA_VR_BUILD
|
||||
48
src/ballistica/app/vr_app.h
Normal file
48
src/ballistica/app/vr_app.h
Normal file
@ -0,0 +1,48 @@
|
||||
// Released under the MIT License. See LICENSE for details.
|
||||
|
||||
#ifndef BALLISTICA_APP_VR_APP_H_
|
||||
#define BALLISTICA_APP_VR_APP_H_
|
||||
|
||||
#if BA_VR_BUILD
|
||||
|
||||
#include "ballistica/app/app.h"
|
||||
|
||||
namespace ballistica {
|
||||
|
||||
class VRApp : public App {
|
||||
public:
|
||||
/// For passing in state of Daydream remote (and maybe gear vr?..).
|
||||
struct VRSimpleRemoteState {
|
||||
bool right_handed = true;
|
||||
float r0 = 0.0f;
|
||||
float r1 = 0.0f;
|
||||
float r2 = 0.0f;
|
||||
};
|
||||
|
||||
/// Return g_app as a VRApp. (assumes it actually is one).
|
||||
static VRApp* get() {
|
||||
assert(g_app != nullptr);
|
||||
assert(dynamic_cast<VRApp*>(g_app) == static_cast<VRApp*>(g_app));
|
||||
return static_cast<VRApp*>(g_app);
|
||||
}
|
||||
|
||||
explicit VRApp(Thread* thread);
|
||||
void PushVRSimpleRemoteStateCall(const VRSimpleRemoteState& state);
|
||||
void VRSetDrawDimensions(int w, int h);
|
||||
void VRPreDraw();
|
||||
void VRPostDraw();
|
||||
void VRSetHead(float tx, float ty, float tz, float yaw, float pitch,
|
||||
float roll);
|
||||
void VRSetHands(const VRHandsState& state);
|
||||
void VRDrawEye(int eye, float yaw, float pitch, float roll, float tan_l,
|
||||
float tan_r, float tan_b, float tan_t, float eye_x,
|
||||
float eye_y, float eye_z, int viewport_x, int viewport_y);
|
||||
|
||||
private:
|
||||
FrameDef* vr_render_frame_def_{};
|
||||
};
|
||||
|
||||
} // namespace ballistica
|
||||
|
||||
#endif // BA_VR_BUILD
|
||||
#endif // BALLISTICA_APP_VR_APP_H_
|
||||
Loading…
x
Reference in New Issue
Block a user