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