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