Merge branch 'dev' into generated-pcm-stream

This commit is contained in:
MihailRis 2025-11-10 12:44:49 +03:00
commit 4f675dde27
56 changed files with 609 additions and 242 deletions

View File

@ -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.

View File

@ -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

View File

@ -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`.
Версия пака, при этом, на данный момент, не учитывается.

View File

@ -20,6 +20,10 @@ hud.open(
[опционально] invid: int
) -> int
-- Возвращает true если указаный макет UI открыт.
hud.is_open(
layoutid: str
) -> bool
-- Открывает инвентарь и UI блока.
-- Если блок не имеет макета UI - бросается исключение.

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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);

View File

@ -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);

View File

@ -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");

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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";
}
}

View File

@ -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();
};

View 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;
};

View File

@ -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() {

View File

@ -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);

View File

@ -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");

View File

@ -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;

View File

@ -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";
}

View File

@ -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) {

View 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());
}

View 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;
};

View File

@ -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;
}

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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();

View File

@ -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"

View File

@ -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());

View File

@ -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);

View File

@ -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");

View File

@ -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";

View File

@ -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;
}

View File

@ -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"

View File

@ -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"

View File

@ -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;

View File

@ -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}
};

View File

@ -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"

View File

@ -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"

View File

@ -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"

View File

@ -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;

View File

@ -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) {

View File

@ -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"

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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 {

View File

@ -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"

View File

@ -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;

View File

@ -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<

View File

@ -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;