feat(world): add player-based chunk loading and UUID support

Restructure world generation to trigger chunk loading based on player
movement. Replace player name with UUID for identification. Implement
chunk request/response protocol. Improve thread management for gen
thread.
This commit is contained in:
2026-06-24 14:51:48 +08:00
parent 6f39ff771d
commit 9d33b240c6
11 changed files with 222 additions and 83 deletions

View File

@@ -55,4 +55,10 @@ inline ChunkPos get_chunk_pos(int world_x, int world_z) {
return {chunk_x, chunk_z};
}
inline float distance2(const ChunkPos& a, const ChunkPos& b) {
float dx = static_cast<float>(a.x) - b.x;
float dz = static_cast<float>(a.z) - b.z;
return dx * dx + dz * dz;
}
} // namespace Cubed

View File

@@ -1,30 +1,48 @@
#pragma once
#include "packet.pb.h"
#include "packet.pb.h" // IWYU pragma: keep
#include <netinet/in.h>
#include <type_traits>
#include <utility>
namespace Cubed {
constexpr int HEADER_LEN = 8;
using Packet = std::shared_ptr<std::vector<uint8_t>>;
enum class PacketEnum {
LOGIN_REQ = 1001,
LOGIN_RSP = 1002,
PLAYER_INFO = 2001,
PLAYER_POS = 2002,
CHUNK_DATA_REQ = 3001,
CHUNK_DATA_RSP = 3002,
PING = 9001,
PONG = 9002
};
template <typename> struct always_false : std::false_type {}; // NOLINT
template <typename T> constexpr uint16_t get_packet_id() {
using enum PacketEnum;
using std::is_same_v;
using U = std::decay_t<T>;
constexpr auto& to_num = std::to_underlying<PacketEnum>;
if constexpr (is_same_v<U, LoginReq>) {
return 1001;
return to_num(LOGIN_REQ);
} else if constexpr (is_same_v<U, LoginRsp>) {
return 1002;
return to_num(LOGIN_RSP);
} else if constexpr (is_same_v<U, PlayerInfo>) {
return 2001;
return to_num(PLAYER_INFO);
} else if constexpr (is_same_v<U, PlayerPos>) {
return 2002;
} else if constexpr (is_same_v<U, ChunkData>) {
return 3001;
return to_num(PLAYER_POS);
} else if constexpr (is_same_v<U, ChunkDataReq>) {
return to_num(CHUNK_DATA_REQ);
} else if constexpr (is_same_v<U, ChunkDataRsp>) {
return to_num(CHUNK_DATA_RSP);
} else if constexpr (is_same_v<U, Ping>) {
return 9001;
return to_num(PING);
} else if (is_same_v<U, Pong>) {
return 9002;
return to_num(PONG);
} else {
static_assert(always_false<U>::value, "Unkonw Type");
}

View File

@@ -48,7 +48,7 @@ public:
unsigned seed() const;
BiomeConditions& conditions();
bool& has_cave();
const OptionalBlockVectorArray& get_neightbor_blocks() const;
static int index(int x, int y, int z);
static int index(const glm::vec3& pos);
@@ -69,7 +69,7 @@ private:
HeightMapArray m_heightmap;
// the index is a array of block id
std::vector<BlockType> m_blocks;
OptionalBlockVectorArray m_neightbor_blocks;
float frequency = 0.01f;
float height = 80;
unsigned m_seed = 0;

View File

@@ -1,17 +1,24 @@
#pragma once
#include "Cubed/gameplay/chunk_pos.hpp"
#include <glm/glm.hpp>
#include <string>
#include <string_view>
namespace Cubed {
class ServerWorld;
class ServerPlayer {
public:
explicit ServerPlayer(std::string_view);
ServerPlayer(std::string_view name, std::string_view uuid,
ServerWorld& m_world);
const glm::vec3& get_pos() const;
const std::string& get_name() const;
void update_pos(float x, float y, float z);
private:
std::string m_name;
std::string m_uuid;
glm::vec3 m_pos{0.0f};
ServerWorld& m_world;
ChunkPos m_last_chunk_pos{0, 0};
};
} // namespace Cubed

View File

@@ -11,6 +11,7 @@
#include <future>
#include <shared_mutex>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_unordered_map.h>
#include <unordered_map>
#include <unordered_set>
#include <utility>
@@ -21,10 +22,10 @@ class ServerWorld {
public:
ServerWorld();
~ServerWorld();
void player_join(const std::string& name);
void player_join(std::string_view name, std::string_view uuid);
void player_exit(const std::string& name);
void init_world();
void need_gen();
void need_gen(std::optional<std::string> uuid);
void update();
void hot_reload();
@@ -64,10 +65,12 @@ public:
void set_block(const glm::ivec3& block_pos, unsigned id);
void sync_player_pos(const std::string& name, float x, float y, float z);
void sync_player_pos(const std::string& uuid, float x, float y, float z);
void handle_player_login(const std::string& player_name,
std::shared_ptr<Session> session);
glm::vec3 get_player_pos(const std::string& name) const;
glm::vec3 get_player_pos(const std::string& uuid) const;
void handle_chunk_req(const std::string& uuid, ChunkPos pos);
private:
enum class ChunkLoadStyle { RANDOM, CENTER };
@@ -77,10 +80,15 @@ private:
};
using ChunkHashMap =
std::unordered_map<ChunkPos, ServerChunk, ChunkPos::Hash>;
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
using PlayerHashMap =
tbb::concurrent_unordered_map<std::string, ServerPlayer>;
using PendingChunkHashMap =
std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using PlayerSessionMap =
tbb::concurrent_hash_map<std::string, std::shared_ptr<Session>>;
using session_acc = PlayerSessionMap::accessor;
using session_cacc = PlayerSessionMap::const_accessor;
PlayerHashMap m_players;
ChunkHashMap m_chunks;
// Can only be used in the gen thread
@@ -90,10 +98,8 @@ private:
CaveCarver m_cave_carcer;
RiverWorm m_river_worm;
std::thread m_gen_thread;
std::thread m_server_thread;
std::stop_source m_server_stop_source;
std::jthread m_gen_thread;
std::jthread m_server_thread;
std::atomic<bool> m_chunk_gen_finished{false};
std::atomic<bool> m_could_gen{true};
@@ -101,7 +107,6 @@ private:
std::atomic<bool> m_need_gen_chunk{false};
std::atomic<bool> m_is_rebuilding{false};
std::atomic<int> m_rendering_distance{24};
std::atomic<int> m_pool_threads{0};
std::atomic<int> m_max_threads{1};
@@ -112,26 +117,27 @@ private:
std::shared_mutex m_chunks_mutex;
std::shared_mutex m_new_chunk_mutex;
std::mutex m_gen_signal_mutex;
std::mutex m_need_gen_queue_mutex;
std::condition_variable m_gen_cv;
std::deque<std::string> m_need_gen_queue;
std::atomic<std::shared_ptr<ThreadPool>> m_gen_thread_pool;
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::RANDOM};
std::optional<std::string> m_request_gen_name = std::nullopt;
tbb::concurrent_hash_map<std::string, std::shared_ptr<Session>>
m_player_session;
// key = uuid
PlayerSessionMap m_player_session;
tbb::concurrent_hash_map<std::string, std::string> m_uuid_to_name;
void init_chunks();
void gen_chunks_internal();
void gen_chunks_internal(std::optional<std::string> uuid);
void compute_required_chunks(ChunkPosSet& required_chunks);
void compute_required_chunks(ChunkPosSet& required_chunks,
const std::optional<std::string>& uuid);
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
const ChunkPosSet&);
void submit_new_chunks();
void submit_new_chunks(const std::optional<std::string>& uuid);
void poll_finished_chunks();
void wait_all_chunk_tasks();
};