1 #include "Application.hpp"
3 #include "Environment.hpp"
4 #include "FrameCounter.hpp"
6 #include "StateControl.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"
22 #include <SDL_image.h>
24 using std::runtime_error;
30 Application::Application(Environment &e)
36 Application::~Application() {
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;
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;
62 void Application::RunS(size_t n, size_t t) {
63 for (size_t i = 0; HasState() && i < n; ++i) {
69 void Application::Run() {
70 Uint32 last = SDL_GetTicks();
71 env.window.GrabMouse();
73 Uint32 now = SDL_GetTicks();
74 int delta = now - last;
80 void Application::Loop(int dt) {
81 env.counter.EnterFrame();
83 if (!HasState()) return;
85 env.state.Commit(*this);
86 if (!HasState()) return;
88 env.counter.ExitFrame();
92 void Application::HandleEvents() {
93 env.counter.EnterHandle();
95 while (HasState() && SDL_PollEvent(&event)) {
97 env.state.Commit(*this);
99 env.counter.ExitHandle();
102 void Application::Handle(const SDL_Event &event) {
103 switch (event.type) {
104 case SDL_WINDOWEVENT:
105 Handle(event.window);
108 GetState().Handle(event);
113 void Application::Handle(const SDL_WindowEvent &event) {
114 switch (event.event) {
115 case SDL_WINDOWEVENT_FOCUS_GAINED:
116 env.window.GrabMouse();
118 case SDL_WINDOWEVENT_FOCUS_LOST:
119 env.window.ReleaseMouse();
121 case SDL_WINDOWEVENT_RESIZED:
122 env.viewport.Resize(event.data1, event.data2);
129 void Application::Update(int dt) {
130 env.counter.EnterUpdate();
132 GetState().Update(dt);
134 env.counter.ExitUpdate();
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();
144 GetState().Render(env.viewport);
147 env.counter.ExitRender();
152 void Application::PushState(State *s) {
153 if (!states.empty()) {
154 states.top()->OnPause();
158 if (s->ref_count == 1) {
164 State *Application::PopState() {
165 State *s = states.top();
169 if (!states.empty()) {
170 states.top()->OnResume();
175 State *Application::SwitchState(State *s_new) {
176 State *s_old = states.top();
177 states.top() = s_new;
181 if (s_old->ref_count == 0) {
184 if (s_new->ref_count == 1) {
191 State &Application::GetState() {
192 return *states.top();
195 bool Application::HasState() const noexcept {
196 return !states.empty();
200 void StateControl::Commit(Application &app) {
201 while (!cue.empty()) {
206 app.PushState(m.state);
209 app.SwitchState(m.state);
215 while (app.HasState()) {
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)) {
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 }});
242 void Assets::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry ®) const {
243 string full = data + set_name + ".types";
244 std::ifstream file(full);
246 throw std::runtime_error("failed to open block type file " + full);
248 TokenStreamReader in(file);
253 while (in.HasMore()) {
254 in.ReadIdentifier(type_name);
255 in.Skip(Token::EQUALS);
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") {
270 } else if (tex_name == "rock-2") {
272 } else if (tex_name == "rock-3") {
274 } else if (tex_name == "debug") {
277 throw runtime_error("unknown texture: " + tex_name);
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 };
303 throw runtime_error("unknown block shape: " + shape_name);
306 throw runtime_error("unknown block property: " + name);
308 in.Skip(Token::SEMICOLON);
310 in.Skip(Token::ANGLE_BRACKET_CLOSE);
311 in.Skip(Token::SEMICOLON);
317 Font Assets::LoadFont(const string &name, int size) const {
318 string full = fonts + name + ".ttf";
319 return Font(full.c_str(), size);
322 Sound Assets::LoadSound(const string &name) const {
323 string full = sounds + name + ".wav";
324 return Sound(full.c_str());
327 Texture Assets::LoadTexture(const string &name) const {
328 string full = textures + name + ".png";
330 SDL_Surface *srf = IMG_Load(full.c_str());
332 throw SDLError("IMG_Load");
336 SDL_FreeSurface(srf);
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());
344 throw SDLError("IMG_Load");
348 tex.Data(layer, *srf);
350 SDL_FreeSurface(srf);
353 SDL_FreeSurface(srf);
357 void FrameCounter::EnterFrame() noexcept {
358 last_enter = SDL_GetTicks();
359 last_tick = last_enter;
362 void FrameCounter::EnterHandle() noexcept {
366 void FrameCounter::ExitHandle() noexcept {
367 current.handle = Tick();
370 void FrameCounter::EnterUpdate() noexcept {
374 void FrameCounter::ExitUpdate() noexcept {
375 current.update = Tick();
378 void FrameCounter::EnterRender() noexcept {
382 void FrameCounter::ExitRender() noexcept {
383 current.render = Tick();
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;
394 if (cur_frame >= NUM_FRAMES) {
403 int FrameCounter::Tick() noexcept {
404 Uint32 now = SDL_GetTicks();
405 int delta = now - last_tick;
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;
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);
425 current = Frame<int>();
428 void FrameCounter::Push() noexcept {
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;