9 Commits

Author SHA1 Message Date
d31ec68f3c feat(packet): implement zstd compression and refactor packet decoding 2026-06-26 20:20:34 +08:00
c7c6ee9a61 refactor(network): extract packet header decoding into function 2026-06-26 19:20:14 +08:00
db7b67f265 refactor(packet): replace zlib with zstd compression and restructure header
Increase header length to 12 bytes, add CompressType and PacketHeader struct, and implement decode_packet_header. Update CMake to find zstd and link against it, adding Findzstd module.
2026-06-26 19:12:39 +08:00
7e42f595c9 build: add ZLIB dependency 2026-06-26 17:23:35 +08:00
11c902d2bf fix(dev_panel): add protobuf to about table libraries 2026-06-26 17:18:05 +08:00
e5e84848a9 feat(gameplay): add player logout and disconnect handling 2026-06-26 17:05:09 +08:00
25e9b0e0d3 feat(gameplay): add thread pool for asynchronous chunk data processing 2026-06-26 16:14:20 +08:00
48300b5fe2 feat(multiplayer): add rendering of other players as colored cubes 2026-06-26 15:52:22 +08:00
3c14a4ffe1 feat(app): add client/server CLI arguments and init guards 2026-06-26 13:48:07 +08:00
30 changed files with 736 additions and 143 deletions

View File

@@ -17,10 +17,11 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
if(MSVC) if(MSVC)
add_compile_options(/utf-8) add_compile_options(/utf-8)
endif() endif()
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
find_package(OpenGL REQUIRED) find_package(OpenGL REQUIRED)
find_package(Protobuf REQUIRED) find_package(Protobuf REQUIRED)
find_package(absl REQUIRED) find_package(absl REQUIRED)
find_package(zstd REQUIRED)
if (UNIX AND NOT APPLE) if (UNIX AND NOT APPLE)
find_package(Freetype REQUIRED) find_package(Freetype REQUIRED)
@@ -207,6 +208,7 @@ target_link_libraries(${PROJECT_NAME}
protobuf::libprotobuf protobuf::libprotobuf
absl::log absl::log
absl::check absl::check
zstd::zstd
) )
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang") if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")

View File

@@ -0,0 +1,8 @@
#version 460
out vec4 color;
in vec2 tc;
void main() {
color = vec4(0.0, 0.0, 1.0, 1.0);
}

View File

@@ -0,0 +1,15 @@
#version 460
layout (location = 0) in vec3 pos;
layout (location = 1) in vec2 texCoord;
uniform mat4 mv_matrix;
uniform mat4 proj_matrix;
out vec2 tc;
void main() {
vec4 viewPos = mv_matrix * vec4(pos, 1.0);
tc = texCoord;
gl_Position = proj_matrix * viewPos;
}

View File

@@ -0,0 +1,22 @@
find_path(ZSTD_INCLUDE_DIRS
NAMES zstd.h
HINTS ${zstd_ROOT_DIR}/include)
find_library(ZSTD_LIBRARIES
NAMES zstd
HINTS ${zstd_ROOT_DIR}/lib)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(zstd DEFAULT_MSG ZSTD_LIBRARIES ZSTD_INCLUDE_DIRS)
mark_as_advanced(
ZSTD_LIBRARIES
ZSTD_INCLUDE_DIRS)
if(ZSTD_FOUND AND NOT (TARGET zstd::zstd))
add_library (zstd::zstd UNKNOWN IMPORTED)
set_target_properties(zstd::zstd
PROPERTIES
IMPORTED_LOCATION ${ZSTD_LIBRARIES}
INTERFACE_INCLUDE_DIRECTORIES ${ZSTD_INCLUDE_DIRS})
endif()

View File

