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