]> git.localhorst.tv Git - blank.git/blob - src/app/app.cpp
add frame statistics printout
[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
8 #include "init.hpp"
9 #include "../audio/Sound.hpp"
10 #include "../graphics/ArrayTexture.hpp"
11 #include "../graphics/CubeMap.hpp"
12 #include "../graphics/Font.hpp"
13 #include "../graphics/Texture.hpp"
14 #include "../io/TokenStreamReader.hpp"
15 #include "../model/bounds.hpp"
16 #include "../model/Model.hpp"
17 #include "../model/ModelRegistry.hpp"
18 #include "../model/Shape.hpp"
19 #include "../model/ShapeRegistry.hpp"
20 #include "../shared/ResourceIndex.hpp"
21 #include "../world/BlockType.hpp"
22 #include "../world/BlockTypeRegistry.hpp"
23 #include "../world/Entity.hpp"
24
25 #include <fstream>
26 #include <iomanip>
27 #include <iostream>
28 #include <stdexcept>
29 #include <SDL_image.h>
30
31 using namespace std;
32
33
34 namespace blank {
35
36 HeadlessApplication::HeadlessApplication(HeadlessEnvironment &e)
37 : env(e)
38 , states() {
39
40 }
41
42 HeadlessApplication::~HeadlessApplication() {
43
44 }
45
46
47 Application::Application(Environment &e)
48 : HeadlessApplication(e)
49 , env(e) {
50
51 }
52
53 Application::~Application() {
54         env.audio.StopAll();
55 }
56
57
58 void HeadlessApplication::RunN(size_t n) {
59         Uint32 last = SDL_GetTicks();
60         for (size_t i = 0; HasState() && i < n; ++i) {
61                 Uint32 now = SDL_GetTicks();
62                 int delta = now - last;
63                 Loop(delta);
64                 last = now;
65         }
66 }
67
68 void HeadlessApplication::RunT(size_t t) {
69         Uint32 last = SDL_GetTicks();
70         Uint32 finish = last + t;
71         while (HasState() && last < finish) {
72                 Uint32 now = SDL_GetTicks();
73                 int delta = now - last;
74                 Loop(delta);
75                 last = now;
76         }
77 }
78
79 void HeadlessApplication::RunS(size_t n, size_t t) {
80         for (size_t i = 0; HasState() && i < n; ++i) {
81                 Loop(t);
82                 cout << '.';
83                 if (i % 32 == 31) {
84                         cout << setfill(' ') << setw(5) << right << (i + 1) << endl;
85                 } else {
86                         cout << flush;
87                 }
88         }
89 }
90
91
92 void HeadlessApplication::Run() {
93         Uint32 last = SDL_GetTicks();
94         while (HasState()) {
95                 Uint32 now = SDL_GetTicks();
96                 int delta = now - last;
97                 Loop(delta);
98                 last = now;
99         }
100 }
101
102 void HeadlessApplication::Loop(int dt) {
103         env.counter.EnterFrame();
104         HandleEvents();
105         if (!HasState()) return;
106         Update(dt);
107         CommitStates();
108         if (!HasState()) return;
109         env.counter.ExitFrame();
110 }
111
112 void Application::Loop(int dt) {
113         env.counter.EnterFrame();
114         HandleEvents();
115         if (!HasState()) return;
116         Update(dt);
117         CommitStates();
118         if (!HasState()) return;
119         Render();
120         env.counter.ExitFrame();
121 }
122
123
124 void HeadlessApplication::HandleEvents() {
125         env.counter.EnterHandle();
126         SDL_Event event;
127         while (HasState() && SDL_PollEvent(&event)) {
128                 Handle(event);
129                 CommitStates();
130         }
131         env.counter.ExitHandle();
132 }
133
134 void HeadlessApplication::Handle(const SDL_Event &event) {
135         GetState().Handle(event);
136 }
137
138
139 void Application::HandleEvents() {
140         env.counter.EnterHandle();
141         SDL_Event event;
142         while (HasState() && SDL_PollEvent(&event)) {
143                 Handle(event);
144                 CommitStates();
145         }
146         env.counter.ExitHandle();
147 }
148
149 void Application::Handle(const SDL_Event &event) {
150         switch (event.type) {
151                 case SDL_WINDOWEVENT:
152                         Handle(event.window);
153                         break;
154                 default:
155                         GetState().Handle(event);
156                         break;
157         }
158 }
159
160 void Application::Handle(const SDL_WindowEvent &event) {
161         switch (event.event) {
162                 case SDL_WINDOWEVENT_FOCUS_GAINED:
163                         GetState().OnFocus();
164                         break;
165                 case SDL_WINDOWEVENT_FOCUS_LOST:
166                         GetState().OnBlur();
167                         break;
168                 case SDL_WINDOWEVENT_RESIZED:
169                         env.viewport.Resize(event.data1, event.data2);
170                         GetState().OnResize(env.viewport);
171                         break;
172                 default:
173                         break;
174         }
175 }
176
177 void HeadlessApplication::Update(int dt) {
178         env.counter.EnterUpdate();
179         if (HasState()) {
180                 GetState().Update(dt);
181         }
182         env.counter.ExitUpdate();
183 }
184
185 void Application::Update(int dt) {
186         env.counter.EnterUpdate();
187         env.audio.Update(dt);
188         if (HasState()) {
189                 GetState().Update(dt);
190         }
191         env.counter.ExitUpdate();
192 }
193
194 void Application::Render() {
195         // gl implementation may (and will probably) delay vsync blocking until
196         // the first write after flipping, which is this clear call
197         env.viewport.Clear();
198         env.counter.EnterRender();
199
200         if (HasState()) {
201                 GetState().Render(env.viewport);
202         }
203
204         env.counter.ExitRender();
205         env.window.Flip();
206 }
207
208
209 void HeadlessApplication::PushState(State *s) {
210         if (!states.empty()) {
211                 states.top()->OnPause();
212         }
213         states.emplace(s);
214         ++s->ref_count;
215         if (s->ref_count == 1) {
216                 s->OnEnter();
217         }
218         s->OnResume();
219 }
220
221 State *HeadlessApplication::PopState() {
222         State *s = states.top();
223         states.pop();
224         s->OnPause();
225         s->OnExit();
226         if (!states.empty()) {
227                 states.top()->OnResume();
228         }
229         return s;
230 }
231
232 State *HeadlessApplication::SwitchState(State *s_new) {
233         State *s_old = states.top();
234         states.top() = s_new;
235         --s_old->ref_count;
236         ++s_new->ref_count;
237         s_old->OnPause();
238         if (s_old->ref_count == 0) {
239                 s_old->OnExit();
240         }
241         if (s_new->ref_count == 1) {
242                 s_new->OnEnter();
243         }
244         s_new->OnResume();
245         return s_old;
246 }
247
248 State &HeadlessApplication::GetState() {
249         return *states.top();
250 }
251
252 void HeadlessApplication::CommitStates() {
253         env.state.Commit(*this);
254 }
255
256 bool HeadlessApplication::HasState() const noexcept {
257         return !states.empty();
258 }
259
260
261 void StateControl::Commit(HeadlessApplication &app) {
262         while (!cue.empty()) {
263                 Memo m(cue.front());
264                 cue.pop();
265                 switch (m.cmd) {
266                         case PUSH:
267                                 app.PushState(m.state);
268                                 break;
269                         case SWITCH:
270                                 app.SwitchState(m.state);
271                                 break;
272                         case POP:
273                                 app.PopState();
274                                 break;
275                         case POP_ALL:
276                                 while (app.HasState()) {
277                                         app.PopState();
278                                 }
279                                 break;
280                         case POP_AFTER:
281                                 while (app.HasState() && &app.GetState() != m.state) {
282                                         app.PopState();
283                                 }
284                                 break;
285                         case POP_UNTIL:
286                                 while (app.HasState()) {
287                                         if (app.PopState() == m.state) {
288                                                 break;
289                                         }
290                                 }
291                 }
292         }
293 }
294
295
296 AssetLoader::AssetLoader(const string &base)
297 : fonts(base + "fonts/")
298 , sounds(base + "sounds/")
299 , textures(base + "textures/")
300 , data(base + "data/") {
301
302 }
303
304 Assets::Assets(const AssetLoader &loader)
305 : large_ui_font(loader.LoadFont("DejaVuSans", 24))
306 , small_ui_font(loader.LoadFont("DejaVuSans", 16)) {
307
308 }
309
310 void AssetLoader::LoadBlockTypes(
311         const string &set_name,
312         BlockTypeRegistry &reg,
313         ResourceIndex &snd_index,
314         ResourceIndex &tex_index,
315         const ShapeRegistry &shapes
316 ) const {
317         string full = data + set_name + ".types";
318         ifstream file(full);
319         if (!file) {
320                 throw runtime_error("failed to open block type file " + full);
321         }
322         TokenStreamReader in(file);
323         string proto;
324         while (in.HasMore()) {
325                 BlockType type;
326                 in.ReadIdentifier(type.name);
327                 in.Skip(Token::EQUALS);
328                 if (in.Peek().type == Token::IDENTIFIER) {
329                         // prototype
330                         in.ReadIdentifier(proto);
331                         type.Copy(reg.Get(proto));
332                 }
333                 type.Read(in, snd_index, tex_index, shapes);
334                 in.Skip(Token::SEMICOLON);
335                 reg.Add(move(type));
336         }
337 }
338
339 CubeMap AssetLoader::LoadCubeMap(const string &name) const {
340         string full = textures + name;
341         string right = full + "-right.png";
342         string left = full + "-left.png";
343         string top = full + "-top.png";
344         string bottom = full + "-bottom.png";
345         string back = full + "-back.png";
346         string front = full + "-front.png";
347
348         CubeMap cm;
349         cm.Bind();
350         SDL_Surface *srf;
351
352         if (!(srf = IMG_Load(right.c_str()))) throw SDLError("IMG_Load");
353         try {
354                 cm.Data(CubeMap::RIGHT, *srf);
355         } catch (...) {
356                 SDL_FreeSurface(srf);
357                 throw;
358         }
359         SDL_FreeSurface(srf);
360
361         if (!(srf = IMG_Load(left.c_str()))) throw SDLError("IMG_Load");
362         try {
363                 cm.Data(CubeMap::LEFT, *srf);
364         } catch (...) {
365                 SDL_FreeSurface(srf);
366                 throw;
367         }
368         SDL_FreeSurface(srf);
369
370         if (!(srf = IMG_Load(top.c_str()))) throw SDLError("IMG_Load");
371         try {
372                 cm.Data(CubeMap::TOP, *srf);
373         } catch (...) {
374                 SDL_FreeSurface(srf);
375                 throw;
376         }
377         SDL_FreeSurface(srf);
378
379         if (!(srf = IMG_Load(bottom.c_str()))) throw SDLError("IMG_Load");
380         try {
381                 cm.Data(CubeMap::BOTTOM, *srf);
382         } catch (...) {
383                 SDL_FreeSurface(srf);
384                 throw;
385         }
386         SDL_FreeSurface(srf);
387
388         if (!(srf = IMG_Load(back.c_str()))) throw SDLError("IMG_Load");
389         try {
390                 cm.Data(CubeMap::BACK, *srf);
391         } catch (...) {
392                 SDL_FreeSurface(srf);
393                 throw;
394         }
395         SDL_FreeSurface(srf);
396
397         if (!(srf = IMG_Load(front.c_str()))) throw SDLError("IMG_Load");
398         try {
399                 cm.Data(CubeMap::FRONT, *srf);
400         } catch (...) {
401                 SDL_FreeSurface(srf);
402                 throw;
403         }
404         SDL_FreeSurface(srf);
405
406         cm.FilterNearest();
407         cm.WrapEdge();
408
409         return cm;
410 }
411
412 Font AssetLoader::LoadFont(const string &name, int size) const {
413         string full = fonts + name + ".ttf";
414         return Font(full.c_str(), size);
415 }
416
417 void AssetLoader::LoadModels(
418         const string &set_name,
419         ModelRegistry &models,
420         ResourceIndex &tex_index,
421         const ShapeRegistry &shapes
422 ) const {
423         string full = data + set_name + ".models";
424         ifstream file(full);
425         if (!file) {
426                 throw runtime_error("failed to open model file " + full);
427         }
428         TokenStreamReader in(file);
429         string model_name;
430         string prop_name;
431         while (in.HasMore()) {
432                 in.ReadIdentifier(model_name);
433                 in.Skip(Token::EQUALS);
434                 in.Skip(Token::ANGLE_BRACKET_OPEN);
435                 Model &model = models.Add(model_name);
436                 while (in.HasMore() && in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
437                         in.ReadIdentifier(prop_name);
438                         in.Skip(Token::EQUALS);
439                         if (prop_name == "root") {
440                                 model.RootPart().Read(in, tex_index, shapes);
441                         } else if (prop_name == "body") {
442                                 model.SetBody(in.GetULong());
443                         } else if (prop_name == "eyes") {
444                                 model.SetEyes(in.GetULong());
445                         } else {
446                                 while (in.HasMore() && in.Peek().type != Token::SEMICOLON) {
447                                         in.Next();
448                                 }
449                         }
450                         in.Skip(Token::SEMICOLON);
451                 }
452                 model.Enumerate();
453                 in.Skip(Token::ANGLE_BRACKET_CLOSE);
454                 in.Skip(Token::SEMICOLON);
455         }
456 }
457
458 void AssetLoader::LoadShapes(const string &set_name, ShapeRegistry &shapes) const {
459         string full = data + set_name + ".shapes";
460         ifstream file(full);
461         if (!file) {
462                 throw runtime_error("failed to open shape file " + full);
463         }
464         TokenStreamReader in(file);
465         string shape_name;
466         while (in.HasMore()) {
467                 in.ReadIdentifier(shape_name);
468                 in.Skip(Token::EQUALS);
469                 Shape &shape = shapes.Add(shape_name);
470                 shape.Read(in);
471                 in.Skip(Token::SEMICOLON);
472         }
473 }
474
475 Sound AssetLoader::LoadSound(const string &name) const {
476         string full = sounds + name + ".wav";
477         return Sound(full.c_str());
478 }
479
480 Texture AssetLoader::LoadTexture(const string &name) const {
481         string full = textures + name + ".png";
482         Texture tex;
483         SDL_Surface *srf = IMG_Load(full.c_str());
484         if (!srf) {
485                 throw SDLError("IMG_Load");
486         }
487         tex.Bind();
488         tex.Data(*srf);
489         SDL_FreeSurface(srf);
490         return tex;
491 }
492
493 void AssetLoader::LoadTexture(const string &name, ArrayTexture &tex, int layer) const {
494         string full = textures + name + ".png";
495         SDL_Surface *srf = IMG_Load(full.c_str());
496         if (!srf) {
497                 throw SDLError("IMG_Load");
498         }
499         tex.Bind();
500         try {
501                 tex.Data(layer, *srf);
502         } catch (...) {
503                 SDL_FreeSurface(srf);
504                 throw;
505         }
506         SDL_FreeSurface(srf);
507 }
508
509 void AssetLoader::LoadTextures(const ResourceIndex &index, ArrayTexture &tex) const {
510         // TODO: where the hell should that size come from?
511         tex.Reserve(16, 16, index.Size(), Format());
512         for (const auto &entry : index.Entries()) {
513                 LoadTexture(entry.first, tex, entry.second);
514         }
515 }
516
517
518 void FrameCounter::EnterFrame() noexcept {
519         last_enter = SDL_GetTicks();
520         last_tick = last_enter;
521 }
522
523 void FrameCounter::EnterHandle() noexcept {
524         Tick();
525 }
526
527 void FrameCounter::ExitHandle() noexcept {
528         current.handle = Tick();
529 }
530
531 void FrameCounter::EnterUpdate() noexcept {
532         Tick();
533 }
534
535 void FrameCounter::ExitUpdate() noexcept {
536         current.update = Tick();
537 }
538
539 void FrameCounter::EnterRender() noexcept {
540         Tick();
541 }
542
543 void FrameCounter::ExitRender() noexcept {
544         current.render = Tick();
545 }
546
547 void FrameCounter::ExitFrame() noexcept {
548         Uint32 now = SDL_GetTicks();
549         current.total = now - last_enter;
550         current.running = current.handle + current.update + current.render;
551         current.waiting = current.total - current.running;
552         Accumulate();
553
554         ++cur_frame;
555         if (cur_frame >= NUM_FRAMES) {
556                 Push();
557                 cur_frame = 0;
558                 changed = true;
559         } else {
560                 changed = false;
561         }
562 }
563
564 int FrameCounter::Tick() noexcept {
565         Uint32 now = SDL_GetTicks();
566         int delta = now - last_tick;
567         last_tick = now;
568         return delta;
569 }
570
571 void FrameCounter::Accumulate() noexcept {
572         sum.handle += current.handle;
573         sum.update += current.update;
574         sum.render += current.render;
575         sum.running += current.running;
576         sum.waiting += current.waiting;
577         sum.total += current.total;
578
579         max.handle = std::max(current.handle, max.handle);
580         max.update = std::max(current.update, max.update);
581         max.render = std::max(current.render, max.render);
582         max.running = std::max(current.running, max.running);
583         max.waiting = std::max(current.waiting, max.waiting);
584         max.total = std::max(current.total, max.total);
585
586         current = Frame<int>();
587 }
588
589 void FrameCounter::Push() noexcept {
590         peak = max;
591         avg.handle = sum.handle * factor;
592         avg.update = sum.update * factor;
593         avg.render = sum.render * factor;
594         avg.running = sum.running * factor;
595         avg.waiting = sum.waiting * factor;
596         avg.total = sum.total * factor;
597
598         //Print(cout);
599
600         sum = Frame<int>();
601         max = Frame<int>();
602 }
603
604 void FrameCounter::Print(ostream &out) const {
605         out << fixed << right << setprecision(2) << setfill(' ')
606                 << "PEAK handle: " << setw(2) << peak.handle
607                 << ".00ms, update: " << setw(2) << peak.update
608                 << ".00ms, render: " << setw(2) << peak.render
609                 << ".00ms, running: " << setw(2) << peak.running
610                 << ".00ms, waiting: " << setw(2) << peak.waiting
611                 << ".00ms, total: " << setw(2) << peak.total
612                 << ".00ms" << endl
613                 << " AVG handle: " << setw(5) << avg.handle
614                 << "ms, update: " << setw(5) << avg.update
615                 << "ms, render: " << setw(5) << avg.render
616                 << "ms, running: " << setw(5) << avg.running
617                 << "ms, waiting: " << setw(5) << avg.waiting
618                 << "ms, total: " << setw(5) << avg.total
619                 << "ms" << endl;
620 }
621
622 }