mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
feat(network): integrate protobuf for player sync and session management
Add Protobuf dependency, define proto messages for player requests, positions, and chunk data. Refactor Session to use strand and async write. Implement player join/exit and position sync in ServerWorld.
This commit is contained in:
@@ -19,6 +19,8 @@ if(MSVC)
|
||||
endif()
|
||||
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
find_package(Freetype REQUIRED)
|
||||
@@ -28,8 +30,7 @@ if (UNIX AND NOT APPLE)
|
||||
find_package(glfw3 REQUIRED)
|
||||
endif()
|
||||
|
||||
add_library(glad STATIC third_party/glad/src/glad.c
|
||||
src/gameplay/network_client.cpp)
|
||||
add_library(glad STATIC third_party/glad/src/glad.c)
|
||||
target_include_directories(glad PUBLIC third_party/glad/include)
|
||||
|
||||
include(FetchContent)
|
||||
@@ -99,7 +100,7 @@ FetchContent_MakeAvailable(tomlplusplus)
|
||||
|
||||
add_subdirectory(third_party/imgui)
|
||||
|
||||
set(INCLUDE_DIR ${PROJECT_SOURCE_DIR}/include)
|
||||
|
||||
|
||||
add_executable(${PROJECT_NAME}
|
||||
src/main.cpp
|
||||
@@ -148,8 +149,18 @@ add_executable(${PROJECT_NAME}
|
||||
src/gameplay/server_player.cpp
|
||||
src/gameplay/client_player.cpp
|
||||
src/gameplay/session.cpp
|
||||
src/gameplay/network_client.cpp
|
||||
)
|
||||
set(PROTO_SOURCES
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/proto/chunk_data.proto
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/proto/player_pos.proto
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/proto/player_request.proto
|
||||
)
|
||||
protobuf_generate(
|
||||
TARGET ${PROJECT_NAME}
|
||||
LANGUAGE cpp
|
||||
PROTOS ${PROTO_SOURCES}
|
||||
)
|
||||
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
message(STATUS "Building with AddressSanitizer enabled for target: ${PROJECT_NAME}")
|
||||
|
||||
@@ -175,10 +186,13 @@ else()
|
||||
)
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${INCLUDE_DIR})
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/include)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/third_party/asio/include
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/src)
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
ASIO_STANDALONE
|
||||
ASIO_NO_DEPRECATED
|
||||
@@ -194,6 +208,7 @@ target_link_libraries(${PROJECT_NAME}
|
||||
tomlplusplus::tomlplusplus
|
||||
imgui
|
||||
tbb
|
||||
protobuf::libprotobuf
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
#include "Cubed/gameplay/session.hpp"
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <thread>
|
||||
namespace Cubed {
|
||||
@@ -10,11 +13,14 @@ public:
|
||||
void stop();
|
||||
void run();
|
||||
int port() const;
|
||||
std::unordered_map<std::string, std::shared_ptr<Session>> m_session;
|
||||
|
||||
private:
|
||||
asio::io_context m_io;
|
||||
std::thread m_server;
|
||||
int m_port = 25530;
|
||||
std::atomic<bool> m_stopped{false};
|
||||
ServerWorld m_world;
|
||||
asio::awaitable<void> listen();
|
||||
};
|
||||
} // namespace Cubed
|
||||
@@ -1,4 +1,17 @@
|
||||
#pragma once
|
||||
#include <glm/glm.hpp>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
namespace Cubed {
|
||||
class ServerPlayer {};
|
||||
class ServerPlayer {
|
||||
public:
|
||||
explicit ServerPlayer(std::string_view);
|
||||
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;
|
||||
glm::vec3 m_pos{0.0f};
|
||||
};
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -7,18 +7,21 @@
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
#include "Cubed/gameplay/server_player.hpp"
|
||||
#include "Cubed/tools/thread_pool.hpp"
|
||||
#include "proto/player_request.pb.h"
|
||||
|
||||
#include <future>
|
||||
#include <shared_mutex>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
namespace Cubed {
|
||||
class ServerWorld {
|
||||
public:
|
||||
ServerWorld();
|
||||
~ServerWorld();
|
||||
void player_join();
|
||||
void player_join(const std::string& name);
|
||||
void player_exit(const std::string& name);
|
||||
void init_world();
|
||||
void need_gen();
|
||||
void update();
|
||||
@@ -60,6 +63,10 @@ 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 handle_player_request(const PlayerRequest& request);
|
||||
glm::vec3 get_player_pos(const std::string& name) const;
|
||||
|
||||
private:
|
||||
enum class ChunkLoadStyle { RANDOM, CENTER };
|
||||
struct PendingChunk {
|
||||
@@ -68,7 +75,7 @@ private:
|
||||
};
|
||||
using ChunkHashMap =
|
||||
std::unordered_map<ChunkPos, ServerChunk, ChunkPos::Hash>;
|
||||
using PlayerHashMap = std::unordered_map<std::size_t, ServerPlayer>;
|
||||
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
|
||||
using PendingChunkHashMap =
|
||||
std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>;
|
||||
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
|
||||
@@ -110,6 +117,8 @@ private:
|
||||
|
||||
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::RANDOM};
|
||||
|
||||
std::optional<std::string> m_request_gen_name = std::nullopt;
|
||||
|
||||
void init_chunks();
|
||||
|
||||
void gen_chunks_internal();
|
||||
@@ -120,7 +129,5 @@ private:
|
||||
void submit_new_chunks();
|
||||
void poll_finished_chunks();
|
||||
void wait_all_chunk_tasks();
|
||||
|
||||
void sync_player_pos(glm::vec3& pos);
|
||||
};
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
#include <string>
|
||||
namespace Cubed {
|
||||
using asio::ip::tcp;
|
||||
|
||||
class ServerWorld;
|
||||
class Session : public std::enable_shared_from_this<Session> {
|
||||
|
||||
public:
|
||||
Session(tcp::socket socket);
|
||||
Session(tcp::socket socket, ServerWorld& server_world);
|
||||
~Session();
|
||||
void start();
|
||||
void send();
|
||||
void send(std::shared_ptr<std::vector<uint8_t>> packet);
|
||||
void close();
|
||||
const std::string& uuid() const;
|
||||
|
||||
@@ -22,12 +22,12 @@ private:
|
||||
static constexpr uint32_t MAX_PACKET_SIZE = 4 * 1024 * 1024;
|
||||
tcp::socket m_socket;
|
||||
std::vector<char> m_read_buffer;
|
||||
std::deque<std::vector<char>> m_write_queue;
|
||||
std::mutex m_write_mutex;
|
||||
|
||||
std::deque<std::shared_ptr<std::vector<uint8_t>>> m_write_queue;
|
||||
asio::strand<asio::io_context::executor_type> m_strand;
|
||||
std::string m_uuid;
|
||||
|
||||
asio::awaitable<void> read();
|
||||
asio::awaitable<void> write();
|
||||
ServerWorld& m_server_world;
|
||||
asio::awaitable<void> read_loop();
|
||||
std::atomic<bool> m_closed{false};
|
||||
void do_write();
|
||||
};
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -9,7 +9,17 @@ NetworkServer::NetworkServer(int port) : m_port(port) {}
|
||||
NetworkServer::~NetworkServer() { stop(); }
|
||||
|
||||
void NetworkServer::stop() {
|
||||
if (m_stopped.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
for (auto& [key, s] : m_session) {
|
||||
if (s) {
|
||||
s->close();
|
||||
}
|
||||
}
|
||||
|
||||
m_io.stop();
|
||||
m_session.clear();
|
||||
if (m_server.joinable()) {
|
||||
m_server.join();
|
||||
}
|
||||
@@ -19,10 +29,17 @@ void NetworkServer::stop() {
|
||||
asio::awaitable<void> NetworkServer::listen() {
|
||||
tcp::acceptor acceptor(m_io, tcp::endpoint(tcp::v4(), m_port));
|
||||
while (true) {
|
||||
|
||||
tcp::socket socket =
|
||||
co_await acceptor.async_accept(asio::use_awaitable);
|
||||
if (m_stopped) {
|
||||
break;
|
||||
}
|
||||
std::shared_ptr<Session> s =
|
||||
std::make_shared<Session>(std::move(socket), m_world);
|
||||
s->start();
|
||||
m_session.emplace(s->uuid(), s);
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
void NetworkServer::run() {
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
#include "Cubed/gameplay/server_player.hpp"
|
||||
|
||||
namespace Cubed {
|
||||
ServerPlayer::ServerPlayer(std::string_view name) : m_name(name) {}
|
||||
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};
|
||||
}
|
||||
} // namespace Cubed
|
||||
@@ -83,12 +83,16 @@ void ServerWorld::gen_chunks_internal() {
|
||||
|
||||
submit_new_chunks();
|
||||
m_chunk_gen_finished = true;
|
||||
m_request_gen_name = std::nullopt;
|
||||
}
|
||||
|
||||
void ServerWorld::compute_required_chunks(ChunkPosSet& required_chunks) {
|
||||
glm::vec3 player_pos;
|
||||
// sync_player_pos(player_pos);
|
||||
ASSERT_MSG(false, "Player Pos");
|
||||
if (m_request_gen_name == std::nullopt) {
|
||||
player_pos = glm::vec3{0.0f};
|
||||
} else {
|
||||
player_pos = get_player_pos(m_request_gen_name.value());
|
||||
}
|
||||
int x = std::floor(player_pos.x);
|
||||
int z = std::floor(player_pos.z);
|
||||
auto [chunk_x, chunk_z] = get_chunk_pos(x, z);
|
||||
@@ -147,7 +151,11 @@ void ServerWorld::submit_new_chunks() {
|
||||
}
|
||||
}
|
||||
glm::vec3 player_pos;
|
||||
sync_player_pos(player_pos);
|
||||
if (m_request_gen_name == std::nullopt) {
|
||||
player_pos = glm::vec3{0.0f};
|
||||
} else {
|
||||
player_pos = get_player_pos(m_request_gen_name.value());
|
||||
}
|
||||
auto dist2 = [player_pos](ChunkPos chunk_pos) {
|
||||
ChunkPos player_chunk_pos =
|
||||
get_chunk_pos(player_pos.x, player_pos.z);
|
||||
@@ -339,6 +347,48 @@ void ServerWorld::rebuild_world() {
|
||||
|
||||
void ServerWorld::update() { poll_finished_chunks(); }
|
||||
|
||||
void ServerWorld::sync_player_pos(const std::string& name, float x, float y,
|
||||
float z) {
|
||||
auto it = m_players.find(name);
|
||||
if (it == m_players.end()) {
|
||||
Logger::warn("Player {} is not in this Server", it->first);
|
||||
return;
|
||||
}
|
||||
it->second.update_pos(x, y, z);
|
||||
}
|
||||
|
||||
void ServerWorld::handle_player_request(const PlayerRequest& request) {
|
||||
if (request.join()) {
|
||||
player_join(request.name());
|
||||
}
|
||||
if (request.exit()) {
|
||||
player_exit(request.name());
|
||||
}
|
||||
if (request.chunk_send()) {
|
||||
m_request_gen_name = request.name();
|
||||
need_gen();
|
||||
}
|
||||
}
|
||||
|
||||
void ServerWorld::player_join(const std::string& name) {
|
||||
m_players.emplace(name, name);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
glm::vec3 ServerWorld::get_player_pos(const std::string& name) const {
|
||||
auto it = m_players.find(name);
|
||||
if (it == m_players.end()) {
|
||||
return glm::vec3{0.0f};
|
||||
}
|
||||
return it->second.get_pos();
|
||||
}
|
||||
|
||||
int ServerWorld::rendering_distance() const {
|
||||
return m_rendering_distance.load();
|
||||
}
|
||||
|
||||
@@ -1,26 +1,41 @@
|
||||
#include "Cubed/gameplay/session.hpp"
|
||||
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
#include "Cubed/tools/uuid.hpp"
|
||||
#include "proto/player_pos.pb.h"
|
||||
#include "proto/player_request.pb.h"
|
||||
using asio::ip::tcp;
|
||||
|
||||
namespace Cubed {
|
||||
Session::Session(tcp::socket socket)
|
||||
: m_socket(std::move(socket)), m_uuid(generate_uuid()) {}
|
||||
Session::Session(tcp::socket socket, ServerWorld& server_world)
|
||||
: m_socket(std::move(socket)), m_uuid(generate_uuid()),
|
||||
m_server_world(server_world) {}
|
||||
|
||||
Session::~Session() {}
|
||||
|
||||
void Session::start() {
|
||||
auto self = shared_from_this();
|
||||
asio::co_spawn(
|
||||
m_socket.get_executor(),
|
||||
[self]() -> asio::awaitable<void> { co_await self->read(); },
|
||||
m_strand,
|
||||
[self]() -> asio::awaitable<void> { co_await self->read_loop(); },
|
||||
asio::detached);
|
||||
}
|
||||
|
||||
void Session::send(std::shared_ptr<std::vector<uint8_t>> packet) {
|
||||
asio::post(m_strand, [self = shared_from_this(),
|
||||
packet = std::move(packet)]() mutable {
|
||||
bool idle = self->m_write_queue.empty();
|
||||
self->m_write_queue.emplace_back(std::move(packet));
|
||||
if (idle) {
|
||||
self->do_write();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const std::string& Session::uuid() const { return m_uuid; }
|
||||
|
||||
asio::awaitable<void> Session::read() {
|
||||
asio::awaitable<void> Session::read_loop() {
|
||||
try {
|
||||
while (true) {
|
||||
std::array<char, HEADER_LEN> header;
|
||||
@@ -46,6 +61,20 @@ asio::awaitable<void> Session::read() {
|
||||
}
|
||||
|
||||
if (cmd_id == 1001) {
|
||||
PlayerRequest player_request;
|
||||
if (player_request.ParseFromArray(body_data.data(),
|
||||
body_data.size())) {
|
||||
}
|
||||
}
|
||||
if (cmd_id == 1002) {
|
||||
PlayerPos player_pos;
|
||||
if (player_pos.ParseFromArray(body_data.data(),
|
||||
body_data.size())) {
|
||||
m_server_world.sync_player_pos(
|
||||
player_pos.name(), player_pos.x(), player_pos.y(),
|
||||
player_pos.z());
|
||||
} else {
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const asio::system_error& e) {
|
||||
@@ -53,7 +82,39 @@ asio::awaitable<void> Session::read() {
|
||||
close();
|
||||
} catch (...) {
|
||||
Logger::error("Unknow Error");
|
||||
close();
|
||||
}
|
||||
co_return;
|
||||
}
|
||||
|
||||
void Session::do_write() {
|
||||
|
||||
auto self = shared_from_this();
|
||||
asio::async_write(
|
||||
m_socket, asio::buffer(*(m_write_queue.front())),
|
||||
asio::bind_executor(m_strand, [self](std::error_code ec, size_t) {
|
||||
if (ec) {
|
||||
Logger::warn("Write Ec {}", ec.message());
|
||||
self->close();
|
||||
return;
|
||||
}
|
||||
self->m_write_queue.pop_front();
|
||||
if (!self->m_write_queue.empty()) {
|
||||
self->do_write();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
void Session::close() {
|
||||
if (m_closed.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
|
||||
m_socket.shutdown(tcp::socket::shutdown_both, ec);
|
||||
|
||||
m_socket.close(ec);
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
12
src/proto/chunk_data.proto
Normal file
12
src/proto/chunk_data.proto
Normal file
@@ -0,0 +1,12 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message ChunkData {
|
||||
int32 pos_x = 1;
|
||||
int32 pos_z = 2;
|
||||
uint32 chunk_seed = 3;
|
||||
repeated uint32 chunk_blocks = 4 [packed=true];
|
||||
repeated uint32 neighbor_blocks_1 = 5 [packed=true];
|
||||
repeated uint32 neighbor_blocks_2 = 6 [packed=true];
|
||||
repeated uint32 neighbor_blocks_3 = 7 [packed=true];
|
||||
repeated uint32 neighbor_blocks_4 = 8 [packed=true];
|
||||
}
|
||||
9
src/proto/player_pos.proto
Normal file
9
src/proto/player_pos.proto
Normal file
@@ -0,0 +1,9 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message PlayerPos {
|
||||
string name = 1;
|
||||
float x = 2;
|
||||
float y = 3;
|
||||
float z = 4;
|
||||
}
|
||||
|
||||
8
src/proto/player_request.proto
Normal file
8
src/proto/player_request.proto
Normal file
@@ -0,0 +1,8 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message PlayerRequest {
|
||||
string name = 1;
|
||||
bool join = 2;
|
||||
bool exit = 3;
|
||||
bool chunk_send = 4;
|
||||
}
|
||||
Reference in New Issue
Block a user