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