diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index fce3abfb..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) ``` @@ -53,6 +57,21 @@ 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_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/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/events.md b/doc/ru/scripting/events.md index 73a6bc0c..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) ``` @@ -53,6 +57,21 @@ 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_block_removed(x, y, z) +``` + +Вызывается при выгрузке чанка, в котором находится блок. + ```lua function on_player_tick(playerid: int, tps: int) ``` 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) diff --git a/res/scripts/classes.lua b/res/scripts/classes.lua index 699c0fd2..f55a503b 100644 --- a/res/scripts/classes.lua +++ b/res/scripts/classes.lua @@ -181,73 +181,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..ec8e8dd6 --- /dev/null +++ b/res/scripts/internal_events.lua @@ -0,0 +1,144 @@ +local updating_blocks = {} +local present_queues = {} +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 + 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 + for id, queue in pairs(present_queues) do + queue.timer = queue.timer + delta + local steps = math.floor(queue.timer / queue.delta * #queue / 3) + 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 - 2 + if index <= 0 then + break + end + local x = queue[index] + local y = queue[index + 1] + local z = queue[index + 2] + + for j=1,3 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() + 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) + 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 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" + 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, 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) + 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/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 54c6df1c..ae43a7f2 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -358,6 +358,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/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 >) diff --git a/src/coders/imageio.cpp b/src/coders/imageio.cpp index 36e1f76d..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() @@ -53,3 +77,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..782d51f5 100644 --- a/src/coders/imageio.hpp +++ b/src/coders/imageio.hpp @@ -4,10 +4,22 @@ #include #include "io/fwd.hpp" +#include "util/Buffer.hpp" +#include "util/EnumMetadata.hpp" +#include "util/span.hpp" +#include "typedefs.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 +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/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/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(); 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/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/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/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/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 3989fd7f..e4d5474c 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -120,6 +120,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/lua/usertypes/lua_type_canvas.cpp b/src/logic/scripting/lua/usertypes/lua_type_canvas.cpp index de8021de..f89c9d5e 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}, }; @@ -354,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); @@ -365,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; } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 19accc89..3a68917d 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 { @@ -340,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()) { @@ -674,6 +682,10 @@ 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"); + 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/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..55238959 --- /dev/null +++ b/src/util/ObjectsPool.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#endif + +namespace util { + struct AlignedDeleter { + void operator()(void* p) const { +#if defined(_WIN32) + _aligned_free(p); +#else + std::free(p); +#endif + } + }; + + 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) { + ptr->~T(); + std::lock_guard lock(mutex); + freeObjects.push(ptr); + }); + } + private: + std::vector> objects; + std::queue freeObjects; + std::mutex mutex; + + 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)); + } + }; +} diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 59115e84..29eda249 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -50,6 +50,8 @@ struct BlockFuncsSet { bool randupdate : 1; bool onblocktick : 1; bool onblockstick : 1; + bool onblockpresent : 1; + bool onblockremoved : 1; }; struct CoordSystem { diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index 7451a563..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,13 +90,15 @@ static inline auto load_inventories( return invs; } +static util::ObjectsPool chunks_pool(1'024); + 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); + auto chunk = chunks_pool.create(x, z); chunksMap[keyfrom(x, z)] = chunk; World& world = *level.getWorld(); @@ -127,8 +130,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 d64f7727..7041f67a 100644 --- a/src/voxels/blocks_agent.cpp +++ b/src/voxels/blocks_agent.cpp @@ -14,39 +14,58 @@ std::vector blocks_agent::pull_register_events() { return events; } +static uint8_t get_events_bits(const Block& def) { + uint8_t bits = 0; + 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; +} + 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} - }); + const auto& voxels = chunk.voxels; + + 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; + 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; + } + 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 { + static_cast(bits | (present ? 1 : 0)), 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 +120,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} - }); + + uint8_t bits = get_events_bits(def); + if (bits == 0) { + return; } + block_register_events.push_back(BlockRegisterEvent { + bits, def.rt.id, {x, y, z} + }); } template @@ -131,9 +153,17 @@ 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(def); + if (bits == 0) { + return; + } + block_register_events.push_back(BlockRegisterEvent { + static_cast(bits | 1), 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..7c0223cb 100644 --- a/src/voxels/blocks_agent.hpp +++ b/src/voxels/blocks_agent.hpp @@ -25,11 +25,11 @@ struct AABB; namespace blocks_agent { struct BlockRegisterEvent { - enum class Type : uint16_t { - REGISTER_UPDATING, - UNREGISTER_UPDATING, - }; - Type type; + 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; }; 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 {