/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "MessageHandler.h"
#include "../GameLoop.h"
#include "../CommandProc.h"
#include "../ActorViewer.h"
#include "../View.h"
#include "../InputProcessor.h"
#include "graphics/GameView.h"
#include "graphics/ObjectManager.h"
#include "gui/GUIManager.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/timer.h"
#include "maths/MathUtil.h"
#include "ps/CConsole.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Profile.h"
#include "ps/Profiler2.h"
#include "ps/Game.h"
#include "ps/VideoMode.h"
#include "ps/GameSetup/Config.h"
#include "ps/GameSetup/GameSetup.h"
#include "renderer/backend/IDevice.h"
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "scriptinterface/ScriptInterface.h"
#include
#if OS_WIN
// We don't include wutil header directly to prevent including Windows headers.
extern void wutil_SetAppWindow(void* hwnd);
#endif
namespace AtlasMessage
{
namespace
{
InputProcessor g_Input;
// This keeps track of the last in-game user input.
// It is used to throttle FPS to save CPU & GPU.
double last_user_activity;
// see comment in GameLoop.cpp about ah_display_error before using INIT_HAVE_DISPLAY_ERROR
const int g_InitFlags = INIT_HAVE_VMODE | INIT_NO_GUI;
// This isn't used directly. When it's emplaced and when it's reset it does mutate `g_Logger`.
std::optional g_FileLogger;
std::optional g_ScriptInterface;
}
MESSAGEHANDLER(Init)
{
UNUSED2(msg);
g_Quickstart = true;
InitVfs(g_AtlasGameLoop->args);
g_FileLogger.emplace();
// Mount mods if there are any specified as command line parameters
if (!Init(g_AtlasGameLoop->args, g_InitFlags | INIT_MODS| INIT_MODS_PUBLIC))
{
// There are no mods specified on the command line,
// but there are in the config file, so mount those.
ShutdownConfigAndSubsequent();
ENSURE(Init(g_AtlasGameLoop->args, g_InitFlags));
}
// Initialise some graphics state for Atlas.
// (This must be done after Init loads the config DB,
// but before the UI constructs its GL canvases.)
g_VideoMode.InitNonSDL();
}
MESSAGEHANDLER(InitAppWindow)
{
#if OS_WIN
wutil_SetAppWindow(msg->handle);
#else
UNUSED2(msg);
#endif
}
MESSAGEHANDLER(InitSDL)
{
UNUSED2(msg);
// When using GLX (Linux), SDL has to load the GL library to find
// glXGetProcAddressARB before it can load any extensions.
// When running in Atlas, we skip the SDL video initialisation code
// which loads the library, and so SDL_GL_GetProcAddress fails (in
// ogl.cpp importExtensionFunctions).
// So, make sure it's loaded:
SDL_InitSubSystem(SDL_INIT_VIDEO);
// wxWidgets doesn't use a proper approach to dynamically load functions and
// doesn't provide GetProcAddr-like function. Technically we need to call
// SDL_GL_LoadLibrary inside GL device creation, but that might lead to a
// crash on Windows because of a wrong order of initialization between SDL
// and wxWidgets context management. So leave the call as is while it works.
// Refs:
// http://trac.wxwidgets.org/ticket/9213
// http://trac.wxwidgets.org/ticket/9215
if (SDL_GL_LoadLibrary(nullptr) && g_Logger)
LOGERROR("SDL failed to load GL library: '%s'", SDL_GetError());
}
MESSAGEHANDLER(InitGraphics)
{
UNUSED2(msg);
g_VideoMode.CreateBackendDevice(false);
g_VideoMode.GetBackendDevice()->OnWindowResize(g_xres, g_yres);
g_ScriptInterface.emplace("Engine", "GUIManager", *g_ScriptContext);
InitGraphics(g_AtlasGameLoop->args, g_InitFlags, {}, *g_ScriptContext, *g_ScriptInterface);
}
MESSAGEHANDLER(Shutdown)
{
UNUSED2(msg);
// Empty the CommandProc, to get rid of its references to entities before
// we kill the EntityManager
GetCommandProc().Destroy();
AtlasView::DestroyViews();
g_AtlasGameLoop->view = AtlasView::GetView_None();
ShutdownNetworkAndUI();
g_ScriptInterface.reset();
ShutdownConfigAndSubsequent();
g_FileLogger.reset();
}
QUERYHANDLER(Exit)
{
UNUSED2(msg);
g_AtlasGameLoop->running = false;
}
MESSAGEHANDLER(RenderEnable)
{
g_AtlasGameLoop->view->SetEnabled(false);
g_AtlasGameLoop->view = AtlasView::GetView(msg->view);
g_AtlasGameLoop->view->SetEnabled(true);
}
MESSAGEHANDLER(SetViewParamB)
{
AtlasView* view = AtlasView::GetView(msg->view);
view->SetParam(*msg->name, msg->value);
}
MESSAGEHANDLER(SetViewParamI)
{
AtlasView* view = AtlasView::GetView(msg->view);
view->SetParam(*msg->name, msg->value);
}
MESSAGEHANDLER(SetViewParamC)
{
AtlasView* view = AtlasView::GetView(msg->view);
view->SetParam(*msg->name, msg->value);
}
MESSAGEHANDLER(SetViewParamS)
{
AtlasView* view = AtlasView::GetView(msg->view);
view->SetParam(*msg->name, *msg->value);
}
MESSAGEHANDLER(SetActorViewer)
{
if (msg->flushcache)
{
// TODO EXTREME DANGER: this'll break horribly if any units remain
// in existence and use their actors after we've deleted all the actors.
// (The actor viewer currently only has one unit at a time, so it's
// alright.)
// Should replace this with proper actor hot-loading system, or something.
AtlasView::GetView_Actor()->GetActorViewer().SetActor(L"", "", -1);
AtlasView::GetView_Actor()->GetActorViewer().UnloadObjects();
// vfs_reload_changed_files();
}
AtlasView::GetView_Actor()->SetSpeedMultiplier(msg->speed);
AtlasView::GetView_Actor()->GetActorViewer().SetActor(*msg->id, *msg->animation, msg->playerID);
}
//////////////////////////////////////////////////////////////////////////
MESSAGEHANDLER(SetCanvas)
{
// Need to set the canvas size before possibly doing any rendering,
// else we'll get GL errors when trying to render to 0x0
CVideoMode::UpdateRenderer(msg->width, msg->height);
g_AtlasGameLoop->glCanvas = msg->canvas;
Atlas_GLSetCurrent(const_cast(g_AtlasGameLoop->glCanvas));
}
MESSAGEHANDLER(ResizeScreen)
{
CVideoMode::UpdateRenderer(msg->width, msg->height);
#if OS_MACOSX
// OS X seems to require this to update the GL canvas
Atlas_GLSetCurrent(const_cast(g_AtlasGameLoop->glCanvas));
#endif
}
QUERYHANDLER(RenderLoop)
{
{
const double time = timer_Time();
static double last_time = time;
const double realFrameLength = time-last_time;
last_time = time;
ENSURE(realFrameLength >= 0.0);
// TODO: filter out big jumps, e.g. when having done a lot of slow
// processing in the last frame
g_AtlasGameLoop->realFrameLength = realFrameLength;
}
if (g_Input.ProcessInput(g_AtlasGameLoop))
last_user_activity = timer_Time();
msg->timeSinceActivity = timer_Time() - last_user_activity;
ReloadChangedFiles();
RendererIncrementalLoad();
// Pump SDL events (e.g. hotkeys)
SDL_Event_ ev;
while (in_poll_priority_event(&ev))
in_dispatch_event(&ev);
if (g_GUI)
g_GUI->TickObjects();
g_AtlasGameLoop->view->Update(g_AtlasGameLoop->realFrameLength);
g_AtlasGameLoop->view->Render();
if (CProfileManager::IsInitialised())
g_Profiler.Frame();
msg->wantHighFPS = g_AtlasGameLoop->view->WantsHighFramerate();
}
//////////////////////////////////////////////////////////////////////////
MESSAGEHANDLER(RenderStyle)
{
g_Renderer.GetSceneRenderer().SetTerrainRenderMode(msg->wireframe ? EDGED_FACES : SOLID);
g_Renderer.GetSceneRenderer().SetWaterRenderMode(msg->wireframe ? EDGED_FACES : SOLID);
g_Renderer.GetSceneRenderer().SetModelRenderMode(msg->wireframe ? EDGED_FACES : SOLID);
g_Renderer.GetSceneRenderer().SetOverlayRenderMode(msg->wireframe ? EDGED_FACES : SOLID);
}
} // namespace AtlasMessage