1 #include "Application.hpp"
3 #include "Environment.hpp"
4 #include "FrameCounter.hpp"
6 #include "StateControl.hpp"
7 #include "TextureIndex.hpp"
10 #include "../audio/Sound.hpp"
11 #include "../graphics/ArrayTexture.hpp"
12 #include "../graphics/CubeMap.hpp"
13 #include "../graphics/Font.hpp"
14 #include "../graphics/Texture.hpp"
15 #include "../io/TokenStreamReader.hpp"
16 #include "../model/shapes.hpp"
17 #include "../world/BlockType.hpp"
18 #include "../world/BlockTypeRegistry.hpp"
19 #include "../world/Entity.hpp"
25 #include <SDL_image.h>
27 using std::runtime_error;
33 HeadlessApplication::HeadlessApplication(HeadlessEnvironment &e)
39 HeadlessApplication::~HeadlessApplication() {
44 Application::Application(Environment &e)
45 : HeadlessApplication(e)
50 Application::~Application() {
55 void HeadlessApplication::RunN(size_t n) {
56 Uint32 last = SDL_GetTicks();
57 for (size_t i = 0; HasState() && i < n; ++i) {
58 Uint32 now = SDL_GetTicks();
59 int delta = now - last;
65 void HeadlessApplication::RunT(size_t t) {
66 Uint32 last = SDL_GetTicks();
67 Uint32 finish = last + t;
68 while (HasState() && last < finish) {
69 Uint32 now = SDL_GetTicks();
70 int delta = now - last;
76 void HeadlessApplication::RunS(size_t n, size_t t) {
77 for (size_t i = 0; HasState() && i < n; ++i) {
81 std::cout << std::setfill(' ') << std::setw(5) << std::right << (i + 1) << std::endl;
83 std::cout << std::flush;
89 void HeadlessApplication::Run() {
90 Uint32 last = SDL_GetTicks();
92 Uint32 now = SDL_GetTicks();
93 int delta = now - last;
99 void HeadlessApplication::Loop(int dt) {
100 env.counter.EnterFrame();
102 if (!HasState()) return;
105 if (!HasState()) return;
106 env.counter.ExitFrame();
109 void Application::Loop(int dt) {
110 env.counter.EnterFrame();
112 if (!HasState()) return;
115 if (!HasState()) return;
117 env.counter.ExitFrame();
121 void HeadlessApplication::HandleEvents() {
122 env.counter.EnterHandle();
124 while (HasState() && SDL_PollEvent(&event)) {
128 env.counter.ExitHandle();
131 void HeadlessApplication::Handle(const SDL_Event &event) {
132 GetState().Handle(event);
136 void Application::HandleEvents() {
137 env.counter.EnterHandle();
139 while (HasState() && SDL_PollEvent(&event)) {
143 env.counter.ExitHandle();
146 void Application::Handle(const SDL_Event &event) {
147 switch (event.type) {
148 case SDL_WINDOWEVENT:
149 Handle(event.window);
152 GetState().Handle(event);
157 void Application::Handle(const SDL_WindowEvent &event) {
158 switch (event.event) {
159 case SDL_WINDOWEVENT_FOCUS_GAINED:
160 env.window.GrabMouse();
162 case SDL_WINDOWEVENT_FOCUS_LOST:
163 env.window.ReleaseMouse();
165 case SDL_WINDOWEVENT_RESIZED:
166 env.viewport.Resize(event.data1, event.data2);
173 void HeadlessApplication::Update(int dt) {
174 env.counter.EnterUpdate();
176 GetState().Update(dt);
178 env.counter.ExitUpdate();
181 void Application::Update(int dt) {
182 env.counter.EnterUpdate();
183 env.audio.Update(dt);
185 GetState().Update(dt);
187 env.counter.ExitUpdate();
190 void Application::Render() {
191 // gl implementation may (and will probably) delay vsync blocking until
192 // the first write after flipping, which is this clear call
193 env.viewport.Clear();
194 env.counter.EnterRender();
197 GetState().Render(env.viewport);
200 env.counter.ExitRender();
205 void HeadlessApplication::PushState(State *s) {
206 if (!states.empty()) {
207 states.top()->OnPause();
211 if (s->ref_count == 1) {
217 State *HeadlessApplication::PopState() {
218 State *s = states.top();
222 if (!states.empty()) {
223 states.top()->OnResume();
228 State *HeadlessApplication::SwitchState(State *s_new) {
229 State *s_old = states.top();
230 states.top() = s_new;
234 if (s_old->ref_count == 0) {
237 if (s_new->ref_count == 1) {
244 State &HeadlessApplication::GetState() {
245 return *states.top();
248 void HeadlessApplication::CommitStates() {
249 env.state.Commit(*this);
252 bool HeadlessApplication::HasState() const noexcept {
253 return !states.empty();
257 void StateControl::Commit(HeadlessApplication &app) {
258 while (!cue.empty()) {
263 app.PushState(m.state);
266 app.SwitchState(m.state);
272 while (app.HasState()) {
277 while (app.HasState() && &app.GetState() != m.state) {
282 while (app.HasState()) {
283 if (app.PopState() == m.state) {
292 AssetLoader::AssetLoader(const string &base)
293 : fonts(base + "fonts/")
294 , sounds(base + "sounds/")
295 , textures(base + "textures/")
296 , data(base + "data/") {
300 Assets::Assets(const AssetLoader &loader)
301 : large_ui_font(loader.LoadFont("DejaVuSans", 24))
302 , small_ui_font(loader.LoadFont("DejaVuSans", 16)) {
308 CuboidShape block_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }});
309 StairShape stair_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f });
310 CuboidShape slab_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }});
314 void AssetLoader::LoadBlockTypes(const std::string &set_name, BlockTypeRegistry ®, TextureIndex &tex_index) const {
315 string full = data + set_name + ".types";
316 std::ifstream file(full);
318 throw std::runtime_error("failed to open block type file " + full);
320 TokenStreamReader in(file);
325 while (in.HasMore()) {
326 in.ReadIdentifier(type_name);
327 in.Skip(Token::EQUALS);
331 in.Skip(Token::ANGLE_BRACKET_OPEN);
332 while (in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
333 in.ReadIdentifier(name);
334 in.Skip(Token::EQUALS);
335 if (name == "visible") {
336 type.visible = in.GetBool();
337 } else if (name == "texture") {
338 in.ReadString(tex_name);
339 type.texture = tex_index.GetID(tex_name);
340 } else if (name == "rgb_mod") {
341 in.ReadVec(type.rgb_mod);
342 } else if (name == "hsl_mod") {
343 in.ReadVec(type.hsl_mod);
344 } else if (name == "outline") {
345 in.ReadVec(type.outline_color);
346 } else if (name == "label") {
347 in.ReadString(type.label);
348 } else if (name == "luminosity") {
349 type.luminosity = in.GetInt();
350 } else if (name == "block_light") {
351 type.block_light = in.GetBool();
352 } else if (name == "collision") {
353 type.collision = in.GetBool();
354 } else if (name == "collide_block") {
355 type.collide_block = in.GetBool();
356 } else if (name == "generate") {
357 type.generate = in.GetBool();
358 } else if (name == "min_solidity") {
359 type.min_solidity = in.GetFloat();
360 } else if (name == "mid_solidity") {
361 type.mid_solidity = in.GetFloat();
362 } else if (name == "max_solidity") {
363 type.max_solidity = in.GetFloat();
364 } else if (name == "min_humidity") {
365 type.min_humidity = in.GetFloat();
366 } else if (name == "mid_humidity") {
367 type.mid_humidity = in.GetFloat();
368 } else if (name == "max_humidity") {
369 type.max_humidity = in.GetFloat();
370 } else if (name == "min_temperature") {
371 type.min_temperature = in.GetFloat();
372 } else if (name == "mid_temperature") {
373 type.mid_temperature = in.GetFloat();
374 } else if (name == "max_temperature") {
375 type.max_temperature = in.GetFloat();
376 } else if (name == "min_richness") {
377 type.min_richness = in.GetFloat();
378 } else if (name == "mid_richness") {
379 type.mid_richness = in.GetFloat();
380 } else if (name == "max_richness") {
381 type.max_richness = in.GetFloat();
382 } else if (name == "commonness") {
383 type.commonness = in.GetFloat();
384 } else if (name == "shape") {
385 in.ReadIdentifier(shape_name);
386 if (shape_name == "block") {
387 type.shape = &block_shape;
388 type.fill = { true, true, true, true, true, true };
389 } else if (shape_name == "slab") {
390 type.shape = &slab_shape;
391 type.fill = { false, true, false, false, false, false };
392 } else if (shape_name == "stair") {
393 type.shape = &stair_shape;
394 type.fill = { false, true, false, false, false, true };
396 throw runtime_error("unknown block shape: " + shape_name);
399 throw runtime_error("unknown block property: " + name);
401 in.Skip(Token::SEMICOLON);
403 in.Skip(Token::ANGLE_BRACKET_CLOSE);
404 in.Skip(Token::SEMICOLON);
410 CubeMap AssetLoader::LoadCubeMap(const string &name) const {
411 string full = textures + name;
412 string right = full + "-right.png";
413 string left = full + "-left.png";
414 string top = full + "-top.png";
415 string bottom = full + "-bottom.png";
416 string back = full + "-back.png";
417 string front = full + "-front.png";
423 if (!(srf = IMG_Load(right.c_str()))) throw SDLError("IMG_Load");
425 cm.Data(CubeMap::RIGHT, *srf);
427 SDL_FreeSurface(srf);
430 SDL_FreeSurface(srf);
432 if (!(srf = IMG_Load(left.c_str()))) throw SDLError("IMG_Load");
434 cm.Data(CubeMap::LEFT, *srf);
436 SDL_FreeSurface(srf);
439 SDL_FreeSurface(srf);
441 if (!(srf = IMG_Load(top.c_str()))) throw SDLError("IMG_Load");
443 cm.Data(CubeMap::TOP, *srf);
445 SDL_FreeSurface(srf);
448 SDL_FreeSurface(srf);
450 if (!(srf = IMG_Load(bottom.c_str()))) throw SDLError("IMG_Load");
452 cm.Data(CubeMap::BOTTOM, *srf);
454 SDL_FreeSurface(srf);
457 SDL_FreeSurface(srf);
459 if (!(srf = IMG_Load(back.c_str()))) throw SDLError("IMG_Load");
461 cm.Data(CubeMap::BACK, *srf);
463 SDL_FreeSurface(srf);
466 SDL_FreeSurface(srf);
468 if (!(srf = IMG_Load(front.c_str()))) throw SDLError("IMG_Load");
470 cm.Data(CubeMap::FRONT, *srf);
472 SDL_FreeSurface(srf);
475 SDL_FreeSurface(srf);
483 Font AssetLoader::LoadFont(const string &name, int size) const {
484 string full = fonts + name + ".ttf";
485 return Font(full.c_str(), size);
488 Sound AssetLoader::LoadSound(const string &name) const {
489 string full = sounds + name + ".wav";
490 return Sound(full.c_str());
493 Texture AssetLoader::LoadTexture(const string &name) const {
494 string full = textures + name + ".png";
496 SDL_Surface *srf = IMG_Load(full.c_str());
498 throw SDLError("IMG_Load");
502 SDL_FreeSurface(srf);
506 void AssetLoader::LoadTexture(const string &name, ArrayTexture &tex, int layer) const {
507 string full = textures + name + ".png";
508 SDL_Surface *srf = IMG_Load(full.c_str());
510 throw SDLError("IMG_Load");
514 tex.Data(layer, *srf);
516 SDL_FreeSurface(srf);
519 SDL_FreeSurface(srf);
522 void AssetLoader::LoadTextures(const TextureIndex &index, ArrayTexture &tex) const {
523 // TODO: where the hell should that size come from?
524 tex.Reserve(16, 16, index.Size(), Format());
525 for (const auto &entry : index.Entries()) {
526 LoadTexture(entry.first, tex, entry.second);
531 TextureIndex::TextureIndex()
536 int TextureIndex::GetID(const string &name) {
537 auto entry = id_map.find(name);
538 if (entry == id_map.end()) {
539 auto result = id_map.emplace(name, Size());
540 return result.first->second;
542 return entry->second;
547 void FrameCounter::EnterFrame() noexcept {
548 last_enter = SDL_GetTicks();
549 last_tick = last_enter;
552 void FrameCounter::EnterHandle() noexcept {
556 void FrameCounter::ExitHandle() noexcept {
557 current.handle = Tick();
560 void FrameCounter::EnterUpdate() noexcept {
564 void FrameCounter::ExitUpdate() noexcept {
565 current.update = Tick();
568 void FrameCounter::EnterRender() noexcept {
572 void FrameCounter::ExitRender() noexcept {
573 current.render = Tick();
576 void FrameCounter::ExitFrame() noexcept {
577 Uint32 now = SDL_GetTicks();
578 current.total = now - last_enter;
579 current.running = current.handle + current.update + current.render;
580 current.waiting = current.total - current.running;
584 if (cur_frame >= NUM_FRAMES) {
593 int FrameCounter::Tick() noexcept {
594 Uint32 now = SDL_GetTicks();
595 int delta = now - last_tick;
600 void FrameCounter::Accumulate() noexcept {
601 sum.handle += current.handle;
602 sum.update += current.update;
603 sum.render += current.render;
604 sum.running += current.running;
605 sum.waiting += current.waiting;
606 sum.total += current.total;
608 max.handle = std::max(current.handle, max.handle);
609 max.update = std::max(current.update, max.update);
610 max.render = std::max(current.render, max.render);
611 max.running = std::max(current.running, max.running);
612 max.waiting = std::max(current.waiting, max.waiting);
613 max.total = std::max(current.total, max.total);
615 current = Frame<int>();
618 void FrameCounter::Push() noexcept {
620 avg.handle = sum.handle * factor;
621 avg.update = sum.update * factor;
622 avg.render = sum.render * factor;
623 avg.running = sum.running * factor;
624 avg.waiting = sum.waiting * factor;
625 avg.total = sum.total * factor;