mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
feat(gameplay): add time sync and network improvements
This commit is contained in:
@@ -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<std::string, Timer> m_timers;
|
||||
std::atomic<bool> m_game_running{false};
|
||||
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;
|
||||
void client_run(std::stop_token token);
|
||||
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
namespace Cubed {
|
||||
using asio::ip::tcp;
|
||||
class ClientWorld;
|
||||
class NetworkClient : std::enable_shared_from_this<NetworkClient> {
|
||||
class NetworkClient : public std::enable_shared_from_this<NetworkClient> {
|
||||
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<void> read_loop();
|
||||
|
||||
std::atomic<bool> m_closed{false};
|
||||
|
||||
std::atomic<bool> m_connected{false};
|
||||
// ClientWorld is managed by App
|
||||
ClientWorld& m_world;
|
||||
|
||||
|
||||
@@ -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 <typename T> constexpr uint16_t get_packet_id() {
|
||||
return to_num(BLOCK_CHANGE_REQ);
|
||||
} else if constexpr (is_same_v<U, BlockChangeRsp>) {
|
||||
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>) {
|
||||
return to_num(PING);
|
||||
} else if (is_same_v<U, Pong>) {
|
||||
|
||||
@@ -73,6 +73,12 @@ public:
|
||||
|
||||
void handle_chunk_req(const std::string& uuid, ChunkPos pos);
|
||||
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:
|
||||
enum class ChunkLoadStyle { RANDOM, CENTER };
|
||||
@@ -128,6 +134,7 @@ private:
|
||||
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::RANDOM};
|
||||
|
||||
PlayerUUIDMap m_uuid_to_name;
|
||||
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
|
||||
void init_chunks();
|
||||
|
||||
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 poll_finished_chunks();
|
||||
void wait_all_chunk_tasks();
|
||||
|
||||
void send_time();
|
||||
};
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<NetworkClient> 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<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
|
||||
@@ -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<void> 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<void> 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<PacketEnum>;
|
||||
switch (cmd_id) {
|
||||
case to_num(PacketEnum::LOGIN_RSP): {
|
||||
@@ -67,19 +76,19 @@ asio::awaitable<void> 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<void> NetworkClient::read_loop() {
|
||||
}
|
||||
|
||||
void NetworkClient::send(std::shared_ptr<std::vector<uint8_t>> 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<std::vector<uint8_t>> 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
|
||||
@@ -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<void> NetworkServer::listen() {
|
||||
|
||||
@@ -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<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!");
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -58,6 +58,7 @@ asio::awaitable<void> 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<PacketEnum>;
|
||||
if (cmd_id == to_num(PacketEnum::LOGIN_REQ)) {
|
||||
LoginReq req;
|
||||
|
||||
@@ -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";
|
||||
import "world/block_change.proto";
|
||||
import "world/time.proto";
|
||||
6
src/proto/world/time.proto
Normal file
6
src/proto/world/time.proto
Normal file
@@ -0,0 +1,6 @@
|
||||
syntax = "proto3";
|
||||
|
||||
message UpdateTime {
|
||||
int64 game_tick = 1;
|
||||
int64 day_tick = 2;
|
||||
}
|
||||
Reference in New Issue
Block a user