diff --git a/dev/tests/example.lua b/dev/tests/example.lua index f030222d..e69de29b 100644 --- a/dev/tests/example.lua +++ b/dev/tests/example.lua @@ -1,3 +0,0 @@ -print("Hello from the example test!") -test.sleep(1) -print("2") diff --git a/src/Mainloop.cpp b/src/Mainloop.cpp new file mode 100644 index 00000000..180e75f5 --- /dev/null +++ b/src/Mainloop.cpp @@ -0,0 +1,30 @@ +#include "Mainloop.hpp" + +#include "debug/Logger.hpp" +#include "engine.hpp" +#include "frontend/screens/MenuScreen.hpp" +#include "window/Window.hpp" + +static debug::Logger logger("mainloop"); + +Mainloop::Mainloop(Engine& engine) : engine(engine) { +} + +void Mainloop::run() { + auto& time = engine.getTime(); + + logger.info() << "starting menu screen"; + engine.setScreen(std::make_shared(&engine)); + + logger.info() << "main loop started"; + while (!Window::isShouldClose()){ + time.update(Window::time()); + engine.updateFrontend(); + if (!Window::isIconified()) { + engine.renderFrame(); + } + engine.postUpdate(); + engine.nextFrame(); + } + logger.info() << "main loop stopped"; +} diff --git a/src/Mainloop.hpp b/src/Mainloop.hpp new file mode 100644 index 00000000..3cd2f608 --- /dev/null +++ b/src/Mainloop.hpp @@ -0,0 +1,11 @@ +#pragma once + +class Engine; + +class Mainloop { + Engine& engine; +public: + Mainloop(Engine& engine); + + void run(); +}; diff --git a/src/TestMainloop.cpp b/src/TestMainloop.cpp new file mode 100644 index 00000000..617a65cd --- /dev/null +++ b/src/TestMainloop.cpp @@ -0,0 +1,31 @@ +#include "TestMainloop.hpp" + +#include "logic/scripting/scripting.hpp" +#include "interfaces/Process.hpp" +#include "debug/Logger.hpp" +#include "engine.hpp" + +static debug::Logger logger("mainloop"); + +inline constexpr int TPS = 20; + +TestMainloop::TestMainloop(Engine& engine) : engine(engine) { +} + +void TestMainloop::run() { + const auto& coreParams = engine.getCoreParameters(); + auto& time = engine.getTime(); + + if (coreParams.testFile.empty()) { + logger.info() << "nothing to do"; + return; + } + + logger.info() << "starting test " << coreParams.testFile; + auto process = scripting::start_coroutine(coreParams.testFile); + while (process->isActive()) { + time.step(1.0f / static_cast(TPS)); + process->update(); + } + logger.info() << "test finished"; +} diff --git a/src/TestMainloop.hpp b/src/TestMainloop.hpp new file mode 100644 index 00000000..b0acc9a7 --- /dev/null +++ b/src/TestMainloop.hpp @@ -0,0 +1,11 @@ +#pragma once + +class Engine; + +class TestMainloop { + Engine& engine; +public: + TestMainloop(Engine& engine); + + void run(); +}; diff --git a/src/Time.hpp b/src/Time.hpp new file mode 100644 index 00000000..57fb5c22 --- /dev/null +++ b/src/Time.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +class Time { + uint64_t frame = 0; + double lastTime = 0.0; + double delta = 0.0; +public: + Time() {} + + void update(double currentTime) { + frame++; + delta = currentTime - lastTime; + lastTime = currentTime; + } + + void step(double delta) { + frame++; + lastTime += delta; + } + + void set(double currentTime) { + lastTime = currentTime; + } + + double getDelta() const { + return delta; + } + + double getTime() const { + return lastTime; + } +}; diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 9ed192b5..72d4540f 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -146,9 +146,7 @@ void ContentPack::scanFolder( std::vector ContentPack::worldPacksList(const fs::path& folder) { fs::path listfile = folder / fs::path("packs.list"); if (!fs::is_regular_file(listfile)) { - std::cerr << "warning: packs.list not found (will be created)"; - std::cerr << std::endl; - files::write_string(listfile, "# autogenerated, do not modify\nbase\n"); + throw std::runtime_error("missing file 'packs.list'"); } return files::read_list(listfile); } diff --git a/src/engine.cpp b/src/engine.cpp index 8f970892..622a2fdb 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -20,7 +20,6 @@ #include "frontend/screens/Screen.hpp" #include "frontend/screens/MenuScreen.hpp" #include "graphics/render/ModelsGenerator.hpp" -#include "graphics/core/Batch2D.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/ImageData.hpp" #include "graphics/core/Shader.hpp" @@ -36,7 +35,8 @@ #include "window/Events.hpp" #include "window/input.hpp" #include "window/Window.hpp" -#include "interfaces/Process.hpp" +#include "Mainloop.hpp" +#include "TestMainloop.hpp" #include #include @@ -92,6 +92,7 @@ Engine::Engine(CoreParameters coreParameters) if (Window::initialize(&settings.display)){ throw initialize_error("could not initialize window"); } + time.set(Window::time()); if (auto icon = load_icon(resdir)) { icon->flipY(); Window::setIcon(icon.get()); @@ -153,13 +154,6 @@ void Engine::onAssetsLoaded() { gui->onAssetsLoad(assets.get()); } -void Engine::updateTimers() { - frame++; - double currentTime = Window::time(); - delta = currentTime - lastTime; - lastTime = currentTime; -} - void Engine::updateHotkeys() { if (Events::jpressed(keycode::F2)) { saveScreenshot(); @@ -179,70 +173,40 @@ void Engine::saveScreenshot() { void Engine::run() { if (params.headless) { - runTest(); + TestMainloop(*this).run(); } else { - mainloop(); + Mainloop(*this).run(); } } -void Engine::runTest() { - if (params.testFile.empty()) { - logger.info() << "nothing to do"; - return; - } - int tps = 20; - - logger.info() << "starting test " << params.testFile; - auto process = scripting::start_coroutine(params.testFile); - while (process->isActive()) { - frame++; - delta = 1.0f / static_cast(tps); - lastTime += delta; - - process->update(); - } - logger.info() << "test finished"; +void Engine::postUpdate() { + network->update(); + processPostRunnables(); } -void Engine::mainloop() { - logger.info() << "starting menu screen"; - setScreen(std::make_shared(this)); - - Batch2D batch(1024); - lastTime = Window::time(); - - logger.info() << "engine started"; - while (!Window::isShouldClose()){ - assert(screen != nullptr); - updateTimers(); - updateHotkeys(); - audio::update(delta); - - gui->act(delta, Viewport(Window::width, Window::height)); - screen->update(delta); - - if (!Window::isIconified()) { - renderFrame(batch); - } - Window::setFramerate( - Window::isIconified() && settings.display.limitFpsIconified.get() - ? 20 - : settings.display.framerate.get() - ); - - network->update(); - processPostRunnables(); - - Window::swapBuffers(); - Events::pollEvents(); - } +void Engine::updateFrontend() { + double delta = time.getDelta(); + updateHotkeys(); + audio::update(delta); + gui->act(delta, Viewport(Window::width, Window::height)); + screen->update(delta); } -void Engine::renderFrame(Batch2D& batch) { - screen->draw(delta); +void Engine::nextFrame() { + Window::setFramerate( + Window::isIconified() && settings.display.limitFpsIconified.get() + ? 20 + : settings.display.framerate.get() + ); + Window::swapBuffers(); + Events::pollEvents(); +} + +void Engine::renderFrame() { + screen->draw(time.getDelta()); Viewport viewport(Window::width, Window::height); - DrawContext ctx(nullptr, viewport, &batch); + DrawContext ctx(nullptr, viewport, nullptr); gui->draw(ctx, *assets); } @@ -333,32 +297,31 @@ void Engine::loadAssets() { } } assets = std::move(new_assets); - - if (content) { - for (auto& [name, def] : content->blocks.getDefs()) { - if (def->model == BlockModel::custom) { - if (def->modelName.empty()) { - assets->store( - std::make_unique( - ModelsGenerator::loadCustomBlockModel( - def->customModelRaw, *assets, !def->shadeless - ) - ), - name + ".model" - ); - def->modelName = def->name + ".model"; - } - } - } - for (auto& [name, def] : content->items.getDefs()) { + + if (content == nullptr) { + return; + } + for (auto& [name, def] : content->blocks.getDefs()) { + if (def->model == BlockModel::custom && def->modelName.empty()) { assets->store( std::make_unique( - ModelsGenerator::generate(*def, *content, *assets) + ModelsGenerator::loadCustomBlockModel( + def->customModelRaw, *assets, !def->shadeless + ) ), name + ".model" ); + def->modelName = def->name + ".model"; } } + for (auto& [name, def] : content->items.getDefs()) { + assets->store( + std::make_unique( + ModelsGenerator::generate(*def, *content, *assets) + ), + name + ".model" + ); + } } static void load_configs(const fs::path& root) { @@ -468,14 +431,6 @@ void Engine::loadAllPacks() { contentPacks = manager.getAll(manager.assembly(allnames)); } -double Engine::getDelta() const { - return delta; -} - -double Engine::getUptime() const { - return lastTime; -} - void Engine::setScreen(std::shared_ptr screen) { // reset audio channels (stop all sources) audio::reset_channel(audio::get_channel_index("regular")); @@ -545,6 +500,14 @@ network::Network& Engine::getNetwork() { return *network; } +Time& Engine::getTime() { + return time; +} + const CoreParameters& Engine::getCoreParameters() const { return params; } + +bool Engine::isHeadless() const { + return params.headless; +} diff --git a/src/engine.hpp b/src/engine.hpp index a0d4a8e9..fee92e81 100644 --- a/src/engine.hpp +++ b/src/engine.hpp @@ -11,6 +11,7 @@ #include "files/engine_paths.hpp" #include "files/settings_io.hpp" #include "util/ObjectsKeeper.hpp" +#include "Time.hpp" #include #include @@ -23,7 +24,6 @@ class Screen; class EnginePaths; class ResPaths; -class Batch2D; class EngineController; class SettingsHandler; struct EngineSettings; @@ -70,17 +70,12 @@ class Engine : public util::ObjectsKeeper { std::unique_ptr network; std::vector basePacks; std::unique_ptr gui; - - uint64_t frame = 0; - double lastTime = 0.0; - double delta = 0.0; + Time time; void loadControls(); void loadSettings(); void saveSettings(); - void updateTimers(); void updateHotkeys(); - void renderFrame(Batch2D& batch); void processPostRunnables(); void loadAssets(); public: @@ -90,11 +85,11 @@ public: /// @brief Start the engine void run(); - /// @brief Start main engine input/update/render loop. - /// Automatically sets MenuScreen - void mainloop(); + void postUpdate(); - void runTest(); + void updateFrontend(); + void renderFrame(); + void nextFrame(); /// @brief Called after assets loading when all engine systems are initialized void onAssetsLoaded(); @@ -122,11 +117,6 @@ public: /// @brief Collect all available content-packs from res/content void loadAllPacks(); - /// @brief Get current frame delta-time - double getDelta() const; - - double getUptime() const; - /// @brief Get active assets storage instance Assets* getAssets(); @@ -169,5 +159,9 @@ public: network::Network& getNetwork(); + Time& getTime(); + const CoreParameters& getCoreParameters() const; + + bool isHeadless() const; }; diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 9043f5b9..c9f08203 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -56,7 +56,7 @@ std::shared_ptr create_debug_panel( static std::wstring fpsString = L""; panel->listenInterval(0.016f, [engine]() { - fps = 1.0f / engine->getDelta(); + fps = 1.0f / engine->getTime().getDelta(); fpsMin = std::min(fps, fpsMin); fpsMax = std::max(fps, fpsMax); }); diff --git a/src/graphics/core/DrawContext.cpp b/src/graphics/core/DrawContext.cpp index 7062ad56..936de95b 100644 --- a/src/graphics/core/DrawContext.cpp +++ b/src/graphics/core/DrawContext.cpp @@ -92,6 +92,9 @@ DrawContext DrawContext::sub(Flushable* flushable) const { ctx.parent = this; ctx.flushable = flushable; ctx.scissorsCount = 0; + if (auto batch2D = dynamic_cast(flushable)) { + ctx.g2d = batch2D; + } return ctx; } diff --git a/src/graphics/core/DrawContext.hpp b/src/graphics/core/DrawContext.hpp index 736b053e..6c20f37f 100644 --- a/src/graphics/core/DrawContext.hpp +++ b/src/graphics/core/DrawContext.hpp @@ -10,7 +10,7 @@ class Framebuffer; class DrawContext { const DrawContext* parent; Viewport viewport; - Batch2D* const g2d; + Batch2D* g2d; Flushable* flushable = nullptr; Framebuffer* fbo = nullptr; bool depthMask = true; diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 38bc2257..49a362e3 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -23,7 +23,7 @@ using namespace gui; -GUI::GUI() { +GUI::GUI() : batch2D(std::make_unique(1024)) { container = std::make_shared(glm::vec2(1000)); uicamera = std::make_unique(glm::vec3(), Window::height); uicamera->perspective = false; @@ -198,7 +198,9 @@ void GUI::act(float delta, const Viewport& vp) { } void GUI::draw(const DrawContext& pctx, const Assets& assets) { - auto& viewport = pctx.getViewport(); + auto ctx = pctx.sub(batch2D.get()); + + auto& viewport = ctx.getViewport(); glm::vec2 wsize = viewport.size(); menu->setPos((wsize - menu->getSize()) / 2.0f); @@ -208,8 +210,8 @@ void GUI::draw(const DrawContext& pctx, const Assets& assets) { uishader->use(); uishader->uniformMatrix("u_projview", uicamera->getProjView()); - pctx.getBatch2D()->begin(); - container->draw(pctx, assets); + batch2D->begin(); + container->draw(ctx, assets); } std::shared_ptr GUI::getFocused() const { diff --git a/src/graphics/ui/GUI.hpp b/src/graphics/ui/GUI.hpp index c0dec649..851a49f7 100644 --- a/src/graphics/ui/GUI.hpp +++ b/src/graphics/ui/GUI.hpp @@ -13,6 +13,7 @@ class Viewport; class DrawContext; class Assets; class Camera; +class Batch2D; /* Some info about padding and margin. @@ -52,6 +53,7 @@ namespace gui { /// @brief The main UI controller class GUI { + std::unique_ptr batch2D; std::shared_ptr container; std::shared_ptr hover; std::shared_ptr pressed; diff --git a/src/logic/EngineController.cpp b/src/logic/EngineController.cpp index 4c38fc78..edd6efb7 100644 --- a/src/logic/EngineController.cpp +++ b/src/logic/EngineController.cpp @@ -132,13 +132,18 @@ static void show_content_missing( menus::show(engine, "reports/missing_content", {std::move(root)}); } -static bool loadWorldContent(Engine* engine, const fs::path& folder) { - return menus::call(engine, [engine, folder]() { +static bool load_world_content(Engine* engine, const fs::path& folder) { + if (engine->isHeadless()) { engine->loadWorldContent(folder); - }); + return true; + } else { + return menus::call(engine, [engine, folder]() { + engine->loadWorldContent(folder); + }); + } } -static void loadWorld(Engine* engine, const std::shared_ptr& worldFiles) { +static void load_world(Engine* engine, const std::shared_ptr& worldFiles) { try { auto content = engine->getContent(); auto& packs = engine->getContentPacks(); @@ -160,7 +165,12 @@ static void loadWorld(Engine* engine, const std::shared_ptr& worldFi void EngineController::openWorld(const std::string& name, bool confirmConvert) { auto paths = engine->getPaths(); auto folder = paths->getWorldsFolder() / fs::u8path(name); - if (!loadWorldContent(engine, folder)) { + auto worldFile = folder / fs::u8path("world.json"); + if (!fs::exists(worldFile)) { + throw std::runtime_error(worldFile.u8string() + " does not exists"); + } + + if (!load_world_content(engine, folder)) { return; } @@ -192,7 +202,7 @@ void EngineController::openWorld(const std::string& name, bool confirmConvert) { } return; } - loadWorld(engine, std::move(worldFiles)); + load_world(engine, std::move(worldFiles)); } inline uint64_t str2seed(const std::string& seedstr) { diff --git a/src/logic/scripting/lua/libs/libtime.cpp b/src/logic/scripting/lua/libs/libtime.cpp index 45ed4288..daac093f 100644 --- a/src/logic/scripting/lua/libs/libtime.cpp +++ b/src/logic/scripting/lua/libs/libtime.cpp @@ -5,11 +5,11 @@ using namespace scripting; static int l_uptime(lua::State* L) { - return lua::pushnumber(L, engine->getUptime()); + return lua::pushnumber(L, engine->getTime().getTime()); } static int l_delta(lua::State* L) { - return lua::pushnumber(L, engine->getDelta()); + return lua::pushnumber(L, engine->getTime().getDelta()); } const luaL_Reg timelib[] = {