From a7ad219261fa1f5859c85f07ec74c1341254813a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 12 May 2026 02:02:06 +0300 Subject: [PATCH 01/59] test environment (sky) --- src/data/dv.hpp | 28 ++++ src/graphics/core/Cubemap.cpp | 23 ++- src/graphics/core/Cubemap.hpp | 1 + src/graphics/core/Framebuffer.cpp | 60 ++++--- src/graphics/core/Framebuffer.hpp | 3 +- src/graphics/core/Texture.cpp | 20 ++- src/graphics/core/Texture.hpp | 6 +- src/graphics/render/Skybox.cpp | 219 +++++++++++++++++--------- src/graphics/render/Skybox.hpp | 52 +++--- src/graphics/render/WorldRenderer.cpp | 146 +++++++++-------- src/graphics/render/WorldRenderer.hpp | 2 + src/world/Environment.cpp | 25 +++ src/world/Environment.hpp | 30 ++++ src/world/Level.hpp | 2 + 14 files changed, 421 insertions(+), 196 deletions(-) create mode 100644 src/world/Environment.cpp create mode 100644 src/world/Environment.hpp diff --git a/src/data/dv.hpp b/src/data/dv.hpp index 3c3f4a71e..8acd22136 100644 --- a/src/data/dv.hpp +++ b/src/data/dv.hpp @@ -9,6 +9,10 @@ #include #include +#ifdef VC_ENABLE_REFLECTION +#include "util/EnumMetadata.hpp" +#endif + namespace util { template class Buffer; } @@ -70,6 +74,14 @@ namespace dv { return ptr != nullptr; } + inline value* operator->() noexcept { + return ptr; + } + + inline const value* operator->() const noexcept { + return ptr; + } + inline value& operator*() noexcept { return *ptr; } @@ -78,6 +90,11 @@ namespace dv { return *ptr; } +#ifdef VC_ENABLE_REFLECTION + template + bool get(T& dst, const util::EnumMetadata& mt) const; +#endif + bool get(std::string& dst) const; bool get(bool& dst) const; bool get(char& dst) const; @@ -552,6 +569,17 @@ namespace dv { } return false; } + +#ifdef VC_ENABLE_REFLECTION + template + inline bool optionalvalue::get(T& dst, const util::EnumMetadata& mt) const { + if (ptr) { + return mt.getItem(ptr->asString(), dst); + } + return false; + } +#endif + inline bool optionalvalue::get(std::string& dst) const { if (ptr) { dst = ptr->asString(); diff --git a/src/graphics/core/Cubemap.cpp b/src/graphics/core/Cubemap.cpp index 631a8d7e4..e55fb3127 100644 --- a/src/graphics/core/Cubemap.cpp +++ b/src/graphics/core/Cubemap.cpp @@ -4,7 +4,7 @@ #include Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat) - : Texture(0, width, height) + : Texture(0U, width, height, imageFormat) { glGenTextures(1, &id); glBindTexture(GL_TEXTURE_CUBE_MAP, id); @@ -14,7 +14,6 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat) glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - uint format = gl::to_glenum(imageFormat); for (uint face = 0; face < 6; face++) { glTexImage2D( GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, @@ -37,3 +36,23 @@ void Cubemap::bind() const { void Cubemap::unbind() const { glBindTexture(GL_TEXTURE_CUBE_MAP, 0); } + +void Cubemap::reload(const ubyte* data, uint width, uint height) { + this->width = width; + this->height = height; + glBindTexture(GL_TEXTURE_CUBE_MAP, id); + for (uint face = 0; face < 6; face++) { + glTexImage2D( + GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, + 0, + format, + width, + height, + 0, + format, + GL_UNSIGNED_BYTE, + data + ); + } + glBindTexture(GL_TEXTURE_CUBE_MAP, 0); +} diff --git a/src/graphics/core/Cubemap.hpp b/src/graphics/core/Cubemap.hpp index 4ca1b9884..e266c4381 100644 --- a/src/graphics/core/Cubemap.hpp +++ b/src/graphics/core/Cubemap.hpp @@ -9,4 +9,5 @@ public: virtual void bind() const override; virtual void unbind() const override; + virtual void reload(const ubyte* data, uint w, uint h) override; }; diff --git a/src/graphics/core/Framebuffer.cpp b/src/graphics/core/Framebuffer.cpp index 5d6be967d..dde31f6be 100644 --- a/src/graphics/core/Framebuffer.cpp +++ b/src/graphics/core/Framebuffer.cpp @@ -1,14 +1,14 @@ #include "Framebuffer.hpp" #include + #include "Texture.hpp" #include "debug/Logger.hpp" static debug::Logger logger("gl-framebuffer"); Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr texture) - : fbo(fbo), depth(depth), texture(std::move(texture)) -{ + : fbo(fbo), depth(depth), texture(std::move(texture)) { if (this->texture) { width = this->texture->getWidth(); height = this->texture->getHeight(); @@ -18,35 +18,55 @@ Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr texture) } } -static std::unique_ptr create_texture(int width, int height, int format) { - GLuint tex; - glGenTextures(1, &tex); - glBindTexture(GL_TEXTURE_2D, tex); - glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr); +static std::unique_ptr create_texture( + int width, int height, bool alpha +) { + GLenum glformat = alpha ? GL_RGBA : GL_RGB; + + GLuint textureid; + glGenTextures(1, &textureid); + glBindTexture(GL_TEXTURE_2D, textureid); + glTexImage2D( + GL_TEXTURE_2D, + 0, + glformat, + width, + height, + 0, + glformat, + GL_UNSIGNED_BYTE, + nullptr + ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0); - return std::make_unique(tex, width, height); + glFramebufferTexture2D( + GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid, 0 + ); + return std::make_unique( + textureid, + width, + height, + alpha ? ImageFormat::RGBA8888 : ImageFormat::RGB888 + ); } -Framebuffer::Framebuffer(uint width, uint height, bool alpha) - : width(width), height(height) -{ +Framebuffer::Framebuffer(uint width, uint height, bool alpha) + : width(width), height(height) { glGenFramebuffers(1, &fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo); - format = alpha ? GL_RGBA : GL_RGB; - // Setup color attachment (texture) - texture = create_texture(width, height, format); + texture = create_texture(width, height, alpha); // Setup depth attachment glGenRenderbuffers(1, &depth); glBindRenderbuffer(GL_RENDERBUFFER, depth); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); - glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth); + glFramebufferRenderbuffer( + GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth + ); if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { logger.error() << "framebuffer is not complete!"; @@ -60,6 +80,10 @@ Framebuffer::~Framebuffer() { glDeleteRenderbuffers(1, &depth); } +void Framebuffer::setTexture(std::unique_ptr texture) { + this->texture = std::move(texture); +} + void Framebuffer::bind() { glBindFramebuffer(GL_FRAMEBUFFER, fbo); } @@ -81,7 +105,7 @@ void Framebuffer::resize(uint width, uint height) { glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height); glBindRenderbuffer(GL_RENDERBUFFER, 0); - texture = create_texture(width, height, format); + texture->resize(width, height); glBindFramebuffer(GL_FRAMEBUFFER, 0); } @@ -102,5 +126,5 @@ uint Framebuffer::getHeight() const { } uint Framebuffer::getFBO() const { - return fbo; + return fbo; } diff --git a/src/graphics/core/Framebuffer.hpp b/src/graphics/core/Framebuffer.hpp index 4d21449c5..f64d33f37 100644 --- a/src/graphics/core/Framebuffer.hpp +++ b/src/graphics/core/Framebuffer.hpp @@ -12,13 +12,14 @@ class Framebuffer : public Bindable { uint depth; uint width; uint height; - uint format; std::shared_ptr texture; public: Framebuffer(uint fbo, uint depth, std::unique_ptr texture); Framebuffer(uint width, uint height, bool alpha=false); ~Framebuffer(); + void setTexture(std::unique_ptr texture); + /// @brief Use framebuffer void bind() override; diff --git a/src/graphics/core/Texture.cpp b/src/graphics/core/Texture.cpp index d60bd2779..5f326d20c 100644 --- a/src/graphics/core/Texture.cpp +++ b/src/graphics/core/Texture.cpp @@ -7,8 +7,8 @@ uint Texture::MAX_RESOLUTION = 1024; // Window.initialize overrides it -Texture::Texture(uint id, uint width, uint height) - : id(id), width(width), height(height) { +Texture::Texture(uint id, uint width, uint height, ImageFormat imageFormat) + : id(id), width(width), height(height), format(gl::to_glenum(imageFormat)) { } Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFormat) @@ -17,7 +17,7 @@ Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFo glBindTexture(GL_TEXTURE_2D, id); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - GLenum format = gl::to_glenum(imageFormat); + format = gl::to_glenum(imageFormat); glTexImage2D( GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, static_cast(data) @@ -41,23 +41,27 @@ void Texture::unbind() const { glBindTexture(GL_TEXTURE_2D, 0); } +void Texture::resize(uint width, uint height) { + reload(nullptr, width, height); +} + void Texture::reload(const ImageData& image) { - width = image.getWidth(); - height = image.getHeight(); reload(image.getData(), width, height); } void Texture::reload(const ubyte* data, uint width, uint height) { + this->width = width; + this->height = height; glBindTexture(GL_TEXTURE_2D, id); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, static_cast(data)); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, + format, GL_UNSIGNED_BYTE, static_cast(data)); glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) { glBindTexture(GL_TEXTURE_2D, id); - glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData()); + glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, format, GL_UNSIGNED_BYTE, image.getData()); glGenerateMipmap(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, 0); } diff --git a/src/graphics/core/Texture.hpp b/src/graphics/core/Texture.hpp index 3c8f1b4cb..50734a577 100644 --- a/src/graphics/core/Texture.hpp +++ b/src/graphics/core/Texture.hpp @@ -11,19 +11,21 @@ protected: uint id; uint width; uint height; + uint format; public: - Texture(uint id, uint width, uint height); + Texture(uint id, uint width, uint height, ImageFormat imageFormat); Texture(const ubyte* data, uint width, uint height, ImageFormat format); virtual ~Texture(); virtual void bind() const; virtual void unbind() const; - void reload(const ubyte* data, uint w, uint h); + virtual void reload(const ubyte* data, uint w, uint h); void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h); void setNearestFilter(); void reload(const ImageData& image); + virtual void resize(uint w, uint h); void setMipMapping(bool flag, bool pixelated); diff --git a/src/graphics/render/Skybox.cpp b/src/graphics/render/Skybox.cpp index c546c62e3..79e5552af 100644 --- a/src/graphics/render/Skybox.cpp +++ b/src/graphics/render/Skybox.cpp @@ -1,47 +1,44 @@ #include "Skybox.hpp" -#include "assets/Assets.hpp" -#include "graphics/core/Shader.hpp" -#include "graphics/core/Mesh.hpp" -#include "graphics/core/Batch3D.hpp" -#include "graphics/core/Texture.hpp" -#include "graphics/core/Cubemap.hpp" -#include "graphics/core/Framebuffer.hpp" -#include "graphics/core/DrawContext.hpp" -#include "window/Window.hpp" -#include "window/Camera.hpp" -#include "maths/UVRegion.hpp" + +#include #include -#include -#include #include -#include #include +#include +#include + +#include "assets/Assets.hpp" +#include "graphics/core/Batch3D.hpp" +#include "graphics/core/Cubemap.hpp" +#include "graphics/core/DrawContext.hpp" +#include "graphics/core/Framebuffer.hpp" +#include "graphics/core/Mesh.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/core/Texture.hpp" +#include "maths/UVRegion.hpp" +#include "window/Camera.hpp" +#include "window/Window.hpp" using namespace advanced_pipeline; const int STARS_COUNT = 3000; + const int STARS_SEED = 632; -Skybox::Skybox(uint size, Shader& shader) - : size(size), - shader(shader), - batch3d(std::make_unique(4096)) -{ - auto cubemap = std::make_unique(size, size, ImageFormat::RGB888); +Skybox::Skybox(uint size, const Assets& assets) + : size(size), + assets(assets), + shader(assets.require("skybox_gen")), + batch3d(std::make_unique(4096)) { - uint fboid; - glGenFramebuffers(1, &fboid); - fbo = std::make_unique(fboid, 0, std::move(cubemap)); - - SkyboxVertex vertices[]{ + SkyboxVertex vertices[] { {{-1.0f, -1.0f}}, {{-1.0f, 1.0f}}, {{1.0f, 1.0f}}, {{-1.0f, -1.0f}}, {{1.0f, 1.0f}}, - {{1.0f, -1.0f}} - }; + {{1.0f, -1.0f}}}; mesh = std::make_unique>(vertices, 6); @@ -52,7 +49,7 @@ Skybox::Skybox(uint size, Shader& shader) false, glm::pi() * 0.25f, }); - + sprites.push_back(SkySprite { "misc/moon_flare", glm::pi() * 0.5f, @@ -68,20 +65,20 @@ Skybox::Skybox(uint size, Shader& shader) true, glm::pi() * 0.25f, }); + + setMode(mode); } Skybox::~Skybox() = default; -void Skybox::drawBackground( - const Camera& camera, const Assets& assets, int width, int height -) { +void Skybox::drawBackground(const Camera& camera, int width, int height) { auto backShader = assets.get("background"); backShader->use(); backShader->uniformMatrix("u_view", camera.getView(false)); backShader->uniform1f( "u_zoom", camera.zoom * camera.getFov() / glm::half_pi() ); - backShader->uniform1f("u_ar", float(width)/float(height)); + backShader->uniform1f("u_ar", float(width) / float(height)); backShader->uniform1i("u_skybox", 1); bind(); mesh->draw(); @@ -93,9 +90,7 @@ void Skybox::drawStars(float angle, float opacity) { random.setSeed(STARS_SEED); glm::mat4 rotation = glm::rotate( - glm::mat4(1.0f), - -angle + glm::pi() * 0.5f, - glm::vec3(0, 0, -1) + glm::mat4(1.0f), -angle + glm::pi() * 0.5f, glm::vec3(0, 0, -1) ); rotation = glm::rotate(rotation, sunAltitude, glm::vec3(1, 0, 0)); @@ -108,41 +103,20 @@ void Skybox::drawStars(float angle, float opacity) { glm::vec3 pos = glm::vec4(rx, ry, rz, 1) * rotation; float sopacity = random.randFloat(); - if (pos.y < 0.0f) - continue; + if (pos.y < 0.0f) continue; sopacity *= (0.2f + std::sqrt(std::cos(angle)) * 0.5f) - 0.05f; - glm::vec4 tint (1,1,1, sopacity * opacity); + glm::vec4 tint(1, 1, 1, sopacity * opacity); batch3d->point(pos * depth, tint); } batch3d->flushPoints(); } -void Skybox::draw( - const DrawContext& pctx, - const Camera& camera, - const Assets& assets, +void Skybox::drawSkySprites( float daytime, - float fog) -{ - const glm::uvec2& viewport = pctx.getViewport(); - - glActiveTexture(GL_TEXTURE0); - - drawBackground(camera, assets, viewport.x, viewport.y); - - DrawContext ctx = pctx.sub(); - ctx.setBlendMode(BlendMode::addition); - - auto shader = assets.get("ui3d"); - shader->use(); - shader->uniformMatrix("u_projview", camera.getProjView(false)); - shader->uniformMatrix("u_apply", glm::mat4(1.0f)); - batch3d->begin(); - - float angle = daytime * glm::pi() * 2.0f; - float opacity = glm::pow(1.0f - fog, 7.0f); - + float angle, + float opacity +) { float depthScale = 2e3; for (auto& sprite : sprites) { batch3d->texture(assets.get(sprite.texture)); @@ -159,39 +133,132 @@ void Skybox::draw( glm::vec3 pos = glm::vec4(0, distance, 0, 1) * rotation; glm::vec3 up = glm::vec4(depthScale, 0, 0, 1) * rotation; glm::vec3 right = glm::vec4(0, 0, depthScale, 1) * rotation; - glm::vec4 tint (1,1,1, opacity); + glm::vec4 tint(1, 1, 1, opacity); if (!sprite.emissive) { - tint *= 0.6f + std::cos(angle)*0.4; + tint *= 0.6f + std::cos(angle) * 0.4; } - batch3d->sprite(pos, right, - up, 1, 1, UVRegion(), tint); + batch3d->sprite(pos, right, up, 1, 1, UVRegion(), tint); } batch3d->flush(); - drawStars(angle, opacity); } -void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality) { +void Skybox::draw( + const Environment& environment, + const DrawContext& pctx, + const Camera& camera, + float daytime, + float fog +) { + const glm::uvec2& viewport = pctx.getViewport(); + + glActiveTexture(GL_TEXTURE0); + + drawBackground(camera, viewport.x, viewport.y); + + float angle = daytime * glm::pi() * 2.0f; + float opacity = glm::pow(1.0f - fog, 7.0f); + + DrawContext ctx = pctx.sub(); + ctx.setBlendMode(BlendMode::addition); + + if (!environment.sky.sprites && !environment.sky.stars) { + return; + } + + auto shader = assets.get("ui3d"); + shader->use(); + shader->uniformMatrix("u_projview", camera.getProjView(false)); + shader->uniformMatrix("u_apply", glm::mat4(1.0f)); + batch3d->begin(); + + if (environment.sky.sprites) { + drawSkySprites(daytime, angle, opacity); + } + if (environment.sky.stars) { + drawStars(angle, opacity); + } +} + +void Skybox::setMode(SkyMode mode) { + if (this->mode == mode && fbo) { + return; + } + this->mode = mode; + + uint size = mode == SkyMode::BOX ? this->size : 1U; + prevT = std::numeric_limits().min(); + + uint fboid; + glGenFramebuffers(1, &fboid); + fbo = std::make_unique( + fboid, + false, + std::make_unique(size, size, ImageFormat::RGB888) + ); +} + +void Skybox::refresh( + const Environment& environment, + const DrawContext& pctx, + float dayTime, + float mie, + const glm::vec3& tint, + const glm::vec3& hightlight, + uint quality +) { + setMode(environment.sky.mode); + + if (mode == SkyMode::NONE) { + return; + } else if (mode == SkyMode::SOLID) { + DrawContext ctx = pctx.sub(); + ctx.setDepthMask(false); + ctx.setDepthTest(false); + ctx.setFramebuffer(fbo.get()); + ctx.setViewport({fbo->getWidth(), fbo->getHeight()}); + + auto cubemap = dynamic_cast(fbo->getTexture()); + assert(cubemap != nullptr); + + glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX); + cubemap->bind(); + + for (int face = 0; face < 6; face++) { + glFramebufferTexture2D( + GL_FRAMEBUFFER, + GL_COLOR_ATTACHMENT0, + GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, + cubemap->getId(), + 0 + ); + glClearColor(tint.r, tint.g, tint.b, 1); + glClear(GL_COLOR_BUFFER_BIT); + } + + cubemap->unbind(); + return; + } + frameid++; - float dayTime = t; DrawContext ctx = pctx.sub(); ctx.setDepthMask(false); ctx.setDepthTest(false); ctx.setFramebuffer(fbo.get()); - ctx.setViewport({size, size}); + ctx.setViewport({fbo->getWidth(), fbo->getHeight()}); auto cubemap = dynamic_cast(fbo->getTexture()); assert(cubemap != nullptr); - ready = true; glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX); cubemap->bind(); shader.use(); - t *= glm::two_pi(); + float t = dayTime * glm::two_pi(); lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f)); - float sunAngle = glm::radians((t / glm::two_pi() - 0.25f) * 360.0f); - float x = -glm::cos(sunAngle + glm::pi() * 0.5f) * glm::radians(sunAltitude); + float sunAngle = glm::radians((dayTime - 0.25f) * 360.0f); + float x = -glm::cos(sunAngle + glm::pi() * 0.5f) * + glm::radians(sunAltitude); float y = sunAngle - glm::pi() * 0.5f; float z = glm::radians(0.0f); rotation = glm::rotate(glm::mat4(1.0f), y, glm::vec3(0, 1, 0)); @@ -207,7 +274,9 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec shader.uniform3f("u_lightDir", lightDir); shader.uniform1f("u_dayTime", dayTime); - if (glm::abs(mie-prevMie) + glm::abs(t-prevT) + glm::abs(prevHighlight.r - hightlight.r) >= 0.01) { + if (glm::abs(mie - prevMie) + glm::abs(t - prevT) + + glm::abs(prevHighlight.r - hightlight.r) >= + 0.01) { for (uint face = 0; face < 6; face++) { refreshFace(face, cubemap); } diff --git a/src/graphics/render/Skybox.hpp b/src/graphics/render/Skybox.hpp index d2dde6f5e..270dc066d 100644 --- a/src/graphics/render/Skybox.hpp +++ b/src/graphics/render/Skybox.hpp @@ -1,14 +1,18 @@ #pragma once +#include #include #include #include -#include -#include "typedefs.hpp" -#include "maths/fastmaths.hpp" -#include "graphics/core/MeshData.hpp" -template class Mesh; +#include "graphics/core/MeshData.hpp" +#include "maths/fastmaths.hpp" +#include "typedefs.hpp" +#include "world/Environment.hpp" + + +template +class Mesh; class Shader; class Assets; class Camera; @@ -21,8 +25,7 @@ struct SkyboxVertex { glm::vec2 position; static constexpr VertexAttribute ATTRIBUTES[] { - {VertexAttribute::Type::FLOAT, false, 2}, - {{}, 0}}; + {VertexAttribute::Type::FLOAT, false, 2}, {{}, 0}}; }; struct SkySprite { @@ -34,10 +37,11 @@ struct SkySprite { }; class Skybox { - std::unique_ptr fbo; + SkyMode mode = SkyMode::SOLID; uint size; + std::unique_ptr fbo; + const Assets& assets; Shader& shader; - bool ready = false; FastRandom random; glm::vec3 lightDir; @@ -53,30 +57,36 @@ class Skybox { glm::mat4 rotation; void drawStars(float angle, float opacity); - void drawBackground( - const Camera& camera, const Assets& assets, int width, int height - ); + void drawBackground(const Camera& camera, int width, int height); + void drawSkySprites(float daytime, float angle, float opacity); void refreshFace(uint face, Cubemap* cubemap); public: - Skybox(uint size, Shader& shader); + Skybox(uint size, const Assets& assets); ~Skybox(); + void setMode(SkyMode mode); + void draw( - const DrawContext& pctx, - const Camera& camera, - const Assets& assets, + const Environment& environment, + const DrawContext& pctx, + const Camera& camera, float daytime, float fog ); - void refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality); + void refresh( + const Environment& environment, + const DrawContext& pctx, + float t, + float mie, + const glm::vec3& tint, + const glm::vec3& hightlight, + uint quality + ); void bind() const; void unbind() const; - bool isReady() const { - return ready; - } - const glm::vec3 getLightDir() const { + const glm::vec3& getLightDir() const { return lightDir; } }; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 2e150e1f7..e5d838f7d 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -1,12 +1,45 @@ #include "WorldRenderer.hpp" +#include + +#include +#include +#include +#include + +#include "BlockWrapsRenderer.hpp" +#include "ChunksRenderer.hpp" +#include "CloudsRenderer.hpp" +#include "DebugLinesRenderer.hpp" +#include "Emitter.hpp" +#include "HandsRenderer.hpp" +#include "ModelBatch.hpp" +#include "NamedSkeletons.hpp" +#include "ParticlesRenderer.hpp" +#include "PrecipitationRenderer.hpp" +#include "Skybox.hpp" +#include "TextNote.hpp" +#include "TextsRenderer.hpp" #include "assets/Assets.hpp" #include "assets/assets_util.hpp" +#include "coders/GLSLExtension.hpp" #include "content/Content.hpp" #include "engine/Engine.hpp" -#include "coders/GLSLExtension.hpp" -#include "frontend/LevelFrontend.hpp" #include "frontend/ContentGfxCache.hpp" +#include "frontend/LevelFrontend.hpp" +#include "graphics/commons/Model.hpp" +#include "graphics/core/Atlas.hpp" +#include "graphics/core/Batch3D.hpp" +#include "graphics/core/DrawContext.hpp" +#include "graphics/core/Font.hpp" +#include "graphics/core/Framebuffer.hpp" +#include "graphics/core/GBuffer.hpp" +#include "graphics/core/LineBatch.hpp" +#include "graphics/core/Mesh.hpp" +#include "graphics/core/PostProcessing.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/core/Shadows.hpp" +#include "graphics/core/Texture.hpp" #include "items/Inventory.hpp" #include "items/ItemDef.hpp" #include "items/ItemStack.hpp" @@ -25,38 +58,6 @@ #include "world/Level.hpp" #include "world/LevelEvents.hpp" #include "world/World.hpp" -#include "graphics/commons/Model.hpp" -#include "graphics/core/Atlas.hpp" -#include "graphics/core/Batch3D.hpp" -#include "graphics/core/DrawContext.hpp" -#include "graphics/core/LineBatch.hpp" -#include "graphics/core/Mesh.hpp" -#include "graphics/core/PostProcessing.hpp" -#include "graphics/core/Framebuffer.hpp" -#include "graphics/core/Shader.hpp" -#include "graphics/core/Texture.hpp" -#include "graphics/core/Font.hpp" -#include "graphics/core/Shadows.hpp" -#include "graphics/core/GBuffer.hpp" -#include "BlockWrapsRenderer.hpp" -#include "ParticlesRenderer.hpp" -#include "PrecipitationRenderer.hpp" -#include "HandsRenderer.hpp" -#include "NamedSkeletons.hpp" -#include "TextsRenderer.hpp" -#include "ChunksRenderer.hpp" -#include "DebugLinesRenderer.hpp" -#include "ModelBatch.hpp" -#include "Skybox.hpp" -#include "Emitter.hpp" -#include "TextNote.hpp" -#include "CloudsRenderer.hpp" - -#include -#include -#include -#include -#include using namespace advanced_pipeline; @@ -105,8 +106,7 @@ WorldRenderer::WorldRenderer( [this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); } ); skybox = std::make_unique( - settings.graphics.skyboxResolution.get(), - assets.require("skybox_gen") + settings.graphics.skyboxResolution.get(), assets ); const auto& content = level.content; @@ -179,6 +179,11 @@ void WorldRenderer::setupWorldShader( } } +float WorldRenderer::calcFogFactor() const { + const auto& settings = engine.getSettings(); + return 15.0f / static_cast(settings.chunks.loadDistance.get() - 2); +} + void WorldRenderer::renderOpaque( const DrawContext& ctx, const Camera& camera, @@ -187,14 +192,13 @@ void WorldRenderer::renderOpaque( ) { texts->render(ctx, camera, settings, hudVisible, false); - bool culling = engine.getSettings().graphics.frustumCulling.get(); - float fogFactor = - 15.0f / static_cast(settings.chunks.loadDistance.get() - 2); + float fogFactor = calcFogFactor(); auto& entityShader = assets.require("entity"); setupWorldShader(entityShader, camera, settings, fogFactor); skybox->bind(); + bool culling = engine.getSettings().graphics.frustumCulling.get(); if (culling) { frustumCulling->update(camera.getProjView()); } @@ -213,22 +217,24 @@ void WorldRenderer::renderOpaque( auto& shader = assets.require("main"); auto& cloudsShader = assets.require("clouds"); - auto& linesShader = assets.require("lines"); setupWorldShader(shader, camera, settings, fogFactor); chunksRenderer->drawChunks(camera, shader); blockWraps->draw(ctx); - int cloudsQuality = settings.graphics.cloudsQuality.get(); - if (cloudsQuality > 0) { - setupWorldShader(cloudsShader, camera, settings, fogFactor); - cloudsRenderer->draw( - cloudsShader, weather, timer, fogFactor, camera, cloudsQuality - ); + if (level.environment.sky.clouds) { + int cloudsQuality = settings.graphics.cloudsQuality.get(); + if (cloudsQuality > 0) { + setupWorldShader(cloudsShader, camera, settings, fogFactor); + cloudsRenderer->draw( + cloudsShader, weather, timer, fogFactor, camera, cloudsQuality + ); + } } if (hudVisible) { + auto& linesShader = assets.require("lines"); renderLines(camera, linesShader, ctx); } skybox->unbind(); @@ -286,21 +292,19 @@ void WorldRenderer::refreshSettings(Shader** shaders) { int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline; shadowMapping->setQuality(shadowsQuality); - + CompileTimeShaderSettings currentSettings { gbufferPipeline, shadowsQuality != 0, - graphics.ssao.get() && gbufferPipeline - }; - if ( - prevCTShaderSettings.advancedRender != currentSettings.advancedRender || + graphics.ssao.get() && gbufferPipeline}; + if (prevCTShaderSettings.advancedRender != currentSettings.advancedRender || prevCTShaderSettings.shadows != currentSettings.shadows || - prevCTShaderSettings.ssao != currentSettings.ssao - ) { + prevCTShaderSettings.ssao != currentSettings.ssao) { std::vector defines; if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS"); if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO"); - if (currentSettings.advancedRender) defines.emplace_back("ADVANCED_RENDER"); + if (currentSettings.advancedRender) + defines.emplace_back("ADVANCED_RENDER"); for (size_t i = 0; shaders[i]; i++) { shaders[i]->recompile(defines); @@ -332,7 +336,8 @@ void WorldRenderer::renderFrame( auto& entityShader = assets.require("entity"); auto& cloudsShader = assets.require("clouds"); auto& translucentShader = assets.require("translucent"); - auto& deferredShader = assets.require("deferred_lighting").getShader(); + auto& deferredShader = + assets.require("deferred_lighting").getShader(); const auto& settings = engine.getSettings(); @@ -342,19 +347,19 @@ void WorldRenderer::renderFrame( &cloudsShader, &translucentShader, &deferredShader, - nullptr - }; + nullptr}; refreshSettings(affectedShaders); const auto& worldInfo = world->getInfo(); - + float clouds = weather.clouds(); clouds = glm::max(worldInfo.fog, clouds); float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f; float random = rand() / static_cast(RAND_MAX); skybox->refresh( + level.environment, pctx, worldInfo.daytime, mie, @@ -385,8 +390,7 @@ void WorldRenderer::renderFrame( texts->render(pctx, camera, settings, hudVisible, true); } skybox->bind(); - float fogFactor = - 15.0f / static_cast(settings.chunks.loadDistance.get() - 2); + float fogFactor = calcFogFactor(); if (gbufferPipeline) { deferredShader.use(); setupWorldShader(deferredShader, camera, settings, fogFactor); @@ -403,7 +407,9 @@ void WorldRenderer::renderFrame( } // Background sky plane - skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); + skybox->draw( + level.environment, ctx, camera, worldInfo.daytime, clouds + ); auto& linesShader = assets.require("lines"); linesShader.use(); @@ -429,14 +435,17 @@ void WorldRenderer::renderFrame( entityShader.use(); setupWorldShader(entityShader, camera, settings, fogFactor); - std::array weatherInstances {&weather.a, &weather.b}; + std::array weatherInstances { + &weather.a, &weather.b}; for (const auto& weather : weatherInstances) { float maxIntensity = weather->fall.maxIntensity; float zero = weather->fall.minOpacity; float one = weather->fall.maxOpacity; float t = (weather->intensity * (one - zero)) * maxIntensity + zero; entityShader.uniform1i("u_alphaClip", weather->fall.opaque); - entityShader.uniform1f("u_opacity", weather->fall.opaque ? t * t : t); + entityShader.uniform1f( + "u_opacity", weather->fall.opaque ? t * t : t + ); if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) { precipitation->render(camera, *weather); } @@ -446,7 +455,7 @@ void WorldRenderer::renderFrame( glBindFramebuffer(GL_FRAMEBUFFER, 0); } postProcessing.render(pctx, assets, timer, camera); - + if (player.currentCamera == player.fpCamera) { DrawContext ctx = pctx.sub(); ctx.setDepthTest(true); @@ -457,7 +466,7 @@ void WorldRenderer::renderFrame( hudcam.far = 10.0f; hudcam.setFov(0.9f); hudcam.position = {}; - + hands->render(camera); display::clearDepth(); @@ -486,13 +495,12 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { if (def.overlayTexture.empty()) { return; } - auto textureRegion = util::get_texture_region( - assets, def.overlayTexture, "blocks:notfound" - ); + auto textureRegion = + util::get_texture_region(assets, def.overlayTexture, "blocks:notfound"); DrawContext ctx = wctx.sub(); ctx.setDepthTest(false); ctx.setCullFace(false); - + auto& shader = assets.require("ui3d"); shader.use(); batch3d->begin(); diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index b9a893d16..c3e6b1669 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -99,6 +99,8 @@ class WorldRenderer { ); void refreshSettings(Shader** shaders); + + float calcFogFactor() const; public: std::unique_ptr particles; std::unique_ptr texts; diff --git a/src/world/Environment.cpp b/src/world/Environment.cpp new file mode 100644 index 000000000..d2ad414bf --- /dev/null +++ b/src/world/Environment.cpp @@ -0,0 +1,25 @@ +#define VC_ENABLE_REFLECTION +#include "Environment.hpp" + +#include "data/dv.hpp" +#include "data/dv_util.hpp" + +dv::value Environment::serialize() const { + return dv::object({ + {"sky", dv::object({ + {"mode", SkyModeMeta.getNameString(sky.mode)}, + {"stars", sky.stars}, + {"clouds", sky.clouds}, + {"sprites", sky.sprites}, + })}, + }); +} + +void Environment::deserialize(const dv::value& src) { + if (auto skyItem = src.at("sky")) { + skyItem->at("mode").get(sky.mode, SkyModeMeta); + skyItem->at("stars").get(sky.stars); + skyItem->at("clouds").get(sky.clouds); + skyItem->at("sprites").get(sky.sprites); + } +} diff --git a/src/world/Environment.hpp b/src/world/Environment.hpp new file mode 100644 index 000000000..5b5f668eb --- /dev/null +++ b/src/world/Environment.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include "interfaces/Serializable.hpp" +#include "util/EnumMetadata.hpp" + +enum class SkyMode { + NONE, SOLID, BOX +}; + +VC_ENUM_METADATA(SkyMode) + {"none", SkyMode::NONE}, + {"solid", SkyMode::SOLID}, + {"box", SkyMode::BOX}, +VC_ENUM_END + +class Environment : public Serializable { +public: + struct { + SkyMode mode = SkyMode::BOX; + bool stars = true; + bool clouds = true; + bool sprites = true; + } sky; + + Environment() = default; + + dv::value serialize() const override; + void deserialize(const dv::value& src) override; +private: +}; diff --git a/src/world/Level.hpp b/src/world/Level.hpp index 0dd3251fa..c8945a23c 100644 --- a/src/world/Level.hpp +++ b/src/world/Level.hpp @@ -6,6 +6,7 @@ #include #include "typedefs.hpp" +#include "Environment.hpp" class Content; class World; @@ -36,6 +37,7 @@ public: std::unique_ptr players; std::unique_ptr pathfinding; std::vector> cameras; // move somewhere? + Environment environment {}; Level( std::unique_ptr world, From 28b77136488d988f7e9eb5311db1ac02f8481c2b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 12 May 2026 18:41:14 +0300 Subject: [PATCH 02/59] move some code there and here --- src/graphics/core/DrawContext.cpp | 22 +++++++++++++ src/graphics/core/DrawContext.hpp | 3 ++ src/graphics/core/Framebuffer.cpp | 4 +-- src/graphics/core/Framebuffer.hpp | 4 +-- src/graphics/core/GBuffer.cpp | 4 +-- src/graphics/core/GBuffer.hpp | 4 +-- src/graphics/core/Texture.hpp | 15 +++++---- src/graphics/core/commons.hpp | 6 ++-- src/graphics/render/HandsRenderer.cpp | 3 ++ src/graphics/render/Skybox.cpp | 46 +++++++++------------------ src/graphics/render/Skybox.hpp | 6 ++-- src/graphics/render/WorldRenderer.cpp | 43 +++++++++++-------------- 12 files changed, 84 insertions(+), 76 deletions(-) diff --git a/src/graphics/core/DrawContext.cpp b/src/graphics/core/DrawContext.cpp index fc37fd46d..86e2131b1 100644 --- a/src/graphics/core/DrawContext.cpp +++ b/src/graphics/core/DrawContext.cpp @@ -74,6 +74,18 @@ DrawContext::~DrawContext() { if (lineWidth != parent->lineWidth) { glLineWidth(parent->lineWidth); } + + for (int i = 0; i < textures.size(); i++) { + auto texture = parent->textures[i]; + if (textures[i] != texture) { + glActiveTexture(GL_TEXTURE0 + i); + if (texture == nullptr) { + glBindTexture(GL_TEXTURE_2D, 0); + } else { + texture->bind(); + } + } + } } const glm::uvec2& DrawContext::getViewport() const { @@ -154,3 +166,13 @@ void DrawContext::setLineWidth(float width) { lineWidth = width; glLineWidth(width); } + +void DrawContext::useTexture(int target, const Bindable* texture) { + glActiveTexture(GL_TEXTURE0 + target); + if (texture == nullptr) { + glBindTexture(GL_TEXTURE_2D, 0); + } else { + texture->bind(); + } + textures.at(target) = texture; +} diff --git a/src/graphics/core/DrawContext.hpp b/src/graphics/core/DrawContext.hpp index b14bf71bc..7e568e337 100644 --- a/src/graphics/core/DrawContext.hpp +++ b/src/graphics/core/DrawContext.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -23,6 +24,7 @@ class DrawContext { BlendMode blendMode = BlendMode::normal; int scissorsCount = 0; float lineWidth = 1.0f; + std::array textures {}; public: DrawContext( const DrawContext* parent, @@ -44,4 +46,5 @@ public: void setBlendMode(BlendMode mode); void setScissors(const glm::vec4& area); void setLineWidth(float width); + void useTexture(int target, const Bindable* texture); }; diff --git a/src/graphics/core/Framebuffer.cpp b/src/graphics/core/Framebuffer.cpp index dde31f6be..1dc677096 100644 --- a/src/graphics/core/Framebuffer.cpp +++ b/src/graphics/core/Framebuffer.cpp @@ -84,11 +84,11 @@ void Framebuffer::setTexture(std::unique_ptr texture) { this->texture = std::move(texture); } -void Framebuffer::bind() { +void Framebuffer::bind() const { glBindFramebuffer(GL_FRAMEBUFFER, fbo); } -void Framebuffer::unbind() { +void Framebuffer::unbind() const { glBindFramebuffer(GL_FRAMEBUFFER, 0); } diff --git a/src/graphics/core/Framebuffer.hpp b/src/graphics/core/Framebuffer.hpp index f64d33f37..5f9dc3491 100644 --- a/src/graphics/core/Framebuffer.hpp +++ b/src/graphics/core/Framebuffer.hpp @@ -21,10 +21,10 @@ public: void setTexture(std::unique_ptr texture); /// @brief Use framebuffer - void bind() override; + void bind() const override; /// @brief Stop using framebuffer - void unbind() override; + void unbind() const override; /// @brief Update framebuffer texture size /// @param width new width diff --git a/src/graphics/core/GBuffer.cpp b/src/graphics/core/GBuffer.cpp index aba3103fc..30b0c817a 100644 --- a/src/graphics/core/GBuffer.cpp +++ b/src/graphics/core/GBuffer.cpp @@ -189,7 +189,7 @@ GBuffer::~GBuffer() { glDeleteFramebuffers(1, &ssaoFbo); } -void GBuffer::bind() { +void GBuffer::bind() const { glBindFramebuffer(GL_FRAMEBUFFER, fbo); glClear(GL_COLOR_BUFFER_BIT); } @@ -198,7 +198,7 @@ void GBuffer::bindSSAO() const { glBindFramebuffer(GL_FRAMEBUFFER, ssaoFbo); } -void GBuffer::unbind() { +void GBuffer::unbind() const { glBindFramebuffer(GL_FRAMEBUFFER, 0); } diff --git a/src/graphics/core/GBuffer.hpp b/src/graphics/core/GBuffer.hpp index ae433c873..a549b720b 100644 --- a/src/graphics/core/GBuffer.hpp +++ b/src/graphics/core/GBuffer.hpp @@ -9,9 +9,9 @@ public: GBuffer(uint width, uint height); ~GBuffer() override; - void bind() override; + void bind() const override; void bindSSAO() const; - void unbind() override; + void unbind() const override; void bindBuffers() const; void bindSSAOBuffer() const; diff --git a/src/graphics/core/Texture.hpp b/src/graphics/core/Texture.hpp index 50734a577..30959ed60 100644 --- a/src/graphics/core/Texture.hpp +++ b/src/graphics/core/Texture.hpp @@ -1,12 +1,13 @@ #pragma once -#include "typedefs.hpp" -#include "maths/UVRegion.hpp" -#include "ImageData.hpp" - #include -class Texture { +#include "ImageData.hpp" +#include "commons.hpp" +#include "maths/UVRegion.hpp" +#include "typedefs.hpp" + +class Texture : public Bindable { protected: uint id; uint width; @@ -17,8 +18,8 @@ public: Texture(const ubyte* data, uint width, uint height, ImageFormat format); virtual ~Texture(); - virtual void bind() const; - virtual void unbind() const; + virtual void bind() const override; + virtual void unbind() const override; virtual void reload(const ubyte* data, uint w, uint h); void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h); diff --git a/src/graphics/core/commons.hpp b/src/graphics/core/commons.hpp index 8785cec10..0ca5a6470 100644 --- a/src/graphics/core/commons.hpp +++ b/src/graphics/core/commons.hpp @@ -59,6 +59,8 @@ namespace advanced_pipeline { inline constexpr int TARGET_SSAO = 5; inline constexpr int TARGET_SHADOWS0 = 6; inline constexpr int TARGET_SHADOWS1 = 7; + + inline constexpr int TARGETS_COUNT = 8; } VC_ENUM_METADATA(CursorShape) @@ -85,6 +87,6 @@ class Bindable { public: virtual ~Bindable() = default; - virtual void bind() = 0; - virtual void unbind() = 0; + virtual void bind() const = 0; + virtual void unbind() const = 0; }; diff --git a/src/graphics/render/HandsRenderer.cpp b/src/graphics/render/HandsRenderer.cpp index 8e2f757b2..17c38d76d 100644 --- a/src/graphics/render/HandsRenderer.cpp +++ b/src/graphics/render/HandsRenderer.cpp @@ -34,4 +34,7 @@ void HandsRenderer::render(const Camera& camera) { glm::vec3(), glm::vec3(1.0f) ); + + modelBatch.render(); + modelBatch.setLightsOffset(glm::vec3()); } diff --git a/src/graphics/render/Skybox.cpp b/src/graphics/render/Skybox.cpp index 79e5552af..ef34384fc 100644 --- a/src/graphics/render/Skybox.cpp +++ b/src/graphics/render/Skybox.cpp @@ -80,9 +80,11 @@ void Skybox::drawBackground(const Camera& camera, int width, int height) { ); backShader->uniform1f("u_ar", float(width) / float(height)); backShader->uniform1i("u_skybox", 1); - bind(); + + auto texture = fbo->getTexture(); + texture->bind(); mesh->draw(); - unbind(); + texture->unbind(); } void Skybox::drawStars(float angle, float opacity) { @@ -150,9 +152,6 @@ void Skybox::draw( float fog ) { const glm::uvec2& viewport = pctx.getViewport(); - - glActiveTexture(GL_TEXTURE0); - drawBackground(camera, viewport.x, viewport.y); float angle = daytime * glm::pi() * 2.0f; @@ -216,13 +215,12 @@ void Skybox::refresh( ctx.setDepthTest(false); ctx.setFramebuffer(fbo.get()); ctx.setViewport({fbo->getWidth(), fbo->getHeight()}); + ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture()); + ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); auto cubemap = dynamic_cast(fbo->getTexture()); assert(cubemap != nullptr); - glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX); - cubemap->bind(); - for (int face = 0; face < 6; face++) { glFramebufferTexture2D( GL_FRAMEBUFFER, @@ -234,8 +232,6 @@ void Skybox::refresh( glClearColor(tint.r, tint.g, tint.b, 1); glClear(GL_COLOR_BUFFER_BIT); } - - cubemap->unbind(); return; } @@ -245,14 +241,12 @@ void Skybox::refresh( ctx.setDepthTest(false); ctx.setFramebuffer(fbo.get()); ctx.setViewport({fbo->getWidth(), fbo->getHeight()}); + ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture()); + ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); auto cubemap = dynamic_cast(fbo->getTexture()); assert(cubemap != nullptr); - glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX); - cubemap->bind(); - shader.use(); - float t = dayTime * glm::two_pi(); lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f)); @@ -266,6 +260,7 @@ void Skybox::refresh( rotation = glm::rotate(rotation, z, glm::vec3(0, 0, 1)); lightDir = glm::vec3(rotation * glm::vec4(0, 0, -1, 1)); + shader.use(); shader.uniform1i("u_quality", quality); shader.uniform1f("u_mie", mie); shader.uniform3f("u_tint", tint); @@ -278,21 +273,18 @@ void Skybox::refresh( glm::abs(prevHighlight.r - hightlight.r) >= 0.01) { for (uint face = 0; face < 6; face++) { - refreshFace(face, cubemap); + refreshFace(face, *cubemap); } } else { uint face = frameid % 6; - refreshFace(face, cubemap); + refreshFace(face, *cubemap); } prevMie = mie; prevT = t; prevHighlight = hightlight; - - cubemap->unbind(); - glActiveTexture(GL_TEXTURE0); } -void Skybox::refreshFace(uint face, Cubemap* cubemap) { +void Skybox::refreshFace(uint face, Cubemap& cubemap) { const glm::vec3 xaxs[] = { {0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}, @@ -325,7 +317,7 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) { GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X + face, - cubemap->getId(), + cubemap.getId(), 0 ); shader.uniform3f("u_xaxis", xaxs[face]); @@ -334,14 +326,6 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) { mesh->draw(); } -void Skybox::bind() const { - glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX); - fbo->getTexture()->bind(); - glActiveTexture(GL_TEXTURE0); -} - -void Skybox::unbind() const { - glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX); - fbo->getTexture()->unbind(); - glActiveTexture(GL_TEXTURE0); +const Cubemap* Skybox::getCubemap() const { + return dynamic_cast(fbo->getTexture()); } diff --git a/src/graphics/render/Skybox.hpp b/src/graphics/render/Skybox.hpp index 270dc066d..6d6074e6e 100644 --- a/src/graphics/render/Skybox.hpp +++ b/src/graphics/render/Skybox.hpp @@ -59,7 +59,7 @@ class Skybox { void drawStars(float angle, float opacity); void drawBackground(const Camera& camera, int width, int height); void drawSkySprites(float daytime, float angle, float opacity); - void refreshFace(uint face, Cubemap* cubemap); + void refreshFace(uint face, Cubemap& cubemap); public: Skybox(uint size, const Assets& assets); ~Skybox(); @@ -83,8 +83,8 @@ public: const glm::vec3& hightlight, uint quality ); - void bind() const; - void unbind() const; + + const Cubemap* getCubemap() const; const glm::vec3& getLightDir() const { return lightDir; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index e5d838f7d..57cc90f24 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -1,12 +1,5 @@ #include "WorldRenderer.hpp" -#include - -#include -#include -#include -#include - #include "BlockWrapsRenderer.hpp" #include "ChunksRenderer.hpp" #include "CloudsRenderer.hpp" @@ -39,6 +32,7 @@ #include "graphics/core/PostProcessing.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/Shadows.hpp" +#include "graphics/core/Cubemap.hpp" #include "graphics/core/Texture.hpp" #include "items/Inventory.hpp" #include "items/ItemDef.hpp" @@ -59,7 +53,11 @@ #include "world/LevelEvents.hpp" #include "world/World.hpp" -using namespace advanced_pipeline; +#include +#include +#include +#include +#include inline constexpr size_t BATCH3D_CAPACITY = 4096; inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000; @@ -160,7 +158,7 @@ void WorldRenderer::setupWorldShader( shader.uniform1i("u_debugNormals", false); shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime); shader.uniform2f("u_lightDir", skybox->getLightDir()); - shader.uniform1i("u_skybox", TARGET_SKYBOX); + shader.uniform1i("u_skybox", advanced_pipeline::TARGET_SKYBOX); auto indices = level.content.getIndices(); // Light emission when an emissive item is chosen @@ -196,7 +194,6 @@ void WorldRenderer::renderOpaque( auto& entityShader = assets.require("entity"); setupWorldShader(entityShader, camera, settings, fogFactor); - skybox->bind(); bool culling = engine.getSettings().graphics.frustumCulling.get(); if (culling) { @@ -237,7 +234,6 @@ void WorldRenderer::renderOpaque( auto& linesShader = assets.require("lines"); renderLines(camera, linesShader, ctx); } - skybox->unbind(); } void WorldRenderer::renderBlockSelection() { @@ -378,18 +374,18 @@ void WorldRenderer::renderFrame( { DrawContext wctx = pctx.sub(); postProcessing.use(wctx, gbufferPipeline); - display::clearDepth(); /* Main opaque pass (GBuffer pass) */ { DrawContext ctx = wctx.sub(); ctx.setDepthTest(true); ctx.setCullFace(true); + ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); + ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); renderOpaque(ctx, camera, settings, hudVisible); } texts->render(pctx, camera, settings, hudVisible, true); } - skybox->bind(); float fogFactor = calcFogFactor(); if (gbufferPipeline) { deferredShader.use(); @@ -399,6 +395,7 @@ void WorldRenderer::renderFrame( { DrawContext ctx = pctx.sub(); ctx.setDepthTest(true); + ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); if (gbufferPipeline) { postProcessing.bindDepthBuffer(); @@ -421,10 +418,12 @@ void WorldRenderer::renderFrame( linesShader.uniformMatrix("u_projview", projView); lineBatch->flush(); - skybox->bind(); + DrawContext wctx = ctx.sub(); + wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); + wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); // Translucent blocks { - auto sctx = ctx.sub(); + auto sctx = wctx.sub(); sctx.setCullFace(true); translucentShader.use(); setupWorldShader(translucentShader, camera, settings, fogFactor); @@ -450,8 +449,6 @@ void WorldRenderer::renderFrame( precipitation->render(camera, *weather); } } - skybox->unbind(); - glBindFramebuffer(GL_FRAMEBUFFER, 0); } postProcessing.render(pctx, assets, timer, camera); @@ -460,6 +457,9 @@ void WorldRenderer::renderFrame( DrawContext ctx = pctx.sub(); ctx.setDepthTest(true); ctx.setCullFace(true); + ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap()); + ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr); + display::clearDepth(); // prepare modified HUD camera Camera hudcam = camera; @@ -467,15 +467,8 @@ void WorldRenderer::renderFrame( hudcam.setFov(0.9f); hudcam.position = {}; - hands->render(camera); - - display::clearDepth(); setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f); - - skybox->bind(); - modelBatch->render(); - modelBatch->setLightsOffset(glm::vec3()); - skybox->unbind(); + hands->render(camera); } renderBlockOverlay(pctx); From 4aaa5b2d7e4d4896ad740d1c36c732eb24f7a527 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 12 May 2026 20:45:48 +0300 Subject: [PATCH 03/59] move sky sprites to environment --- src/graphics/render/Skybox.cpp | 35 +++++---------------------------- src/graphics/render/Skybox.hpp | 17 ++++++---------- src/world/Environment.cpp | 26 ++++++++++++++++++++++-- src/world/Environment.hpp | 36 +++++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 44 deletions(-) diff --git a/src/graphics/render/Skybox.cpp b/src/graphics/render/Skybox.cpp index ef34384fc..9cdf45785 100644 --- a/src/graphics/render/Skybox.cpp +++ b/src/graphics/render/Skybox.cpp @@ -23,7 +23,6 @@ using namespace advanced_pipeline; const int STARS_COUNT = 3000; - const int STARS_SEED = 632; Skybox::Skybox(uint size, const Assets& assets) @@ -41,31 +40,6 @@ Skybox::Skybox(uint size, const Assets& assets) {{1.0f, -1.0f}}}; mesh = std::make_unique>(vertices, 6); - - sprites.push_back(SkySprite { - "misc/moon", - glm::pi() * 0.5f, - 4.0f, - false, - glm::pi() * 0.25f, - }); - - sprites.push_back(SkySprite { - "misc/moon_flare", - glm::pi() * 0.5f, - 0.5f, - false, - glm::pi() * 0.25f, - }); - - sprites.push_back(SkySprite { - "misc/sun", - glm::pi() * 1.5f, - 4.0f, - true, - glm::pi() * 0.25f, - }); - setMode(mode); } @@ -117,7 +91,8 @@ void Skybox::drawStars(float angle, float opacity) { void Skybox::drawSkySprites( float daytime, float angle, - float opacity + float opacity, + const std::vector& sprites ) { float depthScale = 2e3; for (auto& sprite : sprites) { @@ -160,7 +135,7 @@ void Skybox::draw( DrawContext ctx = pctx.sub(); ctx.setBlendMode(BlendMode::addition); - if (!environment.sky.sprites && !environment.sky.stars) { + if (environment.sky.sprites.empty() && !environment.sky.stars) { return; } @@ -170,8 +145,8 @@ void Skybox::draw( shader->uniformMatrix("u_apply", glm::mat4(1.0f)); batch3d->begin(); - if (environment.sky.sprites) { - drawSkySprites(daytime, angle, opacity); + if (!environment.sky.sprites.empty()) { + drawSkySprites(daytime, angle, opacity, environment.sky.sprites); } if (environment.sky.stars) { drawStars(angle, opacity); diff --git a/src/graphics/render/Skybox.hpp b/src/graphics/render/Skybox.hpp index 6d6074e6e..0420bc55e 100644 --- a/src/graphics/render/Skybox.hpp +++ b/src/graphics/render/Skybox.hpp @@ -10,7 +10,6 @@ #include "typedefs.hpp" #include "world/Environment.hpp" - template class Mesh; class Shader; @@ -28,14 +27,6 @@ struct SkyboxVertex { {VertexAttribute::Type::FLOAT, false, 2}, {{}, 0}}; }; -struct SkySprite { - std::string texture; - float phase; - float distance; - bool emissive; - float altitude; -}; - class Skybox { SkyMode mode = SkyMode::SOLID; uint size; @@ -47,7 +38,6 @@ class Skybox { std::unique_ptr> mesh; std::unique_ptr batch3d; - std::vector sprites; int frameid = 0; float prevMie = -1.0f; @@ -58,7 +48,12 @@ class Skybox { void drawStars(float angle, float opacity); void drawBackground(const Camera& camera, int width, int height); - void drawSkySprites(float daytime, float angle, float opacity); + void drawSkySprites( + float daytime, + float angle, + float opacity, + const std::vector& sprites + ); void refreshFace(uint face, Cubemap& cubemap); public: Skybox(uint size, const Assets& assets); diff --git a/src/world/Environment.cpp b/src/world/Environment.cpp index d2ad414bf..932880200 100644 --- a/src/world/Environment.cpp +++ b/src/world/Environment.cpp @@ -5,12 +5,22 @@ #include "data/dv_util.hpp" dv::value Environment::serialize() const { + auto skySpritesList = dv::list(); + for (const auto& sprite : sky.sprites) { + skySpritesList.add(dv::object({ + {"texture", sprite.texture}, + {"phase", sprite.phase}, + {"distance", sprite.distance}, + {"emissive", sprite.emissive}, + {"altitude", sprite.altitude}, + })); + } return dv::object({ {"sky", dv::object({ {"mode", SkyModeMeta.getNameString(sky.mode)}, {"stars", sky.stars}, {"clouds", sky.clouds}, - {"sprites", sky.sprites}, + {"sprites", std::move(skySpritesList)}, })}, }); } @@ -20,6 +30,18 @@ void Environment::deserialize(const dv::value& src) { skyItem->at("mode").get(sky.mode, SkyModeMeta); skyItem->at("stars").get(sky.stars); skyItem->at("clouds").get(sky.clouds); - skyItem->at("sprites").get(sky.sprites); + + if (auto skySpritesList = skyItem->at("sprites")) { + for (int i = 0; i < skySpritesList->size(); i++) { + SkySprite sprite; + const auto& item = (*skySpritesList.ptr)[i]; + item.at("texture").get(sprite.texture); + item.at("phase").get(sprite.phase); + item.at("distance").get(sprite.distance); + item.at("emissive").get(sprite.emissive); + item.at("altitude").get(sprite.altitude); + sky.sprites.push_back(std::move(sprite)); + } + } } } diff --git a/src/world/Environment.hpp b/src/world/Environment.hpp index 5b5f668eb..feb244178 100644 --- a/src/world/Environment.hpp +++ b/src/world/Environment.hpp @@ -3,6 +3,9 @@ #include "interfaces/Serializable.hpp" #include "util/EnumMetadata.hpp" +#include +#include + enum class SkyMode { NONE, SOLID, BOX }; @@ -13,13 +16,44 @@ VC_ENUM_METADATA(SkyMode) {"box", SkyMode::BOX}, VC_ENUM_END +struct SkySprite { + std::string texture; + float phase; + float distance; + bool emissive; + float altitude; +}; + class Environment : public Serializable { public: struct { SkyMode mode = SkyMode::BOX; bool stars = true; bool clouds = true; - bool sprites = true; + // TODO: move defaults to config file + std::vector sprites { + SkySprite { + "misc/moon", + glm::pi() * 0.5f, + 4.0f, + false, + glm::pi() * 0.25f, + }, + SkySprite { + "misc/moon_flare", + glm::pi() * 0.5f, + 0.5f, + false, + glm::pi() * 0.25f, + }, + SkySprite { + "misc/sun", + glm::pi() * 1.5f, + 4.0f, + true, + glm::pi() * 0.25f, + }, + }; } sky; Environment() = default; From 3476bddc6b2a10b05a6a78f9db88c9e35ae5df5e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 16 May 2026 22:24:35 +0300 Subject: [PATCH 04/59] feat: environment is primary --- src/logic/ChunksController.cpp | 2 +- src/logic/EngineController.cpp | 20 +++++++++-- src/logic/LevelController.cpp | 2 +- src/logic/scripting/lua/libs/libworld.cpp | 2 +- src/objects/Player.cpp | 2 +- src/world/Environment.cpp | 2 ++ src/world/Environment.hpp | 2 ++ src/world/Level.cpp | 1 + src/world/World.cpp | 44 ++++++++++++++--------- src/world/World.hpp | 12 ++++--- 10 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/logic/ChunksController.cpp b/src/logic/ChunksController.cpp index 7a460c066..1dc791b19 100644 --- a/src/logic/ChunksController.cpp +++ b/src/logic/ChunksController.cpp @@ -25,7 +25,7 @@ const uint MIN_SURROUNDING = 9; ChunksController::ChunksController(Level& level) : level(level), generator(std::make_unique( - level.content.generators.require(level.getWorld()->getGenerator()), + level.content.generators.require(level.environment.generator), level.content, level.getWorld()->getSeed() )) {} diff --git a/src/logic/EngineController.cpp b/src/logic/EngineController.cpp index 9231ffe67..7637b250e 100644 --- a/src/logic/EngineController.cpp +++ b/src/logic/EngineController.cpp @@ -179,7 +179,7 @@ void EngineController::onMissingContent( if (engine.isHeadless()) { throw std::runtime_error( "missing content: " + - json::stringify(create_missing_content_report(report), true, " ") + util::quote(json::stringify(create_missing_content_report(report), true, " ")) ); } else { engine.setScreen(std::make_shared(engine)); @@ -268,7 +268,7 @@ inline uint64_t str2seed(const std::string& seedstr) { void EngineController::createWorld( const std::string& name, const std::string& seedstr, - const std::string& generatorID + const std::string& environment ) { uint64_t seed = str2seed(seedstr); @@ -279,17 +279,31 @@ void EngineController::createWorld( paths.setCurrentWorldFolder(folder); engine.getContentControl().loadContent(); }); + const auto& generators = engine.getContentControl().get()->generators; + + // generator-based environments must die in 1.0 + bool genBasedEnv = false; + if (auto genDef = generators.find(environment)) { + genBasedEnv = true; + } auto& contentControl = engine.getContentControl(); auto level = World::create( name, - generatorID, + genBasedEnv ? "" : environment, + genBasedEnv ? environment : "", folder, seed, engine.getSettings(), *contentControl.get(), contentControl.getContentPacks() ); + + // generator-based environment initialization + if (genBasedEnv) { + level->environment.generator = environment; + } + if (!engine.isHeadless()) { level->players->create(localPlayer); } diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index 870fed923..6f992ba4c 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -149,7 +149,7 @@ void LevelController::saveWorld() { world->wfile->createDirectories(); scripting::on_world_save(); level->onSave(); - level->getWorld()->write(level.get()); + level->getWorld()->write(*level); } void LevelController::onWorldQuit() { diff --git a/src/logic/scripting/lua/libs/libworld.cpp b/src/logic/scripting/lua/libs/libworld.cpp index c2d6be43c..9c50b6c0b 100644 --- a/src/logic/scripting/lua/libs/libworld.cpp +++ b/src/logic/scripting/lua/libs/libworld.cpp @@ -125,7 +125,7 @@ static int l_is_night(lua::State* L) { } static int l_get_generator(lua::State* L) { - return lua::pushstring(L, require_world_info().generator); + return lua::pushstring(L, require_level().environment.generator); } static int l_get_chunk_data(lua::State* L) { diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index fec65d7de..7d921a4fa 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -127,7 +127,7 @@ void Player::teleport(glm::vec3 position) { void Player::attemptToChooseSpawnpoint() { // looks bad to be here tbh const auto& generatorDef = - level.content.generators.require(level.getWorld()->getGenerator()); + level.content.generators.require(level.environment.generator); int minHeight = generatorDef.playerMinSpawnHeight; int maxHeight = generatorDef.playerMaxSpawnHeight; diff --git a/src/world/Environment.cpp b/src/world/Environment.cpp index 932880200..29c3ef476 100644 --- a/src/world/Environment.cpp +++ b/src/world/Environment.cpp @@ -22,6 +22,7 @@ dv::value Environment::serialize() const { {"clouds", sky.clouds}, {"sprites", std::move(skySpritesList)}, })}, + {"generator", generator} }); } @@ -44,4 +45,5 @@ void Environment::deserialize(const dv::value& src) { } } } + src.at("generator").get(generator); } diff --git a/src/world/Environment.hpp b/src/world/Environment.hpp index feb244178..c2efa828a 100644 --- a/src/world/Environment.hpp +++ b/src/world/Environment.hpp @@ -56,6 +56,8 @@ public: }; } sky; + std::string generator; + Environment() = default; dv::value serialize() const override; diff --git a/src/world/Level.cpp b/src/world/Level.cpp index e6d9000e1..9e40e90cd 100644 --- a/src/world/Level.cpp +++ b/src/world/Level.cpp @@ -31,6 +31,7 @@ Level::Level( entities(std::make_unique(*this)), players(std::make_unique(*this)), pathfinding(std::make_unique(*this)) { + const auto& worldInfo = world->getInfo(); auto& cameraIndices = content.getIndices(ResourceType::CAMERA); for (size_t i = 0; i < cameraIndices.size(); i++) { diff --git a/src/world/World.cpp b/src/world/World.cpp index 6508a4dab..2acc7edaa 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -65,12 +65,12 @@ void World::writeResources(const Content& content) { io::write_json(wfile->getResourcesFile(), root); } -void World::write(Level* level) { - level->chunks->saveAll(); - info.nextEntityId = level->entities->peekNextID(); +void World::write(Level& level) { + level.chunks->saveAll(); + info.nextEntityId = level.entities->peekNextID(); wfile->write(this, &content); - auto playerFile = level->players->serialize(); + auto playerFile = level.players->serialize(); io::write_json(wfile->getPlayerFile(), playerFile); writeResources(content); @@ -78,7 +78,8 @@ void World::write(Level* level) { std::unique_ptr World::create( const std::string& name, - const std::string& generator, + const std::string& environment, + const std::string& generatorOverride, const io::path& directory, uint64_t seed, EngineSettings& settings, @@ -87,7 +88,8 @@ std::unique_ptr World::create( ) { WorldInfo info {}; info.name = name; - info.generator = generator; + info.environment = environment; + info.explicitGenerator = generatorOverride; info.seed = seed; auto world = std::make_unique( info, @@ -101,7 +103,7 @@ std::unique_ptr World::create( logger.info() << "created world '" << name << "' (" << directory.string() << ")"; } - logger.info() << "world seed: " << seed << " generator: " << generator; + logger.info() << "world seed: " << seed << " environment: " << environment; return std::make_unique(std::move(world), content, settings); } @@ -122,16 +124,19 @@ std::unique_ptr World::load( << worldFilesPtr->getFolder().string() << ")"; logger.info() << "world version: " << info->major << "." << info->minor << " seed: " << info->seed - << " generator: " << info->generator; + << " environment: " << info->environment; - auto world = std::make_unique( + auto worldPtr = std::make_unique( info.value(), std::move(worldFilesPtr), content, packs ); + auto world = worldPtr.get(); auto& wfile = world->wfile; wfile->readResourcesData(content); - auto level = std::make_unique(std::move(world), content, settings); - + auto level = std::make_unique(std::move(worldPtr), content, settings); + if (world->getEnvironment().empty()) { + level->environment.generator = world->getInfo().explicitGenerator; + } io::path file = wfile->getPlayerFile(); if (!io::is_regular_file(file)) { logger.warning() << "player.json does not exists"; @@ -163,8 +168,8 @@ void World::setName(const std::string& name) { this->info.name = name; } -void World::setGenerator(const std::string& generator) { - this->info.generator = generator; +void World::setEnvironment(const std::string& environment) { + this->info.environment = environment; } bool World::hasPack(const std::string& id) const { @@ -186,8 +191,8 @@ uint64_t World::getSeed() const { return info.seed; } -std::string World::getGenerator() const { - return info.generator; +std::string World::getEnvironment() const { + return info.environment; } const std::vector& World::getPacks() const { @@ -196,7 +201,8 @@ const std::vector& World::getPacks() const { void WorldInfo::deserialize(const dv::value& root) { name = root["name"].asString(); - generator = root["generator"].asString(generator); + root.at("environment").get(environment); + root.at("generator").get(explicitGenerator); seed = root["seed"].asInteger(seed); if (root.has("version")) { @@ -226,7 +232,11 @@ dv::value WorldInfo::serialize() const { versionobj["minor"] = ENGINE_VERSION_MINOR; root["name"] = name; - root["generator"] = generator; + if (environment.empty()) { + root["generator"] = explicitGenerator; + } else { + root["environment"] = environment; + } root["seed"] = seed; auto& timeobj = root.object("time"); diff --git a/src/world/World.hpp b/src/world/World.hpp index 725b91823..14649fb1b 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -23,7 +23,8 @@ public: struct WorldInfo : public Serializable { std::string name; - std::string generator; + std::string environment; + std::string explicitGenerator; uint64_t seed; int64_t nextInventoryId = 1; int64_t nextPlayerId = 0; @@ -76,7 +77,7 @@ public: void updateTimers(float delta); /// @brief Write all unsaved level data to the world directory - void write(Level* level); + void write(Level& level); /// @brief Check world indices and generate ContentReport if convert required /// @param directory world directory @@ -98,7 +99,8 @@ public: /// @return Level instance containing World instance static std::unique_ptr create( const std::string& name, - const std::string& generator, + const std::string& environment, + const std::string& generatorOverride, const io::path& directory, uint64_t seed, EngineSettings& settings, @@ -123,7 +125,7 @@ public: void setName(const std::string& name); void setSeed(uint64_t seed); - void setGenerator(const std::string& generator); + void setEnvironment(const std::string& environment); /// @brief Check if world has content-pack installed /// @param id content-pack id @@ -137,7 +139,7 @@ public: uint64_t getSeed() const; /// @brief Get world generator id - std::string getGenerator() const; + std::string getEnvironment() const; bool isNameless() const { return info.name.empty(); From 29f5373d81853a1ac1397d0e07484978fa117100 Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Wed, 22 Jul 2026 20:36:01 +1000 Subject: [PATCH 05/59] added bytearray view wrapper for float (float32) --- res/modules/internal/bytearray.lua | 2 ++ res/scripts/stdmin.lua | 1 + 2 files changed, 3 insertions(+) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 58b33a520..a90595979 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -305,6 +305,7 @@ local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2) local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2) local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4) local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4) +local FFIF32view = create_FFIview_class("FFIF32view", "float", 4) return { FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), @@ -314,4 +315,5 @@ return { FFII16view = FFII16view, FFIU32view = FFIU32view, FFII32view = FFII32view, + FFIF32view = FFIF32view, } diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 2868dd634..01b61efc6 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -314,6 +314,7 @@ U16view = bytearray.FFIU16view I16view = bytearray.FFII16view U32view = bytearray.FFIU32view I32view = bytearray.FFII32view +F32view = bytearray.FFIF32view Bytearray_construct = function(...) return Bytearray(...) end bit.compile = require "core:bitwise/compiler" From 27bb3015e4793e21f27a40476b832ab0548a2eb7 Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Thu, 23 Jul 2026 17:08:32 +1000 Subject: [PATCH 06/59] improved bytearray --- res/modules/internal/bytearray.lua | 102 ++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 32 deletions(-) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index a90595979..f7cab06d9 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -4,6 +4,7 @@ local FFI = ffi FFI.cdef[[ void* malloc(size_t); + void *realloc(void *, size_t); void free(void*); typedef struct { unsigned char* bytes; @@ -22,12 +23,33 @@ local GC_THRESHOLD = 50 * 1024 * 1024 local function malloc(size) local raw = FFI.C.malloc(size) - if raw == nil then - return nil + if not raw then + error("failed memory allocation of " .. size .. " bytes") end allocated_bytes = allocated_bytes + size + if allocated_bytes >= GC_THRESHOLD then + collectgarbage("step", 200) + allocated_bytes = 0 + end + return raw +end + +local function realloc(buff, size, throwerror) + if throwerror == nil then throwerror = true end + + if size <= 0 then + free(buff) + return + end + + local raw = FFI.C.realloc(buff, size) + if not raw and throwerror then + error("failed memory reallocation of block " .. buff .. " to new size " .. size) + end + + allocated_bytes = allocated_bytes + size if allocated_bytes >= GC_THRESHOLD then collectgarbage("step", 200) allocated_bytes = 0 @@ -38,23 +60,17 @@ end local function grow_buffer(self, elems) local new_capacity = math.ceil(self.capacity / 0.75 + elems) - local prev = self.bytes - self.bytes = malloc(new_capacity) - FFI.copy(self.bytes, prev, self.size) + self.bytes = realloc(self.bytes, new_capacity) self.capacity = new_capacity - free(prev) end local function trim_buffer(self) - if self.size == self.capacity then - return + if self.size == self.capacity then return end + local new_bytes = realloc(self.bytes, self.size, false) + if new_bytes then + self.bytes = new_bytes + self.capacity = self.size end - local size = self.size - local prev = self.bytes - self.bytes = malloc(size) - FFI.copy(self.bytes, prev, self.size) - self.capacity = size - free(prev) end local function count_elements(b) @@ -125,15 +141,16 @@ local function clear(self) self.size = 0 end -local function reserve(self, new_capacity) - if new_capacity <= self.capacity then - return +local function fill(self, byte) + if self.size > 0 then + FFI.fill(self.bytes, self.size, byte) end - local prev = self.bytes - self.bytes = malloc(new_capacity) - FFI.copy(self.bytes, prev, self.size) +end + +local function reserve(self, new_capacity) + if new_capacity <= self.capacity then return end + self.bytes = realloc(self.bytes, new_capacity) self.capacity = new_capacity - free(prev) end local function get_capacity(self) @@ -150,22 +167,43 @@ local function slice(self, offset, length) length = self.size - offset + 1 end local buffer = malloc(length) - if not buffer then - error("malloc(" .. length .. ") returned NULL") - end FFI.copy(buffer, self.bytes + (offset - 1), length) return bytearray_type(buffer, length, length) end +local function copy(self, srcindex, dst, dstindex, size) + if srcindex > 0 and dstindex > 0 and size > 0 and + srcindex + size - 1 <= self.size and + dstindex + size - 1 <= dst.size + then + FFI.copy(dst.bytes + dstindex - 1, self.bytes + srcindex - 1, size) + end +end + +local function move(self, fromindex, toindex, size) + if fromindex > 0 and toindex > 0 and size > 0 and + fromindex + size - 1 <= self.size and + toindex + size - 1 <= self.size + then + local buff = malloc(size) + FFI.copy(buff, self.bytes + fromindex - 1, size) + FFI.copy(self.bytes + toindex - 1, buff, size) + free(buff) + end +end + local bytearray_methods = { - append=append, - insert=insert, - remove=remove, - trim=trim_buffer, - clear=clear, - reserve=reserve, - get_capacity=get_capacity, - slice=slice, + append = append, + insert = insert, + remove = remove, + trim = trim_buffer, + clear = clear, + reserve = reserve, + get_capacity = get_capacity, + slice = slice, + fill = fill, + copy = copy, + move = move } local bytearray_mt = { From 0d641fa82fe3f16773a9cf643f38b12b32ee3caa Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Thu, 23 Jul 2026 17:47:32 +1000 Subject: [PATCH 07/59] (i hope that correctry) optimized bytearray:move method --- res/modules/internal/bytearray.lua | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index f7cab06d9..04212d3e1 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -185,10 +185,15 @@ local function move(self, fromindex, toindex, size) fromindex + size - 1 <= self.size and toindex + size - 1 <= self.size then - local buff = malloc(size) - FFI.copy(buff, self.bytes + fromindex - 1, size) - FFI.copy(self.bytes + toindex - 1, buff, size) - free(buff) + if toindex >= fromindex and toindex < fromindex + size then + local buff = malloc(size) + FFI.copy(buff, self.bytes + fromindex - 1, size) + FFI.copy(self.bytes + toindex - 1, buff, size) + free(buff) + else + -- убери(-те) это ветвление, если FFI будет производить копирование не побайтово, и оставь(-те) только верхний блок без условия вообще. + FFI.copy(self.bytes + toindex - 1, self.bytes + fromindex - 1, size) + end end end From 67da57d8708375b05865b714db8e1e560b6546c3 Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Thu, 23 Jul 2026 19:29:19 +1000 Subject: [PATCH 08/59] upgraded/improved LuaCanvas::clear method --- .../scripting/lua/usertypes/lua_type_canvas.cpp | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index c670e23df..d03b4e855 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -161,18 +161,11 @@ static LuaCanvas& require_canvas(State* L, int idx) { static int l_clear(State* L) { auto& canvas = require_canvas(L, 1); auto& image = canvas.getData(); - ubyte* data = image.getData(); - RGBA rgba {}; - if (gettop(L) == 1) { - std::fill(data, data + image.getDataSize(), 0); - return 0; - } - rgba = get_rgba(L, 2); - size_t pixels = image.getWidth() * image.getHeight(); - const size_t channels = 4; - for (size_t i = 0; i < pixels * channels; i++) { - data[i] = rgba.arr[i % channels]; - } + size_t pixelscount = image.getWidth() * image.getHeight(); + uint32_t* data = reinterpret_cast(image.getData()); + + if (gettop(L) == 1) std::fill(data, data + pixelscount, 0); + else std::fill(data, data + pixelscount, get_rgba(L, 2).rgba); return 0; } From 8a40bf4fcd1c9696f91b65cea25216db1e942257 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 28 Jul 2026 23:10:47 +0300 Subject: [PATCH 09/59] feat: auto-focus search textbox in content menu --- res/layouts/pages/content.xml.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 2937542f4..cb63a96c7 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -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) From 383dcde2e77cdb7ca672203c8bb21b5806a6d155 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 28 Jul 2026 23:18:41 +0300 Subject: [PATCH 10/59] move default block material name to defaults.toml --- res/content/base/config/defaults.toml | 1 + src/content/ContentBuilder.cpp | 3 +++ src/voxels/Block.hpp | 4 +--- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/res/content/base/config/defaults.toml b/res/content/base/config/defaults.toml index 650c24d74..d2c5142cc 100644 --- a/res/content/base/config/defaults.toml +++ b/res/content/base/config/defaults.toml @@ -1,3 +1,4 @@ generator = "base:demo" player-entity = "base:player" hand-skeleton = "base:hand" +block-material = "base:stone" diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index 25029963b..97498b497 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -39,6 +39,9 @@ std::unique_ptr ContentBuilder::build() { } else { 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; def.rt.solid = diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 5ba3bb267..ded51244e 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -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) From d9631467fc3c8006ebdadedf6f2bd724287591d6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 28 Jul 2026 23:28:01 +0300 Subject: [PATCH 11/59] update doc/*/block-properties.md --- doc/en/block-properties.md | 14 +++++++++++++- doc/ru/block-properties.md | 7 ++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index f3137dd60..d98a8cac5 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -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. diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 50bd7f023..e139f635d 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -1,6 +1,6 @@ # Свойства блоков -## Вид +## Вид & Звук ### Текстура - *texture* @@ -78,6 +78,11 @@ При приближении к блоку движок создаст эмиттер, который будет работать до разрушения блока или отдаления камеры на некоторое расстояние. +### Материал - *material* + +Определяет имя материала блока в формате `пак:имя_материала`, что влияет на выбор звуков взаимодействия с блоком. +Определения материалов расположены в /block_materials. + ## Варианты Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока. From 81af4b9e2acba5d67b5bacdb343618c686dd9d5f Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Wed, 29 Jul 2026 18:47:07 +0300 Subject: [PATCH 12/59] fixing errors in rules.lua --- res/modules/internal/rules.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/res/modules/internal/rules.lua b/res/modules/internal/rules.lua index a1a5a8c32..4d739cf35 100644 --- a/res/modules/internal/rules.lua +++ b/res/modules/internal/rules.lua @@ -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 From 513abd332c3256774314bba239b956ef03b3f16e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 29 Jul 2026 20:02:30 +0300 Subject: [PATCH 13/59] add platform::new_engine_instance subProcess parameter --- src/logic/scripting/lua/libs/libapp.cpp | 3 +- src/util/platform.cpp | 73 ++++++++++++++++++++++++- src/util/platform.hpp | 4 +- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 6442fcbad..12758c724 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -365,7 +365,8 @@ 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)), + true // debug instance may be detached ); return lua::pushinteger(L, port); } diff --git a/src/util/platform.cpp b/src/util/platform.cpp index f113641e5..76ed8a973 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -18,7 +18,11 @@ #pragma comment(lib, "winmm.lib") #else #include +#include #include +#include +#include +#include #endif namespace platform::internal { @@ -216,7 +220,9 @@ std::filesystem::path platform::get_executable_path() { } void platform::new_engine_instance( - const std::vector& args, std::filesystem::path outputFile + const std::vector& args, + std::filesystem::path outputFile, + bool subProcess ) { auto executable = get_executable_path(); @@ -285,6 +291,37 @@ 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()) + ); + } + } catch (const std::exception& err) { + CloseHandle(job); + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return; + } + } if (success) { CloseHandle(pi.hProcess); CloseHandle(pi.hThread); @@ -295,6 +332,40 @@ void platform::new_engine_instance( ); } #else + + 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 argv(args.size() + 2); + argv[0] = const_cast(executable.c_str()); + for (int i = 0; i < args.size(); i++) { + argv[i + 1] = args[i].c_str(); + } + argv[args.size() + 1] = nullptr; + + execvp(executable.c_str(), const_cast(argv.data())); + + _exit(127); + } else { + close(fd); + } + } + std::stringstream ss; ss << executable; for (int i = 0; i < args.size(); i++) { diff --git a/src/util/platform.hpp b/src/util/platform.hpp index 54c56a0c5..2ff535248 100644 --- a/src/util/platform.hpp +++ b/src/util/platform.hpp @@ -19,7 +19,9 @@ namespace platform { std::filesystem::path get_executable_path(); /// @brief Run a separate engine instance with specified arguments void new_engine_instance( - const std::vector& args, std::filesystem::path outputFile + const std::vector& args, + std::filesystem::path outputFile, + bool subProcess ); /// @brief Open URL in web browser bool open_url(const std::string& url); From 340aac82c341cbb9cd70ea5c1c1536035344a58e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 29 Jul 2026 20:24:27 +0300 Subject: [PATCH 14/59] fix macos --- src/util/platform.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/util/platform.cpp b/src/util/platform.cpp index 76ed8a973..bc53d288f 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -18,12 +18,14 @@ #pragma comment(lib, "winmm.lib") #else #include -#include #include #include #include #include #endif +#ifdef __linux__ + #include +#endif namespace platform::internal { std::filesystem::path get_executable_path(); @@ -333,6 +335,8 @@ void platform::new_engine_instance( } #else +// TODO: implement +#ifndef __APPLE__ if (subProcess) { pid_t pid = fork(); @@ -365,6 +369,7 @@ void platform::new_engine_instance( close(fd); } } +#endif // __APPLE_ std::stringstream ss; ss << executable; From 4c18163f8aea02c8b555c47269c8b609df1ac594 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 29 Jul 2026 22:22:54 +0300 Subject: [PATCH 15/59] add implicit io::path constructor from std::string_view --- src/io/path.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/io/path.hpp b/src/io/path.hpp index 538431b7c..3ab95300e 100644 --- a/src/io/path.hpp +++ b/src/io/path.hpp @@ -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 { From 052ae41271124ebca8d3662db050ac78e04cc406 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 29 Jul 2026 23:04:54 +0300 Subject: [PATCH 16/59] fix platform::new_engine_instance --- src/util/platform.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/util/platform.cpp b/src/util/platform.cpp index bc53d288f..88d42d34a 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -362,11 +362,13 @@ void platform::new_engine_instance( } argv[args.size() + 1] = nullptr; + prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); execvp(executable.c_str(), const_cast(argv.data())); _exit(127); } else { close(fd); + return; } } #endif // __APPLE_ From 7f813d2eb140d61baee1a345cd113630bb631f0a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 29 Jul 2026 23:06:02 +0300 Subject: [PATCH 17/59] add app.start_background_instance (test) --- src/devtools/Project.hpp | 2 ++ src/engine/Engine.cpp | 1 + src/logic/scripting/lua/libs/libapp.cpp | 25 ++++++++++++++++++++++++- 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/devtools/Project.hpp b/src/devtools/Project.hpp index daeac7ca0..4906b9373 100644 --- a/src/devtools/Project.hpp +++ b/src/devtools/Project.hpp @@ -1,6 +1,7 @@ #pragma once #include "interfaces/Serializable.hpp" +#include "io/path.hpp" #include #include @@ -21,6 +22,7 @@ struct Permissions { struct Project : Serializable { std::string name; std::string title; + io::path path; std::vector basePacks; Permissions permissions; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 10f8cee6d..55ed9fec0 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -380,6 +380,7 @@ void Engine::loadAssets() { void Engine::loadProject() { io::path projectFile = "project:project.toml"; project = std::make_unique(); + project->path = "project:"; project->deserialize(io::read_object(projectFile)); logger.info() << "loaded project " << util::quote(project->name); } diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 12758c724..771567fd4 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -366,11 +366,33 @@ static int l_start_debug_instance(lua::State* L) { platform::new_engine_instance( std::move(args), outputPath.empty() ? "" : io::resolve(std::string(outputPath)), - true // debug instance may be detached + false ); return lua::pushinteger(L, port); } +static int l_start_background_instance(lua::State* L) { + auto scriptPath = lua::require_lstring(L, 1); + io::path outputPath = "user:background.log"; // TODO: + const auto& paths = engine->getPaths(); + + std::vector args { + "--headless", + "--res", paths.getResourcesFolder().u8string(), + "--dir", paths.getUserFilesFolder().u8string(), + "--script", io::resolve(scriptPath).u8string(), + }; + args.emplace_back("--project"); + args.emplace_back(io::resolve(engine->getProject().path).u8string()); + + platform::new_engine_instance( + std::move(args), + outputPath.empty() ? "" : io::resolve(outputPath), + true + ); + return 0; +} + const luaL_Reg applib[] = { /// content {"is_content_loaded", lua::wrap}, @@ -403,5 +425,6 @@ const luaL_Reg applib[] = { {"get_version", lua::wrap}, {"create_memory_device", lua::wrap}, {"start_debug_instance", lua::wrap}, + {"start_background_instance", lua::wrap}, {nullptr, nullptr} }; From 2088b5dec1fd82d6d41619107fafd6151e2e1442 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 30 Jul 2026 21:03:58 +0300 Subject: [PATCH 18/59] disable blocks updating caused by blocks.fill command --- res/scripts/stdcmd.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua index c2b102675..03df9235f 100644 --- a/res/scripts/stdcmd.lua +++ b/res/scripts/stdcmd.lua @@ -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 From a5a2bd0c8ac7a1891d45851e5ed7823078c6cc48 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 30 Jul 2026 21:31:33 +0300 Subject: [PATCH 19/59] cleanup a bit --- src/devtools/AppScriptsControl.cpp | 6 +----- src/devtools/AppScriptsControl.hpp | 3 +-- src/engine/Engine.cpp | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/devtools/AppScriptsControl.cpp b/src/devtools/AppScriptsControl.cpp index bb1a386a2..f565949aa 100644 --- a/src/devtools/AppScriptsControl.cpp +++ b/src/devtools/AppScriptsControl.cpp @@ -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" diff --git a/src/devtools/AppScriptsControl.hpp b/src/devtools/AppScriptsControl.hpp index 4e44eef60..3531dc2f9 100644 --- a/src/devtools/AppScriptsControl.hpp +++ b/src/devtools/AppScriptsControl.hpp @@ -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 clientScript; std::unique_ptr scriptCoroutine; }; diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 55ed9fec0..0baa102fc 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -192,7 +192,7 @@ void Engine::initialize(CoreParameters coreParameters) { audio::set_input_device(name == "auto" ? "" : name); })); - appScripts = std::make_unique(params, *project); + appScripts = std::make_unique(params); if (params.stdinCommands) { cmd::start_stdin_cmd_reader(*this); From 466095efcb9398f400f27baba045643aede6814c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 30 Jul 2026 23:47:06 +0300 Subject: [PATCH 20/59] add SystemProcess --- src/main.cpp | 1 + src/util/platform.cpp | 100 ++++++++++++++++++++++++++++++++++++++++-- src/util/platform.hpp | 5 ++- 3 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 77175bd8a..098d72a70 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ static debug::Logger logger("main"); static void sigterm_handler(int signum) { + logger.info() << "SIGTERM received"; Engine::getInstance().quit(); } diff --git a/src/util/platform.cpp b/src/util/platform.cpp index 88d42d34a..e0b2587b7 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -25,6 +25,7 @@ #endif #ifdef __linux__ #include + #include #endif namespace platform::internal { @@ -221,7 +222,98 @@ std::filesystem::path platform::get_executable_path() { #endif } -void platform::new_engine_instance( +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__ + kill(pid, SIGKILL); +#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 platform::new_engine_instance( const std::vector& args, std::filesystem::path outputFile, bool subProcess @@ -317,11 +409,12 @@ void platform::new_engine_instance( std::to_string(GetLastError()) ); } + return std::make_unique(pi); } catch (const std::exception& err) { CloseHandle(job); CloseHandle(pi.hProcess); CloseHandle(pi.hThread); - return; + return nullptr; } } if (success) { @@ -368,7 +461,7 @@ void platform::new_engine_instance( _exit(127); } else { close(fd); - return; + return std::make_unique(pid); } } #endif // __APPLE_ @@ -393,6 +486,7 @@ void platform::new_engine_instance( ); } #endif + return nullptr; } bool platform::stdin_has_data() { diff --git a/src/util/platform.hpp b/src/util/platform.hpp index 2ff535248..18dc052b6 100644 --- a/src/util/platform.hpp +++ b/src/util/platform.hpp @@ -2,8 +2,11 @@ #include #include +#include #include +#include "interfaces/Process.hpp" + namespace platform { void configure_encoding(); /// @brief Get Environment locale in ISO format ll_CC @@ -18,7 +21,7 @@ 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( + std::unique_ptr new_engine_instance( const std::vector& args, std::filesystem::path outputFile, bool subProcess From 4a53090610dce86b8f54d59b438a19395e6039be Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 31 Jul 2026 00:06:25 +0300 Subject: [PATCH 21/59] feat: app coroutine error handling --- res/scripts/stdlib.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 9793216a2..2869bba1c 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -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 From 2d4b4b6081d97f5c0591a4184aa221b5a203e65a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 31 Jul 2026 18:06:22 +0300 Subject: [PATCH 22/59] update doc/*/rigging.md --- doc/en/rigging.md | 2 +- doc/ru/rigging.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/rigging.md b/doc/en/rigging.md index 4b69b5769..abb5ad81a 100644 --- a/doc/en/rigging.md +++ b/doc/en/rigging.md @@ -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. diff --git a/doc/ru/rigging.md b/doc/ru/rigging.md index 86ec0a09b..388b19ea2 100644 --- a/doc/ru/rigging.md +++ b/doc/ru/rigging.md @@ -38,7 +38,7 @@ ## Модели -Модели должны располагаться в папке models. На данный момент поддерживается только OBJ формат. +Модели должны располагаться в папке models. На данный момент поддерживаются форматы: obj, vec3 и [vcm](vcm.md). >[!IMPORTANT] > При загрузке obj модели игнорируется файл \*.mtl. From 0701f160e61d5f1eeb9984ea6eebc53ddc09ba27 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 31 Jul 2026 18:07:43 +0300 Subject: [PATCH 23/59] update doc/en/scripting/builtins/libblock.md --- doc/en/scripting/builtins/libblock.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/en/scripting/builtins/libblock.md b/doc/en/scripting/builtins/libblock.md index 4513d57cf..cc69e47d8 100644 --- a/doc/en/scripting/builtins/libblock.md +++ b/doc/en/scripting/builtins/libblock.md @@ -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. From ee9b2ab2660390056a66d11946abd6ecf70528f2 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 31 Jul 2026 18:57:43 +0300 Subject: [PATCH 24/59] add quit signal check to the client mainloop --- src/engine/Mainloop.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/Mainloop.cpp b/src/engine/Mainloop.cpp index 3a196f522..aa505a9ba 100644 --- a/src/engine/Mainloop.cpp +++ b/src/engine/Mainloop.cpp @@ -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(); From 5a8fd3ceab614eab3922d2fd133dbc20248f17bf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 1 Aug 2026 22:32:49 +0300 Subject: [PATCH 25/59] update sub-instance lifetime --- src/engine/Engine.cpp | 1 + src/logic/scripting/lua/libs/libapp.cpp | 8 ++++++-- src/main.cpp | 2 +- src/util/platform.cpp | 6 ++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 0baa102fc..f0e20f922 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -413,6 +413,7 @@ void Engine::onWorldClosed() { } void Engine::quit() { + logger.info() << "quitSignal set to true"; quitSignal = true; if (!isHeadless()) { window->setShouldClose(true); diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 771567fd4..4d932c272 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -22,6 +22,10 @@ using namespace scripting; +namespace { + static std::unique_ptr sub_instance = nullptr; +} + /// @brief Check if content is loaded static int l_is_content_loaded(lua::State* L) { return lua::pushboolean(L, content != nullptr); @@ -377,7 +381,7 @@ static int l_start_background_instance(lua::State* L) { const auto& paths = engine->getPaths(); std::vector args { - "--headless", + // "--headless", "--res", paths.getResourcesFolder().u8string(), "--dir", paths.getUserFilesFolder().u8string(), "--script", io::resolve(scriptPath).u8string(), @@ -385,7 +389,7 @@ static int l_start_background_instance(lua::State* L) { args.emplace_back("--project"); args.emplace_back(io::resolve(engine->getProject().path).u8string()); - platform::new_engine_instance( + ::sub_instance = platform::new_engine_instance( std::move(args), outputPath.empty() ? "" : io::resolve(outputPath), true diff --git a/src/main.cpp b/src/main.cpp index 098d72a70..2438d5f50 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,7 +10,7 @@ static debug::Logger logger("main"); static void sigterm_handler(int signum) { - logger.info() << "SIGTERM received"; + logger.info() << "SIGTERM received " << signum; Engine::getInstance().quit(); } diff --git a/src/util/platform.cpp b/src/util/platform.cpp index e0b2587b7..7b08639e9 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -296,7 +296,8 @@ public: void terminate() final override { #ifdef __linux__ - kill(pid, SIGKILL); + logger.info() << "terminating " << pid; + kill(pid, SIGTERM); #elif defined(_WIN32) if (processHandle != nullptr) { TerminateProcess(processHandle, 1); @@ -461,10 +462,11 @@ std::unique_ptr platform::new_engine_instance( _exit(127); } else { close(fd); + usleep(1000); // waiting for execvp call return std::make_unique(pid); } } -#endif // __APPLE_ +#endif std::stringstream ss; ss << executable; From 4503455ae94082506671355b566e006fd47e0acf Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 2 Aug 2026 01:19:01 +0300 Subject: [PATCH 26/59] add sub-instance project permission & feat: max sub-process depth check --- res/project.toml | 3 ++- src/constants.hpp | 2 ++ src/devtools/Project.hpp | 1 + src/engine/CoreParameters.hpp | 1 + src/logic/scripting/lua/libs/libapp.cpp | 20 ++++++++++++++++++-- src/main.cpp | 2 ++ src/util/command_line.cpp | 13 ++++++++++--- 7 files changed, 36 insertions(+), 6 deletions(-) diff --git a/res/project.toml b/res/project.toml index b641ad00f..281964857 100644 --- a/res/project.toml +++ b/res/project.toml @@ -3,5 +3,6 @@ name = "default" base_packs = ["base"] permissions = [ "network", - "record-audio" + "record-audio", + "sub-instances", ] diff --git a/src/constants.hpp b/src/constants.hpp index f43314f4a..0396f59a6 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -16,6 +16,8 @@ inline constexpr bool ENGINE_DEBUG_BUILD = true; inline const std::string ENGINE_VERSION_STRING = "0.32"; +inline constexpr uint MAX_SUBPROCESS_DEPTH = 2; + /// @brief world regions format version inline constexpr uint REGION_FORMAT_VERSION = 3; diff --git a/src/devtools/Project.hpp b/src/devtools/Project.hpp index 4906b9373..571a8b2c5 100644 --- a/src/devtools/Project.hpp +++ b/src/devtools/Project.hpp @@ -13,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 permissions; diff --git a/src/engine/CoreParameters.hpp b/src/engine/CoreParameters.hpp index b9f585f47..b5d8074ce 100644 --- a/src/engine/CoreParameters.hpp +++ b/src/engine/CoreParameters.hpp @@ -14,5 +14,6 @@ struct CoreParameters { std::filesystem::path projectFolder; std::string debugServerString; int tps = 20; + int subProcessDepth = 0; std::unordered_map projectArgs; }; diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 4d932c272..e3d41d8c7 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -341,6 +341,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) { @@ -361,6 +365,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"); @@ -376,15 +382,25 @@ static int l_start_debug_instance(lua::State* L) { } 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 = "user:background.log"; // TODO: + io::path outputPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : ""; const auto& paths = engine->getPaths(); std::vector args { - // "--headless", + "--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()); diff --git a/src/main.cpp b/src/main.cpp index 2438d5f50..83887422a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -26,6 +26,8 @@ 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; diff --git a/src/util/command_line.cpp b/src/util/command_line.cpp index 7f34bcea4..14f49caf4 100644 --- a/src/util/command_line.cpp +++ b/src/util/command_line.cpp @@ -78,12 +78,19 @@ static bool perform_keyword( params.debugServerString = reader.next(); return true; }, "", "open debugging server where is {transport}:{port}"), + ArgC("--sub-depth", [¶ms, &reader]() -> bool { + params.subProcessDepth = reader.nextInt(); + return true; + }, "", "sub-process depth"), ArgC("--help", []() -> 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; From 46d3e5c7e1e9fcd511bb6fc4d9077b298763b587 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 2 Aug 2026 01:28:02 +0300 Subject: [PATCH 27/59] cleanup command_line.cpp --- src/util/command_line.cpp | 61 ++++++++++++++++++++------------------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/util/command_line.cpp b/src/util/command_line.cpp index 14f49caf4..f05231821 100644 --- a/src/util/command_line.cpp +++ b/src/util/command_line.cpp @@ -13,22 +13,22 @@ namespace fs = std::filesystem; class ArgC { - public: - std::string keyword; - std::function execute; - std::string args; - std::string help; - ArgC( - const std::string& keyword, - std::function 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 execute; + std::string args; + std::string help; + ArgC( + const std::string& keyword, + std::function execute, + const std::string& args, + const std::string& help + ) { + this->keyword = keyword; + this->execute = execute; + this->args = args; + this->help = help; + } }; @@ -36,60 +36,61 @@ static bool perform_keyword( util::ArgsReader& reader, const std::string& keyword, CoreParameters& params ) { static const std::vector argumentsCommandline = { - ArgC("--res", [¶ms, &reader]() -> bool { + ArgC("--res", [](auto& params, auto& reader) -> bool { params.resFolder = reader.next(); return true; }, "", "set resources directory."), - ArgC("--dir", [¶ms, &reader]() -> bool { + ArgC("--dir", [](auto& params, auto& reader) -> bool { params.userFolder = reader.next(); return true; }, "", "set userfiles directory."), - ArgC("--project", [¶ms, &reader]() -> bool { + ArgC("--project", [](auto& params, auto& reader) -> bool { params.projectFolder = reader.next(); return true; }, "", "set project directory."), - ArgC("--test", [¶ms, &reader]() -> bool { + ArgC("--test", [](auto& params, auto& reader) -> bool { params.testMode = true; params.scriptFile = reader.next(); return true; }, "", "test script file."), - ArgC("--script", [¶ms, &reader]() -> bool { + ArgC("--script", [](auto& params, auto& reader) -> bool { params.testMode = false; params.scriptFile = reader.next(); return true; }, "", "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; }, "", "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; }, "", "open debugging server where is {transport}:{port}"), - ArgC("--sub-depth", [¶ms, &reader]() -> bool { + ArgC("--sub-depth", [](auto& params, auto& reader) -> bool { params.subProcessDepth = reader.nextInt(); return true; }, "", "sub-process depth"), - ArgC("--help", []() -> bool { + ArgC("--help", [](auto&, auto&) -> bool { std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n"; std::cout << "Command-line arguments:\n"; for (auto& arg : argumentsCommandline) { if (arg.help.empty()) { continue; } - std::cout << std::setw(24) << std::left << (arg.keyword + " " + arg.args); + std::cout << std::setw(24) << std::left + << (arg.keyword + " " + arg.args); std::cout << "- " << arg.help << std::endl; } std::cout << std::endl; @@ -98,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); From 8b2094c6283314cce1f4dcf28b543dff6731efc1 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 2 Aug 2026 15:02:44 +0300 Subject: [PATCH 28/59] add SIGTERM handler --- src/main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 83887422a..402b829b3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,7 +10,7 @@ static debug::Logger logger("main"); static void sigterm_handler(int signum) { - logger.info() << "SIGTERM received " << signum; + logger.info() << (signum == SIGTERM ? "SIGTERM" : "SIGINT") << " received"; Engine::getInstance().quit(); } @@ -33,7 +33,9 @@ int main(int argc, char** argv) { 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(); From b1e14a294444e4fde541b6d56291635d7f381206 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 2 Aug 2026 16:20:33 +0300 Subject: [PATCH 29/59] add app.terminate(handle) --- src/constants.hpp | 1 + src/logic/scripting/lua/libs/libapp.cpp | 33 ++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/constants.hpp b/src/constants.hpp index 0396f59a6..5bacfe078 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -16,6 +16,7 @@ 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 diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index e3d41d8c7..e86b6d88e 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -20,10 +20,12 @@ #include "window/Window.hpp" #include "world/Level.hpp" +#include + using namespace scripting; namespace { - static std::unique_ptr sub_instance = nullptr; + static std::array, MAX_SUBPROCESSES> processes; } /// @brief Check if content is loaded @@ -405,12 +407,36 @@ static int l_start_background_instance(lua::State* L) { args.emplace_back("--project"); args.emplace_back(io::resolve(engine->getProject().path).u8string()); - ::sub_instance = platform::new_engine_instance( + 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 0; + return lua::pushinteger(L, handle); +} + +static int l_terminate(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[] = { @@ -446,5 +472,6 @@ const luaL_Reg applib[] = { {"create_memory_device", lua::wrap}, {"start_debug_instance", lua::wrap}, {"start_background_instance", lua::wrap}, + {"terminate", lua::wrap}, {nullptr, nullptr} }; From 8de3c0de57018c12473f2fc9c8a0c67dd0aa9f6e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 2 Aug 2026 17:54:08 +0300 Subject: [PATCH 30/59] update vc library --- res/scripts/stdmin.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 2868dd634..e341f68a1 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -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, } From 2eae8f6f275e7a41ac30309e61f8d22d8a644ac7 Mon Sep 17 00:00:00 2001 From: EsPaKira Date: Sun, 2 Aug 2026 19:18:37 +0400 Subject: [PATCH 31/59] fix block.index() calls in grass_block.lua and export base:util.calc_loot() --- res/content/base/modules/util.lua | 6 +++--- res/content/base/scripts/grass_block.lua | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/res/content/base/modules/util.lua b/res/content/base/modules/util.lua index b1a97ad06..1044eb394 100644 --- a/res/content/base/modules/util.lua +++ b/res/content/base/modules/util.lua @@ -12,14 +12,14 @@ function util.drop(ppos, itemid, count, data, pickup_delay) }}) end -local function calc_loot(loot_table) +function calc_loot(loot_table) local results = {} for _, loot in ipairs(loot_table) do local chance = loot.chance or 1 local count = loot.count or 1 - + local roll = math.random() - + if roll < chance then if loot.min and loot.max then count = math.random(loot.min, loot.max) diff --git a/res/content/base/scripts/grass_block.lua b/res/content/base/scripts/grass_block.lua index fb7482c20..b5965de43 100644 --- a/res/content/base/scripts/grass_block.lua +++ b/res/content/base/scripts/grass_block.lua @@ -1,3 +1,6 @@ +local dirtid = block.index("base:dirt") +local grass_blockid = block.index("base:grass_block") + local offsets = {} for lx=-1,1 do for ly=-1,1 do @@ -8,18 +11,17 @@ for lx=-1,1 do end function on_random_update(x, y, z) - local dirtid = block.index('base:dirt'); if block.is_solid_at(x, y+1, z) then block.set(x, y, z, dirtid, 0) return end - local grassblockid = block.index('base:grass_block') + for _, offset in ipairs(offsets) do local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3] if block.get(nx, ny, nz) == dirtid and not block.is_solid_at(nx, ny + 1, nz) then - block.set(nx, ny, nz, grassblockid, 0) + block.set(nx, ny, nz, grass_blockid, 0) return end end From 466f515ea9cf3b23680bc418547a72544ab71f15 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 2 Aug 2026 18:27:19 +0300 Subject: [PATCH 32/59] update doc/*/scripting/builtins/libapp.md --- doc/en/scripting/builtins/libapp.md | 17 +++++++++++++++++ doc/ru/scripting/builtins/libapp.md | 19 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/doc/en/scripting/builtins/libapp.md b/doc/en/scripting/builtins/libapp.md index 0ec755689..35084d9bd 100644 --- a/doc/en/scripting/builtins/libapp.md +++ b/doc/en/scripting/builtins/libapp.md @@ -191,3 +191,20 @@ 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 + +-- Stops the engine sub-instance. +-- Returns true if the instance was alive at the time of the call. +app.terminate(handle: int) -> boolean +``` diff --git a/doc/ru/scripting/builtins/libapp.md b/doc/ru/scripting/builtins/libapp.md index 07e358ef4..920283ce3 100644 --- a/doc/ru/scripting/builtins/libapp.md +++ b/doc/ru/scripting/builtins/libapp.md @@ -142,4 +142,21 @@ app.set_content_sources(sources: table) -- Сбрасывает список источников контента. app.reset_content_sources() -``` \ No newline at end of file +``` + +## Под-экземпляры + +```lua +-- Создаёт headless-экземпляр движка с текущим проектом и указанным сценарием. +-- Возвращает id экземпляра. Число живых под-экземпляров, на данный момент, ограничено одним. +app.start_background_instance( + -- файл сценария + app_script: string, + -- файл лога + output_file: string | nil +) -> int + +-- Останавливает под-экземпляр движка. +-- Возвращает true если экземпляр был жив в момент вызова. +app.terminate(handle: int) -> boolean +``` From 6bbc78098dfd15b353b2d1d28d37133f4fe3651d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 2 Aug 2026 18:32:54 +0300 Subject: [PATCH 33/59] add app.is_instance_alive & rename app.terminate to app.terminate_instance --- doc/en/scripting/builtins/libapp.md | 3 +++ doc/ru/scripting/builtins/libapp.md | 5 ++++- src/logic/scripting/lua/libs/libapp.cpp | 14 ++++++++++++-- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/doc/en/scripting/builtins/libapp.md b/doc/en/scripting/builtins/libapp.md index 35084d9bd..61be41d5d 100644 --- a/doc/en/scripting/builtins/libapp.md +++ b/doc/en/scripting/builtins/libapp.md @@ -204,6 +204,9 @@ app.start_background_instance( 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 diff --git a/doc/ru/scripting/builtins/libapp.md b/doc/ru/scripting/builtins/libapp.md index 920283ce3..bbdf5cd2f 100644 --- a/doc/ru/scripting/builtins/libapp.md +++ b/doc/ru/scripting/builtins/libapp.md @@ -156,7 +156,10 @@ app.start_background_instance( output_file: string | nil ) -> int +-- Проверяет, жив ли под-экземпляр движка. +app.is_instance_alive(handle: int) -> boolean + -- Останавливает под-экземпляр движка. -- Возвращает true если экземпляр был жив в момент вызова. -app.terminate(handle: int) -> boolean +app.terminate_instance(handle: int) -> boolean ``` diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index e86b6d88e..9157b33a6 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -425,7 +425,16 @@ static int l_start_background_instance(lua::State* L) { return lua::pushinteger(L, handle); } -static int l_terminate(lua::State* L) { +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"); @@ -472,6 +481,7 @@ const luaL_Reg applib[] = { {"create_memory_device", lua::wrap}, {"start_debug_instance", lua::wrap}, {"start_background_instance", lua::wrap}, - {"terminate", lua::wrap}, + {"is_instance_alive", lua::wrap}, + {"terminate_instance", lua::wrap}, {nullptr, nullptr} }; From 4d010e8320964bc87e927ee5089e690c0250dd5c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 2 Aug 2026 18:36:12 +0300 Subject: [PATCH 34/59] fix doc/en/scripting/builtins/libapp.md formatting --- doc/en/scripting/builtins/libapp.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/scripting/builtins/libapp.md b/doc/en/scripting/builtins/libapp.md index 61be41d5d..48d79bf8e 100644 --- a/doc/en/scripting/builtins/libapp.md +++ b/doc/en/scripting/builtins/libapp.md @@ -180,13 +180,13 @@ app.get_content_sources() -> table Returns a list of content sources (paths), in descending priority order. -``lua +```lua app.set_content_sources(sources: table) ``` Sets a list of content sources (paths). Specified in descending priority order. -``lua +```lua app.reset_content_sources() ``` From 9444dc0c4596bd0c1fd2710488c8d412da8aab1f Mon Sep 17 00:00:00 2001 From: EsPaKira Date: Sun, 2 Aug 2026 20:02:56 +0400 Subject: [PATCH 35/59] fix lua version compare --- res/layouts/pages/content.xml.lua | 44 +++++++++++++++---------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index cb63a96c7..70b5a486c 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -179,7 +179,7 @@ function place_pack(panel, packinfo, callback, position_func) end end -local Version = {}; +local Version = {} function Version.matches_pattern(version) for _, letter in string.gmatch(version, "%.+") do @@ -187,32 +187,32 @@ function Version.matches_pattern(version) return false; end - local t = string.split(version, "."); + local t = string.split(version, ".") return #t == 2 or #t == 3; end end function Version.__equal(ver1, ver2) - return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3]; + return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3] end function Version.__greater(ver1, ver2) - if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end; - if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end; - return ver1[3] > ver2[3]; + if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end + if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end + return ver1[3] > ver2[3] end function Version.__less(ver1, ver2) - return Version.__greater(ver2, ver1); + return Version.__greater(ver2, ver1) end function Version.__greater_or_equal(ver1, ver2) - return not Version.__less(ver1, ver2); + return not Version.__less(ver1, ver2) end function Version.__less_or_equal(ver1, ver2) - return not Version.__greater(ver1, ver2); + return not Version.__greater(ver1, ver2) end Version.operators = { @@ -224,8 +224,8 @@ Version.operators = { } function Version.compare(op, ver1, ver2) - ver1 = string.split(ver1, "."); - ver2 = string.split(ver2, "."); + ver1 = string.split(ver1, ".") + ver2 = string.split(ver2, ".") local comparison_func = Version.operators[op]; @@ -237,14 +237,14 @@ function Version.compare(op, ver1, ver2) end function Version.parse(version) - local op = string.sub(version, 1, 2); + local op = string.sub(version, 1, 2) if op == ">=" or op == "<=" then - return op, string.sub(version, #op + 1); + return op, string.sub(version, #op + 1) end op = string.sub(version, 1, 1); if op == ">" or op == "<" then - return op, string.sub(version, #op + 1); + return op, string.sub(version, #op + 1) end return "=", version; @@ -252,12 +252,12 @@ end local function compare_version(dependent_version, actual_version) if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then - local op, dep_ver = Version.parse_version(dependent_version); - Version.compare(op, dep_ver, actual_version); + local op, dep_ver = Version.parse_version(dependent_version) + Version.compare(op, dep_ver, actual_version) elseif dependent_version == "*" or dependent_version == actual_version then - return true; + return true else - return false; + return false end end @@ -277,15 +277,15 @@ function check_dependencies(packinfo) ) end - local dep_pack = pack.get_info(depid); - + local dep_pack = pack.get_info(depid) + local op, ver = Version.parse(depver) + if not compare_version(depver, dep_pack.version) then - local op, ver = Version.parse(depver) return string.format( "%s: %s != %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, ver, depid - ); + ) end if table.has(packs_installed, packinfo.id) then From 4be73acc61f6805c9df2743e68a86ee646d43e25 Mon Sep 17 00:00:00 2001 From: EsPaKira Date: Sun, 2 Aug 2026 21:07:02 +0400 Subject: [PATCH 36/59] fix fix lua version comparision --- res/layouts/pages/content.xml.lua | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 70b5a486c..6d684b130 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -182,15 +182,15 @@ end local Version = {} function Version.matches_pattern(version) - for _, letter in string.gmatch(version, "%.+") do - if type(letter) ~= "number" or letter ~= "." then - return false; - end + local t = string.split(version, ".") + if #t ~= 2 and #t ~= 3 then return false end - local t = string.split(version, ".") - - return #t == 2 or #t == 3; + for i = 1, #t do + local matched = string.match(t[i], "^%d+$") + if not matched then return false end end + + return true end function Version.__equal(ver1, ver2) @@ -208,7 +208,7 @@ function Version.__less(ver1, ver2) end function Version.__greater_or_equal(ver1, ver2) - return not Version.__less(ver1, ver2) + return not Version.__less(ver2, ver1) end function Version.__less_or_equal(ver1, ver2) @@ -230,9 +230,9 @@ function Version.compare(op, ver1, ver2) local comparison_func = Version.operators[op]; if comparison_func then - return comparison_func(ver1, ver2); + return comparison_func(ver1, ver2) else - return false; + return false end end @@ -247,13 +247,12 @@ function Version.parse(version) return op, string.sub(version, #op + 1) end - return "=", version; + return "=", version end -local function compare_version(dependent_version, actual_version) +local function compare_version(op, dependent_version, actual_version) if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then - local op, dep_ver = Version.parse_version(dependent_version) - Version.compare(op, dep_ver, actual_version) + return Version.compare(op, dep_ver, actual_version) elseif dependent_version == "*" or dependent_version == actual_version then return true else @@ -280,7 +279,7 @@ function check_dependencies(packinfo) local dep_pack = pack.get_info(depid) local op, ver = Version.parse(depver) - if not compare_version(depver, dep_pack.version) then + if not compare_version(op, ver, dep_pack.version) then return string.format( "%s: %s != %s (%s)", gui.str("error.dependency-version-not-met"), From 74cefdafbd1168f0ca678941322bdf62aa55e881 Mon Sep 17 00:00:00 2001 From: EsPaKira Date: Sun, 2 Aug 2026 21:39:33 +0400 Subject: [PATCH 37/59] fix calc_loot export --- res/content/base/modules/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/content/base/modules/util.lua b/res/content/base/modules/util.lua index 1044eb394..2f53ba6be 100644 --- a/res/content/base/modules/util.lua +++ b/res/content/base/modules/util.lua @@ -12,7 +12,7 @@ function util.drop(ppos, itemid, count, data, pickup_delay) }}) end -function calc_loot(loot_table) +function util.calc_loot(loot_table) local results = {} for _, loot in ipairs(loot_table) do local chance = loot.chance or 1 From a422ea1534f9bb47f0d48d16d6e7a450e9f00e16 Mon Sep 17 00:00:00 2001 From: EsPaKira Date: Sun, 2 Aug 2026 21:43:10 +0400 Subject: [PATCH 38/59] fix util --- res/content/base/modules/util.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/content/base/modules/util.lua b/res/content/base/modules/util.lua index 2f53ba6be..874e6d3d6 100644 --- a/res/content/base/modules/util.lua +++ b/res/content/base/modules/util.lua @@ -37,7 +37,7 @@ end function util.block_loot(blockid) local lootscheme = block.properties[blockid]["base:loot"] if lootscheme then - return calc_loot(lootscheme) + return util.calc_loot(lootscheme) end return {{item=block.get_picking_item(blockid), count=1}} end From f8a44c023f563f2adc511356a1e7865a20b9ab7b Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Mon, 3 Aug 2026 18:25:44 +1000 Subject: [PATCH 39/59] patched code after code review from MihailRis --- res/modules/internal/bytearray.lua | 37 +++++++++---------- .../lua/usertypes/lua_type_canvas.cpp | 7 +++- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 04212d3e1..1615d1a08 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -4,13 +4,14 @@ local FFI = ffi FFI.cdef[[ void* malloc(size_t); - void *realloc(void *, size_t); + void* realloc(void*, size_t); void free(void*); typedef struct { unsigned char* bytes; int size; int capacity; } bytearray_t; + void* memmove(void*, const void*, size_t); ]] local free = FFI.C.free @@ -172,29 +173,27 @@ local function slice(self, offset, length) end local function copy(self, srcindex, dst, dstindex, size) - if srcindex > 0 and dstindex > 0 and size > 0 and - srcindex + size - 1 <= self.size and - dstindex + size - 1 <= dst.size - then - FFI.copy(dst.bytes + dstindex - 1, self.bytes + srcindex - 1, size) + if size <= 0 then error("size of byte range must be positive non-zero integer") end + if srcindex < 1 or srcindex + size - 1 > self.size then + error("specified source byte range is out of source array bounds") end + if dstindex < 1 or dstindex + size - 1 > dst.size then + error("specified destination byte range is out of destination array bounds") + end + + FFI.copy(dst.bytes + dstindex - 1, self.bytes + srcindex - 1, size) end local function move(self, fromindex, toindex, size) - if fromindex > 0 and toindex > 0 and size > 0 and - fromindex + size - 1 <= self.size and - toindex + size - 1 <= self.size - then - if toindex >= fromindex and toindex < fromindex + size then - local buff = malloc(size) - FFI.copy(buff, self.bytes + fromindex - 1, size) - FFI.copy(self.bytes + toindex - 1, buff, size) - free(buff) - else - -- убери(-те) это ветвление, если FFI будет производить копирование не побайтово, и оставь(-те) только верхний блок без условия вообще. - FFI.copy(self.bytes + toindex - 1, self.bytes + fromindex - 1, size) - end + if size <= 0 then error("size of byte range must be positive non-zero integer") end + if fromindex < 1 or fromindex + size - 1 > self.size then + error("specified source byte range is out of array bounds") end + if toindex < 1 or toindex + size - 1 > self.size then + error("specified destination byte range is out of array bounds") + end + + FFI.C.memmove(self.bytes + self.toindex - 1, self.bytes + self.fromindex - 1, size) end local bytearray_methods = { diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index d03b4e855..928515e74 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -164,8 +164,11 @@ static int l_clear(State* L) { size_t pixelscount = image.getWidth() * image.getHeight(); uint32_t* data = reinterpret_cast(image.getData()); - if (gettop(L) == 1) std::fill(data, data + pixelscount, 0); - else std::fill(data, data + pixelscount, get_rgba(L, 2).rgba); + if (gettop(L) == 1) { + std::fill(data, data + pixelscount, 0); + } else { + std::fill(data, data + pixelscount, get_rgba(L, 2).rgba); + } return 0; } From 3eac247a6918398e6bbf92278d615eff8836c8d6 Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Mon, 3 Aug 2026 22:09:11 +1000 Subject: [PATCH 40/59] eliminated hardcoded types sizes in bytearray-viewers declaratons and replaced on usage ffi.sizeof function --- res/modules/internal/bytearray.lua | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 1615d1a08..d58839f15 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -298,7 +298,8 @@ local function FFIBytearray_as_string(bytes) end end -local function create_FFIview_class(name, typename, typesize) +local function create_FFIview_class(name, typename) + local typesize = FFI.sizeof(typename) local ptrtype = typename .. "*" local FFIview_mt = { __index = function(self, key) @@ -343,11 +344,11 @@ local function create_FFIview_class(name, typename, typesize) end end -local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2) -local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2) -local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4) -local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4) -local FFIF32view = create_FFIview_class("FFIF32view", "float", 4) +local FFII16view = create_FFIview_class("FFII16view", "int16_t") +local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t") +local FFII32view = create_FFIview_class("FFII32view", "int32_t") +local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t") +local FFIF32view = create_FFIview_class("FFIF32view", "float") return { FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), From 2c72d33485fd38b5b69b8d9f80bb1bd53f1b720f Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Mon, 3 Aug 2026 22:45:48 +1000 Subject: [PATCH 41/59] improved Bytearray:fill method --- res/modules/internal/bytearray.lua | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index d58839f15..586fa8e25 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -142,8 +142,13 @@ local function clear(self) self.size = 0 end -local function fill(self, byte) - if self.size > 0 then +local function fill(self, index, size, byte) + if index ~= nil and size ~= nil and size > 0 then + if index < 1 or index + size - 1 > self.size then + error("selected fill range is out of array bounds") + end + FFI.fill(self.bytes + index - 1, size, byte) + else FFI.fill(self.bytes, self.size, byte) end end From 5a0f63061ffc866052fa7f0d0bd190987ab447ed Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Mon, 3 Aug 2026 23:41:57 +1000 Subject: [PATCH 42/59] added & upgraded Russian docs for new bytearray functions, new bytearray views (viewers) and other features --- doc/ru/scripting/bytearray.md | 37 ++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/doc/ru/scripting/bytearray.md b/doc/ru/scripting/bytearray.md index c90b6d67e..138eed347 100644 --- a/doc/ru/scripting/bytearray.md +++ b/doc/ru/scripting/bytearray.md @@ -22,11 +22,31 @@ bytes:insert(index: int, value: int) -- Удаляет элемент(ы), если передан `count`, удалит `count` кол-во элементов с выбранного индекса. bytes:remove(index: int, [опционально] count: int) --- Очищает массив +-- Очищает массив. bytes:clear() +--[[ +Заполняет массив определённым байтом полностью либо в определённом диапазоне. +Логика работы следующая: если параметр size или index равны nil, то указанным байтом будет заполнен весь массив, + иначе будет заполнен диапазон внутри массива начиная с индекса index и размером size. +В случае выхода заполняемого блока за границы массива генерирует соответствующую ошибку. +]] +bytes:fill(index: posint/nil, size: posint/nil, byte: uint8) + -- Создаёт новый Bytearray, содержащий копию части данных с offset до offset+length bytes:slice(offset: int, length: int) -> Bytearray + +--[[ +Копирует блок байт из исходного массива, начиная с индекса srcindex, в массив dst, начиная в нём с индекса dstindex. +В случае выхода за границы исходного, конечного массива, либо при некорректном значении параметра size генерирует соответствующую ошибку. +]] +bytes:copy(srcindex: posint, dst: Bytearray, dstindex: posint, size: posint) + +--[[ +Безопасно копирует перекрывающиеся области памяти (байт) в пределах одного массива. +В случае выхода за пределы массива начального или конечного диапазона байт генерирует соответствующую ошибку. +]] +bytes:move(fromindex: posint, toindex: posint, size: posint) ``` ## View @@ -35,12 +55,15 @@ bytes:slice(offset: int, length: int) -> Bytearray Не имеет своих методов, является просто объектной ссылкой на оригинальный Bytearray. -| Класс | Тип элементов | Размер | -| ------------ | ------------------ | ------- | -| `I16view` | `int16_t` | 2 байта | -| `U16view` | `uint16_t` | 2 байта | -| `I32view` | `int32_t` | 4 байта | -| `U32view` | `uint32_t` | 4 байта | +| Класс | Тип элементов | Размер* | +| ------------ | ------------------ | -------- | +| `I16view` | `int16_t` | 2 байта | +| `U16view` | `uint16_t` | 2 байта | +| `I32view` | `int32_t` | 4 байта | +| `U32view` | `uint32_t` | 4 байта | +| `F32view` | `float` | 4 байта | + +* Размеры C-шных типов являются платформозависимыми. В данной таблице предоставлены наиболее распространённые размеры типов. --- From 7c9fff19e457e4d53d7bc68cb8d871ade80fb716 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 3 Aug 2026 19:54:54 +0300 Subject: [PATCH 43/59] add '--log' command line parameter --- src/engine/CoreParameters.hpp | 1 + src/main.cpp | 10 +++++++++- src/util/command_line.cpp | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/engine/CoreParameters.hpp b/src/engine/CoreParameters.hpp index b5d8074ce..9668ce1cd 100644 --- a/src/engine/CoreParameters.hpp +++ b/src/engine/CoreParameters.hpp @@ -12,6 +12,7 @@ struct CoreParameters { std::filesystem::path userFolder = "."; std::filesystem::path scriptFile; std::filesystem::path projectFolder; + std::filesystem::path logFile; std::string debugServerString; int tps = 20; int subProcessDepth = 0; diff --git a/src/main.cpp b/src/main.cpp index 402b829b3..4e739acde 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,7 @@ #include #include +using namespace std::literals; static debug::Logger logger("main"); static void sigterm_handler(int signum) { @@ -36,7 +37,14 @@ int main(int argc, char** argv) { #ifdef NDEBUG std::signal(SIGINT, sigterm_handler); #endif - debug::Logger::init(coreParameters.userFolder.string() + "/latest.log"); + std::filesystem::path logFile = coreParameters.logFile; + if (logFile.empty()) { + logFile = coreParameters.userFolder.string() + "/latest"s + + (coreParameters.subProcessDepth > 0 + ? ".sub" + std::to_string(coreParameters.subProcessDepth) + : "") + ".log"s; + } + debug::Logger::init(logFile); platform::configure_encoding(); auto& engine = Engine::getInstance(); diff --git a/src/util/command_line.cpp b/src/util/command_line.cpp index f05231821..d5435e955 100644 --- a/src/util/command_line.cpp +++ b/src/util/command_line.cpp @@ -82,6 +82,10 @@ static bool perform_keyword( params.subProcessDepth = reader.nextInt(); return true; }, "", "sub-process depth"), + ArgC("--log", [](auto& params, auto& reader) -> bool { + params.logFile = reader.next(); + return true; + }, "", "target log file"), ArgC("--help", [](auto&, auto&) -> bool { std::cout << "VoxelCore v" << ENGINE_VERSION_STRING << "\n\n"; std::cout << "Command-line arguments:\n"; From 48a09b10be84880b3d6e9d53687d40f521d7deca Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 3 Aug 2026 20:02:30 +0300 Subject: [PATCH 44/59] update platform::new_engine_instance --- src/logic/scripting/lua/libs/libapp.cpp | 9 ++++----- src/util/platform.cpp | 9 +++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 9157b33a6..4885915ad 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -403,6 +403,7 @@ static int l_start_background_instance(lua::State* L) { "--script", io::resolve(scriptPath).u8string(), "--sub-depth", std::to_string(engine->getCoreParameters() .subProcessDepth + 1), + "--log", io::resolve(outputPath), }; args.emplace_back("--project"); args.emplace_back(io::resolve(engine->getProject().path).u8string()); @@ -417,11 +418,9 @@ static int l_start_background_instance(lua::State* L) { 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 - ); + ::processes[handle] = + platform::new_engine_instance(std::move(args), "", true); + return lua::pushinteger(L, handle); } diff --git a/src/util/platform.cpp b/src/util/platform.cpp index 7b08639e9..c7b945510 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -28,6 +28,8 @@ #include #endif +using namespace std::literals; + namespace platform::internal { std::filesystem::path get_executable_path(); } @@ -356,7 +358,7 @@ std::unique_ptr platform::new_engine_instance( if (!outputFile.empty()) { si.dwFlags |= STARTF_USESTDHANDLES; si.hStdOutput = CreateFileW( - toWString(outputFile.u8string()).data(), + toWString(outputFile.empty() ? outputFile.u8string() : "NUL"s,).data(), GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, nullptr, @@ -434,9 +436,12 @@ std::unique_ptr platform::new_engine_instance( if (subProcess) { pid_t pid = fork(); + if (outputFile.empty()) { + outputFile = "/dev/null"; + } int fd = open( outputFile.string().c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644 - ); + ); if (pid == 0) { prctl(PR_SET_PDEATHSIG, SIGTERM); From 099830d2fafeb2a4846746df74deb74957dee353 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 3 Aug 2026 20:19:03 +0300 Subject: [PATCH 45/59] fix windows build --- src/logic/scripting/lua/libs/libapp.cpp | 2 +- src/util/platform.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/logic/scripting/lua/libs/libapp.cpp b/src/logic/scripting/lua/libs/libapp.cpp index 4885915ad..84f319a9c 100644 --- a/src/logic/scripting/lua/libs/libapp.cpp +++ b/src/logic/scripting/lua/libs/libapp.cpp @@ -403,7 +403,7 @@ static int l_start_background_instance(lua::State* L) { "--script", io::resolve(scriptPath).u8string(), "--sub-depth", std::to_string(engine->getCoreParameters() .subProcessDepth + 1), - "--log", io::resolve(outputPath), + "--log", io::resolve(outputPath).u8string(), }; args.emplace_back("--project"); args.emplace_back(io::resolve(engine->getProject().path).u8string()); diff --git a/src/util/platform.cpp b/src/util/platform.cpp index c7b945510..5d5e489bc 100644 --- a/src/util/platform.cpp +++ b/src/util/platform.cpp @@ -358,7 +358,7 @@ std::unique_ptr platform::new_engine_instance( if (!outputFile.empty()) { si.dwFlags |= STARTF_USESTDHANDLES; si.hStdOutput = CreateFileW( - toWString(outputFile.empty() ? outputFile.u8string() : "NUL"s,).data(), + toWString(outputFile.empty() ? outputFile.u8string() : "NUL"s).data(), GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, nullptr, From d6923b0e0f2185bb916fcf20b728af5b11aa6ce0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 3 Aug 2026 20:36:16 +0300 Subject: [PATCH 46/59] fix windows build again --- src/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.cpp b/src/main.cpp index 4e739acde..9d8d8e551 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -44,7 +44,7 @@ int main(int argc, char** argv) { ? ".sub" + std::to_string(coreParameters.subProcessDepth) : "") + ".log"s; } - debug::Logger::init(logFile); + debug::Logger::init(logFile.u8string()); platform::configure_encoding(); auto& engine = Engine::getInstance(); From b30207917fb614c941bf8c7b8675fb16f9dc4fb3 Mon Sep 17 00:00:00 2001 From: RomanDonw <126128023+RomanDonw@users.noreply.github.com> Date: Mon, 3 Aug 2026 23:59:28 +1000 Subject: [PATCH 47/59] made Russian docs more exact --- doc/ru/scripting/bytearray.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/ru/scripting/bytearray.md b/doc/ru/scripting/bytearray.md index 138eed347..50ec15d9d 100644 --- a/doc/ru/scripting/bytearray.md +++ b/doc/ru/scripting/bytearray.md @@ -55,15 +55,15 @@ bytes:move(fromindex: posint, toindex: posint, size: posint) Не имеет своих методов, является просто объектной ссылкой на оригинальный Bytearray. -| Класс | Тип элементов | Размер* | -| ------------ | ------------------ | -------- | -| `I16view` | `int16_t` | 2 байта | -| `U16view` | `uint16_t` | 2 байта | -| `I32view` | `int32_t` | 4 байта | -| `U32view` | `uint32_t` | 4 байта | -| `F32view` | `float` | 4 байта | +| Класс | Тип элементов | Размер | +| ------------ | ------------------ | --------- | +| `I16view` | `int16_t` | 2 байта | +| `U16view` | `uint16_t` | 2 байта | +| `I32view` | `int32_t` | 4 байта | +| `U32view` | `uint32_t` | 4 байта | +| `F32view` | `float` | 4 байта* | -* Размеры C-шных типов являются платформозависимыми. В данной таблице предоставлены наиболее распространённые размеры типов. +* Размеры "оригинальных" C-шных типов являются платформозависимыми и их точный размер не описан в стандарте языка. В данной таблице предоставлены наиболее распространённые размеры типов. --- @@ -82,4 +82,4 @@ print(i16[3]) -- -6 (signed вьюшка) for _, num in ipairs(i16) do print(num) end -- 1; 2; -6 -``` \ No newline at end of file +``` From e0afe5c6499f7b24e3bad0b03efc7060b0e862ee Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Tue, 4 Aug 2026 17:18:58 +1000 Subject: [PATCH 48/59] added typesize method for bytearray views (viewers) (both to Lua and docs (only Russian for now)) --- doc/ru/scripting/bytearray.md | 7 +++++++ res/modules/internal/bytearray.lua | 3 +++ 2 files changed, 10 insertions(+) diff --git a/doc/ru/scripting/bytearray.md b/doc/ru/scripting/bytearray.md index 50ec15d9d..b16b3fd39 100644 --- a/doc/ru/scripting/bytearray.md +++ b/doc/ru/scripting/bytearray.md @@ -65,6 +65,13 @@ bytes:move(fromindex: posint, toindex: posint, size: posint) * Размеры "оригинальных" C-шных типов являются платформозависимыми и их точный размер не описан в стандарте языка. В данной таблице предоставлены наиболее распространённые размеры типов. +### Специфические методы "вьюшек" + +```lua +-- Возвращает размер типа вьюшки (полезно для вьюшек, использующих C-шные типы нефиксированного размера: те же float или double). +view:typesize() -> posint +``` + --- ### Пример использования diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 586fa8e25..556085515 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -339,6 +339,9 @@ local function create_FFIview_class(name, typename) return i, ptr[i - 1] end end + end, + typesize = function() + return typesize end } return function (bytes) From 9fc94e40b20f6f686b4113dcd7fe530f04020fba Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Tue, 4 Aug 2026 17:41:24 +1000 Subject: [PATCH 49/59] renamed bytearray F32view to FLT view & improved bytearray Russian docs --- doc/ru/scripting/bytearray.md | 9 +++++++-- res/modules/internal/bytearray.lua | 4 ++-- res/scripts/stdmin.lua | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/doc/ru/scripting/bytearray.md b/doc/ru/scripting/bytearray.md index b16b3fd39..032182891 100644 --- a/doc/ru/scripting/bytearray.md +++ b/doc/ru/scripting/bytearray.md @@ -2,6 +2,8 @@ *Bytearray* - динамический байтовый массив, реализованный через LuaJIT FFI. По нему можно итерироваться стандартными функциями **pairs** и **ipairs** +Примечание касаемо условных обозначений в документации: псведотип `posint` означает натуральное (положительное ненулевое целое) число. + ## Основное ### Создание массива ```lua @@ -61,14 +63,17 @@ bytes:move(fromindex: posint, toindex: posint, size: posint) | `U16view` | `uint16_t` | 2 байта | | `I32view` | `int32_t` | 4 байта | | `U32view` | `uint32_t` | 4 байта | -| `F32view` | `float` | 4 байта* | +| `FLTview` | `float` | 4 байта* | * Размеры "оригинальных" C-шных типов являются платформозависимыми и их точный размер не описан в стандарте языка. В данной таблице предоставлены наиболее распространённые размеры типов. ### Специфические методы "вьюшек" ```lua --- Возвращает размер типа вьюшки (полезно для вьюшек, использующих C-шные типы нефиксированного размера: те же float или double). +--[[ +Возвращает размер типа вьюшки. Результат тот же, что и от применения оператора sizeof() из языка C для данного типа. + Полезно для вьюшек, использующих C-шные типы нефиксированного размера, например те же float или double. +]] view:typesize() -> posint ``` diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 556085515..27472f491 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -356,7 +356,7 @@ local FFII16view = create_FFIview_class("FFII16view", "int16_t") local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t") local FFII32view = create_FFIview_class("FFII32view", "int32_t") local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t") -local FFIF32view = create_FFIview_class("FFIF32view", "float") +local FFIFLTview = create_FFIview_class("FFIFLTview", "float") return { FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), @@ -366,5 +366,5 @@ return { FFII16view = FFII16view, FFIU32view = FFIU32view, FFII32view = FFII32view, - FFIF32view = FFIF32view, + FFIFLTview = FFIFLTview, } diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 01b61efc6..a7c7bdbd2 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -314,7 +314,7 @@ U16view = bytearray.FFIU16view I16view = bytearray.FFII16view U32view = bytearray.FFIU32view I32view = bytearray.FFII32view -F32view = bytearray.FFIF32view +FLTview = bytearray.FFIFLTview Bytearray_construct = function(...) return Bytearray(...) end bit.compile = require "core:bitwise/compiler" From 457fc573492dd9ec2f9399386bd1e1070c723830 Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Tue, 4 Aug 2026 18:07:11 +1000 Subject: [PATCH 50/59] added more new views (viewers) for bytearray (again both to Lua & Russian docs) --- doc/ru/scripting/bytearray.md | 10 ++++++---- res/modules/internal/bytearray.lua | 8 ++++++++ res/scripts/stdmin.lua | 4 ++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/doc/ru/scripting/bytearray.md b/doc/ru/scripting/bytearray.md index 032182891..d15cd6a58 100644 --- a/doc/ru/scripting/bytearray.md +++ b/doc/ru/scripting/bytearray.md @@ -55,15 +55,19 @@ bytes:move(fromindex: posint, toindex: posint, size: posint) Это "вьюшки" поверх Bytearray, которые интерпретируют его байты как массив чисел другого размера - **без копирования данных**. -Не имеет своих методов, является просто объектной ссылкой на оригинальный Bytearray. +Практически не имеют своих методов, являются простыми объектными ссылками на оригинальный Bytearray. | Класс | Тип элементов | Размер | | ------------ | ------------------ | --------- | +| `I8view` | `int8_t` | 1 байт | | `I16view` | `int16_t` | 2 байта | | `U16view` | `uint16_t` | 2 байта | | `I32view` | `int32_t` | 4 байта | | `U32view` | `uint32_t` | 4 байта | +| `I64view` | `int64_t` | 8 байт | +| `U64view` | `uint64_t` | 8 байт | | `FLTview` | `float` | 4 байта* | +| `DBLview` | `double` | 8 байт* | * Размеры "оригинальных" C-шных типов являются платформозависимыми и их точный размер не описан в стандарте языка. В данной таблице предоставлены наиболее распространённые размеры типов. @@ -77,9 +81,7 @@ bytes:move(fromindex: posint, toindex: posint, size: posint) view:typesize() -> posint ``` ---- - -### Пример использования +## Пример использования ```lua local bytes = Bytearray({1, 0, 2, 0, 250, 255}) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 27472f491..42f6bddcd 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -352,19 +352,27 @@ local function create_FFIview_class(name, typename) end end +local FFII8view = create_FFIview_class("FFII8view", "int8_t") local FFII16view = create_FFIview_class("FFII16view", "int16_t") local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t") local FFII32view = create_FFIview_class("FFII32view", "int32_t") local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t") +local FFII64view = create_FFIview_class("FFII32view", "int64_t") +local FFIU64view = create_FFIview_class("FFIU32view", "uint64_t") local FFIFLTview = create_FFIview_class("FFIFLTview", "float") +local FFIDBLview = create_FFIview_class("FFIDBLview", "double") return { FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), FFIBytearray_as_string = FFIBytearray_as_string, FFIBytearray_as_ptr = FFIBytearray_as_ptr, + FFII8view = FFII8view, FFIU16view = FFIU16view, FFII16view = FFII16view, FFIU32view = FFIU32view, FFII32view = FFII32view, + FFIU64view = FFIU64view, + FFII64view = FFII64view, FFIFLTview = FFIFLTview, + FFIDBLview = FFIDBLview, } diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index a7c7bdbd2..93bdf118e 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -310,11 +310,15 @@ local bytearray = require "core:internal/bytearray" Bytearray = bytearray.FFIBytearray Bytearray_as_string = bytearray.FFIBytearray_as_string Bytearray_as_ptr = bytearray.FFIBytearray_as_ptr +I8view = bytearray.FFII8view U16view = bytearray.FFIU16view I16view = bytearray.FFII16view U32view = bytearray.FFIU32view I32view = bytearray.FFII32view +U64view = bytearray.FFIU64view +I64view = bytearray.FFII64view FLTview = bytearray.FFIFLTview +DBLview = bytearray.FFIDBLview Bytearray_construct = function(...) return Bytearray(...) end bit.compile = require "core:bitwise/compiler" From e203b5ef4cc77fd812e5e8f0e3775064ab4cb5ab Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Tue, 4 Aug 2026 19:50:55 +1000 Subject: [PATCH 51/59] fixed two (2) typos in Bytearray:move method implementation --- res/modules/internal/bytearray.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 42f6bddcd..cfae8b53d 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -198,7 +198,7 @@ local function move(self, fromindex, toindex, size) error("specified destination byte range is out of array bounds") end - FFI.C.memmove(self.bytes + self.toindex - 1, self.bytes + self.fromindex - 1, size) + FFI.C.memmove(self.bytes + toindex - 1, self.bytes + fromindex - 1, size) end local bytearray_methods = { From 5843e5828b5e568ed271f55852eb1329e071e417 Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Tue, 4 Aug 2026 20:25:15 +1000 Subject: [PATCH 52/59] upgraded bytearray's test (added testing new bytearray functions and views/viewers) --- dev/tests/bytearray.lua | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/dev/tests/bytearray.lua b/dev/tests/bytearray.lua index 415768c65..2a3d6140b 100644 --- a/dev/tests/bytearray.lua +++ b/dev/tests/bytearray.lua @@ -34,3 +34,67 @@ arr:insert(2, {5, 6}) assert(arr[#arr] == 25) assert(arr[2] == 5) assert(arr[3] == 6) + +-- ============================= + +local arr2 = Bytearray({254, 255, 255, 255, 255, 255, 255, 255}) +--assert(U64view(arr2)[1] == 2 ^ 64 - 2) +assert(I64view(arr2)[1] == -2) +assert(U32view(arr2)[1] == 2 ^ 32 - 2) +assert(I32view(arr2)[1] == -2) +assert(U16view(arr2)[1] == 2 ^ 16 - 2) +assert(I16view(arr2)[1] == -2) +assert(I8view(arr2)[1] == -2) + +assert(I8view(arr2)[2] == -1) +assert(I32view(arr2)[2] == -1) + +local arr3 = Bytearray({0xFF, 0x7F}) +local arr3i16 = I16view(arr3) +local arr3u16 = U16view(arr3) +assert(arr3i16[1] == 32767) + +arr3u16[1] = arr3u16[1] + 1 +assert(arr3i16[1] == -32768) +assert(arr3u16[1] == 32768) + +local arr4 = Bytearray({0x9A, 0x99, 0x59, 0x40}) -- approx. equal 3.4 in hexadecimal representation of single-precision float number. +local arr4flt = FLTview(arr4) +local arr4u32 = U32view(arr4) +assert(arr4flt[1] == 3.4000000953674316) +assert(arr4u32[1] == 0x4059999A) + +arr4flt[1] = arr4flt[1] + 0.1 +assert(arr4flt[1] == 3.5) +assert(arr4u32[1] == 0x40600000) -- hexadecimal representation of 3.5 single-precision float number. + +-- ============================================= + +function barrtostr(barr) + local outstr = "" + for i = 1, #barr do + local lastfmt = "" + if i ~= #barr then lastfmt = "|" end + outstr = outstr .. barr[i] .. lastfmt + end + return outstr +end + +local arr5 = Bytearray({0, 5, 34, 87, 21, 0, 210}) +assert(barrtostr(arr5) == "0|5|34|87|21|0|210") +arr5:move(2, 3, 4) +assert(barrtostr(arr5) == "0|5|5|34|87|21|210") + +local arr6 = Bytearray({1, 2, 3, 4, 6, 7, 8}) +assert(barrtostr(arr6) == "1|2|3|4|6|7|8") +arr5:copy(4, arr6, 2, 3) +assert(barrtostr(arr6) == "1|34|87|21|6|7|8") + +arr5:fill(nil, nil, 192) +assert(barrtostr(arr5) == "192|192|192|192|192|192|192") +arr5:fill(nil, 3, 233) +assert(barrtostr(arr5) == "233|233|233|233|233|233|233") +arr5:fill(2, nil, 254) +assert(barrtostr(arr5) == "254|254|254|254|254|254|254") +arr5:fill(2, 2, 66) +assert(barrtostr(arr5) == "254|66|66|254|254|254|254") \ No newline at end of file From 074f01b1bb1fae161fd83d3f9061594af2c6ce71 Mon Sep 17 00:00:00 2001 From: RomanDonw Date: Tue, 4 Aug 2026 22:40:59 +1000 Subject: [PATCH 53/59] upgraded bytearray test & added DBLview testing section --- dev/tests/bytearray.lua | 85 +++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/dev/tests/bytearray.lua b/dev/tests/bytearray.lua index 2a3d6140b..cbd537100 100644 --- a/dev/tests/bytearray.lua +++ b/dev/tests/bytearray.lua @@ -37,36 +37,39 @@ assert(arr[3] == 6) -- ============================= -local arr2 = Bytearray({254, 255, 255, 255, 255, 255, 255, 255}) ---assert(U64view(arr2)[1] == 2 ^ 64 - 2) -assert(I64view(arr2)[1] == -2) -assert(U32view(arr2)[1] == 2 ^ 32 - 2) -assert(I32view(arr2)[1] == -2) -assert(U16view(arr2)[1] == 2 ^ 16 - 2) -assert(I16view(arr2)[1] == -2) -assert(I8view(arr2)[1] == -2) +arr = Bytearray({254, 255, 255, 255, 255, 255, 255, 255}) +--assert(U64view(arr)[1] == 2 ^ 64 - 2) +assert(I64view(arr)[1] == -2) +assert(U32view(arr)[1] == 2 ^ 32 - 2) +assert(I32view(arr)[1] == -2) +assert(U16view(arr)[1] == 2 ^ 16 - 2) +assert(I16view(arr)[1] == -2) +assert(I8view(arr)[1] == -2) -assert(I8view(arr2)[2] == -1) -assert(I32view(arr2)[2] == -1) +assert(I8view(arr)[2] == -1) +assert(I32view(arr)[2] == -1) -local arr3 = Bytearray({0xFF, 0x7F}) -local arr3i16 = I16view(arr3) -local arr3u16 = U16view(arr3) -assert(arr3i16[1] == 32767) +arr = Bytearray({0xFF, 0x7F}) +local arri16 = I16view(arr) +local arru16 = U16view(arr) +assert(arri16[1] == 32767) -arr3u16[1] = arr3u16[1] + 1 -assert(arr3i16[1] == -32768) -assert(arr3u16[1] == 32768) +arru16[1] = arru16[1] + 1 +assert(arri16[1] == -32768) +assert(arru16[1] == 32768) -local arr4 = Bytearray({0x9A, 0x99, 0x59, 0x40}) -- approx. equal 3.4 in hexadecimal representation of single-precision float number. -local arr4flt = FLTview(arr4) -local arr4u32 = U32view(arr4) -assert(arr4flt[1] == 3.4000000953674316) -assert(arr4u32[1] == 0x4059999A) +arr = Bytearray({0x9A, 0x99, 0x59, 0x40}) -- approx. equal 3.4 in hexadecimal representation of single-precision float number. +local arrflt = FLTview(arr) +local arru32 = U32view(arr) +assert(arrflt[1] == 3.4000000953674316) +assert(arru32[1] == 0x4059999A) -arr4flt[1] = arr4flt[1] + 0.1 -assert(arr4flt[1] == 3.5) -assert(arr4u32[1] == 0x40600000) -- hexadecimal representation of 3.5 single-precision float number. +arrflt[1] = arrflt[1] + 0.1 +assert(arrflt[1] == 3.5) +assert(arru32[1] == 0x40600000) -- hexadecimal representation of 3.5 single-precision float number. + +arr = Bytearray({0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 9, 64}) -- hexadecimal representation of 3.141592653589793 double-precision float number. +assert(DBLview(arr)[1] == 3.141592653589793) -- ============================================= @@ -80,21 +83,21 @@ function barrtostr(barr) return outstr end -local arr5 = Bytearray({0, 5, 34, 87, 21, 0, 210}) -assert(barrtostr(arr5) == "0|5|34|87|21|0|210") -arr5:move(2, 3, 4) -assert(barrtostr(arr5) == "0|5|5|34|87|21|210") +arr = Bytearray({0, 5, 34, 87, 21, 0, 210}) +assert(barrtostr(arr) == "0|5|34|87|21|0|210") +arr:move(2, 3, 4) +assert(barrtostr(arr) == "0|5|5|34|87|21|210") -local arr6 = Bytearray({1, 2, 3, 4, 6, 7, 8}) -assert(barrtostr(arr6) == "1|2|3|4|6|7|8") -arr5:copy(4, arr6, 2, 3) -assert(barrtostr(arr6) == "1|34|87|21|6|7|8") +local arr2 = Bytearray({1, 2, 3, 4, 6, 7, 8}) +assert(barrtostr(arr2) == "1|2|3|4|6|7|8") +arr:copy(4, arr2, 2, 3) +assert(barrtostr(arr2) == "1|34|87|21|6|7|8") -arr5:fill(nil, nil, 192) -assert(barrtostr(arr5) == "192|192|192|192|192|192|192") -arr5:fill(nil, 3, 233) -assert(barrtostr(arr5) == "233|233|233|233|233|233|233") -arr5:fill(2, nil, 254) -assert(barrtostr(arr5) == "254|254|254|254|254|254|254") -arr5:fill(2, 2, 66) -assert(barrtostr(arr5) == "254|66|66|254|254|254|254") \ No newline at end of file +arr:fill(nil, nil, 192) +assert(barrtostr(arr) == "192|192|192|192|192|192|192") +arr:fill(nil, 3, 233) +assert(barrtostr(arr) == "233|233|233|233|233|233|233") +arr:fill(2, nil, 254) +assert(barrtostr(arr) == "254|254|254|254|254|254|254") +arr:fill(2, 2, 66) +assert(barrtostr(arr) == "254|66|66|254|254|254|254") \ No newline at end of file From 7a88bd454a98bfc3ae8c71b4198e2f8dd16b7ca3 Mon Sep 17 00:00:00 2001 From: RomanDonw <126128023+RomanDonw@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:39:21 +1000 Subject: [PATCH 54/59] fixed typo in FFIU64view & FFII64view bytearray views (viewers) declaration (bytearray.lua) --- res/modules/internal/bytearray.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index cfae8b53d..0908b8c9a 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -357,8 +357,8 @@ local FFII16view = create_FFIview_class("FFII16view", "int16_t") local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t") local FFII32view = create_FFIview_class("FFII32view", "int32_t") local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t") -local FFII64view = create_FFIview_class("FFII32view", "int64_t") -local FFIU64view = create_FFIview_class("FFIU32view", "uint64_t") +local FFII64view = create_FFIview_class("FFII64view", "int64_t") +local FFIU64view = create_FFIview_class("FFIU64view", "uint64_t") local FFIFLTview = create_FFIview_class("FFIFLTview", "float") local FFIDBLview = create_FFIview_class("FFIDBLview", "double") From 7f04d2884e7273f11ede01ef3f943aa139fca0c3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Aug 2026 18:54:37 +0300 Subject: [PATCH 55/59] fix view classes static typesize method --- res/modules/internal/bytearray.lua | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/res/modules/internal/bytearray.lua b/res/modules/internal/bytearray.lua index 0908b8c9a..896f43188 100644 --- a/res/modules/internal/bytearray.lua +++ b/res/modules/internal/bytearray.lua @@ -340,16 +340,20 @@ local function create_FFIview_class(name, typename) end end end, - typesize = function() - return typesize - end } - return function (bytes) - local x = setmetatable({ - bytes=bytes, - }, FFIview_mt) - return x - end + return setmetatable({}, { + __call = function (self, bytes) + local x = setmetatable({ + bytes=bytes, + }, FFIview_mt) + return x + end, + __index = { + typesize = function() + return typesize + end + } + }) end local FFII8view = create_FFIview_class("FFII8view", "int8_t") From 5f94e707ea9fdd53d6272b5c718b8c226222c8ee Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Aug 2026 18:55:10 +0300 Subject: [PATCH 56/59] update bytearray test --- dev/tests/bytearray.lua | 58 ++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/dev/tests/bytearray.lua b/dev/tests/bytearray.lua index cbd537100..0d1bdb6ae 100644 --- a/dev/tests/bytearray.lua +++ b/dev/tests/bytearray.lua @@ -18,55 +18,55 @@ Bytearray.insert(arr, {5, 3, 6}) assert(#arr == 12) Bytearray.insert(arr, 2, 8) -assert(#arr == 13) +asserts.equals(13, #arr) for i=1,10 do assert(arr[i] == 10 - i) end print(#arr, arr:get_capacity()) arr:trim() -assert(#arr == arr:get_capacity()) +asserts.equals(#arr, arr:get_capacity()) arr = Bytearray({0, 2, 7, 1, 16, 75, 25}) assert(arr[6] == 75) arr:insert(2, {5, 6}) -assert(arr[#arr] == 25) -assert(arr[2] == 5) -assert(arr[3] == 6) +asserts.equals(25, arr[#arr]) +asserts.equals(5, arr[2]) +asserts.equals(6, arr[3]) -- ============================= arr = Bytearray({254, 255, 255, 255, 255, 255, 255, 255}) ---assert(U64view(arr)[1] == 2 ^ 64 - 2) -assert(I64view(arr)[1] == -2) -assert(U32view(arr)[1] == 2 ^ 32 - 2) -assert(I32view(arr)[1] == -2) -assert(U16view(arr)[1] == 2 ^ 16 - 2) -assert(I16view(arr)[1] == -2) -assert(I8view(arr)[1] == -2) +-- asserts.equals(U64view(arr)[1], 2 ^ 64 - 2) +asserts.equals(I64view(arr)[1], -2) +asserts.equals(U32view(arr)[1], 2 ^ 32 - 2) +asserts.equals(I32view(arr)[1], -2) +asserts.equals(U16view(arr)[1], 2 ^ 16 - 2) +asserts.equals(I16view(arr)[1], -2) +asserts.equals(I8view(arr)[1], -2) -assert(I8view(arr)[2] == -1) -assert(I32view(arr)[2] == -1) +asserts.equals(I8view(arr)[2], -1) +asserts.equals(I32view(arr)[2], -1) arr = Bytearray({0xFF, 0x7F}) local arri16 = I16view(arr) local arru16 = U16view(arr) -assert(arri16[1] == 32767) +asserts.equals(arri16[1], 32767) arru16[1] = arru16[1] + 1 -assert(arri16[1] == -32768) -assert(arru16[1] == 32768) +asserts.equals(arri16[1], -32768) +asserts.equals(arru16[1], 32768) arr = Bytearray({0x9A, 0x99, 0x59, 0x40}) -- approx. equal 3.4 in hexadecimal representation of single-precision float number. local arrflt = FLTview(arr) local arru32 = U32view(arr) -assert(arrflt[1] == 3.4000000953674316) -assert(arru32[1] == 0x4059999A) +asserts.equals(arrflt[1], 3.4000000953674316) +asserts.equals(arru32[1], 0x4059999A) arrflt[1] = arrflt[1] + 0.1 -assert(arrflt[1] == 3.5) -assert(arru32[1] == 0x40600000) -- hexadecimal representation of 3.5 single-precision float number. +asserts.equals(arrflt[1], 3.5) +asserts.equals(arru32[1], 0x40600000) -- hexadecimal representation of 3.5 single-precision float number. arr = Bytearray({0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 9, 64}) -- hexadecimal representation of 3.141592653589793 double-precision float number. assert(DBLview(arr)[1] == 3.141592653589793) @@ -84,20 +84,20 @@ function barrtostr(barr) end arr = Bytearray({0, 5, 34, 87, 21, 0, 210}) -assert(barrtostr(arr) == "0|5|34|87|21|0|210") +asserts.equals(barrtostr(arr), "0|5|34|87|21|0|210") arr:move(2, 3, 4) -assert(barrtostr(arr) == "0|5|5|34|87|21|210") +asserts.equals(barrtostr(arr), "0|5|5|34|87|21|210") local arr2 = Bytearray({1, 2, 3, 4, 6, 7, 8}) -assert(barrtostr(arr2) == "1|2|3|4|6|7|8") +asserts.equals(barrtostr(arr2), "1|2|3|4|6|7|8") arr:copy(4, arr2, 2, 3) -assert(barrtostr(arr2) == "1|34|87|21|6|7|8") +asserts.equals(barrtostr(arr2), "1|34|87|21|6|7|8") arr:fill(nil, nil, 192) -assert(barrtostr(arr) == "192|192|192|192|192|192|192") +asserts.equals(barrtostr(arr), "192|192|192|192|192|192|192") arr:fill(nil, 3, 233) -assert(barrtostr(arr) == "233|233|233|233|233|233|233") +asserts.equals(barrtostr(arr), "233|233|233|233|233|233|233") arr:fill(2, nil, 254) -assert(barrtostr(arr) == "254|254|254|254|254|254|254") +asserts.equals(barrtostr(arr), "254|254|254|254|254|254|254") arr:fill(2, 2, 66) -assert(barrtostr(arr) == "254|66|66|254|254|254|254") \ No newline at end of file +asserts.equals(barrtostr(arr), "254|66|66|254|254|254|254") From 5422eb7ccc4ce7b8cf69b30c2f58a57f23b0b271 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Aug 2026 19:19:50 +0300 Subject: [PATCH 57/59] add ctypes library --- res/modules/internal/ctypes.lua | 38 +++++++++++++++++++++++++++++++++ res/scripts/stdmin.lua | 2 ++ 2 files changed, 40 insertions(+) create mode 100644 res/modules/internal/ctypes.lua diff --git a/res/modules/internal/ctypes.lua b/res/modules/internal/ctypes.lua new file mode 100644 index 000000000..032ea34b5 --- /dev/null +++ b/res/modules/internal/ctypes.lua @@ -0,0 +1,38 @@ +local FFI = ffi + +FFI.cdef[[ + unsigned long long strtoull(const char* nptr, char** endptr, int base); + long long strtoll(const char* nptr, char** endptr, int base); +]] + +local this = {} + +local function create_integral_type(ctype_name, signed) + local parse_func = signed and FFI.C.strtoll or FFI.C.strtoull + return function(value, base) + value = value or 0 + local typename = type(value) + if typename == "number" then + return FFI.cast(ctype_name, value) + elseif typename == "string" then + if type(base) ~= "number" then + base = 10 + end + return parse_func(value, nil, base) + end + error(string.format("invalid value type: %s", typename)) + end +end + + +this.uint8 = create_integral_type("uint8_t", false) +this.uint16 = create_integral_type("uint16_t", false) +this.uint32 = create_integral_type("uint32_t", false) +this.uint64 = create_integral_type("uint64_t", false) + +this.int8 = create_integral_type("int8_t", true) +this.int16 = create_integral_type("int16_t", true) +this.int32 = create_integral_type("int32_t", true) +this.int64 = create_integral_type("int64_t", true) + +return this diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index 196bf8942..03de8cbaa 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -322,6 +322,8 @@ FLTview = bytearray.FFIFLTview DBLview = bytearray.FFIDBLview Bytearray_construct = function(...) return Bytearray(...) end +ctypes = require "core:internal/ctypes" + bit.compile = require "core:bitwise/compiler" bit.execute = require "core:bitwise/executor" From 40a61307b32c9b2c386346c4f17f415cd1bede93 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Aug 2026 19:54:08 +0300 Subject: [PATCH 58/59] update bytearray test --- dev/tests/bytearray.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev/tests/bytearray.lua b/dev/tests/bytearray.lua index 0d1bdb6ae..71e895d37 100644 --- a/dev/tests/bytearray.lua +++ b/dev/tests/bytearray.lua @@ -38,7 +38,7 @@ asserts.equals(6, arr[3]) -- ============================= arr = Bytearray({254, 255, 255, 255, 255, 255, 255, 255}) --- asserts.equals(U64view(arr)[1], 2 ^ 64 - 2) +asserts.equals(U64view(arr)[1], ctypes.uint64(2) ^ 64 - 2) asserts.equals(I64view(arr)[1], -2) asserts.equals(U32view(arr)[1], 2 ^ 32 - 2) asserts.equals(I32view(arr)[1], -2) From 4a381ec2f4c5638659eaae7422e0b6648d1b8add Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 4 Aug 2026 20:24:58 +0300 Subject: [PATCH 59/59] add ctypes docs (minimal) --- doc/en/scripting.md | 1 + doc/en/scripting/builtins/libctypes.md | 28 ++++++++++++++++++++++++++ doc/ru/scripting.md | 1 + doc/ru/scripting/builtins/libctypes.md | 28 ++++++++++++++++++++++++++ 4 files changed, 58 insertions(+) create mode 100644 doc/en/scripting/builtins/libctypes.md create mode 100644 doc/ru/scripting/builtins/libctypes.md diff --git a/doc/en/scripting.md b/doc/en/scripting.md index 09cf40ef4..2072d4353 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -16,6 +16,7 @@ Subsections: - [block](scripting/builtins/libblock.md) - [byteutil](scripting/builtins/libbyteutil.md) - [cameras](scripting/builtins/libcameras.md) + - [ctypes](scripting/builtins/libctypes.md) - [entities](scripting/builtins/libentities.md) - [file](scripting/builtins/libfile.md) - [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md) diff --git a/doc/en/scripting/builtins/libctypes.md b/doc/en/scripting/builtins/libctypes.md new file mode 100644 index 000000000..5517af0fa --- /dev/null +++ b/doc/en/scripting/builtins/libctypes.md @@ -0,0 +1,28 @@ +# *ctypes* Library + +A library for safe work with C types. + +| ctypes.\ | C-Type | Minimum | Maximum | +| --------------- | -------- | -------------------- | -------------------- | +| uint8 | uint8_t | 0 | 255 | +| uint16 | uint16_t | 0 | 65535 | +| uint32 | uint32_t | 0 | 4294967295 | +| uint64 | uint64_t | 0 | 18446744073709551615 | +| int8 | int8_t | -128 | 127 | +| int16 | int16_t | -32768 | 32767 | +| int32 | int32_t | -2147483648 | 2147483647 | +| int64 | int64_t | -9223372036854775808 | 9223372036854775807 | + + +Usage examples: + +```lua +local x = ctypes.uint64(1234) +print(x) --> 1234ULL (cdata) +local y = ctypes.uint64("18446744073709551615") +print(y) --> 18446744073709551615ULL (cdata) +print(x + y) --> 1233ULL + +local z = tonumber(x) +print(z) --> 1234 (number) +``` diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index db3fcd921..1e6d73098 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -16,6 +16,7 @@ - [block](scripting/builtins/libblock.md) - [byteutil](scripting/builtins/libbyteutil.md) - [cameras](scripting/builtins/libcameras.md) + - [ctypes](scripting/builtins/libctypes.md) - [entities](scripting/builtins/libentities.md) - [file](scripting/builtins/libfile.md) - [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md) diff --git a/doc/ru/scripting/builtins/libctypes.md b/doc/ru/scripting/builtins/libctypes.md new file mode 100644 index 000000000..d6c985b44 --- /dev/null +++ b/doc/ru/scripting/builtins/libctypes.md @@ -0,0 +1,28 @@ +# Библиотека *ctypes* + +Библиотека для безопасной работы с типами языка С. + +| ctypes.\ | С-тип | Минимум | Максимум | +| ------------- | -------- | -------------------- | -------------------- | +| uint8 | uint8_t | 0 | 255 | +| uint16 | uint16_t | 0 | 65535 | +| uint32 | uint32_t | 0 | 4294967295 | +| uint64 | uint64_t | 0 | 18446744073709551615 | +| int8 | int8_t | -128 | 127 | +| int16 | int16_t | -32768 | 32767 | +| int32 | int32_t | -2147483648 | 2147483647 | +| int64 | int64_t | -9223372036854775808 | 9223372036854775807 | + + +Примеры использования: + +```lua +local x = ctypes.uint64(1234) +print(x) --> 1234ULL (cdata) +local y = ctypes.uint64("18446744073709551615") +print(y) --> 18446744073709551615ULL (cdata) +print(x + y) --> 1233ULL + +local z = tonumber(x) +print(z) --> 1234 (number) +```