mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-09 02:07:04 +08:00
feature: multiplayer (#25)
* build: add asio library * feat(dev_panel): add Asio to about table * refactor(gameplay): split Chunk into server/client variants and add networking * feat(gameplay): add Session class and UUID generation utility * feat(network): integrate protobuf for player sync and session management Add Protobuf dependency, define proto messages for player requests, positions, and chunk data. Refactor Session to use strand and async write. Implement player join/exit and position sync in ServerWorld. * refactor(proto): restructure protobuf definitions and update build system * feat(protocol): add packet serialization and login handling Introduce packet header and ID mapping for protobuf messages. Refactor session and server_world to use new packet wrapper. Fix missing semicolons in proto files. * refactor(packet): serialize directly into packet buffer with ByteSizeLong * feat(world): add player-based chunk loading and UUID support Restructure world generation to trigger chunk loading based on player movement. Replace player name with UUID for identification. Implement chunk request/response protocol. Improve thread management for gen thread. * feat(server): add block change packet handling and increase reserved threads * refactor(server): enhance thread safety and session management * fix(session): catch std::exception in read_loop and log error * fix(network-server): stop accept loop on shutdown flag * fix(gameplay): correct unknown error log condition and missing chunk pos update * feat(client): add client-side chunk, player, world and network classes * feat(client-chunk): add greedy meshing, receive_chunk, and biome field * feat(client_player): add name constructor param, remove chunk transition * feat(gameplay): integrate network client and thread-safe player pos * feat(gameplay): add block change sync and coordinate utils * refactor(server): replace Chunk with ServerChunk * refactor(gameplay): split world into client and server Remove monolithic World, Player, Chunk classes. Introduce ClientWorld, ServerWorld, ClientPlayer, and related networked components. Add Abseil dependency for logging and checks. Rename old files to pre_remove_* and update all includes and references accordingly. * feat(gameplay): add time sync and network improvements * fix: correct neighbor block indexing and add time update support * feat(gameplay): optimize chunk request with player chunk tracking Add `update_player_chunk()` method to `ClientPlayer` that triggers a chunk request when the player moves more than 2 chunks away from the last recorded chunk. Introduce an atomic `m_requesting_chunk` flag in `ClientWorld` to prevent concurrent requests. Rename `m_player_chunk_pos` to `m_last_chunk_pos` for clarity. * refactor(client_world): split pending queue into data and upload queues * feat(client): add center-based chunk loading priority * feat(app): add client/server CLI arguments and init guards * feat(multiplayer): add rendering of other players as colored cubes * feat(gameplay): add thread pool for asynchronous chunk data processing * feat(gameplay): add player logout and disconnect handling * fix(dev_panel): add protobuf to about table libraries * build: add ZLIB dependency * 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. * refactor(network): extract packet header decoding into function * feat(packet): implement zstd compression and refactor packet decoding * perf(gameplay): optimize protobuf message allocation with Arena and limit thread pool size Switch to Arena allocation for protobuf messages to reduce heap allocations. Pass ChunkDataRsp by value to enable move semantics. Cap thread pool size to hardware concurrency with max 4 threads. * refactor(gameplay): use steady clock and sleep_until for tick loop timing * refactor(gameplay): rename remote player types and add interpolation * refactor(server_world): use thread pool for chunk request handling * refactor(packet): replace if-constexpr chain with explicit template specializations Refactor `get_packet_id()` by removing the if-constexpr chain and instead providing explicit specializations for each packet type. This simplifies the primary template (which now only contains a static assertion) and improves compile-time dispatch clarity. The `always_false` helper is no longer needed in the primary template. * feat(gameplay): add packet to clear all chunks on server rebuild Add new packet S2C_ClearAllChunks (ID 3005) to notify clients when the server rebuilds its world. Clients respond by clearing their chunk cache and requesting fresh chunks. Shift UPDATE_TIME packet ID to 3006 to accommodate. * feat(dev_panel): split world tab; add chunk request logging and fix flag reset * feat(tools): add RecentQueue to replace std::deque in server_world * feat(gameplay): add task ID to chunk requests to discard stale responses * refactor(block): move block source to gameplay directory * feat(toml): add shared TOML utilities and handle ip.toml * feat: add player name configuration and rename config file - Add --player command-line argument and 'player' field in Arguments struct - Rename ip.toml to server.toml and corresponding internal variable to 'server' - Fix block.cpp source path in CMakeLists.txt * build(cmake): modularize build configuration * fix: build fail on windows * refactor(renderer): rename Logger level enum value DEBUG to L_DEBUG * refactor(world): rework chunk state machine and player chunk tracking Introduce ChunkState enum and ChunkEntity struct to manage chunk lifecycle. Store chunks as shared_ptr to avoid move operations during generation. Add clear_unused_chunks to remove chunks not referenced by any player. Implement deferred chunk request queue for safe processing after generation completes. Update player chunk set during required chunk computation. Improve thread safety with mutexes on chunk and player maps. Fix m_gening flag not reset after generation and add assertions for correctness. Change need_gen to require a player UUID, removing std::optional. Add chunk_size query method for debugging. * fix(server): ensure chunk is ready before sending or setting block * refactor(server_world): separate gen and net thread pools Introduce a second thread pool for network operations and a `ThreadPoolKind` enum to distinguish between gen and net pools. Rename `pool_threads()` to `gen_pool_threads()`, add `change_pool_threads()` overload that accepts the pool kind, and update the dev panel to use the gen pool. Adjust logging and initialization to handle both pools. * feat(client_world): implement server exit acknowledgment with timeout * feat(gameplay): implement server stop and client exit handling Add server_stop flag to LogoutRsp protocol. Modify client_world to check for server stop or own logout to set exit flag. Add ServerWorld::stop() to broadcast stop and cleanly shut down. Refactor chunk ownership to unique_ptr. Remove name parameter from get_look_block_pos. * fix(app): correct port validation and remove redundant TOML helper * fix: address unused parameter warnings and missing port assignment * refactor(gameplay): remove old pre_remove chunk, player, and world files * refactor(server): replace chunk set with flat_hash_set and add ref count - Replace std::unordered_set with absl::flat_hash_set for chunk position sets. - Add ref_count field to ChunkEntity and implement update_ref_count(). - Remove clear_unused_chunks() and use ref counting for chunk lifetime. - Add get_chunk_pos_set() accessors to ServerPlayer. * fix(client-world): delay chunk request to wait for server central chunk generation * refactor(gameplay): migrate chunk storage to tbb::concurrent_hash_map * refactor(server_world): replace future-based chunk generation with queue Remove `std::future` per-chunk tracking and poll_finished_chunks(). Instead, generation tasks push completed chunks to a concurrent queue consumed during update. This eliminates wait_all_chunk_tasks() and simplifies synchronization. * refactor(client_world): use concurrent data structures for chunk management Replace std::shared_mutex and std::unordered_set with tbb::concurrent_hash_map for chunk storage, tbb::concurrent_queue for pending uploads, and absl::flat_hash_set for player chunk positions. This improves thread safety and reduces locking overhead. Refactor receive_chunk and request_chunk to work with the new concurrent model. * build(windows): add Nvidia and AMD GPU high performance exports * fix(ci): update header file pattern in clang-format check * style(clang-format): comment out AlignPPAndNotPP option * chore: enable AlignPPAndNotPP in clang-format and pin clang version in CI * ci(workflow): update format check to use LLVM 22 * ci(format-check): install clang-format-22 * perf(world): cull distant players from updates and rendering Add distance2 utility function. On client, skip rendering other players if distance squared exceeds rendering distance. On server, only send player position and block change updates to players in relevant chunks. * refactor(math_tools): convert free functions to inline in header * refactor(dev_panel): remove seed editing and server rebuild world, move client rebuild - Remove text editing for perlin seed and filter function - Remove ServerWorld::rebuild_world() method and its atomic flag - Move 'Rebuild World' button to client world tab - Remove unused perlin_noise_input_buffer * fix(server_world): floor block position coordinates in handle_block_change * refactor(gameplay): pass new_chunks vector as parameter instead of member variable * perf(client): improve chunk rendering performance with snapshots and threaded generation * feat(tools): add PriorityThreadPool with priority scheduling Implement PriorityThreadPool supporting task priorities and FIFO ordering for same priority. Update ClientWorld to use the new pool with explicit priority for chunk operations. Fix ThreadPool stop logic with atomic exchange and remove unnecessary lambda capture. * feat(networking): add priority and sequence ordering to packet send queues * feat(server-world): use priority thread pool for chunk generation Introduce PriorityThreadPool to process chunk generation tasks with priorities based on their distance from the player. Closer chunks receive higher priority, improving responsiveness. * refactor(client): extract AABB creation and add collision check on block place Make ClientPlayer::get_aabb and ClientWorld::get_block_aabb static. Change player size to static constexpr M_SIZE. Use shared_mutex for concurrent reads of other players. Check for collision with other players before placing a block. * feat(build): add build-time version system via CMake configure_file * ci(release): add release build workflow for windows
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/client_world.hpp"
|
||||
#include "Cubed/gameplay/network_server.hpp"
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include "Cubed/camera.hpp"
|
||||
#include "Cubed/dev_panel.hpp"
|
||||
#include "Cubed/gameplay/world.hpp"
|
||||
#include "Cubed/renderer.hpp"
|
||||
#include "Cubed/texture_manager.hpp"
|
||||
#include "Cubed/window.hpp"
|
||||
@@ -10,6 +12,13 @@ namespace Cubed {
|
||||
|
||||
class App {
|
||||
public:
|
||||
struct Argument {
|
||||
bool is_client = false;
|
||||
int port = 25530;
|
||||
std::string ip{"127.0.0.1"};
|
||||
std::string player{"Unknown"};
|
||||
};
|
||||
|
||||
App();
|
||||
~App();
|
||||
static void cursor_position_callback(GLFWwindow* window, double xpos,
|
||||
@@ -36,14 +45,20 @@ public:
|
||||
Renderer& renderer();
|
||||
TextureManager& texture_manager();
|
||||
Window& window();
|
||||
World& world();
|
||||
ClientWorld& client_world();
|
||||
ServerWorld& server_world();
|
||||
const Argument& argument() const;
|
||||
|
||||
private:
|
||||
Camera m_camera;
|
||||
TextureManager m_texture_manager;
|
||||
World m_world;
|
||||
NetworkServer m_server;
|
||||
std::shared_ptr<NetworkClient> m_client;
|
||||
ClientWorld m_client_world;
|
||||
|
||||
DevPanel m_dev_panel{*this};
|
||||
Renderer m_renderer{m_camera, m_world, m_texture_manager, m_dev_panel};
|
||||
Renderer m_renderer{m_camera, m_client_world, m_texture_manager,
|
||||
m_dev_panel};
|
||||
|
||||
Window m_window{m_renderer};
|
||||
|
||||
@@ -53,9 +68,10 @@ 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);
|
||||
void handle_toml();
|
||||
auto init_camera();
|
||||
auto init_texture();
|
||||
auto init_world();
|
||||
|
||||
@@ -8,12 +8,12 @@
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
class Player;
|
||||
class ClientPlayer;
|
||||
|
||||
class Camera {
|
||||
private:
|
||||
bool m_firse_mouse = true;
|
||||
Player* m_player;
|
||||
ClientPlayer* m_player;
|
||||
float m_last_mouse_x, m_last_mouse_y;
|
||||
glm::vec3 m_camera_pos;
|
||||
bool m_under_water = false;
|
||||
@@ -23,7 +23,7 @@ public:
|
||||
|
||||
void update_move_camera();
|
||||
|
||||
void camera_init(Player* player);
|
||||
void camera_init(ClientPlayer* player);
|
||||
void hot_reload();
|
||||
void reset_camera();
|
||||
void update_cursor_position_camera(double xpos, double ypos);
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
#pragma once
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
#include "Cubed/tools/toml.utils.hpp"
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
template <typename T>
|
||||
concept TomlValueType =
|
||||
std::same_as<T, int> || std::same_as<T, bool> || std::same_as<T, double> ||
|
||||
std::same_as<T, const char*> || std::same_as<T, toml::date> ||
|
||||
std::same_as<T, toml::time> || std::same_as<T, toml::date_time> ||
|
||||
std::same_as<T, std::string>;
|
||||
|
||||
class Config {
|
||||
public:
|
||||
Config();
|
||||
@@ -24,7 +16,7 @@ public:
|
||||
void load_or_create_config();
|
||||
void save_to_file();
|
||||
|
||||
template <TomlValueType T> T get(std::string_view key) const {
|
||||
template <TOML::TomlValueType T> T get(std::string_view key) const {
|
||||
size_t cur = 0;
|
||||
auto pos = key.find('.');
|
||||
const toml::table* table = &m_tbl;
|
||||
@@ -61,7 +53,7 @@ public:
|
||||
}
|
||||
}
|
||||
template <typename T> void set(std::string_view key, T&& val) {
|
||||
if constexpr (!TomlValueType<std::decay_t<T>>) {
|
||||
if constexpr (!TOML::TomlValueType<std::decay_t<T>>) {
|
||||
static_assert(false, "Type Not Support");
|
||||
}
|
||||
size_t cur = 0;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
|
||||
#include <array>
|
||||
namespace Cubed {
|
||||
@@ -26,13 +25,10 @@ constexpr float DEFAULT_G = 22.5f;
|
||||
constexpr int SIZE_X = CHUNK_SIZE;
|
||||
constexpr int SIZE_Y = WORLD_SIZE_Y;
|
||||
constexpr int SIZE_Z = CHUNK_SIZE;
|
||||
constexpr int RESERVED_THREADS = 3;
|
||||
constexpr int RESERVED_THREADS = 5;
|
||||
|
||||
constexpr float DEFAULT_CAVE_PROBABILITY = 0.035f;
|
||||
|
||||
constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
|
||||
{1, 1}, {-1, 1}, {1, -1}, {-1, -1}};
|
||||
|
||||
using HeightMapArray = std::array<std::array<int, CHUNK_SIZE>, CHUNK_SIZE>;
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -5,7 +5,7 @@
|
||||
namespace Cubed {
|
||||
|
||||
class App;
|
||||
class Player;
|
||||
class ClientPlayer;
|
||||
class DevPanel {
|
||||
struct ConfigView {
|
||||
float fov = 70.0f;
|
||||
@@ -26,9 +26,6 @@ class DevPanel {
|
||||
int gait = 0;
|
||||
float pos[3] = {0.0f, 0.0f, 0.0f};
|
||||
};
|
||||
struct TextEditing {
|
||||
bool perlin_seed = false;
|
||||
};
|
||||
|
||||
public:
|
||||
DevPanel(App& app);
|
||||
@@ -38,9 +35,8 @@ public:
|
||||
private:
|
||||
App& m_app;
|
||||
ConfigView m_config;
|
||||
Player* m_player;
|
||||
ClientPlayer* m_player;
|
||||
PlayerProfile m_player_profile;
|
||||
TextEditing m_text_editing;
|
||||
bool m_need_save_config = false;
|
||||
bool m_gen_thread_running = true;
|
||||
int m_theme = 0;
|
||||
@@ -58,6 +54,8 @@ private:
|
||||
void show_chunk_table_bar();
|
||||
void show_settings_tab_item();
|
||||
void show_world_tab_item();
|
||||
void show_server_world_table_bar();
|
||||
void show_client_world_table_bar();
|
||||
void show_player_tab_item();
|
||||
void show_items_tab_item();
|
||||
void show_shader_tab_item();
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
#pragma once
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace Cubed {
|
||||
@@ -77,4 +80,32 @@ DesertParams& desert_params();
|
||||
MountainParams& mountain_params();
|
||||
RiverParams& river_params();
|
||||
|
||||
inline BiomeType get_biome_from_id(int id) {
|
||||
using enum BiomeType;
|
||||
auto to = std::to_underlying<BiomeType>;
|
||||
if (id == to(PLAIN)) {
|
||||
return PLAIN;
|
||||
}
|
||||
if (id == to(FOREST)) {
|
||||
return FOREST;
|
||||
}
|
||||
if (id == to(DESERT)) {
|
||||
return DESERT;
|
||||
}
|
||||
if (id == to(MOUNTAIN)) {
|
||||
return MOUNTAIN;
|
||||
}
|
||||
if (id == to(RIVER)) {
|
||||
return RIVER;
|
||||
}
|
||||
if (id == to(SNOWY_PLAIN)) {
|
||||
return SNOWY_PLAIN;
|
||||
}
|
||||
if (id == to(OCEAN)) {
|
||||
return OCEAN;
|
||||
}
|
||||
ASSERT_MSG(false, "Unknown Biome Id");
|
||||
throw std::invalid_argument("Unknown Biome Id");
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
using BlockType = uint8_t;
|
||||
using OptionalBlockVectorArray =
|
||||
std::array<std::optional<std::vector<BlockType>>, 4>;
|
||||
|
||||
struct BlockTexture {
|
||||
std::string name;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "Cubed/constants.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/path.hpp"
|
||||
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
|
||||
@@ -11,11 +11,11 @@
|
||||
#include <optional>
|
||||
namespace Cubed {
|
||||
|
||||
class Chunk;
|
||||
class ServerChunk;
|
||||
|
||||
class ChunkGenerator {
|
||||
public:
|
||||
ChunkGenerator(Chunk& chunk);
|
||||
ChunkGenerator(ServerChunk& chunk);
|
||||
|
||||
static void init();
|
||||
static void reload();
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
void assign_chunk_biome();
|
||||
// Adjust Biome
|
||||
void resolve_biome_adjacency_conflict(
|
||||
const std::array<const Chunk*, 8>& adj_chunks);
|
||||
const std::array<const ServerChunk*, 8>& adj_chunks);
|
||||
// Generate Heightmap
|
||||
void generate_heightmap();
|
||||
// Adjust Height
|
||||
@@ -42,7 +42,7 @@ public:
|
||||
// Generate Structure
|
||||
void generate_vegetation();
|
||||
BiomeType get_biome_at(float world_x, float world_z);
|
||||
Chunk& chunk();
|
||||
ServerChunk& chunk();
|
||||
Random& random();
|
||||
const std::array<BiomeType, 8>& neighbor_biome() const;
|
||||
void ocean_build();
|
||||
@@ -53,7 +53,7 @@ private:
|
||||
static inline std::atomic<bool> is_init{false};
|
||||
static inline unsigned m_generator_seed{0};
|
||||
static inline std::atomic<bool> is_seed_change{false};
|
||||
Chunk& m_chunk;
|
||||
ServerChunk& m_chunk;
|
||||
Random m_random;
|
||||
std::unique_ptr<BiomeBuilder> m_biome_builder{nullptr};
|
||||
bool is_cur_chunk_ins = false;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/constants.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace Cubed {
|
||||
@@ -34,5 +36,29 @@ struct ChunkPos {
|
||||
return *this;
|
||||
};
|
||||
};
|
||||
constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
|
||||
{1, 1}, {-1, 1}, {1, -1}, {-1, -1}};
|
||||
inline ChunkPos get_chunk_pos(int world_x, int world_z) {
|
||||
int chunk_x, chunk_z;
|
||||
if (world_x < 0) {
|
||||
chunk_x = (world_x + 1) / CHUNK_SIZE - 1;
|
||||
}
|
||||
if (world_x >= 0) {
|
||||
chunk_x = world_x / CHUNK_SIZE;
|
||||
}
|
||||
if (world_z < 0) {
|
||||
chunk_z = (world_z + 1) / CHUNK_SIZE - 1;
|
||||
}
|
||||
if (world_z >= 0) {
|
||||
chunk_z = world_z / CHUNK_SIZE;
|
||||
}
|
||||
return {chunk_x, chunk_z};
|
||||
}
|
||||
|
||||
inline float distance2(const ChunkPos& a, const ChunkPos& b) {
|
||||
float dx = static_cast<float>(a.x) - b.x;
|
||||
float dz = static_cast<float>(a.z) - b.z;
|
||||
return dx * dx + dz * dz;
|
||||
}
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -1,94 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/constants.hpp"
|
||||
#include "Cubed/gameplay/biome.hpp"
|
||||
#include "Cubed/gameplay/block.hpp"
|
||||
#include "Cubed/gameplay/chunk_generator.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/vertex_data.hpp"
|
||||
#include "world/chunk_data.pb.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <glad/glad.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <mutex>
|
||||
namespace Cubed {
|
||||
|
||||
struct ChunkInfo {
|
||||
ChunkPos pos{0, 0};
|
||||
unsigned seed{0};
|
||||
BiomeType biome{BiomeType::NONE};
|
||||
unsigned first_random{0};
|
||||
bool has_cave_start{false};
|
||||
bool has_cave{false};
|
||||
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 World;
|
||||
// if want to use, do init_chunk(), gen_vertex_data() and
|
||||
class Chunk {
|
||||
private:
|
||||
using OptionalBlockVectorArray =
|
||||
std::array<std::optional<std::vector<BlockType>>, 4>;
|
||||
|
||||
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<bool> m_gening{false};
|
||||
std::atomic<bool> m_temp_chunk{false};
|
||||
|
||||
bool m_has_cave{false};
|
||||
|
||||
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
|
||||
std::mutex m_vertexs_data_mutex;
|
||||
|
||||
std::unique_ptr<ChunkGenerator> m_generator;
|
||||
|
||||
ChunkPos m_chunk_pos;
|
||||
World& m_world;
|
||||
HeightMapArray m_heightmap;
|
||||
// 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;
|
||||
float frequency = 0.01f;
|
||||
float height = 80;
|
||||
unsigned m_seed = 0;
|
||||
|
||||
BiomeConditions m_conditions;
|
||||
ChunkInfo m_info;
|
||||
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);
|
||||
|
||||
class ClientChunk {
|
||||
public:
|
||||
Chunk(World& world, ChunkPos chunk_pos, bool temp_chunk = false);
|
||||
~Chunk();
|
||||
Chunk(const Chunk&) = delete;
|
||||
Chunk& operator=(const Chunk&) = delete;
|
||||
Chunk(Chunk&&) noexcept;
|
||||
Chunk& operator=(Chunk&&) noexcept;
|
||||
ClientChunk(ClientWorld& world);
|
||||
~ClientChunk();
|
||||
ClientChunk(const ClientChunk&) = delete;
|
||||
ClientChunk& operator=(const ClientChunk&) = delete;
|
||||
ClientChunk(ClientChunk&&) noexcept;
|
||||
ClientChunk& operator=(ClientChunk&&) noexcept;
|
||||
|
||||
static int index(int x, int y, int z);
|
||||
static int index(const glm::vec3& pos);
|
||||
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
|
||||
int world_z, int chunk_x,
|
||||
int chunk_z);
|
||||
@@ -101,33 +49,9 @@ public:
|
||||
BiomeType get_biome() const;
|
||||
ChunkPos get_chunk_pos() const;
|
||||
const std::vector<BlockType>& get_chunk_blocks() const;
|
||||
HeightMapArray get_heightmap() const;
|
||||
static int index(int x, int y, int z);
|
||||
static int index(const glm::vec3& pos);
|
||||
// Init Chunk
|
||||
// Determine biome from temperature and humidity noise
|
||||
void gen_phase_one();
|
||||
// Resolve biome adjacency conflicts with neighbor chunks
|
||||
void gen_phase_two(const std::array<const Chunk*, 8>& adj_chunks);
|
||||
// Generate heightmap using biome-specific noise
|
||||
void gen_phase_three();
|
||||
// Blend heightmap with neighbors for smooth transitions
|
||||
void gen_phase_four(
|
||||
const std::array<std::optional<HeightMapArray>, 8>& neighbor_heightmap,
|
||||
const std::array<BiomeType, 8>& neighbor_biome);
|
||||
// Generate terrain blocks from heightmap and biome
|
||||
void gen_phase_five();
|
||||
// Blend surface blocks at chunk borders with neighbors
|
||||
void gen_phase_six(const std::array<std::optional<std::vector<BlockType>>,
|
||||
4>& neighbor_block);
|
||||
// Generate biome-specific vegetation/structures
|
||||
void gen_phase_seven();
|
||||
// void gen_vertex_data();
|
||||
// 0 : (1, 0)
|
||||
// 1 : (-1, 0)
|
||||
// 2 : (0, 1)
|
||||
// 3 : (0, -1)
|
||||
void receive_chunk(const ChunkDataRsp& data);
|
||||
void gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
|
||||
// Can only be called on the render thread
|
||||
void upload_to_gpu();
|
||||
|
||||
GLuint get_normal_vao() const;
|
||||
@@ -152,20 +76,58 @@ public:
|
||||
void need_upload();
|
||||
|
||||
void set_chunk_block(int index, unsigned id);
|
||||
// ensure thread safe!
|
||||
void gen_chunk();
|
||||
|
||||
bool is_temp_chunk() const;
|
||||
ChunkPos chunk_pos() const;
|
||||
BiomeType biome() const;
|
||||
void biome(BiomeType b);
|
||||
HeightMapArray& heightmap();
|
||||
std::vector<BlockType>& blocks();
|
||||
World& world();
|
||||
ClientWorld& world();
|
||||
unsigned seed() const;
|
||||
BiomeConditions& conditions();
|
||||
ChunkInfo get_info() const;
|
||||
bool& has_cave();
|
||||
};
|
||||
const ChunkRenderSnapshot* get_render_snapshot() 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 BLOCK_SIZE = SIZE_X * SIZE_Y * SIZE_Z;
|
||||
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;
|
||||
|
||||
ChunkRenderSnapshot m_render_snapshot;
|
||||
|
||||
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
|
||||
@@ -6,17 +6,61 @@
|
||||
#include "Cubed/gameplay/game_mode.hpp"
|
||||
#include "Cubed/input.hpp"
|
||||
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <shared_mutex>
|
||||
namespace Cubed {
|
||||
|
||||
enum class Gait { WALK = 0, RUN };
|
||||
class ClientWorld;
|
||||
class ClientPlayer {
|
||||
public:
|
||||
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
|
||||
ClientPlayer(ClientWorld& world);
|
||||
~ClientPlayer();
|
||||
|
||||
class World;
|
||||
void update_chunk_set(const ChunkPosSet& set);
|
||||
const ChunkPosSet& get_chunk_pos_set() const;
|
||||
ChunkPosSet& get_chunk_pos_set();
|
||||
|
||||
static AABB get_aabb(const glm::vec3& pos);
|
||||
const glm::vec3& get_front() const;
|
||||
const Gait& get_gait() const;
|
||||
const std::optional<LookBlock>& get_look_block_pos() const;
|
||||
// thread safe
|
||||
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;
|
||||
const std::string& get_name() const;
|
||||
|
||||
void init(std::string_view name);
|
||||
|
||||
class Player {
|
||||
private:
|
||||
using enum GameMode;
|
||||
float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED;
|
||||
@@ -24,7 +68,6 @@ private:
|
||||
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;
|
||||
@@ -50,63 +93,33 @@ private:
|
||||
// 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};
|
||||
ChunkPos m_last_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};
|
||||
static constexpr 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{};
|
||||
World& m_world;
|
||||
std::string m_uuid;
|
||||
ClientWorld& m_world;
|
||||
|
||||
mutable std::shared_mutex m_player_pos_mutex;
|
||||
mutable std::shared_mutex m_chunk_pos_mutex;
|
||||
ChunkPosSet m_player_chunk_pos_set;
|
||||
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();
|
||||
|
||||
public:
|
||||
Player(World& world, const std::string& name);
|
||||
~Player();
|
||||
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 World& get_world() const;
|
||||
void update_x_move(glm::vec3& player_pos);
|
||||
void update_y_move(glm::vec3& player_pos);
|
||||
void update_z_move(glm::vec3& player_pos);
|
||||
void update_player_chunk();
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
138
include/Cubed/gameplay/client_world.hpp
Normal file
138
include/Cubed/gameplay/client_world.hpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/block.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/client_chunk.hpp"
|
||||
#include "Cubed/gameplay/client_player.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/network_client.hpp"
|
||||
#include "Cubed/tools/priority_thread_pool.hpp"
|
||||
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#include <deque>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
#include <tbb/concurrent_queue.h>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
namespace Cubed {
|
||||
|
||||
struct RemotePlayerInfo {
|
||||
std::string name;
|
||||
glm::vec3 render_pos;
|
||||
glm::vec3 target_pos;
|
||||
};
|
||||
|
||||
struct RemotePlayerRenderData {
|
||||
std::string name;
|
||||
glm::vec3 render_pos;
|
||||
};
|
||||
|
||||
class ClientWorld {
|
||||
public:
|
||||
ClientWorld();
|
||||
~ClientWorld();
|
||||
void init(std::string_view player_name,
|
||||
std::shared_ptr<NetworkClient> client);
|
||||
void update(float delta_time);
|
||||
const std::optional<LookBlock>& get_look_block_pos() 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 rebuild_world();
|
||||
|
||||
void push_delete_vbo(GLuint vbo);
|
||||
void push_delete_vao(GLuint vao);
|
||||
// void hot_reload();
|
||||
|
||||
// void rebuild_world();
|
||||
void report_block_change(const glm::ivec3& pos, unsigned id) const;
|
||||
void receive_block_change(const BlockChangeRsp& rsp);
|
||||
void receive_time(const UpdateTime& rsp);
|
||||
|
||||
void receive_remote_player(const PlayerInfoRsp& rsp);
|
||||
void receive_player_logout(const LogoutRsp& rsp);
|
||||
int rendering_distance() const;
|
||||
void rendering_distance(int rendering_distance);
|
||||
int get_chunk_task_id() const;
|
||||
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();
|
||||
const std::vector<const ChunkRenderSnapshot*>& render_snapshots() const;
|
||||
const std::vector<RemotePlayerRenderData>& render_player_data() const;
|
||||
glm::vec3 sunlight_dir() const;
|
||||
void receive_chunk(std::vector<uint8_t> data, PacketHeader header);
|
||||
void request_exit();
|
||||
bool is_receive_exit();
|
||||
int chunk_size() const;
|
||||
static AABB get_block_aabb(const glm::ivec3& pos);
|
||||
template <typename Fn>
|
||||
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
||||
m_timers.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(std::string(id)),
|
||||
std::forward_as_tuple(threshold, std::forward<Fn>(f)));
|
||||
}
|
||||
|
||||
private:
|
||||
enum class ChunkLoadStyle { RANDOM, CENTER };
|
||||
using ChunkHashMap =
|
||||
tbb::concurrent_hash_map<ChunkPos, std::shared_ptr<ClientChunk>,
|
||||
ChunkPos::TBBHash>;
|
||||
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
|
||||
using ChunkPosVector = std::vector<ChunkPos>;
|
||||
using OtherPlayerHashMap =
|
||||
std::unordered_map<std::string, RemotePlayerInfo>;
|
||||
using chunk_acc = ChunkHashMap::accessor;
|
||||
using chunk_cacc = ChunkHashMap::const_accessor;
|
||||
static constexpr int WORLD_EXIT_TIMEOUT = 200;
|
||||
static constexpr int MAX_UPLOAD_CHUNK_SUM = 16;
|
||||
ClientPlayer m_player;
|
||||
OtherPlayerHashMap m_other_players;
|
||||
ChunkHashMap m_chunks;
|
||||
std::vector<glm::vec4> m_planes;
|
||||
std::jthread m_client_thread;
|
||||
|
||||
std::mutex m_delete_vbo_mutex;
|
||||
std::mutex m_delete_vao_mutex;
|
||||
mutable std::shared_mutex m_other_players_mutex;
|
||||
|
||||
tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue;
|
||||
tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue;
|
||||
|
||||
std::vector<GLuint> m_pending_delete_vbo;
|
||||
std::vector<GLuint> m_pending_delete_vao;
|
||||
|
||||
std::deque<ChunkPos> m_dirty_queue;
|
||||
std::vector<const ChunkRenderSnapshot*> m_render_snapshots;
|
||||
std::vector<RemotePlayerRenderData> m_render_player_data;
|
||||
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
|
||||
std::atomic<bool> m_game_running{false};
|
||||
std::atomic<bool> m_receive_exit{false};
|
||||
std::atomic<int> m_rendering_distance{24};
|
||||
std::atomic<TickType> m_game_ticks{0};
|
||||
std::atomic<TickType> m_day_tick{6000};
|
||||
std::atomic<bool> m_requesting_chunk{false};
|
||||
std::atomic<bool> m_is_rebuilding{false};
|
||||
std::atomic<int> m_chunk_task_id{0};
|
||||
std::shared_ptr<NetworkClient> m_client;
|
||||
ChunkLoadStyle m_chunk_load_style{ChunkLoadStyle::CENTER};
|
||||
|
||||
std::atomic<std::shared_ptr<PriorityThreadPool>> m_thread_pool;
|
||||
|
||||
void client_run(std::stop_token token);
|
||||
|
||||
void set_player_pos();
|
||||
|
||||
void report_player_pos();
|
||||
|
||||
void set_block(const glm::ivec3& pos, unsigned id);
|
||||
|
||||
void update_chunk(const ChunkPosSet& old, const ChunkPosSet& now);
|
||||
};
|
||||
} // namespace Cubed
|
||||
@@ -1,10 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
// Prevent unsigned underflow issues in subtraction
|
||||
#include "Cubed/tools/cubed_assert.hpp"
|
||||
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
namespace Cubed {
|
||||
using TickType = long long;
|
||||
|
||||
constexpr int DEFAULT_PER_TICK_TIME = 50;
|
||||
|
||||
constexpr TickType DAY_TIME = 24000;
|
||||
|
||||
constexpr TickType PER_HOUR = 1000;
|
||||
constexpr TickType PER_HOUR = 1000;
|
||||
|
||||
class Timer {
|
||||
public:
|
||||
template <typename Fn>
|
||||
Timer(TickType threshold, Fn&& f)
|
||||
: m_fn(std::forward<Fn>(f)), m_threshold(threshold) {
|
||||
ASSERT_MSG(threshold > 0, "Threshold Must Rreater Than 0");
|
||||
}
|
||||
bool update() {
|
||||
if (++m_current >= m_threshold) {
|
||||
m_current = 0;
|
||||
m_fn();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
void reset() { m_current = 0; }
|
||||
|
||||
private:
|
||||
std::function<void()> m_fn;
|
||||
TickType m_threshold;
|
||||
TickType m_current = 0;
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
65
include/Cubed/gameplay/network_client.hpp
Normal file
65
include/Cubed/gameplay/network_client.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/gameplay/packet.hpp"
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
namespace Cubed {
|
||||
using asio::ip::tcp;
|
||||
class ClientWorld;
|
||||
class NetworkClient : public std::enable_shared_from_this<NetworkClient> {
|
||||
public:
|
||||
NetworkClient(ClientWorld& world);
|
||||
~NetworkClient();
|
||||
void close();
|
||||
void stop();
|
||||
void send(Packet packet, int priority = 10);
|
||||
void start(std::string ip, int port = 25530);
|
||||
bool is_connected() const;
|
||||
bool is_connect_error() const;
|
||||
|
||||
private:
|
||||
struct Task {
|
||||
int priority = 10;
|
||||
std::uint64_t sequence = 0;
|
||||
Packet packet;
|
||||
Task(int p, std::uint64_t seq, Packet pac)
|
||||
: priority(p), sequence(seq), packet(std::move(pac)) {}
|
||||
};
|
||||
|
||||
struct TaskCompare {
|
||||
bool operator()(const Task& a, const Task& b) const {
|
||||
|
||||
if (a.priority != b.priority) {
|
||||
return a.priority > b.priority;
|
||||
}
|
||||
|
||||
return a.sequence > b.sequence;
|
||||
}
|
||||
};
|
||||
|
||||
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::priority_queue<Task, std::vector<Task>, TaskCompare> m_write_queue;
|
||||
|
||||
asio::strand<asio::io_context::executor_type> m_strand;
|
||||
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;
|
||||
std::atomic_uint64_t m_sequence{0};
|
||||
|
||||
asio::awaitable<void> connect(std::string ip, int port);
|
||||
asio::awaitable<void> read_loop();
|
||||
|
||||
void do_write();
|
||||
};
|
||||
} // namespace Cubed
|
||||
33
include/Cubed/gameplay/network_server.hpp
Normal file
33
include/Cubed/gameplay/network_server.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/server_world.hpp"
|
||||
#include "Cubed/gameplay/session.hpp"
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <thread>
|
||||
namespace Cubed {
|
||||
|
||||
class NetworkServer {
|
||||
public:
|
||||
NetworkServer(int port = 25530);
|
||||
~NetworkServer();
|
||||
void stop();
|
||||
|
||||
// Run in another thread after initialization is complete
|
||||
void start_server(int port = 25530);
|
||||
|
||||
int port() const;
|
||||
ServerWorld& server_world();
|
||||
|
||||
private:
|
||||
asio::io_context m_io;
|
||||
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;
|
||||
asio::awaitable<void> listen();
|
||||
void net_run();
|
||||
};
|
||||
} // namespace Cubed
|
||||
216
include/Cubed/gameplay/packet.hpp
Normal file
216
include/Cubed/gameplay/packet.hpp
Normal file
@@ -0,0 +1,216 @@
|
||||
#pragma once
|
||||
#include "Cubed/tools/compression.hpp"
|
||||
#include "packet.pb.h" // IWYU pragma: keep
|
||||
|
||||
#include <concepts>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#ifdef _WIN32
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#include <winsock2.h>
|
||||
#else
|
||||
#include <netinet/in.h>
|
||||
#endif
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
namespace Cubed {
|
||||
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,
|
||||
};
|
||||
|
||||
inline CompressType get_compress_type(uint16_t id) {
|
||||
using enum CompressType;
|
||||
switch (id) {
|
||||
case std::to_underlying(NONE):
|
||||
return NONE;
|
||||
case std::to_underlying(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,
|
||||
BLOCK_CHANGE_RSP = 3004,
|
||||
S2C_CLEAR_ALL_CHUNKS = 3005,
|
||||
UPDATE_TIME = 3006,
|
||||
PING = 9001,
|
||||
PONG = 9002
|
||||
|
||||
};
|
||||
|
||||
template <typename> struct always_false : std::false_type {}; // NOLINT
|
||||
|
||||
template <typename T> constexpr uint16_t get_packet_id() {
|
||||
static_assert(always_false<T>::value, "Unknown Type");
|
||||
return 0;
|
||||
}
|
||||
|
||||
template <> constexpr uint16_t get_packet_id<LoginReq>() {
|
||||
return std::to_underlying(PacketEnum::LOGIN_REQ);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<LoginRsp>() {
|
||||
return std::to_underlying(PacketEnum::LOGIN_RSP);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<LogoutReq>() {
|
||||
return std::to_underlying(PacketEnum::LOGOUT_REQ);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<LogoutRsp>() {
|
||||
return std::to_underlying(PacketEnum::LOGOUT_RSP);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<PlayerInfo>() {
|
||||
return std::to_underlying(PacketEnum::PLAYER_INFO);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<PlayerPos>() {
|
||||
return std::to_underlying(PacketEnum::PLAYER_POS);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<PlayerInfoRsp>() {
|
||||
return std::to_underlying(PacketEnum::PLAYER_INFO_RSP);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<ChunkDataReq>() {
|
||||
return std::to_underlying(PacketEnum::CHUNK_DATA_REQ);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<ChunkDataRsp>() {
|
||||
return std::to_underlying(PacketEnum::CHUNK_DATA_RSP);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<BlockChangeReq>() {
|
||||
return std::to_underlying(PacketEnum::BLOCK_CHANGE_REQ);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<BlockChangeRsp>() {
|
||||
return std::to_underlying(PacketEnum::BLOCK_CHANGE_RSP);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<S2C_ClearAllChunks>() {
|
||||
return std::to_underlying(PacketEnum::S2C_CLEAR_ALL_CHUNKS);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<UpdateTime>() {
|
||||
return std::to_underlying(PacketEnum::UPDATE_TIME);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<Ping>() {
|
||||
return std::to_underlying(PacketEnum::PING);
|
||||
}
|
||||
template <> constexpr uint16_t get_packet_id<Pong>() {
|
||||
return std::to_underlying(PacketEnum::PONG);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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
|
||||
97
include/Cubed/gameplay/server_chunk.hpp
Normal file
97
include/Cubed/gameplay/server_chunk.hpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
#include "Cubed/constants.hpp"
|
||||
#include "Cubed/gameplay/biome.hpp"
|
||||
#include "Cubed/gameplay/block.hpp"
|
||||
#include "Cubed/gameplay/chunk_generator.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
namespace Cubed {
|
||||
class ServerWorld;
|
||||
class ServerChunk {
|
||||
public:
|
||||
ServerChunk(ServerWorld& world, ChunkPos chunk_pos,
|
||||
bool temp_chunk = false);
|
||||
ServerChunk(const ServerChunk&) = delete;
|
||||
ServerChunk(ServerChunk&&) noexcept;
|
||||
ServerChunk& operator=(const ServerChunk&) = delete;
|
||||
ServerChunk& operator=(ServerChunk&&) noexcept;
|
||||
|
||||
static std::tuple<int, int, int> world_to_block(int world_x, int world_y,
|
||||
int world_z, int chunk_x,
|
||||
int chunk_z);
|
||||
static std::tuple<int, int, int> world_to_block(const glm::ivec3& block_pos,
|
||||
ChunkPos chunk_pos);
|
||||
static std::tuple<int, int, int> block_to_world(int x, int y, int z,
|
||||
int chunk_x, int chunk_z);
|
||||
static std::tuple<int, int, int> block_to_world(const glm::ivec3& block_pos,
|
||||
ChunkPos chunk_pos);
|
||||
|
||||
void set_chunk_block(int index, unsigned id);
|
||||
// ensure thread safe!
|
||||
void gen_chunk();
|
||||
|
||||
BiomeType get_biome() const;
|
||||
ChunkPos get_chunk_pos() const;
|
||||
const std::vector<BlockType>& get_chunk_blocks() const;
|
||||
HeightMapArray get_heightmap() const;
|
||||
bool is_temp_chunk() const;
|
||||
ChunkPos chunk_pos() const;
|
||||
BiomeType biome() const;
|
||||
void biome(BiomeType b);
|
||||
HeightMapArray& heightmap();
|
||||
std::vector<BlockType>& blocks();
|
||||
ServerWorld& world();
|
||||
unsigned seed() const;
|
||||
BiomeConditions& conditions();
|
||||
bool& has_cave();
|
||||
const OptionalBlockVectorArray& get_neightbor_blocks() const;
|
||||
static int index(int x, int y, int z);
|
||||
static int index(const glm::vec3& pos);
|
||||
|
||||
private:
|
||||
static constexpr int SIZE_X = CHUNK_SIZE;
|
||||
static constexpr int SIZE_Y = WORLD_SIZE_Y;
|
||||
static constexpr int SIZE_Z = CHUNK_SIZE;
|
||||
|
||||
std::atomic<bool> m_gening{false};
|
||||
std::atomic<bool> m_temp_chunk{false};
|
||||
|
||||
bool m_has_cave{false};
|
||||
|
||||
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
|
||||
|
||||
ChunkPos m_chunk_pos;
|
||||
ServerWorld& m_world;
|
||||
HeightMapArray m_heightmap;
|
||||
// the index is a array of block id
|
||||
std::vector<BlockType> m_blocks;
|
||||
OptionalBlockVectorArray m_neightbor_blocks;
|
||||
float frequency = 0.01f;
|
||||
float height = 80;
|
||||
unsigned m_seed = 0;
|
||||
|
||||
BiomeConditions m_conditions;
|
||||
|
||||
std::unique_ptr<ChunkGenerator> m_generator;
|
||||
|
||||
// Init Chunk
|
||||
// Determine biome from temperature and humidity noise
|
||||
void gen_phase_one();
|
||||
|
||||
// Generate heightmap using biome-specific noise
|
||||
void gen_phase_two();
|
||||
|
||||
// Generate terrain blocks from heightmap and biome
|
||||
void gen_phase_three();
|
||||
// Blend surface blocks at chunk borders with neighbors
|
||||
void gen_phase_four(const std::array<std::optional<std::vector<BlockType>>,
|
||||
4>& neighbor_block);
|
||||
// Generate biome-specific vegetation/structures
|
||||
void gen_phase_five();
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
54
include/Cubed/gameplay/server_player.hpp
Normal file
54
include/Cubed/gameplay/server_player.hpp
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#include <atomic>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
namespace Cubed {
|
||||
class ServerWorld;
|
||||
class Session;
|
||||
class ServerPlayer {
|
||||
|
||||
public:
|
||||
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
|
||||
ServerPlayer(const ServerPlayer&) = delete;
|
||||
ServerPlayer(ServerPlayer&&) = delete;
|
||||
ServerPlayer& operator=(const ServerPlayer&) = delete;
|
||||
ServerPlayer& operator=(ServerPlayer&&) = delete;
|
||||
ServerPlayer(std::string_view name, std::string_view uuid,
|
||||
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;
|
||||
int task_id() const;
|
||||
void task_id(int id);
|
||||
bool has_player(ChunkPos pos) const;
|
||||
void update_chunk_set(const ChunkPosSet& set);
|
||||
const ChunkPosSet& get_chunk_pos_set() const;
|
||||
ChunkPosSet& get_chunk_pos_set();
|
||||
|
||||
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};
|
||||
std::atomic<int> m_chunk_task_id{0};
|
||||
mutable std::shared_mutex m_chunk_pos_mutex;
|
||||
ChunkPosSet m_player_chunk_pos_set;
|
||||
};
|
||||
} // namespace Cubed
|
||||
187
include/Cubed/gameplay/server_world.hpp
Normal file
187
include/Cubed/gameplay/server_world.hpp
Normal file
@@ -0,0 +1,187 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/gameplay/cave_carver.hpp"
|
||||
#include "Cubed/gameplay/chunk_pos.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/river_worm.hpp"
|
||||
#include "Cubed/gameplay/server_chunk.hpp"
|
||||
#include "Cubed/gameplay/server_player.hpp"
|
||||
#include "Cubed/tools/priority_thread_pool.hpp"
|
||||
#include "Cubed/tools/recent_queue.hpp"
|
||||
#include "Cubed/tools/thread_pool.hpp"
|
||||
#include "world/block_change.pb.h"
|
||||
|
||||
#include <absl/container/flat_hash_set.h>
|
||||
#include <shared_mutex>
|
||||
#include <tbb/concurrent_hash_map.h>
|
||||
#include <tbb/concurrent_queue.h>
|
||||
#include <tbb/concurrent_unordered_map.h>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
namespace Cubed {
|
||||
class Session;
|
||||
class ServerWorld {
|
||||
public:
|
||||
enum class ThreadPoolKind { NET, GEN };
|
||||
ServerWorld();
|
||||
~ServerWorld();
|
||||
void stop();
|
||||
void handle_player_exit(const std::string& uuid);
|
||||
void init_world();
|
||||
void need_gen(std::string uuid);
|
||||
void update();
|
||||
void hot_reload();
|
||||
|
||||
int rendering_distance() const;
|
||||
void rendering_distance(int rendering_distance);
|
||||
void start_gen_thread();
|
||||
void start_server_thread();
|
||||
|
||||
void stop_gen_thread();
|
||||
void stop_server_thread();
|
||||
|
||||
void stop_thread_pool();
|
||||
void start_thread_pool();
|
||||
|
||||
void serever_run(std::stop_token stoken);
|
||||
|
||||
CaveCarver& cave_carcer();
|
||||
RiverWorm& river_worm();
|
||||
|
||||
TickType game_tick() const;
|
||||
TickType day_tick() const;
|
||||
|
||||
void day_tick(TickType tick);
|
||||
|
||||
int per_tick_time() const;
|
||||
void per_tick_time(int ms);
|
||||
bool is_tick_running() const;
|
||||
void tick_running(bool run);
|
||||
|
||||
int gen_pool_threads() const;
|
||||
int max_threads() const;
|
||||
|
||||
void change_pool_threads(ThreadPoolKind kind, int threads);
|
||||
|
||||
int chunk_load_style() const;
|
||||
void set_chunk_load_style(int id);
|
||||
|
||||
bool set_block(const glm::ivec3& block_pos, unsigned id);
|
||||
|
||||
void sync_player_pos(const std::string& uuid, float x, float y, float z);
|
||||
void handle_player_login(const std::string& player_name,
|
||||
std::shared_ptr<Session> session);
|
||||
glm::vec3 get_player_pos(const std::string& uuid) const;
|
||||
|
||||
void handle_chunk_req(int task_id, const std::string& uuid, ChunkPos pos);
|
||||
void handle_block_change(const BlockChangeReq& req);
|
||||
|
||||
int chunk_size() const;
|
||||
template <typename Fn>
|
||||
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
|
||||
m_timers.emplace(std::piecewise_construct,
|
||||
std::forward_as_tuple(std::string(id)),
|
||||
std::forward_as_tuple(threshold, std::forward<Fn>(f)));
|
||||
}
|
||||
|
||||
private:
|
||||
enum class ChunkState { NONE, GENERATING, READY, PENDING_DELETE };
|
||||
struct ChunkEntity {
|
||||
ChunkState state;
|
||||
std::shared_ptr<ServerChunk> chunk;
|
||||
uint32_t ref_count = 0;
|
||||
};
|
||||
|
||||
enum class ChunkLoadStyle { RANDOM, CENTER };
|
||||
struct PendingRequest {
|
||||
std::string uuid;
|
||||
int task_id;
|
||||
ChunkPos pos;
|
||||
};
|
||||
struct PendingChunk {
|
||||
ChunkPos pos;
|
||||
std::unique_ptr<ServerChunk> chunk;
|
||||
};
|
||||
|
||||
using ChunkHashMap =
|
||||
tbb::concurrent_hash_map<ChunkPos, ChunkEntity, ChunkPos::TBBHash>;
|
||||
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
|
||||
using NewChunkVector = std::vector<PendingChunk>;
|
||||
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
|
||||
using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
|
||||
|
||||
using chunk_acc = ChunkHashMap::accessor;
|
||||
using chunk_caac = ChunkHashMap::const_accessor;
|
||||
|
||||
using uuid_acc = PlayerUUIDMap::accessor;
|
||||
using uuid_cacc = PlayerUUIDMap::const_accessor;
|
||||
// key = uuid
|
||||
PlayerHashMap m_players;
|
||||
ChunkHashMap m_chunks;
|
||||
|
||||
CaveCarver m_cave_carcer;
|
||||
RiverWorm m_river_worm;
|
||||
|
||||
std::jthread m_gen_thread;
|
||||
std::jthread m_server_thread;
|
||||
|
||||
std::atomic<bool> m_chunk_gen_finished{false};
|
||||
std::atomic<bool> m_could_gen{true};
|
||||
std::atomic<bool> m_gen_running{false};
|
||||
std::atomic<bool> m_need_gen_chunk{false};
|
||||
std::atomic<bool> m_init{false};
|
||||
std::atomic<bool> m_stopped{false};
|
||||
std::atomic<int> m_rendering_distance{24};
|
||||
std::atomic<int> m_gen_pool_threads{0};
|
||||
std::atomic<int> m_net_pool_threads{0};
|
||||
std::atomic<int> m_max_threads{1};
|
||||
|
||||
std::atomic<TickType> m_game_ticks{0};
|
||||
std::atomic<TickType> m_day_tick{6000};
|
||||
std::atomic<bool> m_tick_running{true};
|
||||
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
|
||||
|
||||
mutable std::shared_mutex m_player_mutex;
|
||||
std::mutex m_need_gen_queue_mutex;
|
||||
std::condition_variable_any m_gen_cv;
|
||||
|
||||
RecentQueue<std::string> m_need_gen_queue;
|
||||
|
||||
std::atomic<std::shared_ptr<PriorityThreadPool>> m_gen_thread_pool;
|
||||
std::atomic<std::shared_ptr<ThreadPool>> m_net_thread_pool;
|
||||
|
||||
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::CENTER};
|
||||
|
||||
PlayerUUIDMap m_uuid_to_name;
|
||||
|
||||
tbb::concurrent_unordered_map<std::string, Timer> m_timers;
|
||||
tbb::concurrent_queue<PendingRequest> m_waiting_chunk_requests;
|
||||
tbb::concurrent_queue<std::unique_ptr<ServerChunk>> m_finished_queue;
|
||||
|
||||
void init_chunks();
|
||||
|
||||
void gen_chunks_internal(const std::string& uuid);
|
||||
|
||||
void compute_required_chunks(ChunkPosSet& required_chunks,
|
||||
const std::optional<std::string>& uuid);
|
||||
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
|
||||
const ChunkPosSet&);
|
||||
void submit_new_chunks(const std::string& uuid, NewChunkVector& new_chunks);
|
||||
// void wait_all_chunk_tasks();
|
||||
|
||||
void update_ref_count(const ChunkPosSet& old, const ChunkPosSet& now);
|
||||
|
||||
void send_time();
|
||||
|
||||
void send_chunk(int task_id, const std::string& uuid, ChunkPos pos);
|
||||
|
||||
int
|
||||
change_pool_threads(std::atomic<std::shared_ptr<ThreadPool>>& thread_pool,
|
||||
int threads);
|
||||
int change_pool_threads(
|
||||
std::atomic<std::shared_ptr<PriorityThreadPool>>& thread_pool,
|
||||
int threads);
|
||||
void send_server_stop();
|
||||
};
|
||||
} // namespace Cubed
|
||||
60
include/Cubed/gameplay/session.hpp
Normal file
60
include/Cubed/gameplay/session.hpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/gameplay/packet.hpp"
|
||||
|
||||
#include <asio.hpp>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
namespace Cubed {
|
||||
|
||||
using asio::ip::tcp;
|
||||
class ServerWorld;
|
||||
class Session : public std::enable_shared_from_this<Session> {
|
||||
|
||||
public:
|
||||
Session(tcp::socket socket, ServerWorld& server_world,
|
||||
asio::io_context& io);
|
||||
~Session();
|
||||
void start();
|
||||
void send(Packet packet, int priority = 10);
|
||||
|
||||
void close();
|
||||
const std::string& uuid() const;
|
||||
|
||||
private:
|
||||
struct Task {
|
||||
int priority = 10;
|
||||
std::uint64_t sequence = 0;
|
||||
Packet packet;
|
||||
Task(int p, std::uint64_t seq, Packet pac)
|
||||
: priority(p), sequence(seq), packet(std::move(pac)) {}
|
||||
};
|
||||
|
||||
struct TaskCompare {
|
||||
bool operator()(const Task& a, const Task& b) const {
|
||||
|
||||
if (a.priority != b.priority) {
|
||||
return a.priority > b.priority;
|
||||
}
|
||||
|
||||
return a.sequence > b.sequence;
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr uint32_t MAX_PACKET_SIZE = 4 * 1024 * 1024;
|
||||
tcp::socket m_socket;
|
||||
std::vector<char> m_read_buffer;
|
||||
std::priority_queue<Task, std::vector<Task>, TaskCompare> m_write_queue;
|
||||
asio::strand<asio::io_context::executor_type> m_strand;
|
||||
std::string m_uuid;
|
||||
ServerWorld& m_server_world;
|
||||
std::atomic<bool> m_closed{false};
|
||||
|
||||
std::atomic_uint64_t m_sequence{0};
|
||||
|
||||
asio::awaitable<void> read_loop();
|
||||
|
||||
void do_write();
|
||||
};
|
||||
} // namespace Cubed
|
||||
@@ -4,13 +4,13 @@
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
class Chunk;
|
||||
class ServerChunk;
|
||||
|
||||
struct TreeStructNode {
|
||||
glm::ivec3 offset{0, 0, 0};
|
||||
unsigned id = 0;
|
||||
};
|
||||
|
||||
bool build_tree(Chunk& chunk, const glm::ivec3& pos);
|
||||
bool build_tree(ServerChunk& chunk, const glm::ivec3& pos);
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -5,14 +5,14 @@
|
||||
#include <glad/glad.h>
|
||||
#include <vector>
|
||||
namespace Cubed {
|
||||
class World;
|
||||
class ClientWorld;
|
||||
struct VertexData {
|
||||
std::vector<Vertex3D> m_vertices;
|
||||
GLuint m_vbo = 0;
|
||||
GLuint m_vao = 0;
|
||||
std::atomic<std::size_t> m_sum{0};
|
||||
World& m_world;
|
||||
VertexData(World& world);
|
||||
ClientWorld& m_world;
|
||||
VertexData(ClientWorld& world);
|
||||
~VertexData();
|
||||
VertexData(const VertexData&) = delete;
|
||||
VertexData(VertexData&&) noexcept;
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
#pragma once
|
||||
#include "Cubed/AABB.hpp"
|
||||
#include "Cubed/gameplay/cave_carver.hpp"
|
||||
#include "Cubed/gameplay/chunk.hpp"
|
||||
#include "Cubed/gameplay/game_time.hpp"
|
||||
#include "Cubed/gameplay/river_worm.hpp"
|
||||
#include "Cubed/tools/thread_pool.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <shared_mutex>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace Cubed {
|
||||
|
||||
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 Player;
|
||||
class TextureManager;
|
||||
class World {
|
||||
private:
|
||||
enum class ChunkLoadStyle { RANDOM, CENTER };
|
||||
|
||||
struct PendingChunk {
|
||||
Chunk chunk;
|
||||
std::future<void> future;
|
||||
};
|
||||
|
||||
using OptionalBlockVectorArray =
|
||||
std::array<std::optional<std::vector<BlockType>>, 4>;
|
||||
using ChunkPtrUpdateList = std::vector<std::pair<ChunkPos, Chunk*>>;
|
||||
using ChunkPairVector = std::vector<std::pair<ChunkPos, Chunk>>;
|
||||
using ChunkPairQueue = std::queue<std::pair<ChunkPos, Chunk>>;
|
||||
using ConstChunkMap =
|
||||
std::unordered_map<ChunkPos, const Chunk*, ChunkPos::Hash>;
|
||||
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
|
||||
using ChunkHashMap = std::unordered_map<ChunkPos, Chunk, ChunkPos::Hash>;
|
||||
using PendingChunkHashMap =
|
||||
std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>;
|
||||
glm::vec3 m_gen_player_pos{0.0f, 0.0f, 0.0f};
|
||||
ChunkHashMap m_chunks;
|
||||
std::unordered_map<std::size_t, Player> m_players;
|
||||
std::vector<glm::vec4> m_planes;
|
||||
|
||||
std::thread m_gen_thread;
|
||||
std::thread m_server_thread;
|
||||
std::atomic<std::shared_ptr<ThreadPool>> m_gen_thread_pool;
|
||||
std::stop_source m_server_stop_source;
|
||||
|
||||
std::atomic<int> m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms
|
||||
|
||||
std::atomic<TickType> m_day_tick = 6000;
|
||||
|
||||
mutable std::shared_mutex m_chunks_mutex;
|
||||
std::mutex m_gen_signal_mutex;
|
||||
std::mutex m_new_chunk_mutex;
|
||||
std::mutex m_delete_vbo_mutex;
|
||||
std::mutex m_delete_vao_mutex;
|
||||
std::mutex m_gen_player_pos_mutex;
|
||||
std::vector<GLuint> m_pending_delete_vbo;
|
||||
std::vector<GLuint> m_pending_delete_vao;
|
||||
std::condition_variable m_gen_cv;
|
||||
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_chunk_gen_finished{false};
|
||||
std::atomic<bool> m_could_gen{true};
|
||||
std::atomic<bool> m_tick_running{true};
|
||||
std::atomic<int> m_rendering_distance{24};
|
||||
std::atomic<int> m_pool_threads{0};
|
||||
std::atomic<int> m_max_threads{1};
|
||||
std::atomic<TickType> m_game_ticks{0};
|
||||
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::RANDOM};
|
||||
std::vector<ChunkPos> m_dirty_queue;
|
||||
std::vector<ChunkRenderSnapshot> m_render_snapshots;
|
||||
std::vector<std::pair<ChunkPos, Chunk>> m_new_finished_chunk;
|
||||
// Can only be used in the gen thread
|
||||
PendingChunkHashMap new_chunks;
|
||||
|
||||
CaveCarver m_cave_carcer;
|
||||
RiverWorm m_river_worm;
|
||||
void init_chunks();
|
||||
|
||||
void gen_chunks_internal();
|
||||
void sync_player_pos(glm::vec3& player_pos);
|
||||
void compute_required_chunks(ChunkPosSet& required_chunks);
|
||||
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
|
||||
const ChunkPosSet&);
|
||||
|
||||
void submit_new_chunks();
|
||||
void poll_finished_chunks();
|
||||
void wait_all_chunk_tasks();
|
||||
|
||||
public:
|
||||
World();
|
||||
~World();
|
||||
|
||||
bool can_move(const AABB& player_box) const;
|
||||
// const BlockRenderData& get_block_render_data(int x, int y ,int z);
|
||||
|
||||
const std::optional<LookBlock>&
|
||||
get_look_block_pos(const std::string& name) const;
|
||||
// const Chunk* get_chunk(const ChunkPos& pos) const;
|
||||
|
||||
Player& get_player(const std::string& name);
|
||||
void init_world();
|
||||
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;
|
||||
static ChunkPos get_chunk_pos(int world_x, int world_z);
|
||||
|
||||
void need_gen();
|
||||
|
||||
void set_block(const glm::ivec3& pos, unsigned id);
|
||||
void update(float delta_time);
|
||||
|
||||
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_gen_thread();
|
||||
void start_server_thread();
|
||||
void stop_gen_thread();
|
||||
void stop_server_thread();
|
||||
void stop_thread_pool();
|
||||
void start_thread_pool();
|
||||
void serever_run(std::stop_token stoken);
|
||||
|
||||
CaveCarver& cave_carcer();
|
||||
RiverWorm& river_worm();
|
||||
std::vector<glm::vec4>& planes();
|
||||
std::vector<ChunkRenderSnapshot>& render_snapshots();
|
||||
|
||||
glm::vec3 sunlight_dir() const;
|
||||
TickType game_tick() const;
|
||||
TickType day_tick() const;
|
||||
void day_tick(TickType tick);
|
||||
int per_tick_time() const;
|
||||
void per_tick_time(int ms);
|
||||
|
||||
bool is_tick_running() const;
|
||||
void tick_running(bool run);
|
||||
int pool_threads() const;
|
||||
int max_threads() const;
|
||||
void change_pool_threads(int threads);
|
||||
int chunk_load_style() const;
|
||||
void set_chunk_load_style(int id);
|
||||
ChunkInfo get_chunk_info(const glm::vec3& world_pos) const;
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
@@ -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
|
||||
|
||||
@@ -11,13 +11,13 @@ namespace Cubed {
|
||||
|
||||
class Camera;
|
||||
class TextureManager;
|
||||
class World;
|
||||
class ClientWorld;
|
||||
class DevPanel;
|
||||
class Renderer {
|
||||
public:
|
||||
constexpr static int NUM_VAO = 7;
|
||||
|
||||
Renderer(const Camera& camera, World& world,
|
||||
Renderer(const Camera& camera, ClientWorld& world,
|
||||
const TextureManager& texture_manager, DevPanel& dev_panel);
|
||||
~Renderer();
|
||||
void hot_reload();
|
||||
@@ -91,7 +91,7 @@ private:
|
||||
const Camera& m_camera;
|
||||
DevPanel& m_dev_panel;
|
||||
const TextureManager& m_texture_manager;
|
||||
World& m_world;
|
||||
ClientWorld& m_world;
|
||||
|
||||
bool m_discard_tranparent = true;
|
||||
bool m_shader_on = true;
|
||||
@@ -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
|
||||
@@ -9,7 +9,7 @@
|
||||
namespace Cubed {
|
||||
|
||||
namespace Logger {
|
||||
enum class Level { TRACE, DEBUG, INFO, ERROR, WARN };
|
||||
enum class Level { L_TRACE, L_DEBUG, L_INFO, L_ERROR, L_WARN };
|
||||
|
||||
template <typename... Args>
|
||||
inline void info(std::format_string<Args...> fmt, Args&&... args) {
|
||||
@@ -53,7 +53,7 @@ inline void log(Level level, std::source_location loc,
|
||||
std::chrono::system_clock::now());
|
||||
std::string msg = std::vformat(fmt.get(), std::make_format_args(args...));
|
||||
switch (level) {
|
||||
case Logger::Level::TRACE:
|
||||
case Logger::Level::L_TRACE:
|
||||
std::osyncstream(std::cout)
|
||||
<< "\033[1;34m"
|
||||
<< std::format("[TRACE][{:%Y-%m-%d %H:%M:%S}]", now_time) << "["
|
||||
@@ -61,20 +61,20 @@ inline void log(Level level, std::source_location loc,
|
||||
<< "[" << loc.function_name() << "]" << msg << "\033[0m"
|
||||
<< "\n";
|
||||
break;
|
||||
case Logger::Level::DEBUG:
|
||||
case Logger::Level::L_DEBUG:
|
||||
std::osyncstream(std::cout)
|
||||
<< "\033[1;34m"
|
||||
<< std::format("[DEBUG][{:%Y-%m-%d %H:%M:%S}]", now_time) << msg
|
||||
<< "\033[0m"
|
||||
<< "\n";
|
||||
break;
|
||||
case Logger::Level::INFO:
|
||||
case Logger::Level::L_INFO:
|
||||
info(fmt, std::forward<Args>(args)...);
|
||||
break;
|
||||
case Logger::Level::WARN:
|
||||
case Logger::Level::L_WARN:
|
||||
warn(fmt, std::forward<Args>(args)...);
|
||||
break;
|
||||
case Logger::Level::ERROR:
|
||||
case Logger::Level::L_ERROR:
|
||||
error(fmt, std::forward<Args>(args)...);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,108 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
namespace Cubed {
|
||||
|
||||
namespace Math {
|
||||
|
||||
void extract_frustum_planes(const glm::mat4& mvp_matrix,
|
||||
std::vector<glm::vec4>& planes);
|
||||
inline void extract_frustum_planes(const glm::mat4& mvp_matrix,
|
||||
std::vector<glm::vec4>& planes) {
|
||||
if (planes.size() != 6) {
|
||||
planes.resize(6);
|
||||
}
|
||||
|
||||
float smootherstep(float edge0, float edge1, float x);
|
||||
bool is_aabb_in_frustum(const glm::vec3& center, const glm::vec3& half_extents,
|
||||
const std::vector<glm::vec4>& planes);
|
||||
float deterministic_random(int x, int z, uint64_t seed);
|
||||
glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t);
|
||||
const float* m = glm::value_ptr(mvp_matrix);
|
||||
|
||||
// left plane
|
||||
planes[0] =
|
||||
glm::vec4(m[3] + m[0], m[7] + m[4], m[11] + m[8], m[15] + m[12]);
|
||||
// right plane
|
||||
planes[1] =
|
||||
glm::vec4(m[3] - m[0], m[7] - m[4], m[11] - m[8], m[15] - m[12]);
|
||||
// bottom plane
|
||||
planes[2] =
|
||||
glm::vec4(m[3] + m[1], m[7] + m[5], m[11] + m[9], m[15] + m[13]);
|
||||
// top plane
|
||||
planes[3] =
|
||||
glm::vec4(m[3] - m[1], m[7] - m[5], m[11] - m[9], m[15] - m[13]);
|
||||
// near plane
|
||||
planes[4] =
|
||||
glm::vec4(m[3] + m[2], m[7] + m[6], m[11] + m[10], m[15] + m[14]);
|
||||
// far plane
|
||||
planes[5] =
|
||||
glm::vec4(m[3] - m[2], m[7] - m[6], m[11] - m[10], m[15] - m[14]);
|
||||
|
||||
for (auto& p : planes) {
|
||||
p = glm::normalize(p);
|
||||
}
|
||||
}
|
||||
|
||||
inline float smootherstep(float edge0, float edge1, float x) {
|
||||
|
||||
x = std::clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
|
||||
|
||||
return x * x * x * (x * (6.0f * x - 15.0f) + 10.0f);
|
||||
}
|
||||
|
||||
inline bool is_aabb_in_frustum(const glm::vec3& center,
|
||||
const glm::vec3& half_extents,
|
||||
const std::vector<glm::vec4>& planes) {
|
||||
for (const auto& plane : planes) {
|
||||
// distance
|
||||
float d = glm::dot(glm::vec3(plane), center) + plane.w;
|
||||
float r = half_extents.x * std::abs(plane.x) +
|
||||
half_extents.y * std::abs(plane.y) +
|
||||
half_extents.z * std::abs(plane.z);
|
||||
if (d + r < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
inline float deterministic_random(int x, int z, uint64_t seed) {
|
||||
uint64_t h = seed;
|
||||
h = h * 6364136223846793005ULL + (uint64_t)x;
|
||||
h = h * 6364136223846793005ULL + (uint64_t)z;
|
||||
return (float)(h >> 40) / (float)(1 << 24);
|
||||
}
|
||||
|
||||
inline glm::vec3 slerp(const glm::vec3& from, const glm::vec3& to, float t) {
|
||||
|
||||
float cos_theta = glm::clamp(glm::dot(from, to), -1.0f, 1.0f);
|
||||
|
||||
if (cos_theta > 0.9995f) {
|
||||
return glm::normalize(glm::mix(from, to, t));
|
||||
}
|
||||
|
||||
if (cos_theta < -0.9995f) {
|
||||
|
||||
glm::vec3 axis = (std::fabs(from.x) < 0.9f)
|
||||
? glm::vec3(1.0f, 0.0f, 0.0f)
|
||||
: glm::vec3(0.0f, 1.0f, 0.0f);
|
||||
glm::vec3 ortho = glm::normalize(glm::cross(from, axis));
|
||||
|
||||
float angle = glm::pi<float>() * t;
|
||||
|
||||
glm::vec3 rotated =
|
||||
from * std::cos(angle) + glm::cross(ortho, from) * std::sin(angle);
|
||||
|
||||
return glm::normalize(rotated);
|
||||
}
|
||||
|
||||
float theta = std::acos(cos_theta);
|
||||
float sin_theta = std::sin(theta);
|
||||
|
||||
float a = std::sin((1.0f - t) * theta) / sin_theta;
|
||||
float b = std::sin(t * theta) / sin_theta;
|
||||
|
||||
return glm::normalize(a * from + b * to);
|
||||
}
|
||||
|
||||
inline float distance2(const glm::vec3& a, const glm::vec3& b) {
|
||||
glm::vec3 diff = a - b;
|
||||
return glm::dot(diff, diff);
|
||||
}
|
||||
|
||||
} // namespace Math
|
||||
|
||||
|
||||
151
include/Cubed/tools/priority_thread_pool.hpp
Normal file
151
include/Cubed/tools/priority_thread_pool.hpp
Normal file
@@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
namespace Cubed {
|
||||
class PriorityThreadPool {
|
||||
private:
|
||||
struct Task {
|
||||
int priority = 10;
|
||||
std::uint64_t sequence;
|
||||
std::function<void()> task;
|
||||
Task(int p, std::uint64_t seq, std::function<void()> t)
|
||||
: priority(p), sequence(seq), task(std::move(t)) {}
|
||||
};
|
||||
|
||||
struct TaskCompare {
|
||||
bool operator()(const Task& a, const Task& b) const {
|
||||
|
||||
if (a.priority != b.priority) {
|
||||
return a.priority > b.priority;
|
||||
}
|
||||
|
||||
return a.sequence > b.sequence;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<std::jthread> m_workers;
|
||||
std::priority_queue<Task, std::vector<Task>, TaskCompare> m_tasks;
|
||||
std::mutex m_mtx;
|
||||
std::condition_variable_any m_cv;
|
||||
std::atomic<bool> m_stopping{false};
|
||||
std::atomic<size_t> m_thread_sum{0};
|
||||
std::atomic_uint64_t m_sequence{0};
|
||||
|
||||
public:
|
||||
PriorityThreadPool(const PriorityThreadPool&) = delete;
|
||||
PriorityThreadPool(PriorityThreadPool&&) = delete;
|
||||
PriorityThreadPool& operator=(const PriorityThreadPool&) = delete;
|
||||
PriorityThreadPool& operator=(PriorityThreadPool&&) = delete;
|
||||
explicit PriorityThreadPool(size_t thread_sum) : m_thread_sum(thread_sum) {
|
||||
for (size_t i = 0; i < thread_sum; i++) {
|
||||
m_workers.emplace_back([this](std::stop_token stoken) {
|
||||
while (true) {
|
||||
std::function<void()> task;
|
||||
{
|
||||
std::unique_lock lock(m_mtx);
|
||||
m_cv.wait(lock, stoken,
|
||||
[this] { return !m_tasks.empty(); });
|
||||
if (stoken.stop_requested() && m_tasks.empty()) {
|
||||
return;
|
||||
}
|
||||
task = std::move(m_tasks.top().task);
|
||||
m_tasks.pop();
|
||||
}
|
||||
task();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
~PriorityThreadPool() { stop(); }
|
||||
template <typename F> auto enqueue(int priority, F&& f) {
|
||||
|
||||
using R = std::invoke_result_t<F>;
|
||||
|
||||
auto task =
|
||||
std::make_shared<std::packaged_task<R()>>(std::forward<F>(f));
|
||||
auto fut = task->get_future();
|
||||
|
||||
{
|
||||
std::lock_guard lock(m_mtx);
|
||||
if (m_stopping)
|
||||
throw std::runtime_error("thread pool stopped");
|
||||
m_tasks.emplace(priority, m_sequence++, [task] { (*task)(); });
|
||||
}
|
||||
m_cv.notify_one();
|
||||
return fut;
|
||||
}
|
||||
|
||||
template <typename F> auto enqueue(F&& f) {
|
||||
return enqueue(10, std::forward<F>(f));
|
||||
}
|
||||
|
||||
void stop() {
|
||||
if (m_stopping.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& w : m_workers) {
|
||||
w.request_stop();
|
||||
}
|
||||
|
||||
m_cv.notify_all();
|
||||
|
||||
for (auto& w : m_workers) {
|
||||
if (w.joinable()) {
|
||||
w.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
size_t thread_sum() const { return m_thread_sum.load(); }
|
||||
};
|
||||
|
||||
template <std::random_access_iterator Iter, typename F>
|
||||
void parallel_do(PriorityThreadPool& pool, Iter first, Iter last,
|
||||
size_t max_threads, F&& f) {
|
||||
max_threads = std::max<size_t>(1, max_threads);
|
||||
max_threads = std::min(max_threads, pool.thread_sum());
|
||||
std::decay_t<F> fn(std::forward<F>(f));
|
||||
size_t length = std::distance(first, last);
|
||||
if (!length) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr size_t MIN_PER_THREAD = 25;
|
||||
size_t num_blocks =
|
||||
std::min(max_threads, (length + MIN_PER_THREAD - 1) / MIN_PER_THREAD);
|
||||
num_blocks = std::max<size_t>(1, num_blocks);
|
||||
size_t block_size = (length + num_blocks - 1) / num_blocks;
|
||||
|
||||
std::vector<std::future<void>> futures;
|
||||
futures.reserve(num_blocks - 1);
|
||||
Iter block_start = first;
|
||||
for (size_t i = 0; i < num_blocks - 1; ++i) {
|
||||
Iter block_end = block_start;
|
||||
auto remain = std::distance(block_start, last);
|
||||
std::advance(block_end, std::min<size_t>(block_size, remain));
|
||||
|
||||
futures.emplace_back(pool.enqueue([block_start, block_end, &fn]() {
|
||||
for (auto it = block_start; it != block_end; ++it) {
|
||||
fn(*it);
|
||||
}
|
||||
}));
|
||||
|
||||
block_start = block_end;
|
||||
}
|
||||
for (auto it = block_start; it != last; ++it) {
|
||||
fn(*it);
|
||||
}
|
||||
|
||||
for (auto& fut : futures) {
|
||||
fut.get();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
55
include/Cubed/tools/recent_queue.hpp
Normal file
55
include/Cubed/tools/recent_queue.hpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
namespace Cubed {
|
||||
template <typename T> class RecentQueue {
|
||||
private:
|
||||
std::list<T> m_list;
|
||||
std::unordered_map<T, typename std::list<T>::iterator> m_map;
|
||||
|
||||
public:
|
||||
void enqueue(T key) {
|
||||
auto it = m_map.find(key);
|
||||
if (it != m_map.end()) {
|
||||
m_list.splice(m_list.end(), m_list, it->second);
|
||||
return;
|
||||
}
|
||||
m_list.emplace_back(std::move(key));
|
||||
auto iter = std::prev(m_list.end());
|
||||
m_map.emplace(*iter, iter);
|
||||
}
|
||||
|
||||
void pop() {
|
||||
if (m_list.empty()) {
|
||||
return;
|
||||
}
|
||||
m_map.erase(m_list.front());
|
||||
m_list.pop_front();
|
||||
}
|
||||
void clear() {
|
||||
m_list.clear();
|
||||
m_map.clear();
|
||||
}
|
||||
|
||||
const T& front() const {
|
||||
assert(!empty());
|
||||
return m_list.front();
|
||||
}
|
||||
const T& back() const {
|
||||
assert(!empty());
|
||||
return m_list.back();
|
||||
}
|
||||
[[nodiscard]]
|
||||
bool empty() const {
|
||||
return m_list.empty();
|
||||
}
|
||||
[[nodiscard]]
|
||||
size_t size() const {
|
||||
return m_list.size();
|
||||
}
|
||||
[[nodiscard]]
|
||||
bool contains(const T& key) const {
|
||||
return m_map.find(key) != m_map.end();
|
||||
}
|
||||
};
|
||||
} // namespace Cubed
|
||||
@@ -31,7 +31,7 @@ public:
|
||||
{
|
||||
std::unique_lock lock(m_mtx);
|
||||
m_cv.wait(lock, stoken,
|
||||
[this, stoken] { return !m_tasks.empty(); });
|
||||
[this] { return !m_tasks.empty(); });
|
||||
if (stoken.stop_requested() && m_tasks.empty()) {
|
||||
return;
|
||||
}
|
||||
@@ -62,7 +62,9 @@ public:
|
||||
return fut;
|
||||
}
|
||||
void stop() {
|
||||
m_stopping = true;
|
||||
if (m_stopping.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
for (auto& w : m_workers) {
|
||||
w.request_stop();
|
||||
}
|
||||
|
||||
33
include/Cubed/tools/toml.utils.hpp
Normal file
33
include/Cubed/tools/toml.utils.hpp
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "Cubed/tools/log.hpp"
|
||||
|
||||
#include <toml++/toml.hpp>
|
||||
namespace Cubed {
|
||||
namespace TOML {
|
||||
|
||||
template <typename T>
|
||||
concept TomlValueType =
|
||||
std::same_as<std::decay_t<T>, int> || std::same_as<std::decay_t<T>, bool> ||
|
||||
std::same_as<std::decay_t<T>, double> ||
|
||||
std::same_as<std::decay_t<T>, char> ||
|
||||
std::same_as<std::decay_t<T>, toml::date> ||
|
||||
std::same_as<std::decay_t<T>, toml::time> ||
|
||||
std::same_as<std::decay_t<T>, toml::date_time> ||
|
||||
std::same_as<std::decay_t<T>, std::string>;
|
||||
|
||||
template <TomlValueType T>
|
||||
std::optional<T> safe_get_value(const toml::table& table, std::string_view key,
|
||||
const T& default_value) {
|
||||
auto value = table[key].value<T>();
|
||||
if (value == std::nullopt) {
|
||||
Logger::warn("Key {} Is Not Find, Wiil Set the Default Value {}", key,
|
||||
default_value);
|
||||
value = default_value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace TOML
|
||||
|
||||
} // namespace Cubed
|
||||
42
include/Cubed/tools/uuid.hpp
Normal file
42
include/Cubed/tools/uuid.hpp
Normal file
@@ -0,0 +1,42 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
namespace Cubed {
|
||||
inline std::string generate_uuid() {
|
||||
|
||||
static std::mt19937_64 rng(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count() ^
|
||||
(std::random_device{}()));
|
||||
std::uniform_int_distribution<uint64_t> dist(0, UINT64_MAX);
|
||||
|
||||
uint64_t a = dist(rng);
|
||||
uint64_t b = dist(rng);
|
||||
|
||||
std::array<uint8_t, 16> bytes{};
|
||||
for (int i = 0; i < 8; ++i) {
|
||||
bytes[i] = (a >> (56 - 8 * i)) & 0xFF;
|
||||
bytes[8 + i] = (b >> (56 - 8 * i)) & 0xFF;
|
||||
}
|
||||
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x40;
|
||||
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << std::hex << std::setfill('0');
|
||||
for (size_t i = 0; i < bytes.size(); ++i) {
|
||||
if (i == 4 || i == 6 || i == 8 || i == 10) {
|
||||
ss << '-';
|
||||
}
|
||||
ss << std::setw(2) << static_cast<int>(bytes[i]);
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
} // 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;
|
||||
|
||||
Reference in New Issue
Block a user