]> git.localhorst.tv Git - blank.git/blob - src/app/app.cpp
reorganized client state
[blank.git] / src / app / app.cpp
1 #include "Application.hpp"
2 #include "Assets.hpp"
3 #include "Environment.hpp"
4 #include "FrameCounter.hpp"
5 #include "State.hpp"
6 #include "StateControl.hpp"
7 #include "TextureIndex.hpp"
8
9 #include "init.hpp"
10 #include "../audio/Sound.hpp"
11 #include "../graphics/ArrayTexture.hpp"
12 #include "../graphics/Font.hpp"
13 #include "../graphics/Texture.hpp"
14 #include "../io/TokenStreamReader.hpp"
15 #include "../model/shapes.hpp"
16 #include "../world/BlockType.hpp"
17 #include "../world/BlockTypeRegistry.hpp"
18 #include "../world/Entity.hpp"
19
20 #include <fstream>
21 #include <iomanip>
22 #include <iostream>
23 #include <stdexcept>
24 #include <SDL_image.h>
25
26 using std::runtime_error;
27 using std::string;
28
29
30 namespace blank {
31
32 HeadlessApplication::HeadlessApplication(HeadlessEnvironment &e)
33 : env(e)
34 , states() {
35
36 }
37
38 HeadlessApplication::~HeadlessApplication() {
39
40 }
41
42
43 Application::Application(Environment &e)
44 : HeadlessApplication(e)
45 , env(e) {
46
47 }
48
49 Application::~Application() {
50         env.audio.StopAll();
51 }
52
53
54 void HeadlessApplication::RunN(size_t n) {
55         Uint32 last = SDL_GetTicks();
56         for (size_t i = 0; HasState() && i < n; ++i) {
57                 Uint32 now = SDL_GetTicks();
58                 int delta = now - last;
59                 Loop(delta);
60                 last = now;
61         }
62 }
63
64 void HeadlessApplication::RunT(size_t t) {
65         Uint32 last = SDL_GetTicks();
66         Uint32 finish = last + t;
67         while (HasState() && last < finish) {
68                 Uint32 now = SDL_GetTicks();
69                 int delta = now - last;
70                 Loop(delta);
71                 last = now;
72         }
73 }
74
75 void HeadlessApplication::RunS(size_t n, size_t t) {
76         for (size_t i = 0; HasState() && i < n; ++i) {
77                 Loop(t);
78                 std::cout << '.';
79                 if (i % 16 == 15) {
80                         std::cout << std::setfill(' ') << std::setw(5) << std::right << (i + 1) << std::endl;
81                 } else {
82                         std::cout << std::flush;
83                 }
84         }
85 }
86
87
88 void HeadlessApplication::Run() {
89         Uint32 last = SDL_GetTicks();
90         while (HasState()) {
91                 Uint32 now = SDL_GetTicks();
92                 int delta = now - last;
93                 Loop(delta);
94                 last = now;
95         }
96 }
97
98 void HeadlessApplication::Loop(int dt) {
99         env.counter.EnterFrame();
100         Update(dt);
101         CommitStates();
102         if (!HasState()) return;
103         env.counter.ExitFrame();
104 }
105
106 void Application::Loop(int dt) {
107         env.counter.EnterFrame();
108         HandleEvents();
109         if (!HasState()) return;
110         Update(dt);
111         CommitStates();
112         if (!HasState()) return;
113         Render();
114         env.counter.ExitFrame();
115 }
116
117
118 void HeadlessApplication::HandleEvents() {
119         env.counter.EnterHandle();
120         SDL_Event event;
121         while (HasState() && SDL_PollEvent(&event)) {
122                 Handle(event);
123                 CommitStates();
124         }
125         env.counter.ExitHandle();
126 }
127
128 void HeadlessApplication::Handle(const SDL_Event &event) {
129         GetState().Handle(event);
130 }
131
132
133 void Application::HandleEvents() {
134         env.counter.EnterHandle();
135         SDL_Event event;
136         while (HasState() && SDL_PollEvent(&event)) {
137                 Handle(event);
138                 CommitStates();
139         }
140         env.counter.ExitHandle();
141 }
142
143 void Application::Handle(const SDL_Event &event) {
144         switch (event.type) {
145                 case SDL_WINDOWEVENT:
146                         Handle(event.window);
147                         break;
148                 default:
149                         GetState().Handle(event);
150                         break;
151         }
152 }
153
154 void Application::Handle(const SDL_WindowEvent &event) {
155         switch (event.event) {
156                 case SDL_WINDOWEVENT_FOCUS_GAINED:
157                         env.window.GrabMouse();
158                         break;
159                 case SDL_WINDOWEVENT_FOCUS_LOST:
160                         env.window.ReleaseMouse();
161                         break;
162                 case SDL_WINDOWEVENT_RESIZED:
163                         env.viewport.Resize(event.data1, event.data2);
164                         break;
165                 default:
166                         break;
167         }
168 }
169
170 void HeadlessApplication::Update(int dt) {
171         env.counter.EnterUpdate();
172         if (HasState()) {
173                 GetState().Update(dt);
174         }
175         env.counter.ExitUpdate();
176 }
177
178 void Application::Update(int dt) {
179         env.counter.EnterUpdate();
180         env.audio.Update(dt);
181         if (HasState()) {
182                 GetState().Update(dt);
183         }
184         env.counter.ExitUpdate();
185 }
186
187 void Application::Render() {
188         // gl implementation may (and will probably) delay vsync blocking until
189         // the first write after flipping, which is this clear call
190         env.viewport.Clear();
191         env.counter.EnterRender();
192
193         if (HasState()) {
194                 GetState().Render(env.viewport);
195         }
196
197         env.counter.ExitRender();
198         env.window.Flip();
199 }
200
201
202 void HeadlessApplication::PushState(State *s) {
203         if (!states.empty()) {
204                 states.top()->OnPause();
205         }
206         states.emplace(s);
207         ++s->ref_count;
208         if (s->ref_count == 1) {
209                 s->OnEnter();
210         }
211         s->OnResume();
212 }
213
214 State *HeadlessApplication::PopState() {
215         State *s = states.top();
216         states.pop();
217         s->OnPause();
218         s->OnExit();
219         if (!states.empty()) {
220                 states.top()->OnResume();
221         }
222         return s;
223 }
224
225 State *HeadlessApplication::SwitchState(State *s_new) {
226         State *s_old = states.top();
227         states.top() = s_new;
228         --s_old->ref_count;
229         ++s_new->ref_count;
230         s_old->OnPause();
231         if (s_old->ref_count == 0) {
232                 s_old->OnExit();
233         }
234         if (s_new->ref_count == 1) {
235                 s_new->OnEnter();
236         }
237         s_new->OnResume();
238         return s_old;
239 }
240
241 State &HeadlessApplication::GetState() {
242         return *states.top();
243 }
244
245 void HeadlessApplication::CommitStates() {
246         env.state.Commit(*this);
247 }
248
249 bool HeadlessApplication::HasState() const noexcept {
250         return !states.empty();
251 }
252
253
254 void StateControl::Commit(HeadlessApplication &app) {
255         while (!cue.empty()) {
256                 Memo m(cue.front());
257                 cue.pop();
258                 switch (m.cmd) {
259                         case PUSH:
260                                 app.PushState(m.state);
261                                 break;
262                         case SWITCH:
263                                 app.SwitchState(m.state);
264                                 break;
265                         case POP:
266                                 app.PopState();
267                                 break;
268                         case POP_ALL:
269                                 while (app.HasState()) {
270                                         app.PopState();
271                                 }
272                                 break;
273                         case POP_AFTER:
274                                 while (app.HasState() && &app.GetState() != m.state) {
275                                         app.PopState();
276                                 }
277                                 break;
278                         case POP_UNTIL:
279                                 while (app.HasState()) {
280                                         if (app.PopState() == m.state) {
281                                                 break;
282                                         }
283                                 }
284                 }
285         }
286 }
287
288
289 AssetLoader::AssetLoader(const string &base)
290 : fonts(base + "fonts/")
291 , sounds(base + "sounds/")
292 , textures(base + "textures/")
293 , data(base + "data/") {
294
295 }
296
297 Assets::Assets(const AssetLoader &loader)
298 : large_ui_font(loader.LoadFont("DejaVuSans", 24))
299 , small_ui_font(loader.LoadFont("DejaVuSans", 16)) {
300
301 }
302
303 namespace {
304
305 CuboidShape block_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }});
306 StairShape stair_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f });
307 CuboidShape slab_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }});
308
309 }
310
311 void AssetLoader::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry &reg, TextureIndex &tex_index) const {
312         string full = data + set_name + ".types";
313         std::ifstream file(full);
314         if (!file) {
315                 throw std::runtime_error("failed to open block type file " + full);
316         }
317         TokenStreamReader in(file);
318         string type_name;
319         string name;
320         string tex_name;
321         string shape_name;
322         while (in.HasMore()) {
323                 in.ReadIdentifier(type_name);
324                 in.Skip(Token::EQUALS);
325                 BlockType type;
326
327                 // read block type
328                 in.Skip(Token::ANGLE_BRACKET_OPEN);
329                 while (in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
330                         in.ReadIdentifier(name);
331                         in.Skip(Token::EQUALS);
332                         if (name == "visible") {
333                                 type.visible = in.GetBool();
334                         } else if (name == "texture") {
335                                 in.ReadString(tex_name);
336                                 type.texture = tex_index.GetID(tex_name);
337                         } else if (name == "color") {
338                                 in.ReadVec(type.color);
339                         } else if (name == "outline") {
340                                 in.ReadVec(type.outline_color);
341                         } else if (name == "label") {
342                                 in.ReadString(type.label);
343                         } else if (name == "luminosity") {
344                                 type.luminosity = in.GetInt();
345                         } else if (name == "block_light") {
346                                 type.block_light = in.GetBool();
347                         } else if (name == "collision") {
348                                 type.collision = in.GetBool();
349                         } else if (name == "collide_block") {
350                                 type.collide_block = in.GetBool();
351                         } else if (name == "shape") {
352                                 in.ReadIdentifier(shape_name);
353                                 if (shape_name == "block") {
354                                         type.shape = &block_shape;
355                                         type.fill = {  true,  true,  true,  true,  true,  true };
356                                 } else if (shape_name == "slab") {
357                                         type.shape = &slab_shape;
358                                         type.fill = { false,  true, false, false, false, false };
359                                 } else if (shape_name == "stair") {
360                                         type.shape = &stair_shape;
361                                         type.fill = { false,  true, false, false, false,  true };
362                                 } else {
363                                         throw runtime_error("unknown block shape: " + shape_name);
364                                 }
365                         } else {
366                                 throw runtime_error("unknown block property: " + name);
367                         }
368                         in.Skip(Token::SEMICOLON);
369                 }
370                 in.Skip(Token::ANGLE_BRACKET_CLOSE);
371                 in.Skip(Token::SEMICOLON);
372
373                 reg.Add(type);
374         }
375 }
376
377 Font AssetLoader::LoadFont(const string &name, int size) const {
378         string full = fonts + name + ".ttf";
379         return Font(full.c_str(), size);
380 }
381
382 Sound AssetLoader::LoadSound(const string &name) const {
383         string full = sounds + name + ".wav";
384         return Sound(full.c_str());
385 }
386
387 Texture AssetLoader::LoadTexture(const string &name) const {
388         string full = textures + name + ".png";
389         Texture tex;
390         SDL_Surface *srf = IMG_Load(full.c_str());
391         if (!srf) {
392                 throw SDLError("IMG_Load");
393         }
394         tex.Bind();
395         tex.Data(*srf);
396         SDL_FreeSurface(srf);
397         return tex;
398 }
399
400 void AssetLoader::LoadTexture(const string &name, ArrayTexture &tex, int layer) const {
401         string full = textures + name + ".png";
402         SDL_Surface *srf = IMG_Load(full.c_str());
403         if (!srf) {
404                 throw SDLError("IMG_Load");
405         }
406         tex.Bind();
407         try {
408                 tex.Data(layer, *srf);
409         } catch (...) {
410                 SDL_FreeSurface(srf);
411                 throw;
412         }
413         SDL_FreeSurface(srf);
414 }
415
416 void AssetLoader::LoadTextures(const TextureIndex &index, ArrayTexture &tex) const {
417         // TODO: where the hell should that size come from?
418         tex.Reserve(16, 16, index.Size(), Format());
419         for (const auto &entry : index.Entries()) {
420                 LoadTexture(entry.first, tex, entry.second);
421         }
422 }
423
424
425 TextureIndex::TextureIndex()
426 : id_map() {
427
428 }
429
430 int TextureIndex::GetID(const string &name) {
431         auto entry = id_map.find(name);
432         if (entry == id_map.end()) {
433                 auto result = id_map.emplace(name, Size());
434                 return result.first->second;
435         } else {
436                 return entry->second;
437         }
438 }
439
440
441 void FrameCounter::EnterFrame() noexcept {
442         last_enter = SDL_GetTicks();
443         last_tick = last_enter;
444 }
445
446 void FrameCounter::EnterHandle() noexcept {
447         Tick();
448 }
449
450 void FrameCounter::ExitHandle() noexcept {
451         current.handle = Tick();
452 }
453
454 void FrameCounter::EnterUpdate() noexcept {
455         Tick();
456 }
457
458 void FrameCounter::ExitUpdate() noexcept {
459         current.update = Tick();
460 }
461
462 void FrameCounter::EnterRender() noexcept {
463         Tick();
464 }
465
466 void FrameCounter::ExitRender() noexcept {
467         current.render = Tick();
468 }
469
470 void FrameCounter::ExitFrame() noexcept {
471         Uint32 now = SDL_GetTicks();
472         current.total = now - last_enter;
473         current.running = current.handle + current.update + current.render;
474         current.waiting = current.total - current.running;
475         Accumulate();
476
477         ++cur_frame;
478         if (cur_frame >= NUM_FRAMES) {
479                 Push();
480                 cur_frame = 0;
481                 changed = true;
482         } else {
483                 changed = false;
484         }
485 }
486
487 int FrameCounter::Tick() noexcept {
488         Uint32 now = SDL_GetTicks();
489         int delta = now - last_tick;
490         last_tick = now;
491         return delta;
492 }
493
494 void FrameCounter::Accumulate() noexcept {
495         sum.handle += current.handle;
496         sum.update += current.update;
497         sum.render += current.render;
498         sum.running += current.running;
499         sum.waiting += current.waiting;
500         sum.total += current.total;
501
502         max.handle = std::max(current.handle, max.handle);
503         max.update = std::max(current.update, max.update);
504         max.render = std::max(current.render, max.render);
505         max.running = std::max(current.running, max.running);
506         max.waiting = std::max(current.waiting, max.waiting);
507         max.total = std::max(current.total, max.total);
508
509         current = Frame<int>();
510 }
511
512 void FrameCounter::Push() noexcept {
513         peak = max;
514         avg.handle = sum.handle * factor;
515         avg.update = sum.update * factor;
516         avg.render = sum.render * factor;
517         avg.running = sum.running * factor;
518         avg.waiting = sum.waiting * factor;
519         avg.total = sum.total * factor;
520
521         sum = Frame<int>();
522         max = Frame<int>();
523 }
524
525 }