From 85db85a7375a5d8582723f9c50b84dac3ec913c6 Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Mon, 29 Jun 2026 14:14:36 +0800 Subject: [PATCH] refactor(client_world): use concurrent data structures for chunk management Replace std::shared_mutex and std::unordered_set with tbb::concurrent_hash_map for chunk storage, tbb::concurrent_queue for pending uploads, and absl::flat_hash_set for player chunk positions. This improves thread safety and reduces locking overhead. Refactor receive_chunk and request_chunk to work with the new concurrent model. --- include/Cubed/gameplay/client_player.hpp | 10 +- include/Cubed/gameplay/client_world.hpp | 21 +- src/dev_panel.cpp | 1 + src/gameplay/client_player.cpp | 16 ++ src/gameplay/client_world.cpp | 254 +++++++++++++---------- src/gameplay/network_client.cpp | 6 +- 6 files changed, 185 insertions(+), 123 deletions(-) diff --git a/include/Cubed/gameplay/client_player.hpp b/include/Cubed/gameplay/client_player.hpp index 5bfce48..d8ce50a 100644 --- a/include/Cubed/gameplay/client_player.hpp +++ b/include/Cubed/gameplay/client_player.hpp @@ -6,6 +6,7 @@ #include "Cubed/gameplay/game_mode.hpp" #include "Cubed/input.hpp" +#include #include #include #include @@ -14,8 +15,14 @@ enum class Gait { WALK = 0, RUN }; class ClientWorld; class ClientPlayer { public: + using ChunkPosSet = absl::flat_hash_set; ClientPlayer(ClientWorld& world); ~ClientPlayer(); + + void update_chunk_set(const ChunkPosSet& set); + const ChunkPosSet& get_chunk_pos_set() const; + ChunkPosSet& get_chunk_pos_set(); + AABB get_aabb(const glm::vec3& pos) const; const glm::vec3& get_front() const; const Gait& get_gait() const; @@ -101,7 +108,8 @@ private: ClientWorld& m_world; mutable std::shared_mutex m_player_pos_mutex; - + mutable std::shared_mutex m_chunk_pos_mutex; + ChunkPosSet m_player_chunk_pos_set; bool ray_cast(const glm::vec3& start, const glm::vec3& dir, glm::ivec3& block_pos, glm::vec3& normal, float distance = 4.0f); diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index e9c1dc1..d26dc81 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -7,7 +7,10 @@ #include "Cubed/gameplay/network_client.hpp" #include "Cubed/tools/thread_pool.hpp" +#include #include +#include +#include #include namespace Cubed { @@ -64,10 +67,10 @@ public: const std::vector& render_snapshots() const; const std::vector& render_player_data() const; glm::vec3 sunlight_dir() const; - void receive_chunk(ChunkDataRsp data); + void receive_chunk(std::vector data, PacketHeader header); void request_exit(); bool is_receive_exit(); - + int chunk_size() const; template void register_timer(std::string_view id, TickType threshold, Fn&& f) { m_timers.emplace(std::piecewise_construct, @@ -78,12 +81,14 @@ public: private: enum class ChunkLoadStyle { RANDOM, CENTER }; using ChunkHashMap = - tbb::concurrent_unordered_map; - using ChunkPosSet = std::unordered_set; + tbb::concurrent_hash_map, + ChunkPos::TBBHash>; + using ChunkPosSet = absl::flat_hash_set; using ChunkPosVector = std::vector; using OtherPlayerHashMap = std::unordered_map; - + using chunk_acc = ChunkHashMap::accessor; + using chunk_cacc = ChunkHashMap::const_accessor; static constexpr int WORLD_EXIT_TIMEOUT = 200; ClientPlayer m_player; @@ -92,13 +97,11 @@ private: std::vector m_planes; std::jthread m_client_thread; - mutable std::shared_mutex m_chunks_mutex; std::mutex m_delete_vbo_mutex; std::mutex m_delete_vao_mutex; - std::mutex m_pending_upload_queue_mutex; std::mutex m_other_players_mutex; - std::deque m_pending_upload_queue; + tbb::concurrent_queue> m_pending_upload_queue; std::vector m_pending_delete_vbo; std::vector m_pending_delete_vao; @@ -127,5 +130,7 @@ private: void report_player_pos(); void set_block(const glm::ivec3& pos, unsigned id); + + void update_chunk(const ChunkPosSet& old, const ChunkPosSet& now); }; } // namespace Cubed diff --git a/src/dev_panel.cpp b/src/dev_panel.cpp index 9e099ba..142bd04 100644 --- a/src/dev_panel.cpp +++ b/src/dev_panel.cpp @@ -576,6 +576,7 @@ void DevPanel::show_client_world_table_bar() { m_player->set_player_pos({0.0f, 255.0f, 0.0f}); } ImGui::Text("Chunk Task Id %d", m_app.client_world().get_chunk_task_id()); + ImGui::Text("Client World Chunk %d", m_app.client_world().chunk_size()); } void DevPanel::show_player_tab_item() { diff --git a/src/gameplay/client_player.cpp b/src/gameplay/client_player.cpp index 7c413c9..3ad6e6e 100644 --- a/src/gameplay/client_player.cpp +++ b/src/gameplay/client_player.cpp @@ -542,6 +542,22 @@ void ClientPlayer::update_scroll(double yoffset) { } } +void ClientPlayer::update_chunk_set(const ChunkPosSet& set) { + std::lock_guard lock(m_chunk_pos_mutex); + m_player_chunk_pos_set.clear(); + m_player_chunk_pos_set.insert(set.begin(), set.end()); +} + +const ClientPlayer::ChunkPosSet& ClientPlayer::get_chunk_pos_set() const { + std::shared_lock lock(m_chunk_pos_mutex); + return m_player_chunk_pos_set; +} + +ClientPlayer::ChunkPosSet& ClientPlayer::get_chunk_pos_set() { + std::lock_guard lock(m_chunk_pos_mutex); + return m_player_chunk_pos_set; +} + float& ClientPlayer::max_walk_speed() { return m_max_walk_speed; } float& ClientPlayer::max_run_speed() { return m_max_run_speed; } float& ClientPlayer::max_speed() { return m_max_speed; } diff --git a/src/gameplay/client_world.cpp b/src/gameplay/client_world.cpp index 5387348..6a42872 100644 --- a/src/gameplay/client_world.cpp +++ b/src/gameplay/client_world.cpp @@ -23,10 +23,9 @@ ClientWorld::ClientWorld() : m_player(*this) {} ClientWorld::~ClientWorld() { stop_client_thread(); stop_thread_pool(); - { - std::lock_guard lock(m_chunks_mutex); - m_chunks.clear(); - } + + m_chunks.clear(); + { std::lock_guard lk(m_delete_vbo_mutex); for (auto x : m_pending_delete_vbo) { @@ -53,14 +52,13 @@ ClientPlayer& ClientWorld::get_player() { return m_player; } int ClientWorld::get_block(const glm::ivec3& block_pos) const { auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); - std::shared_lock lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); + chunk_cacc cacc; - if (it == m_chunks.end()) { + if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) { return 0; } - const auto& chunk_blocks = it->second.get_chunk_blocks(); + const auto& chunk_blocks = cacc->second->get_chunk_blocks(); auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z}); if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || z >= CHUNK_SIZE) { @@ -70,13 +68,12 @@ int ClientWorld::get_block(const glm::ivec3& block_pos) const { } bool ClientWorld::is_solid(const glm::ivec3& block_pos) const { auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); - std::shared_lock lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); + chunk_cacc cacc; - if (it == m_chunks.end()) { + if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) { return false; } - const auto& chunk_blocks = it->second.get_chunk_blocks(); + const auto& chunk_blocks = cacc->second->get_chunk_blocks(); auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z}); if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || z >= CHUNK_SIZE) { @@ -91,13 +88,12 @@ bool ClientWorld::is_solid(const glm::ivec3& block_pos) const { } bool ClientWorld::can_pass_block(const glm::ivec3& block_pos) const { auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); - std::shared_lock lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); + chunk_cacc cacc; - if (it == m_chunks.end()) { + if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) { return true; } - const auto& chunk_blocks = it->second.get_chunk_blocks(); + const auto& chunk_blocks = cacc->second->get_chunk_blocks(); auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z}); if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || z >= CHUNK_SIZE) { @@ -111,31 +107,31 @@ void ClientWorld::rebuild_world() { if (m_is_rebuilding.exchange(true)) { return; } + + stop_client_thread(); stop_thread_pool(); - { - std::lock_guard lk(m_chunks_mutex); - m_chunks.clear(); - } - { - std::lock_guard lock(m_pending_upload_queue_mutex); - m_pending_upload_queue.clear(); - } + + m_chunks.clear(); + + m_pending_upload_queue.clear(); + start_thread_pool(); + start_client_thread(m_player.get_uuid()); request_chunk(); m_is_rebuilding = false; } BlockType ClientWorld::get_block_tpye(const glm::ivec3& block_pos) const { auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z); - std::shared_lock lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); + chunk_cacc cacc; + ; - if (it == m_chunks.end()) { + if (!m_chunks.find(cacc, ChunkPos{chunk_x, chunk_z})) { // Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y, // block_pos.z); return 0; } - const auto& chunk_blocks = it->second.get_chunk_blocks(); + const auto& chunk_blocks = cacc->second->get_chunk_blocks(); auto [x, y, z] = ClientChunk::world_to_block(block_pos, {chunk_x, chunk_z}); if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || z >= CHUNK_SIZE) { @@ -152,22 +148,23 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) { world_z = block_pos.z; auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z); - std::lock_guard lk(m_chunks_mutex); - auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); + { + chunk_acc acc; - if (it == m_chunks.end()) { - return; + if (!m_chunks.find(acc, ChunkPos{chunk_x, chunk_z})) { + return; + } + + auto [x, y, z] = ClientChunk::world_to_block(world_x, world_y, world_z, + chunk_x, chunk_z); + if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE) { + return; + } + + acc->second->set_chunk_block(ClientChunk::index(x, y, z), id); } - auto [x, y, z] = ClientChunk::world_to_block(world_x, world_y, world_z, - chunk_x, chunk_z); - if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || - z >= CHUNK_SIZE) { - return; - } - - it->second.set_chunk_block(ClientChunk::index(x, y, z), id); - static const glm::ivec3 NEIGHBOR_DIRS[] = { {1, 0, 0}, {-1, 0, 0}, {0, 0, -1}, {0, 0, 1}}; @@ -175,9 +172,11 @@ void ClientWorld::set_block(const glm::ivec3& block_pos, unsigned id) { glm::ivec3 neighbor = block_pos + dir; auto [cx, cz] = get_chunk_pos(neighbor.x, neighbor.z); - auto it = m_chunks.find({cx, cz}); - if (it != m_chunks.end()) { - it->second.mark_dirty(); + { + chunk_acc acc; + if (m_chunks.find(acc, {cx, cz})) { + acc->second->mark_dirty(); + } } } } @@ -251,7 +250,6 @@ void ClientWorld::receive_player_logout(const LogoutRsp& rsp) { void ClientWorld::init(std::string_view player_name, std::shared_ptr client) { - m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); m_player.init(player_name); m_client = client; // timer @@ -361,6 +359,26 @@ void ClientWorld::report_player_pos() { m_client->send(make_packet(*pos)); } +void ClientWorld::update_chunk(const ChunkPosSet& old, const ChunkPosSet& now) { + + // Elements in the old set that are not contained in now are not needed by + // the current player. + + for (auto& pos : old) { + if (!now.contains(pos)) { + + chunk_acc acc; + if (!m_chunks.find(acc, pos)) { + Logger::warn("Update Ref Count Error, can't Find old pos " + "in m_chunks"); + continue; + } + + m_chunks.erase(acc); + } + } +} + void ClientWorld::request_chunk() { if (m_requesting_chunk.exchange(true)) { Logger::warn("It is requesting new chunk!"); @@ -386,24 +404,20 @@ void ClientWorld::request_chunk() { } } - ChunkPosVector need_send_pos; - { - std::lock_guard lk(m_chunks_mutex); - for (auto it = m_chunks.begin(); it != m_chunks.end();) { - if (required_chunks.find(it->first) == required_chunks.end()) { - it = m_chunks.unsafe_erase(it); - } else { - ++it; - } - } + ChunkPosSet old = std::move(m_player.get_chunk_pos_set()); + m_player.update_chunk_set(required_chunks); - for (auto pos : required_chunks) { - auto it = m_chunks.find(pos); - if (it == m_chunks.end()) { - need_send_pos.emplace_back(pos); - } + ChunkPosVector need_send_pos; + + for (auto pos : required_chunks) { + chunk_cacc cacc; + if (!m_chunks.find(cacc, pos)) { + need_send_pos.emplace_back(pos); } } + + update_chunk(old, required_chunks); + if (need_send_pos.empty()) { m_requesting_chunk = false; return; @@ -416,9 +430,8 @@ void ClientWorld::request_chunk() { case CENTER: { glm::vec3 player_pos = m_player.get_player_pos(); - auto dist2 = [player_pos](ChunkPos chunk_pos) { - ChunkPos player_chunk_pos = - get_chunk_pos(player_pos.x, player_pos.z); + ChunkPos player_chunk_pos = get_chunk_pos(player_pos.x, player_pos.z); + auto dist2 = [player_chunk_pos](ChunkPos chunk_pos) { float dx = player_chunk_pos.x - chunk_pos.x; float dz = player_chunk_pos.z - chunk_pos.z; return dx * dx + dz * dz; @@ -446,36 +459,48 @@ void ClientWorld::request_chunk() { m_requesting_chunk = false; } -void ClientWorld::receive_chunk(ChunkDataRsp data) { - if (data.task_id() < m_chunk_task_id) { - return; - } +void ClientWorld::receive_chunk(std::vector raw_data, + PacketHeader header) { - { - std::lock_guard lock(m_chunks_mutex); - ChunkPos pos{data.pos().x(), data.pos().z()}; - if (m_chunks.find(pos) != m_chunks.end()) { - Logger::warn("Chunk {} {} has already in client world", pos.x, - pos.z); - return; - } - } // vertex data will genrator in client thread pool instead of net thread; auto pool = m_thread_pool.load(); if (!pool) { Logger::error("Client Thread Pool is nullptr"); return; } - pool->enqueue([this, data = std::move(data)]() { - ClientChunk chunk{*this}; - chunk.receive_chunk(data); - { - std::lock_guard lock(m_pending_upload_queue_mutex); - m_pending_upload_queue.emplace_back(std::move(chunk)); - } - }); + pool->enqueue( + [this, raw_data = std::move(raw_data), header = std::move(header)]() { + Arena arena; + auto* data = Arena::Create(&arena); + if (!decode_packet(*data, raw_data, header)) { + return; + } + + if (data->task_id() < m_chunk_task_id) { + return; + } + + { + chunk_cacc cacc; + ChunkPos pos{data->pos().x(), data->pos().z()}; + if (m_chunks.find(cacc, pos)) { + Logger::warn("Chunk {} {} has already in client world", + pos.x, pos.z); + return; + } + } + + std::unique_ptr chunk = + std::make_unique(*this); + chunk->receive_chunk(*data); + + m_pending_upload_queue.emplace(std::move(chunk)); + }); } bool ClientWorld::is_receive_exit() { return m_receive_exit; } + +int ClientWorld::chunk_size() const { return m_chunks.size(); } + void ClientWorld::request_exit() { if (m_receive_exit) { return; @@ -512,51 +537,60 @@ void ClientWorld::update(float delta_time) { } m_pending_delete_vao.clear(); } - std::vector new_chunks; + std::vector> new_chunks; { - std::lock_guard lock(m_pending_upload_queue_mutex); - for (auto& c : m_pending_upload_queue) { - // Logger::info("{} {}", c.get_chunk_pos().x, c.get_chunk_pos().z); - new_chunks.emplace_back(std::move(c)); + std::unique_ptr chunk; + while (m_pending_upload_queue.try_pop(chunk)) { + new_chunks.emplace_back(std::move(chunk)); } - m_pending_upload_queue.clear(); } for (auto& c : new_chunks) { - c.upload_to_gpu(); + c->upload_to_gpu(); } { - std::lock_guard lock(m_chunks_mutex); + for (auto& c : new_chunks) { - m_chunks.emplace(c.get_chunk_pos(), std::move(c)); + m_chunks.emplace(c->get_chunk_pos(), std::move(c)); } m_render_snapshots.clear(); - for (auto& [pos, chunk] : m_chunks) { - if (chunk.is_dirty()) { + auto chunk_pos_set = m_player.get_chunk_pos_set(); + for (auto& pos : chunk_pos_set) { + std::shared_ptr chunk; + { + chunk_acc acc; + if (m_chunks.find(acc, pos)) { + chunk = acc->second; + } + } + if (!chunk) { + continue; + } + if (chunk->is_dirty()) { // the curial fator influence OptionalBlockVectorArray neighbor_block; for (int i = 0; i < 4; i++) { - auto it = m_chunks.find(pos + CHUNK_DIR[i]); - if (it != m_chunks.end()) { - neighbor_block[i] = (it->second.get_chunk_blocks()); + chunk_cacc cacc; + if (m_chunks.find(cacc, pos + CHUNK_DIR[i])) { + neighbor_block[i] = (cacc->second->get_chunk_blocks()); } else { neighbor_block[i] = std::nullopt; } } - chunk.gen_vertex_data(neighbor_block); - chunk.upload_to_gpu(); + chunk->gen_vertex_data(neighbor_block); + chunk->upload_to_gpu(); } - if (!chunk.is_dirty()) { - if (chunk.is_need_upload()) { - chunk.upload_to_gpu(); + if (!chunk->is_dirty()) { + if (chunk->is_need_upload()) { + chunk->upload_to_gpu(); } m_render_snapshots.push_back( - {chunk.get_normal_vao(), chunk.get_normal_vertices_sum(), - chunk.get_cross_vao(), chunk.get_cross_vertices_sum(), - chunk.get_normal_discard_vao(), - chunk.get_normal_discard_vertices_sum(), - chunk.get_normal_blend_vao(), - chunk.get_normal_blend_vertices_sum(), - chunk.get_water_vao(), chunk.get_water_vertices_sum(), + {chunk->get_normal_vao(), chunk->get_normal_vertices_sum(), + chunk->get_cross_vao(), chunk->get_cross_vertices_sum(), + chunk->get_normal_discard_vao(), + chunk->get_normal_discard_vertices_sum(), + chunk->get_normal_blend_vao(), + chunk->get_normal_blend_vertices_sum(), + chunk->get_water_vao(), chunk->get_water_vertices_sum(), glm::vec3(static_cast(pos.x * CHUNK_SIZE) + static_cast(CHUNK_SIZE / 2), static_cast(WORLD_SIZE_Y / 2), diff --git a/src/gameplay/network_client.cpp b/src/gameplay/network_client.cpp index 4217840..f47f068 100644 --- a/src/gameplay/network_client.cpp +++ b/src/gameplay/network_client.cpp @@ -59,6 +59,7 @@ asio::awaitable NetworkClient::read_loop() { throw std::runtime_error("invalid packet length"); } + // maybe move, don't use it after switch! std::vector body_data(header.compressed_size); if (header.compressed_size > 0) { co_await asio::async_read(m_socket, asio::buffer(body_data), @@ -80,12 +81,9 @@ asio::awaitable NetworkClient::read_loop() { } } break; case std::to_underlying(PacketEnum::CHUNK_DATA_RSP): { - ChunkDataRsp rsp; // Logger::info("Client: Receive Chunk Data rsp, size {}mb", // body_data.size() / 1024.0f / 1024); - if (decode_packet(rsp, body_data, header)) { - m_world.receive_chunk(std::move(rsp)); - } + m_world.receive_chunk(std::move(body_data), header); } break; case std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP): { auto* rsp = Arena::Create(&arena);