@@ -12,6 +12,12 @@ namespace Cubed {
class App { class App {
public: public:
struct Argument {
bool is_client = false;
int port = 25530;
std::string ip{"127.0.0.1"};
};
App(); App();
~App(); ~App();
static void cursor_position_callback(GLFWwindow* window, double xpos, static void cursor_position_callback(GLFWwindow* window, double xpos,
@@ -60,9 +66,9 @@ private:
inline static double fps_time_count = 0.0f; inline static double fps_time_count = 0.0f;
inline static int frame_count = 0; inline static int frame_count = 0;
inline static int fps = 0; inline static int fps = 0;
Argument m_argument;
void init(); void init(int argc, char** argv);
void handle_argument(int argc, char** argv);
auto init_camera(); auto init_camera();
auto init_texture(); auto init_texture();
auto init_world(); auto init_world();

View File

@@ -5,11 +5,22 @@
#include "Cubed/gameplay/client_player.hpp" #include "Cubed/gameplay/client_player.hpp"
#include "Cubed/gameplay/game_time.hpp" #include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/network_client.hpp" #include "Cubed/gameplay/network_client.hpp"
#include "Cubed/tools/thread_pool.hpp"
#include <deque> #include <deque>
#include <tbb/concurrent_unordered_map.h> #include <tbb/concurrent_unordered_map.h>
namespace Cubed { namespace Cubed {
struct OtherPlayerInfo {
std::string name;
glm::vec3 pos;
};
struct RenderPlayerData {
std::string name;
glm::vec3 pos;
};
class ClientWorld { class ClientWorld {
public: public:
ClientWorld(); ClientWorld();
@@ -34,17 +45,24 @@ public:
void receive_block_change(const BlockChangeRsp& rsp); void receive_block_change(const BlockChangeRsp& rsp);
void receive_time(const UpdateTime& rsp); void receive_time(const UpdateTime& rsp);
void receive_other_player(const PlayerInfoRsp& rsp);
void receive_player_logout(const LogoutRsp& 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);
void stop_client_thread(); void stop_client_thread();
void start_thread_pool();
void stop_thread_pool();
void change_pool_threads(int threads);
void hot_reload(); void hot_reload();
void request_chunk(); void request_chunk();
std::vector<glm::vec4>& planes(); std::vector<glm::vec4>& planes();
std::vector<ChunkRenderSnapshot>& render_snapshots(); const std::vector<ChunkRenderSnapshot>& render_snapshots() const;
const std::vector<RenderPlayerData>& render_player_data() const;
glm::vec3 sunlight_dir() const; glm::vec3 sunlight_dir() const;
void receive_chunk(const ChunkDataRsp& data); void receive_chunk(const ChunkDataRsp& data);
void exit();
template <typename Fn> template <typename Fn>
void register_timer(std::string_view id, TickType threshold, Fn&& f) { void register_timer(std::string_view id, TickType threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct, m_timers.emplace(std::piecewise_construct,
@@ -58,7 +76,9 @@ private:
tbb::concurrent_unordered_map<ChunkPos, ClientChunk, ChunkPos::Hash>; tbb::concurrent_unordered_map<ChunkPos, ClientChunk, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>; using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using ChunkPosVector = std::vector<ChunkPos>; using ChunkPosVector = std::vector<ChunkPos>;
using OtherPlayerHashMap = std::unordered_map<std::string, OtherPlayerInfo>;
ClientPlayer m_player; ClientPlayer m_player;
OtherPlayerHashMap m_other_players;
ChunkHashMap m_chunks; ChunkHashMap m_chunks;
std::vector<glm::vec4> m_planes; std::vector<glm::vec4> m_planes;
std::jthread m_client_thread; std::jthread m_client_thread;
@@ -67,13 +87,16 @@ private:
std::mutex m_delete_vbo_mutex; std::mutex m_delete_vbo_mutex;
std::mutex m_delete_vao_mutex; std::mutex m_delete_vao_mutex;
std::mutex m_pending_upload_queue_mutex; std::mutex m_pending_upload_queue_mutex;
std::mutex m_pending_chunk_data_queue_mutex; std::mutex m_other_players_mutex;
std::deque<ClientChunk> m_pending_upload_queue; std::deque<ClientChunk> m_pending_upload_queue;
std::deque<ChunkDataRsp> m_pending_chunk_data_queue;
std::vector<GLuint> m_pending_delete_vbo; std::vector<GLuint> m_pending_delete_vbo;
std::vector<GLuint> m_pending_delete_vao; std::vector<GLuint> m_pending_delete_vao;
std::deque<ChunkPos> m_dirty_queue; std::deque<ChunkPos> m_dirty_queue;
std::vector<ChunkRenderSnapshot> m_render_snapshots; std::vector<ChunkRenderSnapshot> m_render_snapshots;
std::vector<RenderPlayerData> m_render_player_data;
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};
@@ -82,6 +105,9 @@ private:
std::atomic<bool> m_requesting_chunk{false}; std::atomic<bool> m_requesting_chunk{false};
std::shared_ptr<NetworkClient> m_client; std::shared_ptr<NetworkClient> m_client;
ChunkLoadStyle m_chunk_load_style{ChunkLoadStyle::CENTER}; ChunkLoadStyle m_chunk_load_style{ChunkLoadStyle::CENTER};
std::atomic<std::shared_ptr<ThreadPool>> m_thread_pool;
void client_run(std::stop_token token); void client_run(std::stop_token token);
void set_player_pos(); void set_player_pos();

View File

@@ -17,6 +17,7 @@ public:
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; bool is_connected() const;
bool is_connect_error() const;
private: private:
asio::io_context m_io; asio::io_context m_io;
@@ -32,6 +33,7 @@ private:
std::atomic<bool> m_closed{false}; std::atomic<bool> m_closed{false};
std::atomic<bool> m_connected{false}; std::atomic<bool> m_connected{false};
std::atomic<bool> m_connect_error{false};
// ClientWorld is managed by App // ClientWorld is managed by App
ClientWorld& m_world; ClientWorld& m_world;

View File

@@ -13,7 +13,7 @@ public:
void stop(); void stop();
// Run in another thread after initialization is complete // Run in another thread after initialization is complete
void start_server(); void start_server(int port = 25530);
int port() const; int port() const;
ServerWorld& server_world(); ServerWorld& server_world();
@@ -23,6 +23,7 @@ private:
std::thread m_net_thread; std::thread m_net_thread;
int m_port = 25530; int m_port = 25530;
std::atomic<bool> m_stopped{false}; std::atomic<bool> m_stopped{false};
std::atomic<bool> m_started{false};
ServerWorld m_world; ServerWorld m_world;
std::mutex m_session_mutex; std::mutex m_session_mutex;
std::unordered_map<std::string, std::shared_ptr<Session>> m_session; std::unordered_map<std::string, std::shared_ptr<Session>> m_session;

View File

@@ -1,18 +1,53 @@
#pragma once #pragma once
#include "Cubed/tools/compression.hpp"
#include "packet.pb.h" // IWYU pragma: keep #include "packet.pb.h" // IWYU pragma: keep
#include <concepts>
#include <cstdint>
#include <cstring>
#include <memory>
#include <netinet/in.h> #include <netinet/in.h>
#include <span>
#include <stdexcept>
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
namespace Cubed { namespace Cubed {
constexpr int HEADER_LEN = 8; constexpr size_t HEADER_LEN =
sizeof(uint16_t) + sizeof(uint16_t) + sizeof(uint32_t) + sizeof(uint32_t);
constexpr size_t PACKET_COMPRESSION_THRESHOLD = 100;
using Packet = std::shared_ptr<std::vector<uint8_t>>; using Packet = std::shared_ptr<std::vector<uint8_t>>;
enum class CompressType : uint16_t {
NONE = 0,
ZSTD = 1,
};
enum class PacketEnum { inline CompressType get_compress_type(uint16_t id) {
using enum CompressType;
constexpr auto& to = std::to_underlying<CompressType>;
switch (id) {
case to(NONE):
return NONE;
case to(ZSTD):
return ZSTD;
}
throw std::runtime_error(std::format("Unknown CompressType {}", id));
}
struct PacketHeader {
uint16_t cmd{};
CompressType compress_type{}; // 0=none 1=zlib
uint32_t uncompressed_size{};
uint32_t compressed_size{};
};
enum class PacketEnum : uint16_t {
LOGIN_REQ = 1001, LOGIN_REQ = 1001,
LOGIN_RSP = 1002, LOGIN_RSP = 1002,
LOGOUT_REQ = 1003,
LOGOUT_RSP = 1004,
PLAYER_INFO = 2001, PLAYER_INFO = 2001,
PLAYER_POS = 2002, PLAYER_POS = 2002,
PLAYER_INFO_RSP = 2003,
CHUNK_DATA_REQ = 3001, CHUNK_DATA_REQ = 3001,
CHUNK_DATA_RSP = 3002, CHUNK_DATA_RSP = 3002,
BLOCK_CHANGE_REQ = 3003, BLOCK_CHANGE_REQ = 3003,
@@ -34,10 +69,16 @@ template <typename T> constexpr uint16_t get_packet_id() {
return to_num(LOGIN_REQ); return to_num(LOGIN_REQ);
} else if constexpr (is_same_v<U, LoginRsp>) { } else if constexpr (is_same_v<U, LoginRsp>) {
return to_num(LOGIN_RSP); return to_num(LOGIN_RSP);
} else if constexpr (is_same_v<U, LogoutReq>) {
return to_num(LOGOUT_REQ);
} else if constexpr (is_same_v<U, LogoutRsp>) {
return to_num(LOGOUT_RSP);
} else if constexpr (is_same_v<U, PlayerInfo>) { } else if constexpr (is_same_v<U, PlayerInfo>) {
return to_num(PLAYER_INFO); return to_num(PLAYER_INFO);
} else if constexpr (is_same_v<U, PlayerPos>) { } else if constexpr (is_same_v<U, PlayerPos>) {
return to_num(PLAYER_POS); return to_num(PLAYER_POS);
} else if constexpr (is_same_v<U, PlayerInfoRsp>) {
return to_num(PLAYER_INFO_RSP);
} else if constexpr (is_same_v<U, ChunkDataReq>) { } else if constexpr (is_same_v<U, ChunkDataReq>) {
return to_num(CHUNK_DATA_REQ); return to_num(CHUNK_DATA_REQ);
} else if constexpr (is_same_v<U, ChunkDataRsp>) { } else if constexpr (is_same_v<U, ChunkDataRsp>) {
@@ -50,35 +91,106 @@ template <typename T> constexpr uint16_t get_packet_id() {
return to_num(UPDATE_TIME); 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 constexpr (is_same_v<U, Pong>) {
return to_num(PONG); return to_num(PONG);
} else { } else {
static_assert(always_false<U>::value, "Unkonw Type"); static_assert(always_false<U>::value, "Unknown Type");
} }
} }
template <typename T> Packet make_packet(const T& msg) { template <typename T>
uint16_t cmd = get_packet_id<T>(); requires std::derived_from<T, google::protobuf::Message>
Packet make_packet(const T& msg) {
PacketHeader header{};
header.cmd = get_packet_id<T>();
uint32_t raw_size = static_cast<uint32_t>(msg.ByteSizeLong());
std::vector<uint8_t> raw(raw_size);
uint32_t body_len = static_cast<uint32_t>(msg.ByteSizeLong()); if (!msg.SerializeToArray(raw.data(), raw_size)) {
uint32_t total_len = HEADER_LEN + body_len;
auto packet = std::make_shared<std::vector<uint8_t>>(total_len);
uint32_t total_len_net = htonl(total_len);
uint16_t cmd_net = htons(cmd);
std::memcpy(packet->data(), &total_len_net, sizeof(total_len_net));
std::memcpy(packet->data() + 4, &cmd_net, sizeof(cmd_net));
if (!msg.SerializeToArray(packet->data() + HEADER_LEN,
static_cast<int>(body_len))) {
return {}; return {};
} }
std::vector<uint8_t> payload;
if (raw_size >= PACKET_COMPRESSION_THRESHOLD) {
std::vector<uint8_t> compressed = compress_data(raw);
if (compressed.size() < raw.size()) {
payload = std::move(compressed);
header.compress_type = CompressType::ZSTD;
} else {
payload = std::move(raw);
header.compress_type = CompressType::NONE;
}
} else {
payload = std::move(raw);
header.compress_type = CompressType::NONE;
}
header.uncompressed_size = raw_size;
header.compressed_size = static_cast<uint32_t>(payload.size());
auto packet =
std::make_shared<std::vector<uint8_t>>(HEADER_LEN + payload.size());
uint16_t cmd_net = htons(header.cmd);
uint16_t compress_type_net =
htons(std::to_underlying(header.compress_type));
uint32_t uncompressed_size_net = htonl(header.uncompressed_size);
uint32_t compressed_size_net = htonl(header.compressed_size);
std::memcpy(packet->data(), &cmd_net, sizeof(cmd_net));
std::memcpy(packet->data() + 2, &compress_type_net,
sizeof(compress_type_net));
std::memcpy(packet->data() + 4, &uncompressed_size_net,
sizeof(uncompressed_size_net));
std::memcpy(packet->data() + 8, &compressed_size_net,
sizeof(compressed_size_net));
std::memcpy(packet->data() + HEADER_LEN, payload.data(), payload.size());
return packet; return packet;
} }
inline PacketHeader decode_packet_header(std::span<const uint8_t> header) {
if (header.size() < HEADER_LEN)
throw std::runtime_error("Invalid header");
uint16_t cmd_net;
uint16_t compress_type_net;
uint32_t uncompressed_size_net;
uint32_t compressed_size_net;
std::memcpy(&cmd_net, header.data(), sizeof(cmd_net));
std::memcpy(&compress_type_net, header.data() + 2,
sizeof(compress_type_net));
std::memcpy(&uncompressed_size_net, header.data() + 4,
sizeof(uncompressed_size_net));
std::memcpy(&compressed_size_net, header.data() + 8,
sizeof(compressed_size_net));
return {ntohs(cmd_net), get_compress_type(ntohs(compress_type_net)),
ntohl(uncompressed_size_net), ntohl(compressed_size_net)};
}
template <typename T>
requires std::derived_from<T, google::protobuf::Message>
bool decode_packet(T& message, std::span<const uint8_t> data,
const PacketHeader& header) {
if (data.size() != header.compressed_size) {
return false;
}
if (header.compress_type == CompressType::NONE &&
header.uncompressed_size != header.compressed_size) {
return false;
}
switch (header.compress_type) {
case CompressType::NONE: {
return message.ParseFromArray(
data.data(), static_cast<int>(header.uncompressed_size));
}
case CompressType::ZSTD: {
auto raw = decompress_data(data, header.uncompressed_size);
return message.ParseFromArray(raw.data(), static_cast<int>(raw.size()));
}
default:
return false;
}
}
} // namespace Cubed } // namespace Cubed

View File

@@ -1,6 +1,8 @@
#pragma once #pragma once
#include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_time.hpp"
#include <atomic>
#include <glm/glm.hpp> #include <glm/glm.hpp>
#include <memory> #include <memory>
#include <string> #include <string>
@@ -11,19 +13,24 @@ class Session;
class ServerPlayer { class ServerPlayer {
public: public:
ServerPlayer(std::string_view name, std::string_view uuid, ServerPlayer(std::string_view name, std::string_view uuid,
ServerWorld& m_world, std::shared_ptr<Session> session); ServerWorld& m_world, std::shared_ptr<Session> session,
TickType gametick);
const glm::vec3& get_pos() const; const glm::vec3& get_pos() const;
const std::string& get_name() const; const std::string& get_name() const;
const std::string& get_uuid() const; const std::string& get_uuid() const;
std::shared_ptr<Session> get_session() const; std::shared_ptr<Session> get_session() const;
void update_pos(float x, float y, float z); void update_pos(float x, float y, float z);
void update_sync_gametick(TickType gametick);
bool is_disconnect(TickType current_gametick) const;
private: private:
static constexpr TickType TIMEOUT = 200;
std::string m_name; std::string m_name;
std::string m_uuid; std::string m_uuid;
glm::vec3 m_pos{0.0f}; glm::vec3 m_pos{0.0f};
ServerWorld& m_world; ServerWorld& m_world;
ChunkPos m_last_chunk_pos{0, 0}; ChunkPos m_last_chunk_pos{0, 0};
std::shared_ptr<Session> m_session; std::shared_ptr<Session> m_session;
std::atomic<TickType> m_last_gametick{0};
}; };
} // namespace Cubed } // namespace Cubed

View File

@@ -24,7 +24,7 @@ public:
ServerWorld(); ServerWorld();
~ServerWorld(); ~ServerWorld();
void player_join(std::string_view name, std::string_view uuid); void player_join(std::string_view name, std::string_view uuid);
void handle_player_exit(const std::string& name); void handle_player_exit(const std::string& uuid);
void init_world(); void init_world();
void need_gen(std::optional<std::string> uuid); void need_gen(std::optional<std::string> uuid);
void update(); void update();
@@ -112,6 +112,7 @@ private:
std::atomic<bool> m_gen_running{false}; std::atomic<bool> m_gen_running{false};
std::atomic<bool> m_need_gen_chunk{false}; std::atomic<bool> m_need_gen_chunk{false};
std::atomic<bool> m_is_rebuilding{false}; std::atomic<bool> m_is_rebuilding{false};
std::atomic<bool> m_init{false};
std::atomic<int> m_rendering_distance{24}; std::atomic<int> m_rendering_distance{24};
std::atomic<int> m_pool_threads{0}; std::atomic<int> m_pool_threads{0};
std::atomic<int> m_max_threads{1}; std::atomic<int> m_max_threads{1};

View File

@@ -272,6 +272,55 @@ constexpr float CROSS_TANGENTS[2][6][3] = {
{-0.7071f, 0.0f, 0.7071f}}}; {-0.7071f, 0.0f, 0.7071f}}};
#pragma endregion #pragma endregion
#pragma region Player
constexpr float VERTICES_PLAYER[6][6][3] = {
// ===== front (z = +1) =====
{{0.0f, 0.0f, 1.0f}, // bottom left
{0.0f, 2.0f, 1.0f}, // top left
{1.0f, 2.0f, 1.0f}, // top right
{1.0f, 2.0f, 1.0f}, // top right
{1.0f, 0.0f, 1.0f}, // bottom right
{0.0f, 0.0f, 1.0f}}, // bottom left
// ===== right (x = +1) =====
{{1.0f, 0.0f, 1.0f}, // bottom front
{1.0f, 0.0f, 0.0f}, // bottom back
{1.0f, 2.0f, 0.0f}, // top back
{1.0f, 2.0f, 0.0f}, // top back
{1.0f, 2.0f, 1.0f}, // top front
{1.0f, 0.0f, 1.0f}}, // bottom front
// ===== back (z = -1) =====
{{0.0f, 0.0f, 0.0f}, // bottom left
{1.0f, 0.0f, 0.0f}, // bottom right
{1.0f, 2.0f, 0.0f}, // top right
{1.0f, 2.0f, 0.0f}, // top right
{0.0f, 2.0f, 0.0f}, // top left
{0.0f, 0.0f, 0.0f}}, // bottom left
// ===== left (x = -1) =====
{{0.0f, 0.0f, 0.0f}, // bottom back
{0.0f, 0.0f, 1.0f}, // bottom front
{0.0f, 2.0f, 1.0f}, // top front
{0.0f, 2.0f, 1.0f}, // top front
{0.0f, 2.0f, 0.0f}, // top back
{0.0f, 0.0f, 0.0f}}, // bottom back
// ===== top (y = +2) =====
{{0.0f, 2.0f, 0.0f}, // back left
{1.0f, 2.0f, 0.0f}, // back right
{1.0f, 2.0f, 1.0f}, // front right
{1.0f, 2.0f, 1.0f}, // front right
{0.0f, 2.0f, 1.0f}, // front left
{0.0f, 2.0f, 0.0f}}, // back left
// ===== bottom (y = -1) =====
{{0.0f, 0.0f, 1.0f}, // front left
{1.0f, 0.0f, 1.0f}, // front right
{1.0f, 0.0f, 0.0f}, // back right
{1.0f, 0.0f, 0.0f}, // back right
{0.0f, 0.0f, 0.0f}, // back left
{0.0f, 0.0f, 1.0f}} // front left
};
#pragma endregion
// [-1, 1] // [-1, 1]
constexpr float QUAD_VERTICES[] = { constexpr float QUAD_VERTICES[] = {
// postion // texcoorlds // postion // texcoorlds

View File

@@ -99,6 +99,9 @@ private:
bool m_water_depth_fade = true; bool m_water_depth_fade = true;
bool m_pbr = true; bool m_pbr = true;
bool m_flip_y = false; bool m_flip_y = false;
bool m_init = false;
int m_shadow_mode = 0; int m_shadow_mode = 0;
int m_light_cull_face = 0; int m_light_cull_face = 0;
float m_aspect = 0.0f; float m_aspect = 0.0f;
@@ -119,7 +122,7 @@ private:
GLuint m_outline_indices_vbo = 0; GLuint m_outline_indices_vbo = 0;
GLuint m_outline_vbo = 0; GLuint m_outline_vbo = 0;
GLuint m_ui_vbo = 0; GLuint m_ui_vbo = 0;
GLuint m_player_vbo = 0;
GLuint m_fbo = 0; GLuint m_fbo = 0;
GLuint m_screen_texture = 0; GLuint m_screen_texture = 0;
GLuint m_screen_depth_texture = 0; GLuint m_screen_depth_texture = 0;
@@ -171,7 +174,7 @@ private:
2 - outline vao 2 - outline vao
3 - ui vao 3 - ui vao
4 - text vao 4 - text vao
5 - player vao
*/ */
std::vector<GLuint> m_vao; std::vector<GLuint> m_vao;
std::vector<Vertex2D> m_ui; std::vector<Vertex2D> m_ui;
@@ -186,6 +189,7 @@ private:
void render_text(); void render_text();
void render_ui(); void render_ui();
void render_world(); void render_world();
void render_player();
void render_underwater(); void render_underwater();
void render_dev_panel(); void render_dev_panel();

View File

@@ -8,6 +8,7 @@ namespace Cubed {
class TextureManager { class TextureManager {
private: private:
bool m_need_reload = false; bool m_need_reload = false;
bool m_init = false;
GLuint m_block_status_array = 0; GLuint m_block_status_array = 0;
GLuint m_texture_array = 0; GLuint m_texture_array = 0;
GLuint m_cross_plane_array = 0; GLuint m_cross_plane_array = 0;

View File

@@ -0,0 +1,35 @@
#pragma once
#include <format>
#include <span>
#include <stdexcept>
#include <string_view>
namespace Cubed {
class ArgParser {
public:
ArgParser(int argc, char** argv) : m_args(argv, argc) {};
ArgParser(std::span<char*> args) : m_args(args) {}
bool has_next() const { return m_index < m_args.size(); }
std::string_view next() {
if (!has_next()) {
throw std::runtime_error("No more arguments");
}
return m_args[m_index++];
}
std::string_view require_next(std::string_view option) {
if (!has_next()) {
throw std::runtime_error(
std::format("{} requires an argument", option));
}
return next();
}
private:
std::span<char*> m_args;
size_t m_index = 1;
};
} // namespace Cubed

View File

@@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
#include <format>
#include <span>
#include <stdexcept>
#include <vector>
#include <zstd.h>
namespace Cubed {
constexpr int DEFAULT_ZSTD_LEVEL = 3;
inline std::vector<uint8_t> compress_data(std::span<const uint8_t> data) {
size_t max_size = ZSTD_compressBound(data.size());
std::vector<uint8_t> compressed_data(max_size);
size_t compressed_bytes =
ZSTD_compress(compressed_data.data(), max_size, data.data(),
data.size(), DEFAULT_ZSTD_LEVEL);
if (ZSTD_isError(compressed_bytes)) {
throw std::runtime_error(std::format(
"Compress Fail {}", ZSTD_getErrorName(compressed_bytes)));
}
compressed_data.resize(compressed_bytes);
return compressed_data;
}
inline std::vector<uint8_t> decompress_data(std::span<const uint8_t> data,
uint32_t original_size) {
std::vector<uint8_t> decompressed_data(original_size);
size_t decompressed_bytes = ZSTD_decompress(
decompressed_data.data(), original_size, data.data(), data.size());
if (ZSTD_isError(decompressed_bytes)) {
throw std::runtime_error(std::format(
"Decompress Fail {}", ZSTD_getErrorName(decompressed_bytes)));
}
if (decompressed_bytes != original_size) {
throw std::runtime_error("Unexpected decompressed size");
}
return decompressed_data;
}
} // namespace Cubed

View File

@@ -23,6 +23,7 @@ public:
private: private:
bool m_mouse_enable = false; bool m_mouse_enable = false;
bool m_imgui_init = false;
float m_aspect; float m_aspect;
GLFWwindow* m_window; GLFWwindow* m_window;
int m_width; int m_width;

View File

@@ -2,13 +2,13 @@
#include "Cubed/config.hpp" #include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp" #include "Cubed/debug_collector.hpp"
#include "Cubed/tools/arg_parser.hpp"
#include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp" #include "Cubed/tools/log.hpp"
#include "Cubed/tools/system_info.hpp" #include "Cubed/tools/system_info.hpp"
#include <exception> #include <exception>
#include <imgui_impl_glfw.h> #include <imgui_impl_glfw.h>
namespace Cubed { namespace Cubed {
App::App() {} App::App() {}
@@ -33,9 +33,11 @@ void App::cursor_position_callback(GLFWwindow* window, double xpos,
app->m_camera.update_cursor_position_camera(xpos, ypos); app->m_camera.update_cursor_position_camera(xpos, ypos);
} }
} }
void App::init() { void App::init(int argc, char** argv) {
handle_argument(argc, argv);
m_window.init(); m_window.init();
m_window.imgui_init(); m_window.imgui_init();
Logger::info("Window Init Success"); Logger::info("Window Init Success");
glfwSetWindowUserPointer(m_window.get_glfw_window(), this); glfwSetWindowUserPointer(m_window.get_glfw_window(), this);
@@ -53,6 +55,7 @@ void App::init() {
glfwSetCursorEnterCallback(m_window.get_glfw_window(), glfwSetCursorEnterCallback(m_window.get_glfw_window(),
cursor_enter_callback); cursor_enter_callback);
glfwSetCharCallback(m_window.get_glfw_window(), char_callback); glfwSetCharCallback(m_window.get_glfw_window(), char_callback);
ChunkGenerator::init(); ChunkGenerator::init();
BlockManager::init(); BlockManager::init();
m_renderer.init(); m_renderer.init();
@@ -61,12 +64,13 @@ void App::init() {
// MapTable::init_map(); // MapTable::init_map();
m_texture_manager.init_texture(); m_texture_manager.init_texture();
Logger::info("Texture Load Success"); Logger::info("Texture Load Success");
if (!m_argument.is_client) {
m_server.start_server(); m_server.start_server(m_argument.port);
}
m_client = std::make_shared<NetworkClient>(m_client_world); m_client = std::make_shared<NetworkClient>(m_client_world);
m_client->start("127.0.0.1", 25530); m_client->start(m_argument.ip, m_argument.port);
// init will send packet // init will send packet
m_client_world.init("test", m_client); m_client_world.init("test", m_client);
@@ -76,6 +80,53 @@ void App::init() {
m_dev_panel.init(); m_dev_panel.init();
} }
void App::handle_argument(int argc, char** argv) {
static const std::unordered_map<std::string_view,
std::function<void(ArgParser&)>>
HANDLERS{
{"--client", [&](ArgParser&) { m_argument.is_client = true; }},
{"--host", [&](ArgParser&) { m_argument.is_client = false; }},
{"-p",
[&](ArgParser& p) {
auto arg = p.require_next("-p");
auto r =
std::from_chars(arg.begin(), arg.end(), m_argument.port);
if (r.ec != std::errc{} || r.ptr != arg.end()) {
throw std::runtime_error(
std::format("Invalid port: {}", arg));
}
if (m_argument.port > 65535) {
throw std::runtime_error(
std::format("Port {} out of range", m_argument.port));
}
}},
{"--ip",
[&](ArgParser& p) {
auto arg = p.require_next("--ip");
m_argument.ip = arg;
}}
};
ArgParser parser(argc, argv);
while (parser.has_next()) {
auto arg = parser.next();
if (auto it = HANDLERS.find(arg); it != HANDLERS.end()) {
it->second(parser);
} else {
Logger::warn("Unknown argument: {}", arg);
}
}
}
void App::key_callback(GLFWwindow* window, int key, int scancode, int action, void App::key_callback(GLFWwindow* window, int key, int scancode, int action,
int mods) { int mods) {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@@ -233,6 +284,7 @@ void App::run() {
update(); update();
render(); render();
} }
m_client_world.exit();
} }
static Gait player_gait = Gait::WALK; static Gait player_gait = Gait::WALK;
void App::update() { void App::update() {
@@ -276,7 +328,7 @@ int App::start_cubed_application(int argc, char** argv) {
App app; App app;
try { try {
app.init(); app.init(argc, argv);
Logger::info("Game Init Finish Start Run..."); Logger::info("Game Init Finish Start Run...");
app.run(); app.run();

View File

@@ -112,6 +112,8 @@ void DevPanel::show_about_table_bar() {
ImGui::Text("Dear ImGui"); ImGui::Text("Dear ImGui");
ImGui::Text("Tbb"); ImGui::Text("Tbb");
ImGui::Text("Asio"); ImGui::Text("Asio");
ImGui::Text("protobuf");
ImGui::Text("zstd");
ImGui::Separator(); ImGui::Separator();
ImGui::Text("Special Thanks"); ImGui::Text("Special Thanks");
ImGui::Text("TANGERIME"); ImGui::Text("TANGERIME");

View File

@@ -18,7 +18,7 @@ ClientWorld::ClientWorld() : m_player(*this) {}
ClientWorld::~ClientWorld() { ClientWorld::~ClientWorld() {
stop_client_thread(); stop_client_thread();
stop_thread_pool();
{ {
std::lock_guard lock(m_chunks_mutex); std::lock_guard lock(m_chunks_mutex);
m_chunks.clear(); m_chunks.clear();
@@ -190,18 +190,44 @@ void ClientWorld::receive_time(const UpdateTime& rsp) {
m_day_tick = rsp.day_tick(); m_day_tick = rsp.day_tick();
} }
void ClientWorld::receive_other_player(const PlayerInfoRsp& rsp) {
{
std::lock_guard lock(m_other_players_mutex);
m_other_players[rsp.uuid()].name = rsp.name();
m_other_players[rsp.uuid()].pos =
glm::vec3{rsp.pos().x(), rsp.pos().y(), rsp.pos().z()};
// Logger::info("Player {} pos Update", rsp.name());
}
}
void ClientWorld::receive_player_logout(const LogoutRsp& rsp) {
{
std::lock_guard lock(m_other_players_mutex);
int sum = m_other_players.erase(rsp.uuid());
if (sum == 0) {
Logger::warn("Player {} not find", rsp.uuid());
} else {
Logger::info("Player {} erase", rsp.uuid());
}
}
}
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);
m_player.init(player_name); m_player.init(player_name);
m_client = client; m_client = client;
// timer // timer
register_timer("player_pos", 2, [this]() { report_player_pos(); }); register_timer("player_pos", 1, [this]() { report_player_pos(); });
LoginReq req; LoginReq req;
req.set_name(m_player.get_name()); req.set_name(m_player.get_name());
while (!client->is_connected()) { while (!client->is_connected()) {
if (client->is_connect_error()) {
throw std::runtime_error("Can't connect to the server");
}
std::this_thread::sleep_for(milliseconds(200)); std::this_thread::sleep_for(milliseconds(200));
} }
start_thread_pool();
// request login // request login
Logger::info("Send Login Request"); Logger::info("Send Login Request");
m_client->send(make_packet(req)); m_client->send(make_packet(req));
@@ -228,6 +254,26 @@ void ClientWorld::stop_client_thread() {
} }
m_game_running = false; m_game_running = false;
} }
void ClientWorld::start_thread_pool() { change_pool_threads(1); }
void ClientWorld::stop_thread_pool() {
auto pool_ptr = m_thread_pool.load();
if (pool_ptr) {
pool_ptr->stop();
}
m_thread_pool.store(nullptr);
Logger::info("Thread Pool Stopped");
}
void ClientWorld::change_pool_threads(int threads) {
int m_max_threads = std::thread::hardware_concurrency();
if (m_max_threads < 1) {
Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4");
m_max_threads = 1;
}
int used_thread = std::clamp(threads, 1, m_max_threads);
Logger::info("Create New Thread Pool Use {} Threads", used_thread);
m_thread_pool.store(std::make_shared<ThreadPool>(used_thread));
}
void ClientWorld::hot_reload() { void ClientWorld::hot_reload() {
auto& config = Config::get(); auto& config = Config::get();
@@ -238,28 +284,16 @@ void ClientWorld::hot_reload() {
void ClientWorld::client_run(std::stop_token stoken) { void ClientWorld::client_run(std::stop_token stoken) {
Logger::info("Client Thread Started"); Logger::info("Client Thread Started");
while (!stoken.stop_requested()) { while (!stoken.stop_requested()) {
auto t1 = system_clock::now();
for (auto& x : m_timers) { for (auto& x : m_timers) {
x.second.update(); x.second.update();
} }
// vertex data will genrator in client thread instead of net thread;
std::vector<ChunkDataRsp> temp_data;
{
std::lock_guard lock(m_pending_chunk_data_queue_mutex);
for (auto& x : m_pending_chunk_data_queue) {
temp_data.emplace_back(std::move(x));
}
m_pending_chunk_data_queue.clear();
}
for (auto& x : temp_data) {
ClientChunk chunk{*this};
chunk.receive_chunk(x);
{
std::lock_guard lock(m_pending_upload_queue_mutex);
m_pending_upload_queue.emplace_back(std::move(chunk));
}
}
std::this_thread::sleep_for(milliseconds(DEFAULT_PER_TICK_TIME)); auto t2 = system_clock::now();
auto dt = duration_cast<microseconds>(t2 - t1);
auto st = std::max(dt, milliseconds(DEFAULT_PER_TICK_TIME) - dt);
std::this_thread::sleep_for(st);
} }
} }
@@ -367,11 +401,26 @@ void ClientWorld::receive_chunk(const ChunkDataRsp& data) {
return; return;
} }
} }
// vertex data will genrator in client thread pool instead of net thread;
{ auto pool = m_thread_pool.load();
std::lock_guard lock(m_pending_chunk_data_queue_mutex); if (!pool) {
m_pending_chunk_data_queue.emplace_back(std::move(data)); Logger::error("Client Thread Pool is nullptr");
return;
} }
pool->enqueue([this, data = std::move(data)]() {
ClientChunk chunk{*this};
chunk.receive_chunk(data);
{
std::lock_guard lock(m_pending_upload_queue_mutex);
m_pending_upload_queue.emplace_back(std::move(chunk));
}
});
}
void ClientWorld::exit() {
LogoutReq req;
req.set_uuid(m_player.get_uuid());
m_client->send(make_packet(req));
} }
void ClientWorld::update(float delta_time) { void ClientWorld::update(float delta_time) {
@@ -447,6 +496,13 @@ void ClientWorld::update(float delta_time) {
} }
} }
} }
m_render_player_data.clear();
{
std::lock_guard lock(m_other_players_mutex);
for (auto& [uuid, player] : m_other_players) {
m_render_player_data.emplace_back(player.name, player.pos);
}
}
} }
glm::vec3 ClientWorld::sunlight_dir() const { glm::vec3 ClientWorld::sunlight_dir() const {
@@ -473,8 +529,11 @@ int ClientWorld::rendering_distance() const {
void ClientWorld::rendering_distance(int rendering_distance) { void ClientWorld::rendering_distance(int rendering_distance) {
m_rendering_distance = rendering_distance; m_rendering_distance = rendering_distance;
} }
std::vector<ChunkRenderSnapshot>& ClientWorld::render_snapshots() { const std::vector<ChunkRenderSnapshot>& ClientWorld::render_snapshots() const {
return m_render_snapshots; return m_render_snapshots;
}; };
const std::vector<RenderPlayerData>& ClientWorld::render_player_data() const {
return m_render_player_data;
}
std::vector<glm::vec4>& ClientWorld::planes() { return m_planes; } std::vector<glm::vec4>& ClientWorld::planes() { return m_planes; }
} // namespace Cubed } // namespace Cubed

View File

@@ -20,7 +20,7 @@ void NetworkClient::start(std::string ip, int port) {
} }
bool NetworkClient::is_connected() const { return m_connected.load(); } bool NetworkClient::is_connected() const { return m_connected.load(); }
bool NetworkClient::is_connect_error() const { return m_connect_error.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"); Logger::info("Connect Begin");
try { try {
@@ -38,39 +38,35 @@ asio::awaitable<void> NetworkClient::connect(std::string ip, int port) {
} catch (const std::exception& e) { } catch (const std::exception& e) {
Logger::error("Client Error {}", e.what()); Logger::error("Client Error {}", e.what());
m_connect_error = true;
} }
} }
asio::awaitable<void> NetworkClient::read_loop() { asio::awaitable<void> NetworkClient::read_loop() {
try { try {
while (true) { while (true) {
std::array<char, HEADER_LEN> header; std::array<uint8_t, HEADER_LEN> header_buffer;
co_await asio::async_read(m_socket, asio::buffer(header), co_await asio::async_read(m_socket, asio::buffer(header_buffer),
asio::use_awaitable); asio::use_awaitable);
uint32_t total_len_net; auto header = decode_packet_header(header_buffer);
std::memcpy(&total_len_net, header.data(), sizeof(total_len_net)); uint32_t total_len = HEADER_LEN + header.compressed_size;
uint32_t total_len = ntohl(total_len_net);
uint16_t cmd_id_net;
std::memcpy(&cmd_id_net, header.data() + 4, sizeof(cmd_id_net));
uint16_t cmd_id = ntohs(cmd_id_net);
if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) { if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) {
throw std::runtime_error("invalid packet length"); throw std::runtime_error("invalid packet length");
} }
uint32_t body_len = total_len - HEADER_LEN; std::vector<uint8_t> body_data(header.compressed_size);
std::vector<uint8_t> body_data(body_len); if (header.compressed_size > 0) {
if (body_len > 0) {
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);
} }
constexpr auto& to_num = std::to_underlying<PacketEnum>; constexpr auto& to_num = std::to_underlying<PacketEnum>;
switch (cmd_id) { switch (header.cmd) {
case to_num(PacketEnum::LOGIN_RSP): { case to_num(PacketEnum::LOGIN_RSP): {
LoginRsp rsp; LoginRsp rsp;
Logger::info("Client: Receive Login rsp"); Logger::info("Client: Receive Login rsp");
if (rsp.ParseFromArray(body_data.data(), body_data.size())) { if (decode_packet(rsp, body_data, header)) {
if (rsp.success()) { if (rsp.success()) {
m_world.start_client_thread(rsp.uuid()); m_world.start_client_thread(rsp.uuid());
} else { } else {
@@ -82,23 +78,35 @@ asio::awaitable<void> NetworkClient::read_loop() {
ChunkDataRsp rsp; ChunkDataRsp rsp;
Logger::info("Client: Receive Chunk Data rsp, size {}mb", Logger::info("Client: Receive Chunk Data rsp, size {}mb",
body_data.size() / 1024.0f / 1024); body_data.size() / 1024.0f / 1024);
if (rsp.ParseFromArray(body_data.data(), body_data.size())) { if (decode_packet(rsp, body_data, header)) {
m_world.receive_chunk(rsp); m_world.receive_chunk(rsp);
} }
} break; } break;
case to_num(PacketEnum::BLOCK_CHANGE_RSP): { case to_num(PacketEnum::BLOCK_CHANGE_RSP): {
BlockChangeRsp rsp; BlockChangeRsp rsp;
Logger::info("Client: Receive Block Change rsp"); Logger::info("Client: Receive Block Change rsp");
if (rsp.ParseFromArray(body_data.data(), body_data.size())) { if (decode_packet(rsp, body_data, header)) {
m_world.receive_block_change(rsp); m_world.receive_block_change(rsp);
} }
} break; } break;
case to_num(PacketEnum::UPDATE_TIME): { case to_num(PacketEnum::UPDATE_TIME): {
UpdateTime rsp; UpdateTime rsp;
if (rsp.ParseFromArray(body_data.data(), body_data.size())) { if (decode_packet(rsp, body_data, header)) {
m_world.receive_time(rsp); m_world.receive_time(rsp);
} }
} } break;
case to_num(PacketEnum::PLAYER_INFO_RSP): {
PlayerInfoRsp rsp;
if (decode_packet(rsp, body_data, header)) {
m_world.receive_other_player(rsp);
}
} break;
case to_num(PacketEnum::LOGOUT_RSP): {
LogoutRsp rsp;
if (decode_packet(rsp, body_data, header)) {
m_world.receive_player_logout(rsp);
}
} break;
} }
} }
} catch (const asio::system_error& e) { } catch (const asio::system_error& e) {

View File

@@ -9,6 +9,9 @@ NetworkServer::NetworkServer(int port) : m_port(port) {}
NetworkServer::~NetworkServer() { stop(); } NetworkServer::~NetworkServer() { stop(); }
void NetworkServer::stop() { void NetworkServer::stop() {
if (!m_started) {
return;
}
if (m_stopped.exchange(true)) { if (m_stopped.exchange(true)) {
return; return;
} }
@@ -79,9 +82,10 @@ void NetworkServer::net_run() {
Logger::info("Server Started!"); Logger::info("Server Started!");
} }
void NetworkServer::start_server() { void NetworkServer::start_server(int port) {
m_world.init_world(); m_world.init_world();
net_run(); net_run();
m_started = true;
} }
int NetworkServer::port() const { return m_port; } int NetworkServer::port() const { return m_port; }

View File

@@ -3,8 +3,10 @@
#include "Cubed/gameplay/server_world.hpp" #include "Cubed/gameplay/server_world.hpp"
namespace Cubed { namespace Cubed {
ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid, ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid,
ServerWorld& world, std::shared_ptr<Session> session) ServerWorld& world, std::shared_ptr<Session> session,
: m_name(name), m_uuid(uuid), m_world(world), m_session(session) {} TickType gametick)
: m_name(name), m_uuid(uuid), m_world(world), m_session(session),
m_last_gametick(gametick) {}
const glm::vec3& ServerPlayer::get_pos() const { return m_pos; } const glm::vec3& ServerPlayer::get_pos() const { return m_pos; }
const std::string& ServerPlayer::get_name() const { return m_name; } const std::string& ServerPlayer::get_name() const { return m_name; }
const std::string& ServerPlayer::get_uuid() const { return m_uuid; } const std::string& ServerPlayer::get_uuid() const { return m_uuid; }
@@ -18,4 +20,15 @@ void ServerPlayer::update_pos(float x, float y, float z) {
m_last_chunk_pos = chunk_pos; m_last_chunk_pos = chunk_pos;
} }
} }
void ServerPlayer::update_sync_gametick(TickType gametick) {
m_last_gametick = gametick;
}
bool ServerPlayer::is_disconnect(TickType current_gametick) const {
if (current_gametick - m_last_gametick > TIMEOUT) {
return true;
}
return false;
}
} // namespace Cubed } // namespace Cubed

View File

@@ -15,6 +15,9 @@ namespace Cubed {
ServerWorld::ServerWorld() {} ServerWorld::ServerWorld() {}
ServerWorld::~ServerWorld() { ServerWorld::~ServerWorld() {
if (!m_init) {
return;
}
stop_gen_thread(); stop_gen_thread();
stop_server_thread(); stop_server_thread();
wait_all_chunk_tasks(); wait_all_chunk_tasks();
@@ -43,6 +46,22 @@ void ServerWorld::send_time() {
} }
void ServerWorld::init_world() { void ServerWorld::init_world() {
register_timer("player disconnect", 5, [this]() {
std::vector<std::string> disconnect;
{
std::shared_lock lock(m_player_mutex);
for (auto& [uuid, player] : m_players) {
if (player.is_disconnect(m_game_ticks)) {
disconnect.emplace_back(uuid);
}
}
}
for (auto& uuid : disconnect) {
handle_player_exit(uuid);
}
});
m_cave_carcer.init(ChunkGenerator::seed()); m_cave_carcer.init(ChunkGenerator::seed());
m_river_worm.init(ChunkGenerator::seed()); m_river_worm.init(ChunkGenerator::seed());
m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4);
@@ -50,9 +69,6 @@ void ServerWorld::init_world() {
auto t1 = std::chrono::system_clock::now(); auto t1 = std::chrono::system_clock::now();
// init players
// m_players.emplace(HASH::str("TestPlayer"), Player(*this, "TestPlayer"));
start_gen_thread(); start_gen_thread();
init_chunks(); init_chunks();
auto t2 = std::chrono::system_clock::now(); auto t2 = std::chrono::system_clock::now();
@@ -60,6 +76,7 @@ void ServerWorld::init_world() {
Logger::info("Chunk Block Init Finish, Time Consuming: {}", d); Logger::info("Chunk Block Init Finish, Time Consuming: {}", d);
start_server_thread(); start_server_thread();
m_init = true;
} }
void ServerWorld::init_chunks() { hot_reload(); } void ServerWorld::init_chunks() { hot_reload(); }
@@ -396,13 +413,41 @@ void ServerWorld::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,
float z) { float z) {
std::lock_guard lock(m_player_mutex); std::string name;
auto it = m_players.find(uuid); {
if (it == m_players.end()) { std::lock_guard lock(m_player_mutex);
Logger::warn("Player {} is not in this Server", uuid); auto it = m_players.find(uuid);
return; if (it == m_players.end()) {
Logger::warn("Player {} is not in this Server", uuid);
return;
}
it->second.update_pos(x, y, z);
it->second.update_sync_gametick(m_game_ticks);
name = it->second.get_name();
}
// update other player pos;
std::vector<std::shared_ptr<Session>> other;
{
std::shared_lock lock(m_player_mutex);
for (auto& [o_uuid, player] : m_players) {
if (o_uuid == uuid) {
continue;
}
other.emplace_back(player.get_session());
}
}
for (auto& session : other) {
PlayerInfoRsp rsp;
rsp.set_uuid(uuid);
rsp.set_name(name);
auto* pos = rsp.mutable_pos();
pos->set_x(x);
pos->set_y(y);
pos->set_z(z);
session->send(make_packet(rsp));
} }
it->second.update_pos(x, y, z);
} }
void ServerWorld::handle_player_login(const std::string& name, void ServerWorld::handle_player_login(const std::string& name,
@@ -411,9 +456,9 @@ void ServerWorld::handle_player_login(const std::string& name,
Logger::info("Player {} (uuid {}) join the world", name, uuid); Logger::info("Player {} (uuid {}) join the world", name, uuid);
{ {
std::lock_guard lock(m_player_mutex); std::lock_guard lock(m_player_mutex);
m_players.emplace(std::piecewise_construct, m_players.emplace(
std::forward_as_tuple(std::string(uuid)), std::piecewise_construct, std::forward_as_tuple(std::string(uuid)),
std::forward_as_tuple(name, uuid, *this, session)); std::forward_as_tuple(name, uuid, *this, session, m_game_ticks));
} }
m_uuid_to_name.emplace(uuid, name); m_uuid_to_name.emplace(uuid, name);
LoginRsp rsp; LoginRsp rsp;
@@ -427,13 +472,29 @@ void ServerWorld::handle_player_exit(const std::string& uuid) {
std::lock_guard lock(m_player_mutex); std::lock_guard lock(m_player_mutex);
auto it = m_players.find(uuid); auto it = m_players.find(uuid);
if (it != m_players.end()) { if (it != m_players.end()) {
Logger::info("Player {} Exit the Server", it->second.get_name());
m_players.erase(it); m_players.erase(it);
} else { } else {
Logger::error("Player {} isn't in Server", uuid); Logger::error("Player {} isn't in Server", uuid);
return;
} }
} }
m_uuid_to_name.erase(uuid); m_uuid_to_name.erase(uuid);
std::vector<std::shared_ptr<Session>> sessions;
{
std::shared_lock lock(m_player_mutex);
for (auto& [uuid, player] : m_players) {
sessions.emplace_back(player.get_session());
}
}
for (auto& s : sessions) {
LogoutRsp rsp;
rsp.set_uuid(uuid);
s->send(make_packet(rsp));
}
} }
glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const { glm::vec3 ServerWorld::get_player_pos(const std::string& uuid) const {
@@ -488,6 +549,7 @@ void ServerWorld::handle_chunk_req(const std::string& uuid, ChunkPos pos) {
auto it = m_players.find(uuid); auto it = m_players.find(uuid);
if (it != m_players.end()) { if (it != m_players.end()) {
s = it->second.get_session(); s = it->second.get_session();
it->second.update_sync_gametick(m_game_ticks);
} }
} }
if (!s) { if (!s) {

View File

@@ -37,39 +37,35 @@ const std::string& Session::uuid() const { return m_uuid; }
asio::awaitable<void> Session::read_loop() { asio::awaitable<void> Session::read_loop() {
try { try {
while (true) { while (true) {
std::array<char, HEADER_LEN> header; std::array<uint8_t, HEADER_LEN> header_buffer;
co_await asio::async_read(m_socket, asio::buffer(header), co_await asio::async_read(m_socket, asio::buffer(header_buffer),
asio::use_awaitable); asio::use_awaitable);
uint32_t total_len_net; auto header = decode_packet_header(header_buffer);
std::memcpy(&total_len_net, header.data(), sizeof(total_len_net)); uint32_t total_len = HEADER_LEN + header.compressed_size;
uint32_t total_len = ntohl(total_len_net);
uint16_t cmd_id_net;
std::memcpy(&cmd_id_net, header.data() + 4, sizeof(cmd_id_net));
uint16_t cmd_id = ntohs(cmd_id_net);
if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) { if (total_len < HEADER_LEN || total_len > MAX_PACKET_SIZE) {
throw std::runtime_error("invalid packet length"); throw std::runtime_error("invalid packet length");
} }
uint32_t body_len = total_len - HEADER_LEN; std::vector<uint8_t> body_data(header.compressed_size);
std::vector<uint8_t> body_data(body_len); if (header.compressed_size > 0) {
if (body_len > 0) {
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);
} }
constexpr auto& to_num = std::to_underlying<PacketEnum>; constexpr auto& to_num = std::to_underlying<PacketEnum>;
auto cmd_id = header.cmd;
if (cmd_id == to_num(PacketEnum::LOGIN_REQ)) { if (cmd_id == to_num(PacketEnum::LOGIN_REQ)) {
LoginReq req; LoginReq req;
Logger::info("Session: Receive Login req"); Logger::info("Session: Receive Login req");
if (req.ParseFromArray(body_data.data(), body_data.size())) { if (decode_packet(req, body_data, header)) {
m_server_world.handle_player_login(req.name(), m_server_world.handle_player_login(req.name(),
shared_from_this()); shared_from_this());
} }
} }
if (cmd_id == to_num(PacketEnum::PLAYER_POS)) { if (cmd_id == to_num(PacketEnum::PLAYER_POS)) {
PlayerPos pos; PlayerPos pos;
if (pos.ParseFromArray(body_data.data(), body_data.size())) { if (decode_packet(pos, body_data, header)) {
m_server_world.sync_player_pos(pos.uuid(), pos.pos().x(), m_server_world.sync_player_pos(pos.uuid(), pos.pos().x(),
pos.pos().y(), pos.pos().y(),
pos.pos().z()); pos.pos().z());
@@ -77,8 +73,8 @@ asio::awaitable<void> Session::read_loop() {
} }
if (cmd_id == to_num(PacketEnum::CHUNK_DATA_REQ)) { if (cmd_id == to_num(PacketEnum::CHUNK_DATA_REQ)) {
ChunkDataReq req; ChunkDataReq req;
Logger::info("Session: Receive Chunk Data req"); // Logger::info("Session: Receive Chunk Data req");
if (req.ParseFromArray(body_data.data(), body_data.size())) { if (decode_packet(req, body_data, header)) {
m_server_world.handle_chunk_req( m_server_world.handle_chunk_req(
req.uuid(), ChunkPos(req.pos().x(), req.pos().z())); req.uuid(), ChunkPos(req.pos().x(), req.pos().z()));
} }
@@ -86,10 +82,16 @@ asio::awaitable<void> Session::read_loop() {
if (cmd_id == to_num(PacketEnum::BLOCK_CHANGE_REQ)) { if (cmd_id == to_num(PacketEnum::BLOCK_CHANGE_REQ)) {
BlockChangeReq req; BlockChangeReq req;
Logger::info("Session: Receive Block Change req"); Logger::info("Session: Receive Block Change req");
if (req.ParseFromArray(body_data.data(), body_data.size())) { if (decode_packet(req, body_data, header)) {
m_server_world.handle_block_change(req); m_server_world.handle_block_change(req);
} }
} }
if (cmd_id == to_num(PacketEnum::LOGOUT_REQ)) {
LogoutReq req;
if (decode_packet(req, body_data, header)) {
m_server_world.handle_player_exit(req.uuid());
}
}
} }
} catch (const asio::system_error& e) { } catch (const asio::system_error& e) {
auto ec = e.code(); auto ec = e.code();

View File

@@ -9,3 +9,11 @@ message LoginRsp {
string uuid = 2; string uuid = 2;
} }
message LogoutReq {
string uuid = 1;
}
message LogoutRsp {
string uuid = 1;
}

View File

@@ -5,4 +5,10 @@ import "common/vector3.proto";
message PlayerPos { message PlayerPos {
string uuid = 1; string uuid = 1;
Vec3 pos = 2; Vec3 pos = 2;
}
message PlayerInfoRsp {
string uuid = 1;
string name = 2;
Vec3 pos = 3;
} }

View File

@@ -27,25 +27,28 @@ Renderer::Renderer(const Camera& camera, ClientWorld& world,
m_texture_manager(texture_manager), m_world(world) {} m_texture_manager(texture_manager), m_world(world) {}
Renderer::~Renderer() { Renderer::~Renderer() {
glBindBuffer(GL_ARRAY_BUFFER, 0); if (m_init) {
glDeleteBuffers(1, &m_outline_vbo); glBindBuffer(GL_ARRAY_BUFFER, 0);
glDeleteBuffers(1, &m_outline_indices_vbo); glDeleteBuffers(1, &m_outline_vbo);
glDeleteBuffers(1, &m_sky_vbo); glDeleteBuffers(1, &m_outline_indices_vbo);
glDeleteBuffers(1, &m_ui_vbo); glDeleteBuffers(1, &m_sky_vbo);
glDeleteBuffers(1, &m_text_vbo); glDeleteBuffers(1, &m_ui_vbo);
glBindVertexArray(0); glDeleteBuffers(1, &m_text_vbo);
glDeleteVertexArrays(NUM_VAO, m_vao.data()); glDeleteBuffers(1, &m_player_vbo);
glDeleteFramebuffers(1, &m_fbo); glBindVertexArray(0);
glDeleteTextures(1, &m_screen_texture); glDeleteVertexArrays(NUM_VAO, m_vao.data());
glDeleteTextures(1, &m_screen_depth_texture); glDeleteFramebuffers(1, &m_fbo);
glDeleteTextures(1, &m_screen_texture);
glDeleteTextures(1, &m_screen_depth_texture);
glDeleteFramebuffers(1, &m_oit_fbo); glDeleteFramebuffers(1, &m_oit_fbo);
glDeleteTextures(1, &m_accum_texture); glDeleteTextures(1, &m_accum_texture);
glDeleteTextures(1, &m_reveal_texture); glDeleteTextures(1, &m_reveal_texture);
glDeleteTextures(1, &m_oit_depth_texture); glDeleteTextures(1, &m_oit_depth_texture);
glDeleteFramebuffers(1, &m_depth_map_fbo); glDeleteFramebuffers(1, &m_depth_map_fbo);
glDeleteTextures(1, &m_depth_map_texture); glDeleteTextures(1, &m_depth_map_texture);
}
} }
void Renderer::hot_reload() { void Renderer::hot_reload() {
@@ -86,7 +89,9 @@ void Renderer::init() {
"shaders/billboard_f_shader.glsl"}; "shaders/billboard_f_shader.glsl"};
Shader water_shader{"water", "shaders/water_v_shader.glsl", Shader water_shader{"water", "shaders/water_v_shader.glsl",
"shaders/water_f_shader.glsl"}; "shaders/water_f_shader.glsl"};
Shader player_shader{"player", "shaders/player_v_shader.glsl",
"shaders/player_f_shader.glsl"};
m_shaders.insert({player_shader.hash(), std::move(player_shader)});
m_shaders.insert({world_shader.hash(), std::move(world_shader)}); m_shaders.insert({world_shader.hash(), std::move(world_shader)});
m_shaders.insert({outline_shader.hash(), std::move(outline_shader)}); m_shaders.insert({outline_shader.hash(), std::move(outline_shader)});
m_shaders.insert({sky_shdaer.hash(), std::move(sky_shdaer)}); m_shaders.insert({sky_shdaer.hash(), std::move(sky_shdaer)});
@@ -100,6 +105,7 @@ void Renderer::init() {
m_shaders.insert({depth_shader.hash(), std::move(depth_shader)}); m_shaders.insert({depth_shader.hash(), std::move(depth_shader)});
m_shaders.insert({billboard.hash(), std::move(billboard)}); m_shaders.insert({billboard.hash(), std::move(billboard)});
m_shaders.insert({water_shader.hash(), std::move(water_shader)}); m_shaders.insert({water_shader.hash(), std::move(water_shader)});
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL); glDepthFunc(GL_LEQUAL);
@@ -165,12 +171,22 @@ void Renderer::init() {
glEnableVertexAttribArray(1); glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2); glEnableVertexAttribArray(2);
glBindVertexArray(m_vao[5]);
glGenBuffers(1, &m_player_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_player_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(VERTICES_PLAYER), VERTICES_PLAYER,
GL_STATIC_DRAW);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
init_quad(); init_quad();
init_text(); init_text();
hot_reload(); hot_reload();
glBindVertexArray(0); glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0); glBindBuffer(GL_ARRAY_BUFFER, 0);
m_init = true;
} }
const Shader& Renderer::get_shader(const std::string& name) const { const Shader& Renderer::get_shader(const std::string& name) const {
@@ -214,7 +230,7 @@ void Renderer::render() {
render_sky(); render_sky();
render_world(); render_world();
render_outline(); render_outline();
render_player();
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
glEnable(GL_FRAMEBUFFER_SRGB); glEnable(GL_FRAMEBUFFER_SRGB);
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);
@@ -924,6 +940,26 @@ void Renderer::render_world() {
DebugCollector::get().report( DebugCollector::get().report(
"rendered_chunk", "Rendered Chunk: " + std::to_string(rendered_sum)); "rendered_chunk", "Rendered Chunk: " + std::to_string(rendered_sum));
} }
void Renderer::render_player() {
auto& shader = get_shader("player");
shader.use();
m_v_mat = m_camera.get_camera_lookat();
auto& players = m_world.render_player_data();
for (auto& player : players) {
m_m_mat = glm::translate(glm::mat4(1.0f),
player.pos + glm::vec3(-0.5f, 0.0f, -0.5f));
m_mv_mat = m_v_mat * m_m_mat;
shader.set_loc("mv_matrix", m_mv_mat);
shader.set_loc("proj_matrix", m_p_mat);
glBindVertexArray(m_vao[5]);
glEnable(GL_DEPTH_TEST);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
}
#pragma endregion #pragma endregion
void Renderer::render_dev_panel() { void Renderer::render_dev_panel() {
glDisable(GL_DEPTH_TEST); glDisable(GL_DEPTH_TEST);

View File

@@ -36,14 +36,16 @@ TextureManager::TextureManager() {}
TextureManager::~TextureManager() { delet_texture(); } TextureManager::~TextureManager() { delet_texture(); }
void TextureManager::delet_texture() { void TextureManager::delet_texture() {
glDeleteTextures(1, &m_texture_array); if (m_init) {
glDeleteTextures(1, &m_block_status_array); glDeleteTextures(1, &m_texture_array);
glDeleteTextures(1, &m_cross_plane_array); glDeleteTextures(1, &m_block_status_array);
glDeleteTextures(1, &m_normal_texture_array); glDeleteTextures(1, &m_cross_plane_array);
for (auto& id : m_item_textures) { glDeleteTextures(1, &m_normal_texture_array);
glDeleteTextures(1, &id); for (auto& id : m_item_textures) {
glDeleteTextures(1, &id);
}
Logger::info("Successfully delete all texture");
} }
Logger::info("Successfully delete all texture");
} }
GLuint TextureManager::get_block_status_array() const { GLuint TextureManager::get_block_status_array() const {
@@ -313,6 +315,7 @@ void TextureManager::init_texture() {
init_block(); init_block();
init_block_status(); init_block_status();
init_ui(); init_ui();
m_init = true;
} }
void TextureManager::update() { void TextureManager::update() {

View File

@@ -18,10 +18,14 @@ static int windowed_width = 800, windowed_height = 600;
Window::Window(Renderer& renderer) : m_renderer(renderer) {} Window::Window(Renderer& renderer) : m_renderer(renderer) {}
Window::~Window() { Window::~Window() {
if (m_imgui_init) {
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown();
}
ImGui_ImplOpenGL3_Shutdown(); if (ImGui::GetCurrentContext() != nullptr) {
ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext();
ImGui::DestroyContext(); }
if (m_window) { if (m_window) {
glfwDestroyWindow(m_window); glfwDestroyWindow(m_window);
@@ -212,6 +216,8 @@ void Window::imgui_init() {
// Setup Platform/Renderer backends // Setup Platform/Renderer backends
ImGui_ImplGlfw_InitForOpenGL(m_window, false); ImGui_ImplGlfw_InitForOpenGL(m_window, false);
ImGui_ImplOpenGL3_Init(); ImGui_ImplOpenGL3_Init();
m_imgui_init = true;
} }
} // namespace Cubed } // namespace Cubed