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