refactor(server): replace chunk set with flat_hash_set and add ref count

- Replace std::unordered_set with absl::flat_hash_set for chunk position sets.
- Add ref_count field to ChunkEntity and implement update_ref_count().
- Remove clear_unused_chunks() and use ref counting for chunk lifetime.
- Add get_chunk_pos_set() accessors to ServerPlayer.
This commit is contained in:
2026-06-28 20:11:14 +08:00
parent cbe3548dcd
commit 9788f68b12
4 changed files with 67 additions and 27 deletions

View File

@@ -2,20 +2,20 @@
#include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_time.hpp" #include "Cubed/gameplay/game_time.hpp"
#include <absl/container/flat_hash_set.h>
#include <atomic> #include <atomic>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <memory> #include <memory>
#include <shared_mutex> #include <shared_mutex>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <unordered_set>
namespace Cubed { namespace Cubed {
class ServerWorld; class ServerWorld;
class Session; class Session;
class ServerPlayer { class ServerPlayer {
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
public: public:
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
ServerPlayer(const ServerPlayer&) = delete; ServerPlayer(const ServerPlayer&) = delete;
ServerPlayer(ServerPlayer&&) = delete; ServerPlayer(ServerPlayer&&) = delete;
ServerPlayer& operator=(const ServerPlayer&) = delete; ServerPlayer& operator=(const ServerPlayer&) = delete;
@@ -23,7 +23,6 @@ public:
ServerPlayer(std::string_view name, std::string_view uuid, ServerPlayer(std::string_view name, std::string_view uuid,
ServerWorld& m_world, std::shared_ptr<Session> session, ServerWorld& m_world, std::shared_ptr<Session> session,
TickType gametick); TickType gametick);
using PlayerChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
const glm::vec3& get_pos() const; const glm::vec3& get_pos() const;
const std::string& get_name() const; const std::string& get_name() const;
@@ -36,6 +35,8 @@ public:
void task_id(int id); void task_id(int id);
bool has_player(ChunkPos pos) const; bool has_player(ChunkPos pos) const;
void update_chunk_set(const ChunkPosSet& set); void update_chunk_set(const ChunkPosSet& set);
const ChunkPosSet& get_chunk_pos_set() const;
ChunkPosSet& get_chunk_pos_set();
private: private:
static constexpr TickType TIMEOUT = 200; static constexpr TickType TIMEOUT = 200;
@@ -48,6 +49,6 @@ private:
std::atomic<TickType> m_last_gametick{0}; std::atomic<TickType> m_last_gametick{0};
std::atomic<int> m_chunk_task_id{0}; std::atomic<int> m_chunk_task_id{0};
mutable std::shared_mutex m_chunk_pos_mutex; mutable std::shared_mutex m_chunk_pos_mutex;
PlayerChunkPosSet m_player_chunk_pos_set; ChunkPosSet m_player_chunk_pos_set;
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -10,13 +10,13 @@
#include "Cubed/tools/thread_pool.hpp" #include "Cubed/tools/thread_pool.hpp"
#include "world/block_change.pb.h" #include "world/block_change.pb.h"
#include <absl/container/flat_hash_set.h>
#include <future> #include <future>
#include <shared_mutex> #include <shared_mutex>
#include <tbb/concurrent_hash_map.h> #include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h> #include <tbb/concurrent_queue.h>
#include <tbb/concurrent_unordered_map.h> #include <tbb/concurrent_unordered_map.h>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <utility> #include <utility>
#include <vector> #include <vector>
namespace Cubed { namespace Cubed {
@@ -91,6 +91,7 @@ private:
struct ChunkEntity { struct ChunkEntity {
ChunkState state; ChunkState state;
std::shared_ptr<ServerChunk> chunk; std::shared_ptr<ServerChunk> chunk;
uint32_t ref_count = 0;
}; };
enum class ChunkLoadStyle { RANDOM, CENTER }; enum class ChunkLoadStyle { RANDOM, CENTER };
@@ -113,7 +114,7 @@ private:
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>; using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
using PendingChunkHashMap = using PendingChunkHashMap =
std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>; std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>; using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>; using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
using uuid_acc = PlayerUUIDMap::accessor; using uuid_acc = PlayerUUIDMap::accessor;
@@ -177,7 +178,7 @@ private:
void poll_finished_chunks(); void poll_finished_chunks();
void wait_all_chunk_tasks(); void wait_all_chunk_tasks();
void clear_unused_chunks(); void update_ref_count(const ChunkPosSet& old, const ChunkPosSet& now);
void send_time(); void send_time();

View File

@@ -44,4 +44,13 @@ void ServerPlayer::update_chunk_set(const ChunkPosSet& set) {
m_player_chunk_pos_set.insert(set.begin(), set.end()); m_player_chunk_pos_set.insert(set.begin(), set.end());
} }
const ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() const {
std::shared_lock lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set;
}
ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() {
std::lock_guard lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set;
}
} // namespace Cubed } // namespace Cubed

View File

@@ -52,22 +52,44 @@ void ServerWorld::wait_all_chunk_tasks() {
} }
} }
void ServerWorld::clear_unused_chunks() { void ServerWorld::update_ref_count(const ChunkPosSet& old,
const ChunkPosSet& now) {
std::lock_guard lock(m_chunks_mutex);
std::scoped_lock lock(m_chunks_mutex, m_player_mutex); // Elements in the old set that are not contained in now are not needed by
Logger::info("before {}", m_chunks.size()); // the current player.
size_t removed = std::erase_if(m_chunks, [this](const auto& item) { for (auto& pos : old) {
const auto& [pos, chunk] = item; if (!now.contains(pos)) {
for (const auto& [uuid, player] : m_players) { auto it = m_chunks.find(pos);
if (player.has_player(pos)) { if (it == m_chunks.end()) {
return false; Logger::warn(
"Update Ref Count Error, can't Find old pos in m_chunks");
continue;
}
if (it->second.ref_count == 0) {
Logger::error("Chunk {} {} error, ref count is 0", pos.x,
pos.z);
m_chunks.erase(pos);
continue;
}
if (--it->second.ref_count == 0) {
m_chunks.erase(pos);
}
}
}
for (auto& pos : now) {
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
Logger::warn(
"Update Ref Count Error, can't Find now pos in m_chunks");
continue;
}
if (!old.contains(pos)) {
++it->second.ref_count;
} }
} }
return true;
});
Logger::info("removed: {}", removed);
Logger::info("after {}", m_chunks.size());
} }
void ServerWorld::send_time() { void ServerWorld::send_time() {
@@ -212,22 +234,25 @@ void ServerWorld::gen_chunks_internal(const std::string& uuid) {
// Logger::info("gen_chunks_internal"); // Logger::info("gen_chunks_internal");
m_chunk_gen_finished = false; m_chunk_gen_finished = false;
ChunkPosSet required_chunks; ChunkPosSet required_chunks_set;
compute_required_chunks(required_chunks, uuid); compute_required_chunks(required_chunks_set, uuid);
std::vector<ChunkPos> need_gen_chunks_pos; std::vector<ChunkPos> need_gen_chunks_pos;
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks); ChunkPosSet old_set;
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks_set);
{ {
std::lock_guard lock(m_player_mutex); std::lock_guard lock(m_player_mutex);
auto it = m_players.find(uuid); auto it = m_players.find(uuid);
if (it == m_players.end()) { if (it == m_players.end()) {
return; return;
} }
it->second.update_chunk_set(required_chunks); old_set = std::move(it->second.get_chunk_pos_set());
it->second.update_chunk_set(required_chunks_set);
} }
ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!");
clear_unused_chunks(); update_ref_count(old_set, required_chunks_set);
ASSERT_MSG(!required_chunks_set.empty(), "required chunks is empty!!");
Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size()); Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size());
if (need_gen_chunks_pos.empty() && m_new_chunks.empty()) { if (need_gen_chunks_pos.empty() && m_new_chunks.empty()) {
@@ -281,8 +306,8 @@ void ServerWorld::sync_and_collect_missing_chunks(
auto it = m_chunks.find(pos); auto it = m_chunks.find(pos);
if (it == m_chunks.end()) { if (it == m_chunks.end()) {
need_gen_chunks_pos.push_back(pos); need_gen_chunks_pos.push_back(pos);
m_chunks.emplace(pos, m_chunks.emplace(
ChunkEntity{ChunkState::GENERATING, nullptr}); pos, ChunkEntity{ChunkState::GENERATING, nullptr, 0});
} }
} }
} }
@@ -673,12 +698,14 @@ void ServerWorld::handle_player_login(const std::string& name,
void ServerWorld::handle_player_exit(const std::string& uuid) { void ServerWorld::handle_player_exit(const std::string& uuid) {
std::shared_ptr<Session> exit_session; std::shared_ptr<Session> exit_session;
ChunkPosSet old_set;
{ {
std::lock_guard lock(m_player_mutex); std::lock_guard lock(m_player_mutex);
auto it = m_players.find(uuid); auto it = m_players.find(uuid);
if (it != m_players.end()) { if (it != m_players.end()) {
Logger::info("Player {} Exit the Server", it->second.get_name()); Logger::info("Player {} Exit the Server", it->second.get_name());
exit_session = it->second.get_session(); exit_session = it->second.get_session();
old_set = std::move(it->second.get_chunk_pos_set());
m_players.erase(it); m_players.erase(it);
} else { } else {
Logger::error("Player {} isn't in Server", uuid); Logger::error("Player {} isn't in Server", uuid);
@@ -688,6 +715,8 @@ void ServerWorld::handle_player_exit(const std::string& uuid) {
m_uuid_to_name.erase(uuid); m_uuid_to_name.erase(uuid);
update_ref_count(old_set, {});
Arena arena; Arena arena;
auto* rsp = Arena::Create<LogoutRsp>(&arena); auto* rsp = Arena::Create<LogoutRsp>(&arena);
rsp->set_uuid(uuid); rsp->set_uuid(uuid);