]> git.localhorst.tv Git - blank.git/blob - src/app/runtime.cpp
sky box model & shader
[blank.git] / src / app / runtime.cpp
1 #include "Application.hpp"
2 #include "Environment.hpp"
3 #include "Runtime.hpp"
4 #include "WorldState.hpp"
5
6 #include "init.hpp"
7 #include "../client/MasterState.hpp"
8 #include "../io/filesystem.hpp"
9 #include "../io/WorldSave.hpp"
10 #include "../server/ServerState.hpp"
11
12 #include <cctype>
13 #include <cstdlib>
14 #include <fstream>
15 #include <iostream>
16 #include <SDL.h>
17
18 using namespace std;
19
20
21 namespace {
22
23 string default_asset_path() {
24         char *base = SDL_GetBasePath();
25         string assets(base);
26         assets += "assets/";
27         SDL_free(base);
28         return assets;
29 }
30
31 string default_save_path() {
32 #ifndef NDEBUG
33         char *base = SDL_GetBasePath();
34         string save(base);
35         save += "saves/";
36         SDL_free(base);
37         return save;
38 #else
39         char *pref = SDL_GetPrefPath("localhorst", "blank");
40         string save(pref);
41         SDL_free(pref);
42         return save;
43 #endif
44 }
45
46 }
47
48 namespace blank {
49
50 HeadlessEnvironment::HeadlessEnvironment(const Config &config)
51 : config(config)
52 , loader(config.asset_path)
53 , counter()
54 , state() {
55
56 }
57
58 string HeadlessEnvironment::Config::GetWorldPath(const string &world_name) const {
59         return save_path + "worlds/" + world_name + '/';
60 }
61
62 string HeadlessEnvironment::Config::GetWorldPath(const string &world_name, const string &host_name) const {
63         return save_path + "cache/" + host_name + '/' + world_name + '/';
64 }
65
66 Environment::Environment(Window &win, const Config &config)
67 : HeadlessEnvironment(config)
68 , assets(loader)
69 , audio()
70 , viewport()
71 , window(win)
72 , keymap() {
73         viewport.Clear();
74         window.Flip();
75         keymap.LoadDefault();
76
77         string keys_path = config.save_path + "keys.conf";
78         if (!is_file(keys_path)) {
79                 std::ofstream file(keys_path);
80                 keymap.Save(file);
81         } else {
82                 std::ifstream file(keys_path);
83                 keymap.Load(file);
84         }
85 }
86
87
88 Runtime::Runtime() noexcept
89 : name("blank")
90 , mode(NORMAL)
91 , target(STANDALONE)
92 , n(0)
93 , t(0)
94 , config() {
95
96 }
97
98
99 void Runtime::ReadArgs(int argc, const char *const *argv) {
100         if (argc <= 0) return;
101         name = argv[0];
102
103         bool options = true;
104         bool error = false;
105
106         for (int i = 1; i < argc; ++i) {
107                 const char *arg = argv[i];
108                 if (!arg || arg[0] == '\0') {
109                         cerr << "warning: found empty argument at position " << i << endl;
110                         continue;
111                 }
112                 if (options && arg[0] == '-') {
113                         if (arg[1] == '\0') {
114                                 cerr << "warning: incomplete option list at position " << i << endl;
115                         } else if (arg[1] == '-') {
116                                 if (arg[2] == '\0') {
117                                         // stopper
118                                         options = false;
119                                 } else {
120                                         const char *param = arg + 2;
121                                         // long option
122                                         if (strcmp(param, "no-vsync") == 0) {
123                                                 config.vsync = false;
124                                         } else if (strcmp(param, "no-keyboard") == 0) {
125                                                 config.interface.keyboard_disabled = true;
126                                         } else if (strcmp(param, "no-mouse") == 0) {
127                                                 config.interface.mouse_disabled = true;
128                                         } else if (strcmp(param, "no-hud") == 0) {
129                                                 config.interface.visual_disabled = true;
130                                         } else if (strcmp(param, "no-audio") == 0) {
131                                                 config.interface.audio_disabled = true;
132                                         } else if (strcmp(param, "standalone") == 0) {
133                                                 target = STANDALONE;
134                                         } else if (strcmp(param, "server") == 0) {
135                                                 target = SERVER;
136                                         } else if (strcmp(param, "client") == 0) {
137                                                 target = CLIENT;
138                                         } else if (strcmp(param, "asset-path") == 0) {
139                                                 ++i;
140                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
141                                                         cerr << "missing argument to --asset-path" << endl;
142                                                         error = true;
143                                                 } else {
144                                                         config.env.asset_path = argv[i];
145                                                 }
146                                         } else if (strcmp(param, "host") == 0) {
147                                                 ++i;
148                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
149                                                         cerr << "missing argument to --host" << endl;
150                                                         error = true;
151                                                 } else {
152                                                         config.client.host = argv[i];
153                                                 }
154                                         } else if (strcmp(param, "port") == 0) {
155                                                 ++i;
156                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
157                                                         cerr << "missing argument to --port" << endl;
158                                                         error = true;
159                                                 } else {
160                                                         config.server.port = strtoul(argv[i], nullptr, 10);
161                                                         config.client.port = config.server.port;
162                                                 }
163                                         } else if (strcmp(param, "player-name") == 0) {
164                                                 ++i;
165                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
166                                                         cerr << "missing argument to --player-name" << endl;
167                                                         error = true;
168                                                 } else {
169                                                         config.interface.player_name = argv[i];
170                                                 }
171                                         } else if (strcmp(param, "save-path") == 0) {
172                                                 ++i;
173                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
174                                                         cerr << "missing argument to --save-path" << endl;
175                                                         error = true;
176                                                 } else {
177                                                         config.env.save_path = argv[i];
178                                                 }
179                                         } else if (strcmp(param, "world-name") == 0) {
180                                                 ++i;
181                                                 if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
182                                                         cerr << "missing argument to --world-name" << endl;
183                                                         error = true;
184                                                 } else {
185                                                         config.world.name = argv[i];
186                                                 }
187                                         } else {
188                                                 cerr << "unknown option " << arg << endl;
189                                                 error = true;
190                                         }
191                                 }
192                         } else {
193                                 // short options
194                                 for (int j = 1; arg[j] != '\0'; ++j) {
195                                         switch (arg[j]) {
196                                                 case 'd':
197                                                         config.doublebuf = false;
198                                                         break;
199                                                 case 'm':
200                                                         ++i;
201                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
202                                                                 cerr << "missing argument to -m" << endl;
203                                                                 error = true;
204                                                         } else {
205                                                                 config.multisampling = strtoul(argv[i], nullptr, 10);
206                                                         }
207                                                         break;
208                                                 case 'n':
209                                                         ++i;
210                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
211                                                                 cerr << "missing argument to -n" << endl;
212                                                                 error = true;
213                                                         } else {
214                                                                 n = strtoul(argv[i], nullptr, 10);
215                                                         }
216                                                         break;
217                                                 case 's':
218                                                         ++i;
219                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
220                                                                 cerr << "missing argument to -s" << endl;
221                                                                 error = true;
222                                                         } else {
223                                                                 config.gen.seed = strtoul(argv[i], nullptr, 10);
224                                                         }
225                                                         break;
226                                                 case 't':
227                                                         ++i;
228                                                         if (i >= argc || argv[i] == nullptr || argv[i][0] == '\0') {
229                                                                 cerr << "missing argument to -t" << endl;
230                                                                 error = true;
231                                                         } else {
232                                                                 t = strtoul(argv[i], nullptr, 10);
233                                                         }
234                                                         break;
235                                                 case '-':
236                                                         // stopper
237                                                         options = false;
238                                                         break;
239                                                 default:
240                                                         cerr << "unknown option " << arg[j] << endl;
241                                                         error = true;
242                                                         break;
243                                         }
244                                 }
245                         }
246                 } else {
247                         cerr << "unable to interpret argument "
248                                 << i << " (" << arg << ")" << endl;
249                         error = true;
250                 }
251         }
252
253         if (error) {
254                 mode = ERROR;
255                 return;
256         }
257
258         if (config.env.asset_path.empty()) {
259                 config.env.asset_path = default_asset_path();
260         } else if (
261                 config.env.asset_path[config.env.asset_path.size() - 1] != '/' &&
262                 config.env.asset_path[config.env.asset_path.size() - 1] != '\\'
263         ) {
264                 config.env.asset_path += '/';
265         }
266         if (config.env.save_path.empty()) {
267                 config.env.save_path = default_save_path();
268         } else if (
269                 config.env.save_path[config.env.save_path.size() - 1] != '/' &&
270                 config.env.save_path[config.env.save_path.size() - 1] != '\\'
271         ) {
272                 config.env.save_path += '/';
273         }
274
275         if (n > 0) {
276                 if (t > 0) {
277                         mode = FIXED_FRAME_LIMIT;
278                 } else {
279                         mode = FRAME_LIMIT;
280                 }
281         } else if (t > 0) {
282                 mode = TIME_LIMIT;
283         } else {
284                 mode = NORMAL;
285         }
286 }
287
288 int Runtime::Execute() {
289         if (mode == ERROR) {
290                 return 1;
291         }
292
293         InitHeadless init_headless;
294
295         switch (target) {
296                 default:
297                 case STANDALONE:
298                         RunStandalone();
299                         break;
300                 case SERVER:
301                         RunServer();
302                         break;
303                 case CLIENT:
304                         RunClient();
305                         break;
306         }
307
308         return 0;
309 }
310
311 void Runtime::RunStandalone() {
312         Init init(config.doublebuf, config.multisampling);
313
314         Environment env(init.window, config.env);
315         env.viewport.VSync(config.vsync);
316
317         WorldSave save(config.env.GetWorldPath(config.world.name));
318         if (save.Exists()) {
319                 save.Read(config.world);
320                 save.Read(config.gen);
321         } else {
322                 save.Write(config.world);
323                 save.Write(config.gen);
324         }
325
326         Application app(env);
327         WorldState world_state(env, config.gen, config.interface, config.world, save);
328         app.PushState(&world_state);
329         Run(app);
330 }
331
332 void Runtime::RunServer() {
333         HeadlessEnvironment env(config.env);
334
335         WorldSave save(config.env.GetWorldPath(config.world.name));
336         if (save.Exists()) {
337                 save.Read(config.world);
338                 save.Read(config.gen);
339         } else {
340                 save.Write(config.world);
341                 save.Write(config.gen);
342         }
343
344         HeadlessApplication app(env);
345         server::ServerState server_state(env, config.gen, config.world, save, config.server);
346         app.PushState(&server_state);
347         Run(app);
348 }
349
350 void Runtime::RunClient() {
351         Init init(config.doublebuf, config.multisampling);
352
353         Environment env(init.window, config.env);
354         env.viewport.VSync(config.vsync);
355
356         Application app(env);
357         client::MasterState client_state(env, config.world, config.interface, config.client);
358         app.PushState(&client_state);
359         Run(app);
360 }
361
362 void Runtime::Run(HeadlessApplication &app) {
363         switch (mode) {
364                 default:
365                 case NORMAL:
366                         app.Run();
367                         break;
368                 case FRAME_LIMIT:
369                         app.RunN(n);
370                         break;
371                 case TIME_LIMIT:
372                         app.RunT(t);
373                         break;
374                 case FIXED_FRAME_LIMIT:
375                         app.RunS(n, t);
376                         break;
377         }
378 }
379
380 }