]> git.localhorst.tv Git - blank.git/blob - src/app/app.cpp
check for entities under crosshair
[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/Font.hpp"
13 #include "../graphics/Texture.hpp"
14 #include "../io/TokenStreamReader.hpp"
15 #include "../model/shapes.hpp"
16 #include "../world/BlockType.hpp"
17 #include "../world/BlockTypeRegistry.hpp"
18 #include "../world/Entity.hpp"
19
20 #include <fstream>
21 #include <iostream>
22 #include <stdexcept>
23 #include <SDL_image.h>
24
25 using std::runtime_error;
26 using std::string;
27
28
29 namespace blank {
30
31 Application::Application(Environment &e)
32 : env(e)
33 , states() {
34
35 }
36
37 Application::~Application() {
38         env.audio.StopAll();
39 }
40
41
42 void Application::RunN(size_t n) {
43         Uint32 last = SDL_GetTicks();
44         for (size_t i = 0; HasState() && i < n; ++i) {
45                 Uint32 now = SDL_GetTicks();
46                 int delta = now - last;
47                 Loop(delta);
48                 last = now;
49         }
50 }
51
52 void Application::RunT(size_t t) {
53         Uint32 last = SDL_GetTicks();
54         Uint32 finish = last + t;
55         while (HasState() && last < finish) {
56                 Uint32 now = SDL_GetTicks();
57                 int delta = now - last;
58                 Loop(delta);
59                 last = now;
60         }
61 }
62
63 void Application::RunS(size_t n, size_t t) {
64         for (size_t i = 0; HasState() && i < n; ++i) {
65                 Loop(t);
66         }
67 }
68
69
70 void Application::Run() {
71         Uint32 last = SDL_GetTicks();
72         env.window.GrabMouse();
73         while (HasState()) {
74                 Uint32 now = SDL_GetTicks();
75                 int delta = now - last;
76                 Loop(delta);
77                 last = now;
78         }
79 }
80
81 void Application::Loop(int dt) {
82         env.counter.EnterFrame();
83         HandleEvents();
84         if (!HasState()) return;
85         Update(dt);
86         env.state.Commit(*this);
87         if (!HasState()) return;
88         Render();
89         env.counter.ExitFrame();
90 }
91
92
93 void Application::HandleEvents() {
94         env.counter.EnterHandle();
95         SDL_Event event;
96         while (HasState() && SDL_PollEvent(&event)) {
97                 Handle(event);
98                 env.state.Commit(*this);
99         }
100         env.counter.ExitHandle();
101 }
102
103 void Application::Handle(const SDL_Event &event) {
104         switch (event.type) {
105                 case SDL_WINDOWEVENT:
106                         Handle(event.window);
107                         break;
108                 default:
109                         GetState().Handle(event);
110                         break;
111         }
112 }
113
114 void Application::Handle(const SDL_WindowEvent &event) {
115         switch (event.event) {
116                 case SDL_WINDOWEVENT_FOCUS_GAINED:
117                         env.window.GrabMouse();
118                         break;
119                 case SDL_WINDOWEVENT_FOCUS_LOST:
120                         env.window.ReleaseMouse();
121                         break;
122                 case SDL_WINDOWEVENT_RESIZED:
123                         env.viewport.Resize(event.data1, event.data2);
124                         break;
125                 default:
126                         break;
127         }
128 }
129
130 void Application::Update(int dt) {
131         env.counter.EnterUpdate();
132         env.audio.Update(dt);
133         if (HasState()) {
134                 GetState().Update(dt);
135         }
136         env.counter.ExitUpdate();
137 }
138
139 void Application::Render() {
140         // gl implementation may (and will probably) delay vsync blocking until
141         // the first write after flipping, which is this clear call
142         env.viewport.Clear();
143         env.counter.EnterRender();
144
145         if (HasState()) {
146                 GetState().Render(env.viewport);
147         }
148
149         env.counter.ExitRender();
150         env.window.Flip();
151 }
152
153
154 void Application::PushState(State *s) {
155         if (!states.empty()) {
156                 states.top()->OnPause();
157         }
158         states.emplace(s);
159         ++s->ref_count;
160         if (s->ref_count == 1) {
161                 s->OnEnter();
162         }
163         s->OnResume();
164 }
165
166 State *Application::PopState() {
167         State *s = states.top();
168         states.pop();
169         s->OnPause();
170         s->OnExit();
171         if (!states.empty()) {
172                 states.top()->OnResume();
173         }
174         return s;
175 }
176
177 State *Application::SwitchState(State *s_new) {
178         State *s_old = states.top();
179         states.top() = s_new;
180         --s_old->ref_count;
181         ++s_new->ref_count;
182         s_old->OnPause();
183         if (s_old->ref_count == 0) {
184                 s_old->OnExit();
185         }
186         if (s_new->ref_count == 1) {
187                 s_new->OnEnter();
188         }
189         s_new->OnResume();
190         return s_old;
191 }
192
193 State &Application::GetState() {
194         return *states.top();
195 }
196
197 bool Application::HasState() const noexcept {
198         return !states.empty();
199 }
200
201
202 void StateControl::Commit(Application &app) {
203         while (!cue.empty()) {
204                 Memo m(cue.front());
205                 cue.pop();
206                 switch (m.cmd) {
207                         case PUSH:
208                                 app.PushState(m.state);
209                                 break;
210                         case SWITCH:
211                                 app.SwitchState(m.state);
212                                 break;
213                         case POP:
214                                 app.PopState();
215                                 break;
216                         case POP_ALL:
217                                 while (app.HasState()) {
218                                         app.PopState();
219                                 }
220                                 break;
221                 }
222         }
223 }
224
225
226 Assets::Assets(const string &base)
227 : fonts(base + "fonts/")
228 , sounds(base + "sounds/")
229 , textures(base + "textures/")
230 , data(base + "data/")
231 , large_ui_font(LoadFont("DejaVuSans", 24))
232 , small_ui_font(LoadFont("DejaVuSans", 16)) {
233
234 }
235
236 namespace {
237
238 CuboidShape block_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }});
239 StairShape stair_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f });
240 CuboidShape slab_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }});
241
242 }
243
244 void Assets::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry &reg, TextureIndex &tex_index) const {
245         string full = data + set_name + ".types";
246         std::ifstream file(full);
247         if (!file) {
248                 throw std::runtime_error("failed to open block type file " + full);
249         }
250         TokenStreamReader in(file);
251         string type_name;
252         string name;
253         string tex_name;
254         string shape_name;
255         while (in.HasMore()) {
256                 in.ReadIdentifier(type_name);
257                 in.Skip(Token::EQUALS);
258                 BlockType type;
259
260                 // read block type
261                 in.Skip(Token::ANGLE_BRACKET_OPEN);
262                 while (in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
263                         in.ReadIdentifier(name);
264                         in.Skip(Token::EQUALS);
265                         if (name == "visible") {
266                                 type.visible = in.GetBool();
267                         } else if (name == "texture") {
268                                 in.ReadString(tex_name);
269                                 type.texture = tex_index.GetID(tex_name);
270                         } else if (name == "color") {
271                                 in.ReadVec(type.color);
272                         } else if (name == "label") {
273                                 in.ReadString(type.label);
274                         } else if (name == "luminosity") {
275                                 type.luminosity = in.GetInt();
276                         } else if (name == "block_light") {
277                                 type.block_light = in.GetBool();
278                         } else if (name == "collision") {
279                                 type.collision = in.GetBool();
280                         } else if (name == "collide_block") {
281                                 type.collide_block = in.GetBool();
282                         } else if (name == "shape") {
283                                 in.ReadIdentifier(shape_name);
284                                 if (shape_name == "block") {
285                                         type.shape = &block_shape;
286                                         type.fill = {  true,  true,  true,  true,  true,  true };
287                                 } else if (shape_name == "slab") {
288                                         type.shape = &slab_shape;
289                                         type.fill = { false,  true, false, false, false, false };
290                                 } else if (shape_name == "stair") {
291                                         type.shape = &stair_shape;
292                                         type.fill = { false,  true, false, false, false,  true };
293                                 } else {
294                                         throw runtime_error("unknown block shape: " + shape_name);
295                                 }
296                         } else {
297                                 throw runtime_error("unknown block property: " + name);
298                         }
299                         in.Skip(Token::SEMICOLON);
300                 }
301                 in.Skip(Token::ANGLE_BRACKET_CLOSE);
302                 in.Skip(Token::SEMICOLON);
303
304                 reg.Add(type);
305         }
306 }
307
308 Font Assets::LoadFont(const string &name, int size) const {
309         string full = fonts + name + ".ttf";
310         return Font(full.c_str(), size);
311 }
312
313 Sound Assets::LoadSound(const string &name) const {
314         string full = sounds + name + ".wav";
315         return Sound(full.c_str());
316 }
317
318 Texture Assets::LoadTexture(const string &name) const {
319         string full = textures + name + ".png";
320         Texture tex;
321         SDL_Surface *srf = IMG_Load(full.c_str());
322         if (!srf) {
323                 throw SDLError("IMG_Load");
324         }
325         tex.Bind();
326         tex.Data(*srf);
327         SDL_FreeSurface(srf);
328         return tex;
329 }
330
331 void Assets::LoadTexture(const string &name, ArrayTexture &tex, int layer) const {
332         string full = textures + name + ".png";
333         SDL_Surface *srf = IMG_Load(full.c_str());
334         if (!srf) {
335                 throw SDLError("IMG_Load");
336         }
337         tex.Bind();
338         try {
339                 tex.Data(layer, *srf);
340         } catch (...) {
341                 SDL_FreeSurface(srf);
342                 throw;
343         }
344         SDL_FreeSurface(srf);
345 }
346
347 void Assets::LoadTextures(const TextureIndex &index, ArrayTexture &tex) const {
348         // TODO: where the hell should that size come from?
349         tex.Reserve(16, 16, index.Size(), Format());
350         for (const auto &entry : index.Entries()) {
351                 LoadTexture(entry.first, tex, entry.second);
352         }
353 }
354
355
356 TextureIndex::TextureIndex()
357 : id_map() {
358
359 }
360
361 int TextureIndex::GetID(const string &name) {
362         auto entry = id_map.find(name);
363         if (entry == id_map.end()) {
364                 auto result = id_map.emplace(name, Size());
365                 return result.first->second;
366         } else {
367                 return entry->second;
368         }
369 }
370
371
372 void FrameCounter::EnterFrame() noexcept {
373         last_enter = SDL_GetTicks();
374         last_tick = last_enter;
375 }
376
377 void FrameCounter::EnterHandle() noexcept {
378         Tick();
379 }
380
381 void FrameCounter::ExitHandle() noexcept {
382         current.handle = Tick();
383 }
384
385 void FrameCounter::EnterUpdate() noexcept {
386         Tick();
387 }
388
389 void FrameCounter::ExitUpdate() noexcept {
390         current.update = Tick();
391 }
392
393 void FrameCounter::EnterRender() noexcept {
394         Tick();
395 }
396
397 void FrameCounter::ExitRender() noexcept {
398         current.render = Tick();
399 }
400
401 void FrameCounter::ExitFrame() noexcept {
402         Uint32 now = SDL_GetTicks();
403         current.total = now - last_enter;
404         current.running = current.handle + current.update + current.render;
405         current.waiting = current.total - current.running;
406         Accumulate();
407
408         ++cur_frame;
409         if (cur_frame >= NUM_FRAMES) {
410                 Push();
411                 cur_frame = 0;
412                 changed = true;
413         } else {
414                 changed = false;
415         }
416 }
417
418 int FrameCounter::Tick() noexcept {
419         Uint32 now = SDL_GetTicks();
420         int delta = now - last_tick;
421         last_tick = now;
422         return delta;
423 }
424
425 void FrameCounter::Accumulate() noexcept {
426         sum.handle += current.handle;
427         sum.update += current.update;
428         sum.render += current.render;
429         sum.running += current.running;
430         sum.waiting += current.waiting;
431         sum.total += current.total;
432
433         max.handle = std::max(current.handle, max.handle);
434         max.update = std::max(current.update, max.update);
435         max.render = std::max(current.render, max.render);
436         max.running = std::max(current.running, max.running);
437         max.waiting = std::max(current.waiting, max.waiting);
438         max.total = std::max(current.total, max.total);
439
440         current = Frame<int>();
441 }
442
443 void FrameCounter::Push() noexcept {
444         peak = max;
445         avg.handle = sum.handle * factor;
446         avg.update = sum.update * factor;
447         avg.render = sum.render * factor;
448         avg.running = sum.running * factor;
449         avg.waiting = sum.waiting * factor;
450         avg.total = sum.total * factor;
451
452         sum = Frame<int>();
453         max = Frame<int>();
454 }
455
456 }