ballistica/src/ballistica/ballistica.cc
Eric Froemling 4f9af6d8cd
docs fix
2022-07-20 13:08:44 -07:00

312 lines
11 KiB
C++

// Released under the MIT License. See LICENSE for details.
#include "ballistica/ballistica.h"
#include <map>
#include "ballistica/app/app.h"
#include "ballistica/audio/audio_server.h"
#include "ballistica/core/fatal_error.h"
#include "ballistica/core/logging.h"
#include "ballistica/core/thread.h"
#include "ballistica/dynamics/bg/bg_dynamics_server.h"
#include "ballistica/game/account.h"
#include "ballistica/graphics/graphics_server.h"
#include "ballistica/media/media_server.h"
#include "ballistica/networking/network_write_module.h"
#include "ballistica/platform/platform.h"
#include "ballistica/python/python.h"
#include "ballistica/scene/scene.h"
namespace ballistica {
// These are set automatically via script; don't modify them here.
const int kAppBuildNumber = 20662;
const char* kAppVersion = "1.7.5";
// Our standalone globals.
// These are separated out for easy access.
// Everything else should go into AppGlobals (or more ideally into a class).
int g_early_log_writes{10};
Thread* g_main_thread{};
AppGlobals* g_app_globals{};
AppConfig* g_app_config{};
AppInternal* g_app_internal{};
App* g_app{};
Account* g_account{};
Game* g_game{};
BGDynamics* g_bg_dynamics{};
BGDynamicsServer* g_bg_dynamics_server{};
Platform* g_platform{};
Utils* g_utils{};
UI* g_ui{};
Graphics* g_graphics{};
Python* g_python{};
Input* g_input{};
GraphicsServer* g_graphics_server{};
Media* g_media{};
Audio* g_audio{};
MediaServer* g_media_server{};
AudioServer* g_audio_server{};
StdInputModule* g_std_input_module{};
NetworkReader* g_network_reader{};
Networking* g_networking{};
NetworkWriteModule* g_network_write_module{};
TextGraphics* g_text_graphics{};
// Basic overview of our bootstrapping process:
// 1: All threads and globals are created and provisioned. Everything above
// should exist at the end of this step (if it is going to exist).
// Threads should not be talking to each other yet at this point.
// 2: The system is set in motion. Game thread is told to load/apply the config.
// This event kicks off an initial-screen-creation message sent to the
// graphics-server thread. Other systems are informed that bootstrapping
// is complete and that they are free to talk to each other. Initial
// input-devices are added, media loads can begin (at least ones not
// dependent on the screen/renderer), etc.
// 3: The initial screen is created on the graphics-server thread in response
// to the message sent from the game thread. A completion notice is sent
// back to the game thread when done.
// 4: Back on the game thread, any renderer-dependent media-loads/etc. can begin
// and lastly the initial game session is kicked off.
auto BallisticaMain(int argc, char** argv) -> int {
try {
// Even at the absolute start of execution we should be able to
// phone home on errors. Set env var BA_CRASH_TEST=1 to test this.
if (const char* crashenv = getenv("BA_CRASH_TEST")) {
if (!strcmp(crashenv, "1")) {
FatalError("Fatal-Error-Test");
}
}
// -------------------------------------------------------------------------
// Phase 1: Create and provision all globals.
// -------------------------------------------------------------------------
g_app_globals = new AppGlobals(argc, argv);
g_app_internal = CreateAppInternal();
g_platform = Platform::Create();
g_account = new Account();
g_utils = new Utils();
Scene::Init();
// Create a Thread wrapper around the current (main) thread.
g_main_thread = new Thread(ThreadIdentifier::kMain, ThreadType::kMain);
// Spin up g_app.
g_platform->CreateApp();
// Spin up our other standard threads.
auto* media_thread = new Thread(ThreadIdentifier::kMedia);
g_app_globals->pausable_threads.push_back(media_thread);
auto* audio_thread = new Thread(ThreadIdentifier::kAudio);
g_app_globals->pausable_threads.push_back(audio_thread);
auto* game_thread = new Thread(ThreadIdentifier::kGame);
g_app_globals->pausable_threads.push_back(game_thread);
auto* network_write_thread = new Thread(ThreadIdentifier::kNetworkWrite);
g_app_globals->pausable_threads.push_back(network_write_thread);
// And add our other standard modules to them.
game_thread->AddModule<Game>();
network_write_thread->AddModule<NetworkWriteModule>();
media_thread->AddModule<MediaServer>();
g_main_thread->AddModule<GraphicsServer>();
audio_thread->AddModule<AudioServer>();
// Now let the platform spin up any other threads/modules it uses.
// (bg-dynamics in non-headless builds, stdin/stdout where applicable, etc.)
g_platform->CreateAuxiliaryModules();
// Ok at this point we can be considered up-and-running.
g_app_globals->is_bootstrapped = true;
// -------------------------------------------------------------------------
// Phase 2: Set things in motion.
// -------------------------------------------------------------------------
// Let the app and platform do whatever else it wants here such as adding
// initial input devices/etc.
g_app->OnBootstrapComplete();
g_platform->OnBootstrapComplete();
// Ok; now that we're bootstrapped, tell the game thread to read and apply
// the config which should kick off the real action.
g_game->PushApplyConfigCall();
// -------------------------------------------------------------------------
// Phase 3/4: Create a screen and/or kick off game (in other threads).
// -------------------------------------------------------------------------
if (g_app->ManagesEventLoop()) {
// On our event-loop-managing platforms we now simply sit in our event
// loop until the app is quit.
g_main_thread->RunEventLoop(false);
} else {
// In this case we'll now simply return and let the OS feed us events
// until the app quits.
// However, we may need to 'prime the pump' first. For instance,
// if the main thread event loop is driven by frame draws, it may need to
// manually pump events until drawing begins (otherwise it will never
// process the 'create-screen' event and wind up deadlocked).
g_app->PrimeEventPump();
}
} catch (const std::exception& exc) {
std::string error_msg =
std::string("Unhandled exception in BallisticaMain(): ") + exc.what();
// Exiting the app via an exception tends to trigger crash reports
// on various platforms. If it seems we're not on an official live
// build then we'd rather just exit cleanly with an error code and avoid
// polluting crash report logs from dev builds.
FatalError::ReportFatalError(error_msg, true);
bool exit_cleanly = !IsUnmodifiedBlessedBuild();
bool handled = FatalError::HandleFatalError(exit_cleanly, true);
// Do the default thing if it's not been handled.
if (!handled) {
if (exit_cleanly) {
exit(1);
} else {
throw;
}
}
}
g_platform->WillExitMain(false);
return g_app_globals->return_value;
}
auto GetRealTime() -> millisecs_t {
millisecs_t t = g_platform->GetTicks();
// If we're at a different time than our last query, do our funky math.
if (t != g_app_globals->last_real_time_ticks) {
std::lock_guard<std::mutex> lock(g_app_globals->real_time_mutex);
millisecs_t passed = t - g_app_globals->last_real_time_ticks;
// GetTicks() is supposed to be monotonic, but I've seen 'passed'
// equal -1 even when it is using std::chrono::steady_clock. Let's do
// our own filtering here to make 100% sure we don't go backwards.
if (passed < 0) {
passed = 0;
} else {
// Very large times-passed probably means we went to sleep or something;
// clamp to a reasonable value.
if (passed > 250) {
passed = 250;
}
}
g_app_globals->real_time += passed;
g_app_globals->last_real_time_ticks = t;
}
return g_app_globals->real_time;
}
auto FatalError(const std::string& message) -> void {
FatalError::ReportFatalError(message, false);
bool exit_cleanly = !IsUnmodifiedBlessedBuild();
bool handled = FatalError::HandleFatalError(exit_cleanly, false);
assert(handled);
}
auto GetAppInstanceUUID() -> const std::string& {
static std::string session_id;
static bool have_session_id = false;
if (!have_session_id) {
if (g_python) {
Python::ScopedInterpreterLock gil;
auto uuid = g_python->obj(Python::ObjID::kUUIDStrCall).Call();
if (uuid.exists()) {
session_id = uuid.ValueAsString().c_str();
have_session_id = true;
}
}
if (!have_session_id) {
// As an emergency fallback simply use a single random number.
Log("WARNING: GetSessionUUID() using rand fallback.");
srand(static_cast<unsigned int>(
Platform::GetCurrentMilliseconds())); // NOLINT
session_id = std::to_string(static_cast<uint32_t>(rand())); // NOLINT
have_session_id = true;
}
if (session_id.size() >= 100) {
Log("WARNING: session id longer than it should be.");
}
}
return session_id;
}
auto InGameThread() -> bool {
return (g_game && g_game->thread()->IsCurrent());
}
auto InMainThread() -> bool {
return (g_app_globals
&& std::this_thread::get_id() == g_app_globals->main_thread_id);
}
auto InGraphicsThread() -> bool {
return (g_graphics_server && g_graphics_server->thread()->IsCurrent());
}
auto InAudioThread() -> bool {
return (g_audio_server && g_audio_server->thread()->IsCurrent());
}
auto InBGDynamicsThread() -> bool {
return (g_bg_dynamics_server && g_bg_dynamics_server->thread()->IsCurrent());
}
auto InMediaThread() -> bool {
return (g_media_server && g_media_server->thread()->IsCurrent());
}
auto InNetworkWriteThread() -> bool {
return (g_network_write_module
&& g_network_write_module->thread()->IsCurrent());
}
auto GetUIScale() -> UIScale { return g_app_globals->ui_scale; }
void Log(const std::string& msg, bool to_stdout, bool to_server) {
Logging::Log(msg, to_stdout, to_server);
}
auto IsVRMode() -> bool { return g_app_globals->vr_mode; }
auto IsStdinATerminal() -> bool { return g_app_globals->is_stdin_a_terminal; }
void ScreenMessage(const std::string& s, const Vector3f& color) {
if (g_game) {
g_game->PushScreenMessage(s, color);
} else {
Log("ScreenMessage before g_game init (will be lost): '" + s + "'");
}
}
void ScreenMessage(const std::string& msg) {
ScreenMessage(msg, {1.0f, 1.0f, 1.0f});
}
auto GetCurrentThreadName() -> std::string {
return Thread::GetCurrentThreadName();
}
auto IsBootstrapped() -> bool { return g_app_globals->is_bootstrapped; }
// Used by our built-in exception type.
void SetPythonException(PyExcType python_type, const char* description) {
Python::SetPythonException(python_type, description);
}
} // namespace ballistica
// If desired, define main() in the global namespace.
#if BA_DEFINE_MAIN
auto main(int argc, char** argv) -> int {
return ballistica::BallisticaMain(argc, argv);
}
#endif