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