/* ______ ______ ______ ______ ______ __ __ ______ ______ __ __ __ __ ______ __ __ _____
/\ ___\ /\ __ \ /\ == \/\__ _\/\ ___\/\_\_\_\ /\ ___\ /\ __ \ /\ "-./ \ /\ "-./ \ /\ __ \ /\ "-.\ \ /\ __-.
\ \ \____\ \ \/\ \\ \ __<\/_/\ \/\ \ __\\/_/\_\/_ \ \ \____\ \ \/\ \\ \ \-./\ \\ \ \-./\ \\ \ __ \\ \ \-. \\ \ \/\ \
\ \_____\\ \_____\\ \_\ \_\ \ \_\ \ \_____\/\_\/\_\ \ \_____\\ \_____\\ \_\ \ \_\\ \_\ \ \_\\ \_\ \_\\ \_\\"\_\\ \____-
\/_____/ \/_____/ \/_/ /_/ \/_/ \/_____/\/_/\/_/ \/_____/ \/_____/ \/_/ \/_/ \/_/ \/_/ \/_/\/_/ \/_/ \/_/ \/____/
______ ______ __ __ __ __ __ __ __ __ __ ______ __ __ ______ ______ ______ __ ______ ______ ______
/\ ___\ /\ __ \ /\ "-./ \ /\ "-./ \ /\ \/\ \ /\ "-.\ \ /\ \ /\__ _\/\ \_\ \ /\ == \/\ == \ /\ __ \ /\ \ /\ ___\ /\ ___\ /\__ _\
\ \ \____\ \ \/\ \\ \ \-./\ \\ \ \-./\ \\ \ \_\ \\ \ \-. \\ \ \\/_/\ \/\ \____ \ \ \ _-/\ \ __< \ \ \/\ \ _\_\ \\ \ __\ \ \ \____\/_/\ \/
\ \_____\\ \_____\\ \_\ \ \_\\ \_\ \ \_\\ \_____\\ \_\\"\_\\ \_\ \ \_\ \/\_____\ \ \_\ \ \_\ \_\\ \_____\/\_____\\ \_____\\ \_____\ \ \_\
\/_____/ \/_____/ \/_/ \/_/ \/_/ \/_/ \/_____/ \/_/ \/_/ \/_/ \/_/ \/_____/ \/_/ \/_/ /_/ \/_____/\/_____/ \/_____/ \/_____/ \/_/
/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\/////\\\\\*/
///
/// Main driver implementation of the Retro Terrain Engine.
/// Data Realms, LLC - http://www.datarealms.com
/// Cortex Command Community Project - https://github.com/cortex-command-community
/// Cortex Command Community Project Discord - https://discord.gg/TSU6StNQUG
///
#include "allegro.h"
#include "SDL.h"
#include "GUI.h"
#include "GUIInputWrapper.h"
#include "AllegroScreen.h"
#include "AllegroBitmap.h"
#include "MainMenuGUI.h"
#include "ScenarioGUI.h"
#include "PauseMenuGUI.h"
#include "TitleScreen.h"
#include "LoadingScreen.h"
#include "MenuMan.h"
#include "ConsoleMan.h"
#include "SettingsMan.h"
#include "PresetMan.h"
#include "UInputMan.h"
#include "PerformanceMan.h"
#include "FrameMan.h"
#include "MetaMan.h"
#include "WindowMan.h"
#include "NetworkServer.h"
#include "NetworkClient.h"
#include "CameraMan.h"
#include "ActivityMan.h"
#include "PrimitiveMan.h"
#include "ThreadMan.h"
#include "LuaMan.h"
#include "MusicMan.h"
#include "System.h"
#include "tracy/Tracy.hpp"
extern "C" {
FILE __iob_func[3] = {*stdin, *stdout, *stderr};
}
using namespace RTE;
///
/// Initializes all the essential managers.
///
void InitializeManagers() {
ThreadMan::Construct();
TimerMan::Construct();
PresetMan::Construct();
SettingsMan::Construct();
WindowMan::Construct();
LuaMan::Construct();
NetworkServer::Construct();
NetworkClient::Construct();
FrameMan::Construct();
PerformanceMan::Construct();
PostProcessMan::Construct();
PrimitiveMan::Construct();
AudioMan::Construct();
GUISound::Construct();
MusicMan::Construct();
UInputMan::Construct();
ConsoleMan::Construct();
SceneMan::Construct();
MovableMan::Construct();
MetaMan::Construct();
MenuMan::Construct();
CameraMan::Construct();
ActivityMan::Construct();
LoadingScreen::Construct();
g_ThreadMan.Initialize();
g_SettingsMan.Initialize();
g_WindowMan.Initialize();
g_LuaMan.Initialize();
g_NetworkServer.Initialize();
g_NetworkClient.Initialize();
g_TimerMan.Initialize();
g_FrameMan.Initialize();
g_PostProcessMan.Initialize();
g_PerformanceMan.Initialize();
if (g_AudioMan.Initialize()) {
g_GUISound.Initialize();
g_MusicMan.Initialize();
}
g_UInputMan.Initialize();
g_ConsoleMan.Initialize();
g_SceneMan.Initialize();
g_MovableMan.Initialize();
g_MetaMan.Initialize();
g_MenuMan.Initialize();
// Overwrite Settings.ini after all the managers are created to fully populate the file. Up until this moment Settings.ini is populated only with minimal required properties to run.
// If Settings.ini already exists and is fully populated, this will deal with overwriting it to apply any overrides performed by the managers at boot (e.g resolution validation).
if (g_SettingsMan.SettingsNeedOverwrite()) {
g_SettingsMan.UpdateSettingsFile();
}
}
///
/// Destroys all the managers and frees all loaded data before termination.
///
void DestroyManagers() {
g_NetworkClient.Destroy();
g_NetworkServer.Destroy();
g_MetaMan.Destroy();
g_PerformanceMan.Destroy();
g_MovableMan.Destroy();
g_SceneMan.Destroy();
g_ActivityMan.Destroy();
g_GUISound.Destroy();
g_AudioMan.Destroy();
g_MusicMan.Destroy();
g_PresetMan.Destroy();
g_UInputMan.Destroy();
g_PostProcessMan.Destroy();
g_FrameMan.Destroy();
g_TimerMan.Destroy();
g_LuaMan.Destroy();
ContentFile::FreeAllLoaded();
g_ConsoleMan.Destroy();
g_WindowMan.Destroy();
#ifdef DEBUG_BUILD
Entity::ClassInfo::DumpPoolMemoryInfo(Writer("MemCleanupInfo.txt"));
#endif
}
///
/// Command-line argument handling.
///
/// Argument count.
/// Argument values.
void HandleMainArgs(int argCount, char** argValue) {
// Discard the first argument because it's always the executable path/name
argCount--;
argValue++;
if (argCount == 0) {
return;
}
bool launchModeSet = false;
bool singleModuleSet = false;
for (int i = 0; i < argCount;) {
std::string currentArg = argValue[i];
bool lastArg = i + 1 == argCount;
if (currentArg == "-cout") {
System::EnableLoggingToCLI();
}
if (currentArg == "-ext-validate") {
System::EnableExternalModuleValidationMode();
}
if (!lastArg && !singleModuleSet && currentArg == "-module") {
std::string moduleToLoad = argValue[++i];
if (moduleToLoad.find(System::GetModulePackageExtension()) == moduleToLoad.length() - System::GetModulePackageExtension().length()) {
g_PresetMan.SetSingleModuleToLoad(moduleToLoad);
singleModuleSet = true;
}
}
if (!launchModeSet) {
if (currentArg == "-server") {
g_NetworkServer.EnableServerMode();
g_NetworkServer.SetServerPort(!lastArg ? argValue[++i] : "8000");
launchModeSet = true;
} else if (!lastArg && currentArg == "-editor") {
g_ActivityMan.SetEditorToLaunch(argValue[++i]);
launchModeSet = true;
}
}
++i;
}
if (launchModeSet) {
g_SettingsMan.SetSkipIntro(true);
}
}
///
/// Polls the SDL event queue and passes events to be handled by the relevant managers.
///
void PollSDLEvents() {
SDL_Event sdlEvent;
while (SDL_PollEvent(&sdlEvent)) {
switch (sdlEvent.type) {
case SDL_QUIT:
System::SetQuit(true);
return;
case SDL_WINDOWEVENT:
if (sdlEvent.window.event == SDL_WINDOWEVENT_CLOSE) {
System::SetQuit(true);
return;
}
g_WindowMan.QueueWindowEvent(sdlEvent);
break;
case SDL_KEYUP:
case SDL_KEYDOWN:
case SDL_TEXTINPUT:
case SDL_MOUSEMOTION:
case SDL_MOUSEBUTTONUP:
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEWHEEL:
case SDL_CONTROLLERAXISMOTION:
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
case SDL_JOYAXISMOTION:
case SDL_JOYBUTTONDOWN:
case SDL_JOYBUTTONUP:
case SDL_JOYDEVICEADDED:
case SDL_JOYDEVICEREMOVED:
g_UInputMan.QueueInputEvent(sdlEvent);
break;
default:
break;
}
}
}
///
/// Game menus loop.
///
void RunMenuLoop() {
g_UInputMan.DisableKeys(false);
g_UInputMan.TrapMousePos(false);
while (!System::IsSetToQuit()) {
g_WindowMan.ClearRenderer();
PollSDLEvents();
g_WindowMan.Update();
g_UInputMan.Update();
g_TimerMan.Update();
g_TimerMan.UpdateSim();
g_AudioMan.Update();
g_MusicMan.Update();
if (g_WindowMan.ResolutionChanged()) {
g_MenuMan.Reinitialize();
g_ConsoleMan.Destroy();
g_ConsoleMan.Initialize();
g_LoadingScreen.CreateLoadingSplash();
g_WindowMan.CompleteResolutionChange();
}
if (g_MenuMan.Update()) {
break;
}
g_ConsoleMan.Update();
g_MenuMan.Draw();
g_ConsoleMan.Draw(g_FrameMan.GetBackBuffer32());
g_WindowMan.UploadFrame();
}
}
///
/// Game simulation loop.
///
void RunGameLoop() {
if (System::IsSetToQuit()) {
return;
}
g_TimerMan.PauseSim(false);
if (g_ActivityMan.ActivitySetToRestart()) {
g_LoadingScreen.DrawLoadingSplash();
g_WindowMan.UploadFrame();
if (!g_ActivityMan.RestartActivity()) {
// This doesn't work.
// Somewhat related to https://github.com/cortex-command-community/Cortex-Command-Community-Project-Source/issues/472
// Deal with later.
// g_MenuMan.GetTitleScreen()->SetTitleTransitionState(TitleScreen::TitleTransition::ScrollingFadeIn);
}
}
long long updateStartTime = 0;
long long updateTotalTime = 0;
long long updateEndAndDrawStartTime = 0;
long long drawStartTime = 0;
long long drawTotalTime = 0;
while (!System::IsSetToQuit()) {
bool serverUpdated = false;
updateStartTime = g_TimerMan.GetAbsoluteTime();
PollSDLEvents();
g_WindowMan.Update();
g_WindowMan.ClearRenderer();
g_TimerMan.Update();
// Simulation update, as many times as the fixed update step allows in the span since last frame draw.
while (g_TimerMan.TimeForSimUpdate()) {
ZoneScopedN("Simulation Update");
serverUpdated = false;
g_PerformanceMan.NewPerformanceSample();
g_PerformanceMan.UpdateMSPSU();
g_TimerMan.UpdateSim();
g_PerformanceMan.StartPerformanceMeasurement(PerformanceMan::SimTotal);
g_LuaMan.Update();
g_UInputMan.Update();
// It is vital that server is updated after input manager but before activity because input manager will clear received pressed and released events on next update.
if (g_NetworkServer.IsServerModeEnabled()) {
g_NetworkServer.Update(true);
serverUpdated = true;
}
g_FrameMan.Update();
g_MovableMan.CompleteQueuedMOIDDrawings();
g_ConsoleMan.Update();
g_ActivityMan.Update();
if (g_SceneMan.GetScene()) {
g_SceneMan.GetScene()->Update();
}
g_LuaMan.ClearScriptTimings();
g_MovableMan.Update();
g_PerformanceMan.UpdateSortedScriptTimings(g_LuaMan.GetScriptTimings());
g_AudioMan.Update();
g_MusicMan.Update();
g_ActivityMan.LateUpdateGlobalScripts();
// This is to support hot reloading entities in SceneEditorGUI. It's a bit hacky to put it in Main like this, but PresetMan has no update in which to clear the value, and I didn't want to set up a listener for the job.
// It's in this spot to allow it to be set by UInputMan update and ConsoleMan update, and read from ActivityMan update.
g_PresetMan.ClearReloadEntityPresetCalledThisUpdate();
g_PerformanceMan.StopPerformanceMeasurement(PerformanceMan::SimTotal);
if (!g_ActivityMan.IsInActivity()) {
g_TimerMan.PauseSim(true);
if (!g_ActivityMan.ActivitySetToRestart()) {
g_MenuMan.HandleTransitionIntoMenuLoop();
RunMenuLoop();
}
}
if (g_ActivityMan.ActivitySetToRestart()) {
g_LoadingScreen.DrawLoadingSplash();
g_WindowMan.UploadFrame();
if (!g_ActivityMan.RestartActivity()) {
break;
}
}
if (g_ActivityMan.ActivitySetToResume()) {
g_ActivityMan.ResumeActivity();
g_PerformanceMan.ResetSimUpdateTimer();
updateStartTime = g_TimerMan.GetAbsoluteTime();
}
}
if (g_NetworkServer.IsServerModeEnabled()) {
// Pause sim while we're waiting for scene transmission or scene will start changing before clients receive them and those changes will be lost.
g_TimerMan.PauseSim(!(g_NetworkServer.ReadyForSimulation() && g_ActivityMan.IsInActivity()));
if (!serverUpdated) {
g_NetworkServer.Update();
}
if (g_NetworkServer.GetServerSimSleepWhenIdle()) {
long long ticksToSleep = g_TimerMan.GetTimeToSleep();
if (ticksToSleep > 0) {
double secsToSleep = static_cast(ticksToSleep) / static_cast(g_TimerMan.GetTicksPerSecond());
long long milisToSleep = static_cast(secsToSleep) * 1000;
std::this_thread::sleep_for(std::chrono::milliseconds(milisToSleep));
}
}
}
updateEndAndDrawStartTime = g_TimerMan.GetAbsoluteTime();
updateTotalTime = updateEndAndDrawStartTime - updateStartTime;
drawStartTime = updateEndAndDrawStartTime;
g_FrameMan.Draw();
g_WindowMan.DrawPostProcessBuffer();
g_WindowMan.UploadFrame();
drawTotalTime = g_TimerMan.GetAbsoluteTime() - drawStartTime;
g_PerformanceMan.UpdateMSPF(updateTotalTime, drawTotalTime);
}
}
///
/// Self-invoking lambda that installs exception handlers before Main is executed.
///
static const bool RTESetExceptionHandlers = []() {
RTEError::SetExceptionHandlers();
return true;
}();
///
/// Implementation of the main function.
///
int main(int argc, char** argv) {
install_allegro(SYSTEM_NONE, &errno, std::atexit);
loadpng_init();
SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_TIMER);
#if SDL_MINOR_VERSION > 22
SDL_SetHint(SDL_HINT_MOUSE_AUTO_CAPTURE, "0");
#endif
SDL_ShowCursor(SDL_DISABLE);
SDL_SetHint("SDL_ALLOW_TOPMOST", "0");
if (std::filesystem::exists("Base.rte/gamecontrollerdb.txt")) {
SDL_GameControllerAddMappingsFromFile("Base.rte/gamecontrollerdb.txt");
}
#ifdef WIN32
// Stops framespiking from our child threads being sat on for too long
// TODO: use a better thread system that'll do what we want ASAP instead of letting the OS schedule all over us
// Disabled for now because windows is great and this means when the game lags out it freezes the entire computer. Which we wouldn't expect with anything but REALTIME priority.
// Because apparently high priority class is preferred over "processing mouse input"?!
// SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
#endif // WIN32
// argv[0] actually unreliable for exe path and name, because of course, why would it be, why would anything be simple and make sense.
// Just use it anyway until some dumb edge case pops up and it becomes a problem.
System::Initialize(argv[0]);
SeedRNG();
InitializeManagers();
HandleMainArgs(argc, argv);
if (g_NetworkServer.IsServerModeEnabled()) {
SDL_ShowCursor(SDL_ENABLE);
}
g_PresetMan.LoadAllDataModules();
if (!System::IsInExternalModuleValidationMode()) {
// Load the different input device icons. This can't be done during UInputMan::Create() because the icon presets don't exist so we need to do this after modules are loaded.
g_UInputMan.LoadDeviceIcons();
if (g_ConsoleMan.LoadWarningsExist()) {
g_ConsoleMan.PrintString("WARNING: Encountered non-fatal errors during module loading!\nSee \"LogLoadingWarning.txt\" for information.");
g_ConsoleMan.SaveLoadWarningLog("LogLoadingWarning.txt");
// Open the console so the user is aware there are loading warnings.
g_ConsoleMan.SetEnabled(true);
} else {
// Delete an existing log if there are no warnings so there's less junk in the root folder.
if (std::filesystem::exists(System::GetWorkingDirectory() + "LogLoadingWarning.txt")) {
std::remove("LogLoadingWarning.txt");
}
}
if (!g_ActivityMan.Initialize()) {
RunMenuLoop();
}
RunGameLoop();
}
g_ThreadMan.GetPriorityThreadPool().wait_for_tasks();
g_ThreadMan.GetBackgroundThreadPool().wait_for_tasks();
DestroyManagers();
allegro_exit();
SDL_Quit();
return EXIT_SUCCESS;
}
#ifdef _WIN32
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { return main(__argc, __argv); }
#endif