From 47552a1887b7a13d0bf2ed384d5da495ed5dd034 Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Thu, 25 Jun 2026 20:13:51 +0800 Subject: [PATCH] feat(gameplay): add time sync and network improvements --- include/Cubed/gameplay/client_world.hpp | 4 +++ include/Cubed/gameplay/network_client.hpp | 7 +++-- include/Cubed/gameplay/packet.hpp | 3 ++ include/Cubed/gameplay/server_world.hpp | 9 ++++++ src/app.cpp | 6 +++- src/gameplay/client_world.cpp | 37 +++++++++++++++++++++++ src/gameplay/network_client.cpp | 32 ++++++++++++++++++-- src/gameplay/network_server.cpp | 4 ++- src/gameplay/server_world.cpp | 21 ++++++++++++- src/gameplay/session.cpp | 1 + src/proto/packet.proto | 3 +- src/proto/world/time.proto | 6 ++++ 12 files changed, 123 insertions(+), 10 deletions(-) create mode 100644 src/proto/world/time.proto diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index 412c62d..9cfce46 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -32,6 +32,8 @@ public: // void rebuild_world(); void report_block_change(const glm::ivec3& pos, unsigned id) const; void receive_block_change(const BlockChangeRsp& rsp); + void receive_time(const UpdateTime& rsp); + int rendering_distance() const; void rendering_distance(int rendering_distance); void start_client_thread(std::string_view uuid); @@ -72,6 +74,8 @@ private: tbb::concurrent_unordered_map m_timers; std::atomic m_game_running{false}; std::atomic m_rendering_distance{24}; + std::atomic m_game_ticks{0}; + std::atomic m_day_tick{6000}; std::shared_ptr m_client; void client_run(std::stop_token token); diff --git a/include/Cubed/gameplay/network_client.hpp b/include/Cubed/gameplay/network_client.hpp index 56c22bc..758adf5 100644 --- a/include/Cubed/gameplay/network_client.hpp +++ b/include/Cubed/gameplay/network_client.hpp @@ -8,14 +8,15 @@ namespace Cubed { using asio::ip::tcp; class ClientWorld; -class NetworkClient : std::enable_shared_from_this { +class NetworkClient : public std::enable_shared_from_this { public: NetworkClient(ClientWorld& world); ~NetworkClient(); void close(); - + void stop(); void send(Packet packet); void start(std::string ip, int port = 25530); + bool is_connected() const; private: asio::io_context m_io; @@ -30,7 +31,7 @@ private: asio::awaitable read_loop(); std::atomic m_closed{false}; - + std::atomic m_connected{false}; // ClientWorld is managed by App ClientWorld& m_world; diff --git a/include/Cubed/gameplay/packet.hpp b/include/Cubed/gameplay/packet.hpp index 21227f0..4a84f52 100644 --- a/include/Cubed/gameplay/packet.hpp +++ b/include/Cubed/gameplay/packet.hpp @@ -17,6 +17,7 @@ enum class PacketEnum { CHUNK_DATA_RSP = 3002, BLOCK_CHANGE_REQ = 3003, BLOCK_CHANGE_RSP = 3004, + UPDATE_TIME = 3005, PING = 9001, PONG = 9002 @@ -45,6 +46,8 @@ template constexpr uint16_t get_packet_id() { return to_num(BLOCK_CHANGE_REQ); } else if constexpr (is_same_v) { return to_num(BLOCK_CHANGE_RSP); + } else if constexpr (is_same_v) { + return to_num(UPDATE_TIME); } else if constexpr (is_same_v) { return to_num(PING); } else if (is_same_v) { diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index 0e07490..2387600 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -73,6 +73,12 @@ public: void handle_chunk_req(const std::string& uuid, ChunkPos pos); void handle_block_change(const BlockChangeReq& req); + template + void register_timer(std::string_view id, TickType threshold, Fn&& f) { + m_timers.emplace(std::piecewise_construct, + std::forward_as_tuple(std::string(id)), + std::forward_as_tuple(threshold, std::forward(f))); + } private: enum class ChunkLoadStyle { RANDOM, CENTER }; @@ -128,6 +134,7 @@ private: std::atomic m_chunk_load_style{ChunkLoadStyle::RANDOM}; PlayerUUIDMap m_uuid_to_name; + tbb::concurrent_unordered_map m_timers; void init_chunks(); void gen_chunks_internal(std::optional uuid); @@ -139,5 +146,7 @@ private: void submit_new_chunks(const std::optional& uuid); void poll_finished_chunks(); void wait_all_chunk_tasks(); + + void send_time(); }; } // namespace Cubed diff --git a/src/app.cpp b/src/app.cpp index 0bcb4da..7ffbf70 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -13,7 +13,11 @@ namespace Cubed { App::App() {} -App::~App() {} +App::~App() { + if (m_client) { + m_client->stop(); + } +} void App::cursor_position_callback(GLFWwindow* window, double xpos, double ypos) { ImGuiIO& io = ImGui::GetIO(); diff --git a/src/gameplay/client_world.cpp b/src/gameplay/client_world.cpp index 2f90107..2f33846 100644 --- a/src/gameplay/client_world.cpp +++ b/src/gameplay/client_world.cpp @@ -185,6 +185,11 @@ void ClientWorld::receive_block_change(const BlockChangeRsp& rsp) { set_block(pos, rsp.block()); } +void ClientWorld::receive_time(const UpdateTime& rsp) { + m_game_ticks = rsp.game_tick(); + m_day_tick = rsp.day_tick(); +} + void ClientWorld::init(std::string_view player_name, std::shared_ptr client) { m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); @@ -195,7 +200,11 @@ void ClientWorld::init(std::string_view player_name, LoginReq req; req.set_name(m_player.get_name()); + while (!client->is_connected()) { + std::this_thread::sleep_for(milliseconds(200)); + } // request login + Logger::info("Send Login Request"); m_client->send(make_packet(req)); } @@ -384,4 +393,32 @@ void ClientWorld::update(float delta_time) { } } +glm::vec3 ClientWorld::sunlight_dir() const { + float altitude = sin((m_day_tick - 6 * PER_HOUR) / + static_cast(DAY_TIME / 2) * std::numbers::pi) * + 90.0f; + + float t = static_cast(m_day_tick) / DAY_TIME; + float azimuth = 90.0f - 360.0f * (t - 0.25f); + + float alt = glm::radians(altitude); + float az = glm::radians(azimuth); + glm::vec3 dir; + dir.x = cos(alt) * sin(az); + dir.y = sin(alt); + dir.z = cos(alt) * cos(az); + + return glm::normalize(-dir); +} +int ClientWorld::rendering_distance() const { + return m_rendering_distance.load(); +} + +void ClientWorld::rendering_distance(int rendering_distance) { + m_rendering_distance = rendering_distance; +} +std::vector& ClientWorld::render_snapshots() { + return m_render_snapshots; +}; +std::vector& ClientWorld::planes() { return m_planes; } } // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/network_client.cpp b/src/gameplay/network_client.cpp index bde33b6..6663071 100644 --- a/src/gameplay/network_client.cpp +++ b/src/gameplay/network_client.cpp @@ -16,16 +16,24 @@ void NetworkClient::start(std::string ip, int port) { asio::co_spawn(self->m_strand, self->connect(ip, port), asio::detached); self->m_io.run(); }); + Logger::info("NetworkClient Started"); } +bool NetworkClient::is_connected() const { return m_connected.load(); } + asio::awaitable NetworkClient::connect(std::string ip, int port) { + Logger::info("Connect Begin"); try { auto ex = co_await asio::this_coro::executor; tcp::resolver resolver(ex); auto eps = co_await resolver.async_resolve(ip, std::to_string(port), asio::use_awaitable); + Logger::info("Resolve Success"); co_await async_connect(m_socket, eps, asio::use_awaitable); + Logger::info("Connect Success, Server ip {} port {}", ip, port); asio::co_spawn(m_strand, read_loop(), asio::detached); + Logger::info("NetworkClient Read Loop Started"); + m_connected = true; co_return; } catch (const std::exception& e) { @@ -56,6 +64,7 @@ asio::awaitable NetworkClient::read_loop() { co_await asio::async_read(m_socket, asio::buffer(body_data), asio::use_awaitable); } + Logger::info("Client: Receive cmd {}", cmd_id); constexpr auto& to_num = std::to_underlying; switch (cmd_id) { case to_num(PacketEnum::LOGIN_RSP): { @@ -67,19 +76,19 @@ asio::awaitable NetworkClient::read_loop() { Logger::error("Connected Server Fail"); } } - } + } break; case to_num(PacketEnum::CHUNK_DATA_RSP): { ChunkDataRsp rsp; if (rsp.ParseFromArray(body_data.data(), body_data.size())) { m_world.receive_chunk(rsp); } - } + } break; case to_num(PacketEnum::BLOCK_CHANGE_RSP): { BlockChangeRsp rsp; if (rsp.ParseFromArray(body_data.data(), body_data.size())) { m_world.receive_block_change(rsp); } - } + } break; } } } catch (const asio::system_error& e) { @@ -104,6 +113,9 @@ asio::awaitable NetworkClient::read_loop() { } void NetworkClient::send(std::shared_ptr> packet) { + if (m_closed.load()) { + return; + } asio::post(m_strand, [self = shared_from_this(), packet = std::move(packet)]() mutable { bool idle = self->m_write_queue.empty(); @@ -115,6 +127,9 @@ void NetworkClient::send(std::shared_ptr> packet) { } void NetworkClient::do_write() { + if (m_closed.load()) { + return; + } auto self = shared_from_this(); asio::async_write( @@ -142,6 +157,17 @@ void NetworkClient::close() { m_socket.shutdown(tcp::socket::shutdown_both, ec); m_socket.close(ec); + Logger::info("NetworkClient Closed"); + m_connected = false; + m_io.stop(); +} + +void NetworkClient::stop() { + close(); + + if (m_net_thread.joinable()) { + m_net_thread.join(); + } } } // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/network_server.cpp b/src/gameplay/network_server.cpp index d4cdbc6..83079da 100644 --- a/src/gameplay/network_server.cpp +++ b/src/gameplay/network_server.cpp @@ -32,9 +32,11 @@ void NetworkServer::stop() { } if (m_net_thread.joinable()) { + Logger::info("Server join thread={}, current={}", m_net_thread.get_id(), + std::this_thread::get_id()); m_net_thread.join(); } - Logger::info("Server Stopped!"); + Logger::info("Server Net Thread Stopped!"); } asio::awaitable NetworkServer::listen() { diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp index 9272aa2..96eb110 100644 --- a/src/gameplay/server_world.cpp +++ b/src/gameplay/server_world.cpp @@ -32,6 +32,16 @@ void ServerWorld::wait_all_chunk_tasks() { } } +void ServerWorld::send_time() { + UpdateTime rsp; + rsp.set_day_tick(m_day_tick); + rsp.set_game_tick(m_game_ticks); + + for (auto& [uuid, player] : m_players) { + player.get_session()->send(make_packet(rsp)); + } +} + void ServerWorld::init_world() { m_cave_carcer.init(ChunkGenerator::seed()); m_river_worm.init(ChunkGenerator::seed()); @@ -270,12 +280,17 @@ void ServerWorld::stop_thread_pool() { void ServerWorld::serever_run(std::stop_token stoken) { Logger::info("Server Thread Started!"); while (!stoken.stop_requested()) { - std::this_thread::sleep_for(milliseconds(m_per_tick_time)); + auto t1 = system_clock::now(); + if (m_tick_running) { ++m_game_ticks; m_day_tick = (m_day_tick + 1) % DAY_TIME; } update(); + auto t2 = system_clock::now(); + auto dt = duration_cast(t2 - t1); + auto st = std::max(dt, milliseconds(m_per_tick_time) - dt); + std::this_thread::sleep_for(st); } Logger::info("Server Thread Stopped!"); } @@ -373,6 +388,10 @@ void ServerWorld::update() { m_could_gen = true; } } + send_time(); + for (auto& [id, timer] : m_timers) { + timer.update(); + } } void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y, diff --git a/src/gameplay/session.cpp b/src/gameplay/session.cpp index 14dfe91..7e85aa0 100644 --- a/src/gameplay/session.cpp +++ b/src/gameplay/session.cpp @@ -58,6 +58,7 @@ asio::awaitable Session::read_loop() { co_await asio::async_read(m_socket, asio::buffer(body_data), asio::use_awaitable); } + Logger::info("Session: Receive cmd {}", cmd_id); constexpr auto& to_num = std::to_underlying; if (cmd_id == to_num(PacketEnum::LOGIN_REQ)) { LoginReq req; diff --git a/src/proto/packet.proto b/src/proto/packet.proto index 42e1d6c..3b6ad3a 100644 --- a/src/proto/packet.proto +++ b/src/proto/packet.proto @@ -8,4 +8,5 @@ import "auth/auth.proto"; import "system/ping.proto"; import "system/pong.proto"; import "world/chunk_data.proto"; -import "world/block_change.proto"; \ No newline at end of file +import "world/block_change.proto"; +import "world/time.proto"; \ No newline at end of file diff --git a/src/proto/world/time.proto b/src/proto/world/time.proto new file mode 100644 index 0000000..1b381b4 --- /dev/null +++ b/src/proto/world/time.proto @@ -0,0 +1,6 @@ +syntax = "proto3"; + +message UpdateTime { + int64 game_tick = 1; + int64 day_tick = 2; +} \ No newline at end of file