mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
feat(client): add client-side chunk, player, world and network classes
This commit is contained in:
@@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Cubed/constants.hpp"
|
||||||
|
#include "Cubed/gameplay/biome.hpp"
|
||||||
|
#include "Cubed/gameplay/block.hpp"
|
||||||
|
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||||
|
#include "Cubed/gameplay/vertex_data.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <mutex>
|
||||||
|
namespace Cubed {
|
||||||
|
class ClientWorld;
|
||||||
|
struct ChunkRenderSnapshot {
|
||||||
|
GLuint normal_vao;
|
||||||
|
size_t normal_vertices_count;
|
||||||
|
GLuint cross_vao;
|
||||||
|
size_t cross_vertices_count;
|
||||||
|
GLuint normal_discard_vao;
|
||||||
|
size_t normal_discard_vertices_count;
|
||||||
|
GLuint normal_blend_vao;
|
||||||
|
size_t normal_blend_vertices_count;
|
||||||
|
GLuint water_vao;
|
||||||
|
size_t water_vertices_count;
|
||||||
|
glm::vec3 center;
|
||||||
|
glm::vec3 half_extents;
|
||||||
|
};
|
||||||
|
class ClientChunk {
|
||||||
|
public:
|
||||||
|
ClientChunk(ClientWorld& world);
|
||||||
|
~ClientChunk();
|
||||||
|
ClientChunk(const ClientChunk&) = delete;
|
||||||
|
ClientChunk& operator=(const ClientChunk&) = delete;
|
||||||
|
ClientChunk(ClientChunk&&) noexcept;
|
||||||
|
ClientChunk& operator=(ClientChunk&&) noexcept;
|
||||||
|
|
||||||
|
BiomeType get_biome() const;
|
||||||
|
ChunkPos get_chunk_pos() const;
|
||||||
|
const std::vector<BlockType>& get_chunk_blocks() const;
|
||||||
|
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
|
||||||
|
void upload_to_gpu();
|
||||||
|
|
||||||
|
GLuint get_normal_vao() const;
|
||||||
|
size_t get_normal_vertices_sum() const;
|
||||||
|
|
||||||
|
GLuint get_cross_vao() const;
|
||||||
|
size_t get_cross_vertices_sum() const;
|
||||||
|
|
||||||
|
GLuint get_normal_discard_vao() const;
|
||||||
|
size_t get_normal_discard_vertices_sum() const;
|
||||||
|
|
||||||
|
GLuint get_normal_blend_vao() const;
|
||||||
|
size_t get_normal_blend_vertices_sum() const;
|
||||||
|
|
||||||
|
GLuint get_water_vao() const;
|
||||||
|
size_t get_water_vertices_sum() const;
|
||||||
|
|
||||||
|
bool is_dirty() const;
|
||||||
|
void mark_dirty();
|
||||||
|
|
||||||
|
bool is_need_upload() const;
|
||||||
|
void need_upload();
|
||||||
|
|
||||||
|
void set_chunk_block(int index, unsigned id);
|
||||||
|
bool is_temp_chunk() const;
|
||||||
|
ChunkPos chunk_pos() const;
|
||||||
|
BiomeType biome() const;
|
||||||
|
void biome(BiomeType b);
|
||||||
|
std::vector<BlockType>& blocks();
|
||||||
|
ClientWorld& world();
|
||||||
|
unsigned seed() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct FaceKey {
|
||||||
|
BlockType block_id = 0;
|
||||||
|
int face = -1; // 0-5, used to index NORMALS/TANGENTS/TEX_COORDS
|
||||||
|
|
||||||
|
bool valid() const { return block_id != 0; }
|
||||||
|
bool operator==(const FaceKey& o) const {
|
||||||
|
return block_id == o.block_id && face == o.face;
|
||||||
|
}
|
||||||
|
bool operator!=(const FaceKey& o) const { return !(*this == o); }
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr int SIZE_X = CHUNK_SIZE;
|
||||||
|
static constexpr int SIZE_Y = WORLD_SIZE_Y;
|
||||||
|
static constexpr int SIZE_Z = CHUNK_SIZE;
|
||||||
|
static constexpr int VERTEX_DATA_SUM = 5;
|
||||||
|
std::atomic<bool> m_dirty{false};
|
||||||
|
std::atomic<bool> m_need_upload{true};
|
||||||
|
std::atomic<bool> m_is_on_gen_vertex_data{false};
|
||||||
|
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
|
||||||
|
std::mutex m_vertexs_data_mutex;
|
||||||
|
ChunkPos m_chunk_pos;
|
||||||
|
ClientWorld& m_world;
|
||||||
|
// the index is a array of block id
|
||||||
|
std::vector<BlockType> m_blocks;
|
||||||
|
/*
|
||||||
|
0 - normal
|
||||||
|
1 - cross_plane
|
||||||
|
2 - normal_discard
|
||||||
|
3 - transparent and blend
|
||||||
|
4 - water
|
||||||
|
*/
|
||||||
|
std::vector<VertexData> m_vertex_data;
|
||||||
|
unsigned m_seed = 0;
|
||||||
|
void clear_dirty();
|
||||||
|
void gen_vertices(const OptionalBlockVectorArray& neighbor_block);
|
||||||
|
void gen_cross_plane_vertices(int world_x, int world_y, int world_z,
|
||||||
|
BlockType id);
|
||||||
|
void emit_quad(int axis, int face_dir, int layer, int i, int j, int w,
|
||||||
|
int h, int u_axis, int v_axis, FaceKey key);
|
||||||
|
};
|
||||||
|
} // namespace Cubed
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Cubed/AABB.hpp"
|
||||||
|
#include "Cubed/constants.hpp"
|
||||||
|
#include "Cubed/gameplay/block.hpp"
|
||||||
|
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||||
|
#include "Cubed/gameplay/game_mode.hpp"
|
||||||
|
#include "Cubed/input.hpp"
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <optional>
|
||||||
|
namespace Cubed {
|
||||||
|
enum class Gait { WALK = 0, RUN };
|
||||||
|
class ClientWorld;
|
||||||
|
class ClientPlayer {
|
||||||
|
public:
|
||||||
|
ClientPlayer(ClientWorld& world);
|
||||||
|
~ClientPlayer();
|
||||||
|
AABB get_aabb() const;
|
||||||
|
const glm::vec3& get_front() const;
|
||||||
|
const Gait& get_gait() const;
|
||||||
|
const std::optional<LookBlock>& get_look_block_pos() const;
|
||||||
|
const glm::vec3& get_player_pos() const;
|
||||||
|
const MoveState& get_move_state() const;
|
||||||
|
|
||||||
|
void change_mode(GameMode mode);
|
||||||
|
void hot_reload();
|
||||||
|
void set_player_pos(const glm::vec3& pos);
|
||||||
|
void set_place_block(unsigned id);
|
||||||
|
void update(float delta_time);
|
||||||
|
void update_front_vec(float offset_x, float offset_y);
|
||||||
|
void update_player_move_state(int key, int action);
|
||||||
|
void update_scroll(double yoffset);
|
||||||
|
|
||||||
|
float& max_walk_speed();
|
||||||
|
float& max_run_speed();
|
||||||
|
float& max_speed();
|
||||||
|
float& acceleration();
|
||||||
|
float& deceleration();
|
||||||
|
float& g();
|
||||||
|
float& fly_y_speed();
|
||||||
|
|
||||||
|
unsigned place_block() const;
|
||||||
|
|
||||||
|
Gait& gait();
|
||||||
|
GameMode& game_mode();
|
||||||
|
|
||||||
|
const ClientWorld& get_world() const;
|
||||||
|
|
||||||
|
void set_uuid(std::string_view uuid);
|
||||||
|
const std::string& get_uuid() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using enum GameMode;
|
||||||
|
float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED;
|
||||||
|
float m_max_run_speed = DEFAULT_MAX_RUN_SPEED;
|
||||||
|
float m_acceleration = DEFAULT_ACCELERATION;
|
||||||
|
float m_deceleration = DEFAULT_DECELERATION;
|
||||||
|
float m_g = DEFAULT_G;
|
||||||
|
constexpr static float MAX_SPACE_ON_TIME = 0.3f;
|
||||||
|
|
||||||
|
float m_yaw = 0.0f;
|
||||||
|
float m_pitch = 0.0f;
|
||||||
|
|
||||||
|
float m_sensitivity = 0.15f;
|
||||||
|
|
||||||
|
float m_max_speed = m_max_walk_speed;
|
||||||
|
float m_y_speed = 0.0f;
|
||||||
|
float m_fly_y_speed = 7.5f;
|
||||||
|
bool can_up = true;
|
||||||
|
|
||||||
|
float space_on_time = 0.0f;
|
||||||
|
bool space_on = false;
|
||||||
|
bool is_fly = false;
|
||||||
|
|
||||||
|
float m_xz_speed = 0.0f;
|
||||||
|
|
||||||
|
unsigned m_place_block = 1;
|
||||||
|
|
||||||
|
glm::vec3 direction = glm::vec3(0.0f, 0.0f, 0.0f);
|
||||||
|
glm::vec3 move_distance{0.0f, 0.0f, 0.0f};
|
||||||
|
// player is tow block tall, the pos is the lower pos
|
||||||
|
|
||||||
|
glm::vec3 m_player_pos{0.0f, 255.0f, 0.0f};
|
||||||
|
ChunkPos m_player_chunk_pos{0, 0};
|
||||||
|
|
||||||
|
glm::vec3 m_front{0, 0, -1};
|
||||||
|
glm::vec3 m_right{0, 0, 0};
|
||||||
|
glm::vec3 m_size{0.6f, 1.8f, 0.6f};
|
||||||
|
|
||||||
|
Gait m_gait = Gait::WALK;
|
||||||
|
MoveState m_move_state{};
|
||||||
|
GameMode m_game_mode = CREATIVE;
|
||||||
|
std::optional<LookBlock> m_look_block = std::nullopt;
|
||||||
|
std::string m_name{};
|
||||||
|
std::string m_uuid;
|
||||||
|
ClientWorld& m_world;
|
||||||
|
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
|
||||||
|
glm::ivec3& block_pos, glm::vec3& normal,
|
||||||
|
float distance = 4.0f);
|
||||||
|
|
||||||
|
void check_player_chunk_transition();
|
||||||
|
void update_direction();
|
||||||
|
void update_lookup_block();
|
||||||
|
void update_move(float delta_time);
|
||||||
|
void update_x_move();
|
||||||
|
void update_y_move();
|
||||||
|
void update_z_move();
|
||||||
|
};
|
||||||
|
} // namespace Cubed
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Cubed/AABB.hpp"
|
||||||
|
#include "Cubed/gameplay/block.hpp"
|
||||||
|
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||||
|
#include "Cubed/gameplay/client_chunk.hpp"
|
||||||
|
#include "Cubed/gameplay/client_player.hpp"
|
||||||
|
|
||||||
|
#include <deque>
|
||||||
|
#include <tbb/concurrent_unordered_map.h>
|
||||||
|
namespace Cubed {
|
||||||
|
|
||||||
|
class ClientWorld {
|
||||||
|
public:
|
||||||
|
ClientWorld();
|
||||||
|
~ClientWorld();
|
||||||
|
void init();
|
||||||
|
void update(float delta_time);
|
||||||
|
bool can_move(const AABB& player_box) const;
|
||||||
|
const std::optional<LookBlock>&
|
||||||
|
get_look_block_pos(const std::string& name) const;
|
||||||
|
ClientPlayer& get_player();
|
||||||
|
int get_block(const glm::ivec3& block_pos) const;
|
||||||
|
bool is_solid(const glm::ivec3& block_pos) const;
|
||||||
|
bool can_pass_block(const glm::ivec3& block_pos) const;
|
||||||
|
BlockType get_block_tpye(const glm::ivec3& block_pos) const;
|
||||||
|
void set_block(const glm::ivec3& pos, unsigned id);
|
||||||
|
void push_delete_vbo(GLuint vbo);
|
||||||
|
void push_delete_vao(GLuint vao);
|
||||||
|
void hot_reload();
|
||||||
|
|
||||||
|
void rebuild_world();
|
||||||
|
|
||||||
|
int rendering_distance() const;
|
||||||
|
void rendering_distance(int rendering_distance);
|
||||||
|
void start_client_thread();
|
||||||
|
void stop_client_thread();
|
||||||
|
|
||||||
|
std::vector<glm::vec4>& planes();
|
||||||
|
std::vector<ChunkRenderSnapshot>& render_snapshots();
|
||||||
|
glm::vec3 sunlight_dir() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using ChunkHashMap =
|
||||||
|
tbb::concurrent_unordered_map<ChunkPos, ClientChunk, ChunkPos::Hash>;
|
||||||
|
ClientPlayer m_player;
|
||||||
|
ChunkHashMap m_chunks;
|
||||||
|
std::vector<glm::vec4> m_planes;
|
||||||
|
std::jthread m_client_thread;
|
||||||
|
|
||||||
|
mutable std::shared_mutex m_chunks_mutex;
|
||||||
|
std::mutex m_delete_vbo_mutex;
|
||||||
|
std::mutex m_delete_vao_mutex;
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
} // namespace Cubed
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Cubed/gameplay/packet.hpp"
|
||||||
|
|
||||||
|
#include <asio.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
namespace Cubed {
|
||||||
|
using asio::ip::tcp;
|
||||||
|
class NetworkClient : std::enable_shared_from_this<NetworkClient> {
|
||||||
|
public:
|
||||||
|
NetworkClient();
|
||||||
|
~NetworkClient();
|
||||||
|
void close();
|
||||||
|
asio::awaitable<void> connect(std::string ip, int port);
|
||||||
|
void send(Packet packet);
|
||||||
|
void start(std::string ip, int port = 25530);
|
||||||
|
|
||||||
|
private:
|
||||||
|
asio::io_context m_io;
|
||||||
|
|
||||||
|
std::thread m_net_thread;
|
||||||
|
static constexpr uint32_t MAX_PACKET_SIZE = 4 * 1024 * 1024;
|
||||||
|
tcp::socket m_socket;
|
||||||
|
std::vector<char> m_read_buffer;
|
||||||
|
std::deque<Packet> m_write_queue;
|
||||||
|
asio::strand<asio::io_context::executor_type> m_strand;
|
||||||
|
asio::awaitable<void> read_loop();
|
||||||
|
std::atomic<bool> m_closed{false};
|
||||||
|
|
||||||
|
// ClientWorld is managed by App
|
||||||
|
// ClientWorld& m_world;
|
||||||
|
|
||||||
|
void do_write();
|
||||||
|
};
|
||||||
|
} // namespace Cubed
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
#include "Cubed/gameplay/network_client.hpp"
|
||||||
|
|
||||||
|
#include "Cubed/tools/log.hpp"
|
||||||
|
|
||||||
|
namespace Cubed {
|
||||||
|
NetworkClient::NetworkClient() : m_socket(m_io) {}
|
||||||
|
|
||||||
|
NetworkClient::~NetworkClient() { close(); }
|
||||||
|
|
||||||
|
void NetworkClient::start(std::string ip, int port) {
|
||||||
|
if (m_net_thread.joinable()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_net_thread = std::thread([self = shared_from_this(), ip, port]() {
|
||||||
|
asio::co_spawn(self->m_strand, self->connect(ip, port), asio::detached);
|
||||||
|
self->m_io.run();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
asio::awaitable<void> NetworkClient::connect(std::string ip, int port) {
|
||||||
|
try {
|
||||||
|
auto ex = co_await asio::this_coro::executor;
|
||||||
|
tcp::resolver resolver(ex);
|
||||||
|
auto eps = co_await resolver.async_resolve(ip, std::to_string(port),
|
||||||
|
asio::use_awaitable);
|
||||||
|
co_await async_connect(m_socket, eps, asio::use_awaitable);
|
||||||
|
asio::co_spawn(m_strand, read_loop(), asio::detached);
|
||||||
|
co_return;
|
||||||
|
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
Logger::error("Client Error {}", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
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);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
case to_num(PacketEnum::LOGIN_RSP): {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (const asio::system_error& e) {
|
||||||
|
auto ec = e.code();
|
||||||
|
|
||||||
|
if (ec == asio::error::eof || ec == asio::error::operation_aborted) {
|
||||||
|
|
||||||
|
Logger::info("Client disconnected");
|
||||||
|
} else {
|
||||||
|
Logger::warn("Asio Error {}", e.what());
|
||||||
|
}
|
||||||
|
|
||||||
|
close();
|
||||||
|
} catch (const std::exception& e) {
|
||||||
|
Logger::error("Session Error {}", e.what());
|
||||||
|
close();
|
||||||
|
} catch (...) {
|
||||||
|
Logger::error("Unknow Error");
|
||||||
|
close();
|
||||||
|
}
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkClient::send(std::shared_ptr<std::vector<uint8_t>> packet) {
|
||||||
|
asio::post(m_strand, [self = shared_from_this(),
|
||||||
|
packet = std::move(packet)]() mutable {
|
||||||
|
bool idle = self->m_write_queue.empty();
|
||||||
|
self->m_write_queue.emplace_back(std::move(packet));
|
||||||
|
if (idle) {
|
||||||
|
self->do_write();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkClient::do_write() {
|
||||||
|
|
||||||
|
auto self = shared_from_this();
|
||||||
|
asio::async_write(
|
||||||
|
m_socket, asio::buffer(*(m_write_queue.front())),
|
||||||
|
asio::bind_executor(m_strand, [self](std::error_code ec, size_t) {
|
||||||
|
if (ec) {
|
||||||
|
Logger::warn("Write Ec {}", ec.message());
|
||||||
|
self->close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self->m_write_queue.pop_front();
|
||||||
|
if (!self->m_write_queue.empty()) {
|
||||||
|
self->do_write();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NetworkClient::close() {
|
||||||
|
if (m_closed.exchange(true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
|
||||||
|
m_socket.shutdown(tcp::socket::shutdown_both, ec);
|
||||||
|
|
||||||
|
m_socket.close(ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Cubed
|
||||||
Reference in New Issue
Block a user