diff --git a/include/Cubed/gameplay/chunk_pos.hpp b/include/Cubed/gameplay/chunk_pos.hpp index 8b1bef1..6d5f549 100644 --- a/include/Cubed/gameplay/chunk_pos.hpp +++ b/include/Cubed/gameplay/chunk_pos.hpp @@ -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(a.x) - b.x; + float dz = static_cast(a.z) - b.z; + return dx * dx + dz * dz; +} + } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/packet.hpp b/include/Cubed/gameplay/packet.hpp index 22237d7..4e6cfa1 100644 --- a/include/Cubed/gameplay/packet.hpp +++ b/include/Cubed/gameplay/packet.hpp @@ -1,30 +1,48 @@ #pragma once -#include "packet.pb.h" +#include "packet.pb.h" // IWYU pragma: keep #include #include +#include namespace Cubed { constexpr int HEADER_LEN = 8; using Packet = std::shared_ptr>; + +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 struct always_false : std::false_type {}; // NOLINT template constexpr uint16_t get_packet_id() { - + using enum PacketEnum; using std::is_same_v; + using U = std::decay_t; + constexpr auto& to_num = std::to_underlying; if constexpr (is_same_v) { - return 1001; + return to_num(LOGIN_REQ); } else if constexpr (is_same_v) { - return 1002; + return to_num(LOGIN_RSP); } else if constexpr (is_same_v) { - return 2001; + return to_num(PLAYER_INFO); } else if constexpr (is_same_v) { - return 2002; - } else if constexpr (is_same_v) { - return 3001; + return to_num(PLAYER_POS); + } else if constexpr (is_same_v) { + return to_num(CHUNK_DATA_REQ); + } else if constexpr (is_same_v) { + return to_num(CHUNK_DATA_RSP); } else if constexpr (is_same_v) { - return 9001; + return to_num(PING); } else if (is_same_v) { - return 9002; + return to_num(PONG); } else { static_assert(always_false::value, "Unkonw Type"); } diff --git a/include/Cubed/gameplay/server_chunk.hpp b/include/Cubed/gameplay/server_chunk.hpp index df996e9..eb5be60 100644 --- a/include/Cubed/gameplay/server_chunk.hpp +++ b/include/Cubed/gameplay/server_chunk.hpp @@ -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 m_blocks; - + OptionalBlockVectorArray m_neightbor_blocks; float frequency = 0.01f; float height = 80; unsigned m_seed = 0; diff --git a/include/Cubed/gameplay/server_player.hpp b/include/Cubed/gameplay/server_player.hpp index 45364ec..f1a7447 100644 --- a/include/Cubed/gameplay/server_player.hpp +++ b/include/Cubed/gameplay/server_player.hpp @@ -1,17 +1,24 @@ #pragma once +#include "Cubed/gameplay/chunk_pos.hpp" + #include #include #include 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 diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index 67d089b..7bb98a1 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -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 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); - 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; - using PlayerHashMap = std::unordered_map; + using PlayerHashMap = + tbb::concurrent_unordered_map; using PendingChunkHashMap = std::unordered_map; using ChunkPosSet = std::unordered_set; + using PlayerSessionMap = + tbb::concurrent_hash_map>; + 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 m_chunk_gen_finished{false}; std::atomic m_could_gen{true}; @@ -101,7 +107,6 @@ private: std::atomic m_need_gen_chunk{false}; std::atomic m_is_rebuilding{false}; std::atomic m_rendering_distance{24}; - std::atomic m_pool_threads{0}; std::atomic 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 m_need_gen_queue; + std::atomic> m_gen_thread_pool; std::atomic m_chunk_load_style{ChunkLoadStyle::RANDOM}; - std::optional m_request_gen_name = std::nullopt; - - tbb::concurrent_hash_map> - m_player_session; - + // key = uuid + PlayerSessionMap m_player_session; + tbb::concurrent_hash_map m_uuid_to_name; void init_chunks(); - void gen_chunks_internal(); + void gen_chunks_internal(std::optional uuid); - void compute_required_chunks(ChunkPosSet& required_chunks); + void compute_required_chunks(ChunkPosSet& required_chunks, + const std::optional& uuid); void sync_and_collect_missing_chunks(std::vector&, const ChunkPosSet&); - void submit_new_chunks(); + void submit_new_chunks(const std::optional& uuid); void poll_finished_chunks(); void wait_all_chunk_tasks(); }; diff --git a/src/gameplay/server_chunk.cpp b/src/gameplay/server_chunk.cpp index ddd40e0..aba890e 100644 --- a/src/gameplay/server_chunk.cpp +++ b/src/gameplay/server_chunk.cpp @@ -156,11 +156,10 @@ void ServerChunk::gen_chunk() { gen_phase_two(); gen_phase_three(); - OptionalBlockVectorArray neightbor_blocks; for (int i = 0; i < 4; i++) { - neightbor_blocks[i] = neighbor[i].get_chunk_blocks(); + m_neightbor_blocks[i] = neighbor[i].get_chunk_blocks(); } - gen_phase_four(neightbor_blocks); + gen_phase_four(m_neightbor_blocks); gen_phase_five(); } // Logger::info("Cross Sum {}", m_cross_vertices_sum.load()); @@ -169,6 +168,10 @@ bool ServerChunk::is_temp_chunk() const { return m_temp_chunk.load(); } bool& ServerChunk::has_cave() { return m_has_cave; } +const OptionalBlockVectorArray& ServerChunk::get_neightbor_blocks() const { + return m_neightbor_blocks; +} + void ServerChunk::set_chunk_block(int index, unsigned id) { m_blocks[index] = id; } diff --git a/src/gameplay/server_player.cpp b/src/gameplay/server_player.cpp index d8212d8..69dc750 100644 --- a/src/gameplay/server_player.cpp +++ b/src/gameplay/server_player.cpp @@ -1,10 +1,18 @@ #include "Cubed/gameplay/server_player.hpp" +#include "Cubed/gameplay/server_world.hpp" namespace Cubed { -ServerPlayer::ServerPlayer(std::string_view name) : m_name(name) {} +ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid, + ServerWorld& world) + : m_name(name), m_uuid(uuid), m_world(world) {} const glm::vec3& ServerPlayer::get_pos() const { return m_pos; } const std::string& ServerPlayer::get_name() const { return m_name; } void ServerPlayer::update_pos(float x, float y, float z) { m_pos = glm::vec3{x, y, z}; + ChunkPos chunk_pos = get_chunk_pos(x, z); + float dist = distance2(chunk_pos, m_last_chunk_pos); + if (dist > 2) { + m_world.need_gen(m_uuid); + } } } // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp index dbbf1be..99ab6f8 100644 --- a/src/gameplay/server_world.cpp +++ b/src/gameplay/server_world.cpp @@ -5,6 +5,7 @@ #include "Cubed/gameplay/session.hpp" #include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/log.hpp" +#include "Cubed/tools/uuid.hpp" #include using namespace std::chrono; @@ -58,12 +59,12 @@ void ServerWorld::init_chunks() { } } -void ServerWorld::gen_chunks_internal() { +void ServerWorld::gen_chunks_internal(std::optional uuid) { // Logger::info("gen_chunks_internal"); m_chunk_gen_finished = false; ChunkPosSet required_chunks; - compute_required_chunks(required_chunks); + compute_required_chunks(required_chunks, uuid); ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!"); @@ -83,17 +84,17 @@ void ServerWorld::gen_chunks_internal() { new_chunks.emplace(pos, ServerChunk(*this, pos)); } - submit_new_chunks(); + submit_new_chunks(uuid); m_chunk_gen_finished = true; - m_request_gen_name = std::nullopt; } -void ServerWorld::compute_required_chunks(ChunkPosSet& required_chunks) { +void ServerWorld::compute_required_chunks( + ChunkPosSet& required_chunks, const std::optional& uuid) { glm::vec3 player_pos; - if (m_request_gen_name == std::nullopt) { + if (uuid == std::nullopt) { player_pos = glm::vec3{0.0f}; } else { - player_pos = get_player_pos(m_request_gen_name.value()); + player_pos = get_player_pos(uuid.value()); } int x = std::floor(player_pos.x); int z = std::floor(player_pos.z); @@ -129,7 +130,7 @@ void ServerWorld::sync_and_collect_missing_chunks( } } } -void ServerWorld::submit_new_chunks() { +void ServerWorld::submit_new_chunks(const std::optional& uuid) { using enum ChunkLoadStyle; std::lock_guard lock(m_new_chunk_mutex); auto pool_ptr = m_gen_thread_pool.load(); @@ -153,10 +154,10 @@ void ServerWorld::submit_new_chunks() { } } glm::vec3 player_pos; - if (m_request_gen_name == std::nullopt) { + if (uuid == std::nullopt) { player_pos = glm::vec3{0.0f}; } else { - player_pos = get_player_pos(m_request_gen_name.value()); + player_pos = get_player_pos(uuid.value()); } auto dist2 = [player_pos](ChunkPos chunk_pos) { ChunkPos player_chunk_pos = @@ -203,27 +204,35 @@ void ServerWorld::poll_finished_chunks() { void ServerWorld::start_gen_thread() { m_gen_running = true; Logger::info("Gen Thread Started"); - m_gen_thread = std::thread([this]() { - while (m_gen_running) { - std::unique_lock lk(m_gen_signal_mutex); + m_gen_thread = std::jthread([this](std::stop_token token) { + while (!token.stop_requested()) { + std::unique_lock lk(m_need_gen_queue_mutex); - m_gen_cv.wait(lk, [this]() { - return m_need_gen_chunk.load() || !m_gen_running; + m_gen_cv.wait(lk, [this](std::stop_token token) { + return m_need_gen_chunk.load() || !m_gen_running || + !m_need_gen_queue.empty() || token.stop_requested(); }); if (!m_gen_running) { break; } + if (token.stop_requested()) { + break; + } m_need_gen_chunk = false; + std::optional uuid{std::nullopt}; + if (!m_need_gen_queue.empty()) { + uuid = m_need_gen_queue.front(); + m_need_gen_queue.pop_front(); + } lk.unlock(); - - gen_chunks_internal(); + gen_chunks_internal(uuid); } }); } void ServerWorld::start_server_thread() { - m_server_thread = std::thread( - [this]() { serever_run(m_server_stop_source.get_token()); }); + m_server_thread = + std::jthread([this](std::stop_token token) { serever_run(token); }); } void ServerWorld::start_thread_pool() { @@ -238,6 +247,7 @@ void ServerWorld::start_thread_pool() { void ServerWorld::stop_gen_thread() { m_gen_running = false; m_gen_cv.notify_all(); + m_gen_thread.request_stop(); if (m_gen_thread.joinable()) { m_gen_thread.join(); } @@ -245,7 +255,7 @@ void ServerWorld::stop_gen_thread() { } void ServerWorld::stop_server_thread() { - m_server_stop_source.request_stop(); + m_server_thread.request_stop(); if (m_server_thread.joinable()) { m_server_thread.join(); } @@ -273,20 +283,22 @@ void ServerWorld::serever_run(std::stop_token stoken) { Logger::info("Server Thread Stopped!"); } -void ServerWorld::need_gen() { +void ServerWorld::need_gen(std::optional uuid) { - if (!m_could_gen) { - Logger::warn("It is generating or consuming new chunks"); - return; - } + // if (!m_could_gen) { + // Logger::warn("It is generating or consuming new chunks"); + // return; + // } m_could_gen = false; - { - // std::lock_guard lk(m_gen_player_pos_mutex); - // m_gen_player_pos = get_player("TestPlayer").get_player_pos(); - ASSERT_MSG(false, "Player Pos Handle"); + + if (uuid) { + std::lock_guard lock(m_need_gen_queue_mutex); + m_need_gen_queue.push_back(*uuid); } + // m_gen_player_pos = get_player("TestPlayer").get_player_pos(); + m_need_gen_chunk = true; m_gen_cv.notify_one(); @@ -321,7 +333,7 @@ void ServerWorld::hot_reload() { auto& config = Config::get(); int dist = config.get("world.rendering_distance"); m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE; - need_gen(); + need_gen(std::nullopt); } void ServerWorld::rebuild_world() { @@ -342,16 +354,16 @@ void ServerWorld::rebuild_world() { ChunkGenerator::reload(); start_thread_pool(); start_gen_thread(); - need_gen(); + need_gen(std::nullopt); m_is_rebuilding = false; } void ServerWorld::update() { poll_finished_chunks(); } -void ServerWorld::sync_player_pos(const std::string& name, float x, float y, +void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y, float z) { - auto it = m_players.find(name); + auto it = m_players.find(uuid); if (it == m_players.end()) { Logger::warn("Player {} is not in this Server", it->first); return; @@ -361,33 +373,89 @@ void ServerWorld::sync_player_pos(const std::string& name, float x, float y, void ServerWorld::handle_player_login(const std::string& name, std::shared_ptr session) { - player_join(name); - m_player_session.emplace(name, session); + std::string uuid = generate_uuid(); + player_join(name, uuid); + m_player_session.emplace(uuid, session); + m_uuid_to_name.emplace(uuid, name); LoginRsp rsp; rsp.set_success(true); - rsp.set_uuid(name); + rsp.set_uuid(uuid); session->send(make_packet(name)); } -void ServerWorld::player_join(const std::string& name) { - m_players.emplace(name, name); +void ServerWorld::player_join(std::string_view name, std::string_view uuid) { + Logger::info("Player {} (uuid {}) join the world", name, uuid); + m_players.emplace(std::piecewise_construct, + std::forward_as_tuple(std::string(uuid)), + std::forward_as_tuple(name, uuid, *this)); } + void ServerWorld::player_exit(const std::string& name) { auto it = m_players.find(name); if (it == m_players.end()) { Logger::error("Player {} isn't in Server", it->first); } - m_players.erase(it); + m_players.unsafe_erase(it); } -glm::vec3 ServerWorld::get_player_pos(const std::string& name) const { - auto it = m_players.find(name); +glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const { + auto it = m_players.find(uuid); if (it == m_players.end()) { + Logger::error("Can't find player uuid {}", uuid); return glm::vec3{0.0f}; } return it->second.get_pos(); } +void ServerWorld::handle_chunk_req(const std::string& uuid, ChunkPos pos) { + ChunkDataRsp rsq; + auto* rsq_pos = rsq.mutable_pos(); + rsq_pos->set_x(pos.x); + rsq_pos->set_z(pos.z); + { + std::shared_lock lock(m_chunks_mutex); + auto it = m_chunks.find(pos); + if (it == m_chunks.end()) { + return; + } + rsq.set_chunk_seed(it->second.seed()); + auto* blocks = rsq.mutable_chunk_blocks(); + auto& chunk_blocks = it->second.get_chunk_blocks(); + blocks->Assign(chunk_blocks.begin(), chunk_blocks.end()); + auto& neighbor_blocks = it->second.get_neightbor_blocks(); + auto assign = [](auto* nb, + const std::optional>& blocks) { + if (!blocks) { + return; + } + if (!nb) { + return; + } + nb->Assign(blocks->begin(), blocks->end()); + }; + auto* nb1 = rsq.mutable_neighbor_blocks_1(); + auto* nb2 = rsq.mutable_neighbor_blocks_2(); + auto* nb3 = rsq.mutable_neighbor_blocks_3(); + auto* nb4 = rsq.mutable_neighbor_blocks_4(); + assign(nb1, neighbor_blocks[1]); + assign(nb2, neighbor_blocks[2]); + assign(nb3, neighbor_blocks[3]); + assign(nb4, neighbor_blocks[4]); + } + std::shared_ptr s; + { + session_cacc cacc; + if (m_player_session.find(cacc, uuid)) { + s = cacc->second; + } + } + if (!s) { + Logger::error("Player {} session not exist", uuid); + return; + } + s->send(make_packet(rsq)); +} + int ServerWorld::rendering_distance() const { return m_rendering_distance.load(); } diff --git a/src/gameplay/session.cpp b/src/gameplay/session.cpp index 51e7f32..3933e21 100644 --- a/src/gameplay/session.cpp +++ b/src/gameplay/session.cpp @@ -52,15 +52,33 @@ asio::awaitable Session::read_loop() { throw std::runtime_error("invalid packet length"); } uint32_t body_len = total_len - HEADER_LEN; - std::vector body_data(body_len); + std::vector body_data(body_len); if (body_len > 0) { co_await asio::async_read(m_socket, asio::buffer(body_data), asio::use_awaitable); } - - if (cmd_id == 1001) { + constexpr auto& to_num = std::to_underlying; + if (cmd_id == to_num(PacketEnum::LOGIN_REQ)) { + LoginReq req; + if (req.ParseFromArray(body_data.data(), body_data.size())) { + m_server_world.handle_player_login(req.name(), + shared_from_this()); + } } - if (cmd_id == 1002) { + if (cmd_id == to_num(PacketEnum::PLAYER_POS)) { + PlayerPos pos; + if (pos.ParseFromArray(body_data.data(), body_data.size())) { + m_server_world.sync_player_pos(pos.uuid(), pos.pos().x(), + pos.pos().y(), + pos.pos().z()); + } + } + if (cmd_id == to_num(PacketEnum::CHUNK_DATA_REQ)) { + ChunkDataReq req; + if (req.ParseFromArray(body_data.data(), body_data.size())) { + m_server_world.handle_chunk_req( + req.uuid(), ChunkPos(req.pos().x(), req.pos().z())); + } } } } catch (const asio::system_error& e) { diff --git a/src/proto/common/chunk_pos.proto b/src/proto/common/chunk_pos.proto index c0d3d7e..5872d09 100644 --- a/src/proto/common/chunk_pos.proto +++ b/src/proto/common/chunk_pos.proto @@ -2,5 +2,5 @@ syntax = "proto3"; message ChunkPos { int32 x = 1; - int32 y = 2; + int32 z = 2; } \ No newline at end of file diff --git a/src/proto/world/chunk_data.proto b/src/proto/world/chunk_data.proto index 090caa5..2801849 100644 --- a/src/proto/world/chunk_data.proto +++ b/src/proto/world/chunk_data.proto @@ -2,7 +2,12 @@ syntax = "proto3"; import "common/chunk_pos.proto"; -message ChunkData { +message ChunkDataReq { + string uuid = 1; + ChunkPos pos = 2; +} + +message ChunkDataRsp { ChunkPos pos = 1; uint32 chunk_seed = 2; repeated uint32 chunk_blocks = 3 [packed=true];