refactor(server): enhance thread safety and session management

This commit is contained in:
2026-06-24 17:56:11 +08:00
parent 85341651b3
commit af8812916d
7 changed files with 137 additions and 70 deletions

View File

@@ -25,6 +25,7 @@ private:
int m_port = 25530; int m_port = 25530;
std::atomic<bool> m_stopped{false}; std::atomic<bool> m_stopped{false};
ServerWorld m_world; ServerWorld m_world;
std::mutex m_session_mutex;
asio::awaitable<void> listen(); asio::awaitable<void> listen();
void net_run(); void net_run();
}; };

View File

@@ -2,16 +2,20 @@
#include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/chunk_pos.hpp"
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <memory>
#include <string> #include <string>
#include <string_view> #include <string_view>
namespace Cubed { namespace Cubed {
class ServerWorld; class ServerWorld;
class Session;
class ServerPlayer { class ServerPlayer {
public: public:
ServerPlayer(std::string_view name, std::string_view uuid, ServerPlayer(std::string_view name, std::string_view uuid,
ServerWorld& m_world); ServerWorld& m_world, std::shared_ptr<Session> session);
const glm::vec3& get_pos() const; const glm::vec3& get_pos() const;
const std::string& get_name() const; const std::string& get_name() const;
const std::string& get_uuid() const;
std::shared_ptr<Session> get_session() const;
void update_pos(float x, float y, float z); void update_pos(float x, float y, float z);
private: private:
@@ -20,5 +24,6 @@ private:
glm::vec3 m_pos{0.0f}; glm::vec3 m_pos{0.0f};
ServerWorld& m_world; ServerWorld& m_world;
ChunkPos m_last_chunk_pos{0, 0}; ChunkPos m_last_chunk_pos{0, 0};
std::shared_ptr<Session> m_session;
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -24,7 +24,7 @@ public:
ServerWorld(); ServerWorld();
~ServerWorld(); ~ServerWorld();
void player_join(std::string_view name, std::string_view uuid); void player_join(std::string_view name, std::string_view uuid);
void player_exit(const std::string& name); void handle_player_exit(const std::string& name);
void init_world(); void init_world();
void need_gen(std::optional<std::string> uuid); void need_gen(std::optional<std::string> uuid);
void update(); void update();
@@ -81,20 +81,18 @@ private:
std::future<void> future; std::future<void> future;
}; };
using ChunkHashMap = using ChunkHashMap =
std::unordered_map<ChunkPos, ServerChunk, ChunkPos::Hash>; tbb::concurrent_unordered_map<ChunkPos, ServerChunk, ChunkPos::Hash>;
using PlayerHashMap = using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
tbb::concurrent_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 = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using PlayerSessionMap = using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
tbb::concurrent_hash_map<std::string, std::shared_ptr<Session>>; using uuid_acc = PlayerUUIDMap::accessor;
using session_acc = PlayerSessionMap::accessor; using uuid_cacc = PlayerUUIDMap::const_accessor;
using session_cacc = PlayerSessionMap::const_accessor; // key = uuid
PlayerHashMap m_players; PlayerHashMap m_players;
ChunkHashMap m_chunks; ChunkHashMap m_chunks;
// Can only be used in the gen thread PendingChunkHashMap m_new_chunks;
PendingChunkHashMap new_chunks;
std::vector<std::pair<ChunkPos, ServerChunk>> m_new_finished_chunk; std::vector<std::pair<ChunkPos, ServerChunk>> m_new_finished_chunk;
CaveCarver m_cave_carcer; CaveCarver m_cave_carcer;
@@ -119,8 +117,9 @@ private:
std::shared_mutex m_chunks_mutex; std::shared_mutex m_chunks_mutex;
std::shared_mutex m_new_chunk_mutex; std::shared_mutex m_new_chunk_mutex;
std::shared_mutex m_player_mutex;
std::mutex m_need_gen_queue_mutex; std::mutex m_need_gen_queue_mutex;
std::condition_variable m_gen_cv; std::condition_variable_any m_gen_cv;
std::deque<std::string> m_need_gen_queue; std::deque<std::string> m_need_gen_queue;
@@ -128,9 +127,7 @@ private:
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::RANDOM}; std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::RANDOM};
// key = uuid PlayerUUIDMap m_uuid_to_name;
PlayerSessionMap m_player_session;
tbb::concurrent_hash_map<std::string, std::string> m_uuid_to_name;
void init_chunks(); void init_chunks();
void gen_chunks_internal(std::optional<std::string> uuid); void gen_chunks_internal(std::optional<std::string> uuid);

