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/CubeMap.hpp"
12 #include "../graphics/Font.hpp"
13 #include "../graphics/Texture.hpp"
14 #include "../io/TokenStreamReader.hpp"
15 #include "../model/bounds.hpp"
16 #include "../model/Model.hpp"
17 #include "../model/ModelRegistry.hpp"
18 #include "../model/Shape.hpp"
19 #include "../model/ShapeRegistry.hpp"
20 #include "../shared/ResourceIndex.hpp"
21 #include "../world/BlockType.hpp"
22 #include "../world/BlockTypeRegistry.hpp"
23 #include "../world/Entity.hpp"
29 #include <SDL_image.h>
31 using std::runtime_error;
37 HeadlessApplication::HeadlessApplication(HeadlessEnvironment &e)
43 HeadlessApplication::~HeadlessApplication() {
48 Application::Application(Environment &e)
49 : HeadlessApplication(e)
54 Application::~Application() {
59 void HeadlessApplication::RunN(size_t n) {
60 Uint32 last = SDL_GetTicks();
61 for (size_t i = 0; HasState() && i < n; ++i) {
62 Uint32 now = SDL_GetTicks();
63 int delta = now - last;
69 void HeadlessApplication::RunT(size_t t) {
70 Uint32 last = SDL_GetTicks();
71 Uint32 finish = last + t;
72 while (HasState() && last < finish) {
73 Uint32 now = SDL_GetTicks();
74 int delta = now - last;
80 void HeadlessApplication::RunS(size_t n, size_t t) {
81 for (size_t i = 0; HasState() && i < n; ++i) {
85 std::cout << std::setfill(' ') << std::setw(5) << std::right << (i + 1) << std::endl;
87 std::cout << std::flush;
93 void HeadlessApplication::Run() {
94 Uint32 last = SDL_GetTicks();
96 Uint32 now = SDL_GetTicks();
97 int delta = now - last;
103 void HeadlessApplication::Loop(int dt) {
104 env.counter.EnterFrame();
106 if (!HasState()) return;
109 if (!HasState()) return;
110 env.counter.ExitFrame();
113 void Application::Loop(int dt) {
114 env.counter.EnterFrame();
116 if (!HasState()) return;
119 if (!HasState()) return;
121 env.counter.ExitFrame();
125 void HeadlessApplication::HandleEvents() {
126 env.counter.EnterHandle();
128 while (HasState() && SDL_PollEvent(&event)) {
132 env.counter.ExitHandle();
135 void HeadlessApplication::Handle(const SDL_Event &event) {
136 GetState().Handle(event);
140 void Application::HandleEvents() {
141 env.counter.EnterHandle();
143 while (HasState() && SDL_PollEvent(&event)) {
147 env.counter.ExitHandle();
150 void Application::Handle(const SDL_Event &event) {
151 switch (event.type) {
152 case SDL_WINDOWEVENT:
153 Handle(event.window);
156 GetState().Handle(event);
161 void Application::Handle(const SDL_WindowEvent &event) {
162 switch (event.event) {
163 case SDL_WINDOWEVENT_FOCUS_GAINED:
164 env.window.GrabMouse();
166 case SDL_WINDOWEVENT_FOCUS_LOST:
167 env.window.ReleaseMouse();
169 case SDL_WINDOWEVENT_RESIZED:
170 env.viewport.Resize(event.data1, event.data2);
177 void HeadlessApplication::Update(int dt) {
178 env.counter.EnterUpdate();
180 GetState().Update(dt);
182 env.counter.ExitUpdate();
185 void Application::Update(int dt) {
186 env.counter.EnterUpdate();
187 env.audio.Update(dt);
189 GetState().Update(dt);
191 env.counter.ExitUpdate();
194 void Application::Render() {
195 // gl implementation may (and will probably) delay vsync blocking until
196 // the first write after flipping, which is this clear call
197 env.viewport.Clear();
198 env.counter.EnterRender();
201 GetState().Render(env.viewport);
204 env.counter.ExitRender();
209 void HeadlessApplication::PushState(State *s) {
210 if (!states.empty()) {
211 states.top()->OnPause();
215 if (s->ref_count == 1) {
221 State *HeadlessApplication::PopState() {
222 State *s = states.top();
226 if (!states.empty()) {
227 states.top()->OnResume();
232 State *HeadlessApplication::SwitchState(State *s_new) {
233 State *s_old = states.top();
234 states.top() = s_new;
238 if (s_old->ref_count == 0) {
241 if (s_new->ref_count == 1) {
248 State &HeadlessApplication::GetState() {
249 return *states.top();
252 void HeadlessApplication::CommitStates() {
253 env.state.Commit(*this);
256 bool HeadlessApplication::HasState() const noexcept {
257 return !states.empty();
261 void StateControl::Commit(HeadlessApplication &app) {
262 while (!cue.empty()) {
267 app.PushState(m.state);
270 app.SwitchState(m.state);
276 while (app.HasState()) {
281 while (app.HasState() && &app.GetState() != m.state) {
286 while (app.HasState()) {
287 if (app.PopState() == m.state) {
296 AssetLoader::AssetLoader(const string &base)
297 : fonts(base + "fonts/")
298 , sounds(base + "sounds/")
299 , textures(base + "textures/")
300 , data(base + "data/") {
304 Assets::Assets(const AssetLoader &loader)
305 : large_ui_font(loader.LoadFont("DejaVuSans", 24))
306 , small_ui_font(loader.LoadFont("DejaVuSans", 16)) {
312 CuboidBounds block_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }});
313 StairBounds stair_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.5f, 0.5f }}, { 0.0f, 0.0f });
314 CuboidBounds slab_shape({{ -0.5f, -0.5f, -0.5f }, { 0.5f, 0.0f, 0.5f }});
318 void AssetLoader::LoadBlockTypes(
319 const string &set_name,
320 BlockTypeRegistry ®,
321 ResourceIndex &snd_index,
322 ResourceIndex &tex_index,
323 const ShapeRegistry &shapes
325 string full = data + set_name + ".types";
326 std::ifstream file(full);
328 throw std::runtime_error("failed to open block type file " + full);
330 TokenStreamReader in(file);
335 while (in.HasMore()) {
336 in.ReadIdentifier(type_name);
337 in.Skip(Token::EQUALS);
341 in.Skip(Token::ANGLE_BRACKET_OPEN);
342 while (in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
343 in.ReadIdentifier(name);
344 in.Skip(Token::EQUALS);
345 if (name == "visible") {
346 type.visible = in.GetBool();
347 } else if (name == "texture") {
348 in.ReadString(tex_name);
349 type.textures.push_back(tex_index.GetID(tex_name));
350 } else if (name == "textures") {
351 in.Skip(Token::BRACKET_OPEN);
352 while (in.Peek().type != Token::BRACKET_CLOSE) {
353 in.ReadString(tex_name);
354 type.textures.push_back(tex_index.GetID(tex_name));
355 if (in.Peek().type == Token::COMMA) {
356 in.Skip(Token::COMMA);
359 in.Skip(Token::BRACKET_CLOSE);
360 } else if (name == "rgb_mod") {
361 in.ReadVec(type.rgb_mod);
362 } else if (name == "hsl_mod") {
363 in.ReadVec(type.hsl_mod);
364 } else if (name == "outline") {
365 in.ReadVec(type.outline_color);
366 } else if (name == "label") {
367 in.ReadString(type.label);
368 } else if (name == "place_sound") {
369 in.ReadString(tex_name);
370 type.place_sound = snd_index.GetID(tex_name);
371 } else if (name == "remove_sound") {
372 in.ReadString(tex_name);
373 type.remove_sound = snd_index.GetID(tex_name);
374 } else if (name == "luminosity") {
375 type.luminosity = in.GetInt();
376 } else if (name == "block_light") {
377 type.block_light = in.GetBool();
378 } else if (name == "collision") {
379 type.collision = in.GetBool();
380 } else if (name == "collide_block") {
381 type.collide_block = in.GetBool();
382 } else if (name == "generate") {
383 type.generate = in.GetBool();
384 } else if (name == "min_solidity") {
385 type.min_solidity = in.GetFloat();
386 } else if (name == "mid_solidity") {
387 type.mid_solidity = in.GetFloat();
388 } else if (name == "max_solidity") {
389 type.max_solidity = in.GetFloat();
390 } else if (name == "min_humidity") {
391 type.min_humidity = in.GetFloat();
392 } else if (name == "mid_humidity") {
393 type.mid_humidity = in.GetFloat();
394 } else if (name == "max_humidity") {
395 type.max_humidity = in.GetFloat();
396 } else if (name == "min_temperature") {
397 type.min_temperature = in.GetFloat();
398 } else if (name == "mid_temperature") {
399 type.mid_temperature = in.GetFloat();
400 } else if (name == "max_temperature") {
401 type.max_temperature = in.GetFloat();
402 } else if (name == "min_richness") {
403 type.min_richness = in.GetFloat();
404 } else if (name == "mid_richness") {
405 type.mid_richness = in.GetFloat();
406 } else if (name == "max_richness") {
407 type.max_richness = in.GetFloat();
408 } else if (name == "commonness") {
409 type.commonness = in.GetFloat();
410 } else if (name == "shape") {
411 in.ReadIdentifier(shape_name);
412 type.shape = &shapes.Get(shape_name);
414 std::cerr << "warning: unknown block type property " << name << std::endl;
415 while (in.Peek().type != Token::SEMICOLON) {
419 in.Skip(Token::SEMICOLON);
421 in.Skip(Token::ANGLE_BRACKET_CLOSE);
422 in.Skip(Token::SEMICOLON);
428 CubeMap AssetLoader::LoadCubeMap(const string &name) const {
429 string full = textures + name;
430 string right = full + "-right.png";
431 string left = full + "-left.png";
432 string top = full + "-top.png";
433 string bottom = full + "-bottom.png";
434 string back = full + "-back.png";
435 string front = full + "-front.png";
441 if (!(srf = IMG_Load(right.c_str()))) throw SDLError("IMG_Load");
443 cm.Data(CubeMap::RIGHT, *srf);
445 SDL_FreeSurface(srf);
448 SDL_FreeSurface(srf);
450 if (!(srf = IMG_Load(left.c_str()))) throw SDLError("IMG_Load");
452 cm.Data(CubeMap::LEFT, *srf);
454 SDL_FreeSurface(srf);
457 SDL_FreeSurface(srf);
459 if (!(srf = IMG_Load(top.c_str()))) throw SDLError("IMG_Load");
461 cm.Data(CubeMap::TOP, *srf);
463 SDL_FreeSurface(srf);
466 SDL_FreeSurface(srf);
468 if (!(srf = IMG_Load(bottom.c_str()))) throw SDLError("IMG_Load");
470 cm.Data(CubeMap::BOTTOM, *srf);
472 SDL_FreeSurface(srf);
475 SDL_FreeSurface(srf);
477 if (!(srf = IMG_Load(back.c_str()))) throw SDLError("IMG_Load");
479 cm.Data(CubeMap::BACK, *srf);
481 SDL_FreeSurface(srf);
484 SDL_FreeSurface(srf);
486 if (!(srf = IMG_Load(front.c_str()))) throw SDLError("IMG_Load");
488 cm.Data(CubeMap::FRONT, *srf);
490 SDL_FreeSurface(srf);
493 SDL_FreeSurface(srf);
501 Font AssetLoader::LoadFont(const string &name, int size) const {
502 string full = fonts + name + ".ttf";
503 return Font(full.c_str(), size);
506 void AssetLoader::LoadModels(
507 const string &set_name,
508 ModelRegistry &models,
509 ResourceIndex &tex_index,
510 const ShapeRegistry &shapes
512 string full = data + set_name + ".models";
513 std::ifstream file(full);
515 throw std::runtime_error("failed to open model file " + full);
517 TokenStreamReader in(file);
520 while (in.HasMore()) {
521 in.ReadIdentifier(model_name);
522 in.Skip(Token::EQUALS);
523 in.Skip(Token::ANGLE_BRACKET_OPEN);
524 Model &model = models.Add(model_name);
525 while (in.HasMore() && in.Peek().type != Token::ANGLE_BRACKET_CLOSE) {
526 in.ReadIdentifier(prop_name);
527 in.Skip(Token::EQUALS);
528 if (prop_name == "root") {
529 model.RootPart().Read(in, tex_index, shapes);
530 } else if (prop_name == "eyes") {
531 model.SetEyes(in.GetULong());
533 while (in.HasMore() && in.Peek().type != Token::SEMICOLON) {
537 in.Skip(Token::SEMICOLON);
540 in.Skip(Token::ANGLE_BRACKET_CLOSE);
541 in.Skip(Token::SEMICOLON);
545 void AssetLoader::LoadShapes(const string &set_name, ShapeRegistry &shapes) const {
546 string full = data + set_name + ".shapes";
547 std::ifstream file(full);
549 throw std::runtime_error("failed to open shape file " + full);
551 TokenStreamReader in(file);
553 while (in.HasMore()) {
554 in.ReadIdentifier(shape_name);
555 in.Skip(Token::EQUALS);
556 Shape &shape = shapes.Add(shape_name);
558 in.Skip(Token::SEMICOLON);
562 Sound AssetLoader::LoadSound(const string &name) const {
563 string full = sounds + name + ".wav";
564 return Sound(full.c_str());
567 Texture AssetLoader::LoadTexture(const string &name) const {
568 string full = textures + name + ".png";
570 SDL_Surface *srf = IMG_Load(full.c_str());
572 throw SDLError("IMG_Load");
576 SDL_FreeSurface(srf);
580 void AssetLoader::LoadTexture(const string &name, ArrayTexture &tex, int layer) const {
581 string full = textures + name + ".png";
582 SDL_Surface *srf = IMG_Load(full.c_str());
584 throw SDLError("IMG_Load");
588 tex.Data(layer, *srf);
590 SDL_FreeSurface(srf);
593 SDL_FreeSurface(srf);
596 void AssetLoader::LoadTextures(const ResourceIndex &index, ArrayTexture &tex) const {
597 // TODO: where the hell should that size come from?
598 tex.Reserve(16, 16, index.Size(), Format());
599 for (const auto &entry : index.Entries()) {
600 LoadTexture(entry.first, tex, entry.second);
605 void FrameCounter::EnterFrame() noexcept {
606 last_enter = SDL_GetTicks();
607 last_tick = last_enter;
610 void FrameCounter::EnterHandle() noexcept {
614 void FrameCounter::ExitHandle() noexcept {
615 current.handle = Tick();
618 void FrameCounter::EnterUpdate() noexcept {
622 void FrameCounter::ExitUpdate() noexcept {
623 current.update = Tick();
626 void FrameCounter::EnterRender() noexcept {
630 void FrameCounter::ExitRender() noexcept {
631 current.render = Tick();
634 void FrameCounter::ExitFrame() noexcept {
635 Uint32 now = SDL_GetTicks();
636 current.total = now - last_enter;
637 current.running = current.handle + current.update + current.render;
638 current.waiting = current.total - current.running;
642 if (cur_frame >= NUM_FRAMES) {
651 int FrameCounter::Tick() noexcept {
652 Uint32 now = SDL_GetTicks();
653 int delta = now - last_tick;
658 void FrameCounter::Accumulate() noexcept {
659 sum.handle += current.handle;
660 sum.update += current.update;
661 sum.render += current.render;
662 sum.running += current.running;
663 sum.waiting += current.waiting;
664 sum.total += current.total;
666 max.handle = std::max(current.handle, max.handle);
667 max.update = std::max(current.update, max.update);
668 max.render = std::max(current.render, max.render);
669 max.running = std::max(current.running, max.running);
670 max.waiting = std::max(current.waiting, max.waiting);
671 max.total = std::max(current.total, max.total);
673 current = Frame<int>();
676 void FrameCounter::Push() noexcept {
678 avg.handle = sum.handle * factor;
679 avg.update = sum.update * factor;
680 avg.render = sum.render * factor;
681 avg.running = sum.running * factor;
682 avg.waiting = sum.waiting * factor;
683 avg.total = sum.total * factor;