mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
feat(gameplay): add time sync and network improvements
This commit is contained in:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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>) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -9,3 +9,4 @@ 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";
|
||||||
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