feat(gameplay): add time sync and network improvements

This commit is contained in:
2026-06-25 20:13:51 +08:00
parent d7d47e9e7f
commit 47552a1887
12 changed files with 123 additions and 10 deletions

View File

@@ -32,6 +32,8 @@ public:
// void rebuild_world(); // void rebuild_world();
void report_block_change(const glm::ivec3& pos, unsigned id) const; void report_block_change(const glm::ivec3& pos, unsigned id) const;
void receive_block_change(const BlockChangeRsp& rsp); void receive_block_change(const BlockChangeRsp& rsp);
void receive_time(const UpdateTime& rsp);
int rendering_distance() const; int rendering_distance() const;
void rendering_distance(int rendering_distance); void rendering_distance(int rendering_distance);
void start_client_thread(std::string_view uuid); void start_client_thread(std::string_view uuid);
@@ -72,6 +74,8 @@ private:
tbb::concurrent_unordered_map<std::string, Timer> m_timers; tbb::concurrent_unordered_map<std::string, Timer> m_timers;
std::atomic<bool> m_game_running{false}; std::atomic<bool> m_game_running{false};
std::atomic<int> m_rendering_distance{24}; std::atomic<int> m_rendering_distance{24};
std::atomic<TickType> m_game_ticks{0};
std::atomic<TickType> m_day_tick{6000};
std::shared_ptr<NetworkClient> m_client; std::shared_ptr<NetworkClient> m_client;
void client_run(std::stop_token token); void client_run(std::stop_token token);

View File

