mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +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
|
||||
|
||||
## Visual
|
||||
## Visual & Audio
|
||||
|
||||
### *texture*
|
||||
|
||||
|
|
@ -70,6 +70,18 @@ Rotation profile (set of available block rotations and behaviour of placing bloc
|
|||
- "pane" - panels, doors, signs
|
||||
- "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
|
||||
|
||||
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*
|
||||
|
||||
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.*
|
||||
|
||||
Default value: *true*.
|
||||
|
||||
### *selectable*
|
||||
|
||||
If set to `false` the cursor will ignore the entity, passing the ray.
|
||||
|
||||
### *sensors*
|
||||
|
||||
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 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]
|
||||
> 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.
|
||||
|
||||
``lua
|
||||
```lua
|
||||
app.set_content_sources(sources: table<string>)
|
||||
```
|
||||
|
||||
Sets a list of content sources (paths). Specified in descending priority order.
|
||||
|
||||
``lua
|
||||
```lua
|
||||
app.reset_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
|
||||
|
||||
-- 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.
|
||||
-- on behalf of the player, calling the on_placed event.
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ world.raycast(
|
|||
-- exclusion filtering mode:
|
||||
-- if true, filter_entities defines the list of ignored entity types
|
||||
entities_exclusion: bool = false,
|
||||
-- include entities with selectable=false
|
||||
nonselect_entities: bool = false;
|
||||
|
||||
[==[ additional parameters (blocks) ]==]
|
||||
-- id of the block types used for filtering
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Свойства блоков
|
||||
|
||||
## Вид
|
||||
## Вид & Звук
|
||||
|
||||
### Текстура - *texture*
|
||||
|
||||
|
|
@ -78,6 +78,11 @@
|
|||
При приближении к блоку движок создаст эмиттер, который будет работать
|
||||
до разрушения блока или отдаления камеры на некоторое расстояние.
|
||||
|
||||
### Материал - *material*
|
||||
|
||||
Определяет имя материала блока в формате `пак:имя_материала`, что влияет на выбор звуков взаимодействия с блоком.
|
||||
Определения материалов расположены в /block_materials.
|
||||
|
||||
## Варианты
|
||||
|
||||
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.
|
||||
|
|
|
|||
|
|
@ -61,12 +61,16 @@
|
|||
|
||||
### Блокирование - *blocking*
|
||||
|
||||
Определяет блокирует ли сущность установку блоков.
|
||||
Определяет, блокирует ли сущность установку блока, пересекающегося с хитбоксом сущности.
|
||||
|
||||
*В будущем будет также блокировать движение других сущностей.*
|
||||
|
||||
Значение по-умолчанию: *true*.
|
||||
|
||||
### Выделяемость - *selectable*
|
||||
|
||||
При значении в `false` курсор будет игнорировать cущность, пропуская луч далее.
|
||||
|
||||
### Список сенсоров - *sensors*
|
||||
|
||||
Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел.
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
## Модели
|
||||
|
||||
Модели должны располагаться в папке models. На данный момент поддерживается только OBJ формат.
|
||||
Модели должны располагаться в папке models. На данный момент поддерживаются форматы: obj, vec3 и [vcm](vcm.md).
|
||||
|
||||
>[!IMPORTANT]
|
||||
> При загрузке obj модели игнорируется файл \*.mtl.
|
||||
|
|
|
|||
|
|
@ -142,4 +142,24 @@ app.set_content_sources(sources: table<string>)
|
|||
|
||||
-- Сбрасывает список источников контента.
|
||||
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 определяет список игнорируемых
|
||||
entities_exclusion: bool = false,
|
||||
-- учитывать сущности с selectable=false
|
||||
nonselect_entities: bool = false;
|
||||
|
||||
[==[ дополнительные параметры (блоки) ]==]
|
||||
-- id типов блоков используемых для фильтрации
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
generator = "base:demo"
|
||||
player-entity = "base:player"
|
||||
hand-skeleton = "base:hand"
|
||||
block-material = "base:stone"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ dropitem = ARGS
|
|||
if dropitem.item then
|
||||
dropitem.id = item.index(dropitem.item)
|
||||
end
|
||||
if dropitem then
|
||||
if dropitem then
|
||||
timer = dropitem.pickup_delay or timer
|
||||
end
|
||||
if SAVED_DATA.item then
|
||||
|
|
@ -26,6 +26,7 @@ function on_save()
|
|||
end
|
||||
|
||||
if vc.is_client() then
|
||||
local SCALE = 0.3
|
||||
local scale = {1, 1, 1}
|
||||
local rotation = mat4.rotate({
|
||||
math.random(), math.random(), math.random()
|
||||
|
|
@ -35,7 +36,7 @@ if vc.is_client() then
|
|||
local matrix = mat4.idt()
|
||||
rig:set_model(0, item.model_name(dropitem.id))
|
||||
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.scale(matrix, scale, matrix)
|
||||
rig:set_matrix(0, matrix)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ function on_open(params)
|
|||
mode = params.mode
|
||||
end
|
||||
refresh()
|
||||
document.search_textbox.focused = true
|
||||
end
|
||||
|
||||
-- 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)
|
||||
local rule = rules.rules[name]
|
||||
|
|
@ -27,13 +27,13 @@ end
|
|||
|
||||
function rules.reset(name)
|
||||
local rule = rules.get_rule(name)
|
||||
rules.set(rule.default)
|
||||
rules.set(name, rule.default)
|
||||
end
|
||||
|
||||
function rules.listen(name, handler)
|
||||
local rule = rules.get_rule(name)
|
||||
local id = rules.nexid
|
||||
rules.nextid = rules.nexid + 1
|
||||
local id = rules.nextid
|
||||
rules.nextid = rules.nextid + 1
|
||||
rule.listeners[utf8.encode(id)] = handler
|
||||
return id
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,5 +3,6 @@ name = "default"
|
|||
base_packs = ["base"]
|
||||
permissions = [
|
||||
"network",
|
||||
"record-audio"
|
||||
"record-audio",
|
||||
"sub-instances",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ console.add_command(
|
|||
for y=y1,y2 do
|
||||
for z=z1,z2 do
|
||||
for x=x1,x2 do
|
||||
block.set(x, y, z, id)
|
||||
block.set(x, y, z, id, 0, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -140,7 +140,20 @@ local __vc_named_coroutines = {}
|
|||
local __vc_next_coroutine = 1
|
||||
|
||||
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
|
||||
__vc_next_coroutine = __vc_next_coroutine + 1
|
||||
__vc_coroutines[id] = co
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ vc = {
|
|||
is_client = function()
|
||||
return not _vc_headless
|
||||
end,
|
||||
get_version = __vc_app.get_version,
|
||||
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,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ inline constexpr bool ENGINE_DEBUG_BUILD = true;
|
|||
|
||||
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
|
||||
inline constexpr uint REGION_FORMAT_VERSION = 3;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,17 +26,21 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
|||
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) {
|
||||
for (auto& variant : def.variants->variants) {
|
||||
variant.rt.solid =
|
||||
variant.model.type == BlockModelType::BLOCK ||
|
||||
def.explictlySolid;
|
||||
variant.rt.solid = isSolid(variant) || def.explictlySolid;
|
||||
}
|
||||
def.defaults = def.variants->variants.at(0);
|
||||
} else {
|
||||
def.defaults.rt.solid =
|
||||
def.defaults.model.type == BlockModelType::BLOCK ||
|
||||
def.explictlySolid;
|
||||
def.defaults.rt.solid = isSolid(def.defaults) || def.explictlySolid;
|
||||
}
|
||||
if (def.material.empty()) {
|
||||
defaults.at("block-material").get(def.material);
|
||||
}
|
||||
|
||||
const float EPSILON = 0.01f;
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
|
|||
root.at("skeleton-name").get(def.skeletonName);
|
||||
root.at("material").get(def.material);
|
||||
root.at("blocking").get(def.blocking);
|
||||
root.at("selectable").get(def.selectable);
|
||||
root.at("solid").get(def.solid);
|
||||
root.at("mass").get(def.mass);
|
||||
root.at("elasticity").get(def.elasticity);
|
||||
|
|
|
|||
|
|
@ -8,11 +8,7 @@
|
|||
|
||||
static debug::Logger logger("app-scripts");
|
||||
|
||||
AppScriptsControl::AppScriptsControl(
|
||||
const CoreParameters& params, const Project& project
|
||||
)
|
||||
: project(project) {
|
||||
|
||||
AppScriptsControl::AppScriptsControl(const CoreParameters& params) {
|
||||
io::path scriptFile =
|
||||
params.scriptFile.empty()
|
||||
? "project:start.lua"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ struct Project;
|
|||
|
||||
class AppScriptsControl {
|
||||
public:
|
||||
AppScriptsControl(const CoreParameters& params, const Project& project);
|
||||
AppScriptsControl(const CoreParameters& params);
|
||||
|
||||
void tick();
|
||||
void loadProjectClientScript();
|
||||
|
|
@ -24,7 +24,6 @@ public:
|
|||
|
||||
bool isFinished() const;
|
||||
private:
|
||||
const Project& project;
|
||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||
std::unique_ptr<Process> scriptCoroutine;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "interfaces/Serializable.hpp"
|
||||
#include "io/path.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
|
@ -12,6 +13,7 @@ struct Permissions {
|
|||
static inline std::string NETWORK = "network";
|
||||
static inline std::string RECORD_AUDIO = "record-audio";
|
||||
static inline std::string WRITE_TO_USER = "write-to-user";
|
||||
static inline std::string SUB_INSTANCES = "sub-instances";
|
||||
|
||||
std::set<std::string> permissions;
|
||||
|
||||
|
|
@ -21,6 +23,7 @@ struct Permissions {
|
|||
struct Project : Serializable {
|
||||
std::string name;
|
||||
std::string title;
|
||||
io::path path;
|
||||
std::vector<std::string> basePacks;
|
||||
Permissions permissions;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,6 @@ struct CoreParameters {
|
|||
std::filesystem::path projectFolder;
|
||||
std::string debugServerString;
|
||||
int tps = 20;
|
||||
int subProcessDepth = 0;
|
||||
std::unordered_map<std::string, std::string> projectArgs;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ void Engine::initialize(CoreParameters coreParameters) {
|
|||
audio::set_input_device(name == "auto" ? "" : name);
|
||||
}));
|
||||
|
||||
appScripts = std::make_unique<AppScriptsControl>(params, *project);
|
||||
appScripts = std::make_unique<AppScriptsControl>(params);
|
||||
|
||||
if (params.stdinCommands) {
|
||||
cmd::start_stdin_cmd_reader(*this);
|
||||
|
|
@ -380,6 +380,7 @@ void Engine::loadAssets() {
|
|||
void Engine::loadProject() {
|
||||
io::path projectFile = "project:project.toml";
|
||||
project = std::make_unique<Project>();
|
||||
project->path = "project:";
|
||||
project->deserialize(io::read_object(projectFile));
|
||||
logger.info() << "loaded project " << util::quote(project->name);
|
||||
}
|
||||
|
|
@ -412,6 +413,7 @@ void Engine::onWorldClosed() {
|
|||
}
|
||||
|
||||
void Engine::quit() {
|
||||
logger.info() << "quitSignal set to true";
|
||||
quitSignal = true;
|
||||
if (!isHeadless()) {
|
||||
window->setShouldClose(true);
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ void Mainloop::run() {
|
|||
auto& appScripts = engine.getAppScripts();
|
||||
|
||||
logger.info() << "main loop started";
|
||||
while (!window.isShouldClose()) {
|
||||
while (!window.isShouldClose() && !engine.isQuitSignal()) {
|
||||
time.update(window.time());
|
||||
engine.applicationTick();
|
||||
engine.updateFrontend();
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ void ContentGfxCache::refreshVariant(
|
|||
uint8_t variantIndex,
|
||||
const Atlas& atlas
|
||||
) {
|
||||
std::unordered_map<std::string, std::string> optionalTextures;
|
||||
bool denseRender = settings.denseRender.get();
|
||||
for (uint side = 0; side < 6; side++) {
|
||||
std::string tex = variant.textureFaces[side];
|
||||
|
|
@ -43,6 +44,7 @@ void ContentGfxCache::refreshVariant(
|
|||
texOpaque = tex;
|
||||
} else if (variant.culling == CullingMode::OPTIONAL && !denseRender) {
|
||||
tex = texOpaque;
|
||||
optionalTextures[variant.textureFaces[side]] = texOpaque;
|
||||
}
|
||||
size_t index = getRegionIndex(def.rt.id, variantIndex, side, false);
|
||||
sideregions[index] = atlas.get(tex);
|
||||
|
|
@ -56,7 +58,12 @@ void ContentGfxCache::refreshVariant(
|
|||
if (pos == std::string::npos) {
|
||||
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) {
|
||||
vertex.uv = region->apply(vertex.uv);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -514,6 +514,7 @@ void BlocksRenderer::render(
|
|||
int x = i % CHUNK_W;
|
||||
int y = i / (CHUNK_D * CHUNK_W);
|
||||
int z = (i / CHUNK_D) % CHUNK_W;
|
||||
|
||||
switch (def.getModel(state.userbits).type) {
|
||||
case BlockModelType::BLOCK:
|
||||
blockCube({x, y, z}, texfaces, def, vox.state, !def.shadeless,
|
||||
|
|
@ -532,7 +533,6 @@ void BlocksRenderer::render(
|
|||
break;
|
||||
}
|
||||
case BlockModelType::CUSTOM: {
|
||||
if (!denseRender)
|
||||
blockCustomModel(
|
||||
{x, y, z},
|
||||
def,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ namespace io {
|
|||
}
|
||||
}
|
||||
|
||||
path(std::string_view str) : path(std::string(str)) {}
|
||||
|
||||
path(const char* str) : path(std::string(str)) {}
|
||||
|
||||
bool operator==(const std::string& other) const {
|
||||
|
|
|
|||
|
|
@ -20,8 +20,14 @@
|
|||
#include "window/Window.hpp"
|
||||
#include "world/Level.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
namespace {
|
||||
static std::array<std::unique_ptr<Process>, MAX_SUBPROCESSES> processes;
|
||||
}
|
||||
|
||||
/// @brief Check if content is loaded
|
||||
static int l_is_content_loaded(lua::State* L) {
|
||||
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)) {
|
||||
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);
|
||||
if (port == 0) {
|
||||
|
|
@ -357,6 +367,8 @@ static int l_start_debug_instance(lua::State* L) {
|
|||
"--res", paths.getResourcesFolder().u8string(),
|
||||
"--dir", paths.getUserFilesFolder().u8string(),
|
||||
"--dbg-server", "tcp:" + std::to_string(port),
|
||||
"--sub-depth", std::to_string(engine->getCoreParameters()
|
||||
.subProcessDepth + 1),
|
||||
};
|
||||
if (!projectPath.empty()) {
|
||||
args.emplace_back("--project");
|
||||
|
|
@ -365,11 +377,77 @@ static int l_start_debug_instance(lua::State* L) {
|
|||
|
||||
platform::new_engine_instance(
|
||||
std::move(args),
|
||||
outputPath.empty() ? "" : io::resolve(std::string(outputPath))
|
||||
outputPath.empty() ? "" : io::resolve(std::string(outputPath)),
|
||||
false
|
||||
);
|
||||
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[] = {
|
||||
/// content
|
||||
{"is_content_loaded", lua::wrap<l_is_content_loaded>},
|
||||
|
|
@ -402,5 +480,8 @@ const luaL_Reg applib[] = {
|
|||
{"get_version", lua::wrap<l_get_version>},
|
||||
{"create_memory_device", lua::wrap<l_create_memory_device>},
|
||||
{"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}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -311,6 +311,10 @@ static int l_world_raycast(lua::State* L) {
|
|||
entityRaycast.entityFilterExcludeMode = lua::toboolean(L, -1);
|
||||
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)) {
|
||||
load_blocks_filter(L, -1, filteredBlocks);
|
||||
blocksRaycast.filter = &filteredBlocks;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
static debug::Logger logger("main");
|
||||
|
||||
static void sigterm_handler(int signum) {
|
||||
logger.info() << (signum == SIGTERM ? "SIGTERM" : "SIGINT") << " received";
|
||||
Engine::getInstance().quit();
|
||||
}
|
||||
|
||||
|
|
@ -25,12 +26,16 @@ int main(int argc, char** argv) {
|
|||
if (!parse_cmdline(argc, argv, coreParameters)) {
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
logger.debug() << "sub-process depth: "
|
||||
<< coreParameters.subProcessDepth;
|
||||
} catch (const std::runtime_error& err) {
|
||||
std::cerr << err.what() << std::endl;
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
std::signal(SIGTERM, sigterm_handler);
|
||||
|
||||
#ifdef NDEBUG
|
||||
std::signal(SIGINT, sigterm_handler);
|
||||
#endif
|
||||
debug::Logger::init(coreParameters.userFolder.string() + "/latest.log");
|
||||
platform::configure_encoding();
|
||||
|
||||
|
|
|
|||
|
|
@ -176,7 +176,8 @@ std::optional<Entities::RaycastResult> Entities::rayCast(
|
|||
|
||||
for (auto [entity, eid, transform, body] : view.each()) {
|
||||
if (eid.uid == settings.ignoredUid || !body.enabled ||
|
||||
(settings.solidEntitiesOnly && !eid.def.solid)) {
|
||||
(settings.solidEntitiesOnly && !eid.def.solid) ||
|
||||
(!eid.def.selectable && !settings.includeNonSelectable)) {
|
||||
continue;
|
||||
}
|
||||
if (settings.entitiesFilter) {
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ public:
|
|||
bool solidEntitiesOnly = false;
|
||||
bool entityFilterExcludeMode = false;
|
||||
const std::set<entitydefid_t>* entitiesFilter = nullptr;
|
||||
bool includeNonSelectable = false;
|
||||
};
|
||||
|
||||
struct RaycastResult {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,9 @@ struct EntityDef {
|
|||
/// @brief Solid entity material 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
|
||||
bool blocking = true;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,22 +13,22 @@
|
|||
namespace fs = std::filesystem;
|
||||
|
||||
class ArgC {
|
||||
public:
|
||||
std::string keyword;
|
||||
std::function<bool()> execute;
|
||||
std::string args;
|
||||
std::string help;
|
||||
ArgC(
|
||||
const std::string& keyword,
|
||||
std::function<bool()> execute,
|
||||
const std::string& args,
|
||||
const std::string& help
|
||||
) {
|
||||
this->keyword = keyword;
|
||||
this->execute = execute;
|
||||
this->args = args;
|
||||
this->help = help;
|
||||
}
|
||||
public:
|
||||
std::string keyword;
|
||||
std::function<bool(CoreParameters&, util::ArgsReader&)> execute;
|
||||
std::string args;
|
||||
std::string help;
|
||||
ArgC(
|
||||
const std::string& keyword,
|
||||
std::function<bool(CoreParameters&, util::ArgsReader&)> execute,
|
||||
const std::string& args,
|
||||
const std::string& help
|
||||
) {
|
||||
this->keyword = keyword;
|
||||
this->execute = execute;
|
||||
this->args = args;
|
||||
this->help = help;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -36,54 +36,62 @@ static bool perform_keyword(
|
|||
util::ArgsReader& reader, const std::string& keyword, CoreParameters& params
|
||||
) {
|
||||
static const std::vector<ArgC> argumentsCommandline = {
|
||||
ArgC("--res", [¶ms, &reader]() -> bool {
|
||||
ArgC("--res", [](auto& params, auto& reader) -> bool {
|
||||
params.resFolder = reader.next();
|
||||
return true;
|
||||
}, "<path>", "set resources directory."),
|
||||
ArgC("--dir", [¶ms, &reader]() -> bool {
|
||||
ArgC("--dir", [](auto& params, auto& reader) -> bool {
|
||||
params.userFolder = reader.next();
|
||||
return true;
|
||||
}, "<path>", "set userfiles directory."),
|
||||
ArgC("--project", [¶ms, &reader]() -> bool {
|
||||
ArgC("--project", [](auto& params, auto& reader) -> bool {
|
||||
params.projectFolder = reader.next();
|
||||
return true;
|
||||
}, "<path>", "set project directory."),
|
||||
ArgC("--test", [¶ms, &reader]() -> bool {
|
||||
ArgC("--test", [](auto& params, auto& reader) -> bool {
|
||||
params.testMode = true;
|
||||
params.scriptFile = reader.next();
|
||||
return true;
|
||||
}, "<path>", "test script file."),
|
||||
ArgC("--script", [¶ms, &reader]() -> bool {
|
||||
ArgC("--script", [](auto& params, auto& reader) -> bool {
|
||||
params.testMode = false;
|
||||
params.scriptFile = reader.next();
|
||||
return true;
|
||||
}, "<path>", "main script file."),
|
||||
ArgC("--headless", [¶ms]() -> bool {
|
||||
ArgC("--headless", [](auto& params, auto& reader) -> bool {
|
||||
params.headless = true;
|
||||
return true;
|
||||
}, "", "run in headless mode."),
|
||||
ArgC("--stdin-cmd", [¶ms]() -> bool {
|
||||
ArgC("--stdin-cmd", [](auto& params, auto& reader) -> bool {
|
||||
params.stdinCommands = true;
|
||||
return true;
|
||||
}, "", "run commands from stdin."),
|
||||
ArgC("--tps", [¶ms, &reader]() -> bool {
|
||||
ArgC("--tps", [](auto& params, auto& reader) -> bool {
|
||||
params.tps = reader.nextInt();
|
||||
return true;
|
||||
}, "<tps>", "headless mode tick-rate (default - 20)."),
|
||||
ArgC("--version", []() -> bool {
|
||||
ArgC("--version", [](auto&, auto&) -> bool {
|
||||
std::cout << ENGINE_VERSION_STRING << std::endl;
|
||||
return false;
|
||||
}, "", "display the engine version."),
|
||||
ArgC("--dbg-server", [¶ms, &reader]() -> bool {
|
||||
ArgC("--dbg-server", [](auto& params, auto& reader) -> bool {
|
||||
params.debugServerString = reader.next();
|
||||
return true;
|
||||
}, "<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 << "Command-line arguments:\n";
|
||||
for (auto& a : argumentsCommandline) {
|
||||
std::cout << std::setw(24) << std::left << (a.keyword + " " + a.args);
|
||||
std::cout << "- " << a.help << std::endl;
|
||||
for (auto& arg : argumentsCommandline) {
|
||||
if (arg.help.empty()) {
|
||||
continue;
|
||||
}
|
||||
std::cout << std::setw(24) << std::left
|
||||
<< (arg.keyword + " " + arg.args);
|
||||
std::cout << "- " << arg.help << std::endl;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
return false;
|
||||
|
|
@ -91,7 +99,7 @@ static bool perform_keyword(
|
|||
};
|
||||
for (auto& a : argumentsCommandline) {
|
||||
if (a.keyword == keyword) {
|
||||
return a.execute();
|
||||
return a.execute(params, reader);
|
||||
}
|
||||
}
|
||||
throw std::runtime_error("unknown argument " + keyword);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,13 @@
|
|||
#else
|
||||
#include <sys/poll.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
|
||||
|
||||
namespace platform::internal {
|
||||
|
|
@ -215,8 +222,102 @@ std::filesystem::path platform::get_executable_path() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void platform::new_engine_instance(
|
||||
const std::vector<std::string>& args, std::filesystem::path outputFile
|
||||
class SystemProcess final : public Process {
|
||||
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();
|
||||
|
||||
|
|
@ -285,6 +386,38 @@ void platform::new_engine_instance(
|
|||
&si,
|
||||
&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) {
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
|
@ -295,6 +428,46 @@ void platform::new_engine_instance(
|
|||
);
|
||||
}
|
||||
#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;
|
||||
ss << executable;
|
||||
for (int i = 0; i < args.size(); i++) {
|
||||
|
|
@ -315,6 +488,7 @@ void platform::new_engine_instance(
|
|||
);
|
||||
}
|
||||
#endif
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool platform::stdin_has_data() {
|
||||
|
|
|
|||
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
|
||||
#include "interfaces/Process.hpp"
|
||||
|
||||
namespace platform {
|
||||
void configure_encoding();
|
||||
/// @brief Get Environment locale in ISO format ll_CC
|
||||
|
|
@ -18,8 +21,10 @@ namespace platform {
|
|||
/// @brief Get current process running executable path
|
||||
std::filesystem::path get_executable_path();
|
||||
/// @brief Run a separate engine instance with specified arguments
|
||||
void new_engine_instance(
|
||||
const std::vector<std::string>& args, std::filesystem::path outputFile
|
||||
std::unique_ptr<Process> new_engine_instance(
|
||||
const std::vector<std::string>& args,
|
||||
std::filesystem::path outputFile,
|
||||
bool subProcess
|
||||
);
|
||||
/// @brief Open URL in web browser
|
||||
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 std::string DEFAULT_MATERIAL = "base:stone";
|
||||
|
||||
struct BlockFuncsSet {
|
||||
bool init : 1;
|
||||
bool update : 1;
|
||||
|
|
@ -206,7 +204,7 @@ public:
|
|||
dv::value properties = nullptr;
|
||||
|
||||
/// @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
|
||||
/// clouds)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue