]> git.localhorst.tv Git - blank.git/blob - src/app/app.cpp
collect and load textures required by block types
[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         if (HasState()) {
133                 GetState().Update(dt);
134         }
135         env.counter.ExitUpdate();
136 }
137
138 void Application::Render() {
139         // gl implementation may (and will probably) delay vsync blocking until
140         // the first write after flipping, which is this clear call
141         env.viewport.Clear();
142         env.counter.EnterRender();
143
144         if (HasState()) {
145                 GetState().Render(env.viewport);
146         }
147
148         env.counter.ExitRender();
149         env.window.Flip();
150 }
151
152
153 void Application::PushState(State *s) {
154         if (!states.empty()) {
155                 states.top()->OnPause();
156         }
157         states.emplace(s);
158         ++s->ref_count;
159         if (s->ref_count == 1) {
160                 s->OnEnter();
161         }
162         s->OnResume();
163 }
164
165 State *Application::PopState() {
166         State *s = states.top();
167         states.pop();
168         s->OnPause();
169         s->OnExit();
170         if (!states.empty()) {
171                 states.top()->OnResume();
172         }
173         return s;
174 }
175
176 State *Application::SwitchState(State *s_new) {
177         State *s_old = states.top();
178         states.top() = s_new;
179         --s_old->ref_count;
180         ++s_new->ref_count;
181         s_old->OnPause();
182         if (s_old->ref_count == 0) {
183                 s_old->OnExit();
184         }
185         if (s_new->ref_count == 1) {
186                 s_new->OnEnter();
187         }
188         s_new->OnResume();
189         return s_old;
190 }
191
192 State &Application::GetState() {
193         return *states.top();
194 }
195
196 bool Application::HasState() const noexcept {
197         return !states.empty();
198 }
199
200
201 void StateControl::Commit(Application &app) {
202         while (!cue.empty()) {
203                 Memo m(cue.front());
204                 cue.pop();
205                 switch (m.cmd) {
206                         case PUSH:
207                                 app.PushState(m.state);
208                                 break;
209                         case SWITCH:
210                                 app.SwitchState(m.state);
211                                 break;
212                         case POP:
213                                 app.PopState();
214                                 break;
215                         case POP_ALL:
216                                 while (app.HasState()) {
217                                         app.PopState();
218                                 }
219                                 break;
220                 }
221         }
222 }
223
224
225 Assets::Assets(const string &base)
226 : fonts(base + "fonts/")
227 , sounds(base + "sounds/")
228 , textures(base + "textures/")
229 , data(base + "data/")
230 , large_ui_font(LoadFont("DejaVuSans", 24))
231 , small_ui_font(LoadFont("DejaVuSans", 16)) {
232
233 }
234
235 namespace {
236
237 CuboidShape block_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }});
238 StairShape stair_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f });
239 CuboidShape slab_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }});
240
241 }
242
243 void Assets::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry &reg, TextureIndex &tex_index) const {
244         string full = data + set_name + ".types";
245         std::ifstream file(full);
246         if (!file) {
247                 throw std::runtime_error("failed to open block type file " + full);
248         }
249         TokenStreamReader in(file);
250         string type_name;
251         string name;
252         string tex_name;
253         string shape_name;
254         while (in.HasMore()) {
255                 in.ReadIdentifier(type_name);
256                 in.Skip(Token::EQUALS);
257                 BlockType type;
258
259                 // read block type
260                 in.Skip(Token::ANGLE_BRACKET_OPEN);
261                 while (in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
262                         in.ReadIdentifier(name);
263                         in.Skip(Token::EQUALS);
264                         if (name == "visible") {
265                                 type.visible = in.GetBool();
266                         } else if (name == "texture") {
267                                 in.ReadString(tex_name);
268                                 type.texture = tex_index.GetID(tex_name);
269                         } else if (name == "color") {
270                                 in.ReadVec(type.color);
271                         } else if (name == "label") {
272                                 in.ReadString(type.label);
273                         } else if (name == "luminosity") {
274                                 type.luminosity = in.GetInt();
275                         } else if (name == "block_light") {
276                                 type.block_light = in.GetBool();
277                         } else if (name == "collision") {
278                                 type.collision = in.GetBool();
279                         } else if (name == "collide_block") {
280                                 type.collide_block = in.GetBool();
281                         } else if (name == "shape") {
282                                 in.ReadIdentifier(shape_name);
283                                 if (shape_name == "block") {
284                                         type.shape = &block_shape;
285                                         type.fill = {  true,  true,  true,  true,  true,  true };
286                                 } else if (shape_name == "slab") {
287                                         type.shape = &slab_shape;
288                                         type.fill = { false,  true, false, false, false, false };
289                                 } else if (shape_name == "stair") {
290                                         type.shape = &stair_shape;
291                                         type.fill = { false,  true, false, false, false,  true };
292                                 } else {
293                                         throw runtime_error("unknown block shape: " + shape_name);
294                                 }
295                         } else {
296                                 throw runtime_error("unknown block property: " + name);
297                         }
298                         in.Skip(Token::SEMICOLON);
299                 }
300                 in.Skip(Token::ANGLE_BRACKET_CLOSE);
301                 in.Skip(Token::SEMICOLON);
302
303                 reg.Add(type);
304         }
305 }
306
307 Font Assets::LoadFont(const string &name, int size) const {
308         string full = fonts + name + ".ttf";
309         return Font(full.c_str(), size);
310 }
311
312 Sound Assets::LoadSound(const string &name) const {
313         string full = sounds + name + ".wav";
314         return Sound(full.c_str());
315 }
316
317 Texture Assets::LoadTexture(const string &name) const {
318         string full = textures + name + ".png";
319         Texture tex;
320         SDL_Surface *srf = IMG_Load(full.c_str());
321         if (!srf) {
322                 throw SDLError("IMG_Load");
323         }
324         tex.Bind();
325         tex.Data(*srf);
326         SDL_FreeSurface(srf);
327         return tex;
328 }
329
330 void Assets::LoadTexture(const string &name, ArrayTexture &tex, int layer) const {
331         string full = textures + name + ".png";
332         SDL_Surface *srf = IMG_Load(full.c_str());
333         if (!srf) {
334                 throw SDLError("IMG_Load");
335         }
336         tex.Bind();
337         try {
338                 tex.Data(layer, *srf);
339         } catch (...) {
340                 SDL_FreeSurface(srf);
341                 throw;
342         }
343         SDL_FreeSurface(srf);
344 }
345
346 void Assets::LoadTextures(const TextureIndex &index, ArrayTexture &tex) const {
347         // TODO: where the hell should that size come from?
348         tex.Reserve(16, 16, index.Size(), Format());
349         for (const auto &entry : index.Entries()) {
350                 LoadTexture(entry.first, tex, entry.second);
351         }
352 }
353
354
355 TextureIndex::TextureIndex()
356 : id_map() {
357
358 }
359
360 int TextureIndex::GetID(const string &name) {
361         auto entry = id_map.find(name);
362         if (entry == id_map.end()) {
363                 auto result = id_map.emplace(name, Size());
364                 return result.first->second;
365         } else {
366                 return entry->second;
367         }
368 }
369
370
371 void FrameCounter::EnterFrame() noexcept {
372         last_enter = SDL_GetTicks();
373         last_tick = last_enter;
374 }
375
376 void FrameCounter::EnterHandle() noexcept {
377         Tick();
378 }
379
380 void FrameCounter::ExitHandle() noexcept {
381         current.handle = Tick();
382 }
383
384 void FrameCounter::EnterUpdate() noexcept {
385         Tick();
386 }
387
388 void FrameCounter::ExitUpdate() noexcept {
389         current.update = Tick();
390 }
391
392 void FrameCounter::EnterRender() noexcept {
393         Tick();
394 }
395
396 void FrameCounter::ExitRender() noexcept {
397         current.render = Tick();
398 }
399
400 void FrameCounter::ExitFrame() noexcept {
401         Uint32 now = SDL_GetTicks();
402         current.total = now - last_enter;
403         current.running = current.handle + current.update + current.render;
404         current.waiting = current.total - current.running;
405         Accumulate();
406
407         ++cur_frame;
408         if (cur_frame >= NUM_FRAMES) {
409                 Push();
410                 cur_frame = 0;
411                 changed = true;
412         } else {
413                 changed = false;
414         }
415 }
416
417 int FrameCounter::Tick() noexcept {
418         Uint32 now = SDL_GetTicks();
419         int delta = now - last_tick;
420         last_tick = now;
421         return delta;
422 }
423
424 void FrameCounter::Accumulate() noexcept {
425         sum.handle += current.handle;
426         sum.update += current.update;
427         sum.render += current.render;
428         sum.running += current.running;
429         sum.waiting += current.waiting;
430         sum.total += current.total;
431
432         max.handle = std::max(current.handle, max.handle);
433         max.update = std::max(current.update, max.update);
434         max.render = std::max(current.render, max.render);
435         max.running = std::max(current.running, max.running);
436         max.waiting = std::max(current.waiting, max.waiting);
437         max.total = std::max(current.total, max.total);
438
439         current = Frame<int>();
440 }
441
442 void FrameCounter::Push() noexcept {
443         peak = max;
444         avg.handle = sum.handle * factor;
445         avg.update = sum.update * factor;
446         avg.render = sum.render * factor;
447         avg.running = sum.running * factor;
448         avg.waiting = sum.waiting * factor;
449         avg.total = sum.total * factor;
450
451         sum = Frame<int>();
452         max = Frame<int>();
453 }
454
455 }