]> git.localhorst.tv Git - blank.git/blob - src/app/app.cpp
state management and control
[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/Font.hpp"
11 #include "../world/BlockType.hpp"
12 #include "../world/Entity.hpp"
13
14 #include <iostream>
15 #include <stdexcept>
16
17 using std::string;
18
19
20 namespace blank {
21
22 Application::Application(Environment &e)
23 : env(e)
24 , states() {
25
26 }
27
28 Application::~Application() {
29         env.audio.StopAll();
30 }
31
32
33 void Application::RunN(size_t n) {
34         Uint32 last = SDL_GetTicks();
35         for (size_t i = 0; HasState() && i < n; ++i) {
36                 Uint32 now = SDL_GetTicks();
37                 int delta = now - last;
38                 Loop(delta);
39                 last = now;
40         }
41 }
42
43 void Application::RunT(size_t t) {
44         Uint32 last = SDL_GetTicks();
45         Uint32 finish = last + t;
46         while (HasState() && last < finish) {
47                 Uint32 now = SDL_GetTicks();
48                 int delta = now - last;
49                 Loop(delta);
50                 last = now;
51         }
52 }
53
54 void Application::RunS(size_t n, size_t t) {
55         for (size_t i = 0; HasState() && i < n; ++i) {
56                 Loop(t);
57         }
58 }
59
60
61 void Application::Run() {
62         Uint32 last = SDL_GetTicks();
63         env.window.GrabMouse();
64         while (HasState()) {
65                 Uint32 now = SDL_GetTicks();
66                 int delta = now - last;
67                 Loop(delta);
68                 last = now;
69         }
70 }
71
72 void Application::Loop(int dt) {
73         env.counter.EnterFrame();
74         HandleEvents();
75         if (!HasState()) return;
76         Update(dt);
77         env.state.Commit(*this);
78         if (!HasState()) return;
79         Render();
80         env.counter.ExitFrame();
81 }
82
83
84 void Application::HandleEvents() {
85         env.counter.EnterHandle();
86         SDL_Event event;
87         while (HasState() && SDL_PollEvent(&event)) {
88                 Handle(event);
89                 env.state.Commit(*this);
90         }
91         env.counter.ExitHandle();
92 }
93
94 void Application::Handle(const SDL_Event &event) {
95         switch (event.type) {
96                 case SDL_QUIT:
97                         env.state.PopAll();
98                         break;
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         states.emplace(s);
149 }
150
151 State *Application::PopState() {
152         State *s = states.top();
153         states.pop();
154         return s;
155 }
156
157 State *Application::SwitchState(State *s_new) {
158         State *s_old = states.top();
159         states.top() = s_new;
160         return s_old;
161 }
162
163 State &Application::GetState() {
164         return *states.top();
165 }
166
167 bool Application::HasState() const noexcept {
168         return !states.empty();
169 }
170
171
172 void StateControl::Commit(Application &app) {
173         while (!cue.empty()) {
174                 Memo m(cue.front());
175                 cue.pop();
176                 switch (m.cmd) {
177                         case PUSH:
178                                 app.PushState(m.state);
179                                 break;
180                         case SWITCH:
181                                 app.SwitchState(m.state);
182                                 break;
183                         case POP:
184                                 app.PopState();
185                                 break;
186                         case POP_ALL:
187                                 while (app.HasState()) {
188                                         app.PopState();
189                                 }
190                                 break;
191                 }
192         }
193 }
194
195
196 Assets::Assets(const string &base)
197 : fonts(base + "fonts/")
198 , sounds(base + "sounds/") {
199
200 }
201
202 Font Assets::LoadFont(const string &name, int size) const {
203         string full = fonts + name + ".ttf";
204         return Font(full.c_str(), size);
205 }
206
207 Sound Assets::LoadSound(const string &name) const {
208         string full = sounds + name + ".wav";
209         return Sound(full.c_str());
210 }
211
212
213 void FrameCounter::EnterFrame() noexcept {
214         last_enter = SDL_GetTicks();
215         last_tick = last_enter;
216 }
217
218 void FrameCounter::EnterHandle() noexcept {
219         Tick();
220 }
221
222 void FrameCounter::ExitHandle() noexcept {
223         current.handle = Tick();
224 }
225
226 void FrameCounter::EnterUpdate() noexcept {
227         Tick();
228 }
229
230 void FrameCounter::ExitUpdate() noexcept {
231         current.update = Tick();
232 }
233
234 void FrameCounter::EnterRender() noexcept {
235         Tick();
236 }
237
238 void FrameCounter::ExitRender() noexcept {
239         current.render = Tick();
240 }
241
242 void FrameCounter::ExitFrame() noexcept {
243         Uint32 now = SDL_GetTicks();
244         current.total = now - last_enter;
245         current.running = current.handle + current.update + current.render;
246         current.waiting = current.total - current.running;
247         Accumulate();
248
249         ++cur_frame;
250         if (cur_frame >= NUM_FRAMES) {
251                 Push();
252                 cur_frame = 0;
253                 changed = true;
254         } else {
255                 changed = false;
256         }
257 }
258
259 int FrameCounter::Tick() noexcept {
260         Uint32 now = SDL_GetTicks();
261         int delta = now - last_tick;
262         last_tick = now;
263         return delta;
264 }
265
266 void FrameCounter::Accumulate() noexcept {
267         sum.handle += current.handle;
268         sum.update += current.update;
269         sum.render += current.render;
270         sum.running += current.running;
271         sum.waiting += current.waiting;
272         sum.total += current.total;
273
274         max.handle = std::max(current.handle, max.handle);
275         max.update = std::max(current.update, max.update);
276         max.render = std::max(current.render, max.render);
277         max.running = std::max(current.running, max.running);
278         max.waiting = std::max(current.waiting, max.waiting);
279         max.total = std::max(current.total, max.total);
280
281         current = Frame<int>();
282 }
283
284 void FrameCounter::Push() noexcept {
285         peak = max;
286         avg.handle = sum.handle * factor;
287         avg.update = sum.update * factor;
288         avg.render = sum.render * factor;
289         avg.running = sum.running * factor;
290         avg.waiting = sum.waiting * factor;
291         avg.total = sum.total * factor;
292
293         sum = Frame<int>();
294         max = Frame<int>();
295 }
296
297 }