diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index a41f95f9..c193fb18 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -48,6 +48,79 @@ Example: Content pack picture should be added as *icon.png* file. Recommended size: 128x128 -See *res/content/base* as an example of content pack structure. +# File System +Every loaded pack is mounted to the internal file system as a new mount point, whose name matches the pack's id. +This means that accessing pack files does not require the use of additional functions: + +```lua +print(file.read("your_pack_id:package.json")) -- will output the contents of the pack's package.json +``` + +This is also one of the reasons why some ids are reserved and cannot be used. + +Mount points are mounted as read-only. To gain write access, use the `pack.request_writeable` function. + +Read more about the [file](scripting/builtins/libfile.md) library. + +# Content Pack Structure + +Don't be intimidated by the following text, as a minimal pack only requires `package.json`. + +- Content: + - `block_materials/` - Block material definitions + - `blocks/` - Block definitions + - `items/` - Item definitions + - `generators/` - World generators + - `entities/` - Entity definitions + - `skeletons/` - Entity skeleton definitions + - `presets/` - Presets (can also be used by packs for their own purposes) + - `text3d/` - 3D Text + - `weather/` - Weather +- Code: + - `modules/` - Script modules + - `scripts/` - Content scripts, world scripts + - `components/` - Entity components +- Assets (Client-side resources): + - `fonts/` - Fonts + - `models/` - 3D Models + - `textures/` - Textures + - `shaders/` - Shaders + - `effects/` - Post-processing effects + - `sounds/` - Sounds and Music + - `texts/` - Localization files +- GUI: + - `layouts/` - UI Layouts + - `pages/` - Menu page layouts (for the pagebox element) +- Configuration: + - `config/` - Configuration files + - `defaults.toml` - Overrides for standard content bindings, such as the player entity, default generator, etc. + - `bindings.toml` - Keyboard/Mouse bindings + - `user-props.toml` - User properties for content definitions + - `devtools/` - Auxiliary files for internal debugging tools + - `content.json` - Automatically generated content lists, used for validating world indices and for conversion when mismatched + - `icon.png` - Pack icon + - `package.json` - Pack definition file + - `preload.json` - Asset preload lists for assets that are not loaded automatically; avoid listing assets unnecessarily + - `resources.json` - Definition lists for [resources](resources.md) (not to be confused with assets) + - `resource-aliases.json` - Files declaring aliases for [resources](resources.md) + +> [!WARNING] +> Manually editing `content.json` is strongly discouraged and will most likely lead to irreversible world corruption. + +# Content Sources + +Content packs are searched for within **content sources**, which are paths in the engine's file system. + +Source priority determines the scan order: if the same pack is found in multiple sources, the one belonging to the source with the highest priority will be selected. + +Content sources in descending order of priority: +- `world:content` - Content in the world folder (`world/content/`) +- `user:content` - Content in the user folder (`$HOME/.voxeng/content/`) +- `project:content` - Content in the project folder (`project/content/`) +- `res:content` - Built-in content shipped with the engine core (`res/content/`) + +I.e., if the same pack exists in both `world:content` and `user:content`, the version from `world:content` will be selected. + +The pack version, however, is currently not taken into account. diff --git a/doc/en/scripting/builtins/libhud.md b/doc/en/scripting/builtins/libhud.md index 58d78302..a7e3fa85 100644 --- a/doc/en/scripting/builtins/libhud.md +++ b/doc/en/scripting/builtins/libhud.md @@ -20,6 +20,12 @@ hud.open( [optional] invid: int ) -> int +-- Returns true if specified layout is open. +hud.is_open( + layoutid: str +) -> bool + + -- Open block UI and inventory. -- Throws an exception if block has no UI layout. -- Returns block inventory ID (if *"inventory-size"=0* a virtual diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 4245555f..32f9be59 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -50,5 +50,80 @@ Изображение контент-пака добавляется в виде файла *icon.png* в папку пака (не в textures). Рекомендованный размер изображения: 128x128 -Новые блоки добавляются в под-папку **blocks**, предметы в **items**, текстуры в **textures** -С примером файловой структуры лучше ознакомиться через базовый пакет (*res/content/base*) +# Файловая система + +Каждый загруженный пак монтируется к внутренней файловой системе как новая точка входа, имя которой совпадает с id пака. + +Это значит, что для доступа к файлам пака не требуется использование дополнительных функций: + +```lua +print(file.read("пак:package.json")) -- выведет содержимое package.json пака +``` + +Также это является одной из причин того, что некоторые id зарезервированы от использования. + +Точки входа монтируются как read-only. Для получения доступа к записи существует функция pack.request_writeable. + +Подробнее про библиотеку [file](scripting/builtins/libfile.md). + +# Структура контент-пака + +Пусть вас не пугает следующий текст, ведь минимальный пак содержит только package.json. + +- Контент: + - `block_materials/` - определения материалов блоков + - `blocks/` - определения блоков + - `items/` - определения предметов + - `generators/` - генераторы мира + - `entities/` - определения сущностей + - `skeletons/` - определения скелетов сущностей + - `presets/` - пресеты (может использоваться и паками в своих целях) + - `text3d/` - 3D текст + - `weather/` - погода +- Код: + - `modules/` - скриптовые модули + - `scripts/` - скрипты контента, мира + - `components/` - компоненты сущностей +- Ассеты (ресурсы клиентской стороны): + - `fonts/` - шрифты + - `models/` - 3D модели + - `textures/` - текстуры + - `shaders/` - шейдеры + - `effects/` - эффекты пост-обработки + - `sounds/` - звуки и музыка + - `texts/` - файлы локализации +- GUI: + - `layouts/` - макеты UI + - `pages/` - макеты страниц меню (элемент pagebox) +- Конфигурация: + - `config/` - файлы конфигурации + - `defaults.toml` - переопределения стандартных привязок к контенту, такие как сущность игрока, генератор по-умолчанию и т.д. + - `bindings.toml` - привязки к клавишам / мыши + - `user-props.toml` - пользовательские свойства определений контента + - `devtools/` - вспомогательные файлы внутренних инструментов отладки + - `content.json` - генерируемые автоматически списки контента, используемое для проверки корректности индексов в мире и конвертации при несовпадении + - `icon.png` - иконка пака + - `package.json` - файл опеределения пака + - `preload.json` - списки предзагрузки ассетов, которые не загружаются автоматически, не следует указывать ассеты без надобности + - `resources.json` - списки определений [ресурсов](resources.md) (не путать с ассетами) + - `resource-aliases.json` - файлы объявления псевдонимов для [ресурсов](resources.md) + +> [!WARNING] +> Ручное редактирование `content.json` противопоказано и скорее всего приведёт к необратимой поломке миров. + +# Источники контента + +Поиск контент-паков производится среди **источников контента**, являющихся путями в файловой системе движка. + +Приоритет источника определяет порядок сканирования: если один и тот же пак найден в нескольких источниках, +будет выбран тот, что принадлежит источнику с наивысшим приоритетом. + +Источники контента в порядке убывания приоритета: +- `world:content` - контент папке с миром (`мир/content/`) +- `user:content` - контент в папке пользователя (`$HOME/.voxeng/content/`) +- `project:content` - контент в папке с проектом (`проект/content/`) +- `res:content` - встроенный контент, поставляемый вместе с ядром движка (`res/content/`) + +Т.е. если в `world:content` и в `user:content` есть один и тот же пак, будет выбрана версия из `world:content`. + +Версия пака, при этом, на данный момент, не учитывается. diff --git a/doc/ru/scripting/builtins/libhud.md b/doc/ru/scripting/builtins/libhud.md index 5f0856b3..6ed81e05 100644 --- a/doc/ru/scripting/builtins/libhud.md +++ b/doc/ru/scripting/builtins/libhud.md @@ -20,6 +20,10 @@ hud.open( [опционально] invid: int ) -> int +-- Возвращает true если указаный макет UI открыт. +hud.is_open( + layoutid: str +) -> bool -- Открывает инвентарь и UI блока. -- Если блок не имеет макета UI - бросается исключение. diff --git a/res/modules/internal/events.lua b/res/modules/internal/events.lua index 7635af25..a0cd8276 100644 --- a/res/modules/internal/events.lua +++ b/res/modules/internal/events.lua @@ -2,7 +2,14 @@ local events = { handlers = {} } +local __parse_path = parse_path +local __pack_is_installed = pack.is_installed + function events.on(event, func) + local prefix = __parse_path(event) + if prefix ~= "core" and not __pack_is_installed(prefix) then + error("pack prefix required") + end if events.handlers[event] == nil then events.handlers[event] = {} end diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index a81c1232..a9199132 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -114,7 +114,6 @@ events = require "core:internal/events" function pack.unload(prefix) events.remove_by_prefix(prefix) - __vc__pack_envs[prefix] = nil end function __vc_start_app_script(path) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 63409c36..4af92e1c 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -230,15 +230,37 @@ function require(path) return __load_script(prefix .. ":modules/" .. file .. ".lua", nil, env) end -function __scripts_cleanup() +-- TODO: move to string +local function join(t, sep) + local s = "" + for i, v in ipairs(t) do + s = s..tostring(v) + if i < #t then + s = s..sep + end + end + return s +end + +function __scripts_cleanup(non_reset_packs) debug.log("cleaning scripts cache") + if #non_reset_packs == 0 then + debug.log("no non-reset packs") + else + debug.log("non-reset packs: "..join(non_reset_packs, ", ")) + end for k, v in pairs(__cached_scripts) do local packname, _ = parse_path(k) + if table.has(non_reset_packs, packname) then + goto continue + end if packname ~= "core" then debug.log("unloaded "..k) __cached_scripts[k] = nil package.loaded[k] = nil end + __vc__pack_envs[packname] = nil + ::continue:: end end diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 8dc7c31c..0fde22fc 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -9,7 +9,7 @@ #include "content/Content.hpp" #include "content/ContentPack.hpp" #include "debug/Logger.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "io/io.hpp" #include "graphics/core/Texture.hpp" #include "logic/scripting/scripting.hpp" diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 4fda3b0d..a1c04dfc 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -15,7 +15,7 @@ #include "coders/vec3.hpp" #include "constants.hpp" #include "debug/Logger.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "io/io.hpp" #include "frontend/UiDocument.hpp" #include "graphics/core/Atlas.hpp" diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index 5d53cb35..fa1335f3 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -6,7 +6,7 @@ #include #include "debug/Logger.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "typedefs.hpp" #include "util/stringutil.hpp" #include "coders/json.hpp" diff --git a/src/content/ContentControl.cpp b/src/content/ContentControl.cpp index 9c99135b..c887baa6 100644 --- a/src/content/ContentControl.cpp +++ b/src/content/ContentControl.cpp @@ -1,7 +1,7 @@ #include "ContentControl.hpp" #include "io/io.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "Content.hpp" #include "ContentPack.hpp" #include "ContentBuilder.hpp" @@ -30,6 +30,7 @@ ContentControl::ContentControl( manager->setSources({ "world:content", "user:content", + "project:content", "res:content", }); } @@ -48,10 +49,10 @@ std::vector& ContentControl::getBasePacks() { return basePacks; } -void ContentControl::resetContent() { +void ContentControl::resetContent(const std::vector& nonReset) { paths.setCurrentWorldFolder(""); - scripting::cleanup(); + scripting::cleanup(nonReset); std::vector resRoots; { auto pack = ContentPack::createCore(); @@ -78,8 +79,6 @@ void ContentControl::loadContent(const std::vector& names) { } void ContentControl::loadContent() { - scripting::cleanup(); - std::vector names; for (auto& pack : contentPacks) { names.push_back(pack.id); diff --git a/src/content/ContentControl.hpp b/src/content/ContentControl.hpp index ef45a04a..f066601e 100644 --- a/src/content/ContentControl.hpp +++ b/src/content/ContentControl.hpp @@ -34,7 +34,7 @@ public: std::vector& getBasePacks(); /// @brief Reset content to base packs list - void resetContent(); + void resetContent(const std::vector& nonReset); void loadContent(const std::vector& names); diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index ea29e27c..a88146be 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -13,7 +13,7 @@ #include "objects/rigging.hpp" #include "util/listutil.hpp" #include "util/stringutil.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" static debug::Logger logger("content-loader"); diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 021f083e..d2ffdc9e 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -8,7 +8,7 @@ #include "coders/json.hpp" #include "constants.hpp" #include "data/dv.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "io/io.hpp" #include "coders/commons.hpp" #include "debug/Logger.hpp" diff --git a/src/content/loading/GeneratorLoader.cpp b/src/content/loading/GeneratorLoader.cpp index 2f8f40d5..668425e0 100644 --- a/src/content/loading/GeneratorLoader.cpp +++ b/src/content/loading/GeneratorLoader.cpp @@ -6,7 +6,7 @@ #include "../ContentPack.hpp" #include "io/io.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "logic/scripting/scripting.hpp" #include "util/stringutil.hpp" #include "world/generator/GeneratorDef.hpp" diff --git a/src/core_defs.cpp b/src/core_defs.cpp index 79551cd4..a72d6eef 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -4,7 +4,7 @@ #include "content/Content.hpp" #include "content/ContentBuilder.hpp" #include "io/io.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "window/input.hpp" #include "voxels/Block.hpp" #include "coders/toml.hpp" diff --git a/src/devtools/Editor.cpp b/src/devtools/Editor.cpp index 82475ee4..0bd0af7d 100644 --- a/src/devtools/Editor.cpp +++ b/src/devtools/Editor.cpp @@ -1,7 +1,7 @@ #include "Editor.hpp" #include "engine/Engine.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "coders/syntax_parser.hpp" #include "SyntaxProcessor.hpp" diff --git a/src/devtools/Project.cpp b/src/devtools/Project.cpp index 881f6cc7..61e99c6a 100644 --- a/src/devtools/Project.cpp +++ b/src/devtools/Project.cpp @@ -1,8 +1,13 @@ #include "Project.hpp" #include "data/dv_util.hpp" +#include "debug/Logger.hpp" +#include "io/io.hpp" +#include "io/path.hpp" #include "logic/scripting/scripting.hpp" +static debug::Logger logger("project"); + Project::~Project() = default; dv::value Project::serialize() const { @@ -18,3 +23,23 @@ void Project::deserialize(const dv::value& src) { src.at("title").get(title); dv::get(src, "base_packs", basePacks); } + +void Project::loadProjectClientScript() { + io::path scriptFile = "project:project_client.lua"; + if (io::exists(scriptFile)) { + logger.info() << "starting project client script"; + clientScript = scripting::load_client_project_script(scriptFile); + } else { + logger.warning() << "project client script does not exists"; + } +} + +void Project::loadProjectStartScript() { + io::path scriptFile = "project:start.lua"; + if (io::exists(scriptFile)) { + logger.info() << "starting project start script"; + setupCoroutine = scripting::start_app_script(scriptFile); + } else { + logger.warning() << "project start script does not exists"; + } +} diff --git a/src/devtools/Project.hpp b/src/devtools/Project.hpp index be9d88a3..f0851b30 100644 --- a/src/devtools/Project.hpp +++ b/src/devtools/Project.hpp @@ -4,6 +4,7 @@ #include #include +#include "interfaces/Process.hpp" #include "interfaces/Serializable.hpp" namespace scripting { @@ -15,9 +16,13 @@ struct Project : Serializable { std::string title; std::vector basePacks; std::unique_ptr clientScript; + std::unique_ptr setupCoroutine; ~Project(); dv::value serialize() const override; void deserialize(const dv::value& src) override; + + void loadProjectClientScript(); + void loadProjectStartScript(); }; diff --git a/src/engine/CoreParameters.hpp b/src/engine/CoreParameters.hpp new file mode 100644 index 00000000..c937bf3a --- /dev/null +++ b/src/engine/CoreParameters.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +struct CoreParameters { + bool headless = false; + bool testMode = false; + std::filesystem::path resFolder = "res"; + std::filesystem::path userFolder = "."; + std::filesystem::path scriptFile; + std::filesystem::path projectFolder; + std::string debugServerString; + int tps = 20; +}; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 3053b613..6598562f 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -8,8 +8,6 @@ #include "assets/AssetsLoader.hpp" #include "audio/audio.hpp" #include "coders/GLSLExtension.hpp" -#include "coders/imageio.hpp" -#include "coders/json.hpp" #include "coders/toml.hpp" #include "coders/commons.hpp" #include "devtools/Editor.hpp" @@ -18,28 +16,28 @@ #include "content/ContentControl.hpp" #include "core_defs.hpp" #include "io/io.hpp" +#include "io/settings_io.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" #include "frontend/screens/Screen.hpp" #include "graphics/render/ModelsGenerator.hpp" #include "graphics/core/DrawContext.hpp" -#include "graphics/core/ImageData.hpp" #include "graphics/core/Shader.hpp" #include "graphics/ui/GUI.hpp" #include "graphics/ui/elements/Menu.hpp" -#include "objects/rigging.hpp" #include "logic/EngineController.hpp" #include "logic/CommandsInterpreter.hpp" #include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting_hud.hpp" #include "network/Network.hpp" #include "util/platform.hpp" -#include "window/Camera.hpp" #include "window/input.hpp" #include "window/Window.hpp" #include "world/Level.hpp" #include "Mainloop.hpp" #include "ServerMainloop.hpp" +#include "WindowControl.hpp" +#include "EnginePaths.hpp" #include #include @@ -50,29 +48,6 @@ static debug::Logger logger("engine"); -static std::unique_ptr load_icon() { - try { - auto file = "res:textures/misc/icon.png"; - if (io::exists(file)) { - return imageio::read(file); - } - } catch (const std::exception& err) { - logger.error() << "could not load window icon: " << err.what(); - } - return nullptr; -} - -static std::unique_ptr load_client_project_script() { - io::path scriptFile = "project:project_client.lua"; - if (io::exists(scriptFile)) { - logger.info() << "starting project script"; - return scripting::load_client_project_script(scriptFile); - } else { - logger.warning() << "project script does not exists"; - } - return nullptr; -} - Engine::Engine() = default; Engine::~Engine() = default; @@ -87,7 +62,7 @@ Engine& Engine::getInstance() { void Engine::onContentLoad() { editor->loadTools(); - langs::setup(langs::get_current(), paths.resPaths.collectRoots()); + langs::setup(langs::get_current(), paths->resPaths.collectRoots()); if (isHeadless()) { return; @@ -108,29 +83,9 @@ void Engine::onContentLoad() { } void Engine::initializeClient() { - std::string title = project->title; - if (title.empty()) { - title = "VoxelCore v" + - std::to_string(ENGINE_VERSION_MAJOR) + "." + - std::to_string(ENGINE_VERSION_MINOR); - } - if (ENGINE_DEBUG_BUILD) { - title += " [debug]"; - } - if (debuggingServer) { - title = "[debugging] " + title; - } - auto [window, input] = Window::initialize(&settings.display, title); - if (!window || !input){ - throw initialize_error("could not initialize window"); - } - window->setFramerate(settings.display.framerate.get()); + windowControl = std::make_unique(*this); + auto [window, input] = windowControl->initialize(); - time.set(window->time()); - if (auto icon = load_icon()) { - icon->flipY(); - window->setIcon(icon.get()); - } this->window = std::move(window); this->input = std::move(input); @@ -171,15 +126,12 @@ void Engine::initialize(CoreParameters coreParameters) { logger.info() << "engine version: " << ENGINE_VERSION_STRING; if (params.headless) { - logger.info() << "headless mode is enabled"; + logger.info() << "engine runs in headless mode"; } if (params.projectFolder.empty()) { params.projectFolder = params.resFolder; } - paths.setResourcesFolder(params.resFolder); - paths.setUserFilesFolder(params.userFolder); - paths.setProjectFolder(params.projectFolder); - paths.prepare(); + paths = std::make_unique(params); loadProject(); editor = std::make_unique(*this); @@ -197,10 +149,6 @@ void Engine::initialize(CoreParameters coreParameters) { ); } } - - if (!params.scriptFile.empty()) { - paths.setScriptFolder(params.scriptFile.parent_path()); - } loadSettings(); controller = std::make_unique(*this); @@ -214,7 +162,7 @@ void Engine::initialize(CoreParameters coreParameters) { langs::locale_by_envlocale(platform::detect_locale()) ); } - content = std::make_unique(*project, paths, *input, [this]() { + content = std::make_unique(*project, *paths, *input, [this]() { onContentLoad(); }); scripting::initialize(this); @@ -223,10 +171,13 @@ void Engine::initialize(CoreParameters coreParameters) { gui->setPageLoader(scripting::create_page_loader()); } keepAlive(settings.ui.language.observe([this](auto lang) { - langs::setup(lang, paths.resPaths.collectRoots()); + langs::setup(lang, paths->resPaths.collectRoots()); }, true)); - project->clientScript = load_client_project_script(); + project->loadProjectStartScript(); + if (!params.headless) { + project->loadProjectClientScript(); + } } void Engine::loadSettings() { @@ -256,29 +207,17 @@ void Engine::loadControls() { void Engine::updateHotkeys() { if (input->jpressed(Keycode::F2)) { - saveScreenshot(); + windowControl->saveScreenshot(); } if (input->pressed(Keycode::LEFT_CONTROL) && input->pressed(Keycode::F3) && input->jpressed(Keycode::U)) { gui->toggleDebug(); } if (input->jpressed(Keycode::F11)) { - if (settings.display.windowMode.get() != static_cast(WindowMode::FULLSCREEN)) { - settings.display.windowMode.set(static_cast(WindowMode::FULLSCREEN)); - } else { - settings.display.windowMode.set(static_cast(WindowMode::WINDOWED)); - } + windowControl->toggleFullscreen(); } } -void Engine::saveScreenshot() { - auto image = window->takeScreenshot(); - image->flipY(); - io::path filename = paths.getNewScreenshotFile("png"); - imageio::write(filename.string(), image.get()); - logger.info() << "saved screenshot as " << filename.string(); -} - void Engine::run() { if (params.headless) { ServerMainloop(*this).run(); @@ -301,6 +240,12 @@ void Engine::detachDebugger() { debuggingServer.reset(); } +void Engine::applicationTick() { + if (project->setupCoroutine && project->setupCoroutine->isActive()) { + project->setupCoroutine->update(); + } +} + void Engine::updateFrontend() { double delta = time.getDelta(); updateHotkeys(); @@ -310,14 +255,8 @@ void Engine::updateFrontend() { gui->postAct(); } -void Engine::nextFrame() { - window->setFramerate( - window->isIconified() && settings.display.limitFpsIconified.get() - ? 20 - : settings.display.framerate.get() - ); - window->swapBuffers(); - input->pollEvents(); +void Engine::nextFrame(bool waitForRefresh) { + windowControl->nextFrame(waitForRefresh); } void Engine::startPauseLoop() { @@ -336,7 +275,7 @@ void Engine::startPauseLoop() { if (isHeadless()) { platform::sleep(1.0 / params.tps * 1000); } else { - nextFrame(); + nextFrame(false); } } if (initialCursorLocked) { @@ -407,17 +346,18 @@ void Engine::setLevelConsumer(OnWorldOpen levelConsumer) { void Engine::loadAssets() { logger.info() << "loading assets"; - Shader::preprocessor->setPaths(&paths.resPaths); + Shader::preprocessor->setPaths(&paths->resPaths); auto content = this->content->get(); auto new_assets = std::make_unique(); - AssetsLoader loader(*this, *new_assets, paths.resPaths); + AssetsLoader loader(*this, *new_assets, paths->resPaths); AssetsLoader::addDefaults(loader, content); // no need // correct log messages order is more useful - bool threading = false; // look at two upper lines + // todo: before setting to true, check if GLSLExtension thread safe + bool threading = false; // look at three upper lines if (threading) { auto task = loader.startTask([=](){}); task->waitForEnd(); @@ -454,6 +394,7 @@ void Engine::setScreen(std::shared_ptr screen) { } if (project->clientScript && this->screen) { project->clientScript->onScreenChange(this->screen->getName(), true); + window->setShouldRefresh(); } } @@ -487,11 +428,11 @@ Assets* Engine::getAssets() { } EnginePaths& Engine::getPaths() { - return paths; + return *paths; } ResPaths& Engine::getResPaths() { - return paths.resPaths; + return paths->resPaths; } std::shared_ptr Engine::getScreen() { diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index 98a2c3ef..6b09b2ca 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -1,25 +1,27 @@ #pragma once -#include "delegates.hpp" -#include "typedefs.hpp" -#include "settings.hpp" - -#include "io/engine_paths.hpp" -#include "io/settings_io.hpp" -#include "util/ObjectsKeeper.hpp" +#include "CoreParameters.hpp" #include "PostRunnables.hpp" #include "Time.hpp" +#include "delegates.hpp" +#include "settings.hpp" +#include "typedefs.hpp" +#include "util/ObjectsKeeper.hpp" #include #include -class Window; class Assets; -class Level; -class Screen; class ContentControl; class EngineController; +class EnginePaths; class Input; +class Level; +class ResPaths; +class Screen; +class SettingsHandler; +class Window; +class WindowControl; struct Project; namespace gui { @@ -44,24 +46,12 @@ public: initialize_error(const std::string& message) : std::runtime_error(message) {} }; -struct CoreParameters { - bool headless = false; - bool testMode = false; - std::filesystem::path resFolder = "res"; - std::filesystem::path userFolder = "."; - std::filesystem::path scriptFile; - std::filesystem::path projectFolder; - std::string debugServerString; - int tps = 20; -}; - using OnWorldOpen = std::function, int64_t)>; class Engine : public util::ObjectsKeeper { CoreParameters params; EngineSettings settings; - EnginePaths paths; - + std::unique_ptr paths; std::unique_ptr project; std::unique_ptr settingsHandler; std::unique_ptr assets; @@ -75,6 +65,7 @@ class Engine : public util::ObjectsKeeper { std::unique_ptr gui; std::unique_ptr editor; std::unique_ptr debuggingServer; + std::unique_ptr windowControl; PostRunnables postRunnables; Time time; OnWorldOpen levelConsumer; @@ -105,9 +96,10 @@ public: void postUpdate(); + void applicationTick(); void updateFrontend(); void renderFrame(); - void nextFrame(); + void nextFrame(bool waitForRefresh); void startPauseLoop(); /// @brief Set screen (scene). @@ -143,8 +135,6 @@ public: postRunnables.postRunnable(callback); } - void saveScreenshot(); - EngineController* getController(); void setLevelConsumer(OnWorldOpen levelConsumer); diff --git a/src/io/engine_paths.cpp b/src/engine/EnginePaths.cpp similarity index 87% rename from src/io/engine_paths.cpp rename to src/engine/EnginePaths.cpp index 98363d9b..8a786140 100644 --- a/src/io/engine_paths.cpp +++ b/src/engine/EnginePaths.cpp @@ -1,44 +1,56 @@ -#include "engine_paths.hpp" +#include "EnginePaths.hpp" + +#include "debug/Logger.hpp" +#include "io/devices/StdfsDevice.hpp" +#include "io/devices/ZipFileDevice.hpp" +#include "maths/util.hpp" +#include "typedefs.hpp" +#include "util/platform.hpp" +#include "util/random.hpp" +#include "util/stringutil.hpp" +#include "world/files/WorldFiles.hpp" #include #include +#include #include #include -#include "typedefs.hpp" -#include "util/stringutil.hpp" -#include "util/platform.hpp" +#include #include -#include "io/devices/StdfsDevice.hpp" -#include "io/devices/ZipFileDevice.hpp" -#include "world/files/WorldFiles.hpp" -#include "debug/Logger.hpp" - -#include -#include "maths/util.hpp" - -template -static std::string generate_random_base64() { - auto now = std::chrono::high_resolution_clock::now(); - auto seed = now.time_since_epoch().count(); - - util::PseudoRandom random(seed); // fixme: replace with safe random - ubyte bytes[n]; - random.rand(bytes, n); - return util::base64_urlsafe_encode(bytes, n); -} - namespace fs = std::filesystem; -static debug::Logger logger("engine-paths"); +static std::random_device random_device; static inline io::path SCREENSHOTS_FOLDER = "user:screenshots"; static inline io::path CONTENT_FOLDER = "user:content"; static inline io::path WORLDS_FOLDER = "user:worlds"; -void EnginePaths::prepare() { +static debug::Logger logger("engine-paths"); + +template +static std::string generate_random_base64() { + auto randomEngine = util::seeded_random_engine(random_device); + static std::uniform_int_distribution dist(0, 0xFF); + ubyte bytes[n]; + for (size_t i = 0; i < n; i++) { + bytes[i] = dist(randomEngine); + } + return util::base64_urlsafe_encode(bytes, n); +} + +EnginePaths::EnginePaths(CoreParameters& params) + : resourcesFolder(params.resFolder), + userFilesFolder(params.userFolder), + projectFolder(params.projectFolder) { + if (!params.scriptFile.empty()) { + scriptFolder = params.scriptFile.parent_path(); + io::set_device("script", std::make_shared(*scriptFolder)); + } + io::set_device("res", std::make_shared(resourcesFolder, false)); io::set_device("user", std::make_shared(userFilesFolder)); + io::set_device("project", std::make_shared(projectFolder)); if (!io::is_directory("res:")) { throw std::runtime_error( @@ -59,15 +71,7 @@ void EnginePaths::prepare() { io::create_subdevice("config", "user", "config"); } -const std::filesystem::path& EnginePaths::getUserFilesFolder() const { - return userFilesFolder; -} - -const std::filesystem::path& EnginePaths::getResourcesFolder() const { - return resourcesFolder; -} - -io::path EnginePaths::getNewScreenshotFile(const std::string& ext) { +io::path EnginePaths::getNewScreenshotFile(const std::string& ext) const { auto folder = SCREENSHOTS_FOLDER; if (!io::is_directory(folder)) { io::create_directories(folder); @@ -95,10 +99,6 @@ io::path EnginePaths::getWorldsFolder() const { return WORLDS_FOLDER; } -io::path EnginePaths::getCurrentWorldFolder() { - return currentWorldFolder; -} - io::path EnginePaths::getWorldFolderByName(const std::string& name) { return getWorldsFolder() / name; } @@ -132,24 +132,6 @@ std::vector EnginePaths::scanForWorlds() const { return folders; } -void EnginePaths::setUserFilesFolder(std::filesystem::path folder) { - this->userFilesFolder = std::move(folder); -} - -void EnginePaths::setResourcesFolder(std::filesystem::path folder) { - this->resourcesFolder = std::move(folder); -} - -void EnginePaths::setScriptFolder(std::filesystem::path folder) { - io::set_device("script", std::make_shared(folder)); - this->scriptFolder = std::move(folder); -} - -void EnginePaths::setProjectFolder(std::filesystem::path folder) { - io::set_device("project", std::make_shared(folder)); - this->projectFolder = std::move(folder); -} - void EnginePaths::setCurrentWorldFolder(io::path folder) { if (folder.empty()) { io::remove_device("world"); diff --git a/src/io/engine_paths.hpp b/src/engine/EnginePaths.hpp similarity index 73% rename from src/io/engine_paths.hpp rename to src/engine/EnginePaths.hpp index 8b9b7f8e..41d02236 100644 --- a/src/io/engine_paths.hpp +++ b/src/engine/EnginePaths.hpp @@ -1,15 +1,15 @@ #pragma once +#include "io/io.hpp" +#include "data/dv.hpp" +#include "CoreParameters.hpp" + #include -#include #include #include #include #include -#include "io.hpp" -#include "data/dv.hpp" - struct PathsRoot { std::string name; io::path path; @@ -46,24 +46,13 @@ class EnginePaths { public: ResPaths resPaths; - void prepare(); - - void setUserFilesFolder(std::filesystem::path folder); - const std::filesystem::path& getUserFilesFolder() const; - - void setResourcesFolder(std::filesystem::path folder); - const std::filesystem::path& getResourcesFolder() const; - - void setScriptFolder(std::filesystem::path folder); - - void setProjectFolder(std::filesystem::path folder); + EnginePaths(CoreParameters& params); io::path getWorldFolderByName(const std::string& name); io::path getWorldsFolder() const; void setCurrentWorldFolder(io::path folder); - io::path getCurrentWorldFolder(); - io::path getNewScreenshotFile(const std::string& ext); + io::path getNewScreenshotFile(const std::string& ext) const; std::string mount(const io::path& file); void unmount(const std::string& name); @@ -80,9 +69,9 @@ public: static inline io::path CONTROLS_FILE = "user:controls.toml"; static inline io::path SETTINGS_FILE = "user:settings.toml"; private: - std::filesystem::path userFilesFolder {"."}; - std::filesystem::path resourcesFolder {"res"}; - std::filesystem::path projectFolder = resourcesFolder; + std::filesystem::path resourcesFolder; + std::filesystem::path userFilesFolder; + std::filesystem::path projectFolder; io::path currentWorldFolder; std::optional scriptFolder; std::vector entryPoints; diff --git a/src/engine/Mainloop.cpp b/src/engine/Mainloop.cpp index a14c5464..d6ca17cb 100644 --- a/src/engine/Mainloop.cpp +++ b/src/engine/Mainloop.cpp @@ -18,6 +18,7 @@ Mainloop::Mainloop(Engine& engine) : engine(engine) { void Mainloop::run() { auto& time = engine.getTime(); auto& window = engine.getWindow(); + auto& settings = engine.getSettings(); engine.setLevelConsumer([this](auto level, int64_t localPlayer) { if (level == nullptr) { @@ -38,13 +39,17 @@ void Mainloop::run() { logger.info() << "main loop started"; while (!window.isShouldClose()){ time.update(window.time()); + engine.applicationTick(); engine.updateFrontend(); if (!window.isIconified()) { engine.renderFrame(); } engine.postUpdate(); - engine.nextFrame(); + engine.nextFrame( + settings.display.adaptiveFpsInMenu.get() && + dynamic_cast(engine.getScreen().get()) != nullptr + ); } logger.info() << "main loop stopped"; } diff --git a/src/engine/ServerMainloop.cpp b/src/engine/ServerMainloop.cpp index ab35ffbd..41887aee 100644 --- a/src/engine/ServerMainloop.cpp +++ b/src/engine/ServerMainloop.cpp @@ -1,6 +1,7 @@ #include "ServerMainloop.hpp" #include "Engine.hpp" +#include "EnginePaths.hpp" #include "logic/scripting/scripting.hpp" #include "logic/LevelController.hpp" #include "interfaces/Process.hpp" @@ -60,6 +61,7 @@ void ServerMainloop::run() { controller->getLevel()->getWorld()->updateTimers(delta); controller->update(glm::min(delta, 0.2), false); } + engine.applicationTick(); engine.postUpdate(); if (!coreParams.testMode) { diff --git a/src/engine/WindowControl.cpp b/src/engine/WindowControl.cpp new file mode 100644 index 00000000..5ae96965 --- /dev/null +++ b/src/engine/WindowControl.cpp @@ -0,0 +1,93 @@ +#include "WindowControl.hpp" + +#include "Engine.hpp" +#include "engine/EnginePaths.hpp" +#include "devtools/Project.hpp" +#include "coders/imageio.hpp" +#include "window/Window.hpp" +#include "window/input.hpp" +#include "debug/Logger.hpp" +#include "graphics/core/ImageData.hpp" +#include "util/platform.hpp" + +static debug::Logger logger("window-control"); + +namespace { + static std::unique_ptr load_icon() { + try { + auto file = "res:textures/misc/icon.png"; + if (io::exists(file)) { + return imageio::read(file); + } + } catch (const std::exception& err) { + logger.error() << "could not load window icon: " << err.what(); + } + return nullptr; + } +} + +WindowControl::WindowControl(Engine& engine) : engine(engine) {} + +WindowControl::Result WindowControl::initialize() { + const auto& project = engine.getProject(); + auto& settings = engine.getSettings(); + + std::string title = project.title; + if (title.empty()) { + title = "VoxelCore v" + + std::to_string(ENGINE_VERSION_MAJOR) + "." + + std::to_string(ENGINE_VERSION_MINOR); + } + if (ENGINE_DEBUG_BUILD) { + title += " [debug]"; + } + if (engine.getDebuggingServer()) { + title = "[debugging] " + title; + } + + auto [window, input] = Window::initialize(&settings.display, title); + if (!window || !input){ + throw initialize_error("could not initialize window"); + } + window->setFramerate(settings.display.framerate.get()); + if (auto icon = load_icon()) { + icon->flipY(); + window->setIcon(icon.get()); + } + + return Result {std::move(window), std::move(input)}; +} + +void WindowControl::saveScreenshot() { + auto& window = engine.getWindow(); + const auto& paths = engine.getPaths(); + + auto image = window.takeScreenshot(); + image->flipY(); + io::path filename = paths.getNewScreenshotFile("png"); + imageio::write(filename.string(), image.get()); + logger.info() << "saved screenshot as " << filename.string(); +} + +void WindowControl::toggleFullscreen() { + auto& settings = engine.getSettings(); + auto& windowMode = settings.display.windowMode; + if (windowMode.get() != static_cast(WindowMode::FULLSCREEN)) { + windowMode.set(static_cast(WindowMode::FULLSCREEN)); + } else { + windowMode.set(static_cast(WindowMode::WINDOWED)); + } +} + +void WindowControl::nextFrame(bool waitForRefresh) { + const auto& settings = engine.getSettings(); + auto& window = engine.getWindow(); + auto& input = engine.getInput(); + window.setFramerate( + window.isIconified() && settings.display.limitFpsIconified.get() + ? 20 + : settings.display.framerate.get() + ); + window.swapBuffers(); + input.pollEvents(waitForRefresh && !window.checkShouldRefresh()); +} diff --git a/src/engine/WindowControl.hpp b/src/engine/WindowControl.hpp new file mode 100644 index 00000000..2bea138e --- /dev/null +++ b/src/engine/WindowControl.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +class Window; +class Input; +class Engine; + +class WindowControl { +public: + struct Result { + std::unique_ptr window; + std::unique_ptr input; + }; + WindowControl(Engine& engine); + + Result initialize(); + + void nextFrame(bool waitForRefresh); + + void saveScreenshot(); + + void toggleFullscreen(); +private: + Engine& engine; +}; diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index c8301d26..89789e29 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -756,3 +756,13 @@ void Hud::setAllowPause(bool flag) { } allowPause = flag; } + +bool Hud::isOpen(const std::string& layoutid) const { + for (const auto& element : elements) { + auto doc = element.getDocument(); + if (doc && doc->getId() == layoutid) { + return true; + } + } + return false; +} diff --git a/src/frontend/hud.hpp b/src/frontend/hud.hpp index c0c7d1ef..560382d3 100644 --- a/src/frontend/hud.hpp +++ b/src/frontend/hud.hpp @@ -213,6 +213,8 @@ public: void setAllowPause(bool flag); + bool isOpen(const std::string& layoutid) const; + static bool showGeneratorMinimap; /// @brief Runtime updating debug visualization texture diff --git a/src/frontend/menu.cpp b/src/frontend/menu.cpp index e46888df..372264a5 100644 --- a/src/frontend/menu.cpp +++ b/src/frontend/menu.cpp @@ -11,7 +11,7 @@ #include "graphics/ui/elements/Menu.hpp" #include "graphics/ui/gui_util.hpp" #include "interfaces/Task.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "locale.hpp" #include "logic/scripting/scripting.hpp" #include "screens/MenuScreen.hpp" diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index d398b3cc..f3694d85 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -6,6 +6,7 @@ #include "core_defs.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" +#include "engine/EnginePaths.hpp" #include "assets/Assets.hpp" #include "frontend/ContentGfxCache.hpp" #include "frontend/LevelFrontend.hpp" diff --git a/src/frontend/screens/MenuScreen.cpp b/src/frontend/screens/MenuScreen.cpp index c1339494..6472578d 100644 --- a/src/frontend/screens/MenuScreen.cpp +++ b/src/frontend/screens/MenuScreen.cpp @@ -12,9 +12,11 @@ #include "window/Camera.hpp" #include "engine/Engine.hpp" -MenuScreen::MenuScreen(Engine& engine) : Screen(engine) { - uicamera = - std::make_unique(glm::vec3(), engine.getWindow().getSize().y); +MenuScreen::MenuScreen(Engine& engine) + : Screen(engine), + uicamera( + std::make_unique(glm::vec3(), engine.getWindow().getSize().y) + ) { uicamera->perspective = false; uicamera->near = -1.0f; uicamera->far = 1.0f; @@ -24,7 +26,7 @@ MenuScreen::MenuScreen(Engine& engine) : Screen(engine) { MenuScreen::~MenuScreen() = default; void MenuScreen::onOpen() { - engine.getContentControl().resetContent(); + engine.getContentControl().resetContent({}); auto menu = engine.getGUI().getMenu(); menu->reset(); diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 94b8ee13..8bbb2c9d 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -19,6 +19,7 @@ #include "logic/LevelController.hpp" #include "util/stringutil.hpp" #include "engine/Engine.hpp" +#include "engine/EnginePaths.hpp" #include "io/io.hpp" #include "audio/audio.hpp" #include "maths/util.hpp" diff --git a/src/graphics/ui/elements/Container.cpp b/src/graphics/ui/elements/Container.cpp index b930d76a..8d45c2a1 100644 --- a/src/graphics/ui/elements/Container.cpp +++ b/src/graphics/ui/elements/Container.cpp @@ -2,6 +2,8 @@ #include "graphics/core/DrawContext.hpp" #include "graphics/core/Batch2D.hpp" +#include "window/Window.hpp" +#include "../GUI.hpp" #include #include @@ -77,6 +79,10 @@ void Container::act(float delta) { node->act(delta); } } + if (!intervalEvents.empty()) { + // TODO: make it interval-based + gui.getWindow().setShouldRefresh(); + } for (IntervalEvent& event : intervalEvents) { event.timer += delta; if (event.timer > event.interval) { @@ -87,9 +93,10 @@ void Container::act(float delta) { } } } + GUI& gui = this->gui; intervalEvents.erase(std::remove_if( intervalEvents.begin(), intervalEvents.end(), - [](const IntervalEvent& event) { + [&gui](const IntervalEvent& event) { return event.repeat == 0; } ), intervalEvents.end()); diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp index 5e1c8327..f4e89338 100644 --- a/src/io/settings_io.cpp +++ b/src/io/settings_io.cpp @@ -51,6 +51,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.add("framerate", &settings.display.framerate); builder.add("limit-fps-iconified", &settings.display.limitFpsIconified); builder.add("window-mode", &settings.display.windowMode); + builder.add("adaptive-menu-fps", &settings.display.adaptiveFpsInMenu); builder.section("camera"); builder.add("sensitivity", &settings.camera.sensitivity); diff --git a/src/logic/EngineController.cpp b/src/logic/EngineController.cpp index 0d0a5a60..d6820fa1 100644 --- a/src/logic/EngineController.cpp +++ b/src/logic/EngineController.cpp @@ -4,28 +4,29 @@ #include #include -#include "engine/Engine.hpp" #include "coders/commons.hpp" -#include "debug/Logger.hpp" #include "coders/json.hpp" -#include "content/ContentReport.hpp" #include "content/ContentControl.hpp" +#include "content/ContentReport.hpp" #include "content/PacksManager.hpp" -#include "world/files/WorldConverter.hpp" -#include "world/files/WorldFiles.hpp" +#include "debug/Logger.hpp" +#include "engine/Engine.hpp" +#include "engine/EnginePaths.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" #include "frontend/screens/LevelScreen.hpp" #include "frontend/screens/MenuScreen.hpp" -#include "graphics/ui/GUI.hpp" #include "graphics/ui/elements/Menu.hpp" #include "graphics/ui/gui_util.hpp" -#include "objects/Players.hpp" +#include "graphics/ui/GUI.hpp" #include "interfaces/Task.hpp" +#include "LevelController.hpp" +#include "objects/Players.hpp" #include "util/stringutil.hpp" +#include "world/files/WorldConverter.hpp" +#include "world/files/WorldFiles.hpp" #include "world/Level.hpp" #include "world/World.hpp" -#include "LevelController.hpp" static debug::Logger logger("engine-control"); diff --git a/src/logic/scripting/lua/libs/libaudio.cpp b/src/logic/scripting/lua/libs/libaudio.cpp index 7ec6f1a6..b80676c1 100644 --- a/src/logic/scripting/lua/libs/libaudio.cpp +++ b/src/logic/scripting/lua/libs/libaudio.cpp @@ -1,6 +1,7 @@ #include "audio/audio.hpp" #include "assets/Assets.hpp" #include "engine/Engine.hpp" +#include "engine/EnginePaths.hpp" #include "api_lua.hpp" inline const char* DEFAULT_CHANNEL = "regular"; diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 9f14e2a0..8f8a7bc8 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -8,7 +8,7 @@ #include "content/Content.hpp" #include "content/ContentControl.hpp" #include "engine/Engine.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "io/io.hpp" #include "io/settings_io.hpp" #include "frontend/menu.hpp" @@ -43,7 +43,16 @@ static int l_reset_content(lua::State* L) { if (level != nullptr) { throw std::runtime_error("world must be closed before"); } - content_control->resetContent(); + std::vector nonResetPacks; + if (lua::istable(L, 1)) { + int len = lua::objlen(L, 1); + for (int i = 0; i < len; i++) { + lua::rawgeti(L, i + 1, 1); + nonResetPacks.emplace_back(lua::require_lstring(L, -1)); + lua::pop(L); + } + } + content_control->resetContent(std::move(nonResetPacks)); return 0; } diff --git a/src/logic/scripting/lua/libs/libentity.cpp b/src/logic/scripting/lua/libs/libentity.cpp index 26d3ea61..3b9730c2 100644 --- a/src/logic/scripting/lua/libs/libentity.cpp +++ b/src/logic/scripting/lua/libs/libentity.cpp @@ -2,6 +2,7 @@ #include "content/Content.hpp" #include "engine/Engine.hpp" +#include "engine/EnginePaths.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" #include "objects/Entity.hpp" diff --git a/src/logic/scripting/lua/libs/libfile.cpp b/src/logic/scripting/lua/libs/libfile.cpp index 28ed5a6e..c3ee4bdb 100644 --- a/src/logic/scripting/lua/libs/libfile.cpp +++ b/src/logic/scripting/lua/libs/libfile.cpp @@ -3,7 +3,7 @@ #include "coders/gzip.hpp" #include "engine/Engine.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "io/io.hpp" #include "io/devices/ZipFileDevice.hpp" #include "util/stringutil.hpp" diff --git a/src/logic/scripting/lua/libs/libgeneration.cpp b/src/logic/scripting/lua/libs/libgeneration.cpp index ec10c294..2e872ec7 100644 --- a/src/logic/scripting/lua/libs/libgeneration.cpp +++ b/src/logic/scripting/lua/libs/libgeneration.cpp @@ -9,6 +9,7 @@ #include "content/Content.hpp" #include "content/ContentControl.hpp" #include "engine/Engine.hpp" +#include "engine/EnginePaths.hpp" #include "../usertypes/lua_type_voxelfragment.hpp" using namespace scripting; diff --git a/src/logic/scripting/lua/libs/libhud.cpp b/src/logic/scripting/lua/libs/libhud.cpp index e3d29783..7ab5d33c 100644 --- a/src/logic/scripting/lua/libs/libhud.cpp +++ b/src/logic/scripting/lua/libs/libhud.cpp @@ -189,6 +189,11 @@ static int l_reload_script(lua::State* L) { return 0; } +static int l_is_open(lua::State* L) { + auto layoutid = lua::require_string(L, 1); + return lua::pushboolean(L, hud->isOpen(layoutid)); +} + const luaL_Reg hudlib[] = { {"open_inventory", wrap_hud}, {"close_inventory", wrap_hud}, @@ -208,5 +213,6 @@ const luaL_Reg hudlib[] = { {"_set_debug_cheats", wrap_hud}, {"set_allow_pause", wrap_hud}, {"reload_script", wrap_hud}, + {"is_open", wrap_hud}, {nullptr, nullptr} }; diff --git a/src/logic/scripting/lua/libs/libpack.cpp b/src/logic/scripting/lua/libs/libpack.cpp index f23e96b3..b7162b4d 100644 --- a/src/logic/scripting/lua/libs/libpack.cpp +++ b/src/logic/scripting/lua/libs/libpack.cpp @@ -10,7 +10,7 @@ #include "graphics/ui/elements/Menu.hpp" #include "frontend/locale.hpp" #include "world/files/WorldFiles.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "world/Level.hpp" #include "world/World.hpp" #include "api_lua.hpp" diff --git a/src/logic/scripting/lua/libs/libworld.cpp b/src/logic/scripting/lua/libs/libworld.cpp index 41778204..eb966f50 100644 --- a/src/logic/scripting/lua/libs/libworld.cpp +++ b/src/logic/scripting/lua/libs/libworld.cpp @@ -10,7 +10,7 @@ #include "content/ContentControl.hpp" #include "engine/Engine.hpp" #include "world/files/WorldFiles.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "io/io.hpp" #include "lighting/Lighting.hpp" #include "voxels/Chunk.hpp" diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 80e2cff7..eef391b5 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -4,7 +4,7 @@ #include #include "io/io.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "debug/Logger.hpp" #include "util/stringutil.hpp" #include "libs/api_lua.hpp" diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index a09f5086..6fdbe91e 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -336,6 +336,22 @@ int lua::create_environment(State* L, int parent) { return id; } +int lua::restore_pack_environment(lua::State* L, const std::string& packid) { + if(!lua::getglobal(L, "__vc__pack_envs")) { + return -1; + } + int id = nextEnvironment++; + + if (lua::getfield(L, packid)) { + // envname = env + setglobal(L, env_name(id)); + lua::pop(L); + return id; + } + lua::pop(L); + return -1; +} + void lua::remove_environment(State* L, int id) { if (id == 0) { return; diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index 29f25f34..f64cdbf7 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -613,6 +613,7 @@ namespace lua { return 0; } int create_environment(lua::State*, int parent); + int restore_pack_environment(lua::State*, const std::string& packid); void remove_environment(lua::State*, int id); inline void close(lua::State* L) { diff --git a/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp b/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp index 9f9d2ed8..d6528298 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_heightmap.cpp @@ -8,6 +8,7 @@ #include "graphics/core/ImageData.hpp" #include "maths/Heightmap.hpp" #include "engine/Engine.hpp" +#include "engine/EnginePaths.hpp" #include "../lua_util.hpp" #include "lua_type_heightmap.hpp" diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 244975c3..19accc89 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -9,7 +9,7 @@ #include "content/ContentControl.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "io/io.hpp" #include "frontend/UiDocument.hpp" #include "items/Inventory.hpp" @@ -172,7 +172,15 @@ std::unique_ptr scripting::start_app_script(const io::path& script) { const ContentPack& pack ) { auto L = lua::get_main_state(); - int id = lua::create_environment(L, 0); + int id = lua::restore_pack_environment(L, pack.id); + if (id != -1) { + return std::shared_ptr(new int(id), [=](int* id) { //-V508 + lua::remove_environment(L, *id); + delete id; + }); + } + id = lua::create_environment(L, 0); + lua::pushenv(L, id); lua::pushvalue(L, -1); lua::setfield(L, "PACK_ENV"); @@ -347,7 +355,7 @@ void scripting::on_world_quit() { scripting::controller = nullptr; } -void scripting::cleanup() { +void scripting::cleanup(const std::vector& nonReset) { auto L = lua::get_main_state(); lua::requireglobal(L, "pack"); for (auto& pack : content_control->getAllContentPacks()) { @@ -358,7 +366,12 @@ void scripting::cleanup() { lua::pop(L); if (lua::getglobal(L, "__scripts_cleanup")) { - lua::call_nothrow(L, 0); + lua::createtable(L, nonReset.size(), 0); + for (size_t i = 0; i < nonReset.size(); i++) { + lua::pushstring(L, nonReset[i]); + lua::rawseti(L, i + 1); + } + lua::call_nothrow(L, 1); } } diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index d9c7e614..a096e9cc 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -82,7 +82,7 @@ namespace scripting { void on_world_tick(int tps); void on_world_save(); void on_world_quit(); - void cleanup(); + void cleanup(const std::vector& nonReset); void on_blocks_tick(const Block& block, int tps); void update_block(const Block& block, const glm::ivec3& pos); void random_update_block(const Block& block, const glm::ivec3& pos); diff --git a/src/settings.hpp b/src/settings.hpp index f97fb513..ee35d5a4 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -31,6 +31,8 @@ struct DisplaySettings { IntegerSetting framerate {-1, -1, 120}; /// @brief Limit framerate when window is iconified FlagSetting limitFpsIconified {false}; + /// @brief Adaptive framerate in menu (experimental) + FlagSetting adaptiveFpsInMenu {false}; }; struct ChunksSettings { diff --git a/src/util/command_line.cpp b/src/util/command_line.cpp index 3691b77b..7cb3614e 100644 --- a/src/util/command_line.cpp +++ b/src/util/command_line.cpp @@ -6,7 +6,7 @@ #include #include -#include "io/engine_paths.hpp" +#include "engine/EnginePaths.hpp" #include "util/ArgsReader.hpp" #include "engine/Engine.hpp" diff --git a/src/window/Window.hpp b/src/window/Window.hpp index 135e4f7d..71004470 100644 --- a/src/window/Window.hpp +++ b/src/window/Window.hpp @@ -18,6 +18,8 @@ enum class WindowMode { class Window { public: + static inline constexpr int FPS_UNLIMITED = 0; + Window(glm::ivec2 size) : size(std::move(size)) {} virtual ~Window() = default; @@ -40,6 +42,9 @@ public: virtual void popScissor() = 0; virtual void resetScissor() = 0; + virtual void setShouldRefresh() = 0; + virtual bool checkShouldRefresh() = 0; + virtual double time() = 0; virtual void setFramerate(int framerate) = 0; diff --git a/src/window/detail/GLFWWindow.cpp b/src/window/detail/GLFWWindow.cpp index 27f56d3c..a80c10e0 100644 --- a/src/window/detail/GLFWWindow.cpp +++ b/src/window/detail/GLFWWindow.cpp @@ -180,14 +180,18 @@ public: : window(window) { } - void pollEvents() override { + void pollEvents(bool waitForRefresh) override { delta.x = 0.0f; delta.y = 0.0f; scroll = 0; currentFrame++; codepoints.clear(); pressedKeys.clear(); - glfwPollEvents(); + if (waitForRefresh) { + glfwWaitEvents(); + } else { + glfwPollEvents(); + } for (auto& [_, binding] : bindings.getAll()) { if (!binding.enabled) { @@ -377,6 +381,18 @@ public: prevSwap = time(); } + void setShouldRefresh() override { + shouldRefresh = true; + } + + bool checkShouldRefresh() override { + if (shouldRefresh) { + shouldRefresh = false; + return true; + } + return false; + } + bool isMaximized() const override { return glfwGetWindowAttrib(window, GLFW_MAXIMIZED); } @@ -557,12 +573,14 @@ private: double prevSwap = 0.0; int posX = 0; int posY = 0; + bool shouldRefresh = true; }; static_assert(!std::is_abstract()); static void mouse_button_callback(GLFWwindow* window, int button, int action, int) { auto handler = static_cast(glfwGetWindowUserPointer(window)); handler->input.onMouseCallback(button, action == GLFW_PRESS); + handler->setShouldRefresh(); } static void character_callback(GLFWwindow* window, unsigned int codepoint) { @@ -574,6 +592,8 @@ static void key_callback( GLFWwindow* window, int key, int /*scancode*/, int action, int /*mode*/ ) { auto handler = static_cast(glfwGetWindowUserPointer(window)); + handler->setShouldRefresh(); + auto& input = handler->input; if (key == GLFW_KEY_UNKNOWN) { return; @@ -599,11 +619,13 @@ static void window_size_callback(GLFWwindow* window, int width, int height) { static void scroll_callback(GLFWwindow* window, double, double yoffset) { auto handler = static_cast(glfwGetWindowUserPointer(window)); handler->input.scroll += yoffset; + handler->setShouldRefresh(); } static void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos) { auto handler = static_cast(glfwGetWindowUserPointer(window)); handler->input.setCursorPosition(xpos, ypos); + handler->setShouldRefresh(); } static void iconify_callback(GLFWwindow* window, int iconified) { @@ -629,6 +651,11 @@ static void create_standard_cursors() { } } +static void refresh_callback(GLFWwindow* window) { + auto handler = static_cast(glfwGetWindowUserPointer(window)); + handler->setShouldRefresh(); +} + static void setup_callbacks(GLFWwindow* window) { glfwSetKeyCallback(window, key_callback); glfwSetMouseButtonCallback(window, mouse_button_callback); @@ -637,6 +664,7 @@ static void setup_callbacks(GLFWwindow* window) { glfwSetCharCallback(window, character_callback); glfwSetScrollCallback(window, scroll_callback); glfwSetWindowIconifyCallback(window, iconify_callback); + glfwSetWindowRefreshCallback(window, refresh_callback); } std::tuple< diff --git a/src/window/input.hpp b/src/window/input.hpp index 2edfe4c7..84c12040 100644 --- a/src/window/input.hpp +++ b/src/window/input.hpp @@ -261,7 +261,7 @@ class Input { public: virtual ~Input() = default; - virtual void pollEvents() = 0; + virtual void pollEvents(bool waitForRefresh) = 0; virtual const char* getClipboardText() const = 0; virtual void setClipboardText(const char* str) = 0;