mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-12 22:41:49 +00:00
Merge branch 'main' into refactor-world-renderer
This commit is contained in:
commit
61b601f95a
41 changed files with 464 additions and 78 deletions
|
|
@ -1,6 +1,6 @@
|
||||||
# Block properties
|
# Block properties
|
||||||
|
|
||||||
## Visual
|
## Visual & Audio
|
||||||
|
|
||||||
### *texture*
|
### *texture*
|
||||||
|
|
||||||
|
|
@ -70,6 +70,18 @@ Rotation profile (set of available block rotations and behaviour of placing bloc
|
||||||
- "pane" - panels, doors, signs
|
- "pane" - panels, doors, signs
|
||||||
- "stairs" - "pane" + flipped variants
|
- "stairs" - "pane" + flipped variants
|
||||||
|
|
||||||
|
### *particles*
|
||||||
|
|
||||||
|
Particles are specified as a JSON object. Property names can be found [in the particles section](particles.md).
|
||||||
|
|
||||||
|
When camera is near to the block, the engine will create an emitter that will run
|
||||||
|
until the block is destroyed or the camera moves away a certain distance.
|
||||||
|
|
||||||
|
### Material - *material*
|
||||||
|
|
||||||
|
Defines the name of the block's material in the format `pack:material_name`, which affects the selection of block interaction sounds.
|
||||||
|
Material definitions are located in /block_materials.
|
||||||
|
|
||||||
## Variants
|
## Variants
|
||||||
|
|
||||||
Some properties can vary dynamically, depending on the variant number stored in the user bits of the block.
|
Some properties can vary dynamically, depending on the variant number stored in the user bits of the block.
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,16 @@ Determines how the physics engine will work with it.
|
||||||
|
|
||||||
### *blocking*
|
### *blocking*
|
||||||
|
|
||||||
Determines whether the entity blocks installation of blocks.
|
If true - the entity blocks the placement of a block that intersects with the entity's hitbox.
|
||||||
|
|
||||||
*In the future will also block other entities movement.*
|
*In the future will also block other entities movement.*
|
||||||
|
|
||||||
Default value: *true*.
|
Default value: *true*.
|
||||||
|
|
||||||
|
### *selectable*
|
||||||
|
|
||||||
|
If set to `false` the cursor will ignore the entity, passing the ray.
|
||||||
|
|
||||||
### *sensors*
|
### *sensors*
|
||||||
|
|
||||||
A sensor is an area attached to a physical body that detects the entry of other bodies into it.
|
A sensor is an area attached to a physical body that detects the entry of other bodies into it.
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ Models are loaded automatically; adding them to preload.json is not required.
|
||||||
|
|
||||||
## Models
|
## Models
|
||||||
|
|
||||||
Models should be located in the models folder. Currently only OBJ format is supported.
|
Models must be located in the models folder. Currently, the following formats are supported: obj, vec3, and [vcm](vcm.md).
|
||||||
|
|
||||||
>[!IMPORTANT]
|
>[!IMPORTANT]
|
||||||
> When loading an obj model, the \*.mtl file is ignored.
|
> When loading an obj model, the \*.mtl file is ignored.
|
||||||
|
|
|
||||||
|
|
@ -180,14 +180,34 @@ app.get_content_sources() -> table<string>
|
||||||
|
|
||||||
Returns a list of content sources (paths), in descending priority order.
|
Returns a list of content sources (paths), in descending priority order.
|
||||||
|
|
||||||
``lua
|
```lua
|
||||||
app.set_content_sources(sources: table<string>)
|
app.set_content_sources(sources: table<string>)
|
||||||
```
|
```
|
||||||
|
|
||||||
Sets a list of content sources (paths). Specified in descending priority order.
|
Sets a list of content sources (paths). Specified in descending priority order.
|
||||||
|
|
||||||
``lua
|
```lua
|
||||||
app.reset_content_sources()
|
app.reset_content_sources()
|
||||||
```
|
```
|
||||||
|
|
||||||
Resets content sources.
|
Resets content sources.
|
||||||
|
|
||||||
|
## Sub-instances
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Creates a headless engine instance with the current project and the specified application script.
|
||||||
|
-- Returns the instance ID. The number of active sub-instances is currently limited to one.
|
||||||
|
app.start_background_instance(
|
||||||
|
-- script file
|
||||||
|
app_script: string,
|
||||||
|
-- log file
|
||||||
|
output_file: string | nil
|
||||||
|
) -> int
|
||||||
|
|
||||||
|
-- Checks if the engine sub-instance is alive.
|
||||||
|
app.is_instance_alive(handle: int) -> boolean
|
||||||
|
|
||||||
|
-- Stops the engine sub-instance.
|
||||||
|
-- Returns true if the instance was alive at the time of the call.
|
||||||
|
app.terminate(handle: int) -> boolean
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ block.get(x: int, y: int, z: int) -> int
|
||||||
block.get_states(x: int, y: int, z: int) -> int
|
block.get_states(x: int, y: int, z: int) -> int
|
||||||
|
|
||||||
-- Set block with given integer ID and state (default - 0) at given position.
|
-- Set block with given integer ID and state (default - 0) at given position.
|
||||||
block.set(x: int, y: int, z: int, id: int, states: int)
|
-- If noupdate=true is passed, the `on_update` event will not be called for adjacent blocks.
|
||||||
|
block.set(x: int, y: int, z: int, id: int, states: int, noupdate: boolean=false)
|
||||||
|
|
||||||
-- Places a block with a given integer id and state (default - 0) at given position.
|
-- Places a block with a given integer id and state (default - 0) at given position.
|
||||||
-- on behalf of the player, calling the on_placed event.
|
-- on behalf of the player, calling the on_placed event.
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,8 @@ world.raycast(
|
||||||
-- exclusion filtering mode:
|
-- exclusion filtering mode:
|
||||||
-- if true, filter_entities defines the list of ignored entity types
|
-- if true, filter_entities defines the list of ignored entity types
|
||||||
entities_exclusion: bool = false,
|
entities_exclusion: bool = false,
|
||||||
|
-- include entities with selectable=false
|
||||||
|
nonselect_entities: bool = false;
|
||||||
|
|
||||||
[==[ additional parameters (blocks) ]==]
|
[==[ additional parameters (blocks) ]==]
|
||||||
-- id of the block types used for filtering
|
-- id of the block types used for filtering
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Свойства блоков
|
# Свойства блоков
|
||||||
|
|
||||||
## Вид
|
## Вид & Звук
|
||||||
|
|
||||||
### Текстура - *texture*
|
### Текстура - *texture*
|
||||||
|
|
||||||
|
|
@ -78,6 +78,11 @@
|
||||||
При приближении к блоку движок создаст эмиттер, который будет работать
|
При приближении к блоку движок создаст эмиттер, который будет работать
|
||||||
до разрушения блока или отдаления камеры на некоторое расстояние.
|
до разрушения блока или отдаления камеры на некоторое расстояние.
|
||||||
|
|
||||||
|
### Материал - *material*
|
||||||
|
|
||||||
|
Определяет имя материала блока в формате `пак:имя_материала`, что влияет на выбор звуков взаимодействия с блоком.
|
||||||
|
Определения материалов расположены в /block_materials.
|
||||||
|
|
||||||
## Варианты
|
## Варианты
|
||||||
|
|
||||||
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.
|
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,16 @@
|
||||||
|
|
||||||
### Блокирование - *blocking*
|
### Блокирование - *blocking*
|
||||||
|
|
||||||
Определяет блокирует ли сущность установку блоков.
|
Определяет, блокирует ли сущность установку блока, пересекающегося с хитбоксом сущности.
|
||||||
|
|
||||||
*В будущем будет также блокировать движение других сущностей.*
|
*В будущем будет также блокировать движение других сущностей.*
|
||||||
|
|
||||||
Значение по-умолчанию: *true*.
|
Значение по-умолчанию: *true*.
|
||||||
|
|
||||||
|
### Выделяемость - *selectable*
|
||||||
|
|
||||||
|
При значении в `false` курсор будет игнорировать cущность, пропуская луч далее.
|
||||||
|
|
||||||
### Список сенсоров - *sensors*
|
### Список сенсоров - *sensors*
|
||||||
|
|
||||||
Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел.
|
Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел.
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
## Модели
|
## Модели
|
||||||
|
|
||||||
Модели должны располагаться в папке models. На данный момент поддерживается только OBJ формат.
|
Модели должны располагаться в папке models. На данный момент поддерживаются форматы: obj, vec3 и [vcm](vcm.md).
|
||||||
|
|
||||||
>[!IMPORTANT]
|
>[!IMPORTANT]
|
||||||
> При загрузке obj модели игнорируется файл \*.mtl.
|
> При загрузке obj модели игнорируется файл \*.mtl.
|
||||||
|
|
|
||||||
|
|
@ -143,3 +143,23 @@ app.set_content_sources(sources: table<string>)
|
||||||
-- Сбрасывает список источников контента.
|
-- Сбрасывает список источников контента.
|
||||||
app.reset_content_sources()
|
app.reset_content_sources()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Под-экземпляры
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Создаёт headless-экземпляр движка с текущим проектом и указанным сценарием.
|
||||||
|
-- Возвращает id экземпляра. Число живых под-экземпляров, на данный момент, ограничено одним.
|
||||||
|
app.start_background_instance(
|
||||||
|
-- файл сценария
|
||||||
|
app_script: string,
|
||||||
|
-- файл лога
|
||||||
|
output_file: string | nil
|
||||||
|
) -> int
|
||||||
|
|
||||||
|
-- Проверяет, жив ли под-экземпляр движка.
|
||||||
|
app.is_instance_alive(handle: int) -> boolean
|
||||||
|
|
||||||
|
-- Останавливает под-экземпляр движка.
|
||||||
|
-- Возвращает true если экземпляр был жив в момент вызова.
|
||||||
|
app.terminate_instance(handle: int) -> boolean
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,8 @@ world.raycast(
|
||||||
-- исключающий режим фильтрации:
|
-- исключающий режим фильтрации:
|
||||||
-- при значении (true) filter_entities определяет список игнорируемых
|
-- при значении (true) filter_entities определяет список игнорируемых
|
||||||
entities_exclusion: bool = false,
|
entities_exclusion: bool = false,
|
||||||
|
-- учитывать сущности с selectable=false
|
||||||
|
nonselect_entities: bool = false;
|
||||||
|
|
||||||
[==[ дополнительные параметры (блоки) ]==]
|
[==[ дополнительные параметры (блоки) ]==]
|
||||||
-- id типов блоков используемых для фильтрации
|
-- id типов блоков используемых для фильтрации
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
generator = "base:demo"
|
generator = "base:demo"
|
||||||
player-entity = "base:player"
|
player-entity = "base:player"
|
||||||
hand-skeleton = "base:hand"
|
hand-skeleton = "base:hand"
|
||||||
|
block-material = "base:stone"
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ function on_save()
|
||||||
end
|
end
|
||||||
|
|
||||||
if vc.is_client() then
|
if vc.is_client() then
|
||||||
|
local SCALE = 0.3
|
||||||
local scale = {1, 1, 1}
|
local scale = {1, 1, 1}
|
||||||
local rotation = mat4.rotate({
|
local rotation = mat4.rotate({
|
||||||
math.random(), math.random(), math.random()
|
math.random(), math.random(), math.random()
|
||||||
|
|
@ -35,7 +36,7 @@ if vc.is_client() then
|
||||||
local matrix = mat4.idt()
|
local matrix = mat4.idt()
|
||||||
rig:set_model(0, item.model_name(dropitem.id))
|
rig:set_model(0, item.model_name(dropitem.id))
|
||||||
local bodysize = math.min(scale[1], scale[2], scale[3])
|
local bodysize = math.min(scale[1], scale[2], scale[3])
|
||||||
body:set_size({scale[1], bodysize, scale[3]})
|
body:set_size(vec3.mul({scale[1], bodysize, scale[3]}, SCALE))
|
||||||
mat4.mul(matrix, rotation, matrix)
|
mat4.mul(matrix, rotation, matrix)
|
||||||
mat4.scale(matrix, scale, matrix)
|
mat4.scale(matrix, scale, matrix)
|
||||||
rig:set_matrix(0, matrix)
|
rig:set_matrix(0, matrix)
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ function on_open(params)
|
||||||
mode = params.mode
|
mode = params.mode
|
||||||
end
|
end
|
||||||
refresh()
|
refresh()
|
||||||
|
document.search_textbox.focused = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add - packs to be added to the world (after apply)
|
-- add - packs to be added to the world (after apply)
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
local rules = {nexid = 1, rules = {}}
|
local rules = {nextid = 1, rules = {}}
|
||||||
|
|
||||||
function rules.get_rule(name)
|
function rules.get_rule(name)
|
||||||
local rule = rules.rules[name]
|
local rule = rules.rules[name]
|
||||||
|
|
@ -27,13 +27,13 @@ end
|
||||||
|
|
||||||
function rules.reset(name)
|
function rules.reset(name)
|
||||||
local rule = rules.get_rule(name)
|
local rule = rules.get_rule(name)
|
||||||
rules.set(rule.default)
|
rules.set(name, rule.default)
|
||||||
end
|
end
|
||||||
|
|
||||||
function rules.listen(name, handler)
|
function rules.listen(name, handler)
|
||||||
local rule = rules.get_rule(name)
|
local rule = rules.get_rule(name)
|
||||||
local id = rules.nexid
|
local id = rules.nextid
|
||||||
rules.nextid = rules.nexid + 1
|
rules.nextid = rules.nextid + 1
|
||||||
rule.listeners[utf8.encode(id)] = handler
|
rule.listeners[utf8.encode(id)] = handler
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,5 +3,6 @@ name = "default"
|
||||||
base_packs = ["base"]
|
base_packs = ["base"]
|
||||||
permissions = [
|
permissions = [
|
||||||
"network",
|
"network",
|
||||||
"record-audio"
|
"record-audio",
|
||||||
|
"sub-instances",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ console.add_command(
|
||||||
for y=y1,y2 do
|
for y=y1,y2 do
|
||||||
for z=z1,z2 do
|
for z=z1,z2 do
|
||||||
for x=x1,x2 do
|
for x=x1,x2 do
|
||||||
block.set(x, y, z, id)
|
block.set(x, y, z, id, 0, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,20 @@ local __vc_named_coroutines = {}
|
||||||
local __vc_next_coroutine = 1
|
local __vc_next_coroutine = 1
|
||||||
|
|
||||||
function __vc_start_coroutine(chunk)
|
function __vc_start_coroutine(chunk)
|
||||||
local co = coroutine.create(chunk)
|
local co = coroutine.create(function()
|
||||||
|
local _, err = xpcall(chunk, function(msg)
|
||||||
|
local traceback = debug.get_traceback(0)
|
||||||
|
local s = string.format("%s:", msg)
|
||||||
|
for i=1,#traceback - 2 do
|
||||||
|
local frame = traceback[i]
|
||||||
|
s = s .. "\n\t"..tb_frame_tostring(frame)
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end)
|
||||||
|
if err then
|
||||||
|
error(err)
|
||||||
|
end
|
||||||
|
end)
|
||||||
local id = __vc_next_coroutine
|
local id = __vc_next_coroutine
|
||||||
__vc_next_coroutine = __vc_next_coroutine + 1
|
__vc_next_coroutine = __vc_next_coroutine + 1
|
||||||
__vc_coroutines[id] = co
|
__vc_coroutines[id] = co
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ vc = {
|
||||||
is_client = function()
|
is_client = function()
|
||||||
return not _vc_headless
|
return not _vc_headless
|
||||||
end,
|
end,
|
||||||
|
get_version = __vc_app.get_version,
|
||||||
get_setting = __vc_app.get_setting,
|
get_setting = __vc_app.get_setting,
|
||||||
get_setting_str = __vc_app.get_setting_str,
|
str_setting = __vc_app.str_setting,
|
||||||
get_setting_info = __vc_app.get_setting_info,
|
get_setting_info = __vc_app.get_setting_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ inline constexpr bool ENGINE_DEBUG_BUILD = true;
|
||||||
|
|
||||||
inline const std::string ENGINE_VERSION_STRING = "0.32";
|
inline const std::string ENGINE_VERSION_STRING = "0.32";
|
||||||
|
|
||||||
|
inline constexpr uint MAX_SUBPROCESSES = 1;
|
||||||
|
inline constexpr uint MAX_SUBPROCESS_DEPTH = 2;
|
||||||
|
|
||||||
/// @brief world regions format version
|
/// @brief world regions format version
|
||||||
inline constexpr uint REGION_FORMAT_VERSION = 3;
|
inline constexpr uint REGION_FORMAT_VERSION = 3;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,21 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||||
def.rt.tags.insert(tags.add(tag));
|
def.rt.tags.insert(tags.add(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto isSolid = [](const Variant& variant) {
|
||||||
|
return variant.model.type == BlockModelType::BLOCK ||
|
||||||
|
variant.culling == CullingMode::OPTIONAL;
|
||||||
|
};
|
||||||
|
|
||||||
if (def.variants) {
|
if (def.variants) {
|
||||||
for (auto& variant : def.variants->variants) {
|
for (auto& variant : def.variants->variants) {
|
||||||
variant.rt.solid =
|
variant.rt.solid = isSolid(variant) || def.explictlySolid;
|
||||||
variant.model.type == BlockModelType::BLOCK ||
|
|
||||||
def.explictlySolid;
|
|
||||||
}
|
}
|
||||||
def.defaults = def.variants->variants.at(0);
|
def.defaults = def.variants->variants.at(0);
|
||||||
} else {
|
} else {
|
||||||
def.defaults.rt.solid =
|
def.defaults.rt.solid = isSolid(def.defaults) || def.explictlySolid;
|
||||||
def.defaults.model.type == BlockModelType::BLOCK ||
|
}
|
||||||
def.explictlySolid;
|
if (def.material.empty()) {
|
||||||
|
defaults.at("block-material").get(def.material);
|
||||||
}
|
}
|
||||||
|
|
||||||
const float EPSILON = 0.01f;
|
const float EPSILON = 0.01f;
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
|
||||||
root.at("skeleton-name").get(def.skeletonName);
|
root.at("skeleton-name").get(def.skeletonName);
|
||||||
root.at("material").get(def.material);
|
root.at("material").get(def.material);
|
||||||
root.at("blocking").get(def.blocking);
|
root.at("blocking").get(def.blocking);
|
||||||
|
root.at("selectable").get(def.selectable);
|
||||||
root.at("solid").get(def.solid);
|
root.at("solid").get(def.solid);
|
||||||
root.at("mass").get(def.mass);
|
root.at("mass").get(def.mass);
|
||||||
root.at("elasticity").get(def.elasticity);
|
root.at("elasticity").get(def.elasticity);
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,7 @@
|
||||||
|
|
||||||
static debug::Logger logger("app-scripts");
|
static debug::Logger logger("app-scripts");
|
||||||
|
|
||||||
AppScriptsControl::AppScriptsControl(
|
AppScriptsControl::AppScriptsControl(const CoreParameters& params) {
|
||||||
const CoreParameters& params, const Project& project
|
|
||||||
)
|
|
||||||
: project(project) {
|
|
||||||
|
|
||||||
io::path scriptFile =
|
io::path scriptFile =
|
||||||
params.scriptFile.empty()
|
params.scriptFile.empty()
|
||||||
? "project:start.lua"
|
? "project:start.lua"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ struct Project;
|
||||||
|
|
||||||
class AppScriptsControl {
|
class AppScriptsControl {
|
||||||
public:
|
public:
|
||||||
AppScriptsControl(const CoreParameters& params, const Project& project);
|
AppScriptsControl(const CoreParameters& params);
|
||||||
|
|
||||||
void tick();
|
void tick();
|
||||||
void loadProjectClientScript();
|
void loadProjectClientScript();
|
||||||
|
|
@ -24,7 +24,6 @@ public:
|
||||||
|
|
||||||
bool isFinished() const;
|
bool isFinished() const;
|
||||||
private:
|
private:
|
||||||
const Project& project;
|
|
||||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||||
std::unique_ptr<Process> scriptCoroutine;
|
std::unique_ptr<Process> scriptCoroutine;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "interfaces/Serializable.hpp"
|
#include "interfaces/Serializable.hpp"
|
||||||
|
#include "io/path.hpp"
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -12,6 +13,7 @@ struct Permissions {
|
||||||
static inline std::string NETWORK = "network";
|
static inline std::string NETWORK = "network";
|
||||||
static inline std::string RECORD_AUDIO = "record-audio";
|
static inline std::string RECORD_AUDIO = "record-audio";
|
||||||
static inline std::string WRITE_TO_USER = "write-to-user";
|
static inline std::string WRITE_TO_USER = "write-to-user";
|
||||||
|
static inline std::string SUB_INSTANCES = "sub-instances";
|
||||||
|
|
||||||
std::set<std::string> permissions;
|
std::set<std::string> permissions;
|
||||||
|
|
||||||
|
|
@ -21,6 +23,7 @@ struct Permissions {
|
||||||
struct Project : Serializable {
|
struct Project : Serializable {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string title;
|
std::string title;
|
||||||
|
io::path path;
|
||||||
std::vector<std::string> basePacks;
|
std::vector<std::string> basePacks;
|
||||||
Permissions permissions;
|
Permissions permissions;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,5 +14,6 @@ struct CoreParameters {
|
||||||
std::filesystem::path projectFolder;
|
std::filesystem::path projectFolder;
|
||||||
std::string debugServerString;
|
std::string debugServerString;
|
||||||
int tps = 20;
|
int tps = 20;
|
||||||
|
int subProcessDepth = 0;
|
||||||
std::unordered_map<std::string, std::string> projectArgs;
|
std::unordered_map<std::string, std::string> projectArgs;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||||
audio::set_input_device(name == "auto" ? "" : name);
|
audio::set_input_device(name == "auto" ? "" : name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
appScripts = std::make_unique<AppScriptsControl>(params, *project);
|
appScripts = std::make_unique<AppScriptsControl>(params);
|
||||||
|
|
||||||
if (params.stdinCommands) {
|
if (params.stdinCommands) {
|
||||||
cmd::start_stdin_cmd_reader(*this);
|
cmd::start_stdin_cmd_reader(*this);
|
||||||
|
|
@ -380,6 +380,7 @@ void Engine::loadAssets() {
|
||||||
void Engine::loadProject() {
|
void Engine::loadProject() {
|
||||||
io::path projectFile = "project:project.toml";
|
io::path projectFile = "project:project.toml";
|
||||||
project = std::make_unique<Project>();
|
project = std::make_unique<Project>();
|
||||||
|
project->path = "project:";
|
||||||
project->deserialize(io::read_object(projectFile));
|
project->deserialize(io::read_object(projectFile));
|
||||||
logger.info() << "loaded project " << util::quote(project->name);
|
logger.info() << "loaded project " << util::quote(project->name);
|
||||||
}
|
}
|
||||||
|
|
@ -412,6 +413,7 @@ void Engine::onWorldClosed() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::quit() {
|
void Engine::quit() {
|
||||||
|
logger.info() << "quitSignal set to true";
|
||||||
quitSignal = true;
|
quitSignal = true;
|
||||||
if (!isHeadless()) {
|
if (!isHeadless()) {
|
||||||
window->setShouldClose(true);
|
window->setShouldClose(true);
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ void Mainloop::run() {
|
||||||
auto& appScripts = engine.getAppScripts();
|
auto& appScripts = engine.getAppScripts();
|
||||||
|
|
||||||
logger.info() << "main loop started";
|
logger.info() << "main loop started";
|
||||||
while (!window.isShouldClose()) {
|
while (!window.isShouldClose() && !engine.isQuitSignal()) {
|
||||||
time.update(window.time());
|
time.update(window.time());
|
||||||
engine.applicationTick();
|
engine.applicationTick();
|
||||||
engine.updateFrontend();
|
engine.updateFrontend();
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ void ContentGfxCache::refreshVariant(
|
||||||
uint8_t variantIndex,
|
uint8_t variantIndex,
|
||||||
const Atlas& atlas
|
const Atlas& atlas
|
||||||
) {
|
) {
|
||||||
|
std::unordered_map<std::string, std::string> optionalTextures;
|
||||||
bool denseRender = settings.denseRender.get();
|
bool denseRender = settings.denseRender.get();
|
||||||
for (uint side = 0; side < 6; side++) {
|
for (uint side = 0; side < 6; side++) {
|
||||||
std::string tex = variant.textureFaces[side];
|
std::string tex = variant.textureFaces[side];
|
||||||
|
|
@ -43,6 +44,7 @@ void ContentGfxCache::refreshVariant(
|
||||||
texOpaque = tex;
|
texOpaque = tex;
|
||||||
} else if (variant.culling == CullingMode::OPTIONAL && !denseRender) {
|
} else if (variant.culling == CullingMode::OPTIONAL && !denseRender) {
|
||||||
tex = texOpaque;
|
tex = texOpaque;
|
||||||
|
optionalTextures[variant.textureFaces[side]] = texOpaque;
|
||||||
}
|
}
|
||||||
size_t index = getRegionIndex(def.rt.id, variantIndex, side, false);
|
size_t index = getRegionIndex(def.rt.id, variantIndex, side, false);
|
||||||
sideregions[index] = atlas.get(tex);
|
sideregions[index] = atlas.get(tex);
|
||||||
|
|
@ -56,7 +58,12 @@ void ContentGfxCache::refreshVariant(
|
||||||
if (pos == std::string::npos) {
|
if (pos == std::string::npos) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) {
|
std::string texture = mesh.texture.substr(pos+1);
|
||||||
|
const auto& found = optionalTextures.find(texture);
|
||||||
|
if (found != optionalTextures.end()) {
|
||||||
|
texture = found->second;
|
||||||
|
}
|
||||||
|
if (auto region = atlas.getIf(texture)) {
|
||||||
for (auto& vertex : mesh.vertices) {
|
for (auto& vertex : mesh.vertices) {
|
||||||
vertex.uv = region->apply(vertex.uv);
|
vertex.uv = region->apply(vertex.uv);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -514,6 +514,7 @@ void BlocksRenderer::render(
|
||||||
int x = i % CHUNK_W;
|
int x = i % CHUNK_W;
|
||||||
int y = i / (CHUNK_D * CHUNK_W);
|
int y = i / (CHUNK_D * CHUNK_W);
|
||||||
int z = (i / CHUNK_D) % CHUNK_W;
|
int z = (i / CHUNK_D) % CHUNK_W;
|
||||||
|
|
||||||
switch (def.getModel(state.userbits).type) {
|
switch (def.getModel(state.userbits).type) {
|
||||||
case BlockModelType::BLOCK:
|
case BlockModelType::BLOCK:
|
||||||
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
|
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
|
||||||
|
|
@ -532,7 +533,6 @@ void BlocksRenderer::render(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BlockModelType::CUSTOM: {
|
case BlockModelType::CUSTOM: {
|
||||||
if (!denseRender)
|
|
||||||
blockCustomModel(
|
blockCustomModel(
|
||||||
{x, y, z},
|
{x, y, z},
|
||||||
def,
|
def,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ namespace io {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path(std::string_view str) : path(std::string(str)) {}
|
||||||
|
|
||||||
path(const char* str) : path(std::string(str)) {}
|
path(const char* str) : path(std::string(str)) {}
|
||||||
|
|
||||||
bool operator==(const std::string& other) const {
|
bool operator==(const std::string& other) const {
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,14 @@
|
||||||
#include "window/Window.hpp"
|
#include "window/Window.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
using namespace scripting;
|
using namespace scripting;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static std::array<std::unique_ptr<Process>, MAX_SUBPROCESSES> processes;
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Check if content is loaded
|
/// @brief Check if content is loaded
|
||||||
static int l_is_content_loaded(lua::State* L) {
|
static int l_is_content_loaded(lua::State* L) {
|
||||||
return lua::pushboolean(L, content != nullptr);
|
return lua::pushboolean(L, content != nullptr);
|
||||||
|
|
@ -337,6 +343,10 @@ static int l_start_debug_instance(lua::State* L) {
|
||||||
if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) {
|
if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) {
|
||||||
throw std::runtime_error("project has no debugging permission");
|
throw std::runtime_error("project has no debugging permission");
|
||||||
}
|
}
|
||||||
|
const auto& params = engine->getCoreParameters();
|
||||||
|
if (params.subProcessDepth >= MAX_SUBPROCESS_DEPTH) {
|
||||||
|
throw std::runtime_error("max subprocess depth exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
int port = lua::tointeger(L, 1);
|
int port = lua::tointeger(L, 1);
|
||||||
if (port == 0) {
|
if (port == 0) {
|
||||||
|
|
@ -357,6 +367,8 @@ static int l_start_debug_instance(lua::State* L) {
|
||||||
"--res", paths.getResourcesFolder().u8string(),
|
"--res", paths.getResourcesFolder().u8string(),
|
||||||
"--dir", paths.getUserFilesFolder().u8string(),
|
"--dir", paths.getUserFilesFolder().u8string(),
|
||||||
"--dbg-server", "tcp:" + std::to_string(port),
|
"--dbg-server", "tcp:" + std::to_string(port),
|
||||||
|
"--sub-depth", std::to_string(engine->getCoreParameters()
|
||||||
|
.subProcessDepth + 1),
|
||||||
};
|
};
|
||||||
if (!projectPath.empty()) {
|
if (!projectPath.empty()) {
|
||||||
args.emplace_back("--project");
|
args.emplace_back("--project");
|
||||||
|
|
@ -365,11 +377,77 @@ static int l_start_debug_instance(lua::State* L) {
|
||||||
|
|
||||||
platform::new_engine_instance(
|
platform::new_engine_instance(
|
||||||
std::move(args),
|
std::move(args),
|
||||||
outputPath.empty() ? "" : io::resolve(std::string(outputPath))
|
outputPath.empty() ? "" : io::resolve(std::string(outputPath)),
|
||||||
|
false
|
||||||
);
|
);
|
||||||
return lua::pushinteger(L, port);
|
return lua::pushinteger(L, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int l_start_background_instance(lua::State* L) {
|
||||||
|
if (!engine->getProject().permissions.has(Permissions::SUB_INSTANCES)) {
|
||||||
|
throw std::runtime_error("project has no sub-instances permission");
|
||||||
|
}
|
||||||
|
const auto& params = engine->getCoreParameters();
|
||||||
|
if (params.subProcessDepth >= MAX_SUBPROCESS_DEPTH) {
|
||||||
|
throw std::runtime_error("max subprocess depth exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto scriptPath = lua::require_lstring(L, 1);
|
||||||
|
io::path outputPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : "";
|
||||||
|
const auto& paths = engine->getPaths();
|
||||||
|
|
||||||
|
std::vector<std::string> args {
|
||||||
|
"--headless",
|
||||||
|
"--res", paths.getResourcesFolder().u8string(),
|
||||||
|
"--dir", paths.getUserFilesFolder().u8string(),
|
||||||
|
"--script", io::resolve(scriptPath).u8string(),
|
||||||
|
"--sub-depth", std::to_string(engine->getCoreParameters()
|
||||||
|
.subProcessDepth + 1),
|
||||||
|
};
|
||||||
|
args.emplace_back("--project");
|
||||||
|
args.emplace_back(io::resolve(engine->getProject().path).u8string());
|
||||||
|
|
||||||
|
int handle = -1;
|
||||||
|
for (int i = 0; i < ::processes.size(); i++) {
|
||||||
|
if (!::processes[i] || !::processes[i]->isActive()) {
|
||||||
|
handle = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handle == -1) {
|
||||||
|
throw std::runtime_error("sub-processes limit exceeded");
|
||||||
|
}
|
||||||
|
::processes[handle] = platform::new_engine_instance(
|
||||||
|
std::move(args),
|
||||||
|
outputPath.empty() ? "" : io::resolve(outputPath),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
return lua::pushinteger(L, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_is_instance_alive(lua::State* L) {
|
||||||
|
int handle = lua::tointeger(L, 1);
|
||||||
|
if (handle < 0 || handle >= ::processes.size()) {
|
||||||
|
throw std::runtime_error("invalid process handle");
|
||||||
|
}
|
||||||
|
auto& process = ::processes[handle];
|
||||||
|
return lua::pushboolean(L, process && process->isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_terminate_instance(lua::State* L) {
|
||||||
|
int handle = lua::tointeger(L, 1);
|
||||||
|
if (handle < 0 || handle >= ::processes.size()) {
|
||||||
|
throw std::runtime_error("invalid process handle");
|
||||||
|
}
|
||||||
|
auto& process = ::processes[handle];
|
||||||
|
if (process == nullptr) {
|
||||||
|
return lua::pushboolean(L, false);
|
||||||
|
}
|
||||||
|
bool active = process->isActive();
|
||||||
|
process.reset();
|
||||||
|
return lua::pushboolean(L, active);
|
||||||
|
}
|
||||||
|
|
||||||
const luaL_Reg applib[] = {
|
const luaL_Reg applib[] = {
|
||||||
/// content
|
/// content
|
||||||
{"is_content_loaded", lua::wrap<l_is_content_loaded>},
|
{"is_content_loaded", lua::wrap<l_is_content_loaded>},
|
||||||
|
|
@ -402,5 +480,8 @@ const luaL_Reg applib[] = {
|
||||||
{"get_version", lua::wrap<l_get_version>},
|
{"get_version", lua::wrap<l_get_version>},
|
||||||
{"create_memory_device", lua::wrap<l_create_memory_device>},
|
{"create_memory_device", lua::wrap<l_create_memory_device>},
|
||||||
{"start_debug_instance", lua::wrap<l_start_debug_instance>},
|
{"start_debug_instance", lua::wrap<l_start_debug_instance>},
|
||||||
|
{"start_background_instance", lua::wrap<l_start_background_instance>},
|
||||||
|
{"is_instance_alive", lua::wrap<l_is_instance_alive>},
|
||||||
|
{"terminate_instance", lua::wrap<l_terminate_instance>},
|
||||||
{nullptr, nullptr}
|
{nullptr, nullptr}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -311,6 +311,10 @@ static int l_world_raycast(lua::State* L) {
|
||||||
entityRaycast.entityFilterExcludeMode = lua::toboolean(L, -1);
|
entityRaycast.entityFilterExcludeMode = lua::toboolean(L, -1);
|
||||||
lua::pop(L);
|
lua::pop(L);
|
||||||
}
|
}
|
||||||
|
if (lua::getfield(L, "nonselect_entities", 1)) {
|
||||||
|
entityRaycast.includeNonSelectable = lua::toboolean(L, -1);
|
||||||
|
lua::pop(L);
|
||||||
|
}
|
||||||
if (lua::getfield(L, "filter_blocks", 1)) {
|
if (lua::getfield(L, "filter_blocks", 1)) {
|
||||||
load_blocks_filter(L, -1, filteredBlocks);
|
load_blocks_filter(L, -1, filteredBlocks);
|
||||||
blocksRaycast.filter = &filteredBlocks;
|
blocksRaycast.filter = &filteredBlocks;
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
static debug::Logger logger("main");
|
static debug::Logger logger("main");
|
||||||
|
|
||||||
static void sigterm_handler(int signum) {
|
static void sigterm_handler(int signum) {
|
||||||
|
logger.info() << (signum == SIGTERM ? "SIGTERM" : "SIGINT") << " received";
|
||||||
Engine::getInstance().quit();
|
Engine::getInstance().quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -25,12 +26,16 @@ int main(int argc, char** argv) {
|
||||||
if (!parse_cmdline(argc, argv, coreParameters)) {
|
if (!parse_cmdline(argc, argv, coreParameters)) {
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
logger.debug() << "sub-process depth: "
|
||||||
|
<< coreParameters.subProcessDepth;
|
||||||
} catch (const std::runtime_error& err) {
|
} catch (const std::runtime_error& err) {
|
||||||
std::cerr << err.what() << std::endl;
|
std::cerr << err.what() << std::endl;
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
std::signal(SIGTERM, sigterm_handler);
|
std::signal(SIGTERM, sigterm_handler);
|
||||||
|
#ifdef NDEBUG
|
||||||
|
std::signal(SIGINT, sigterm_handler);
|
||||||
|
#endif
|
||||||
debug::Logger::init(coreParameters.userFolder.string() + "/latest.log");
|
debug::Logger::init(coreParameters.userFolder.string() + "/latest.log");
|
||||||
platform::configure_encoding();
|
platform::configure_encoding();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -176,7 +176,8 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
|
||||||
|
|
||||||
for (auto [entity, eid, transform, body] : view.each()) {
|
for (auto [entity, eid, transform, body] : view.each()) {
|
||||||
if (eid.uid == settings.ignoredUid || !body.enabled ||
|
if (eid.uid == settings.ignoredUid || !body.enabled ||
|
||||||
(settings.solidEntitiesOnly && !eid.def.solid)) {
|
(settings.solidEntitiesOnly && !eid.def.solid) ||
|
||||||
|
(!eid.def.selectable && !settings.includeNonSelectable)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (settings.entitiesFilter) {
|
if (settings.entitiesFilter) {
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ public:
|
||||||
bool solidEntitiesOnly = false;
|
bool solidEntitiesOnly = false;
|
||||||
bool entityFilterExcludeMode = false;
|
bool entityFilterExcludeMode = false;
|
||||||
const std::set<entitydefid_t>* entitiesFilter = nullptr;
|
const std::set<entitydefid_t>* entitiesFilter = nullptr;
|
||||||
|
bool includeNonSelectable = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RaycastResult {
|
struct RaycastResult {
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,9 @@ struct EntityDef {
|
||||||
/// @brief Solid entity material material
|
/// @brief Solid entity material material
|
||||||
std::string material;
|
std::string material;
|
||||||
|
|
||||||
|
/// @brief Can the entity be interacted with cursor (on_attack, on_use)
|
||||||
|
bool selectable = true;
|
||||||
|
|
||||||
/// @brief Does entity prevent blocks setup
|
/// @brief Does entity prevent blocks setup
|
||||||
bool blocking = true;
|
bool blocking = true;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,22 +13,22 @@
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
class ArgC {
|
class ArgC {
|
||||||
public:
|
public:
|
||||||
std::string keyword;
|
std::string keyword;
|
||||||
std::function<bool()> execute;
|
std::function<bool(CoreParameters&, util::ArgsReader&)> execute;
|
||||||
std::string args;
|
std::string args;
|
||||||
std::string help;
|
std::string help;
|
||||||
ArgC(
|
ArgC(
|
||||||
const std::string& keyword,
|
const std::string& keyword,
|
||||||
std::function<bool()> execute,
|
std::function<bool(CoreParameters&, util::ArgsReader&)> execute,
|
||||||
const std::string& args,
|
const std::string& args,
|
||||||
const std::string& help
|
const std::string& help
|
||||||
) {
|
) {
|
||||||
this->keyword = keyword;
|
this->keyword = keyword;
|
||||||
this->execute = execute;
|
this->execute = execute;
|
||||||
this->args = args;
|
this->args = args;
|
||||||
this->help = help;
|
this->help = help;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -36,54 +36,62 @@ static bool perform_keyword(
|
||||||
util::ArgsReader& reader, const std::string& keyword, CoreParameters& params
|
util::ArgsReader& reader, const std::string& keyword, CoreParameters& params
|
||||||
) {
|
) {
|
||||||
static const std::vector<ArgC> argumentsCommandline = {
|
static const std::vector<ArgC> argumentsCommandline = {
|
||||||
ArgC("--res", [¶ms, &reader]() -> bool {
|
ArgC("--res", [](auto& params, auto& reader) -> bool {
|
||||||
params.resFolder = reader.next();
|
params.resFolder = reader.next();
|
||||||
return true;
|
return true;
|
||||||
}, "<path>", "set resources directory."),
|
}, "<path>", "set resources directory."),
|
||||||
ArgC("--dir", [¶ms, &reader]() -> bool {
|
ArgC("--dir", [](auto& params, auto& reader) -> bool {
|
||||||
params.userFolder = reader.next();
|
params.userFolder = reader.next();
|
||||||
return true;
|
return true;
|
||||||
}, "<path>", "set userfiles directory."),
|
}, "<path>", "set userfiles directory."),
|
||||||
ArgC("--project", [¶ms, &reader]() -> bool {
|
ArgC("--project", [](auto& params, auto& reader) -> bool {
|
||||||
params.projectFolder = reader.next();
|
params.projectFolder = reader.next();
|
||||||
return true;
|
return true;
|
||||||
}, "<path>", "set project directory."),
|
}, "<path>", "set project directory."),
|
||||||
ArgC("--test", [¶ms, &reader]() -> bool {
|
ArgC("--test", [](auto& params, auto& reader) -> bool {
|
||||||
params.testMode = true;
|
params.testMode = true;
|
||||||
params.scriptFile = reader.next();
|
params.scriptFile = reader.next();
|
||||||
return true;
|
return true;
|
||||||
}, "<path>", "test script file."),
|
}, "<path>", "test script file."),
|
||||||
ArgC("--script", [¶ms, &reader]() -> bool {
|
ArgC("--script", [](auto& params, auto& reader) -> bool {
|
||||||
params.testMode = false;
|
params.testMode = false;
|
||||||
params.scriptFile = reader.next();
|
params.scriptFile = reader.next();
|
||||||
return true;
|
return true;
|
||||||
}, "<path>", "main script file."),
|
}, "<path>", "main script file."),
|
||||||
ArgC("--headless", [¶ms]() -> bool {
|
ArgC("--headless", [](auto& params, auto& reader) -> bool {
|
||||||
params.headless = true;
|
params.headless = true;
|
||||||
return true;
|
return true;
|
||||||
}, "", "run in headless mode."),
|
}, "", "run in headless mode."),
|
||||||
ArgC("--stdin-cmd", [¶ms]() -> bool {
|
ArgC("--stdin-cmd", [](auto& params, auto& reader) -> bool {
|
||||||
params.stdinCommands = true;
|
params.stdinCommands = true;
|
||||||
return true;
|
return true;
|
||||||
}, "", "run commands from stdin."),
|
}, "", "run commands from stdin."),
|
||||||
ArgC("--tps", [¶ms, &reader]() -> bool {
|
ArgC("--tps", [](auto& params, auto& reader) -> bool {
|
||||||
params.tps = reader.nextInt();
|
params.tps = reader.nextInt();
|
||||||
return true;
|
return true;
|
||||||
}, "<tps>", "headless mode tick-rate (default - 20)."),
|
}, "<tps>", "headless mode tick-rate (default - 20)."),
|
||||||
ArgC("--version", []() -> bool {
|
ArgC("--version", [](auto&, auto&) -> bool {
|
||||||
std::cout << ENGINE_VERSION_STRING << std::endl;
|
std::cout << ENGINE_VERSION_STRING << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}, "", "display the engine version."),
|
}, "", "display the engine version."),
|
||||||
ArgC("--dbg-server", [¶ms, &reader]() -> bool {
|
ArgC("--dbg-server", [](auto& params, auto& reader) -> bool {
|
||||||
params.debugServerString = reader.next();
|
params.debugServerString = reader.next();
|
||||||
return true;
|
return true;
|
||||||
}, "<serv>", "open debugging server where <serv> is {transport}:{port}"),
|
}, "<serv>", "open debugging server where <serv> is {transport}:{port}"),
|
||||||
ArgC("--help", []() -> bool {
|
ArgC("--sub-depth", [](auto& params, auto& reader) -> bool {
|
||||||
|
params.subProcessDepth = reader.nextInt();
|
||||||
|
return true;
|
||||||
|
}, "<depth>", "sub-process depth"),
|
||||||
|
ArgC("--help", [](auto&, auto&) -> bool {
|
||||||
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
|
std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n";
|
||||||
std::cout << "Command-line arguments:\n";
|
std::cout << "Command-line arguments:\n";
|
||||||
for (auto& a : argumentsCommandline) {
|
for (auto& arg : argumentsCommandline) {
|
||||||
std::cout << std::setw(24) << std::left << (a.keyword + " " + a.args);
|
if (arg.help.empty()) {
|
||||||
std::cout << "- " << a.help << std::endl;
|
continue;
|
||||||
|
}
|
||||||
|
std::cout << std::setw(24) << std::left
|
||||||
|
<< (arg.keyword + " " + arg.args);
|
||||||
|
std::cout << "- " << arg.help << std::endl;
|
||||||
}
|
}
|
||||||
std::cout << std::endl;
|
std::cout << std::endl;
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -91,7 +99,7 @@ static bool perform_keyword(
|
||||||
};
|
};
|
||||||
for (auto& a : argumentsCommandline) {
|
for (auto& a : argumentsCommandline) {
|
||||||
if (a.keyword == keyword) {
|
if (a.keyword == keyword) {
|
||||||
return a.execute();
|
return a.execute(params, reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw std::runtime_error("unknown argument " + keyword);
|
throw std::runtime_error("unknown argument " + keyword);
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,13 @@
|
||||||
#else
|
#else
|
||||||
#include <sys/poll.h>
|
#include <sys/poll.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#endif
|
||||||
|
#ifdef __linux__
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace platform::internal {
|
namespace platform::internal {
|
||||||
|
|
@ -215,8 +222,102 @@ std::filesystem::path platform::get_executable_path() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void platform::new_engine_instance(
|
class SystemProcess final : public Process {
|
||||||
const std::vector<std::string>& args, std::filesystem::path outputFile
|
public:
|
||||||
|
#ifdef __linux__
|
||||||
|
SystemProcess(pid_t pid) : pid(pid) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~SystemProcess() {
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
SystemProcess(const PROCESS_INFORMATION& pi)
|
||||||
|
: processHandle(pi.hProcess), pid(pi.dwProcessId) {
|
||||||
|
if (pi.hThread != nullptr) {
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~SystemProcess() {
|
||||||
|
terminate();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
SystemProcess(const SystemProcess&) = delete;
|
||||||
|
SystemProcess(SystemProcess&&) noexcept = default;
|
||||||
|
SystemProcess& operator=(const SystemProcess&) = delete;
|
||||||
|
|
||||||
|
SystemProcess& operator=(SystemProcess&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
#ifdef __linux__
|
||||||
|
pid = other.pid;
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
if (processHandle != nullptr) {
|
||||||
|
CloseHandle(processHandle);
|
||||||
|
}
|
||||||
|
processHandle = other.processHandle;
|
||||||
|
pid = other.pid;
|
||||||
|
other.processHandle = nullptr;
|
||||||
|
other.pid = 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isActive() const override {
|
||||||
|
#ifdef __linux__
|
||||||
|
if (kill(pid, 0) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return errno != ESRCH;
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
if (processHandle == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
DWORD exitCode;
|
||||||
|
if (GetExitCodeProcess(processHandle, &exitCode)) {
|
||||||
|
return exitCode == STILL_ACTIVE;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void update() override {}
|
||||||
|
|
||||||
|
void waitForEnd() override {
|
||||||
|
#ifdef __linux__
|
||||||
|
waitpid(pid, nullptr, 0);
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
if (processHandle != nullptr) {
|
||||||
|
WaitForSingleObject(processHandle, INFINITE);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
void terminate() final override {
|
||||||
|
#ifdef __linux__
|
||||||
|
logger.info() << "terminating " << pid;
|
||||||
|
kill(pid, SIGTERM);
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
if (processHandle != nullptr) {
|
||||||
|
TerminateProcess(processHandle, 1);
|
||||||
|
WaitForSingleObject(processHandle, INFINITE);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
#ifdef __linux__
|
||||||
|
pid_t pid;
|
||||||
|
#elif defined(_WIN32)
|
||||||
|
HANDLE processHandle = nullptr;
|
||||||
|
DWORD pid = 0;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Process> platform::new_engine_instance(
|
||||||
|
const std::vector<std::string>& args,
|
||||||
|
std::filesystem::path outputFile,
|
||||||
|
bool subProcess
|
||||||
) {
|
) {
|
||||||
auto executable = get_executable_path();
|
auto executable = get_executable_path();
|
||||||
|
|
||||||
|
|
@ -285,6 +386,38 @@ void platform::new_engine_instance(
|
||||||
&si,
|
&si,
|
||||||
&pi
|
&pi
|
||||||
);
|
);
|
||||||
|
if (subProcess) {
|
||||||
|
HANDLE job = CreateJobObjectW(nullptr, nullptr);
|
||||||
|
|
||||||
|
JOBOBJECT_EXTENDED_LIMIT_INFORMATION info{};
|
||||||
|
info.BasicLimitInformation.LimitFlags =
|
||||||
|
JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!SetInformationJobObject(
|
||||||
|
job,
|
||||||
|
JobObjectExtendedLimitInformation,
|
||||||
|
&info,
|
||||||
|
sizeof(info))) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"SetInformationJobObject failed with code: " +
|
||||||
|
std::to_string(GetLastError())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!AssignProcessToJobObject(job, pi.hProcess)) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"SetInformationJobObject failed with code: " +
|
||||||
|
std::to_string(GetLastError())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return std::make_unique<SystemProcess>(pi);
|
||||||
|
} catch (const std::exception& err) {
|
||||||
|
CloseHandle(job);
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
CloseHandle(pi.hProcess);
|
CloseHandle(pi.hProcess);
|
||||||
CloseHandle(pi.hThread);
|
CloseHandle(pi.hThread);
|
||||||
|
|
@ -295,6 +428,46 @@ void platform::new_engine_instance(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
// TODO: implement
|
||||||
|
#ifndef __APPLE__
|
||||||
|
if (subProcess) {
|
||||||
|
pid_t pid = fork();
|
||||||
|
|
||||||
|
int fd = open(
|
||||||
|
outputFile.string().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pid == 0) {
|
||||||
|
prctl(PR_SET_PDEATHSIG, SIGTERM);
|
||||||
|
|
||||||
|
if (getppid() == 1) {
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
dup2(fd, STDOUT_FILENO);
|
||||||
|
dup2(fd, STDERR_FILENO);
|
||||||
|
close(fd);
|
||||||
|
|
||||||
|
std::vector<const char*> argv(args.size() + 2);
|
||||||
|
argv[0] = const_cast<char*>(executable.c_str());
|
||||||
|
for (int i = 0; i < args.size(); i++) {
|
||||||
|
argv[i + 1] = args[i].c_str();
|
||||||
|
}
|
||||||
|
argv[args.size() + 1] = nullptr;
|
||||||
|
|
||||||
|
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
|
||||||
|
execvp(executable.c_str(), const_cast<char**>(argv.data()));
|
||||||
|
|
||||||
|
_exit(127);
|
||||||
|
} else {
|
||||||
|
close(fd);
|
||||||
|
usleep(1000); // waiting for execvp call
|
||||||
|
return std::make_unique<SystemProcess>(pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
ss << executable;
|
ss << executable;
|
||||||
for (int i = 0; i < args.size(); i++) {
|
for (int i = 0; i < args.size(); i++) {
|
||||||
|
|
@ -315,6 +488,7 @@ void platform::new_engine_instance(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool platform::stdin_has_data() {
|
bool platform::stdin_has_data() {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,11 @@
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "interfaces/Process.hpp"
|
||||||
|
|
||||||
namespace platform {
|
namespace platform {
|
||||||
void configure_encoding();
|
void configure_encoding();
|
||||||
/// @brief Get Environment locale in ISO format ll_CC
|
/// @brief Get Environment locale in ISO format ll_CC
|
||||||
|
|
@ -18,8 +21,10 @@ namespace platform {
|
||||||
/// @brief Get current process running executable path
|
/// @brief Get current process running executable path
|
||||||
std::filesystem::path get_executable_path();
|
std::filesystem::path get_executable_path();
|
||||||
/// @brief Run a separate engine instance with specified arguments
|
/// @brief Run a separate engine instance with specified arguments
|
||||||
void new_engine_instance(
|
std::unique_ptr<Process> new_engine_instance(
|
||||||
const std::vector<std::string>& args, std::filesystem::path outputFile
|
const std::vector<std::string>& args,
|
||||||
|
std::filesystem::path outputFile,
|
||||||
|
bool subProcess
|
||||||
);
|
);
|
||||||
/// @brief Open URL in web browser
|
/// @brief Open URL in web browser
|
||||||
bool open_url(const std::string& url);
|
bool open_url(const std::string& url);
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,6 @@ inline constexpr size_t MAX_USER_BLOCK_FIELDS_SIZE = 240;
|
||||||
|
|
||||||
inline constexpr int BLOCK_MAX_VARIANTS = 16;
|
inline constexpr int BLOCK_MAX_VARIANTS = 16;
|
||||||
|
|
||||||
inline std::string DEFAULT_MATERIAL = "base:stone";
|
|
||||||
|
|
||||||
struct BlockFuncsSet {
|
struct BlockFuncsSet {
|
||||||
bool init : 1;
|
bool init : 1;
|
||||||
bool update : 1;
|
bool update : 1;
|
||||||
|
|
@ -206,7 +204,7 @@ public:
|
||||||
dv::value properties = nullptr;
|
dv::value properties = nullptr;
|
||||||
|
|
||||||
/// @brief id of used BlockMaterial, may specify non-existing material
|
/// @brief id of used BlockMaterial, may specify non-existing material
|
||||||
std::string material = DEFAULT_MATERIAL;
|
std::string material;
|
||||||
|
|
||||||
/// @brief Light emission R, G, B, S (sky lights: sun, moon, radioactive
|
/// @brief Light emission R, G, B, S (sky lights: sun, moon, radioactive
|
||||||
/// clouds)
|
/// clouds)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue