]> git.localhorst.tv Git - blank.git/blob - src/app/app.cpp
centralize fonts
[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 "../world/BlockType.hpp"
14 #include "../world/Entity.hpp"
15
16 #include <iostream>
17 #include <stdexcept>
18 #include <SDL_image.h>
19
20 using std::string;
21
22
23 namespace blank {
24
25 Application::Application(Environment &e)
26 : env(e)
27 , states() {
28
29 }
30
31 Application::~Application() {
32         env.audio.StopAll();
33 }
34
35
36 void Application::RunN(size_t n) {
37         Uint32 last = SDL_GetTicks();
38         for (size_t i = 0; HasState() && i < n; ++i) {
39                 Uint32 now = SDL_GetTicks();
40                 int delta = now - last;
41                 Loop(delta);
42                 last = now;
43         }
44 }
45
46 void Application::RunT(size_t t) {
47         Uint32 last = SDL_GetTicks();
48         Uint32 finish = last + t;
49         while (HasState() && last < finish) {
50                 Uint32 now = SDL_GetTicks();
51                 int delta = now - last;
52                 Loop(delta);
53                 last = now;
54         }
55 }
56
57 void Application::RunS(size_t n, size_t t) {
58         for (size_t i = 0; HasState() && i < n; ++i) {
59                 Loop(t);
60         }
61 }
62
63
64 void Application::Run() {
65         Uint32 last = SDL_GetTicks();
66         env.window.GrabMouse();
67         while (HasState()) {
68                 Uint32 now = SDL_GetTicks();
69                 int delta = now - last;
70                 Loop(delta);
71                 last = now;
72         }
73 }
74
75 void Application::Loop(int dt) {
76         env.counter.EnterFrame();
77         HandleEvents();
78         if (!HasState()) return;
79         Update(dt);
80         env.state.Commit(*this);
81         if (!HasState()) return;
82         Render();
83         env.counter.ExitFrame();
84 }
85
86
87 void Application::HandleEvents() {
88         env.counter.EnterHandle();
89         SDL_Event event;
90         while (HasState() && SDL_PollEvent(&event)) {
91                 Handle(event);
92                 env.state.Commit(*this);
93         }
94         env.counter.ExitHandle();
95 }
96
97 void Application::Handle(const SDL_Event &event) {
98         switch (event.type) {
99                 case SDL_WINDOWEVENT:
100                         Handle(event.window);
101                         break;
102                 default:
103                         GetState().Handle(event);
104                         break;
105         }
106 }
107
108 void Application::Handle(const SDL_WindowEvent &event) {
109         switch (event.event) {
110                 case SDL_WINDOWEVENT_FOCUS_GAINED:
111                         env.window.GrabMouse();
112                         break;
113                 case SDL_WINDOWEVENT_FOCUS_LOST:
114                         env.window.ReleaseMouse();
115                         break;
116                 case SDL_WINDOWEVENT_RESIZED:
117                         env.viewport.Resize(event.data1, event.data2);
118                         break;
119                 default:
120                         break;
121         }
122 }
123
124 void Application::Update(int dt) {
125         env.counter.EnterUpdate();
126         if (HasState()) {
127                 GetState().Update(dt);
128         }
129         env.counter.ExitUpdate();
130 }
131
132 void Application::Render() {
133         // gl implementation may (and will probably) delay vsync blocking until
134         // the first write after flipping, which is this clear call
135         env.viewport.Clear();
136         env.counter.EnterRender();
137
138         if (HasState()) {
139                 GetState().Render(env.viewport);
140         }
141
142         env.counter.ExitRender();
143         env.window.Flip();
144 }
145
146
147 void Application::PushState(State *s) {
148         if (!states.empty()) {
149                 states.top()->OnPause();
150         }
151         states.emplace(s);
152         ++s->ref_count;
153         if (s->ref_count == 1) {
154                 s->OnEnter();
155         }
156         s->OnResume();
157 }
158
159 State *Application::PopState() {
160         State *s = states.top();
161         states.pop();
162         s->OnPause();
163         s->OnExit();
164         if (!states.empty()) {
165                 states.top()->OnResume();
166         }
167         return s;
168 }
169
170 State *Application::SwitchState(State *s_new) {
171         State *s_old = states.top();
172         states.top() = s_new;
173         --s_old->ref_count;
174         ++s_new->ref_count;
175         s_old->OnPause();
176         if (s_old->ref_count == 0) {
177                 s_old->OnExit();
178         }
179         if (s_new->ref_count == 1) {
180                 s_new->OnEnter();
181         }
182         s_new->OnResume();
183         return s_old;
184 }
185
186 State &Application::GetState() {
187         return *states.top();
188 }
189
190 bool Application::HasState() const noexcept {
191         return !states.empty();
192 }
193
194
195 void StateControl::Commit(Application &app) {
196         while (!cue.empty()) {
197                 Memo m(cue.front());
198                 cue.pop();
199                 switch (m.cmd) {
200                         case PUSH:
201                                 app.PushState(m.state);
202                                 break;
203                         case SWITCH:
204                                 app.SwitchState(m.state);
205                                 break;
206                         case POP:
207                                 app.PopState();
208                                 break;
209                         case POP_ALL:
210                                 while (app.HasState()) {
211                                         app.PopState();
212                                 }
213                                 break;
214                 }
215         }
216 }
217
218
219 Assets::Assets(const string &base)
220 : fonts(base + "fonts/")
221 , sounds(base + "sounds/")
222 , textures(base + "textures/")
223 , large_ui_font(LoadFont("DejaVuSans", 24))
224 , small_ui_font(LoadFont("DejaVuSans", 16)) {
225
226 }
227
228 Font Assets::LoadFont(const string &name, int size) const {
229         string full = fonts + name + ".ttf";
230         return Font(full.c_str(), size);
231 }
232
233 Sound Assets::LoadSound(const string &name) const {
234         string full = sounds + name + ".wav";
235         return Sound(full.c_str());
236 }
237
238 Texture Assets::LoadTexture(const string &name) const {
239         string full = textures + name + ".png";
240         Texture tex;
241         SDL_Surface *srf = IMG_Load(full.c_str());
242         if (!srf) {
243                 throw SDLError("IMG_Load");
244         }
245         tex.Bind();
246         tex.Data(*srf);
247         SDL_FreeSurface(srf);
248         return tex;
249 }
250
251 void Assets::LoadTexture(const string &name, ArrayTexture &tex, int layer) const {
252         string full = textures + name + ".png";
253         SDL_Surface *srf = IMG_Load(full.c_str());
254         if (!srf) {
255                 throw SDLError("IMG_Load");
256         }
257         tex.Bind();
258         try {
259                 tex.Data(layer, *srf);
260         } catch (...) {
261                 SDL_FreeSurface(srf);
262                 throw;
263         }
264         SDL_FreeSurface(srf);
265 }
266
267
268 void FrameCounter::EnterFrame() noexcept {
269         last_enter = SDL_GetTicks();
270         last_tick = last_enter;
271 }
272
273 void FrameCounter::EnterHandle() noexcept {
274         Tick();
275 }
276
277 void FrameCounter::ExitHandle() noexcept {
278         current.handle = Tick();
279 }
280
281 void FrameCounter::EnterUpdate() noexcept {
282         Tick();
283 }
284
285 void FrameCounter::ExitUpdate() noexcept {
286         current.update = Tick();
287 }
288
289 void FrameCounter::EnterRender() noexcept {
290         Tick();
291 }
292
293 void FrameCounter::ExitRender() noexcept {
294         current.render = Tick();
295 }
296
297 void FrameCounter::ExitFrame() noexcept {
298         Uint32 now = SDL_GetTicks();
299         current.total = now - last_enter;
300         current.running = current.handle + current.update + current.render;
301         current.waiting = current.total - current.running;
302         Accumulate();
303
304         ++cur_frame;
305         if (cur_frame >= NUM_FRAMES) {
306                 Push();
307                 cur_frame = 0;
308                 changed = true;
309         } else {
310                 changed = false;
311         }
312 }
313
314 int FrameCounter::Tick() noexcept {
315         Uint32 now = SDL_GetTicks();
316         int delta = now - last_tick;
317         last_tick = now;
318         return delta;
319 }
320
321 void FrameCounter::Accumulate() noexcept {
322         sum.handle += current.handle;
323         sum.update += current.update;
324         sum.render += current.render;
325         sum.running += current.running;
326         sum.waiting += current.waiting;
327         sum.total += current.total;
328
329         max.handle = std::max(current.handle, max.handle);
330         max.update = std::max(current.update, max.update);
331         max.render = std::max(current.render, max.render);
332         max.running = std::max(current.running, max.running);
333         max.waiting = std::max(current.waiting, max.waiting);
334         max.total = std::max(current.total, max.total);
335
336         current = Frame<int>();
337 }
338
339 void FrameCounter::Push() noexcept {
340         peak = max;
341         avg.handle = sum.handle * factor;
342         avg.update = sum.update * factor;
343         avg.render = sum.render * factor;
344         avg.running = sum.running * factor;
345         avg.waiting = sum.waiting * factor;
346         avg.total = sum.total * factor;
347
348         sum = Frame<int>();
349         max = Frame<int>();
350 }
351
352 }