From 9653b1814393de29202f91f9a94b83a9915bd5c5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 17 Nov 2025 20:18:02 +0300 Subject: [PATCH 01/20] fix: unknown warning option in clang build --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 000f2c3c..226ac8c2 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -112,7 +112,7 @@ target_compile_options( -Wno-sign-compare -Wno-unknown-pragmas > - $<$: + $<$: -Wduplicated-branches -Wduplicated-cond >) From 473f9f1a6a36d65d6ab7d0ce66400e73d198bdfa Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 17 Nov 2025 21:33:44 +0300 Subject: [PATCH 02/20] move block ticks register to separate script --- res/scripts/classes.lua | 67 ----------------------- res/scripts/internal_events.lua | 66 ++++++++++++++++++++++ src/logic/scripting/lua/libs/libblock.cpp | 2 +- src/logic/scripting/scripting.cpp | 1 + src/voxels/blocks_agent.cpp | 56 ++++++++++++------- src/voxels/blocks_agent.hpp | 9 ++- 6 files changed, 109 insertions(+), 92 deletions(-) create mode 100644 res/scripts/internal_events.lua diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 950544bf..9076808c 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -171,73 +171,6 @@ local function clean(iterable, checkFun, ...) end end -local updating_blocks = {} -local TYPE_REGISTER = 0 -local TYPE_UNREGISTER = 1 - -block.__perform_ticks = function(delta) - for id, entry in pairs(updating_blocks) do - entry.timer = entry.timer + delta - local steps = math.floor(entry.timer / entry.delta * #entry / 3) - if steps == 0 then - goto continue - end - entry.timer = 0.0 - local event = entry.event - local tps = entry.tps - for i=1, steps do - local x = entry[entry.pointer + 1] - local y = entry[entry.pointer + 2] - local z = entry[entry.pointer + 3] - entry.pointer = (entry.pointer + 3) % #entry - events.emit(event, x, y, z, tps) - end - ::continue:: - end -end - -block.__process_register_events = function() - local register_events = block.__pull_register_events() - if not register_events then - return - end - for i=1, #register_events, 4 do - local header = register_events[i] - local type = bit.band(header, 0xFFFF) - local id = bit.rshift(header, 16) - local x = register_events[i + 1] - local y = register_events[i + 2] - local z = register_events[i + 3] - - local list = updating_blocks[id] - if type == TYPE_REGISTER then - if not list then - list = {} - list.event = block.name(id) .. ".blocktick" - list.tps = 20 / (block.properties[id]["tick-interval"] or 1) - list.delta = 1.0 / list.tps - list.timer = 0.0 - list.pointer = 0 - updating_blocks[id] = list - end - table.insert(list, x) - table.insert(list, y) - table.insert(list, z) - elseif type == TYPE_UNREGISTER then - if list then - for j=1, #list, 3 do - if list[j] == x and list[j + 1] == y and list[j + 2] == z then - for k=1,3 do - table.remove(list, j) - end - j = j - 3 - end - end - end - end - end -end - network.__process_events = function() local CLIENT_CONNECTED = 1 local CONNECTED_TO_SERVER = 2 diff --git a/res/scripts/internal_events.lua b/res/scripts/internal_events.lua new file mode 100644 index 00000000..06666d04 --- /dev/null +++ b/res/scripts/internal_events.lua @@ -0,0 +1,66 @@ +local updating_blocks = {} +local TYPE_REGISTER = 0 +local TYPE_UNREGISTER = 1 + +block.__perform_ticks = function(delta) + for id, entry in pairs(updating_blocks) do + entry.timer = entry.timer + delta + local steps = math.floor(entry.timer / entry.delta * #entry / 3) + if steps == 0 then + goto continue + end + entry.timer = 0.0 + local event = entry.event + local tps = entry.tps + for i=1, steps do + local x = entry[entry.pointer + 1] + local y = entry[entry.pointer + 2] + local z = entry[entry.pointer + 3] + entry.pointer = (entry.pointer + 3) % #entry + events.emit(event, x, y, z, tps) + end + ::continue:: + end +end + +block.__process_register_events = function() + local register_events = block.__pull_register_events() + if not register_events then + return + end + for i=1, #register_events, 4 do + local header = register_events[i] + local event_bits = bit.band(header, 0xFFFF) + local id = bit.rshift(header, 16) + local x = register_events[i + 1] + local y = register_events[i + 2] + local z = register_events[i + 3] + + local list = updating_blocks[id] + if bit.band(event_bits, TYPE_REGISTER) ~= 0 then + if not list then + list = {} + list.event = block.name(id) .. ".blocktick" + list.tps = 20 / (block.properties[id]["tick-interval"] or 1) + list.delta = 1.0 / list.tps + list.timer = 0.0 + list.pointer = 0 + updating_blocks[id] = list + end + table.insert(list, x) + table.insert(list, y) + table.insert(list, z) + elseif bit.band(event_bits, TYPE_UNREGISTER) ~= 0 then + if list then + for j=1, #list, 3 do + if list[j] == x and list[j + 1] == y and list[j + 2] == z then + for k=1,3 do + table.remove(list, j) + end + j = j - 3 + end + end + end + end + end +end diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index 8ac548e5..fa525eb0 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -730,7 +730,7 @@ static int l_pull_register_events(lua::State* L) { lua::createtable(L, events.size() * 4, 0); for (int i = 0; i < events.size(); i++) { const auto& event = events[i]; - lua::pushinteger(L, static_cast(event.type) | event.id << 16); + lua::pushinteger(L, static_cast(event.bits) | event.id << 16); lua::rawseti(L, i * 4 + 1); for (int j = 0; j < 3; j++) { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 19accc89..07b35d1e 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -72,6 +72,7 @@ void scripting::initialize(Engine* engine) { load_script(io::path("stdlib.lua"), true); load_script(io::path("classes.lua"), true); + load_script(io::path("internal_events.lua"), true); } class LuaCoroutine : public Process { diff --git a/src/voxels/blocks_agent.cpp b/src/voxels/blocks_agent.cpp index d64f7727..e4e17e27 100644 --- a/src/voxels/blocks_agent.cpp +++ b/src/voxels/blocks_agent.cpp @@ -14,39 +14,46 @@ std::vector blocks_agent::pull_register_events() { return events; } +static uint16_t get_events_bits(bool present, const Block& def) { + uint16_t bits = 0; + if (def.rt.funcsset.onblocktick) { + bits |= present ? BlockRegisterEvent::REGISTER_UPDATING_BIT + : BlockRegisterEvent::UNREGISTER_UPDATING_BIT; + } + return bits; +} + static void on_chunk_register_event( const ContentIndices& indices, const Chunk& chunk, - BlockRegisterEvent::Type type + bool present ) { for (int i = 0; i < CHUNK_VOL; i++) { const auto& def = indices.blocks.require(chunk.voxels[i].id); - if (def.rt.funcsset.onblocktick) { - int x = i % CHUNK_W + chunk.x * CHUNK_W; - int z = (i / CHUNK_W) % CHUNK_D + chunk.z * CHUNK_D; - int y = (i / CHUNK_W / CHUNK_D); - block_register_events.push_back(BlockRegisterEvent { - type, def.rt.id, {x, y, z} - }); + uint16_t bits = get_events_bits(present, def); + if (bits == 0) { + continue; } + int x = i % CHUNK_W + chunk.x * CHUNK_W; + int z = (i / CHUNK_W) % CHUNK_D + chunk.z * CHUNK_D; + int y = (i / CHUNK_W / CHUNK_D); + block_register_events.push_back(BlockRegisterEvent { + bits, def.rt.id, {x, y, z} + }); } } void blocks_agent::on_chunk_present( const ContentIndices& indices, const Chunk& chunk ) { - on_chunk_register_event( - indices, chunk, BlockRegisterEvent::Type::REGISTER_UPDATING - ); + on_chunk_register_event(indices, chunk, true); } void blocks_agent::on_chunk_remove( const ContentIndices& indices, const Chunk& chunk ) { - on_chunk_register_event( - indices, chunk, BlockRegisterEvent::Type::UNREGISTER_UPDATING - ); + on_chunk_register_event(indices, chunk, false); } template @@ -101,11 +108,14 @@ static void finalize_block( chunk.flags.blocksData = true; } } - if (def.rt.funcsset.onblocktick) { - block_register_events.push_back(BlockRegisterEvent { - BlockRegisterEvent::Type::UNREGISTER_UPDATING, def.rt.id, {x, y, z} - }); + + uint16_t bits = get_events_bits(false, def); + if (bits == 0) { + return; } + block_register_events.push_back(BlockRegisterEvent { + bits, def.rt.id, {x, y, z} + }); } template @@ -131,9 +141,17 @@ static void initialize_block( refresh_chunk_heights(chunk, id == BLOCK_AIR, y); mark_neighboirs_modified(chunks, cx, cz, lx, lz); + uint16_t bits = get_events_bits(true, def); + if (bits == 0) { + return; + } + block_register_events.push_back(BlockRegisterEvent { + bits, def.rt.id, {x, y, z} + }); + if (def.rt.funcsset.onblocktick) { block_register_events.push_back(BlockRegisterEvent { - BlockRegisterEvent::Type::REGISTER_UPDATING, def.rt.id, {x, y, z} + bits, def.rt.id, {x, y, z} }); } } diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp index ab798133..b5ca82a6 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -25,11 +25,10 @@ struct AABB; namespace blocks_agent { struct BlockRegisterEvent { - enum class Type : uint16_t { - REGISTER_UPDATING, - UNREGISTER_UPDATING, - }; - Type type; + static inline constexpr uint16_t REGISTER_UPDATING_BIT = 0x1; + static inline constexpr uint16_t UNREGISTER_UPDATING_BIT = 0x2; + static inline constexpr uint16_t PRESENT_EVENT_BIT = 0x4; + uint16_t bits; blockid_t id; glm::ivec3 coord; }; From 1c81f8b7ad6901f2bbc1ef37af990290f2ddc905 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 17 Nov 2025 22:14:34 +0300 Subject: [PATCH 03/20] fix warning --- src/frontend/debug_panel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index c42b2930..32439b65 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -87,7 +87,7 @@ std::shared_ptr create_debug_panel( fpsMax = fps; }); - panel->listenInterval(1.0f, [&engine, &gui]() { + panel->listenInterval(1.0f, [&engine]() { const auto& network = engine.getNetwork(); size_t totalDownload = network.getTotalDownload(); size_t totalUpload = network.getTotalUpload(); From ca3bc45d0f70abe4baf1e9187a08fe053b26008c Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 17 Nov 2025 23:50:52 +0300 Subject: [PATCH 04/20] add 'on_block_present' event --- res/scripts/internal_events.lua | 105 ++++++++++++++++++++++++------ src/logic/ChunksController.cpp | 4 +- src/logic/scripting/scripting.cpp | 2 + src/voxels/Block.hpp | 1 + src/voxels/GlobalChunks.cpp | 2 - src/voxels/blocks_agent.cpp | 32 +++++---- src/voxels/blocks_agent.hpp | 8 +-- 7 files changed, 113 insertions(+), 41 deletions(-) diff --git a/res/scripts/internal_events.lua b/res/scripts/internal_events.lua index 06666d04..4f72e1b1 100644 --- a/res/scripts/internal_events.lua +++ b/res/scripts/internal_events.lua @@ -1,6 +1,8 @@ local updating_blocks = {} -local TYPE_REGISTER = 0 -local TYPE_UNREGISTER = 1 +local present_queues = {} +local TYPE_REGISTER = 1 +local TYPE_UPDATING = 2 +local TYPE_PRESENT = 4 block.__perform_ticks = function(delta) for id, entry in pairs(updating_blocks) do @@ -21,10 +23,45 @@ block.__perform_ticks = function(delta) end ::continue:: end + for id, queue in pairs(present_queues) do + queue.timer = queue.timer + delta + local steps = math.floor(queue.timer / queue.delta * #queue / 4) + if steps == 0 then + goto continue + end + queue.timer = 0.0 + local event = queue.event + local update_list = updating_blocks[id] + for i=1, steps do + local index = #queue - 3 + if index <= 0 then + break + end + local is_register = queue[index] + local x = queue[index + 1] + local y = queue[index + 2] + local z = queue[index + 3] + + for j=1,4 do + table.remove(queue, index) + end + events.emit(event, x, y, z) + + if queue.updating then + table.insert(update_list, x) + table.insert(update_list, y) + table.insert(update_list, z) + end + end + ::continue:: + end end +local block_pull_register_events = block.__pull_register_events +block.__pull_register_events = nil + block.__process_register_events = function() - local register_events = block.__pull_register_events() + local register_events = block_pull_register_events() if not register_events then return end @@ -36,31 +73,59 @@ block.__process_register_events = function() local y = register_events[i + 2] local z = register_events[i + 3] + local is_register = bit.band(event_bits, TYPE_REGISTER) ~= 0 + local is_updating = bit.band(event_bits, TYPE_UPDATING) ~= 0 + local is_present = bit.band(event_bits, TYPE_PRESENT) ~= 0 local list = updating_blocks[id] - if bit.band(event_bits, TYPE_REGISTER) ~= 0 then - if not list then - list = {} - list.event = block.name(id) .. ".blocktick" - list.tps = 20 / (block.properties[id]["tick-interval"] or 1) - list.delta = 1.0 / list.tps - list.timer = 0.0 - list.pointer = 0 - updating_blocks[id] = list + + if not list and is_register and is_updating then + list = {} + list.event = block.name(id) .. ".blocktick" + list.tps = 20 / (block.properties[id]["tick-interval"] or 1) + list.delta = 1.0 / list.tps + list.timer = 0.0 + list.pointer = 0 + updating_blocks[id] = list + end + + if is_register and is_present then + local present_queue = present_queues[id] + if not present_queue then + present_queue = {} + present_queue.event = block.name(id) .. ".blockpresent" + present_queue.tps = 20 / (block.properties[id]["tick-interval"] or 1) + present_queue.delta = 1.0 / present_queue.tps + present_queue.timer = 0.0 + present_queue.pointer = 0 + present_queue.updating = is_updating + present_queues[id] = present_queue end + table.insert(present_queue, is_register) + table.insert(present_queue, x) + table.insert(present_queue, y) + table.insert(present_queue, z) + goto continue + end + if not is_updating then + goto continue + end + if is_register then table.insert(list, x) table.insert(list, y) table.insert(list, z) - elseif bit.band(event_bits, TYPE_UNREGISTER) ~= 0 then - if list then - for j=1, #list, 3 do - if list[j] == x and list[j + 1] == y and list[j + 2] == z then - for k=1,3 do - table.remove(list, j) - end - j = j - 3 + else + if not list then + goto continue + end + for j=1, #list, 3 do + if list[j] == x and list[j + 1] == y and list[j + 2] == z then + for k=1,3 do + table.remove(list, j) end + j = j - 3 end end end + ::continue:: end end diff --git a/src/logic/ChunksController.cpp b/src/logic/ChunksController.cpp index 97bbdf99..b521b0d8 100644 --- a/src/logic/ChunksController.cpp +++ b/src/logic/ChunksController.cpp @@ -15,6 +15,7 @@ #include "voxels/Chunks.hpp" #include "voxels/GlobalChunks.hpp" #include "world/Level.hpp" +#include "world/LevelEvents.hpp" #include "world/World.hpp" #include "world/generator/WorldGenerator.hpp" @@ -172,13 +173,12 @@ void ChunksController::createChunk(const Player& player, int x, int z) const { auto chunk = level.chunks->create(x, z); player.chunks->putChunk(chunk); auto& chunkFlags = chunk->flags; - if (!chunkFlags.loaded) { generator->generate(chunk->voxels, x, z); chunkFlags.unsaved = true; } chunk->updateHeights(); - + level.events->trigger(LevelEventType::CHUNK_PRESENT, chunk.get()); if (!chunkFlags.loadedLights) { Lighting::prebuildSkyLight(*chunk, *level.content.getIndices()); } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 07b35d1e..fb2e997b 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -675,6 +675,8 @@ void scripting::load_content_script( register_event(env, "on_block_tick", prefix + ".blocktick"); funcsset.onblockstick = register_event(env, "on_blocks_tick", prefix + ".blockstick"); + funcsset.onblockpresent = + register_event(env, "on_block_present", prefix + ".blockpresent"); } void scripting::load_content_script( diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 59115e84..9d091893 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -50,6 +50,7 @@ struct BlockFuncsSet { bool randupdate : 1; bool onblocktick : 1; bool onblockstick : 1; + bool onblockpresent : 1; }; struct CoordSystem { diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 7451a563..66da7792 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -127,8 +127,6 @@ std::shared_ptr GlobalChunks::create(int x, int z) { chunk->flags.loadedLights = true; } chunk->blocksMetadata = regions.getBlocksData(chunk->x, chunk->z); - - level.events->trigger(LevelEventType::CHUNK_PRESENT, chunk.get()); return chunk; } diff --git a/src/voxels/blocks_agent.cpp b/src/voxels/blocks_agent.cpp index e4e17e27..fa71aad9 100644 --- a/src/voxels/blocks_agent.cpp +++ b/src/voxels/blocks_agent.cpp @@ -14,12 +14,12 @@ std::vector blocks_agent::pull_register_events() { return events; } -static uint16_t get_events_bits(bool present, const Block& def) { - uint16_t bits = 0; - if (def.rt.funcsset.onblocktick) { - bits |= present ? BlockRegisterEvent::REGISTER_UPDATING_BIT - : BlockRegisterEvent::UNREGISTER_UPDATING_BIT; - } +static uint8_t get_events_bits(bool reg, const Block& def) { + uint8_t bits = 0; + auto funcsset = def.rt.funcsset; + bits |= BlockRegisterEvent::REGISTER_BIT * reg; + bits |= BlockRegisterEvent::UPDATING_BIT * funcsset.onblocktick; + bits |= BlockRegisterEvent::PRESENT_EVENT_BIT * funcsset.onblockpresent; return bits; } @@ -28,10 +28,16 @@ static void on_chunk_register_event( const Chunk& chunk, bool present ) { - for (int i = 0; i < CHUNK_VOL; i++) { - const auto& def = - indices.blocks.require(chunk.voxels[i].id); - uint16_t bits = get_events_bits(present, def); + const auto& voxels = chunk.voxels; + + int totalBegin = chunk.bottom * (CHUNK_W * CHUNK_D); + int totalEnd = chunk.top * (CHUNK_W * CHUNK_D); + + for (int i = totalBegin; i <= totalEnd; i++) { + blockid_t id = voxels[i].id; + const auto& def = + indices.blocks.require(id); + uint8_t bits = get_events_bits(present, def); if (bits == 0) { continue; } @@ -39,7 +45,7 @@ static void on_chunk_register_event( int z = (i / CHUNK_W) % CHUNK_D + chunk.z * CHUNK_D; int y = (i / CHUNK_W / CHUNK_D); block_register_events.push_back(BlockRegisterEvent { - bits, def.rt.id, {x, y, z} + bits, id, {x, y, z} }); } } @@ -109,7 +115,7 @@ static void finalize_block( } } - uint16_t bits = get_events_bits(false, def); + uint8_t bits = get_events_bits(false, def); if (bits == 0) { return; } @@ -141,7 +147,7 @@ static void initialize_block( refresh_chunk_heights(chunk, id == BLOCK_AIR, y); mark_neighboirs_modified(chunks, cx, cz, lx, lz); - uint16_t bits = get_events_bits(true, def); + uint8_t bits = get_events_bits(true, def); if (bits == 0) { return; } diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp index b5ca82a6..93a3c1ad 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -25,10 +25,10 @@ struct AABB; namespace blocks_agent { struct BlockRegisterEvent { - static inline constexpr uint16_t REGISTER_UPDATING_BIT = 0x1; - static inline constexpr uint16_t UNREGISTER_UPDATING_BIT = 0x2; - static inline constexpr uint16_t PRESENT_EVENT_BIT = 0x4; - uint16_t bits; + static inline constexpr uint8_t REGISTER_BIT = 0x1; + static inline constexpr uint8_t UPDATING_BIT = 0x2; + static inline constexpr uint8_t PRESENT_EVENT_BIT = 0x4; + uint8_t bits; blockid_t id; glm::ivec3 coord; }; From 957f9f59983790583fb57c9e9a3661631f380153 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 00:14:53 +0300 Subject: [PATCH 05/20] fix major chunks loading performance issue --- src/voxels/blocks_agent.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/voxels/blocks_agent.cpp b/src/voxels/blocks_agent.cpp index fa71aad9..338c3cb8 100644 --- a/src/voxels/blocks_agent.cpp +++ b/src/voxels/blocks_agent.cpp @@ -14,10 +14,9 @@ std::vector blocks_agent::pull_register_events() { return events; } -static uint8_t get_events_bits(bool reg, const Block& def) { +static uint8_t get_events_bits(const Block& def) { uint8_t bits = 0; auto funcsset = def.rt.funcsset; - bits |= BlockRegisterEvent::REGISTER_BIT * reg; bits |= BlockRegisterEvent::UPDATING_BIT * funcsset.onblocktick; bits |= BlockRegisterEvent::PRESENT_EVENT_BIT * funcsset.onblockpresent; return bits; @@ -33,11 +32,17 @@ static void on_chunk_register_event( int totalBegin = chunk.bottom * (CHUNK_W * CHUNK_D); int totalEnd = chunk.top * (CHUNK_W * CHUNK_D); + uint8_t flagsCache[1024] {}; + for (int i = totalBegin; i <= totalEnd; i++) { blockid_t id = voxels[i].id; - const auto& def = - indices.blocks.require(id); - uint8_t bits = get_events_bits(present, def); + uint8_t bits = id < sizeof(flagsCache) ? flagsCache[id] : 0; + if ((bits & 0x80) == 0) { + const auto& def = indices.blocks.require(id); + bits = get_events_bits(def); + flagsCache[id] = bits | 0x80; + } + bits &= 0x7F; if (bits == 0) { continue; } @@ -45,7 +50,7 @@ static void on_chunk_register_event( int z = (i / CHUNK_W) % CHUNK_D + chunk.z * CHUNK_D; int y = (i / CHUNK_W / CHUNK_D); block_register_events.push_back(BlockRegisterEvent { - bits, id, {x, y, z} + static_cast(bits | (present ? 1 : 0)), id, {x, y, z} }); } } @@ -115,7 +120,7 @@ static void finalize_block( } } - uint8_t bits = get_events_bits(false, def); + uint8_t bits = get_events_bits(def); if (bits == 0) { return; } @@ -147,12 +152,12 @@ static void initialize_block( refresh_chunk_heights(chunk, id == BLOCK_AIR, y); mark_neighboirs_modified(chunks, cx, cz, lx, lz); - uint8_t bits = get_events_bits(true, def); + uint8_t bits = get_events_bits(def); if (bits == 0) { return; } block_register_events.push_back(BlockRegisterEvent { - bits, def.rt.id, {x, y, z} + static_cast(bits | 1), def.rt.id, {x, y, z} }); if (def.rt.funcsset.onblocktick) { From 922a86d2b0b4a5f077d3b99010d9162c8aea44d9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 18:41:21 +0300 Subject: [PATCH 06/20] add that thing --- src/logic/ChunksController.cpp | 4 ++++ src/voxels/GlobalChunks.cpp | 8 +++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/logic/ChunksController.cpp b/src/logic/ChunksController.cpp index b521b0d8..692d8705 100644 --- a/src/logic/ChunksController.cpp +++ b/src/logic/ChunksController.cpp @@ -163,7 +163,11 @@ bool ChunksController::buildLights( return false; } +#include "util/timeutil.hpp" + void ChunksController::createChunk(const Player& player, int x, int z) const { + timeutil::ScopeLogTimer log(111); + if (!player.isLoadingChunks()) { if (auto chunk = level.chunks->fetch(x, z)) { player.chunks->putChunk(chunk); diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 66da7792..830e13d6 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -89,13 +89,19 @@ static inline auto load_inventories( return invs; } +#include "util/timeutil.hpp" + std::shared_ptr GlobalChunks::create(int x, int z) { const auto& found = chunksMap.find(keyfrom(x, z)); if (found != chunksMap.end()) { return found->second; } - auto chunk = std::make_shared(x, z); + std::shared_ptr chunk; + { + timeutil::ScopeLogTimer log(555); + chunk = std::make_shared(x, z); + } chunksMap[keyfrom(x, z)] = chunk; World& world = *level.getWorld(); From 5fdb71c47bfb2daef6a15c15692d15a528bf17a9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 19:47:35 +0300 Subject: [PATCH 07/20] add ObjectsPool --- src/util/BufferPool.hpp | 2 -- src/util/ObjectsPool.hpp | 51 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 src/util/ObjectsPool.hpp diff --git a/src/util/BufferPool.hpp b/src/util/BufferPool.hpp index aeeebb8c..c3a1112d 100644 --- a/src/util/BufferPool.hpp +++ b/src/util/BufferPool.hpp @@ -5,8 +5,6 @@ #include #include -#include "typedefs.hpp" - namespace util { /// @brief Thread-safe pool of same-sized buffers /// @tparam T array type diff --git a/src/util/ObjectsPool.hpp b/src/util/ObjectsPool.hpp new file mode 100644 index 00000000..de3e30ae --- /dev/null +++ b/src/util/ObjectsPool.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +namespace util { + struct AlignedDeleter { + void operator()(void* p) const { + std::free(p); + } + }; + + template + class ObjectsPool { + public: + ObjectsPool(size_t preallocated = 0) { + for (size_t i = 0; i < preallocated; i++) { + allocateNew(); + } + } + + template + std::shared_ptr create(Args&&... args) { + std::lock_guard lock(mutex); + if (freeObjects.empty()) { + allocateNew(); + } + auto ptr = freeObjects.front(); + freeObjects.pop(); + new (ptr)T(std::forward(args)...); + return std::shared_ptr(reinterpret_cast(ptr), [this](T* ptr) { + std::lock_guard lock(mutex); + freeObjects.push(ptr); + }); + } + private: + std::vector> objects; + std::queue freeObjects; + std::mutex mutex; + + void allocateNew() { + std::unique_ptr ptr( + std::aligned_alloc(alignof(T), sizeof(T)) + ); + freeObjects.push(ptr.get()); + objects.push_back(std::move(ptr)); + } + }; +} From 5317106d812c400d4f4b1d71e3228d88bbdc81a1 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 19:48:00 +0300 Subject: [PATCH 08/20] test chunks pool --- src/voxels/GlobalChunks.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 830e13d6..58734dc6 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -90,6 +90,9 @@ static inline auto load_inventories( } #include "util/timeutil.hpp" +#include "util/ObjectsPool.hpp" + +static util::ObjectsPool chunks_pool(1'024); std::shared_ptr GlobalChunks::create(int x, int z) { const auto& found = chunksMap.find(keyfrom(x, z)); @@ -100,7 +103,8 @@ std::shared_ptr GlobalChunks::create(int x, int z) { std::shared_ptr chunk; { timeutil::ScopeLogTimer log(555); - chunk = std::make_shared(x, z); + // chunk = std::make_shared(x, z); + chunk = chunks_pool.create(x, z); } chunksMap[keyfrom(x, z)] = chunk; From 8e66d78ef0f314fab1b650e2f8c510ab7c21bcf0 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 20:08:11 +0300 Subject: [PATCH 09/20] fix msvc build --- src/util/ObjectsPool.hpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/util/ObjectsPool.hpp b/src/util/ObjectsPool.hpp index de3e30ae..35b3753b 100644 --- a/src/util/ObjectsPool.hpp +++ b/src/util/ObjectsPool.hpp @@ -4,11 +4,16 @@ #include #include #include +#include namespace util { struct AlignedDeleter { void operator()(void* p) const { +#if defined(_WIN32) + _aligned_free(p); +#else std::free(p); +#endif } }; @@ -42,7 +47,11 @@ namespace util { void allocateNew() { std::unique_ptr ptr( +#if defined(_WIN32) + _aligned_malloc(sizeof(T), alignof(T)); +#else std::aligned_alloc(alignof(T), sizeof(T)) +#endif ); freeObjects.push(ptr.get()); objects.push_back(std::move(ptr)); From 263c0e3b570a3510736122b7f1de2e8928f956f6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 20:14:37 +0300 Subject: [PATCH 10/20] fix macos build --- src/util/ObjectsPool.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/util/ObjectsPool.hpp b/src/util/ObjectsPool.hpp index 35b3753b..bcd3294c 100644 --- a/src/util/ObjectsPool.hpp +++ b/src/util/ObjectsPool.hpp @@ -4,7 +4,10 @@ #include #include #include + +#if defined(_WIN32) #include +#endif namespace util { struct AlignedDeleter { From d3e73be3fe3bb72ed14840571bc3a5fb611ab67d Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 20:24:18 +0300 Subject: [PATCH 11/20] fix windows build again --- src/util/ObjectsPool.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util/ObjectsPool.hpp b/src/util/ObjectsPool.hpp index bcd3294c..0dc97fd2 100644 --- a/src/util/ObjectsPool.hpp +++ b/src/util/ObjectsPool.hpp @@ -51,7 +51,7 @@ namespace util { void allocateNew() { std::unique_ptr ptr( #if defined(_WIN32) - _aligned_malloc(sizeof(T), alignof(T)); + _aligned_malloc(sizeof(T), alignof(T)) #else std::aligned_alloc(alignof(T), sizeof(T)) #endif From 1a6880a572eb8f5b3df768c0ad892c139f0542ad Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 20:46:24 +0300 Subject: [PATCH 12/20] remove that thing --- src/logic/ChunksController.cpp | 4 ---- src/voxels/GlobalChunks.cpp | 23 ++++++++--------------- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/logic/ChunksController.cpp b/src/logic/ChunksController.cpp index 692d8705..b521b0d8 100644 --- a/src/logic/ChunksController.cpp +++ b/src/logic/ChunksController.cpp @@ -163,11 +163,7 @@ bool ChunksController::buildLights( return false; } -#include "util/timeutil.hpp" - void ChunksController::createChunk(const Player& player, int x, int z) const { - timeutil::ScopeLogTimer log(111); - if (!player.isLoadingChunks()) { if (auto chunk = level.chunks->fetch(x, z)) { player.chunks->putChunk(chunk); diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 58734dc6..438ae6d8 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -2,22 +2,23 @@ #include -#include "content/Content.hpp" +#include "Block.hpp" +#include "Chunk.hpp" #include "coders/json.hpp" +#include "content/Content.hpp" #include "debug/Logger.hpp" -#include "world/files/WorldFiles.hpp" #include "items/Inventories.hpp" #include "lighting/Lightmap.hpp" #include "maths/voxmaths.hpp" #include "objects/Entities.hpp" #include "objects/Entity.hpp" -#include "voxels/blocks_agent.hpp" #include "typedefs.hpp" -#include "world/LevelEvents.hpp" +#include "util/ObjectsPool.hpp" +#include "voxels/blocks_agent.hpp" +#include "world/files/WorldFiles.hpp" #include "world/Level.hpp" +#include "world/LevelEvents.hpp" #include "world/World.hpp" -#include "Block.hpp" -#include "Chunk.hpp" static debug::Logger logger("chunks-storage"); @@ -89,9 +90,6 @@ static inline auto load_inventories( return invs; } -#include "util/timeutil.hpp" -#include "util/ObjectsPool.hpp" - static util::ObjectsPool chunks_pool(1'024); std::shared_ptr GlobalChunks::create(int x, int z) { @@ -100,12 +98,7 @@ std::shared_ptr GlobalChunks::create(int x, int z) { return found->second; } - std::shared_ptr chunk; - { - timeutil::ScopeLogTimer log(555); - // chunk = std::make_shared(x, z); - chunk = chunks_pool.create(x, z); - } + auto chunk = chunks_pool.create(x, z); chunksMap[keyfrom(x, z)] = chunk; World& world = *level.getWorld(); From b3e5f2a1c01de2f7bda4f864c1d19ce13914d4d5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 20:58:12 +0300 Subject: [PATCH 13/20] update doc/*/scripting/events.md --- doc/en/scripting/events.md | 9 +++++++++ doc/ru/scripting/events.md | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index fce3abfb..ec66b9f9 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -53,6 +53,15 @@ function on_block_tick(x, y, z, tps: number) Called tps (20 / tick-interval) times per second for a block. Use 1/tps instead of `time.delta()`. +```lua +function on_block_present(x, y, z) +``` + +Called for a specific block when it appears in the world (generated/loaded/placed). +The call occurs within a time period that may depend on the event queue load. +Under light load, it occurs during the first tick interval of the block. +on_block_tick is not called until the event is called. + ```lua function on_player_tick(playerid: int, tps: int) ``` diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 73a6bc0c..311e5a32 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -53,6 +53,15 @@ function on_block_tick(x, y, z, tps: number) Вызывается tps (20 / tick-interval) раз в секунду для конкретного блока. Используйте 1/tps вместо `time.delta()`. +```lua +function on_block_present(x, y, z) +``` + +Вызывается для конкретного блока при появлении (генерации/загрузке/размещении). +Вызов происходит в течение времени, которое может зависеть от нагрузки очереди событий. +При малой нагрузке происходит в течение первого такта блока (tick-interval). +До вызова события on_block_tick не вызывается. + ```lua function on_player_tick(playerid: int, tps: int) ``` From f721731ecc57fe71989187ed64a08ef7051aa1e7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 22:11:44 +0300 Subject: [PATCH 14/20] add 'on_block_removed' event --- res/scripts/internal_events.lua | 41 ++++++++++++++++-------- res/scripts/stdlib.lua | 4 +++ src/frontend/screens/LevelScreen.cpp | 2 ++ src/logic/EngineController.cpp | 4 ++- src/logic/LevelController.cpp | 10 +++++- src/logic/LevelController.hpp | 2 ++ src/logic/scripting/lua/libs/libcore.cpp | 1 + src/logic/scripting/scripting.cpp | 9 ++++++ src/logic/scripting/scripting.hpp | 1 + src/voxels/Block.hpp | 1 + src/voxels/blocks_agent.cpp | 1 + src/voxels/blocks_agent.hpp | 1 + 12 files changed, 61 insertions(+), 16 deletions(-) diff --git a/res/scripts/internal_events.lua b/res/scripts/internal_events.lua index 4f72e1b1..ec8e8dd6 100644 --- a/res/scripts/internal_events.lua +++ b/res/scripts/internal_events.lua @@ -1,8 +1,9 @@ local updating_blocks = {} local present_queues = {} -local TYPE_REGISTER = 1 -local TYPE_UPDATING = 2 -local TYPE_PRESENT = 4 +local REGISTER_BIT = 0x1 +local UPDATING_BIT = 0x2 +local PRESENT_BIT = 0x4 +local REMOVED_BIT = 0x8 block.__perform_ticks = function(delta) for id, entry in pairs(updating_blocks) do @@ -25,7 +26,7 @@ block.__perform_ticks = function(delta) end for id, queue in pairs(present_queues) do queue.timer = queue.timer + delta - local steps = math.floor(queue.timer / queue.delta * #queue / 4) + local steps = math.floor(queue.timer / queue.delta * #queue / 3) if steps == 0 then goto continue end @@ -33,16 +34,15 @@ block.__perform_ticks = function(delta) local event = queue.event local update_list = updating_blocks[id] for i=1, steps do - local index = #queue - 3 + local index = #queue - 2 if index <= 0 then break end - local is_register = queue[index] - local x = queue[index + 1] - local y = queue[index + 2] - local z = queue[index + 3] + local x = queue[index] + local y = queue[index + 1] + local z = queue[index + 2] - for j=1,4 do + for j=1,3 do table.remove(queue, index) end events.emit(event, x, y, z) @@ -65,6 +65,10 @@ block.__process_register_events = function() if not register_events then return end + + local emit_event = events.emit + local removed_events = {} + for i=1, #register_events, 4 do local header = register_events[i] local event_bits = bit.band(header, 0xFFFF) @@ -73,11 +77,21 @@ block.__process_register_events = function() local y = register_events[i + 2] local z = register_events[i + 3] - local is_register = bit.band(event_bits, TYPE_REGISTER) ~= 0 - local is_updating = bit.band(event_bits, TYPE_UPDATING) ~= 0 - local is_present = bit.band(event_bits, TYPE_PRESENT) ~= 0 + local is_register = bit.band(event_bits, REGISTER_BIT) ~= 0 + local is_updating = bit.band(event_bits, UPDATING_BIT) ~= 0 + local is_present = bit.band(event_bits, PRESENT_BIT) ~= 0 + local is_removed = bit.band(event_bits, REMOVED_BIT) ~= 0 local list = updating_blocks[id] + if not is_register and is_removed then + local rm_event = removed_events[id] + if not rm_event then + rm_event = block.name(id) .. ".blockremoved" + removed_events[id] = rm_event + end + emit_event(rm_event, x, y, z) + end + if not list and is_register and is_updating then list = {} list.event = block.name(id) .. ".blocktick" @@ -100,7 +114,6 @@ block.__process_register_events = function() present_queue.updating = is_updating present_queues[id] = present_queue end - table.insert(present_queue, is_register) table.insert(present_queue, x) table.insert(present_queue, y) table.insert(present_queue, z) diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index a9199132..8624a844 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -357,6 +357,10 @@ function __vc_on_world_tick(tps) time.schedules.world:tick(1.0 / tps) end +function __vc_process_before_quit() + block.__process_register_events() +end + function __vc_on_world_save() local rule_values = {} for name, rule in pairs(rules.rules) do diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index eab517da..10348734 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -109,6 +109,7 @@ LevelScreen::~LevelScreen() { scripting::on_frontend_close(); // unblock all bindings input.getBindings().enableAll(); + playerController->getPlayer()->chunks->saveAndClear(); controller->onWorldQuit(); engine.getPaths().setCurrentWorldFolder(""); } @@ -278,5 +279,6 @@ void LevelScreen::onEngineShutdown() { if (hud->isInventoryOpen()) { hud->closeInventory(); } + controller->processBeforeQuit(); controller->saveWorld(); } diff --git a/src/logic/EngineController.cpp b/src/logic/EngineController.cpp index d6820fa1..db956e15 100644 --- a/src/logic/EngineController.cpp +++ b/src/logic/EngineController.cpp @@ -366,7 +366,9 @@ void EngineController::reconfigPacks( ); } } else { - auto world = controller->getLevel()->getWorld(); + auto level = controller->getLevel(); + auto world = level->getWorld(); + controller->processBeforeQuit(); controller->saveWorld(); auto names = PacksManager::getNames(world->getPacks()); diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index 95d91014..11717d9a 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -27,7 +27,8 @@ LevelController::LevelController( : settings(engine->getSettings()), level(std::move(levelPtr)), chunks(std::make_unique(*level)), - playerTickClock(20, 3) { + playerTickClock(20, 3), + localPlayer(clientPlayer) { level->events->listen(LevelEventType::CHUNK_PRESENT, [](auto, Chunk* chunk) { scripting::on_chunk_present(*chunk, chunk->flags.loaded); @@ -121,6 +122,13 @@ void LevelController::update(float delta, bool pause) { level->entities->clean(); } +void LevelController::processBeforeQuit() { + if (localPlayer) { + localPlayer->chunks->saveAndClear(); + } + scripting::process_before_quit(); +} + void LevelController::saveWorld() { auto world = level->getWorld(); if (world->isNameless()) { diff --git a/src/logic/LevelController.hpp b/src/logic/LevelController.hpp index 7ae0fc5b..c7b98623 100644 --- a/src/logic/LevelController.hpp +++ b/src/logic/LevelController.hpp @@ -20,6 +20,7 @@ class LevelController { std::unique_ptr chunks; util::Clock playerTickClock; + Player* localPlayer; public: LevelController(Engine* engine, std::unique_ptr level, Player* clientPlayer); @@ -27,6 +28,7 @@ public: /// @param pause is world and player simulation paused void update(float delta, bool pause); + void processBeforeQuit(); void saveWorld(); void onWorldQuit(); diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 8f8a7bc8..8454e5c3 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -119,6 +119,7 @@ static int l_close_world(lua::State* L) { if (controller == nullptr) { throw std::runtime_error("no world open"); } + controller->processBeforeQuit(); bool save_world = lua::toboolean(L, 1); if (save_world) { controller->saveWorld(); diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index fb2e997b..3a68917d 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -341,6 +341,13 @@ void scripting::on_world_save() { } } +void scripting::process_before_quit() { + auto L = lua::get_main_state(); + if (lua::getglobal(L, "__vc_process_before_quit")) { + lua::call_nothrow(L, 0, 0); + } +} + void scripting::on_world_quit() { auto L = lua::get_main_state(); for (auto& pack : content_control->getAllContentPacks()) { @@ -677,6 +684,8 @@ void scripting::load_content_script( register_event(env, "on_blocks_tick", prefix + ".blockstick"); funcsset.onblockpresent = register_event(env, "on_block_present", prefix + ".blockpresent"); + funcsset.onblockremoved = + register_event(env, "on_block_removed", prefix + ".blockremoved"); } void scripting::load_content_script( diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index a096e9cc..966450c6 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -81,6 +81,7 @@ namespace scripting { void on_world_load(LevelController* controller); void on_world_tick(int tps); void on_world_save(); + void process_before_quit(); void on_world_quit(); void cleanup(const std::vector& nonReset); void on_blocks_tick(const Block& block, int tps); diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 9d091893..29eda249 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -51,6 +51,7 @@ struct BlockFuncsSet { bool onblocktick : 1; bool onblockstick : 1; bool onblockpresent : 1; + bool onblockremoved : 1; }; struct CoordSystem { diff --git a/src/voxels/blocks_agent.cpp b/src/voxels/blocks_agent.cpp index 338c3cb8..7041f67a 100644 --- a/src/voxels/blocks_agent.cpp +++ b/src/voxels/blocks_agent.cpp @@ -19,6 +19,7 @@ static uint8_t get_events_bits(const Block& def) { auto funcsset = def.rt.funcsset; bits |= BlockRegisterEvent::UPDATING_BIT * funcsset.onblocktick; bits |= BlockRegisterEvent::PRESENT_EVENT_BIT * funcsset.onblockpresent; + bits |= BlockRegisterEvent::REMOVED_EVENT_BIT * funcsset.onblockremoved; return bits; } diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp index 93a3c1ad..7c0223cb 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -28,6 +28,7 @@ struct BlockRegisterEvent { static inline constexpr uint8_t REGISTER_BIT = 0x1; static inline constexpr uint8_t UPDATING_BIT = 0x2; static inline constexpr uint8_t PRESENT_EVENT_BIT = 0x4; + static inline constexpr uint8_t REMOVED_EVENT_BIT = 0x8; uint8_t bits; blockid_t id; glm::ivec3 coord; From b1312c354d8af44c51b5c7b4eba2adf0168ca5a6 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 22:18:56 +0300 Subject: [PATCH 15/20] update doc/*/scripting/events.md --- doc/en/scripting/events.md | 10 ++++++++++ doc/ru/scripting/events.md | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index ec66b9f9..fc8bb27f 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -4,6 +4,10 @@ Callbacks specified in block script. +> [!WARNING] +> events such as on_block_tick, on_block_present, and on_block_removed +> can cause performance issues if used carelessly or excessively. + ```lua function on_placed(x, y, z, playerid) ``` @@ -62,6 +66,12 @@ The call occurs within a time period that may depend on the event queue load. Under light load, it occurs during the first tick interval of the block. on_block_tick is not called until the event is called. +```lua +function on_block_removed(x, y, z) +``` + +Called when chunk containing the block unloads. + ```lua function on_player_tick(playerid: int, tps: int) ``` diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 311e5a32..83baef25 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -4,6 +4,10 @@ Функции для обработки событий, прописываемые в скрипте блока. +> [!WARNING] +> Mass events such as on_block_tick, on_block_present, and on_block_removed, +> if used carelessly or excessively, can lead to performance issues. + ```lua function on_placed(x, y, z, playerid) ``` @@ -62,6 +66,12 @@ function on_block_present(x, y, z) При малой нагрузке происходит в течение первого такта блока (tick-interval). До вызова события on_block_tick не вызывается. +```lua +function on_block_removed(x, y, z) +``` + +Вызывается при выгрузке чанка, в котором находится блок. + ```lua function on_player_tick(playerid: int, tps: int) ``` From c951d787b797cfdefbad135c20f04b1fca586d25 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 18 Nov 2025 22:25:04 +0300 Subject: [PATCH 16/20] fix: ObjectsPool missing destructor call --- src/util/ObjectsPool.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/util/ObjectsPool.hpp b/src/util/ObjectsPool.hpp index 0dc97fd2..55238959 100644 --- a/src/util/ObjectsPool.hpp +++ b/src/util/ObjectsPool.hpp @@ -39,6 +39,7 @@ namespace util { freeObjects.pop(); new (ptr)T(std::forward(args)...); return std::shared_ptr(reinterpret_cast(ptr), [this](T* ptr) { + ptr->~T(); std::lock_guard lock(mutex); freeObjects.push(ptr); }); From d714e6943ad32ecce55ffa136fea36f56b0c0870 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 19 Nov 2025 00:34:16 +0300 Subject: [PATCH 17/20] add canvas:encode method --- src/coders/imageio.cpp | 11 ++++ src/coders/imageio.hpp | 11 ++++ src/coders/png.cpp | 64 +++++++++++++++++++ src/coders/png.hpp | 2 + .../lua/usertypes/lua_type_canvas.cpp | 20 ++++++ 5 files changed, 108 insertions(+) diff --git a/src/coders/imageio.cpp b/src/coders/imageio.cpp index 36e1f76d..dc9a78b8 100644 --- a/src/coders/imageio.cpp +++ b/src/coders/imageio.cpp @@ -53,3 +53,14 @@ void imageio::write(const io::path& file, const ImageData* image) { } return found->second(io::resolve(file).u8string(), image); } + +util::Buffer imageio::encode( + ImageFileFormat format, const ImageData& image +) { + switch (format) { + case ImageFileFormat::PNG: + return png::encode_image(image); + default: + throw std::runtime_error("file format is not supported for encoding"); + } +} diff --git a/src/coders/imageio.hpp b/src/coders/imageio.hpp index 0e59ae37..e844cf67 100644 --- a/src/coders/imageio.hpp +++ b/src/coders/imageio.hpp @@ -4,10 +4,20 @@ #include #include "io/fwd.hpp" +#include "util/Buffer.hpp" +#include "util/EnumMetadata.hpp" class ImageData; namespace imageio { + enum class ImageFileFormat { + PNG + }; + + VC_ENUM_METADATA(ImageFileFormat) + {"png", ImageFileFormat::PNG}, + VC_ENUM_END + inline const std::string PNG = ".png"; bool is_read_supported(const std::string& extension); @@ -15,4 +25,5 @@ namespace imageio { std::unique_ptr read(const io::path& file); void write(const io::path& file, const ImageData* image); + util::Buffer encode(ImageFileFormat format, const ImageData& image); } diff --git a/src/coders/png.cpp b/src/coders/png.cpp index 4121b052..a6c3f9e6 100644 --- a/src/coders/png.cpp +++ b/src/coders/png.cpp @@ -11,6 +11,60 @@ static debug::Logger logger("png-coder"); +static util::Buffer write_to_memory(uint width, uint height, const ubyte* data, bool alpha) { + uint pixsize = alpha ? 4 : 3; + + std::vector buffer; + png_structp png_ptr = png_create_write_struct( + PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr + ); + png_infop info_ptr = png_create_info_struct(png_ptr); + + png_set_write_fn( + png_ptr, + &buffer, + [](png_structp pngPtr, png_bytep data, png_size_t length) { + auto& buf = *reinterpret_cast*>(png_get_io_ptr(pngPtr)); + buf.insert( + buf.end(), + reinterpret_cast(data), + reinterpret_cast(data) + length + ); + }, + nullptr + ); + + png_set_IHDR( + png_ptr, + info_ptr, + width, + height, + 8, + alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE + ); + + png_write_info(png_ptr, info_ptr); + + auto row = std::make_unique(pixsize * width); + for (uint y = 0; y < height; y++) { + for (uint x = 0; x < width; x++) { + for (uint i = 0; i < pixsize; i++) { + row[x * pixsize + i] = + (png_byte)data[(y * width + x) * pixsize + i]; + } + } + png_write_row(png_ptr, row.get()); + } + + png_write_end(png_ptr, nullptr); + png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); + png_destroy_write_struct(&png_ptr, &info_ptr); + return util::Buffer(buffer.data(), buffer.size()); +} + // returns 0 if all-right, 1 otherwise static int png_write( const char* filename, uint width, uint height, const ubyte* data, bool alpha @@ -230,3 +284,13 @@ void png::write_image(const std::string& filename, const ImageData* image) { image->getFormat() == ImageFormat::rgba8888 ); } + +util::Buffer png::encode_image(const ImageData& image) { + auto format = image.getFormat(); + return write_to_memory( + image.getWidth(), + image.getHeight(), + image.getData(), + format == ImageFormat::rgba8888 + ); +} diff --git a/src/coders/png.hpp b/src/coders/png.hpp index 9ba2eda9..468efc4b 100644 --- a/src/coders/png.hpp +++ b/src/coders/png.hpp @@ -4,6 +4,7 @@ #include #include "typedefs.hpp" +#include "util/Buffer.hpp" class Texture; class ImageData; @@ -11,6 +12,7 @@ class ImageData; namespace png { std::unique_ptr load_image(const ubyte* bytes, size_t size); void write_image(const std::string& filename, const ImageData* image); + util::Buffer encode_image(const ImageData& image); std::unique_ptr load_texture(const ubyte* bytes, size_t size); std::unique_ptr load_texture(const std::string& filename); } diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index de8021de..25839659 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -1,8 +1,10 @@ +#define VC_ENABLE_REFLECTION #include "lua_type_canvas.hpp" #include "graphics/core/ImageData.hpp" #include "graphics/core/Texture.hpp" #include "logic/scripting/lua/lua_util.hpp" +#include "coders/imageio.hpp" #include "engine/Engine.hpp" #include "assets/Assets.hpp" @@ -284,6 +286,23 @@ static int l_sub(State* L) { return 0; } +static int l_encode(State* L) { + auto canvas = touserdata(L, 1); + if (canvas == nullptr) { + return 0; + } + auto format = imageio::ImageFileFormat::PNG; + if (lua::isstring(L, 2)) { + auto name = lua::require_string(L, 2); + if (!imageio::ImageFileFormatMeta.getItem(name, format)) { + throw std::runtime_error("unsupported image file format"); + } + } + + auto buffer = imageio::encode(format, canvas->getData()); + return lua::create_bytearray(L, buffer.data(), buffer.size()); +} + static std::unordered_map methods { {"at", lua::wrap}, {"set", lua::wrap}, @@ -296,6 +315,7 @@ static std::unordered_map methods { {"mul", lua::wrap}, {"add", lua::wrap}, {"sub", lua::wrap}, + {"encode", lua::wrap}, {"_set_data", lua::wrap}, }; From d9f75c7c408e1913f793d9edf52f361e70e2d4d7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 19 Nov 2025 17:40:17 +0300 Subject: [PATCH 18/20] add test optimization --- src/voxels/voxel.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/voxels/voxel.hpp b/src/voxels/voxel.hpp index dcff106f..a54f5015 100644 --- a/src/voxels/voxel.hpp +++ b/src/voxels/voxel.hpp @@ -16,22 +16,34 @@ struct blockstate { uint8_t userbits : 8; // bits for use in block script }; static_assert(sizeof(blockstate) == 2); +static_assert(alignof(blockstate) == 1); +static_assert(sizeof(blockstate) == sizeof(blockstate_t)); /// @brief blockstate cast to an integer (optimized out in most cases) +#ifdef _WIN32 +inline blockstate_t blockstate2int(blockstate b) { + return *reinterpret_cast(&b); +#else inline constexpr blockstate_t blockstate2int(blockstate b) { return static_cast(b.rotation) | static_cast(b.segment) << 3 | static_cast(b.reserved) << 6 | static_cast(b.userbits) << 8; +#endif } /// @brief integer cast to a blockstate (optimized out in most cases) +#ifdef _WIN32 +inline blockstate int2blockstate(blockstate_t i) { + return *reinterpret_cast(&i); +#else inline constexpr blockstate int2blockstate(blockstate_t i) { return { static_cast(i & 0b111), static_cast((i >> 3) & 0b111), static_cast((i >> 6) & 0b11), static_cast((i >> 8) & 0xFF)}; +#endif } struct voxel { From 2095e82f1aee841ea8ecc8debdece852828c0269 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 19 Nov 2025 19:21:09 +0300 Subject: [PATCH 19/20] add Canvas.decode static method --- src/coders/imageio.cpp | 40 +++++++++++++++---- src/coders/imageio.hpp | 3 ++ .../lua/usertypes/lua_type_canvas.cpp | 20 ++++++++++ 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/src/coders/imageio.cpp b/src/coders/imageio.cpp index dc9a78b8..b63c65fc 100644 --- a/src/coders/imageio.cpp +++ b/src/coders/imageio.cpp @@ -1,3 +1,4 @@ +#define VC_ENABLE_REFLECTION #include "imageio.hpp" #include @@ -7,28 +8,34 @@ #include "io/io.hpp" #include "png.hpp" +using namespace imageio; + using image_reader = std::function(const ubyte*, size_t)>; using image_writer = std::function; -static std::unordered_map readers { - {".png", png::load_image}, +static std::unordered_map readers { + {ImageFileFormat::PNG, png::load_image}, }; -static std::unordered_map writers { - {".png", png::write_image}, +static std::unordered_map writers { + {ImageFileFormat::PNG, png::write_image}, }; bool imageio::is_read_supported(const std::string& extension) { - return readers.find(extension) != readers.end(); + return extension == ".png"; } bool imageio::is_write_supported(const std::string& extension) { - return writers.find(extension) != writers.end(); + return extension == ".png"; } std::unique_ptr imageio::read(const io::path& file) { - auto found = readers.find(file.extension()); + ImageFileFormat format; + if (!ImageFileFormatMeta.getItem(file.extension().substr(1), format)) { + throw std::runtime_error("unsupported image format"); + } + auto found = readers.find(format); if (found == readers.end()) { throw std::runtime_error( "file format is not supported (read): " + file.string() @@ -44,8 +51,25 @@ std::unique_ptr imageio::read(const io::path& file) { } } +std::unique_ptr imageio::decode( + ImageFileFormat format, util::span src +) { + auto found = readers.find(format); + try { + return std::unique_ptr(found->second(src.data(), src.size())); + } catch (const std::runtime_error& err) { + throw std::runtime_error( + "could not to decode image: " + std::string(err.what()) + ); + } +} + void imageio::write(const io::path& file, const ImageData* image) { - auto found = writers.find(file.extension()); + ImageFileFormat format; + if (!ImageFileFormatMeta.getItem(file.extension().substr(1), format)) { + throw std::runtime_error("unsupported image format"); + } + auto found = writers.find(format); if (found == writers.end()) { throw std::runtime_error( "file format is not supported (write): " + file.string() diff --git a/src/coders/imageio.hpp b/src/coders/imageio.hpp index e844cf67..782d51f5 100644 --- a/src/coders/imageio.hpp +++ b/src/coders/imageio.hpp @@ -6,6 +6,8 @@ #include "io/fwd.hpp" #include "util/Buffer.hpp" #include "util/EnumMetadata.hpp" +#include "util/span.hpp" +#include "typedefs.hpp" class ImageData; @@ -25,5 +27,6 @@ namespace imageio { std::unique_ptr read(const io::path& file); void write(const io::path& file, const ImageData* image); + std::unique_ptr decode(ImageFileFormat format, util::span src); util::Buffer encode(ImageFileFormat format, const ImageData& image); } diff --git a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index 25839659..f89c9d5e 100644 --- a/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp +++ b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp @@ -374,6 +374,23 @@ static int l_meta_meta_call(lua::State* L) { ); } +static int l_canvas_decode(lua::State* L) { + auto bytes = bytearray_as_string(L, 1); + auto formatName = require_lstring(L, 2); + imageio::ImageFileFormat format; + if (!imageio::ImageFileFormatMeta.getItem(formatName, format)) { + throw std::runtime_error("unsupported image format"); + } + return newuserdata( + L, + nullptr, + imageio::decode( + format, + {reinterpret_cast(bytes.data()), bytes.size()} + ) + ); +} + int LuaCanvas::createMetatable(State* L) { createtable(L, 0, 3); pushcfunction(L, lua::wrap); @@ -385,5 +402,8 @@ int LuaCanvas::createMetatable(State* L) { pushcfunction(L, lua::wrap); setfield(L, "__call"); setmetatable(L); + + pushcfunction(L, lua::wrap); + setfield(L, "decode"); return 1; } From 9b2e81b61775ea632d64e25b1f933d8beb6f7808 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 19 Nov 2025 19:50:48 +0300 Subject: [PATCH 20/20] update doc/*/scripting/ui.md --- doc/en/scripting/ui.md | 8 ++++++++ doc/ru/scripting/ui.md | 38 +++++++++++++++++++++++--------------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index c35bf908..b58e7e2a 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -201,6 +201,14 @@ Here, *color* can be specified in the following ways: | data:mul(*color* or Canvas) | multiplies a color by the specified color or canvas | | data:add(*color* or Canvas) | adds a color or another canvas to a color | | data:sub(*color* or Canvas) | subtracts a color or another canvas to a color | +| data:encode(format: str) | encodes image to specified format and returns bytearray | + +To decode a byte array into a Canvas, use the static method: +```lua +Canvas.decode(data: Bytearray, format: str) -> Canvas +``` + +Currently, only png is supported. ## Inline frame (iframe) diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index a6d6006d..e593dc4a 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -186,21 +186,29 @@ document["worlds-panel"]:clear() - r: int, g: int, b: int - r: int, g: int, b: int, a: int -| Метод | Описание | -|----------------------------------------------------------|------------------------------------------------------| -| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | -| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | -| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | -| data:clear() | очищает холст | -| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | -| data:update() | применяет изменения и загружает холст в видеопамять | -| data:set_data(data: table) | заменяет данные пикселей (ширина * высота * 4 чисел) | -| data:create_texture(name: str) | создаёт и делится текстурой с рендерером | -| data:unbind_texture() | отвязывает текстуру от холста | -| data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст | -| data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету | -| data:sub(*цвет* или Canvas) | вычитает цвет или другой холст к цвету | +| Метод | Описание | +|----------------------------------------------------------|-----------------------------------------------------------------| +| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | +| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | +| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | +| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | +| data:clear() | очищает холст | +| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | +| data:update() | применяет изменения и загружает холст в видеопамять | +| data:set_data(data: table) | заменяет данные пикселей (ширина * высота * 4 чисел) | +| data:create_texture(name: str) | создаёт и делится текстурой с рендерером | +| data:unbind_texture() | отвязывает текстуру от холста | +| data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст | +| data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету | +| data:sub(*цвет* или Canvas) | вычитает цвет или другой холст к цвету | +| data:encode(format: str) | кодирует изображение в указанный формат и возращает массив байт | + +Для декодирования массива байт в Canvas используйте статический метод: +```lua +Canvas.decode(data: Bytearray, format: str) -> Canvas +``` + +На данный момент, из форматов поддерживается только png. ## Рамка встраивания (iframe)