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