]> git.localhorst.tv Git - blank.git/blob - src/app/app.cpp
actually load shapes
[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(const string &set_name, BlockTypeRegistry &reg, TextureIndex &tex_index) const {
317         string full = data + set_name + ".types";
318         std::ifstream file(full);
319         if (!file) {
320                 throw std::runtime_error("failed to open block type file " + full);
321         }
322         TokenStreamReader in(file);
323         string type_name;
324         string name;
325         string tex_name;
326         string shape_name;
327         while (in.HasMore()) {
328                 in.ReadIdentifier(type_name);
329                 in.Skip(Token::EQUALS);
330                 BlockType type;
331
332                 // read block type
333                 in.Skip(Token::ANGLE_BRACKET_OPEN);
334                 while (in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
335                         in.ReadIdentifier(name);
336                         in.Skip(Token::EQUALS);
337                         if (name == "visible") {
338                                 type.visible = in.GetBool();
339                         } else if (name == "texture") {
340                                 in.ReadString(tex_name);
341                                 type.textures.push_back(tex_index.GetID(tex_name));
342                         } else if (name == "textures") {
343                                 in.Skip(Token::BRACKET_OPEN);
344                                 while (in.Peek().type != Token::BRACKET_CLOSE) {
345                                         in.ReadString(tex_name);
346                                         type.textures.push_back(tex_index.GetID(tex_name));
347                                         if (in.Peek().type == Token::COMMA) {
348                                                 in.Skip(Token::COMMA);
349                                         }
350                                 }
351                                 in.Skip(Token::BRACKET_CLOSE);
352                         } else if (name == "rgb_mod") {
353                                 in.ReadVec(type.rgb_mod);
354                         } else if (name == "hsl_mod") {
355                                 in.ReadVec(type.hsl_mod);
356                         } else if (name == "outline") {
357                                 in.ReadVec(type.outline_color);
358                         } else if (name == "label") {
359                                 in.ReadString(type.label);
360                         } else if (name == "luminosity") {
361                                 type.luminosity = in.GetInt();
362                         } else if (name == "block_light") {
363                                 type.block_light = in.GetBool();
364                         } else if (name == "collision") {
365                                 type.collision = in.GetBool();
366                         } else if (name == "collide_block") {
367                                 type.collide_block = in.GetBool();
368                         } else if (name == "generate") {
369                                 type.generate = in.GetBool();
370                         } else if (name == "min_solidity") {
371                                 type.min_solidity = in.GetFloat();
372                         } else if (name == "mid_solidity") {
373                                 type.mid_solidity = in.GetFloat();
374                         } else if (name == "max_solidity") {
375                                 type.max_solidity = in.GetFloat();
376                         } else if (name == "min_humidity") {
377                                 type.min_humidity = in.GetFloat();
378                         } else if (name == "mid_humidity") {
379                                 type.mid_humidity = in.GetFloat();
380                         } else if (name == "max_humidity") {
381                                 type.max_humidity = in.GetFloat();
382                         } else if (name == "min_temperature") {
383                                 type.min_temperature = in.GetFloat();
384                         } else if (name == "mid_temperature") {
385                                 type.mid_temperature = in.GetFloat();
386                         } else if (name == "max_temperature") {
387                                 type.max_temperature = in.GetFloat();
388                         } else if (name == "min_richness") {
389                                 type.min_richness = in.GetFloat();
390                         } else if (name == "mid_richness") {
391                                 type.mid_richness = in.GetFloat();
392                         } else if (name == "max_richness") {
393                                 type.max_richness = in.GetFloat();
394                         } else if (name == "commonness") {
395                                 type.commonness = in.GetFloat();
396                         } else if (name == "shape") {
397                                 in.ReadIdentifier(shape_name);
398                                 if (shape_name == "block") {
399                                         type.shape = &block_shape;
400                                         type.fill = {  true,  true,  true,  true,  true,  true };
401                                 } else if (shape_name == "slab") {
402                                         type.shape = &slab_shape;
403                                         type.fill = { false,  true, false, false, false, false };
404                                 } else if (shape_name == "stair") {
405                                         type.shape = &stair_shape;
406                                         type.fill = { false,  true, false, false, false,  true };
407                                 } else {
408                                         throw runtime_error("unknown block shape: " + shape_name);
409                                 }
410                         } else {
411                                 throw runtime_error("unknown block property: " + name);
412                         }
413                         in.Skip(Token::SEMICOLON);
414                 }
415                 in.Skip(Token::ANGLE_BRACKET_CLOSE);
416                 in.Skip(Token::SEMICOLON);
417
418                 reg.Add(type);
419         }
420 }
421
422 CubeMap AssetLoader::LoadCubeMap(const string &name) const {
423         string full = textures + name;
424         string right = full + "-right.png";
425         string left = full + "-left.png";
426         string top = full + "-top.png";
427         string bottom = full + "-bottom.png";
428         string back = full + "-back.png";
429         string front = full + "-front.png";
430
431         CubeMap cm;
432         cm.Bind();
433         SDL_Surface *srf;
434
435         if (!(srf = IMG_Load(right.c_str()))) throw SDLError("IMG_Load");
436         try {
437                 cm.Data(CubeMap::RIGHT, *srf);
438         } catch (...) {
439                 SDL_FreeSurface(srf);
440                 throw;
441         }
442         SDL_FreeSurface(srf);
443
444         if (!(srf = IMG_Load(left.c_str()))) throw SDLError("IMG_Load");
445         try {
446                 cm.Data(CubeMap::LEFT, *srf);
447         } catch (...) {
448                 SDL_FreeSurface(srf);
449                 throw;
450         }
451         SDL_FreeSurface(srf);
452
453         if (!(srf = IMG_Load(top.c_str()))) throw SDLError("IMG_Load");
454         try {
455                 cm.Data(CubeMap::TOP, *srf);
456         } catch (...) {
457                 SDL_FreeSurface(srf);
458                 throw;
459         }
460         SDL_FreeSurface(srf);
461
462         if (!(srf = IMG_Load(bottom.c_str()))) throw SDLError("IMG_Load");
463         try {
464                 cm.Data(CubeMap::BOTTOM, *srf);
465         } catch (...) {
466                 SDL_FreeSurface(srf);
467                 throw;
468         }
469         SDL_FreeSurface(srf);
470
471         if (!(srf = IMG_Load(back.c_str()))) throw SDLError("IMG_Load");
472         try {
473                 cm.Data(CubeMap::BACK, *srf);
474         } catch (...) {
475                 SDL_FreeSurface(srf);
476                 throw;
477         }
478         SDL_FreeSurface(srf);
479
480         if (!(srf = IMG_Load(front.c_str()))) throw SDLError("IMG_Load");
481         try {
482                 cm.Data(CubeMap::FRONT, *srf);
483         } catch (...) {
484                 SDL_FreeSurface(srf);
485                 throw;
486         }
487         SDL_FreeSurface(srf);
488
489         cm.FilterNearest();
490         cm.WrapEdge();
491
492         return cm;
493 }
494
495 Font AssetLoader::LoadFont(const string &name, int size) const {
496         string full = fonts + name + ".ttf";
497         return Font(full.c_str(), size);
498 }
499
500 void AssetLoader::LoadShapes(const string &set_name, ShapeRegistry &shapes) const {
501         string full = data + set_name + ".shapes";
502         std::ifstream file(full);
503         if (!file) {
504                 throw std::runtime_error("failed to open shape file " + full);
505         }
506         TokenStreamReader in(file);
507         string shape_name;
508         while (in.HasMore()) {
509                 in.ReadIdentifier(shape_name);
510                 in.Skip(Token::EQUALS);
511                 Shape &shape = shapes.Add(shape_name);
512                 shape.Read(in);
513                 in.Skip(Token::SEMICOLON);
514         }
515 }
516
517 Sound AssetLoader::LoadSound(const string &name) const {
518         string full = sounds + name + ".wav";
519         return Sound(full.c_str());
520 }
521
522 Texture AssetLoader::LoadTexture(const string &name) const {
523         string full = textures + name + ".png";
524         Texture tex;
525         SDL_Surface *srf = IMG_Load(full.c_str());
526         if (!srf) {
527                 throw SDLError("IMG_Load");
528         }
529         tex.Bind();
530         tex.Data(*srf);
531         SDL_FreeSurface(srf);
532         return tex;
533 }
534
535 void AssetLoader::LoadTexture(const string &name, ArrayTexture &tex, int layer) const {
536         string full = textures + name + ".png";
537         SDL_Surface *srf = IMG_Load(full.c_str());
538         if (!srf) {
539                 throw SDLError("IMG_Load");
540         }
541         tex.Bind();
542         try {
543                 tex.Data(layer, *srf);
544         } catch (...) {
545                 SDL_FreeSurface(srf);
546                 throw;
547         }
548         SDL_FreeSurface(srf);
549 }
550
551 void AssetLoader::LoadTextures(const TextureIndex &index, ArrayTexture &tex) const {
552         // TODO: where the hell should that size come from?
553         tex.Reserve(16, 16, index.Size(), Format());
554         for (const auto &entry : index.Entries()) {
555                 LoadTexture(entry.first, tex, entry.second);
556         }
557 }
558
559
560 TextureIndex::TextureIndex()
561 : id_map() {
562
563 }
564
565 int TextureIndex::GetID(const string &name) {
566         auto entry = id_map.find(name);
567         if (entry == id_map.end()) {
568                 auto result = id_map.emplace(name, Size());
569                 return result.first->second;
570         } else {
571                 return entry->second;
572         }
573 }
574
575
576 void FrameCounter::EnterFrame() noexcept {
577         last_enter = SDL_GetTicks();
578         last_tick = last_enter;
579 }
580
581 void FrameCounter::EnterHandle() noexcept {
582         Tick();
583 }
584
585 void FrameCounter::ExitHandle() noexcept {
586         current.handle = Tick();
587 }
588
589 void FrameCounter::EnterUpdate() noexcept {
590         Tick();
591 }
592
593 void FrameCounter::ExitUpdate() noexcept {
594         current.update = Tick();
595 }
596
597 void FrameCounter::EnterRender() noexcept {
598         Tick();
599 }
600
601 void FrameCounter::ExitRender() noexcept {
602         current.render = Tick();
603 }
604
605 void FrameCounter::ExitFrame() noexcept {
606         Uint32 now = SDL_GetTicks();
607         current.total = now - last_enter;
608         current.running = current.handle + current.update + current.render;
609         current.waiting = current.total - current.running;
610         Accumulate();
611
612         ++cur_frame;
613         if (cur_frame >= NUM_FRAMES) {
614                 Push();
615                 cur_frame = 0;
616                 changed = true;
617         } else {
618                 changed = false;
619         }
620 }
621
622 int FrameCounter::Tick() noexcept {
623         Uint32 now = SDL_GetTicks();
624         int delta = now - last_tick;
625         last_tick = now;
626         return delta;
627 }
628
629 void FrameCounter::Accumulate() noexcept {
630         sum.handle += current.handle;
631         sum.update += current.update;
632         sum.render += current.render;
633         sum.running += current.running;
634         sum.waiting += current.waiting;
635         sum.total += current.total;
636
637         max.handle = std::max(current.handle, max.handle);
638         max.update = std::max(current.update, max.update);
639         max.render = std::max(current.render, max.render);
640         max.running = std::max(current.running, max.running);
641         max.waiting = std::max(current.waiting, max.waiting);
642         max.total = std::max(current.total, max.total);
643
644         current = Frame<int>();
645 }
646
647 void FrameCounter::Push() noexcept {
648         peak = max;
649         avg.handle = sum.handle * factor;
650         avg.update = sum.update * factor;
651         avg.render = sum.render * factor;
652         avg.running = sum.running * factor;
653         avg.waiting = sum.waiting * factor;
654         avg.total = sum.total * factor;
655
656         sum = Frame<int>();
657         max = Frame<int>();
658 }
659
660 }