mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
Compare commits
9 Commits
fbb6fe2fa6
...
d31ec68f3c
| Author | SHA1 | Date | |
|---|---|---|---|
| d31ec68f3c | |||
| c7c6ee9a61 | |||
| db7b67f265 | |||
| 7e42f595c9 | |||
| 11c902d2bf | |||
| e5e84848a9 | |||
| 25e9b0e0d3 | |||
| 48300b5fe2 | |||
| 3c14a4ffe1 |
@@ -17,10 +17,11 @@ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
|
||||
if(MSVC)
|
||||
add_compile_options(/utf-8)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules")
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(Protobuf REQUIRED)
|
||||
find_package(absl REQUIRED)
|
||||
find_package(zstd REQUIRED)
|
||||
|
||||
if (UNIX AND NOT APPLE)
|
||||
find_package(Freetype REQUIRED)
|
||||
@@ -207,6 +208,7 @@ target_link_libraries(${PROJECT_NAME}
|
||||
protobuf::libprotobuf
|
||||
absl::log
|
||||
absl::check
|
||||
zstd::zstd
|
||||
)
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
|
||||
8
assets/shaders/player_f_shader.glsl
Normal file
8
assets/shaders/player_f_shader.glsl
Normal 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);
|
||||
}
|
||||
15
assets/shaders/player_v_shader.glsl
Normal file
15
assets/shaders/player_v_shader.glsl
Normal 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;
|
||||
}
|
||||
22
cmake/modules/Findzstd.cmake
Normal file
22
cmake/modules/Findzstd.cmake
Normal 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()
|
||||
@@ -12,6 +12,12 @@ namespace Cubed {
|
||||
|
||||
class App {
|
||||
public:
|
||||
struct Argument {
|
||||
bool is_client = false;
|
||||
int port = 25530;
|
||||
std::string ip{"127.0.0.1"};
|
||||
};
|
||||
|
||||
App();
|
||||
~App();
|
||||
static void cursor_position_callback(GLFWwindow* window, double xpos,
|
||||
@@ -60,9 +66,9 @@ private:
|
||||
inline static double fps_time_count = 0.0f;
|
||||
inline static int frame_count = 0;
|
||||
inline static int fps = 0;
|
||||
|
||||
void init();
|
||||
|
||||
Argument m_argument;
|
||||
void init(int argc, char** argv);
|
||||
void handle_argument(int argc, char** argv);
|
||||
auto init_camera();
|
||||
auto init_texture();
|
||||
auto init_world();
|
||||
|
||||
@@ -5,11 +5,22 @@
|
||||
#include "Cubed/gameplay/client_player.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/network_client.hpp"
|
||||
#include "Cubed/tools/thread_pool.hpp"
|
||||
|
||||
#include <deque>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
namespace Cubed {
|
||||
|
||||
struct OtherPlayerInfo {
|
||||
std::string name;
|
||||
glm::vec3 pos;
|
||||
};
|
||||
|
||||
struct RenderPlayerData {
|
||||
std::string name;
|
||||
glm::vec3 pos;
|
||||
};
|
||||
|
||||
class ClientWorld {
|
||||
public:
|
||||
ClientWorld();
|
||||
@@ -34,17 +45,24 @@ public:
|
||||
void receive_block_change(const BlockChangeRsp& 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;
|
||||
void rendering_distance(int rendering_distance);
|
||||
void start_client_thread(std::string_view uuid);
|
||||
void stop_client_thread();
|
||||
|
||||
void start_thread_pool();
|
||||
void stop_thread_pool();
|
||||
void change_pool_threads(int threads);
|
||||
void hot_reload();
|
||||
void request_chunk();
|
||||
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;
|
||||
void receive_chunk(const ChunkDataRsp& data);
|
||||
void exit();
|
||||
template <typename Fn>
|
||||
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
||||
m_timers.emplace(std::piecewise_construct,
|
||||
@@ -58,7 +76,9 @@ private:
|
||||
tbb::concurrent_unordered_map<ChunkPos, ClientChunk, ChunkPos::Hash>;
|
||||
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
|
||||
using ChunkPosVector = std::vector<ChunkPos>;
|
||||
using OtherPlayerHashMap = std::unordered_map<std::string, OtherPlayerInfo>;
|
||||
ClientPlayer m_player;
|
||||
OtherPlayerHashMap m_other_players;
|
||||
ChunkHashMap m_chunks;
|
||||
std::vector<glm::vec4> m_planes;
|
||||
std::jthread m_client_thread;
|
||||
@@ -67,13 +87,16 @@ private:
|
||||
std::mutex m_delete_vbo_mutex;
|
||||
std::mutex m_delete_vao_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<ChunkDataRsp> m_pending_chunk_data_queue;
|
||||
|
||||
std::vector<GLuint> m_pending_delete_vbo;
|
||||
std::vector<GLuint> m_pending_delete_vao;
|
||||
|
||||
std::deque<ChunkPos> m_dirty_queue;
|
||||
std::vector<ChunkRenderSnapshot> m_render_snapshots;
|
||||
std::vector<RenderPlayerData> m_render_player_data;
|
||||
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
|
||||
std::atomic<bool> m_game_running{false};
|
||||
std::atomic<int> m_rendering_distance{24};
|
||||
@@ -82,6 +105,9 @@ private:
|
||||
std::atomic<bool> m_requesting_chunk{false};
|
||||
std::shared_ptr<NetworkClient> m_client;
|
||||
ChunkLoadStyle m_chunk_load_style{ChunkLoadStyle::CENTER};
|
||||
|
||||
std::atomic<std::shared_ptr<ThreadPool>> m_thread_pool;
|
||||
|
||||
void client_run(std::stop_token token);
|
||||
|
||||
void set_player_pos();
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
void send(Packet packet);
|
||||
void start(std::string ip, int port = 25530);
|
||||
bool is_connected() const;
|
||||
bool is_connect_error() const;
|
||||
|
||||
private:
|
||||
asio::io_context m_io;
|
||||
@@ -32,6 +33,7 @@ private:
|
||||
|
||||
std::atomic<bool> m_closed{false};
|
||||
std::atomic<bool> m_connected{false};
|
||||
std::atomic<bool> m_connect_error{false};
|
||||
// ClientWorld is managed by App
|
||||
ClientWorld& m_world;
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ public:
|
||||
void stop();
|
||||
|
||||
// Run in another thread after initialization is complete
|
||||
void start_server();
|
||||
void start_server(int port = 25530);
|
||||
|
||||
int port() const;
|
||||
ServerWorld& server_world();
|
||||
@@ -23,6 +23,7 @@ private:
|
||||
std::thread m_net_thread;
|
||||
int m_port = 25530;
|
||||
std::atomic<bool> m_stopped{false};
|
||||
std::atomic<bool> m_started{false};
|
||||
ServerWorld m_world;
|
||||
std::mutex m_session_mutex;
|
||||
std::unordered_map<std::string, std::shared_ptr<Session>> m_session;
|
||||
|
||||
@@ -1,18 +1,53 @@
|
||||
#pragma once
|
||||
#include "Cubed/tools/compression.hpp"
|
||||
#include "packet.pb.h" // IWYU pragma: keep
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <netinet/in.h>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
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>>;
|
||||
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_RSP = 1002,
|
||||
LOGOUT_REQ = 1003,
|
||||
LOGOUT_RSP = 1004,
|
||||
PLAYER_INFO = 2001,
|
||||
PLAYER_POS = 2002,
|
||||
PLAYER_INFO_RSP = 2003,
|
||||
CHUNK_DATA_REQ = 3001,
|
||||
CHUNK_DATA_RSP = 3002,
|
||||
BLOCK_CHANGE_REQ = 3003,
|
||||
@@ -34,10 +69,16 @@ template <typename T> constexpr uint16_t get_packet_id() {
|
||||
return to_num(LOGIN_REQ);
|
||||
} else if constexpr (is_same_v<U, LoginRsp>) {
|
||||
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>) {
|
||||
return to_num(PLAYER_INFO);
|
||||
} else if constexpr (is_same_v<U, PlayerPos>) {
|
||||
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>) {
|
||||
return to_num(CHUNK_DATA_REQ);
|
||||
} 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);
|
||||
} else if constexpr (is_same_v<U, Ping>) {
|
||||
return to_num(PING);
|
||||
} else if (is_same_v<U, Pong>) {
|
||||
} else if constexpr (is_same_v<U, Pong>) {
|
||||
return to_num(PONG);
|
||||
} 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) {
|
||||
uint16_t cmd = get_packet_id<T>();
|
||||
template <typename 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());
|
||||
|
||||
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))) {
|
||||
if (!msg.SerializeToArray(raw.data(), raw_size)) {
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -11,19 +13,24 @@ class Session;
|
||||
class ServerPlayer {
|
||||
public:
|
||||
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 std::string& get_name() const;
|
||||
const std::string& get_uuid() const;
|
||||
std::shared_ptr<Session> get_session() const;
|
||||
void update_pos(float x, float y, float z);
|
||||
void update_sync_gametick(TickType gametick);
|
||||
bool is_disconnect(TickType current_gametick) const;
|
||||
|
||||
private:
|
||||
static constexpr TickType TIMEOUT = 200;
|
||||
std::string m_name;
|
||||
std::string m_uuid;
|
||||
glm::vec3 m_pos{0.0f};
|
||||
ServerWorld& m_world;
|
||||
ChunkPos m_last_chunk_pos{0, 0};
|
||||
std::shared_ptr<Session> m_session;
|
||||
std::atomic<TickType> m_last_gametick{0};
|
||||
};
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
ServerWorld();
|
||||
~ServerWorld();
|
||||
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 need_gen(std::optional<std::string> uuid);
|
||||
void update();
|
||||
@@ -112,6 +112,7 @@ private:
|
||||
std::atomic<bool> m_gen_running{false};
|
||||
std::atomic<bool> m_need_gen_chunk{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_pool_threads{0};
|
||||
std::atomic<int> m_max_threads{1};
|
||||
|
||||
@@ -272,6 +272,55 @@ constexpr float CROSS_TANGENTS[2][6][3] = {
|
||||
{-0.7071f, 0.0f, 0.7071f}}};
|
||||
|
||||
#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]
|
||||
constexpr float QUAD_VERTICES[] = {
|
||||
// postion // texcoorlds
|
||||
|
||||
@@ -99,6 +99,9 @@ private:
|
||||
bool m_water_depth_fade = true;
|
||||
bool m_pbr = true;
|
||||
bool m_flip_y = false;
|
||||
|
||||
bool m_init = false;
|
||||
|
||||
int m_shadow_mode = 0;
|
||||
int m_light_cull_face = 0;
|
||||
float m_aspect = 0.0f;
|
||||
@@ -119,7 +122,7 @@ private:
|
||||
GLuint m_outline_indices_vbo = 0;
|
||||
GLuint m_outline_vbo = 0;
|
||||
GLuint m_ui_vbo = 0;
|
||||
|
||||
GLuint m_player_vbo = 0;
|
||||
GLuint m_fbo = 0;
|
||||
GLuint m_screen_texture = 0;
|
||||
GLuint m_screen_depth_texture = 0;
|
||||
@@ -171,7 +174,7 @@ private:
|
||||
2 - outline vao
|
||||
3 - ui vao
|
||||
4 - text vao
|
||||
|
||||
5 - player vao
|
||||
*/
|
||||
std::vector<GLuint> m_vao;
|
||||
std::vector<Vertex2D> m_ui;
|
||||
@@ -186,6 +189,7 @@ private:
|
||||
void render_text();
|
||||
void render_ui();
|
||||
void render_world();
|
||||
void render_player();
|
||||
void render_underwater();
|
||||
void render_dev_panel();
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Cubed {
|
||||
class TextureManager {
|
||||
private:
|
||||
bool m_need_reload = false;
|
||||
bool m_init = false;
|
||||
GLuint m_block_status_array = 0;
|
||||
GLuint m_texture_array = 0;
|
||||
GLuint m_cross_plane_array = 0;
|
||||
|
||||
35
include/Cubed/tools/arg_parser.hpp
Normal file
35
include/Cubed/tools/arg_parser.hpp
Normal 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
|
||||
40
include/Cubed/tools/compression.hpp
Normal file
40
include/Cubed/tools/compression.hpp
Normal 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
|
||||
@@ -23,6 +23,7 @@ public:
|
||||
|
||||
private:
|
||||
bool m_mouse_enable = false;
|
||||
bool m_imgui_init = false;
|
||||
float m_aspect;
|
||||
GLFWwindow* m_window;
|
||||
int m_width;
|
||||
|
||||
64
src/app.cpp
64
src/app.cpp
@@ -2,13 +2,13 @@
|
||||
|
||||
#include "Cubed/config.hpp"
|
||||
#include "Cubed/debug_collector.hpp"
|
||||
#include "Cubed/tools/arg_parser.hpp"
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
#include "Cubed/tools/log.hpp"
|
||||
#include "Cubed/tools/system_info.hpp"
|
||||
|
||||
#include <exception>
|
||||
#include <imgui_impl_glfw.h>
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
App::App() {}
|
||||
@@ -33,9 +33,11 @@ void App::cursor_position_callback(GLFWwindow* window, double xpos,
|
||||
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.imgui_init();
|
||||
|
||||
Logger::info("Window Init Success");
|
||||
|
||||
glfwSetWindowUserPointer(m_window.get_glfw_window(), this);
|
||||
@@ -53,6 +55,7 @@ void App::init() {
|
||||
glfwSetCursorEnterCallback(m_window.get_glfw_window(),
|
||||
cursor_enter_callback);
|
||||
glfwSetCharCallback(m_window.get_glfw_window(), char_callback);
|
||||
|
||||
ChunkGenerator::init();
|
||||
BlockManager::init();
|
||||
m_renderer.init();
|
||||
@@ -61,12 +64,13 @@ void App::init() {
|
||||
// MapTable::init_map();
|
||||
m_texture_manager.init_texture();
|
||||
Logger::info("Texture Load Success");
|
||||
|
||||
m_server.start_server();
|
||||
if (!m_argument.is_client) {
|
||||
m_server.start_server(m_argument.port);
|
||||
}
|
||||
|
||||
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
|
||||
m_client_world.init("test", m_client);
|
||||
|
||||
@@ -76,6 +80,53 @@ void App::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,
|
||||
int mods) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
@@ -233,6 +284,7 @@ void App::run() {
|
||||
update();
|
||||
render();
|
||||
}
|
||||
m_client_world.exit();
|
||||
}
|
||||
static Gait player_gait = Gait::WALK;
|
||||
void App::update() {
|
||||
@@ -276,7 +328,7 @@ int App::start_cubed_application(int argc, char** argv) {
|
||||
App app;
|
||||
|
||||
try {
|
||||
app.init();
|
||||
app.init(argc, argv);
|
||||
Logger::info("Game Init Finish Start Run...");
|
||||
app.run();
|
||||
|
||||
|
||||
@@ -112,6 +112,8 @@ void DevPanel::show_about_table_bar() {
|
||||
ImGui::Text("Dear ImGui");
|
||||
ImGui::Text("Tbb");
|
||||
ImGui::Text("Asio");
|
||||
ImGui::Text("protobuf");
|
||||
ImGui::Text("zstd");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Special Thanks");
|
||||
ImGui::Text("TANGERIME");
|
||||
|
||||
@@ -18,7 +18,7 @@ ClientWorld::ClientWorld() : m_player(*this) {}
|
||||
|
||||
ClientWorld::~ClientWorld() {
|
||||
stop_client_thread();
|
||||
|
||||
stop_thread_pool();
|
||||
{
|
||||
std::lock_guard lock(m_chunks_mutex);
|
||||
m_chunks.clear();
|
||||
@@ -190,18 +190,44 @@ void ClientWorld::receive_time(const UpdateTime& rsp) {
|
||||
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,
|
||||
std::shared_ptr<NetworkClient> client) {
|
||||
m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4);
|
||||
m_player.init(player_name);
|
||||
m_client = client;
|
||||
// timer
|
||||
register_timer("player_pos", 2, [this]() { report_player_pos(); });
|
||||
register_timer("player_pos", 1, [this]() { report_player_pos(); });
|
||||
LoginReq req;
|
||||
req.set_name(m_player.get_name());
|
||||
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));
|
||||
}
|
||||
start_thread_pool();
|
||||
// request login
|
||||
Logger::info("Send Login Request");
|
||||
m_client->send(make_packet(req));
|
||||
@@ -228,6 +254,26 @@ void ClientWorld::stop_client_thread() {
|
||||
}
|
||||
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() {
|
||||
auto& config = Config::get();
|
||||
@@ -238,28 +284,16 @@ void ClientWorld::hot_reload() {
|
||||
void ClientWorld::client_run(std::stop_token stoken) {
|
||||
Logger::info("Client Thread Started");
|
||||
while (!stoken.stop_requested()) {
|
||||
auto t1 = system_clock::now();
|
||||
|
||||
for (auto& x : m_timers) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_pending_chunk_data_queue_mutex);
|
||||
m_pending_chunk_data_queue.emplace_back(std::move(data));
|
||||
// vertex data will genrator in client thread pool instead of net thread;
|
||||
auto pool = m_thread_pool.load();
|
||||
if (!pool) {
|
||||
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) {
|
||||
@@ -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 {
|
||||
@@ -473,8 +529,11 @@ int ClientWorld::rendering_distance() const {
|
||||
void ClientWorld::rendering_distance(int 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;
|
||||
};
|
||||
const std::vector<RenderPlayerData>& ClientWorld::render_player_data() const {
|
||||
return m_render_player_data;
|
||||
}
|
||||
std::vector<glm::vec4>& ClientWorld::planes() { return m_planes; }
|
||||
} // namespace Cubed
|
||||
@@ -20,7 +20,7 @@ void NetworkClient::start(std::string ip, int port) {
|
||||
}
|
||||
|
||||
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) {
|
||||
Logger::info("Connect Begin");
|
||||
try {
|
||||
@@ -38,39 +38,35 @@ asio::awaitable<void> NetworkClient::connect(std::string ip, int port) {
|
||||
|
||||
} catch (const std::exception& e) {
|
||||
Logger::error("Client Error {}", e.what());
|
||||
m_connect_error = true;
|
||||
}
|
||||
}
|
||||
|
||||
asio::awaitable<void> NetworkClient::read_loop() {
|
||||
try {
|
||||
while (true) {
|
||||
std::array<char, HEADER_LEN> header;
|
||||
co_await asio::async_read(m_socket, asio::buffer(header),
|
||||
std::array<uint8_t, HEADER_LEN> header_buffer;
|
||||
co_await asio::async_read(m_socket, asio::buffer(header_buffer),
|
||||
asio::use_awaitable);
|
||||
uint32_t total_len_net;
|
||||
std::memcpy(&total_len_net, header.data(), sizeof(total_len_net));
|
||||
uint32_t total_len = ntohl(total_len_net);
|
||||
auto header = decode_packet_header(header_buffer);
|
||||
uint32_t total_len = HEADER_LEN + header.compressed_size;
|
||||
|
||||
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) {
|
||||
|
||||
throw std::runtime_error("invalid packet length");
|
||||
}
|
||||
uint32_t body_len = total_len - HEADER_LEN;
|
||||
std::vector<uint8_t> body_data(body_len);
|
||||
if (body_len > 0) {
|
||||
std::vector<uint8_t> body_data(header.compressed_size);
|
||||
if (header.compressed_size > 0) {
|
||||
co_await asio::async_read(m_socket, asio::buffer(body_data),
|
||||
asio::use_awaitable);
|
||||
}
|
||||
|
||||
constexpr auto& to_num = std::to_underlying<PacketEnum>;
|
||||
switch (cmd_id) {
|
||||
switch (header.cmd) {
|
||||
case to_num(PacketEnum::LOGIN_RSP): {
|
||||
LoginRsp 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()) {
|
||||
m_world.start_client_thread(rsp.uuid());
|
||||
} else {
|
||||
@@ -82,23 +78,35 @@ asio::awaitable<void> NetworkClient::read_loop() {
|
||||
ChunkDataRsp rsp;
|
||||
Logger::info("Client: Receive Chunk Data rsp, size {}mb",
|
||||
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);
|
||||
}
|
||||
} break;
|
||||
case to_num(PacketEnum::BLOCK_CHANGE_RSP): {
|
||||
BlockChangeRsp 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);
|
||||
}
|
||||
} break;
|
||||
case to_num(PacketEnum::UPDATE_TIME): {
|
||||
UpdateTime rsp;
|
||||
if (rsp.ParseFromArray(body_data.data(), body_data.size())) {
|
||||
if (decode_packet(rsp, body_data, header)) {
|
||||
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) {
|
||||
|
||||
@@ -9,6 +9,9 @@ NetworkServer::NetworkServer(int port) : m_port(port) {}
|
||||
NetworkServer::~NetworkServer() { stop(); }
|
||||
|
||||
void NetworkServer::stop() {
|
||||
if (!m_started) {
|
||||
return;
|
||||
}
|
||||
if (m_stopped.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
@@ -79,9 +82,10 @@ void NetworkServer::net_run() {
|
||||
Logger::info("Server Started!");
|
||||
}
|
||||
|
||||
void NetworkServer::start_server() {
|
||||
void NetworkServer::start_server(int port) {
|
||||
m_world.init_world();
|
||||
net_run();
|
||||
m_started = true;
|
||||
}
|
||||
|
||||
int NetworkServer::port() const { return m_port; }
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
namespace Cubed {
|
||||
ServerPlayer::ServerPlayer(std::string_view name, std::string_view uuid,
|
||||
ServerWorld& world, std::shared_ptr<Session> session)
|
||||
: m_name(name), m_uuid(uuid), m_world(world), m_session(session) {}
|
||||
ServerWorld& world, std::shared_ptr<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 std::string& ServerPlayer::get_name() const { return m_name; }
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -15,6 +15,9 @@ namespace Cubed {
|
||||
ServerWorld::ServerWorld() {}
|
||||
|
||||
ServerWorld::~ServerWorld() {
|
||||
if (!m_init) {
|
||||
return;
|
||||
}
|
||||
stop_gen_thread();
|
||||
stop_server_thread();
|
||||
wait_all_chunk_tasks();
|
||||
@@ -43,6 +46,22 @@ void ServerWorld::send_time() {
|
||||
}
|
||||
|
||||
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_river_worm.init(ChunkGenerator::seed());
|
||||
m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4);
|
||||
@@ -50,9 +69,6 @@ void ServerWorld::init_world() {
|
||||
|
||||
auto t1 = std::chrono::system_clock::now();
|
||||
|
||||
// init players
|
||||
// m_players.emplace(HASH::str("TestPlayer"), Player(*this, "TestPlayer"));
|
||||
|
||||
start_gen_thread();
|
||||
init_chunks();
|
||||
auto t2 = std::chrono::system_clock::now();
|
||||
@@ -60,6 +76,7 @@ void ServerWorld::init_world() {
|
||||
Logger::info("Chunk Block Init Finish, Time Consuming: {}", d);
|
||||
|
||||
start_server_thread();
|
||||
m_init = true;
|
||||
}
|
||||
|
||||
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,
|
||||
float z) {
|
||||
std::lock_guard lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
if (it == m_players.end()) {
|
||||
Logger::warn("Player {} is not in this Server", uuid);
|
||||
return;
|
||||
std::string name;
|
||||
{
|
||||
std::lock_guard lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
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,
|
||||
@@ -411,9 +456,9 @@ void ServerWorld::handle_player_login(const std::string& name,
|
||||
Logger::info("Player {} (uuid {}) join the world", name, uuid);
|
||||
{
|
||||
std::lock_guard lock(m_player_mutex);
|
||||
m_players.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(std::string(uuid)),
|
||||
std::forward_as_tuple(name, uuid, *this, session));
|
||||
m_players.emplace(
|
||||
std::piecewise_construct, std::forward_as_tuple(std::string(uuid)),
|
||||
std::forward_as_tuple(name, uuid, *this, session, m_game_ticks));
|
||||
}
|
||||
m_uuid_to_name.emplace(uuid, name);
|
||||
LoginRsp rsp;
|
||||
@@ -427,13 +472,29 @@ void ServerWorld::handle_player_exit(const std::string& uuid) {
|
||||
std::lock_guard lock(m_player_mutex);
|
||||
auto it = m_players.find(uuid);
|
||||
if (it != m_players.end()) {
|
||||
|
||||
Logger::info("Player {} Exit the Server", it->second.get_name());
|
||||
m_players.erase(it);
|
||||
} else {
|
||||
Logger::error("Player {} isn't in Server", uuid);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -488,6 +549,7 @@ void ServerWorld::handle_chunk_req(const std::string& uuid, ChunkPos pos) {
|
||||
auto it = m_players.find(uuid);
|
||||
if (it != m_players.end()) {
|
||||
s = it->second.get_session();
|
||||
it->second.update_sync_gametick(m_game_ticks);
|
||||
}
|
||||
}
|
||||
if (!s) {
|
||||
|
||||
@@ -37,39 +37,35 @@ const std::string& Session::uuid() const { return m_uuid; }
|
||||
asio::awaitable<void> Session::read_loop() {
|
||||
try {
|
||||
while (true) {
|
||||
std::array<char, HEADER_LEN> header;
|
||||
co_await asio::async_read(m_socket, asio::buffer(header),
|
||||
std::array<uint8_t, HEADER_LEN> header_buffer;
|
||||
co_await asio::async_read(m_socket, asio::buffer(header_buffer),
|
||||
asio::use_awaitable);
|
||||
|
||||
uint32_t total_len_net;
|
||||
std::memcpy(&total_len_net, header.data(), sizeof(total_len_net));
|
||||
uint32_t total_len = ntohl(total_len_net);
|
||||
auto header = decode_packet_header(header_buffer);
|
||||
uint32_t total_len = HEADER_LEN + header.compressed_size;
|
||||
|
||||
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) {
|
||||
|
||||
throw std::runtime_error("invalid packet length");
|
||||
}
|
||||
uint32_t body_len = total_len - HEADER_LEN;
|
||||
std::vector<uint8_t> body_data(body_len);
|
||||
if (body_len > 0) {
|
||||
std::vector<uint8_t> body_data(header.compressed_size);
|
||||
if (header.compressed_size > 0) {
|
||||
co_await asio::async_read(m_socket, asio::buffer(body_data),
|
||||
asio::use_awaitable);
|
||||
}
|
||||
constexpr auto& to_num = std::to_underlying<PacketEnum>;
|
||||
auto cmd_id = header.cmd;
|
||||
if (cmd_id == to_num(PacketEnum::LOGIN_REQ)) {
|
||||
LoginReq 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(),
|
||||
shared_from_this());
|
||||
}
|
||||
}
|
||||
if (cmd_id == to_num(PacketEnum::PLAYER_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(),
|
||||
pos.pos().y(),
|
||||
pos.pos().z());
|
||||
@@ -77,8 +73,8 @@ asio::awaitable<void> Session::read_loop() {
|
||||
}
|
||||
if (cmd_id == to_num(PacketEnum::CHUNK_DATA_REQ)) {
|
||||
ChunkDataReq req;
|
||||
Logger::info("Session: Receive Chunk Data req");
|
||||
if (req.ParseFromArray(body_data.data(), body_data.size())) {
|
||||
// Logger::info("Session: Receive Chunk Data req");
|
||||
if (decode_packet(req, body_data, header)) {
|
||||
m_server_world.handle_chunk_req(
|
||||
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)) {
|
||||
BlockChangeReq 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);
|
||||
}
|
||||
}
|
||||
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) {
|
||||
auto ec = e.code();
|
||||
|
||||
@@ -9,3 +9,11 @@ message LoginRsp {
|
||||
string uuid = 2;
|
||||
}
|
||||
|
||||
message LogoutReq {
|
||||
string uuid = 1;
|
||||
}
|
||||
|
||||
message LogoutRsp {
|
||||
string uuid = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,4 +5,10 @@ import "common/vector3.proto";
|
||||
message PlayerPos {
|
||||
string uuid = 1;
|
||||
Vec3 pos = 2;
|
||||
}
|
||||
|
||||
message PlayerInfoRsp {
|
||||
string uuid = 1;
|
||||
string name = 2;
|
||||
Vec3 pos = 3;
|
||||
}
|
||||
@@ -27,25 +27,28 @@ Renderer::Renderer(const Camera& camera, ClientWorld& world,
|
||||
m_texture_manager(texture_manager), m_world(world) {}
|
||||
|
||||
Renderer::~Renderer() {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glDeleteBuffers(1, &m_outline_vbo);
|
||||
glDeleteBuffers(1, &m_outline_indices_vbo);
|
||||
glDeleteBuffers(1, &m_sky_vbo);
|
||||
glDeleteBuffers(1, &m_ui_vbo);
|
||||
glDeleteBuffers(1, &m_text_vbo);
|
||||
glBindVertexArray(0);
|
||||
glDeleteVertexArrays(NUM_VAO, m_vao.data());
|
||||
glDeleteFramebuffers(1, &m_fbo);
|
||||
glDeleteTextures(1, &m_screen_texture);
|
||||
glDeleteTextures(1, &m_screen_depth_texture);
|
||||
if (m_init) {
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
glDeleteBuffers(1, &m_outline_vbo);
|
||||
glDeleteBuffers(1, &m_outline_indices_vbo);
|
||||
glDeleteBuffers(1, &m_sky_vbo);
|
||||
glDeleteBuffers(1, &m_ui_vbo);
|
||||
glDeleteBuffers(1, &m_text_vbo);
|
||||
glDeleteBuffers(1, &m_player_vbo);
|
||||
glBindVertexArray(0);
|
||||
glDeleteVertexArrays(NUM_VAO, m_vao.data());
|
||||
glDeleteFramebuffers(1, &m_fbo);
|
||||
glDeleteTextures(1, &m_screen_texture);
|
||||
glDeleteTextures(1, &m_screen_depth_texture);
|
||||
|
||||
glDeleteFramebuffers(1, &m_oit_fbo);
|
||||
glDeleteTextures(1, &m_accum_texture);
|
||||
glDeleteTextures(1, &m_reveal_texture);
|
||||
glDeleteTextures(1, &m_oit_depth_texture);
|
||||
glDeleteFramebuffers(1, &m_oit_fbo);
|
||||
glDeleteTextures(1, &m_accum_texture);
|
||||
glDeleteTextures(1, &m_reveal_texture);
|
||||
glDeleteTextures(1, &m_oit_depth_texture);
|
||||
|
||||
glDeleteFramebuffers(1, &m_depth_map_fbo);
|
||||
glDeleteTextures(1, &m_depth_map_texture);
|
||||
glDeleteFramebuffers(1, &m_depth_map_fbo);
|
||||
glDeleteTextures(1, &m_depth_map_texture);
|
||||
}
|
||||
}
|
||||
|
||||
void Renderer::hot_reload() {
|
||||
@@ -86,7 +89,9 @@ void Renderer::init() {
|
||||
"shaders/billboard_f_shader.glsl"};
|
||||
Shader water_shader{"water", "shaders/water_v_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({outline_shader.hash(), std::move(outline_shader)});
|
||||
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({billboard.hash(), std::move(billboard)});
|
||||
m_shaders.insert({water_shader.hash(), std::move(water_shader)});
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthFunc(GL_LEQUAL);
|
||||
|
||||
@@ -165,12 +171,22 @@ void Renderer::init() {
|
||||
glEnableVertexAttribArray(1);
|
||||
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_text();
|
||||
hot_reload();
|
||||
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
m_init = true;
|
||||
}
|
||||
|
||||
const Shader& Renderer::get_shader(const std::string& name) const {
|
||||
@@ -214,7 +230,7 @@ void Renderer::render() {
|
||||
render_sky();
|
||||
render_world();
|
||||
render_outline();
|
||||
|
||||
render_player();
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glEnable(GL_FRAMEBUFFER_SRGB);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
@@ -924,6 +940,26 @@ void Renderer::render_world() {
|
||||
DebugCollector::get().report(
|
||||
"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
|
||||
void Renderer::render_dev_panel() {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
|
||||
@@ -36,14 +36,16 @@ TextureManager::TextureManager() {}
|
||||
TextureManager::~TextureManager() { delet_texture(); }
|
||||
|
||||
void TextureManager::delet_texture() {
|
||||
glDeleteTextures(1, &m_texture_array);
|
||||
glDeleteTextures(1, &m_block_status_array);
|
||||
glDeleteTextures(1, &m_cross_plane_array);
|
||||
glDeleteTextures(1, &m_normal_texture_array);
|
||||
for (auto& id : m_item_textures) {
|
||||
glDeleteTextures(1, &id);
|
||||
if (m_init) {
|
||||
glDeleteTextures(1, &m_texture_array);
|
||||
glDeleteTextures(1, &m_block_status_array);
|
||||
glDeleteTextures(1, &m_cross_plane_array);
|
||||
glDeleteTextures(1, &m_normal_texture_array);
|
||||
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 {
|
||||
@@ -313,6 +315,7 @@ void TextureManager::init_texture() {
|
||||
init_block();
|
||||
init_block_status();
|
||||
init_ui();
|
||||
m_init = true;
|
||||
}
|
||||
|
||||
void TextureManager::update() {
|
||||
|
||||
@@ -18,10 +18,14 @@ static int windowed_width = 800, windowed_height = 600;
|
||||
Window::Window(Renderer& renderer) : m_renderer(renderer) {}
|
||||
|
||||
Window::~Window() {
|
||||
if (m_imgui_init) {
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
}
|
||||
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplGlfw_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
if (ImGui::GetCurrentContext() != nullptr) {
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
|
||||
if (m_window) {
|
||||
glfwDestroyWindow(m_window);
|
||||
@@ -212,6 +216,8 @@ void Window::imgui_init() {
|
||||
// Setup Platform/Renderer backends
|
||||
ImGui_ImplGlfw_InitForOpenGL(m_window, false);
|
||||
ImGui_ImplOpenGL3_Init();
|
||||
|
||||
m_imgui_init = true;
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
Reference in New Issue
Block a user