feat(gameplay): implement server stop and client exit handling

Add server_stop flag to LogoutRsp protocol. Modify client_world to check for server stop or own logout to set exit flag. Add ServerWorld::stop() to broadcast stop and cleanly shut down. Refactor chunk ownership to unique_ptr. Remove name parameter from get_look_block_pos.
This commit is contained in:
2026-06-28 15:27:51 +08:00
parent 429520b1f7
commit a5764e901b
7 changed files with 54 additions and 22 deletions

View File

@@ -29,8 +29,7 @@ public:
void init(std::string_view player_name, void init(std::string_view player_name,
std::shared_ptr<NetworkClient> client); std::shared_ptr<NetworkClient> client);
void update(float delta_time); void update(float delta_time);
const std::optional<LookBlock>& const std::optional<LookBlock>& get_look_block_pos() const;
get_look_block_pos(const std::string& name) const;
ClientPlayer& get_player(); ClientPlayer& get_player();
int get_block(const glm::ivec3& block_pos) const; int get_block(const glm::ivec3& block_pos) const;
bool is_solid(const glm::ivec3& block_pos) const; bool is_solid(const glm::ivec3& block_pos) const;
@@ -66,8 +65,9 @@ public:
const std::vector<RemotePlayerRenderData>& render_player_data() const; const std::vector<RemotePlayerRenderData>& render_player_data() const;
glm::vec3 sunlight_dir() const; glm::vec3 sunlight_dir() const;
void receive_chunk(ChunkDataRsp data); void receive_chunk(ChunkDataRsp data);
void receive_exit();
void request_exit(); void request_exit();
bool is_receive_exit();
template <typename Fn> template <typename Fn>
void register_timer(std::string_view id, TickType threshold, Fn&& f) { void register_timer(std::string_view id, TickType threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct, m_timers.emplace(std::piecewise_construct,

View File

@@ -26,6 +26,7 @@ public:
enum class ThreadPoolKind { NET, GEN }; enum class ThreadPoolKind { NET, GEN };
ServerWorld(); ServerWorld();
~ServerWorld(); ~ServerWorld();
void stop();
void handle_player_exit(const std::string& uuid); void handle_player_exit(const std::string& uuid);
void init_world(); void init_world();
void need_gen(std::string uuid); void need_gen(std::string uuid);
@@ -78,7 +79,6 @@ public:
void handle_block_change(const BlockChangeReq& req); void handle_block_change(const BlockChangeReq& req);
int chunk_size() const; int chunk_size() const;
template <typename Fn> template <typename Fn>
void register_timer(std::string_view id, TickType threshold, Fn&& f) { void register_timer(std::string_view id, TickType threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct, m_timers.emplace(std::piecewise_construct,
@@ -100,12 +100,12 @@ private:
ChunkPos pos; ChunkPos pos;
}; };
struct PendingChunk { struct PendingChunk {
ServerChunk chunk; std::unique_ptr<ServerChunk> chunk;
std::future<void> future; std::future<void> future;
}; };
struct FinishedChunk { struct FinishedChunk {
ChunkPos pos; ChunkPos pos;
std::shared_ptr<ServerChunk> chunk; std::unique_ptr<ServerChunk> chunk;
}; };
using ChunkHashMap = using ChunkHashMap =
@@ -136,6 +136,7 @@ private:
std::atomic<bool> m_need_gen_chunk{false}; std::atomic<bool> m_need_gen_chunk{false};
std::atomic<bool> m_is_rebuilding{false}; std::atomic<bool> m_is_rebuilding{false};
std::atomic<bool> m_init{false}; std::atomic<bool> m_init{false};
std::atomic<bool> m_stopped{false};
std::atomic<int> m_rendering_distance{24}; std::atomic<int> m_rendering_distance{24};
std::atomic<int> m_gen_pool_threads{0}; std::atomic<int> m_gen_pool_threads{0};
std::atomic<int> m_net_pool_threads{0}; std::atomic<int> m_net_pool_threads{0};
@@ -185,5 +186,6 @@ private:
int int
change_pool_threads(std::atomic<std::shared_ptr<ThreadPool>>& thread_pool, change_pool_threads(std::atomic<std::shared_ptr<ThreadPool>>& thread_pool,
int threads); int threads);
void send_server_stop();
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -301,11 +301,16 @@ void App::run() {
last_time = glfwGetTime(); last_time = glfwGetTime();
while (!glfwWindowShouldClose(m_window.get_glfw_window())) { while (!glfwWindowShouldClose(m_window.get_glfw_window())) {
if (m_client_world.is_receive_exit()) {
break;
}
update(); update();
render(); render();
} }
m_client_world.request_exit(); m_client_world.request_exit();
if (!m_argument.is_client) {
m_server.server_world().stop();
}
} }
static Gait player_gait = Gait::WALK; static Gait player_gait = Gait::WALK;
void App::update() { void App::update() {

View File

@@ -44,8 +44,7 @@ ClientWorld::~ClientWorld() {
m_timers.clear(); m_timers.clear();
} }
const std::optional<LookBlock>& const std::optional<LookBlock>& ClientWorld::get_look_block_pos() const {
ClientWorld::get_look_block_pos(const std::string& name) const {
return m_player.get_look_block_pos(); return m_player.get_look_block_pos();
} }
@@ -231,6 +230,14 @@ void ClientWorld::receive_remote_player(const PlayerInfoRsp& rsp) {
} }
void ClientWorld::receive_player_logout(const LogoutRsp& rsp) { void ClientWorld::receive_player_logout(const LogoutRsp& rsp) {
if (rsp.server_stop()) {
m_receive_exit = true;
return;
}
if (rsp.uuid() == m_player.get_uuid()) {
m_receive_exit = true;
return;
}
{ {
std::lock_guard lock(m_other_players_mutex); std::lock_guard lock(m_other_players_mutex);
int sum = m_other_players.erase(rsp.uuid()); int sum = m_other_players.erase(rsp.uuid());
@@ -240,9 +247,6 @@ void ClientWorld::receive_player_logout(const LogoutRsp& rsp) {
Logger::info("Player {} erase", rsp.uuid()); Logger::info("Player {} erase", rsp.uuid());
} }
} }
if (rsp.uuid() == m_player.get_uuid()) {
m_receive_exit = true;
}
} }
void ClientWorld::init(std::string_view player_name, void ClientWorld::init(std::string_view player_name,
@@ -465,8 +469,7 @@ void ClientWorld::receive_chunk(ChunkDataRsp data) {
} }
}); });
} }
void ClientWorld::receive_exit() { m_receive_exit = true; } bool ClientWorld::is_receive_exit() { return m_receive_exit; }
void ClientWorld::request_exit() { void ClientWorld::request_exit() {
if (m_receive_exit) { if (m_receive_exit) {
return; return;

View File

@@ -16,10 +16,16 @@ using namespace google::protobuf;
namespace Cubed { namespace Cubed {
ServerWorld::ServerWorld() {} ServerWorld::ServerWorld() {}
ServerWorld::~ServerWorld() { ServerWorld::~ServerWorld() { stop(); }
void ServerWorld::stop() {
if (!m_init) { if (!m_init) {
return; return;
} }
if (m_stopped.exchange(true)) {
return;
}
send_server_stop();
stop_gen_thread(); stop_gen_thread();
stop_server_thread(); stop_server_thread();
wait_all_chunk_tasks(); wait_all_chunk_tasks();
@@ -115,12 +121,14 @@ void ServerWorld::send_chunk(int task_id, const std::string& uuid,
Logger::error("Chunk {} {} is invaild", pos.x, pos.z); Logger::error("Chunk {} {} is invaild", pos.x, pos.z);
return; return;
} }
rsp->set_chunk_seed(it->second.chunk->seed()); rsp->set_chunk_seed(it->second.chunk->seed());
rsp->set_biome_type(std::to_underlying(it->second.chunk->biome())); rsp->set_biome_type(std::to_underlying(it->second.chunk->biome()));
auto* blocks = rsp->mutable_chunk_blocks(); auto* blocks = rsp->mutable_chunk_blocks();
auto& chunk_blocks = it->second.chunk->get_chunk_blocks(); auto& chunk_blocks = it->second.chunk->get_chunk_blocks();
blocks->Assign(chunk_blocks.begin(), chunk_blocks.end()); blocks->Assign(chunk_blocks.begin(), chunk_blocks.end());
auto& neighbor_blocks = it->second.chunk->get_neightbor_blocks(); auto& neighbor_blocks = it->second.chunk->get_neightbor_blocks();
auto assign = [](auto* nb, auto assign = [](auto* nb,
const std::optional<std::vector<BlockType>>& blocks) { const std::optional<std::vector<BlockType>>& blocks) {
if (!blocks) { if (!blocks) {
@@ -231,7 +239,8 @@ void ServerWorld::gen_chunks_internal(const std::string& uuid) {
// Create new chunk // Create new chunk
std::lock_guard lock(m_new_chunk_mutex); std::lock_guard lock(m_new_chunk_mutex);
for (auto& pos : need_gen_chunks_pos) { for (auto& pos : need_gen_chunks_pos) {
m_new_chunks.emplace(pos, ServerChunk(*this, pos)); m_new_chunks.emplace(
pos, std::make_unique<ServerChunk>(ServerChunk(*this, pos)));
} }
} }
@@ -292,7 +301,7 @@ void ServerWorld::submit_new_chunks(const std::string& uuid) {
for (auto& [pos, task] : m_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(); });
} }
} }
break; break;
@@ -320,7 +329,7 @@ void ServerWorld::submit_new_chunks(const std::string& uuid) {
for (auto& [pos, task] : tasks) { for (auto& [pos, task] : tasks) {
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(); });
} }
} }
} }
@@ -346,9 +355,8 @@ void ServerWorld::poll_finished_chunks() {
return true; return true;
} }
// Spawn complete, move away // Spawn complete, move away
m_new_finished_chunk.emplace_back( m_new_finished_chunk.emplace_back(pair.first,
pair.first, std::move(pending.chunk));
std::make_shared<ServerChunk>(std::move(pending.chunk)));
return true; return true;
}); });
@@ -683,6 +691,7 @@ void ServerWorld::handle_player_exit(const std::string& uuid) {
Arena arena; Arena arena;
auto* rsp = Arena::Create<LogoutRsp>(&arena); auto* rsp = Arena::Create<LogoutRsp>(&arena);
rsp->set_uuid(uuid); rsp->set_uuid(uuid);
rsp->set_server_stop(false);
exit_session->send(make_packet(*rsp)); exit_session->send(make_packet(*rsp));
std::vector<std::shared_ptr<Session>> sessions; std::vector<std::shared_ptr<Session>> sessions;
@@ -800,6 +809,18 @@ int ServerWorld::change_pool_threads(
thread_pool.store(std::make_shared<ThreadPool>(used_thread)); thread_pool.store(std::make_shared<ThreadPool>(used_thread));
return used_thread; return used_thread;
} }
void ServerWorld::send_server_stop() {
Arena arena;
auto* rsp = Arena::Create<LogoutRsp>(&arena);
rsp->set_server_stop(true);
std::shared_lock lock(m_player_mutex);
for (auto& [uuid, player] : m_players) {
player.get_session()->send(make_packet(*rsp));
}
Logger::info("Send Server Mesaage Success");
}
int ServerWorld::chunk_load_style() const { int ServerWorld::chunk_load_style() const {
return std::to_underlying(m_chunk_load_style.load()); return std::to_underlying(m_chunk_load_style.load());
} }

View File

@@ -15,5 +15,6 @@ message LogoutReq {
message LogoutRsp { message LogoutRsp {
string uuid = 1; string uuid = 1;
bool server_stop = 2;
} }

View File

@@ -276,7 +276,7 @@ void Renderer::render_outline() {
const auto& shader = get_shader("outline"); const auto& shader = get_shader("outline");
shader.use(); shader.use();
const auto& block_pos = m_world.get_look_block_pos("TestPlayer"); const auto& block_pos = m_world.get_look_block_pos();
if (block_pos != std::nullopt) { if (block_pos != std::nullopt) {