Merge branch 'dev' into generated-pcm-stream
This commit is contained in:
commit
4f675dde27
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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`.
|
||||
|
||||
Версия пака, при этом, на данный момент, не учитывается.
|
||||
|
||||
@ -20,6 +20,10 @@ hud.open(
|
||||
[опционально] invid: int
|
||||
) -> int
|
||||
|
||||
-- Возвращает true если указаный макет UI открыт.
|
||||
hud.is_open(
|
||||
layoutid: str
|
||||
) -> bool
|
||||
|
||||
-- Открывает инвентарь и UI блока.
|
||||
-- Если блок не имеет макета UI - бросается исключение.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "coders/json.hpp"
|
||||
|
||||
@ -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<std::string>& ContentControl::getBasePacks() {
|
||||
return basePacks;
|
||||
}
|
||||
|
||||
void ContentControl::resetContent() {
|
||||
void ContentControl::resetContent(const std::vector<std::string>& nonReset) {
|
||||
paths.setCurrentWorldFolder("");
|
||||
|
||||
scripting::cleanup();
|
||||
scripting::cleanup(nonReset);
|
||||
std::vector<PathsRoot> resRoots;
|
||||
{
|
||||
auto pack = ContentPack::createCore();
|
||||
@ -78,8 +79,6 @@ void ContentControl::loadContent(const std::vector<std::string>& names) {
|
||||
}
|
||||
|
||||
void ContentControl::loadContent() {
|
||||
scripting::cleanup();
|
||||
|
||||
std::vector<std::string> names;
|
||||
for (auto& pack : contentPacks) {
|
||||
names.push_back(pack.id);
|
||||
|
||||
@ -34,7 +34,7 @@ public:
|
||||
std::vector<std::string>& getBasePacks();
|
||||
|
||||
/// @brief Reset content to base packs list
|
||||
void resetContent();
|
||||
void resetContent(const std::vector<std::string>& nonReset);
|
||||
|
||||
void loadContent(const std::vector<std::string>& names);
|
||||
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "interfaces/Process.hpp"
|
||||
#include "interfaces/Serializable.hpp"
|
||||
|
||||
namespace scripting {
|
||||
@ -15,9 +16,13 @@ struct Project : Serializable {
|
||||
std::string title;
|
||||
std::vector<std::string> basePacks;
|
||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||
std::unique_ptr<Process> setupCoroutine;
|
||||
|
||||
~Project();
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
|
||||
void loadProjectClientScript();
|
||||
void loadProjectStartScript();
|
||||
};
|
||||
|
||||
15
src/engine/CoreParameters.hpp
Normal file
15
src/engine/CoreParameters.hpp
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
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;
|
||||
};
|
||||
@ -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 <iostream>
|
||||
#include <assert.h>
|
||||
@ -50,29 +48,6 @@
|
||||
|
||||
static debug::Logger logger("engine");
|
||||
|
||||
static std::unique_ptr<ImageData> 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<scripting::IClientProjectScript> 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<WindowControl>(*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<EnginePaths>(params);
|
||||
loadProject();
|
||||
|
||||
editor = std::make_unique<devtools::Editor>(*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<EngineController>(*this);
|
||||
@ -214,7 +162,7 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||
langs::locale_by_envlocale(platform::detect_locale())
|
||||
);
|
||||
}
|
||||
content = std::make_unique<ContentControl>(*project, paths, *input, [this]() {
|
||||
content = std::make_unique<ContentControl>(*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<int>(WindowMode::FULLSCREEN)) {
|
||||
settings.display.windowMode.set(static_cast<int>(WindowMode::FULLSCREEN));
|
||||
} else {
|
||||
settings.display.windowMode.set(static_cast<int>(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<Assets>();
|
||||
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> 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<Screen> Engine::getScreen() {
|
||||
|
||||
@ -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 <memory>
|
||||
#include <string>
|
||||
|
||||
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<void(std::unique_ptr<Level>, int64_t)>;
|
||||
|
||||
class Engine : public util::ObjectsKeeper {
|
||||
CoreParameters params;
|
||||
EngineSettings settings;
|
||||
EnginePaths paths;
|
||||
|
||||
std::unique_ptr<EnginePaths> paths;
|
||||
std::unique_ptr<Project> project;
|
||||
std::unique_ptr<SettingsHandler> settingsHandler;
|
||||
std::unique_ptr<Assets> assets;
|
||||
@ -75,6 +65,7 @@ class Engine : public util::ObjectsKeeper {
|
||||
std::unique_ptr<gui::GUI> gui;
|
||||
std::unique_ptr<devtools::Editor> editor;
|
||||
std::unique_ptr<devtools::DebuggingServer> debuggingServer;
|
||||
std::unique_ptr<WindowControl> 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);
|
||||
|
||||
@ -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 <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include "typedefs.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "util/platform.hpp"
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#include "io/devices/StdfsDevice.hpp"
|
||||
#include "io/devices/ZipFileDevice.hpp"
|
||||
#include "world/files/WorldFiles.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include "maths/util.hpp"
|
||||
|
||||
template<int n>
|
||||
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<int n>
|
||||
static std::string generate_random_base64() {
|
||||
auto randomEngine = util::seeded_random_engine(random_device);
|
||||
static std::uniform_int_distribution<integer_t> 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<io::StdfsDevice>(*scriptFolder));
|
||||
}
|
||||
|
||||
io::set_device("res", std::make_shared<io::StdfsDevice>(resourcesFolder, false));
|
||||
io::set_device("user", std::make_shared<io::StdfsDevice>(userFilesFolder));
|
||||
io::set_device("project", std::make_shared<io::StdfsDevice>(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<io::path> 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<io::StdfsDevice>(folder));
|
||||
this->scriptFolder = std::move(folder);
|
||||
}
|
||||
|
||||
void EnginePaths::setProjectFolder(std::filesystem::path folder) {
|
||||
io::set_device("project", std::make_shared<io::StdfsDevice>(folder));
|
||||
this->projectFolder = std::move(folder);
|
||||
}
|
||||
|
||||
void EnginePaths::setCurrentWorldFolder(io::path folder) {
|
||||
if (folder.empty()) {
|
||||
io::remove_device("world");
|
||||
@ -1,15 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "data/dv.hpp"
|
||||
#include "CoreParameters.hpp"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#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<std::filesystem::path> scriptFolder;
|
||||
std::vector<PathsRoot> entryPoints;
|
||||
@ -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<const MenuScreen*>(engine.getScreen().get()) != nullptr
|
||||
);
|
||||
}
|
||||
logger.info() << "main loop stopped";
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
93
src/engine/WindowControl.cpp
Normal file
93
src/engine/WindowControl.cpp
Normal file
@ -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<ImageData> 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<int>(WindowMode::FULLSCREEN)) {
|
||||
windowMode.set(static_cast<int>(WindowMode::FULLSCREEN));
|
||||
} else {
|
||||
windowMode.set(static_cast<int>(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());
|
||||
}
|
||||
26
src/engine/WindowControl.hpp
Normal file
26
src/engine/WindowControl.hpp
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
class Window;
|
||||
class Input;
|
||||
class Engine;
|
||||
|
||||
class WindowControl {
|
||||
public:
|
||||
struct Result {
|
||||
std::unique_ptr<Window> window;
|
||||
std::unique_ptr<Input> input;
|
||||
};
|
||||
WindowControl(Engine& engine);
|
||||
|
||||
Result initialize();
|
||||
|
||||
void nextFrame(bool waitForRefresh);
|
||||
|
||||
void saveScreenshot();
|
||||
|
||||
void toggleFullscreen();
|
||||
private:
|
||||
Engine& engine;
|
||||
};
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -12,9 +12,11 @@
|
||||
#include "window/Camera.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
|
||||
MenuScreen::MenuScreen(Engine& engine) : Screen(engine) {
|
||||
uicamera =
|
||||
std::make_unique<Camera>(glm::vec3(), engine.getWindow().getSize().y);
|
||||
MenuScreen::MenuScreen(Engine& engine)
|
||||
: Screen(engine),
|
||||
uicamera(
|
||||
std::make_unique<Camera>(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();
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
|
||||
#include "graphics/core/DrawContext.hpp"
|
||||
#include "graphics/core/Batch2D.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "../GUI.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
@ -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());
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -4,28 +4,29 @@
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
|
||||
#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");
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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<std::string> 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;
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<l_open_inventory>},
|
||||
{"close_inventory", wrap_hud<l_close_inventory>},
|
||||
@ -208,5 +213,6 @@ const luaL_Reg hudlib[] = {
|
||||
{"_set_debug_cheats", wrap_hud<l_set_debug_cheats>},
|
||||
{"set_allow_pause", wrap_hud<l_set_allow_pause>},
|
||||
{"reload_script", wrap_hud<l_reload_script>},
|
||||
{"is_open", wrap_hud<l_is_open>},
|
||||
{nullptr, nullptr}
|
||||
};
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
#include <iostream>
|
||||
|
||||
#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"
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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"
|
||||
|
||||
|
||||
@ -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<Process> 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<int>(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<std::string>& 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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<std::string>& 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);
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include <string>
|
||||
#include <iomanip>
|
||||
|
||||
#include "io/engine_paths.hpp"
|
||||
#include "engine/EnginePaths.hpp"
|
||||
#include "util/ArgsReader.hpp"
|
||||
#include "engine/Engine.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;
|
||||
|
||||
@ -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<GLFWWindow>());
|
||||
|
||||
static void mouse_button_callback(GLFWwindow* window, int button, int action, int) {
|
||||
auto handler = static_cast<GLFWWindow*>(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<GLFWWindow*>(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<GLFWWindow*>(glfwGetWindowUserPointer(window));
|
||||
handler->input.scroll += yoffset;
|
||||
handler->setShouldRefresh();
|
||||
}
|
||||
|
||||
static void cursor_pos_callback(GLFWwindow* window, double xpos, double ypos) {
|
||||
auto handler = static_cast<GLFWWindow*>(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<GLFWWindow*>(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<
|
||||
|
||||
@ -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;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user