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