diff --git a/block/block/flowing_water.hpp b/block/block/flowing_water.hpp index a400777..5e63e62 100644 --- a/block/block/flowing_water.hpp +++ b/block/block/flowing_water.hpp @@ -1,6 +1,6 @@ #pragma once -#include "public/Trait.hpp" #include "public/LiquidEvent.hpp" +#include "public/Trait.hpp" #include @@ -162,6 +162,7 @@ template <> struct BlockTrait { (LightLvl)0, BlockCollision::kWater, &kFlowingWaterMesh, + &kLiquidEvent, }; } }; diff --git a/block/block/water.hpp b/block/block/water.hpp index efef3a1..f456cb9 100644 --- a/block/block/water.hpp +++ b/block/block/water.hpp @@ -1,4 +1,5 @@ #pragma once +#include "public/LiquidEvent.hpp" #include "public/Trait.hpp" template <> struct BlockTrait : public SingleBlockTrait { @@ -8,5 +9,7 @@ template <> struct BlockTrait : public SingleBlockTrait { BlockTransparency::kSemiTransparent, (LightLvl)0, BlockCollision::kWater, + nullptr, + &kLiquidEvent, }; }; diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index ed62ec1..f360ab9 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -33,6 +33,7 @@ add_executable(HyperCraft_client src/ChunkSetSunlightTask.cpp src/ChunkFloodSunlightTask.cpp src/ChunkSetBlockTask.cpp + src/ChunkUpdateBlockTask.cpp ) target_link_libraries(HyperCraft_client diff --git a/client/include/client/Chunk.hpp b/client/include/client/Chunk.hpp index 0d97d51..a309a2d 100644 --- a/client/include/client/Chunk.hpp +++ b/client/include/client/Chunk.hpp @@ -45,6 +45,7 @@ class Chunk : public std::enable_shared_from_this { inline const ChunkPos3 &GetPosition() const { return m_position; } + // TODO: Protect Block & Light RW with mutexes // Block Getter and Setter inline const Block *GetBlockData() const { return m_blocks; } template inline Block GetBlock(T x, T y, T z) const { return m_blocks[ChunkXYZ2Index(x, y, z)]; } @@ -53,6 +54,12 @@ class Chunk : public std::enable_shared_from_this { inline Block &GetBlockRef(uint32_t idx) { return m_blocks[idx]; } template inline void SetBlock(T x, T y, T z, Block b) { m_blocks[ChunkXYZ2Index(x, y, z)] = b; } inline void SetBlock(uint32_t idx, Block b) { m_blocks[idx] = b; } + template inline Block GetBlockFromNeighbour(T x, T y, T z) const { + return m_blocks[ChunkXYZ2Index((x + kSize) % kSize, (y + kSize) % kSize, (z + kSize) % kSize)]; + } + template inline Block GetBlockFromNeighbour(T x, T y, T z) const { + return m_blocks[ChunkXYZ2Index(x % kSize, y % kSize, z % kSize)]; + } // Sunlight Getter and Setter inline InnerPos1 GetSunlightHeight(uint32_t idx) const { return m_sunlight_heights[idx]; } @@ -67,14 +74,6 @@ class Chunk : public std::enable_shared_from_this { template inline bool GetSunlight(T x, T y, T z) const { return y >= m_sunlight_heights[ChunkXZ2Index(x, z)]; } - - // Neighbours - template inline Block GetBlockFromNeighbour(T x, T y, T z) const { - return m_blocks[ChunkXYZ2Index((x + kSize) % kSize, (y + kSize) % kSize, (z + kSize) % kSize)]; - } - template inline Block GetBlockFromNeighbour(T x, T y, T z) const { - return m_blocks[ChunkXYZ2Index(x % kSize, y % kSize, z % kSize)]; - } template inline bool GetSunlightFromNeighbour(T x, T y, T z) const { return GetSunlight((x + kSize) % kSize, (y + kSize) % kSize, (z + kSize) % kSize); } @@ -83,20 +82,18 @@ class Chunk : public std::enable_shared_from_this { } // Creation - static inline std::shared_ptr Create(const ChunkPos3 &position) { - auto ret = std::make_shared(); - ret->m_position = position; - return ret; - } + inline explicit Chunk(const ChunkPos3 &position) : m_position{position} {} + static inline std::shared_ptr Create(const ChunkPos3 &position) { return std::make_shared(position); } // Generated Flag inline void SetGeneratedFlag() { m_generated_flag.store(true, std::memory_order_release); } inline bool IsGenerated() const { return m_generated_flag.load(std::memory_order_acquire); } private: + const ChunkPos3 m_position{}; + Block m_blocks[kSize * kSize * kSize]; InnerPos1 m_sunlight_heights[kSize * kSize]{}; - ChunkPos3 m_position{}; std::atomic_bool m_generated_flag{false}; }; diff --git a/client/include/client/ChunkFloodSunlightTask.inl b/client/include/client/ChunkFloodSunlightTask.inl index cbb96c5..e16a224 100644 --- a/client/include/client/ChunkFloodSunlightTask.inl +++ b/client/include/client/ChunkFloodSunlightTask.inl @@ -45,7 +45,7 @@ private: public: inline static constexpr ChunkTaskType kType = ChunkTaskType::kFloodSunlight; - void Run(ChunkTaskPool *p_task_pool, ChunkTaskRunnerData &&data); + static void Run(ChunkTaskPool *p_task_pool, ChunkTaskRunnerData &&data); }; } // namespace hc::client diff --git a/client/include/client/ChunkTaskPool.hpp b/client/include/client/ChunkTaskPool.hpp index 6513d23..899bf78 100644 --- a/client/include/client/ChunkTaskPool.hpp +++ b/client/include/client/ChunkTaskPool.hpp @@ -21,7 +21,7 @@ class Chunk; class ChunkTaskPool; class ChunkTaskPoolLocked; -enum class ChunkTaskType { kGenerate, kSetBlock, kSetSunlight, kMesh, kFloodSunlight, COUNT }; +enum class ChunkTaskType { kGenerate, kSetBlock, kSetSunlight, kMesh, kFloodSunlight, kUpdateBlock, COUNT }; enum class ChunkTaskPriority { kHigh, kTick, kLow }; template class ChunkTaskData; @@ -46,6 +46,7 @@ template class ChunkTaskDataBase { #include "ChunkMeshTask.inl" #include "ChunkSetBlockTask.inl" #include "ChunkSetSunlightTask.inl" +#include "ChunkUpdateBlockTask.inl" namespace hc::client { @@ -90,17 +91,19 @@ class ChunkTaskPool { World &m_world; libcuckoo::cuckoohash_map m_data_map; moodycamel::ConcurrentQueue m_high_priority_runner_data_queue, m_low_priority_runner_data_queue; - std::atomic_bool m_high_priority_producer_flag; + std::atomic_bool m_high_priority_producer_flag, m_tick_producer_flag; std::mutex m_producer_mutex; - template - void produce_runner_data(ChunkTaskPoolToken *p_token, std::size_t max_tasks); + template + void produce_runner_data(moodycamel::ConcurrentQueue *p_queue, + const moodycamel::ProducerToken &token, std::size_t max_tasks); friend class ChunkTaskPoolToken; friend class ChunkTaskPoolLocked; public: - inline explicit ChunkTaskPool(World *p_world) : m_world{*p_world}, m_high_priority_producer_flag{false} {} + inline explicit ChunkTaskPool(World *p_world) + : m_world{*p_world}, m_high_priority_producer_flag{false}, m_tick_producer_flag{false} {} template inline void Push(const ChunkPos3 &chunk_pos, Args &&...args) { bool high_priority = false; @@ -127,6 +130,7 @@ class ChunkTaskPool { return m_low_priority_runner_data_queue.size_approx() + m_high_priority_runner_data_queue.size_approx(); } void Run(ChunkTaskPoolToken *p_token); + inline void ProduceTickTasks() { m_tick_producer_flag.store(true, std::memory_order_release); } }; class ChunkTaskPoolLocked { diff --git a/client/include/client/ChunkUpdateBlockTask.inl b/client/include/client/ChunkUpdateBlockTask.inl new file mode 100644 index 0000000..ef2a341 --- /dev/null +++ b/client/include/client/ChunkUpdateBlockTask.inl @@ -0,0 +1,58 @@ +#include + +#include +#include + +namespace hc::client { + +template <> +class ChunkTaskData final : public ChunkTaskDataBase { +private: + std::unordered_map m_updates; + +public: + inline static constexpr ChunkTaskType kType = ChunkTaskType::kUpdateBlock; + + inline void Push(InnerPos3 update, uint64_t tick) { m_updates[update] = tick; } + inline void Push(std::span updates, uint64_t tick) { + for (const auto &pos : updates) + m_updates[pos] = tick; + } + inline constexpr ChunkTaskPriority GetPriority() const { return ChunkTaskPriority::kTick; } + inline bool IsQueued() const { return !m_updates.empty(); } + std::optional> Pop(const ChunkTaskPoolLocked &task_pool, + const ChunkPos3 &chunk_pos); + + inline void OnUnload() { m_updates.clear(); } +}; + +template <> class ChunkTaskRunnerData { +private: + std::array, 27> m_chunk_ptr_array; + std::vector m_updates; + +public: + inline static constexpr ChunkTaskType kType = ChunkTaskType::kUpdateBlock; + + inline ChunkTaskRunnerData(std::array, 27> &&chunk_ptr_array, + std::vector &&updates) + : m_chunk_ptr_array{std::move(chunk_ptr_array)}, m_updates{std::move(updates)} {} + inline const ChunkPos3 &GetChunkPos() const { return m_chunk_ptr_array[26]->GetPosition(); } + inline const std::shared_ptr &GetChunkPtr() const { return m_chunk_ptr_array[26]; } + inline const auto &GetChunkPtrArray() const { return m_chunk_ptr_array; } + inline const auto &GetUpdates() const { return m_updates; } +}; + +template <> class ChunkTaskRunner { +private: + block::Block m_update_neighbours[block::kBlockUpdateMaxNeighbours]; + InnerPos3 m_update_neighbour_pos[block::kBlockUpdateMaxNeighbours]; + block::BlockUpdateSetBlock m_update_set_blocks[block::kBlockUpdateMaxNeighbours]; + +public: + inline static constexpr ChunkTaskType kType = ChunkTaskType::kUpdateBlock; + + void Run(ChunkTaskPool *p_task_pool, ChunkTaskRunnerData &&data); +}; + +} // namespace hc::client diff --git a/client/include/client/World.hpp b/client/include/client/World.hpp index dd2e76a..1c7bfc9 100644 --- a/client/include/client/World.hpp +++ b/client/include/client/World.hpp @@ -47,6 +47,7 @@ class World : public std::enable_shared_from_this { friend class ChunkUpdatePool; template friend class ChunkTaskRunner; + std::atomic_uint64_t m_tick; static_assert(sizeof(ChunkPos3) <= sizeof(uint64_t)); std::atomic_uint64_t m_center_chunk_pos; std::atomic m_load_chunk_radius, m_unload_chunk_radius; @@ -106,6 +107,12 @@ class World : public std::enable_shared_from_this { inline const std::weak_ptr &GetClientWeakPtr() const { return m_client_weak_ptr; } inline std::shared_ptr LockClient() const { return m_client_weak_ptr.lock(); } + inline uint64_t GetCurrentTick() const { return m_tick.load(std::memory_order_acquire); } + inline void NextTick() { + m_tick.fetch_add(1, std::memory_order_acq_rel); + m_chunk_task_pool.ProduceTickTasks(); + } + const auto &GetChunkPool() const { return m_chunk_pool; } const auto &GetChunkTaskPool() const { return m_chunk_task_pool; } const auto &GetChunkUpdatePool() const { return m_chunk_update_pool; } diff --git a/client/src/Application.cpp b/client/src/Application.cpp index b5a97c3..c03447f 100644 --- a/client/src/Application.cpp +++ b/client/src/Application.cpp @@ -325,7 +325,8 @@ void Application::glfw_key_callback(GLFWwindow *window, int key, int scancode, i } else if (key == GLFW_KEY_ESCAPE) { app->m_mouse_captured ^= 1; glfwSetInputMode(window, GLFW_CURSOR, app->m_mouse_captured ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); - } + } else if (key == GLFW_KEY_U) + app->m_world->NextTick(); } void Application::glfw_framebuffer_resize_callback(GLFWwindow *window, int width, int height) { diff --git a/client/src/ChunkFloodSunlightTask.cpp b/client/src/ChunkFloodSunlightTask.cpp index e849494..508e807 100644 --- a/client/src/ChunkFloodSunlightTask.cpp +++ b/client/src/ChunkFloodSunlightTask.cpp @@ -47,7 +47,7 @@ void ChunkTaskRunner::Run(ChunkTaskPool *p_task_p auto up_sl = up_chunk->GetSunlightHeight(xz_idx), sl = chunk->GetSunlightHeight(xz_idx); if (up_sl > 0) { if (sl != kChunkSize) { - set_sunlights.push_back({xz, kChunkSize}); + set_sunlights.emplace_back(xz, kChunkSize); xz_next_updates.push_back(xz); } } else { @@ -57,7 +57,7 @@ void ChunkTaskRunner::Run(ChunkTaskPool *p_task_p ; ++y; if (sl != y) - set_sunlights.push_back({xz, y}); + set_sunlights.emplace_back(xz, y); if ((sl == 0) != (y == 0)) xz_next_updates.push_back(xz); } diff --git a/client/src/ChunkGenerateTask.cpp b/client/src/ChunkGenerateTask.cpp index 8c27f80..7d83096 100644 --- a/client/src/ChunkGenerateTask.cpp +++ b/client/src/ChunkGenerateTask.cpp @@ -19,7 +19,7 @@ ChunkTaskData::Pop(const ChunkTaskPoolLocked &task_poo void ChunkTaskRunner::Run(ChunkTaskPool *p_task_pool, ChunkTaskRunnerData &&data) { std::shared_ptr client = p_task_pool->GetWorld().LockClient(); - if (!client || !client->IsConnected()) + if (!client) return; const auto &chunk_ptr = data.GetChunkPtr(); diff --git a/client/src/ChunkSetBlockTask.cpp b/client/src/ChunkSetBlockTask.cpp index ba4007c..d266754 100644 --- a/client/src/ChunkSetBlockTask.cpp +++ b/client/src/ChunkSetBlockTask.cpp @@ -34,6 +34,7 @@ void ChunkTaskRunner::Run(ChunkTaskPool *p_task_pool, std::bitset<27> neighbour_remesh_set{}; std::unordered_set flood_sunlights; + std::unordered_set block_updates[27]; for (const auto &block_change : block_changes) { auto block_pos = block_change.first; @@ -42,10 +43,24 @@ void ChunkTaskRunner::Run(ChunkTaskPool *p_task_pool, if (new_block == old_block) continue; - neighbour_remesh_set[26] = true; - flood_sunlights.emplace(block_pos.x, block_pos.z); chunk->SetBlock(block_idx, new_block); + const auto register_block_update = [&block_updates](InnerPos3 pos) { + auto [rel_chunk_pos, inner_pos] = ChunkInnerPosFromBlockPos(BlockPos3(pos)); + uint32_t chunk_idx = CmpXYZ2NeighbourIndex(rel_chunk_pos.x, rel_chunk_pos.y, rel_chunk_pos.z); + block_updates[chunk_idx].insert(inner_pos); + }; + register_block_update(block_pos); + register_block_update({block_pos.x - 1, block_pos.y, block_pos.z}); + register_block_update({block_pos.x + 1, block_pos.y, block_pos.z}); + register_block_update({block_pos.x, block_pos.y - 1, block_pos.z}); + register_block_update({block_pos.x, block_pos.y + 1, block_pos.z}); + register_block_update({block_pos.x, block_pos.y, block_pos.z - 1}); + register_block_update({block_pos.x, block_pos.y, block_pos.z + 1}); + + flood_sunlights.emplace(block_pos.x, block_pos.z); + + neighbour_remesh_set[26] = true; for (uint32_t i = 0; i < 26; ++i) { if (neighbour_remesh_set[i]) continue; @@ -55,15 +70,22 @@ void ChunkTaskRunner::Run(ChunkTaskPool *p_task_pool, } } + auto current_tick = p_task_pool->GetWorld().GetCurrentTick(); for (uint32_t i = 0; i < 27; ++i) { - if (!neighbour_remesh_set[i]) - continue; ChunkPos3 nei_pos; Chunk::NeighbourIndex2CmpXYZ(i, glm::value_ptr(nei_pos)); nei_pos += chunk->GetPosition(); - p_task_pool->Push(nei_pos, data.IsHighPriority()); + // Trigger remesh + if (neighbour_remesh_set[i]) + p_task_pool->Push(nei_pos, data.IsHighPriority()); + // Trigger block update + if (!block_updates[i].empty()) { + p_task_pool->Push( + nei_pos, std::vector(block_updates[i].begin(), block_updates[i].end()), current_tick); + } } + // Trigger sunlight flood auto flood_sunlight_vec = std::vector{flood_sunlights.begin(), flood_sunlights.end()}; p_task_pool->Push(chunk->GetPosition(), std::span{flood_sunlight_vec}); } diff --git a/client/src/ChunkTaskPool.cpp b/client/src/ChunkTaskPool.cpp index ec401a5..435ce13 100644 --- a/client/src/ChunkTaskPool.cpp +++ b/client/src/ChunkTaskPool.cpp @@ -13,8 +13,9 @@ struct ChunkTaskSortEntry { inline bool operator<(const ChunkTaskSortEntry &r) const { return dist2 < r.dist2; } }; -template -void ChunkTaskPool::produce_runner_data(ChunkTaskPoolToken *p_token, std::size_t max_tasks) { +template +void ChunkTaskPool::produce_runner_data(moodycamel::ConcurrentQueue *p_queue, + const moodycamel::ProducerToken &token, std::size_t max_tasks) { auto center_pos = m_world.GetCenterChunkPos(); auto load_radius = m_world.GetLoadChunkRadius(), unload_radius = m_world.GetUnloadChunkRadius(); @@ -59,9 +60,11 @@ void ChunkTaskPool::produce_runner_data(ChunkTaskPoolToken *p_token, std::size_t [&] { if (data.m_running) return; - if constexpr (TaskPriority == ChunkTaskPriority::kHigh) - if (data.GetPriority() == ChunkTaskPriority::kLow) + { + auto p = data.GetPriority(); + if (((p != TaskPriorities) && ...)) return; + } auto runner_data_opt = data.Pop(locked_pool, e.pos); if (runner_data_opt.has_value()) { runner_data_vec.emplace_back(std::move(runner_data_opt.value())); @@ -77,22 +80,26 @@ void ChunkTaskPool::produce_runner_data(ChunkTaskPoolToken *p_token, std::size_t } } if (!runner_data_vec.empty()) { - if constexpr (TaskPriority == ChunkTaskPriority::kHigh) - m_high_priority_runner_data_queue.enqueue_bulk(p_token->m_high_priority_producer_token, - std::make_move_iterator(runner_data_vec.begin()), - runner_data_vec.size()); - else - m_low_priority_runner_data_queue.enqueue_bulk(p_token->m_low_priority_producer_token, - std::make_move_iterator(runner_data_vec.begin()), - runner_data_vec.size()); + p_queue->enqueue_bulk(token, std::make_move_iterator(runner_data_vec.begin()), runner_data_vec.size()); } } void ChunkTaskPool::Run(ChunkTaskPoolToken *p_token) { + if (p_token->m_producer_config.max_tick_tasks) { + if (m_tick_producer_flag.exchange(false, std::memory_order_acq_rel)) { + std::scoped_lock lock{m_producer_mutex}; + produce_runner_data(&m_high_priority_runner_data_queue, + p_token->m_high_priority_producer_token, + p_token->m_producer_config.max_tick_tasks); + return; + } + } if (p_token->m_producer_config.max_high_priority_tasks) { if (m_high_priority_producer_flag.exchange(false, std::memory_order_acq_rel)) { std::scoped_lock lock{m_producer_mutex}; - produce_runner_data(p_token, p_token->m_producer_config.max_high_priority_tasks); + produce_runner_data(&m_high_priority_runner_data_queue, + p_token->m_high_priority_producer_token, + p_token->m_producer_config.max_high_priority_tasks); return; } } @@ -102,7 +109,9 @@ void ChunkTaskPool::Run(ChunkTaskPoolToken *p_token) { !m_low_priority_runner_data_queue.try_dequeue(p_token->m_low_priority_consumer_token, runner_data)) { std::scoped_lock lock{m_producer_mutex}; if (!m_low_priority_runner_data_queue.try_dequeue(p_token->m_low_priority_consumer_token, runner_data)) { - produce_runner_data(p_token, p_token->m_producer_config.max_tick_tasks); + produce_runner_data( + &m_low_priority_runner_data_queue, p_token->m_low_priority_producer_token, + p_token->m_producer_config.max_tasks); return; } } diff --git a/client/src/ChunkUpdateBlockTask.cpp b/client/src/ChunkUpdateBlockTask.cpp new file mode 100644 index 0000000..3fa52aa --- /dev/null +++ b/client/src/ChunkUpdateBlockTask.cpp @@ -0,0 +1,91 @@ +#include + +#include + +namespace hc::client { + +std::optional> +ChunkTaskData::Pop(const ChunkTaskPoolLocked &task_pool, const ChunkPos3 &chunk_pos) { + if (m_updates.empty()) + return std::nullopt; + + std::array, 27> chunks; + auto &chunk = chunks[26]; + + for (uint32_t i = 0; i < 27; ++i) { + ChunkPos3 nei_pos; + Chunk::NeighbourIndex2CmpXYZ(i, glm::value_ptr(nei_pos)); + nei_pos += chunk_pos; + + if (task_pool.AnyNotIdle(nei_pos)) + return std::nullopt; + + std::shared_ptr nei_chunk = task_pool.GetWorld().GetChunkPool().FindChunk(nei_pos); + if (nei_chunk == nullptr) + return std::nullopt; + chunks[i] = std::move(nei_chunk); + } + + std::vector updates_to_apply; + + uint64_t current_tick = task_pool.GetWorld().GetCurrentTick(); + for (auto it = m_updates.begin(); it != m_updates.end();) { + auto pos = it->first; + auto block = chunk->GetBlock(pos.x, pos.y, pos.z); + + if (block.GetEvent() == nullptr || block.GetEvent()->on_update_func == nullptr) + it = m_updates.erase(it); + else if (it->second + block.GetEvent()->update_tick_interval <= current_tick) { + updates_to_apply.push_back(pos); + it = m_updates.erase(it); + } else + ++it; + } + if (updates_to_apply.empty()) + return std::nullopt; + + return ChunkTaskRunnerData{std::move(chunks), std::move(updates_to_apply)}; +} + +void ChunkTaskRunner::Run(ChunkTaskPool *p_task_pool, + ChunkTaskRunnerData &&data) { + const auto &neighbour_chunks = data.GetChunkPtrArray(); + const auto &chunk = neighbour_chunks.back(); + + const auto get_block = [&neighbour_chunks](auto x, auto y, auto z) -> block::Block { + return neighbour_chunks[Chunk::GetBlockNeighbourIndex(x, y, z)]->GetBlockFromNeighbour(x, y, z); + }; + + std::vector> set_blocks[27]; + + for (const auto &update : data.GetUpdates()) { + const auto *p_blk_event = chunk->GetBlock(update.x, update.y, update.z).GetEvent(); + if (!p_blk_event || !p_blk_event->on_update_func) + continue; + for (uint32_t i = 0; i < p_blk_event->update_neighbour_count; ++i) { + InnerPos3 pos = update + p_blk_event->update_neighbours[i]; + m_update_neighbour_pos[i] = pos; + m_update_neighbours[i] = get_block(pos.x, pos.y, pos.z); + } + uint32_t set_block_count = 0; + p_blk_event->on_update_func(m_update_neighbours, m_update_set_blocks, &set_block_count); + for (uint32_t i = 0; i < set_block_count; ++i) { + InnerPos3 set_pos = m_update_neighbour_pos[m_update_set_blocks[i].idx]; + block::Block set_blk = m_update_set_blocks[i].block; + auto [rel_chunk_pos, inner_pos] = ChunkInnerPosFromBlockPos(BlockPos3(set_pos)); + uint32_t nei_chunk_idx = CmpXYZ2NeighbourIndex(rel_chunk_pos.x, rel_chunk_pos.y, rel_chunk_pos.z); + set_blocks[nei_chunk_idx].emplace_back(inner_pos, set_blk); + } + } + + for (uint32_t i = 0; i < 27; ++i) { + if (set_blocks[i].empty()) + continue; + ChunkPos3 nei_pos; + Chunk::NeighbourIndex2CmpXYZ(i, glm::value_ptr(nei_pos)); + nei_pos += chunk->GetPosition(); + p_task_pool->GetWorld().m_chunk_update_pool.SetBlockUpdateBulk(nei_pos, set_blocks[i], true); + } +} + +} // namespace hc::client