1 /* Copyright (C) 2016 Wildfire Games.
2 * This file is part of 0 A.D.
4 * 0 A.D. is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
9 * 0 A.D. is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
20 This module drives the game when running without Atlas (our integrated
21 map editor). It receives input and OS messages via SDL and feeds them
22 into the input dispatcher, where they are passed on to the game GUI and
24 It also contains main(), which either runs the above controller or
25 that of Atlas depending on commandline parameters.
29 // not for any PCH effort, but instead for the (common) definitions
32 #include "lib/precompiled.h"
34 #include "lib/debug.h"
35 #include "lib/status.h"
36 #include "lib/secure_crt.h"
37 #include "lib/frequency_filter.h"
38 #include "lib/input.h"
40 #include "lib/timer.h"
41 #include "lib/external_libraries/libsdl.h"
43 #include "ps/ArchiveBuilder.h"
44 #include "ps/CConsole.h"
45 #include "ps/CLogger.h"
46 #include "ps/ConfigDB.h"
47 #include "ps/Filesystem.h"
49 #include "ps/Globals.h"
50 #include "ps/Hotkey.h"
51 #include "ps/Loader.h"
52 #include "ps/Profile.h"
53 #include "ps/Profiler2.h"
54 #include "ps/Pyrogenesis.h"
55 #include "ps/Replay.h"
56 #include "ps/TouchInput.h"
57 #include "ps/UserReport.h"
59 #include "ps/VideoMode.h"
61 #include "ps/GameSetup/GameSetup.h"
62 #include "ps/GameSetup/Atlas.h"
63 #include "ps/GameSetup/Config.h"
64 #include "ps/GameSetup/CmdLineArgs.h"
65 #include "ps/GameSetup/Paths.h"
66 #include "ps/XML/Xeromyces.h"
67 #include "network/NetClient.h"
68 #include "network/NetServer.h"
69 #include "network/NetSession.h"
70 #include "lobby/IXmppClient.h"
71 #include "graphics/Camera.h"
72 #include "graphics/GameView.h"
73 #include "graphics/TextureManager.h"
74 #include "gui/GUIManager.h"
75 #include "renderer/Renderer.h"
76 #include "simulation2/Simulation2.h"
79 #include <unistd.h> // geteuid
82 extern bool g_GameRestarted
;
86 // to avoid redundant and/or recursive resizing, we save the new
87 // size after VIDEORESIZE messages and only update the video mode
89 // these values are the latest resize message, and reset to 0 once we've
90 // updated the video mode
91 static int g_ResizedW
;
92 static int g_ResizedH
;
94 // main app message handler
95 static InReaction
MainInputHandler(const SDL_Event_
* ev
)
100 switch(ev
->ev
.window
.event
)
102 case SDL_WINDOWEVENT_ENTER
:
105 case SDL_WINDOWEVENT_LEAVE
:
108 case SDL_WINDOWEVENT_RESIZED
:
109 g_ResizedW
= ev
->ev
.window
.data1
;
110 g_ResizedH
= ev
->ev
.window
.data2
;
112 case SDL_WINDOWEVENT_MOVED
:
113 g_VideoMode
.UpdatePosition(ev
->ev
.window
.data1
, ev
->ev
.window
.data2
);
122 std::string hotkey
= static_cast<const char*>(ev
->ev
.user
.data1
);
123 if (hotkey
== "exit")
128 else if (hotkey
== "screenshot")
130 WriteScreenshot(L
".png");
133 else if (hotkey
== "bigscreenshot")
135 WriteBigScreenshot(L
".bmp", 10);
138 else if (hotkey
== "togglefullscreen")
140 g_VideoMode
.ToggleFullscreen();
143 else if (hotkey
== "profile2.toggle")
145 g_Profiler2
.Toggle();
155 // dispatch all pending events to the various receivers.
156 static void PumpEvents()
158 JSContext
* cx
= g_GUI
->GetScriptInterface()->GetContext();
159 JSAutoRequest
rq(cx
);
161 PROFILE3("dispatch events");
164 while (in_poll_event(&ev
))
169 JS::RootedValue
tmpVal(cx
);
170 ScriptInterface::ToJSVal(cx
, &tmpVal
, ev
);
171 std::string data
= g_GUI
->GetScriptInterface()->StringifyJSON(&tmpVal
);
172 PROFILE2_ATTR("%s", data
.c_str());
174 in_dispatch_event(&ev
);
177 g_TouchInput
.Frame();
181 static int ProgressiveLoad()
183 PROFILE3("progressive load");
185 wchar_t description
[100];
186 int progress_percent
;
189 Status ret
= LDR_ProgressiveLoad(10e-3, description
, ARRAY_SIZE(description
), &progress_percent
);
192 // no load active => no-op (skip code below)
195 // current task didn't complete. we only care about this insofar as the
196 // load process is therefore not yet finished.
199 // just finished loading
200 case INFO::ALL_COMPLETE
:
201 g_Game
->ReallyStartGame();
202 wcscpy_s(description
, ARRAY_SIZE(description
), L
"Game is starting..");
203 // LDR_ProgressiveLoad returns L""; set to valid text to
204 // avoid problems in converting to JSString
208 WARN_RETURN_STATUS_IF_ERR(ret
);
209 // can't do this above due to legit ERR::TIMED_OUT
213 catch (PSERROR_Game_World_MapLoadFailed
& e
)
215 // Map loading failed
217 // Call script function to do the actual work
218 // (delete game data, switch GUI page, show error, etc.)
219 CancelLoad(CStr(e
.what()).FromUTF8());
222 GUI_DisplayLoadProgress(progress_percent
, description
);
227 static void RendererIncrementalLoad()
229 PROFILE3("renderer incremental load");
231 const double maxTime
= 0.1f
;
233 double startTime
= timer_Time();
236 more
= g_Renderer
.GetTextureManager().MakeProgress();
238 while (more
&& timer_Time() - startTime
< maxTime
);
242 static bool quit
= false; // break out of main loop
246 g_Profiler2
.RecordFrameStart();
248 g_Profiler2
.IncrementFrameNumber();
249 PROFILE2_ATTR("%d", g_Profiler2
.GetFrameNumber());
254 const double time
= timer_Time();
255 g_frequencyFilter
->Update(time
);
256 // .. old method - "exact" but contains jumps
258 static double last_time
;
259 const double time
= timer_Time();
260 const float TimeSinceLastFrame
= (float)(time
-last_time
);
262 ONCE(return); // first call: set last_time and return
264 // .. new method - filtered and more smooth, but errors may accumulate
266 const float realTimeSinceLastFrame
= 1.0 / g_frequencyFilter
->SmoothedFrequency();
268 ENSURE(realTimeSinceLastFrame
> 0.0f
);
270 // decide if update/render is necessary
271 bool need_render
= !g_app_minimized
;
272 bool need_update
= true;
274 // If we are not running a multiplayer game, disable updates when the game is
275 // minimized or out of focus and relinquish the CPU a bit, in order to make
277 if(g_PauseOnFocusLoss
&& !g_NetClient
&& !g_app_has_focus
)
279 PROFILE3("non-focus delay");
281 // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored
285 // Throttling: limit update and render frequency to the minimum to 50 FPS
286 // in the "inactive" state, so that other windows get enough CPU time,
287 // (and it's always nice for power+thermal management).
288 // TODO: when the game performance is high enough, implementing a limit for
289 // in-game framerate might be sensible.
290 const float maxFPSMenu
= 50.0;
291 bool limit_fps
= false;
292 CFG_GET_VAL("gui.menu.limitfps", limit_fps
);
293 if (limit_fps
&& (!g_Game
|| !g_Game
->IsGameStarted()))
295 float remainingFrameTime
= (1000.0 / maxFPSMenu
) - realTimeSinceLastFrame
;
296 if (remainingFrameTime
> 0)
297 SDL_Delay(remainingFrameTime
);
301 // this scans for changed files/directories and reloads them, thus
302 // allowing hotloading (changes are immediately assimilated in-game).
303 ReloadChangedFiles();
307 RendererIncrementalLoad();
311 // if the user quit by closing the window, the GL context will be broken and
312 // may crash when we call Render() on some drivers, so leave this loop
317 // respond to pumped resize events
318 if (g_ResizedW
|| g_ResizedH
)
320 g_VideoMode
.ResizeWindow(g_ResizedW
, g_ResizedH
);
321 g_ResizedW
= g_ResizedH
= 0;
329 g_GUI
->TickObjects();
333 if (g_Game
&& g_Game
->IsGameStarted() && need_update
)
335 g_Game
->Update(realTimeSinceLastFrame
);
337 g_Game
->GetView()->Update(float(realTimeSinceLastFrame
));
340 // Immediately flush any messages produced by simulation code
342 g_NetClient
->Flush();
344 // Keep us connected to any XMPP servers
346 g_XmppClient
->recv();
348 g_UserReporter
.Update();
350 g_Console
->Update(realTimeSinceLastFrame
);
357 PROFILE3("swap buffers");
358 SDL_GL_SwapWindow(g_VideoMode
.GetWindow());
364 g_GameRestarted
= false;
368 static void MainControllerInit()
370 // add additional input handlers only needed by this controller:
372 // must be registered after gui_handler. Should mayhap even be last.
373 in_add_handler(MainInputHandler
);
378 static void MainControllerShutdown()
384 // stop the main loop and trigger orderly shutdown. called from several
385 // places: the event handler (SDL_QUIT and hotkey) and JS exitProgram.
392 static bool restart_in_atlas
= false;
393 // called by game code to indicate main() should restart in Atlas mode
394 // instead of terminating
395 void restart_mainloop_in_atlas()
398 restart_in_atlas
= true;
401 static bool restart
= false;
402 // trigger an orderly shutdown and restart the game.
403 void restart_engine()
409 extern CmdLineArgs g_args
;
411 // moved into a helper function to ensure args is destroyed before
412 // exit(), which may result in a memory leak.
413 static void RunGameOrAtlas(int argc
, const char* argv
[])
415 CmdLineArgs
args(argc
, argv
);
419 if (args
.Has("version") || args
.Has("-version"))
421 debug_printf("Pyrogenesis %s\n", engine_version
);
425 // We need to initialise libxml2 in the main thread before
426 // any thread uses it. So initialise it here before we
428 CXeromyces::Startup();
430 // Atlas handles the whole init/shutdown/etc sequence by itself;
431 if (ATLAS_RunIfOnCmdLine(args
, false))
434 const bool isReplay
= args
.Has("replay");
435 const bool isVisualReplay
= args
.Has("replay-visual");
436 const std::string replayFile
= isReplay
? args
.Get("replay") : (isVisualReplay
? args
.Get("replay-visual") : "");
438 // Ensure the replay file exists
439 if (isReplay
|| isVisualReplay
)
441 if (!FileExists(OsPath(replayFile
)))
443 debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile
.c_str());
446 if (DirectoryExists(OsPath(replayFile
)))
448 debug_printf("ERROR: The requested replay file '%s' is a directory!\n", replayFile
.c_str());
453 // run non-visual simulation replay if requested
457 g_VFS
= CreateVfs(20 * MiB
);
458 g_VFS
->Mount(L
"cache/", paths
.Cache(), VFS_MOUNT_ARCHIVABLE
);
459 MountMods(paths
, GetMods(args
, INIT_MODS
));
462 CReplayPlayer replay
;
463 replay
.Load(replayFile
);
464 replay
.Replay(args
.Has("serializationtest"), args
.Has("ooslog"));
469 CXeromyces::Terminate();
473 // run in archive-building mode if requested
474 if (args
.Has("archivebuild"))
478 OsPath
mod(args
.Get("archivebuild"));
480 if (args
.Has("archivebuild-output"))
481 zip
= args
.Get("archivebuild-output");
483 zip
= mod
.Filename().ChangeExtension(L
".zip");
485 CArchiveBuilder
builder(mod
, paths
.Cache());
487 // Add mods provided on the command line
488 // NOTE: We do not handle mods in the user mod path here
489 std::vector
<CStr
> mods
= args
.GetMultiple("mod");
490 for (size_t i
= 0; i
< mods
.size(); ++i
)
491 builder
.AddBaseMod(paths
.RData()/"mods"/mods
[i
]);
493 builder
.Build(zip
, args
.Has("archivebuild-compress"));
495 CXeromyces::Terminate();
499 const double res
= timer_Resolution();
500 g_frequencyFilter
= CreateFrequencyFilter(res
, 30.0);
503 int flags
= INIT_MODS
;
508 if (!Init(args
, flags
))
511 Shutdown(SHUTDOWN_FROM_CONFIG
);
514 InitGraphics(args
, 0);
515 MainControllerInit();
519 MainControllerShutdown();
523 if (restart_in_atlas
)
525 ATLAS_RunIfOnCmdLine(args
, true);
529 // Shut down libxml2 (done here to match the Startup call)
530 CXeromyces::Terminate();
534 // In Android we compile the engine as a shared library, not an executable,
535 // so rename main() to a different symbol that the wrapper library can load
537 #define main pyrogenesis_main
538 extern "C" __attribute__((visibility ("default"))) int main(int argc
, char* argv
[]);
541 extern "C" int main(int argc
, char* argv
[])
544 // Don't allow people to run the game with root permissions,
545 // because bad things can happen, check before we do anything
548 std::cerr
<< "********************************************************\n"
549 << "WARNING: Attempted to run the game with root permission!\n"
550 << "This is not allowed because it can alter home directory \n"
551 << "permissions and opens your system to vulnerabilities. \n"
552 << "(You received this message because you were either \n"
553 <<" logged in as root or used e.g. the 'sudo' command.) \n"
554 << "********************************************************\n\n";
559 EarlyInit(); // must come at beginning of main
561 RunGameOrAtlas(argc
, const_cast<const char**>(argv
));
563 // Shut down profiler initialised by EarlyInit
564 g_Profiler2
.Shutdown();