View File

@@ -12,14 +12,25 @@ void NetworkServer::stop() {
if (m_stopped.exchange(true)) { if (m_stopped.exchange(true)) {
return; return;
} }
for (auto& [key, s] : m_session) {
if (s) {
s->close();
}
}
m_io.stop(); m_io.stop();
std::vector<std::shared_ptr<Session>> sessions;
{
std::lock_guard lock(m_session_mutex);
for (auto& [id, s] : m_session) {
sessions.push_back(s);
}
m_session.clear(); m_session.clear();
}
for (auto& s : sessions) {
s->close();
}
if (m_net_thread.joinable()) { if (m_net_thread.joinable()) {
m_net_thread.join(); m_net_thread.join();
} }
@@ -27,22 +38,36 @@ void NetworkServer::stop() {
} }
asio::awaitable<void> NetworkServer::listen() { asio::awaitable<void> NetworkServer::listen() {
try {
tcp::acceptor acceptor(m_io, tcp::endpoint(tcp::v4(), m_port)); tcp::acceptor acceptor(m_io, tcp::endpoint(tcp::v4(), m_port));
while (true) { while (true) {
tcp::socket socket = tcp::socket socket =
co_await acceptor.async_accept(asio::use_awaitable); co_await acceptor.async_accept(asio::use_awaitable);
if (m_stopped) {
break;
}
std::shared_ptr<Session> s = std::shared_ptr<Session> s =
std::make_shared<Session>(std::move(socket), m_world); std::make_shared<Session>(std::move(socket), m_world);
s->start(); {
std::lock_guard lock(m_session_mutex);
m_session.emplace(s->uuid(), s); m_session.emplace(s->uuid(), s);
} }
s->start();
}
} catch (const std::exception& e) {
if (!m_stopped) {
Logger::error("accept error {}", e.what());
}
} catch (...) {
Logger::error("Network Server: Unkown Error");
}
co_return; co_return;
} }
void NetworkServer::net_run() { void NetworkServer::net_run() {
if (m_net_thread.joinable()) {
return;
}
m_net_thread = std::thread([this]() { m_net_thread = std::thread([this]() {
asio::co_spawn(m_io, listen(), asio::detached); asio::co_spawn(m_io, listen(), asio::detached);
m_io.run(); m_io.run();

View File

@@ -3,10 +3,12 @@
#include "Cubed/gameplay/server_world.hpp" #include "Cubed/gameplay/server_world.hpp"
namespace Cubed { namespace Cubed {
ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid, ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid,
ServerWorld& world) ServerWorld& world, std::shared_ptr<Session> session)
: m_name(name), m_uuid(uuid), m_world(world) {} : m_name(name), m_uuid(uuid), m_world(world), m_session(session) {}
const glm::vec3& ServerPlayer::get_pos() const { return m_pos; } const glm::vec3& ServerPlayer::get_pos() const { return m_pos; }
const std::string& ServerPlayer::get_name() const { return m_name; } const std::string& ServerPlayer::get_name() const { return m_name; }
const std::string& ServerPlayer::get_uuid() const { return m_uuid; }
std::shared_ptr<Session> ServerPlayer::get_session() const { return m_session; }
void ServerPlayer::update_pos(float x, float y, float z) { void ServerPlayer::update_pos(float x, float y, float z) {
m_pos = glm::vec3{x, y, z}; m_pos = glm::vec3{x, y, z};
ChunkPos chunk_pos = get_chunk_pos(x, z); ChunkPos chunk_pos = get_chunk_pos(x, z);

View File

@@ -24,7 +24,8 @@ ServerWorld::~ServerWorld() {
} }
void ServerWorld::wait_all_chunk_tasks() { void ServerWorld::wait_all_chunk_tasks() {
for (auto& [pos, task] : new_chunks) { std::lock_guard lock(m_new_chunk_mutex);
for (auto& [pos, task] : m_new_chunks) {
task.future.get(); task.future.get();
} }
} }
@@ -71,9 +72,11 @@ void ServerWorld::gen_chunks_internal(std::optional<std::string> uuid) {
return; return;
} }
{
std::lock_guard lock(m_new_chunk_mutex);
for (auto& pos : need_gen_chunks_pos) { for (auto& pos : need_gen_chunks_pos) {
new_chunks.emplace(pos, ServerChunk(*this, pos)); m_new_chunks.emplace(pos, ServerChunk(*this, pos));
}
} }
submit_new_chunks(uuid); submit_new_chunks(uuid);
@@ -109,7 +112,7 @@ void ServerWorld::sync_and_collect_missing_chunks(
std::lock_guard lk(m_chunks_mutex); std::lock_guard lk(m_chunks_mutex);
for (auto it = m_chunks.begin(); it != m_chunks.end();) { for (auto it = m_chunks.begin(); it != m_chunks.end();) {
if (required_chunks.find(it->first) == required_chunks.end()) { if (required_chunks.find(it->first) == required_chunks.end()) {
it = m_chunks.erase(it); it = m_chunks.unsafe_erase(it);
} else { } else {
++it; ++it;
} }
@@ -131,7 +134,7 @@ void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
} }
switch (m_chunk_load_style) { switch (m_chunk_load_style) {
case RANDOM: case RANDOM:
for (auto& [pos, task] : new_chunks) { for (auto& [pos, task] : m_new_chunks) {
if (!task.future.valid()) { if (!task.future.valid()) {
task.future = task.future =
pool_ptr->enqueue([&task]() { task.chunk.gen_chunk(); }); pool_ptr->enqueue([&task]() { task.chunk.gen_chunk(); });
@@ -140,7 +143,7 @@ void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
break; break;
case CENTER: { case CENTER: {
std::vector<std::pair<ChunkPos, PendingChunk*>> tasks; std::vector<std::pair<ChunkPos, PendingChunk*>> tasks;
for (auto& [pos, task] : new_chunks) { for (auto& [pos, task] : m_new_chunks) {
if (!task.future.valid()) { if (!task.future.valid()) {
tasks.emplace_back(pos, &task); tasks.emplace_back(pos, &task);
} }
@@ -177,7 +180,7 @@ void ServerWorld::poll_finished_chunks() {
m_new_finished_chunk.clear(); m_new_finished_chunk.clear();
std::lock_guard lock(m_new_chunk_mutex); std::lock_guard lock(m_new_chunk_mutex);
std::erase_if( std::erase_if(
new_chunks, [&](std::pair<const ChunkPos, PendingChunk>& pair) { m_new_chunks, [&](std::pair<const ChunkPos, PendingChunk>& pair) {
auto& pending = pair.second; auto& pending = pair.second;
if (!pending.future.valid()) { if (!pending.future.valid()) {
return false; return false;
@@ -200,9 +203,9 @@ void ServerWorld::start_gen_thread() {
while (!token.stop_requested()) { while (!token.stop_requested()) {
std::unique_lock<std::mutex> lk(m_need_gen_queue_mutex); std::unique_lock<std::mutex> lk(m_need_gen_queue_mutex);
m_gen_cv.wait(lk, [this](std::stop_token token) { m_gen_cv.wait(lk, token, [this]() {
return m_need_gen_chunk.load() || !m_gen_running || return m_need_gen_chunk.load() || !m_gen_running ||
!m_need_gen_queue.empty() || token.stop_requested(); !m_need_gen_queue.empty();
}); });
if (!m_gen_running) { if (!m_gen_running) {
break; break;
@@ -330,10 +333,9 @@ void ServerWorld::hot_reload() {
} }
void ServerWorld::rebuild_world() { void ServerWorld::rebuild_world() {
if (m_is_rebuilding) { if (m_is_rebuilding.exchange(true)) {
return; return;
} }
m_is_rebuilding = true;
stop_gen_thread(); stop_gen_thread();
stop_thread_pool(); stop_thread_pool();
m_cave_carcer.reload(ChunkGenerator::seed()); m_cave_carcer.reload(ChunkGenerator::seed());
@@ -343,22 +345,40 @@ void ServerWorld::rebuild_world() {
m_chunks.clear(); m_chunks.clear();
m_new_finished_chunk.clear(); m_new_finished_chunk.clear();
} }
{
std::lock_guard lock(m_new_chunk_mutex);
m_new_chunks.clear();
}
m_could_gen = true; m_could_gen = true;
ChunkGenerator::reload(); ChunkGenerator::reload();
start_thread_pool(); start_thread_pool();
start_gen_thread(); start_gen_thread();
need_gen(std::nullopt); need_gen(std::nullopt);
m_is_rebuilding = false; m_is_rebuilding = false;
} }
void ServerWorld::update() { poll_finished_chunks(); } void ServerWorld::update() {
poll_finished_chunks();
{
std::lock_guard lk(m_chunks_mutex);
bool consumed = false;
for (auto& x : m_new_finished_chunk) {
m_chunks.emplace(x.first, std::move(x.second));
consumed = true;
}
if (consumed) {
m_could_gen = true;
}
}
}
void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y, void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y,
float z) { float z) {
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::warn("Player {} is not in this Server", it->first); Logger::warn("Player {} is not in this Server", uuid);
return; return;
} }
it->second.update_pos(x, y, z); it->second.update_pos(x, y, z);
@@ -367,31 +387,36 @@ void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y,
void ServerWorld::handle_player_login(const std::string& name, void ServerWorld::handle_player_login(const std::string& name,
std::shared_ptr<Session> session) { std::shared_ptr<Session> session) {
std::string uuid = generate_uuid(); std::string uuid = generate_uuid();
player_join(name, uuid); Logger::info("Player {} (uuid {}) join the world", name, uuid);
m_player_session.emplace(uuid, session); {
std::lock_guard lock(m_player_mutex);
m_players.emplace(std::piecewise_construct,
std::forward_as_tuple(std::string(uuid)),
std::forward_as_tuple(name, uuid, *this, session));
}
m_uuid_to_name.emplace(uuid, name); m_uuid_to_name.emplace(uuid, name);
LoginRsp rsp; LoginRsp rsp;
rsp.set_success(true); rsp.set_success(true);
rsp.set_uuid(uuid); rsp.set_uuid(uuid);
session->send(make_packet(name)); session->send(make_packet(rsp));
} }
void ServerWorld::player_join(std::string_view name, std::string_view uuid) { void ServerWorld::handle_player_exit(const std::string& uuid) {
Logger::info("Player {} (uuid {}) join the world", name, uuid); {
m_players.emplace(std::piecewise_construct, std::lock_guard lock(m_player_mutex);
std::forward_as_tuple(std::string(uuid)), auto it = m_players.find(uuid);
std::forward_as_tuple(name, uuid, *this)); if (it != m_players.end()) {
}
void ServerWorld::player_exit(const std::string& name) { m_players.erase(it);
auto it = m_players.find(name); } else {
if (it == m_players.end()) { Logger::error("Player {} isn't in Server", uuid);
Logger::error("Player {} isn't in Server", it->first);
} }
m_players.unsafe_erase(it); }
m_uuid_to_name.erase(uuid);
} }
glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const { glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const {
std::shared_lock 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::error("Can't find player uuid {}", uuid); Logger::error("Can't find player uuid {}", uuid);
@@ -437,9 +462,10 @@ void ServerWorld::handle_chunk_req(const std::string& uuid, ChunkPos pos) {
} }
std::shared_ptr<Session> s; std::shared_ptr<Session> s;
{ {
session_cacc cacc; std::shared_lock lock(m_player_mutex);
if (m_player_session.find(cacc, uuid)) { auto it = m_players.find(uuid);
s = cacc->second; if (it != m_players.end()) {
s = it->second.get_session();
} }
} }
if (!s) { if (!s) {
@@ -462,13 +488,16 @@ void ServerWorld::handle_block_change(const BlockChangeReq& req) {
pos->set_y(y); pos->set_y(y);
pos->set_z(z); pos->set_z(z);
rsp.set_block(req.block()); rsp.set_block(req.block());
{
for (auto& [uuid, session] : m_player_session) { std::shared_lock lock(m_player_mutex);
for (auto& [uuid, player] : m_players) {
auto session = player.get_session();
if (session) { if (session) {
session->send(make_packet(rsp)); session->send(make_packet(rsp));
} }
} }
} }
}
int ServerWorld::rendering_distance() const { int ServerWorld::rendering_distance() const {
return m_rendering_distance.load(); return m_rendering_distance.load();

View File

@@ -88,7 +88,15 @@ asio::awaitable<void> Session::read_loop() {
} }
} }
} catch (const asio::system_error& e) { } catch (const asio::system_error& e) {
Logger::warn("Catch Asio Error {}", e.what()); auto ec = e.code();
if (ec == asio::error::eof || ec == asio::error::operation_aborted) {
Logger::info("Client disconnected");
} else {
Logger::warn("Asio Error {}", e.what());
}
close(); close();
} catch (...) { } catch (...) {
Logger::error("Unknow Error"); Logger::error("Unknow Error");