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 "scriptinterface/ScriptEngine.h"
77 #include "simulation2/Simulation2.h"
80 #include <unistd.h> // geteuid
83 extern bool g_GameRestarted
;
87 // to avoid redundant and/or recursive resizing, we save the new
88 // size after VIDEORESIZE messages and only update the video mode
90 // these values are the latest resize message, and reset to 0 once we've
91 // updated the video mode
92 static int g_ResizedW
;
93 static int g_ResizedH
;
95 // main app message handler
96 static InReaction
MainInputHandler(const SDL_Event_
* ev
)
100 case SDL_WINDOWEVENT
:
101 switch(ev
->ev
.window
.event
)
103 case SDL_WINDOWEVENT_ENTER
:
106 case SDL_WINDOWEVENT_LEAVE
:
109 case SDL_WINDOWEVENT_RESIZED
:
110 g_ResizedW
= ev
->ev
.window
.data1
;
111 g_ResizedH
= ev
->ev
.window
.data2
;
113 case SDL_WINDOWEVENT_MOVED
:
114 g_VideoMode
.UpdatePosition(ev
->ev
.window
.data1
, ev
->ev
.window
.data2
);
123 std::string hotkey
= static_cast<const char*>(ev
->ev
.user
.data1
);
124 if (hotkey
== "exit")
129 else if (hotkey
== "screenshot")
131 WriteScreenshot(L
".png");
134 else if (hotkey
== "bigscreenshot")
136 WriteBigScreenshot(L
".bmp", 10);
139 else if (hotkey
== "togglefullscreen")
141 g_VideoMode
.ToggleFullscreen();
144 else if (hotkey
== "profile2.toggle")
146 g_Profiler2
.Toggle();
156 // dispatch all pending events to the various receivers.
157 static void PumpEvents()
159 JSContext
* cx
= g_GUI
->GetScriptInterface()->GetContext();
160 JSAutoRequest
rq(cx
);
162 PROFILE3("dispatch events");
165 while (in_poll_event(&ev
))
170 JS::RootedValue
tmpVal(cx
);
171 ScriptInterface::ToJSVal(cx
, &tmpVal
, ev
);
172 std::string data
= g_GUI
->GetScriptInterface()->StringifyJSON(&tmpVal
);
173 PROFILE2_ATTR("%s", data
.c_str());
175 in_dispatch_event(&ev
);
178 g_TouchInput
.Frame();
182 static int ProgressiveLoad()
184 PROFILE3("progressive load");
186 wchar_t description
[100];
187 int progress_percent
;
190 Status ret
= LDR_ProgressiveLoad(10e-3, description
, ARRAY_SIZE(description
), &progress_percent
);
193 // no load active => no-op (skip code below)
196 // current task didn't complete. we only care about this insofar as the
197 // load process is therefore not yet finished.
200 // just finished loading
201 case INFO::ALL_COMPLETE
:
202 g_Game
->ReallyStartGame();
203 wcscpy_s(description
, ARRAY_SIZE(description
), L
"Game is starting..");
204 // LDR_ProgressiveLoad returns L""; set to valid text to
205 // avoid problems in converting to JSString
209 WARN_RETURN_STATUS_IF_ERR(ret
);
210 // can't do this above due to legit ERR::TIMED_OUT
214 catch (PSERROR_Game_World_MapLoadFailed
& e
)
216 // Map loading failed
218 // Call script function to do the actual work
219 // (delete game data, switch GUI page, show error, etc.)
220 CancelLoad(CStr(e
.what()).FromUTF8());
223 GUI_DisplayLoadProgress(progress_percent
, description
);
228 static void RendererIncrementalLoad()
230 PROFILE3("renderer incremental load");
232 const double maxTime
= 0.1f
;
234 double startTime
= timer_Time();
237 more
= g_Renderer
.GetTextureManager().MakeProgress();
239 while (more
&& timer_Time() - startTime
< maxTime
);
243 static bool quit
= false; // break out of main loop
247 g_Profiler2
.RecordFrameStart();
249 g_Profiler2
.IncrementFrameNumber();
250 PROFILE2_ATTR("%d", g_Profiler2
.GetFrameNumber());
255 const double time
= timer_Time();
256 g_frequencyFilter
->Update(time
);
257 // .. old method - "exact" but contains jumps
259 static double last_time
;
260 const double time
= timer_Time();
261 const float TimeSinceLastFrame
= (float)(time
-last_time
);
263 ONCE(return); // first call: set last_time and return
265 // .. new method - filtered and more smooth, but errors may accumulate
267 const float realTimeSinceLastFrame
= 1.0 / g_frequencyFilter
->SmoothedFrequency();
269 ENSURE(realTimeSinceLastFrame
> 0.0f
);
271 // decide if update/render is necessary
272 bool need_render
= !g_app_minimized
;
273 bool need_update
= true;
275 // If we are not running a multiplayer game, disable updates when the game is
276 // minimized or out of focus and relinquish the CPU a bit, in order to make
278 if(g_PauseOnFocusLoss
&& !g_NetClient
&& !g_app_has_focus
)
280 PROFILE3("non-focus delay");
282 // don't use SDL_WaitEvent: don't want the main loop to freeze until app focus is restored
286 // Throttling: limit update and render frequency to the minimum to 50 FPS
287 // in the "inactive" state, so that other windows get enough CPU time,
288 // (and it's always nice for power+thermal management).
289 // TODO: when the game performance is high enough, implementing a limit for
290 // in-game framerate might be sensible.
291 const float maxFPSMenu
= 50.0;
292 bool limit_fps
= false;
293 CFG_GET_VAL("gui.menu.limitfps", limit_fps
);
294 if (limit_fps
&& (!g_Game
|| !g_Game
->IsGameStarted()))
296 float remainingFrameTime
= (1000.0 / maxFPSMenu
) - realTimeSinceLastFrame
;
297 if (remainingFrameTime
> 0)
298 SDL_Delay(remainingFrameTime
);
302 // this scans for changed files/directories and reloads them, thus
303 // allowing hotloading (changes are immediately assimilated in-game).
304 ReloadChangedFiles();
308 RendererIncrementalLoad();
312 // if the user quit by closing the window, the GL context will be broken and
313 // may crash when we call Render() on some drivers, so leave this loop
318 // respond to pumped resize events
319 if (g_ResizedW
|| g_ResizedH
)
321 g_VideoMode
.ResizeWindow(g_ResizedW
, g_ResizedH
);
322 g_ResizedW
= g_ResizedH
= 0;
330 g_GUI
->TickObjects();
334 if (g_Game
&& g_Game
->IsGameStarted() && need_update
)
336 g_Game
->Update(realTimeSinceLastFrame
);
338 g_Game
->GetView()->Update(float(realTimeSinceLastFrame
));
341 // Immediately flush any messages produced by simulation code
343 g_NetClient
->Flush();
345 // Keep us connected to any XMPP servers
347 g_XmppClient
->recv();
349 g_UserReporter
.Update();
351 g_Console
->Update(realTimeSinceLastFrame
);
358 PROFILE3("swap buffers");
359 SDL_GL_SwapWindow(g_VideoMode
.GetWindow());
365 g_GameRestarted
= false;
369 static void MainControllerInit()
371 // add additional input handlers only needed by this controller:
373 // must be registered after gui_handler. Should mayhap even be last.
374 in_add_handler(MainInputHandler
);
379 static void MainControllerShutdown()
385 // stop the main loop and trigger orderly shutdown. called from several
386 // places: the event handler (SDL_QUIT and hotkey) and JS exitProgram.
393 static bool restart_in_atlas
= false;
394 // called by game code to indicate main() should restart in Atlas mode
395 // instead of terminating
396 void restart_mainloop_in_atlas()
399 restart_in_atlas
= true;
402 static bool restart
= false;
403 // trigger an orderly shutdown and restart the game.
404 void restart_engine()
410 extern CmdLineArgs g_args
;
412 // moved into a helper function to ensure args is destroyed before
413 // exit(), which may result in a memory leak.
414 static void RunGameOrAtlas(int argc
, const char* argv
[])
416 CmdLineArgs
args(argc
, argv
);
420 if (args
.Has("version") || args
.Has("-version"))
422 debug_printf("Pyrogenesis %s\n", engine_version
);
426 const bool isVisualReplay
= args
.Has("replay-visual");
427 const bool isNonVisualReplay
= args
.Has("replay");
429 const CStr replayFile
=
430 isVisualReplay
? args
.Get("replay-visual") :
431 isNonVisualReplay
? args
.Get("replay") : "";
433 if (isVisualReplay
|| isNonVisualReplay
)
435 if (!FileExists(OsPath(replayFile
)))
437 debug_printf("ERROR: The requested replay file '%s' does not exist!\n", replayFile
.c_str());
440 if (DirectoryExists(OsPath(replayFile
)))
442 debug_printf("ERROR: The requested replay file '%s' is a directory!\n", replayFile
.c_str());
447 // We need to initialize SpiderMonkey and libxml2 in the main thread before
448 // any thread uses them. So initialize them here before we might run Atlas.
449 ScriptEngine scriptEngine
;
450 CXeromyces::Startup();
452 if (ATLAS_RunIfOnCmdLine(args
, false))
454 CXeromyces::Terminate();
458 if (isNonVisualReplay
)
460 if (!args
.Has("mod"))
462 LOGERROR("At least one mod should be specified! Did you mean to add the argument '-mod=public'?");
463 CXeromyces::Terminate();
468 g_VFS
= CreateVfs(20 * MiB
);
469 g_VFS
->Mount(L
"cache/", paths
.Cache(), VFS_MOUNT_ARCHIVABLE
);
470 MountMods(paths
, GetMods(args
, INIT_MODS
));
473 CReplayPlayer replay
;
474 replay
.Load(replayFile
);
476 args
.Has("serializationtest"),
477 args
.Has("rejointest") ? args
.Get("rejointest").ToInt() : -1,
483 CXeromyces::Terminate();
487 // run in archive-building mode if requested
488 if (args
.Has("archivebuild"))
492 OsPath
mod(args
.Get("archivebuild"));
494 if (args
.Has("archivebuild-output"))
495 zip
= args
.Get("archivebuild-output");
497 zip
= mod
.Filename().ChangeExtension(L
".zip");
499 CArchiveBuilder
builder(mod
, paths
.Cache());
501 // Add mods provided on the command line
502 // NOTE: We do not handle mods in the user mod path here
503 std::vector
<CStr
> mods
= args
.GetMultiple("mod");
504 for (size_t i
= 0; i
< mods
.size(); ++i
)
505 builder
.AddBaseMod(paths
.RData()/"mods"/mods
[i
]);
507 builder
.Build(zip
, args
.Has("archivebuild-compress"));
509 CXeromyces::Terminate();
513 const double res
= timer_Resolution();
514 g_frequencyFilter
= CreateFrequencyFilter(res
, 30.0);
517 int flags
= INIT_MODS
;
522 if (!Init(args
, flags
))
525 Shutdown(SHUTDOWN_FROM_CONFIG
);
528 InitGraphics(args
, 0);
529 MainControllerInit();
533 MainControllerShutdown();
537 if (restart_in_atlas
)
538 ATLAS_RunIfOnCmdLine(args
, true);
540 CXeromyces::Terminate();
544 // In Android we compile the engine as a shared library, not an executable,
545 // so rename main() to a different symbol that the wrapper library can load
547 #define main pyrogenesis_main
548 extern "C" __attribute__((visibility ("default"))) int main(int argc
, char* argv
[]);
551 extern "C" int main(int argc
, char* argv
[])
554 // Don't allow people to run the game with root permissions,
555 // because bad things can happen, check before we do anything
558 std::cerr
<< "********************************************************\n"
559 << "WARNING: Attempted to run the game with root permission!\n"
560 << "This is not allowed because it can alter home directory \n"
561 << "permissions and opens your system to vulnerabilities. \n"
562 << "(You received this message because you were either \n"
563 <<" logged in as root or used e.g. the 'sudo' command.) \n"
564 << "********************************************************\n\n";
569 EarlyInit(); // must come at beginning of main
571 RunGameOrAtlas(argc
, const_cast<const char**>(argv
));
573 // Shut down profiler initialised by EarlyInit
574 g_Profiler2
.Shutdown();