@@ -8,14 +8,15 @@
namespace Cubed { namespace Cubed {
using asio::ip::tcp; using asio::ip::tcp;
class ClientWorld; class ClientWorld;
class NetworkClient : std::enable_shared_from_this<NetworkClient> { class NetworkClient : public std::enable_shared_from_this<NetworkClient> {
public: public:
NetworkClient(ClientWorld& world); NetworkClient(ClientWorld& world);
~NetworkClient(); ~NetworkClient();
void close(); void close();
void stop();
void send(Packet packet); void send(Packet packet);
void start(std::string ip, int port = 25530); void start(std::string ip, int port = 25530);
bool is_connected() const;
private: private:
asio::io_context m_io; asio::io_context m_io;
@@ -30,7 +31,7 @@ private:
asio::awaitable<void> read_loop(); asio::awaitable<void> read_loop();
std::atomic<bool> m_closed{false}; std::atomic<bool> m_closed{false};
std::atomic<bool> m_connected{false};
// ClientWorld is managed by App // ClientWorld is managed by App
ClientWorld& m_world; ClientWorld& m_world;

View File

@@ -17,6 +17,7 @@ enum class PacketEnum {
CHUNK_DATA_RSP = 3002, CHUNK_DATA_RSP = 3002,
BLOCK_CHANGE_REQ = 3003, BLOCK_CHANGE_REQ = 3003,
BLOCK_CHANGE_RSP = 3004, BLOCK_CHANGE_RSP = 3004,
UPDATE_TIME = 3005,
PING = 9001, PING = 9001,
PONG = 9002 PONG = 9002
@@ -45,6 +46,8 @@ template <typename T> constexpr uint16_t get_packet_id() {
return to_num(BLOCK_CHANGE_REQ); return to_num(BLOCK_CHANGE_REQ);
} else if constexpr (is_same_v<U, BlockChangeRsp>) { } else if constexpr (is_same_v<U, BlockChangeRsp>) {
return to_num(BLOCK_CHANGE_RSP); return to_num(BLOCK_CHANGE_RSP);
} else if constexpr (is_same_v<U, UpdateTime>) {
return to_num(UPDATE_TIME);
} else if constexpr (is_same_v<U, Ping>) { } else if constexpr (is_same_v<U, Ping>) {
return to_num(PING); return to_num(PING);
} else if (is_same_v<U, Pong>) { } else if (is_same_v<U, Pong>) {

View File

@@ -73,6 +73,12 @@ public:
void handle_chunk_req(const std::string& uuid, ChunkPos pos); void handle_chunk_req(const std::string& uuid, ChunkPos pos);
void handle_block_change(const BlockChangeReq& req); void handle_block_change(const BlockChangeReq& req);
template <typename Fn>
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<Fn>(f)));
}
private: private:
enum class ChunkLoadStyle { RANDOM, CENTER }; enum class ChunkLoadStyle { RANDOM, CENTER };
@@ -128,6 +134,7 @@ private:
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::RANDOM}; std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::RANDOM};
PlayerUUIDMap m_uuid_to_name; PlayerUUIDMap m_uuid_to_name;
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
void init_chunks(); void init_chunks();
void gen_chunks_internal(std::optional<std::string> uuid); void gen_chunks_internal(std::optional<std::string> uuid);
@@ -139,5 +146,7 @@ private:
void submit_new_chunks(const std::optional<std::string>& uuid); void submit_new_chunks(const std::optional<std::string>& uuid);
void poll_finished_chunks(); void poll_finished_chunks();
void wait_all_chunk_tasks(); void wait_all_chunk_tasks();
void send_time();
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -13,7 +13,11 @@ namespace Cubed {
App::App() {} App::App() {}
App::~App() {} App::~App() {
if (m_client) {
m_client->stop();
}
}
void App::cursor_position_callback(GLFWwindow* window, double xpos, void App::cursor_position_callback(GLFWwindow* window, double xpos,
double ypos) { double ypos) {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();

View File

@@ -185,6 +185,11 @@ void ClientWorld::receive_block_change(const BlockChangeRsp& rsp) {
set_block(pos, rsp.block()); 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, void ClientWorld::init(std::string_view player_name,
std::shared_ptr<NetworkClient> client) { std::shared_ptr<NetworkClient> client) {
m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4);
@@ -195,7 +200,11 @@ void ClientWorld::init(std::string_view player_name,
LoginReq req; LoginReq req;
req.set_name(m_player.get_name()); req.set_name(m_player.get_name());
while (!client->is_connected()) {
std::this_thread::sleep_for(milliseconds(200));
}
// request login // request login
Logger::info("Send Login Request");
m_client->send(make_packet(req)); 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<float>(DAY_TIME / 2) * std::numbers::pi) *
90.0f;
float t = static_cast<float>(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<ChunkRenderSnapshot>& ClientWorld::render_snapshots() {
return m_render_snapshots;
};
std::vector<glm::vec4>& ClientWorld::planes() { return m_planes; }
} // namespace Cubed } // namespace Cubed

View File

@@ -16,16 +16,24 @@ void NetworkClient::start(std::string ip, int port) {
asio::co_spawn(self->m_strand, self->connect(ip, port), asio::detached); asio::co_spawn(self->m_strand, self->connect(ip, port), asio::detached);
self->m_io.run(); self->m_io.run();
}); });
Logger::info("NetworkClient Started");
} }
bool NetworkClient::is_connected() const { return m_connected.load(); }
asio::awaitable<void> NetworkClient::connect(std::string ip, int port) { asio::awaitable<void> NetworkClient::connect(std::string ip, int port) {
Logger::info("Connect Begin");
try { try {
auto ex = co_await asio::this_coro::executor; auto ex = co_await asio::this_coro::executor;
tcp::resolver resolver(ex); tcp::resolver resolver(ex);
auto eps = co_await resolver.async_resolve(ip, std::to_string(port), auto eps = co_await resolver.async_resolve(ip, std::to_string(port),
asio::use_awaitable); asio::use_awaitable);
Logger::info("Resolve Success");
co_await async_connect(m_socket, eps, asio::use_awaitable); 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); asio::co_spawn(m_strand, read_loop(), asio::detached);
Logger::info("NetworkClient Read Loop Started");
m_connected = true;
co_return; co_return;
} catch (const std::exception& e) { } catch (const std::exception& e) {
@@ -56,6 +64,7 @@ asio::awaitable<void> NetworkClient::read_loop() {
co_await asio::async_read(m_socket, asio::buffer(body_data), co_await asio::async_read(m_socket, asio::buffer(body_data),
asio::use_awaitable); asio::use_awaitable);
} }
Logger::info("Client: Receive cmd {}", cmd_id);
constexpr auto& to_num = std::to_underlying<PacketEnum>; constexpr auto& to_num = std::to_underlying<PacketEnum>;
switch (cmd_id) { switch (cmd_id) {
case to_num(PacketEnum::LOGIN_RSP): { case to_num(PacketEnum::LOGIN_RSP): {
@@ -67,19 +76,19 @@ asio::awaitable<void> NetworkClient::read_loop() {
Logger::error("Connected Server Fail"); Logger::error("Connected Server Fail");
} }
} }
} } break;
case to_num(PacketEnum::CHUNK_DATA_RSP): { case to_num(PacketEnum::CHUNK_DATA_RSP): {
ChunkDataRsp rsp; ChunkDataRsp rsp;
if (rsp.ParseFromArray(body_data.data(), body_data.size())) { if (rsp.ParseFromArray(body_data.data(), body_data.size())) {
m_world.receive_chunk(rsp); m_world.receive_chunk(rsp);
} }
} } break;
case to_num(PacketEnum::BLOCK_CHANGE_RSP): { case to_num(PacketEnum::BLOCK_CHANGE_RSP): {
BlockChangeRsp rsp; BlockChangeRsp rsp;
if (rsp.ParseFromArray(body_data.data(), body_data.size())) { if (rsp.ParseFromArray(body_data.data(), body_data.size())) {
m_world.receive_block_change(rsp); m_world.receive_block_change(rsp);
} }
} } break;
} }
} }
} catch (const asio::system_error& e) { } catch (const asio::system_error& e) {
@@ -104,6 +113,9 @@ asio::awaitable<void> NetworkClient::read_loop() {
} }
void NetworkClient::send(std::shared_ptr<std::vector<uint8_t>> packet) { void NetworkClient::send(std::shared_ptr<std::vector<uint8_t>> packet) {
if (m_closed.load()) {
return;
}
asio::post(m_strand, [self = shared_from_this(), asio::post(m_strand, [self = shared_from_this(),
packet = std::move(packet)]() mutable { packet = std::move(packet)]() mutable {
bool idle = self->m_write_queue.empty(); bool idle = self->m_write_queue.empty();
@@ -115,6 +127,9 @@ void NetworkClient::send(std::shared_ptr<std::vector<uint8_t>> packet) {
} }
void NetworkClient::do_write() { void NetworkClient::do_write() {
if (m_closed.load()) {
return;
}
auto self = shared_from_this(); auto self = shared_from_this();
asio::async_write( asio::async_write(
@@ -142,6 +157,17 @@ void NetworkClient::close() {
m_socket.shutdown(tcp::socket::shutdown_both, ec); m_socket.shutdown(tcp::socket::shutdown_both, ec);
m_socket.close(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 } // namespace Cubed

View File

@@ -32,9 +32,11 @@ void NetworkServer::stop() {
} }
if (m_net_thread.joinable()) { 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(); m_net_thread.join();
} }
Logger::info("Server Stopped!"); Logger::info("Server Net Thread Stopped!");
} }
asio::awaitable<void> NetworkServer::listen() { asio::awaitable<void> NetworkServer::listen() {

View File

@@ -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() { void ServerWorld::init_world() {
m_cave_carcer.init(ChunkGenerator::seed()); m_cave_carcer.init(ChunkGenerator::seed());
m_river_worm.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) { void ServerWorld::serever_run(std::stop_token stoken) {
Logger::info("Server Thread Started!"); Logger::info("Server Thread Started!");
while (!stoken.stop_requested()) { while (!stoken.stop_requested()) {
std::this_thread::sleep_for(milliseconds(m_per_tick_time)); auto t1 = system_clock::now();
if (m_tick_running) { if (m_tick_running) {
++m_game_ticks; ++m_game_ticks;
m_day_tick = (m_day_tick + 1) % DAY_TIME; m_day_tick = (m_day_tick + 1) % DAY_TIME;
} }
update(); update();
auto t2 = system_clock::now();
auto dt = duration_cast<microseconds>(t2 - t1);
auto st = std::max(dt, milliseconds(m_per_tick_time) - dt);
std::this_thread::sleep_for(st);
} }
Logger::info("Server Thread Stopped!"); Logger::info("Server Thread Stopped!");
} }
@@ -373,6 +388,10 @@ void ServerWorld::update() {
m_could_gen = true; 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, void ServerWorld::sync_player_pos(const std::string& uuid, float x, float y,

View File

@@ -58,6 +58,7 @@ asio::awaitable<void> Session::read_loop() {
co_await asio::async_read(m_socket, asio::buffer(body_data), co_await asio::async_read(m_socket, asio::buffer(body_data),
asio::use_awaitable); asio::use_awaitable);
} }
Logger::info("Session: Receive cmd {}", cmd_id);
constexpr auto& to_num = std::to_underlying<PacketEnum>; constexpr auto& to_num = std::to_underlying<PacketEnum>;
if (cmd_id == to_num(PacketEnum::LOGIN_REQ)) { if (cmd_id == to_num(PacketEnum::LOGIN_REQ)) {
LoginReq req; LoginReq req;

View File

@@ -8,4 +8,5 @@ import "auth/auth.proto";
import "system/ping.proto"; import "system/ping.proto";
import "system/pong.proto"; import "system/pong.proto";
import "world/chunk_data.proto"; import "world/chunk_data.proto";
import "world/block_change.proto"; import "world/block_change.proto";
import "world/time.proto";

View File

@@ -0,0 +1,6 @@
syntax = "proto3";
message UpdateTime {
int64 game_tick = 1;
int64 day_tick = 2;
}