From 27e3cd6851311f17dab6bfe0e13340514b64d05b Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Tue, 23 Jun 2026 14:49:16 +0800 Subject: [PATCH] refactor(gameplay): split Chunk into server/client variants and add networking --- CMakeLists.txt | 10 +- include/Cubed/constants.hpp | 4 - include/Cubed/gameplay/block.hpp | 3 + include/Cubed/gameplay/cave_carver.hpp | 1 + include/Cubed/gameplay/chunk_generator.hpp | 10 +- include/Cubed/gameplay/chunk_pos.hpp | 20 ++ include/Cubed/gameplay/client_chunk.hpp | 0 include/Cubed/gameplay/client_player.hpp | 0 include/Cubed/gameplay/client_world.hpp | 0 include/Cubed/gameplay/network_client.hpp | 0 include/Cubed/gameplay/network_server.hpp | 20 ++ include/Cubed/gameplay/server_chunk.hpp | 96 +++++ include/Cubed/gameplay/server_player.hpp | 4 + include/Cubed/gameplay/server_world.hpp | 126 +++++++ src/gameplay/chunk.cpp | 2 +- src/gameplay/chunk_generator.cpp | 8 +- src/gameplay/client_chunk.cpp | 0 src/gameplay/client_player.cpp | 0 src/gameplay/client_world.cpp | 0 src/gameplay/network_client.cpp | 0 src/gameplay/network_server.cpp | 38 ++ src/gameplay/server_chunk.cpp | 193 ++++++++++ src/gameplay/server_player.cpp | 0 src/gameplay/server_world.cpp | 394 +++++++++++++++++++++ 24 files changed, 914 insertions(+), 15 deletions(-) create mode 100644 include/Cubed/gameplay/client_chunk.hpp create mode 100644 include/Cubed/gameplay/client_player.hpp create mode 100644 include/Cubed/gameplay/client_world.hpp create mode 100644 include/Cubed/gameplay/network_client.hpp create mode 100644 include/Cubed/gameplay/network_server.hpp create mode 100644 include/Cubed/gameplay/server_chunk.hpp create mode 100644 include/Cubed/gameplay/server_player.hpp create mode 100644 include/Cubed/gameplay/server_world.hpp create mode 100644 src/gameplay/client_chunk.cpp create mode 100644 src/gameplay/client_player.cpp create mode 100644 src/gameplay/client_world.cpp create mode 100644 src/gameplay/network_client.cpp create mode 100644 src/gameplay/network_server.cpp create mode 100644 src/gameplay/server_chunk.cpp create mode 100644 src/gameplay/server_player.cpp create mode 100644 src/gameplay/server_world.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e5fb2db..328130f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,8 @@ if (UNIX AND NOT APPLE) find_package(glfw3 REQUIRED) endif() -add_library(glad STATIC third_party/glad/src/glad.c) +add_library(glad STATIC third_party/glad/src/glad.c + src/gameplay/network_client.cpp) target_include_directories(glad PUBLIC third_party/glad/include) include(FetchContent) @@ -139,6 +140,13 @@ add_executable(${PROJECT_NAME} src/block.cpp src/gameplay/vertex_data.cpp src/gameplay/builders/ocean_builder.cpp + src/gameplay/network_server.cpp + src/gameplay/server_world.cpp + src/gameplay/client_world.cpp + src/gameplay/server_chunk.cpp + src/gameplay/client_chunk.cpp + src/gameplay/server_player.cpp + src/gameplay/client_player.cpp ) if(CMAKE_BUILD_TYPE STREQUAL "Debug") diff --git a/include/Cubed/constants.hpp b/include/Cubed/constants.hpp index f1e0f23..b30794b 100644 --- a/include/Cubed/constants.hpp +++ b/include/Cubed/constants.hpp @@ -1,5 +1,4 @@ #pragma once -#include "Cubed/gameplay/chunk_pos.hpp" #include namespace Cubed { @@ -30,9 +29,6 @@ constexpr int RESERVED_THREADS = 3; 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, CHUNK_SIZE>; } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/block.hpp b/include/Cubed/gameplay/block.hpp index d859711..5912211 100644 --- a/include/Cubed/gameplay/block.hpp +++ b/include/Cubed/gameplay/block.hpp @@ -2,12 +2,15 @@ #include #include +#include #include #include namespace Cubed { using BlockType = uint8_t; +using OptionalBlockVectorArray = + std::array>, 4>; struct BlockTexture { std::string name; diff --git a/include/Cubed/gameplay/cave_carver.hpp b/include/Cubed/gameplay/cave_carver.hpp index b5bc764..5120c4a 100644 --- a/include/Cubed/gameplay/cave_carver.hpp +++ b/include/Cubed/gameplay/cave_carver.hpp @@ -1,5 +1,6 @@ #pragma once #include "Cubed/constants.hpp" +#include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/path.hpp" #include diff --git a/include/Cubed/gameplay/chunk_generator.hpp b/include/Cubed/gameplay/chunk_generator.hpp index 2cf5d04..2fce476 100644 --- a/include/Cubed/gameplay/chunk_generator.hpp +++ b/include/Cubed/gameplay/chunk_generator.hpp @@ -11,11 +11,11 @@ #include 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& adj_chunks); + const std::array& 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& neighbor_biome() const; void ocean_build(); @@ -53,7 +53,7 @@ private: static inline std::atomic is_init{false}; static inline unsigned m_generator_seed{0}; static inline std::atomic is_seed_change{false}; - Chunk& m_chunk; + ServerChunk& m_chunk; Random m_random; std::unique_ptr m_biome_builder{nullptr}; bool is_cur_chunk_ins = false; diff --git a/include/Cubed/gameplay/chunk_pos.hpp b/include/Cubed/gameplay/chunk_pos.hpp index b89026f..8b1bef1 100644 --- a/include/Cubed/gameplay/chunk_pos.hpp +++ b/include/Cubed/gameplay/chunk_pos.hpp @@ -1,5 +1,7 @@ #pragma once +#include "Cubed/constants.hpp" + #include namespace Cubed { @@ -34,5 +36,23 @@ 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}; +} } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/client_chunk.hpp b/include/Cubed/gameplay/client_chunk.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/Cubed/gameplay/client_player.hpp b/include/Cubed/gameplay/client_player.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/Cubed/gameplay/network_client.hpp b/include/Cubed/gameplay/network_client.hpp new file mode 100644 index 0000000..e69de29 diff --git a/include/Cubed/gameplay/network_server.hpp b/include/Cubed/gameplay/network_server.hpp new file mode 100644 index 0000000..b40da77 --- /dev/null +++ b/include/Cubed/gameplay/network_server.hpp @@ -0,0 +1,20 @@ +#pragma once +#include +#include +namespace Cubed { + +class NetworkServer { +public: + NetworkServer(int port = 25530); + ~NetworkServer(); + void stop(); + void run(); + int port() const; + +private: + asio::io_context m_io; + std::thread m_server; + int m_port = 25530; + asio::awaitable listen(); +}; +} // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/gameplay/server_chunk.hpp b/include/Cubed/gameplay/server_chunk.hpp new file mode 100644 index 0000000..df996e9 --- /dev/null +++ b/include/Cubed/gameplay/server_chunk.hpp @@ -0,0 +1,96 @@ +#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 +#include +#include +#include +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 world_to_block(int world_x, int world_y, + int world_z, int chunk_x, + int chunk_z); + static std::tuple world_to_block(const glm::ivec3& block_pos, + ChunkPos chunk_pos); + static std::tuple block_to_world(int x, int y, int z, + int chunk_x, int chunk_z); + static std::tuple 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& 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& blocks(); + ServerWorld& world(); + unsigned seed() const; + BiomeConditions& conditions(); + bool& has_cave(); + + 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 m_gening{false}; + std::atomic m_temp_chunk{false}; + + bool m_has_cave{false}; + + std::atomic m_biome = BiomeType::PLAIN; + + ChunkPos m_chunk_pos; + ServerWorld& m_world; + HeightMapArray m_heightmap; + // the index is a array of block id + std::vector m_blocks; + + float frequency = 0.01f; + float height = 80; + unsigned m_seed = 0; + + BiomeConditions m_conditions; + + std::unique_ptr 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>, + 4>& neighbor_block); + // Generate biome-specific vegetation/structures + void gen_phase_five(); +}; +} // namespace Cubed diff --git a/include/Cubed/gameplay/server_player.hpp b/include/Cubed/gameplay/server_player.hpp new file mode 100644 index 0000000..60579b8 --- /dev/null +++ b/include/Cubed/gameplay/server_player.hpp @@ -0,0 +1,4 @@ +#pragma once +namespace Cubed { +class ServerPlayer {}; +} // namespace Cubed diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp new file mode 100644 index 0000000..f414cbc --- /dev/null +++ b/include/Cubed/gameplay/server_world.hpp @@ -0,0 +1,126 @@ +#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/thread_pool.hpp" + +#include +#include +#include +#include +#include +namespace Cubed { +class ServerWorld { +public: + ServerWorld(); + ~ServerWorld(); + void player_join(); + void init_world(); + void need_gen(); + void update(); + 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(); + + 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); + + void set_block(const glm::ivec3& block_pos, unsigned id); + +private: + enum class ChunkLoadStyle { RANDOM, CENTER }; + struct PendingChunk { + ServerChunk chunk; + std::future future; + }; + using ChunkHashMap = + std::unordered_map; + using PlayerHashMap = std::unordered_map; + using PendingChunkHashMap = + std::unordered_map; + using ChunkPosSet = std::unordered_set; + PlayerHashMap m_players; + ChunkHashMap m_chunks; + // Can only be used in the gen thread + PendingChunkHashMap new_chunks; + std::vector> m_new_finished_chunk; + + CaveCarver m_cave_carcer; + RiverWorm m_river_worm; + + std::thread m_gen_thread; + std::thread m_server_thread; + + std::stop_source m_server_stop_source; + + std::atomic m_chunk_gen_finished{false}; + std::atomic m_could_gen{true}; + std::atomic m_gen_running{false}; + std::atomic m_need_gen_chunk{false}; + std::atomic m_is_rebuilding{false}; + std::atomic m_rendering_distance{24}; + + std::atomic m_pool_threads{0}; + std::atomic m_max_threads{1}; + + std::atomic m_game_ticks{0}; + std::atomic m_day_tick{6000}; + std::atomic m_tick_running{true}; + std::atomic m_per_tick_time = DEFAULT_PER_TICK_TIME; // ms + + std::shared_mutex m_chunks_mutex; + std::shared_mutex m_new_chunk_mutex; + std::mutex m_gen_signal_mutex; + std::condition_variable m_gen_cv; + + std::atomic> m_gen_thread_pool; + + std::atomic m_chunk_load_style{ChunkLoadStyle::RANDOM}; + + void init_chunks(); + + void gen_chunks_internal(); + + void compute_required_chunks(ChunkPosSet& required_chunks); + void sync_and_collect_missing_chunks(std::vector&, + const ChunkPosSet&); + void submit_new_chunks(); + void poll_finished_chunks(); + void wait_all_chunk_tasks(); + + void sync_player_pos(glm::vec3& pos); +}; +} // namespace Cubed diff --git a/src/gameplay/chunk.cpp b/src/gameplay/chunk.cpp index 7710e66..3d1e484 100644 --- a/src/gameplay/chunk.cpp +++ b/src/gameplay/chunk.cpp @@ -246,7 +246,7 @@ size_t Chunk::get_water_vertices_sum() const { } void Chunk::gen_phase_one() { - m_generator = std::make_unique(*this); + // m_generator = std::make_unique(*this); if (!m_generator) { Logger::error("ChunkGenerator is Nullptr"); return; diff --git a/src/gameplay/chunk_generator.cpp b/src/gameplay/chunk_generator.cpp index 0868a13..c3c5fb8 100644 --- a/src/gameplay/chunk_generator.cpp +++ b/src/gameplay/chunk_generator.cpp @@ -10,8 +10,8 @@ #include "Cubed/gameplay/cave_path.hpp" #include "Cubed/gameplay/chunk.hpp" #include "Cubed/gameplay/river.path.hpp" +#include "Cubed/gameplay/server_world.hpp" #include "Cubed/gameplay/tree.hpp" -#include "Cubed/gameplay/world.hpp" #include "Cubed/tools/cubed_assert.hpp" #include "Cubed/tools/cubed_hash.hpp" #include "Cubed/tools/math_tools.hpp" @@ -99,7 +99,7 @@ void carve_worm(const std::vector& points, const ChunkPos& chunk_pos, using enum BiomeType; constexpr int BLEND_RADIUS = 8; -ChunkGenerator::ChunkGenerator(Chunk& chunk) : m_chunk(chunk) { +ChunkGenerator::ChunkGenerator(ServerChunk& chunk) : m_chunk(chunk) { ASSERT_MSG(is_init, "ChunksGenerator is not init"); ChunkPos pos = m_chunk.get_chunk_pos(); unsigned seed = HASH::chunk_seed_hash(pos.x, pos.z, m_generator_seed); @@ -157,7 +157,7 @@ void ChunkGenerator::assign_chunk_biome() { } void ChunkGenerator::resolve_biome_adjacency_conflict( - const std::array& adj_chunks) { + const std::array& adj_chunks) { auto m_biome = m_chunk.biome(); for (int i = 0; i < 8; i++) { auto& chunk = adj_chunks[i]; @@ -799,7 +799,7 @@ void ChunkGenerator::generate_river() { } } -Chunk& ChunkGenerator::chunk() { return m_chunk; } +ServerChunk& ChunkGenerator::chunk() { return m_chunk; } Random& ChunkGenerator::random() { return m_random; } const std::array& ChunkGenerator::neighbor_biome() const { diff --git a/src/gameplay/client_chunk.cpp b/src/gameplay/client_chunk.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/gameplay/client_player.cpp b/src/gameplay/client_player.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/gameplay/client_world.cpp b/src/gameplay/client_world.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/gameplay/network_client.cpp b/src/gameplay/network_client.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/gameplay/network_server.cpp b/src/gameplay/network_server.cpp new file mode 100644 index 0000000..87ac618 --- /dev/null +++ b/src/gameplay/network_server.cpp @@ -0,0 +1,38 @@ +#include "Cubed/gameplay/network_server.hpp" + +#include "Cubed/tools/log.hpp" +using asio::ip::tcp; +namespace Cubed { + +NetworkServer::NetworkServer(int port) : m_port(port) {} + +NetworkServer::~NetworkServer() { stop(); } + +void NetworkServer::stop() { + m_io.stop(); + if (m_server.joinable()) { + m_server.join(); + } + Logger::info("Server Stopped!"); +} + +asio::awaitable NetworkServer::listen() { + tcp::acceptor acceptor(m_io, tcp::endpoint(tcp::v4(), m_port)); + while (true) { + + tcp::socket socket = + co_await acceptor.async_accept(asio::use_awaitable); + } +} + +void NetworkServer::run() { + m_server = std::thread([this]() { + asio::co_spawn(m_io, listen(), asio::detached); + m_io.run(); + }); + Logger::info("Server Started!"); +} + +int NetworkServer::port() const { return m_port; } + +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/server_chunk.cpp b/src/gameplay/server_chunk.cpp new file mode 100644 index 0000000..ddd40e0 --- /dev/null +++ b/src/gameplay/server_chunk.cpp @@ -0,0 +1,193 @@ +#include "Cubed/gameplay/server_chunk.hpp" + +#include "Cubed/tools/cubed_assert.hpp" + +namespace Cubed { +ServerChunk::ServerChunk(ServerWorld& world, ChunkPos chunk_pos, + bool temp_chunk) + : m_temp_chunk(temp_chunk), m_chunk_pos(chunk_pos), m_world(world) {} + +ServerChunk::ServerChunk(ServerChunk&& other) noexcept + : m_biome(other.m_biome.load()), m_chunk_pos(std::move(other.m_chunk_pos)), + m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)), + m_blocks(std::move(other.m_blocks)), m_seed(other.m_seed), + m_conditions(other.m_conditions) {} + +ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept { + // Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&& + // other) this {}", other.m_chunk_pos.x, other.m_chunk_pos.z, + // static_cast(&other)); + m_chunk_pos = std::move(other.m_chunk_pos); + m_heightmap = std::move(other.m_heightmap); + m_blocks = std::move(other.m_blocks); + m_biome = other.m_biome.load(); + m_seed = other.m_seed; + m_conditions = other.m_conditions; + return *this; +} + +std::tuple ServerChunk::world_to_block(int world_x, int world_y, + int world_z, int chunk_x, + int chunk_z) { + int x, y, z; + y = world_y; + x = world_x - chunk_x * CHUNK_SIZE; + z = world_z - chunk_z * CHUNK_SIZE; + return {x, y, z}; +} + +std::tuple +ServerChunk::world_to_block(const glm::ivec3& block_pos, ChunkPos chunk_pos) { + return world_to_block(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x, + chunk_pos.z); +} + +std::tuple +ServerChunk::block_to_world(int x, int y, int z, int chunk_x, int chunk_z) { + int world_x = x + chunk_x * CHUNK_SIZE; + int world_z = z + chunk_z * CHUNK_SIZE; + int world_y = y; + return {world_x, world_y, world_z}; +} +std::tuple +ServerChunk::block_to_world(const glm::ivec3& block_pos, ChunkPos chunk_pos) { + return block_to_world(block_pos.x, block_pos.y, block_pos.z, chunk_pos.x, + chunk_pos.z); +} + +BiomeType ServerChunk::get_biome() const { return m_biome.load(); } + +ChunkPos ServerChunk::get_chunk_pos() const { return m_chunk_pos; } + +const std::vector& ServerChunk::get_chunk_blocks() const { + return m_blocks; +} + +HeightMapArray ServerChunk::get_heightmap() const { + // Logger::info("Chunk pos {} {} in get_heightmap this {}", m_chunk_pos.x, + // m_chunk_pos.z, static_cast(this)); + return m_heightmap; +} + +int ServerChunk::index(int x, int y, int z) { + ASSERT(!(x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE)); + if ((x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z < 0 || + (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z >= + CHUNK_SIZE * CHUNK_SIZE * WORLD_SIZE_Y) { + Logger::error("block pos x {} y {} z {} range error", x, y, z); + ASSERT(0); + } + return (x * WORLD_SIZE_Y + y) * CHUNK_SIZE + z; +} + +int ServerChunk::index(const glm::vec3& pos) { + return ServerChunk::index(pos.x, pos.y, pos.z); +} + +void ServerChunk::gen_phase_one() { + m_generator = std::make_unique(*this); + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + m_generator->assign_chunk_biome(); + m_seed = m_generator->chunk_seed(); +} +void ServerChunk::gen_phase_two() { + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + m_generator->generate_heightmap(); +} +void ServerChunk::gen_phase_three() { + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + m_generator->generate_terrain_blocks(); +} + +void ServerChunk::gen_phase_four( + const std::array>, 4>& + neighbor_block) { + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + // This must be fully completed before any other operations can proceed! + m_generator->blend_surface_blocks_borders(neighbor_block); +} + +void ServerChunk::gen_phase_five() { + if (!m_generator) { + Logger::error("ChunkGenerator is Nullptr"); + return; + } + m_generator->ocean_build(); + m_generator->generate_river(); + m_generator->generate_cave(); + + m_generator->generate_vegetation(); + m_generator = nullptr; +} + +void ServerChunk::gen_chunk() { + if (m_gening.exchange(true)) + return; + m_gening = true; + if (m_blocks.size() != 0) { + Logger::warn( + "Request Generator Chunk {} {} ,but the Blocks size is Not 0", + m_chunk_pos.x, m_chunk_pos.z); + } + std::vector neighbor; + for (int i = 0; i < 4; i++) { + neighbor.emplace_back(m_world, m_chunk_pos + CHUNK_DIR[i], true); + } + for (auto& chunk : neighbor) { + chunk.gen_phase_one(); + chunk.gen_phase_two(); + chunk.gen_phase_three(); + chunk.gen_phase_five(); + } + gen_phase_one(); + gen_phase_two(); + gen_phase_three(); + + OptionalBlockVectorArray neightbor_blocks; + for (int i = 0; i < 4; i++) { + neightbor_blocks[i] = neighbor[i].get_chunk_blocks(); + } + gen_phase_four(neightbor_blocks); + gen_phase_five(); +} +// Logger::info("Cross Sum {}", m_cross_vertices_sum.load()); + +bool ServerChunk::is_temp_chunk() const { return m_temp_chunk.load(); } + +bool& ServerChunk::has_cave() { return m_has_cave; } + +void ServerChunk::set_chunk_block(int index, unsigned id) { + m_blocks[index] = id; +} +ChunkPos ServerChunk::chunk_pos() const { return m_chunk_pos; } + +BiomeType ServerChunk::biome() const { return m_biome; } + +void ServerChunk::biome(BiomeType b) { m_biome = b; } + +HeightMapArray& ServerChunk::heightmap() { return m_heightmap; } +std::vector& ServerChunk::blocks() { return m_blocks; } +ServerWorld& ServerChunk::world() { return m_world; } +unsigned ServerChunk::seed() const { + if (m_seed == 0) { + Logger::warn("Seed Not Generator"); + } + return m_seed; +} + +BiomeConditions& ServerChunk::conditions() { return m_conditions; } + +} // namespace Cubed \ No newline at end of file diff --git a/src/gameplay/server_player.cpp b/src/gameplay/server_player.cpp new file mode 100644 index 0000000..e69de29 diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp new file mode 100644 index 0000000..0edf5d6 --- /dev/null +++ b/src/gameplay/server_world.cpp @@ -0,0 +1,394 @@ +#include "Cubed/gameplay/server_world.hpp" + +#include "Cubed/config.hpp" +#include "Cubed/tools/cubed_assert.hpp" +#include "Cubed/tools/log.hpp" + +#include +using namespace std::chrono; +using namespace std::chrono_literals; + +namespace Cubed { +ServerWorld::ServerWorld() {} + +ServerWorld::~ServerWorld() { + stop_gen_thread(); + stop_server_thread(); + wait_all_chunk_tasks(); + stop_thread_pool(); + + m_chunks.clear(); +} + +void ServerWorld::wait_all_chunk_tasks() { + for (auto& [pos, task] : new_chunks) { + task.future.get(); + } +} + +void ServerWorld::init_world() { + m_cave_carcer.init(ChunkGenerator::seed()); + m_river_worm.init(ChunkGenerator::seed()); + m_chunks.reserve(MAX_DISTANCE * MAX_DISTANCE * 4); + start_thread_pool(); + + auto t1 = std::chrono::system_clock::now(); + + // init players + // m_players.emplace(HASH::str("TestPlayer"), Player(*this, "TestPlayer")); + + start_gen_thread(); + init_chunks(); + auto t2 = std::chrono::system_clock::now(); + auto d = std::chrono::duration_cast(t2 - t1); + Logger::info("Chunk Block Init Finish, Time Consuming: {}", d); + + start_server_thread(); + + Logger::info("TestPlayer Create Finish"); +} + +void ServerWorld::init_chunks() { + hot_reload(); + while (!m_chunk_gen_finished) { + // Logger::info("World Spawn: {:.2f}%", m_chunk_gen_fraction.load()); + std::this_thread::sleep_for(std::chrono::microseconds(200)); + } +} + +void ServerWorld::gen_chunks_internal() { + // Logger::info("gen_chunks_internal"); + m_chunk_gen_finished = false; + + ChunkPosSet required_chunks; + compute_required_chunks(required_chunks); + + ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!"); + + std::vector need_gen_chunks_pos; + + sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks); + + Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size()); + + if (need_gen_chunks_pos.empty()) { + m_could_gen = true; + + return; + } + + for (auto& pos : need_gen_chunks_pos) { + new_chunks.emplace(pos, ServerChunk(*this, pos)); + } + + submit_new_chunks(); + m_chunk_gen_finished = true; +} + +void ServerWorld::compute_required_chunks(ChunkPosSet& required_chunks) { + glm::vec3 player_pos; + // sync_player_pos(player_pos); + ASSERT_MSG(false, "Player Pos"); + int x = std::floor(player_pos.x); + int z = std::floor(player_pos.z); + auto [chunk_x, chunk_z] = get_chunk_pos(x, z); + int radius = m_rendering_distance; + int r2 = radius * radius; + required_chunks.reserve(radius * radius); + + for (int dx = -radius; dx <= radius; ++dx) { + for (int dz = -radius; dz <= radius; ++dz) { + if (dx * dx + dz * dz <= r2) { + required_chunks.emplace(chunk_x + dx, chunk_z + dz); + } + } + } +} +void ServerWorld::sync_and_collect_missing_chunks( + std::vector& need_gen_chunks_pos, + const ChunkPosSet& required_chunks) { + std::lock_guard lk(m_chunks_mutex); + for (auto it = m_chunks.begin(); it != m_chunks.end();) { + if (required_chunks.find(it->first) == required_chunks.end()) { + it = m_chunks.erase(it); + } else { + ++it; + } + } + + for (auto pos : required_chunks) { + auto it = m_chunks.find(pos); + if (it == m_chunks.end()) { + need_gen_chunks_pos.push_back(pos); + } + } +} +void ServerWorld::submit_new_chunks() { + using enum ChunkLoadStyle; + std::lock_guard lock(m_new_chunk_mutex); + auto pool_ptr = m_gen_thread_pool.load(); + if (!pool_ptr) { + return; + } + switch (m_chunk_load_style) { + case RANDOM: + for (auto& [pos, task] : new_chunks) { + if (!task.future.valid()) { + task.future = + pool_ptr->enqueue([&task]() { task.chunk.gen_chunk(); }); + } + } + break; + case CENTER: { + std::vector> tasks; + for (auto& [pos, task] : new_chunks) { + if (!task.future.valid()) { + tasks.emplace_back(pos, &task); + } + } + glm::vec3 player_pos; + sync_player_pos(player_pos); + auto dist2 = [player_pos](ChunkPos chunk_pos) { + ChunkPos player_chunk_pos = + get_chunk_pos(player_pos.x, player_pos.z); + float dx = player_chunk_pos.x - chunk_pos.x; + float dz = player_chunk_pos.z - chunk_pos.z; + return dx * dx + dz * dz; + }; + + std::sort(tasks.begin(), tasks.end(), + [&dist2](const auto& a, const auto& b) { + return dist2(a.first) < dist2(b.first); + }); + for (auto& [pos, task] : tasks) { + if (!task->future.valid()) { + task->future = + pool_ptr->enqueue([task]() { task->chunk.gen_chunk(); }); + } + } + } + } +} + +void ServerWorld::poll_finished_chunks() { + m_new_finished_chunk.clear(); + std::lock_guard lock(m_new_chunk_mutex); + std::erase_if( + new_chunks, [&](std::pair& pair) { + auto& pending = pair.second; + if (!pending.future.valid()) { + return false; + } + if (pending.future.wait_for(0ms) != std::future_status::ready) { + return false; + } + pending.future.get(); + + m_new_finished_chunk.emplace_back(pair.first, + std::move(pending.chunk)); + return true; + }); +} + +void ServerWorld::start_gen_thread() { + m_gen_running = true; + Logger::info("Gen Thread Started"); + m_gen_thread = std::thread([this]() { + while (m_gen_running) { + std::unique_lock lk(m_gen_signal_mutex); + + m_gen_cv.wait(lk, [this]() { + return m_need_gen_chunk.load() || !m_gen_running; + }); + if (!m_gen_running) { + break; + } + m_need_gen_chunk = false; + lk.unlock(); + + gen_chunks_internal(); + } + }); +} + +void ServerWorld::start_server_thread() { + m_server_thread = std::thread( + [this]() { serever_run(m_server_stop_source.get_token()); }); +} + +void ServerWorld::start_thread_pool() { + int max_thread = std::thread::hardware_concurrency(); + if (m_pool_threads == 0) { + change_pool_threads(max_thread - RESERVED_THREADS); + } else { + change_pool_threads(m_pool_threads); + } +} + +void ServerWorld::stop_gen_thread() { + m_gen_running = false; + m_gen_cv.notify_all(); + if (m_gen_thread.joinable()) { + m_gen_thread.join(); + } + Logger::info("Gen Thread Stopped"); +} + +void ServerWorld::stop_server_thread() { + m_server_stop_source.request_stop(); + if (m_server_thread.joinable()) { + m_server_thread.join(); + } +} + +void ServerWorld::stop_thread_pool() { + auto pool_ptr = m_gen_thread_pool.load(); + if (pool_ptr) { + pool_ptr->stop(); + } + m_gen_thread_pool.store(nullptr); + Logger::info("Thread Pool Stopped"); +} + +void ServerWorld::serever_run(std::stop_token stoken) { + Logger::info("Server Thread Started!"); + while (!stoken.stop_requested()) { + std::this_thread::sleep_for(milliseconds(m_per_tick_time)); + if (m_tick_running) { + ++m_game_ticks; + m_day_tick = (m_day_tick + 1) % DAY_TIME; + } + update(); + } + Logger::info("Server Thread Stopped!"); +} + +void ServerWorld::need_gen() { + + if (!m_could_gen) { + Logger::warn("It is generating or consuming new chunks"); + return; + } + + m_could_gen = false; + { + // std::lock_guard lk(m_gen_player_pos_mutex); + // m_gen_player_pos = get_player("TestPlayer").get_player_pos(); + ASSERT_MSG(false, "Player Pos Handle"); + } + + m_need_gen_chunk = true; + + m_gen_cv.notify_one(); +} + +void ServerWorld::set_block(const glm::ivec3& block_pos, unsigned id) { + + int world_x, world_y, world_z; + world_x = block_pos.x; + world_y = block_pos.y; + world_z = block_pos.z; + + auto [chunk_x, chunk_z] = get_chunk_pos(world_x, world_z); + std::lock_guard lk(m_chunks_mutex); + auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z}); + + if (it == m_chunks.end()) { + return; + } + + auto [x, y, z] = ServerChunk::world_to_block(world_x, world_y, world_z, + chunk_x, chunk_z); + if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y || + z >= CHUNK_SIZE) { + return; + } + + it->second.set_chunk_block(ServerChunk::index(x, y, z), id); +} + +void ServerWorld::hot_reload() { + auto& config = Config::get(); + int dist = config.get("world.rendering_distance"); + m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE; + need_gen(); +} + +void ServerWorld::rebuild_world() { + if (m_is_rebuilding) { + return; + } + m_is_rebuilding = true; + stop_gen_thread(); + stop_thread_pool(); + m_cave_carcer.reload(ChunkGenerator::seed()); + m_river_worm.reload(ChunkGenerator::seed()); + { + std::lock_guard lk(m_chunks_mutex); + m_chunks.clear(); + m_new_finished_chunk.clear(); + } + m_could_gen = true; + ChunkGenerator::reload(); + start_thread_pool(); + start_gen_thread(); + need_gen(); + + m_is_rebuilding = false; +} + +void ServerWorld::update() { poll_finished_chunks(); } + +int ServerWorld::rendering_distance() const { + return m_rendering_distance.load(); +} + +void ServerWorld::rendering_distance(int rendering_distance) { + m_rendering_distance = rendering_distance; +} + +CaveCarver& ServerWorld::cave_carcer() { return m_cave_carcer; } +RiverWorm& ServerWorld::river_worm() { return m_river_worm; } + +TickType ServerWorld::game_tick() const { return m_game_ticks.load(); } +TickType ServerWorld::day_tick() const { return m_day_tick.load(); } +void ServerWorld::day_tick(TickType tick) { + tick %= DAY_TIME; + m_day_tick = tick; +} +int ServerWorld::per_tick_time() const { return m_per_tick_time.load(); } +void ServerWorld::per_tick_time(int ms) { m_per_tick_time = ms; } + +bool ServerWorld::is_tick_running() const { return m_tick_running.load(); } +void ServerWorld::tick_running(bool run) { m_tick_running = run; } +int ServerWorld::pool_threads() const { return m_pool_threads.load(); } +int ServerWorld::max_threads() const { return m_max_threads.load(); } +void ServerWorld::change_pool_threads(int threads) { + m_max_threads = std::thread::hardware_concurrency(); + if (m_max_threads < 1) { + Logger::warn("Can't Get Max Support Threads, Set Max Threads to 4"); + m_max_threads = 4; + } + int used_thread = std::clamp(threads, 1, m_max_threads.load()); + Logger::info("Create New Thread Pool Use {} Threads", used_thread); + m_gen_thread_pool.store(std::make_shared(used_thread)); + m_pool_threads = used_thread; +} +int ServerWorld::chunk_load_style() const { + return std::to_underlying(m_chunk_load_style.load()); +} +void ServerWorld::set_chunk_load_style(int id) { + using enum ChunkLoadStyle; + + switch (id) { + case std::to_underlying(RANDOM): + m_chunk_load_style = RANDOM; + return; + case std::to_underlying(CENTER): + m_chunk_load_style = CENTER; + return; + } + Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id); +} + +} // namespace Cubed \ No newline at end of file