10 Commits

Author SHA1 Message Date
12b1107923 fix(client-world): delay chunk request to wait for server central chunk generation 2026-06-28 20:57:29 +08:00
9788f68b12 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.
2026-06-28 20:11:14 +08:00
cbe3548dcd refactor(gameplay): remove old pre_remove chunk, player, and world files 2026-06-28 19:29:52 +08:00
4073664624 fix: address unused parameter warnings and missing port assignment 2026-06-28 19:28:23 +08:00
a016e08a2a fix(app): correct port validation and remove redundant TOML helper 2026-06-28 15:47:08 +08:00
a5764e901b 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.
2026-06-28 15:27:51 +08:00
429520b1f7 feat(client_world): implement server exit acknowledgment with timeout 2026-06-28 15:04:21 +08:00
d2636189a4 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.
2026-06-28 14:46:42 +08:00
3d4c41a76e fix(server): ensure chunk is ready before sending or setting block 2026-06-28 14:30:36 +08:00
28b66ee275 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.
2026-06-28 14:27:30 +08:00
21 changed files with 470 additions and 2672 deletions

View File

@@ -123,6 +123,9 @@ target_link_libraries(${PROJECT_NAME}
protobuf::libprotobuf
absl::log
absl::check
absl::base
absl::strings
absl::flat_hash_map
zstd::zstd
$<$<PLATFORM_ID:Windows>:ws2_32>

View File

@@ -29,8 +29,7 @@ public:
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 std::string& name) const;
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;
@@ -66,7 +65,9 @@ public:
const std::vector<RemotePlayerRenderData>& render_player_data() const;
glm::vec3 sunlight_dir() const;
void receive_chunk(ChunkDataRsp data);
void exit();
void request_exit();
bool is_receive_exit();
template <typename Fn>
void register_timer(std::string_view id, TickType threshold, Fn&& f) {
m_timers.emplace(std::piecewise_construct,
@@ -82,6 +83,9 @@ private:
using ChunkPosVector = std::vector<ChunkPos>;
using OtherPlayerHashMap =
std::unordered_map<std::string, RemotePlayerInfo>;
static constexpr int WORLD_EXIT_TIMEOUT = 200;
ClientPlayer m_player;
OtherPlayerHashMap m_other_players;
ChunkHashMap m_chunks;
@@ -104,6 +108,7 @@ private:
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};

View File

@@ -1,171 +0,0 @@
#pragma once
#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 <atomic>
#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 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);
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;
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);
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 gen_vertex_data(const OptionalBlockVectorArray& neighbor_block);
void upload_to_gpu();
GLuint get_normal_vao() const;
size_t get_normal_vertices_sum() const;
GLuint get_cross_vao() const;
size_t get_cross_vertices_sum() const;
GLuint get_normal_discard_vao() const;
size_t get_normal_discard_vertices_sum() const;
GLuint get_normal_blend_vao() const;
size_t get_normal_blend_vertices_sum() const;
GLuint get_water_vao() const;
size_t get_water_vertices_sum() const;
bool is_dirty() const;
void mark_dirty();
bool is_need_upload() const;
void need_upload();
void set_chunk_block(int index, unsigned id);
// 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();
unsigned seed() const;
BiomeConditions& conditions();
ChunkInfo get_info() const;
bool& has_cave();
};
} // namespace Cubed

View File

@@ -1,112 +0,0 @@
#pragma once
#include "Cubed/AABB.hpp"
#include "Cubed/constants.hpp"
#include "Cubed/gameplay/block.hpp"
#include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/game_mode.hpp"
#include "Cubed/input.hpp"
#include <glm/glm.hpp>
#include <optional>
#include <string>
namespace Cubed {
enum class Gait { WALK = 0, RUN };
class World;
class Player {
private:
using enum GameMode;
float m_max_walk_speed = DEFAULT_MAX_WALK_SPEED;
float m_max_run_speed = DEFAULT_MAX_RUN_SPEED;
float m_acceleration = DEFAULT_ACCELERATION;
float m_deceleration = DEFAULT_DECELERATION;
float m_g = DEFAULT_G;
constexpr static float MAX_SPACE_ON_TIME = 0.3f;
float m_yaw = 0.0f;
float m_pitch = 0.0f;
float m_sensitivity = 0.15f;
float m_max_speed = m_max_walk_speed;
float m_y_speed = 0.0f;
float m_fly_y_speed = 7.5f;
bool can_up = true;
float space_on_time = 0.0f;
bool space_on = false;
bool is_fly = false;
float m_xz_speed = 0.0f;
unsigned m_place_block = 1;
glm::vec3 direction = glm::vec3(0.0f, 0.0f, 0.0f);
glm::vec3 move_distance{0.0f, 0.0f, 0.0f};
// player is tow block tall, the pos is the lower pos
glm::vec3 m_player_pos{0.0f, 255.0f, 0.0f};
ChunkPos m_player_chunk_pos{0, 0};
glm::vec3 m_front{0, 0, -1};
glm::vec3 m_right{0, 0, 0};
glm::vec3 m_size{0.6f, 1.8f, 0.6f};
Gait m_gait = Gait::WALK;
MoveState m_move_state{};
GameMode m_game_mode = CREATIVE;
std::optional<LookBlock> m_look_block = std::nullopt;
std::string m_name{};
World& m_world;
bool ray_cast(const glm::vec3& start, const glm::vec3& dir,
glm::ivec3& block_pos, glm::vec3& normal,
float distance = 4.0f);
void check_player_chunk_transition();
void update_direction();
void update_lookup_block();
void update_move(float delta_time);
void update_x_move();
void update_y_move();
void update_z_move();
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;
};
} // namespace Cubed

View File

@@ -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

View File

@@ -93,4 +93,5 @@ private:
// Generate biome-specific vegetation/structures
void gen_phase_five();
};
} // namespace Cubed

View File

@@ -2,19 +2,28 @@
#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;
@@ -24,6 +33,10 @@ public:
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;
@@ -35,5 +48,7 @@ private:
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

View File

@@ -10,24 +10,26 @@
#include "Cubed/tools/thread_pool.hpp"
#include "world/block_change.pb.h"
#include <absl/container/flat_hash_set.h>
#include <future>
#include <shared_mutex>
#include <tbb/concurrent_hash_map.h>
#include <tbb/concurrent_queue.h>
#include <tbb/concurrent_unordered_map.h>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
namespace Cubed {
class Session;
class ServerWorld {
public:
enum class ThreadPoolKind { NET, GEN };
ServerWorld();
~ServerWorld();
void player_join(std::string_view name, std::string_view uuid);
void stop();
void handle_player_exit(const std::string& uuid);
void init_world();
void need_gen(std::optional<std::string> uuid);
void need_gen(std::string uuid);
void update();
void hot_reload();
@@ -58,9 +60,10 @@ public:
bool is_tick_running() const;
void tick_running(bool run);
int pool_threads() const;
int gen_pool_threads() const;
int max_threads() const;
void change_pool_threads(int threads);
void change_pool_threads(ThreadPoolKind kind, int threads);
int chunk_load_style() const;
void set_chunk_load_style(int id);
@@ -74,6 +77,8 @@ public:
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,
@@ -82,25 +87,43 @@ public:
}
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 {
ServerChunk chunk;
std::unique_ptr<ServerChunk> chunk;
std::future<void> future;
};
struct FinishedChunk {
ChunkPos pos;
std::unique_ptr<ServerChunk> chunk;
};
using ChunkHashMap =
tbb::concurrent_unordered_map<ChunkPos, ServerChunk, ChunkPos::Hash>;
std::unordered_map<ChunkPos, ChunkEntity, ChunkPos::Hash>;
using PlayerHashMap = std::unordered_map<std::string, ServerPlayer>;
using PendingChunkHashMap =
std::unordered_map<ChunkPos, PendingChunk, ChunkPos::Hash>;
using ChunkPosSet = std::unordered_set<ChunkPos, ChunkPos::Hash>;
using ChunkPosSet = absl::flat_hash_set<ChunkPos, ChunkPos::Hash>;
using PlayerUUIDMap = tbb::concurrent_hash_map<std::string, std::string>;
using uuid_acc = PlayerUUIDMap::accessor;
using uuid_cacc = PlayerUUIDMap::const_accessor;
// key = uuid
PlayerHashMap m_players;
ChunkHashMap m_chunks;
PendingChunkHashMap m_new_chunks;
std::vector<std::pair<ChunkPos, ServerChunk>> m_new_finished_chunk;
std::vector<FinishedChunk> m_new_finished_chunk;
CaveCarver m_cave_carcer;
RiverWorm m_river_worm;
@@ -114,8 +137,10 @@ private:
std::atomic<bool> m_need_gen_chunk{false};
std::atomic<bool> m_is_rebuilding{false};
std::atomic<bool> m_init{false};
std::atomic<bool> m_stopped{false};
std::atomic<int> m_rendering_distance{24};
std::atomic<int> m_pool_threads{0};
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};
@@ -132,23 +157,36 @@ private:
RecentQueue<std::string> m_need_gen_queue;
std::atomic<std::shared_ptr<ThreadPool>> 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;
void init_chunks();
void gen_chunks_internal(std::optional<std::string> uuid);
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::optional<std::string>& uuid);
void submit_new_chunks(const std::string& uuid);
void poll_finished_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);
void send_server_stop();
};
} // namespace Cubed

View File

@@ -27,14 +27,6 @@ std::optional<T> safe_get_value(const toml::table& table, std::string_view key,
}
return value;
}
template <typename U>
requires std::convertible_to<U, std::string>
std::optional<std::string> safe_get_value(const toml::table& table,
std::string_view key,
U&& default_value) {
return safe_get_value<std::string>(
table, key, std::string(std::forward<U>(default_value)));
}
} // namespace TOML

View File

@@ -99,7 +99,7 @@ void App::handle_argument(int argc, char** argv) {
auto r = std::from_chars(arg.data(), arg.data() + arg.size(),
m_argument.port);
if (r.ec != std::errc{} || arg.data() + arg.size()) {
if (r.ec != std::errc{} || r.ptr != arg.data() + arg.size()) {
throw std::runtime_error(
std::format("Invalid port: {}", arg));
}
@@ -143,7 +143,8 @@ void App::handle_toml() {
return;
}
m_argument.ip = *TOML::safe_get_value(server, "ip", "127.0.01");
m_argument.ip =
*TOML::safe_get_value(server, "ip", std::string("127.0.01"));
m_argument.port = *TOML::safe_get_value(server, "port", 25530);
m_argument.is_client = *TOML::safe_get_value(server, "client", false);
}
@@ -245,8 +246,7 @@ void App::window_focus_callback(GLFWwindow* window, int focused) {
}
}
void App::window_reshape_callback(GLFWwindow* window, int new_width,
int new_height) {
void App::window_reshape_callback(GLFWwindow* window, int, int) {
App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
ASSERT_MSG(app, "nullptr");
@@ -301,11 +301,16 @@ void App::run() {
last_time = glfwGetTime();
while (!glfwWindowShouldClose(m_window.get_glfw_window())) {
if (m_client_world.is_receive_exit()) {
break;
}
update();
render();
}
m_client_world.exit();
m_client_world.request_exit();
if (!m_argument.is_client) {
m_server.server_world().stop();
}
}
static Gait player_gait = Gait::WALK;
void App::update() {

View File

@@ -504,13 +504,14 @@ void DevPanel::show_server_world_table_bar() {
}
ImGui::Text("Pool Threads %d Max Support Threads %d Reserved Threads %d",
m_app.server_world().pool_threads(),
m_app.server_world().gen_pool_threads(),
m_app.server_world().max_threads(), RESERVED_THREADS);
ImGui::SliderInt("Set Pool Threads", &m_threads, 1,
m_app.server_world().max_threads());
ImGui::SameLine();
if (ImGui::Button("Set")) {
m_app.server_world().change_pool_threads(m_threads);
m_app.server_world().change_pool_threads(
ServerWorld::ThreadPoolKind::GEN, m_threads);
}
if (m_threads > m_app.server_world().max_threads() - RESERVED_THREADS) {
ImGui::TextColored(
@@ -530,7 +531,7 @@ void DevPanel::show_server_world_table_bar() {
ImGui::SameLine();
if (ImGui::Button("Request Chunk Build")) {
Logger::warn("This Request Chunk Build button is not finish");
m_app.server_world().need_gen(std::nullopt);
m_app.server_world().need_gen(m_player->get_uuid());
}
ImGui::SameLine();
if (ImGui::Checkbox("Gen Thread", &m_gen_thread_running)) {
@@ -540,6 +541,7 @@ void DevPanel::show_server_world_table_bar() {
m_app.server_world().stop_gen_thread();
}
}
ImGui::Text("Server Chunk Size %d", m_app.server_world().chunk_size());
if (ImGui::BeginTabBar("World Settings")) {
if (ImGui::BeginTabItem("Time")) {
@@ -695,8 +697,9 @@ void DevPanel::show_shader_tab_item() {
ImGui::Checkbox("Flip Y", &m_app.renderer().flip_y());
if (ImGui::SliderFloat("AmbientStrength",
&m_app.renderer().ambient_strength(), 0.0f,
0.35f))
;
0.35f)) {
}
ImGui::SliderFloat("SpecularStrength",
&m_app.renderer().specular_strength(), 0.0f, 2.0f);
ImGui::Checkbox("Discard Transparent",

View File

@@ -44,8 +44,7 @@ ClientWorld::~ClientWorld() {
m_timers.clear();
}
const std::optional<LookBlock>&
ClientWorld::get_look_block_pos(const std::string& name) const {
const std::optional<LookBlock>& ClientWorld::get_look_block_pos() const {
return m_player.get_look_block_pos();
}
@@ -231,6 +230,14 @@ void ClientWorld::receive_remote_player(const PlayerInfoRsp& rsp) {
}
void ClientWorld::receive_player_logout(const LogoutRsp& rsp) {
if (rsp.server_stop()) {
m_receive_exit = true;
return;
}
if (rsp.uuid() == m_player.get_uuid()) {
m_receive_exit = true;
return;
}
{
std::lock_guard lock(m_other_players_mutex);
int sum = m_other_players.erase(rsp.uuid());
@@ -274,6 +281,12 @@ void ClientWorld::start_client_thread(std::string_view uuid) {
m_game_running = true;
client_run(token);
});
// Wait for 20 ticks, after the server's central chunk is generated, then
// request chunks
std::this_thread::sleep_for(milliseconds(20 * DEFAULT_PER_TICK_TIME));
request_chunk();
}
@@ -462,12 +475,24 @@ void ClientWorld::receive_chunk(ChunkDataRsp data) {
}
});
}
void ClientWorld::exit() {
bool ClientWorld::is_receive_exit() { return m_receive_exit; }
void ClientWorld::request_exit() {
if (m_receive_exit) {
return;
}
Arena arena;
auto* req = Arena::Create<LogoutReq>(&arena);
req->set_uuid(m_player.get_uuid());
m_client->send(make_packet(*req));
int cnt = 0;
while (!m_receive_exit) {
std::this_thread::sleep_for(milliseconds(DEFAULT_PER_TICK_TIME));
++cnt;
if (cnt >= WORLD_EXIT_TIMEOUT) {
Logger::warn("Can't Receive Server Exit Sign");
break;
}
}
}
void ClientWorld::update(float delta_time) {

View File

@@ -83,6 +83,7 @@ void NetworkServer::net_run() {
}
void NetworkServer::start_server(int port) {
m_port = port;
m_world.init_world();
net_run();
m_started = true;

View File

@@ -1,804 +0,0 @@
#include "Cubed/gameplay/chunk.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/log.hpp"
#include <utility>
namespace Cubed {
using OptionalBlockVectorArray =
std::array<std::optional<std::vector<BlockType>>, 4>;
namespace {
// ────────────────────────────────────────────────────────────────────────────
// Face direction mapping
// Original DIR[6]: {+Z,+X,-Z,-X,+Y,-Y} => face index 0-5
// Axis × direction => face:
// axis=2(Z) dir=+1 => face 0 (+Z)
// axis=0(X) dir=+1 => face 1 (+X)
// axis=2(Z) dir=-1 => face 2 (-Z)
// axis=0(X) dir=-1 => face 3 (-X)
// axis=1(Y) dir=+1 => face 4 (+Y)
// axis=1(Y) dir=-1 => face 5 (-Y)
// ────────────────────────────────────────────────────────────────────────────
inline int axis_dir_to_face(int axis, int dir) {
// axis: 0=X 1=Y 2=Z
// dir: +1 or -1
static const int TABLE[3][2] = {
{3, 1}, // X: dir=-1->face3(-X), dir=+1->face1(+X)
{5, 4}, // Y: dir=-1->face5(-Y), dir=+1->face4(+Y)
{2, 0}, // Z: dir=-1->face2(-Z), dir=+1->face0(+Z)
};
return TABLE[axis][dir > 0 ? 1 : 0];
}
inline BlockType
get_block_safe(int lx, int ly, int lz, ChunkPos& chunk_pos,
const std::vector<BlockType>& blocks,
const OptionalBlockVectorArray& neighbor_block) {
if (lx >= 0 && lx < CHUNK_SIZE && ly >= 0 && ly < WORLD_SIZE_Y && lz >= 0 &&
lz < CHUNK_SIZE) {
return blocks[Chunk::index(lx, ly, lz)];
}
// Out of bounds: check neighbors
int world_x = lx + chunk_pos.x * CHUNK_SIZE;
int world_z = lz + chunk_pos.z * CHUNK_SIZE;
auto [nb_cx, nb_cz] = World::get_chunk_pos(world_x, world_z);
const std::optional<std::vector<BlockType>>* nb = nullptr;
if (nb_cx == chunk_pos.x + 1)
nb = &neighbor_block[0];
else if (nb_cx == chunk_pos.x - 1)
nb = &neighbor_block[1];
else if (nb_cz == chunk_pos.z + 1)
nb = &neighbor_block[2];
else if (nb_cz == chunk_pos.z - 1)
nb = &neighbor_block[3];
if (!nb || !nb->has_value())
return 0; // Neighbor does not exist, treat as opaque
int nbx = world_x - nb_cx * CHUNK_SIZE;
int nby = ly;
int nbz = world_z - nb_cz * CHUNK_SIZE;
if (nbx < 0 || nby < 0 || nbz < 0 || nbx >= CHUNK_SIZE ||
nby >= WORLD_SIZE_Y || nbz >= CHUNK_SIZE)
return 0;
int idx = Chunk::index(nbx, nby, nbz);
if (static_cast<size_t>(idx) >= (*nb)->size()) {
return 0;
}
return (**nb)[idx];
}
// Determine whether the face from cur_id looking towards neighbor_id should be
// culled (does not need to be rendered)
inline bool is_face_culled(BlockType cur_id, BlockType neighbor_id) {
if (!BlockManager::is_transparent(neighbor_id))
return true; // Neighbor is opaque, blocking
// Neighbor transparency: same block type culls each other (e.g., water
// adjacent to water does not render internal faces)
if (neighbor_id == cur_id)
return true;
return false;
}
inline int choose_buf(BlockType id) {
if (!BlockManager::is_transparent(id))
return 0;
if (BlockManager::is_discard(id))
return 2;
if (BlockManager::is_blend(id)) {
return (id == 7) ? 4 : 3; // water=4, other blend=3
}
return 3; // fallback
}
} // namespace
Chunk::Chunk(World& world, ChunkPos chunk_pos, bool temp_chunk)
: m_temp_chunk(temp_chunk), m_chunk_pos(chunk_pos), m_world(world) {
for (int i = 0; i < VERTEX_DATA_SUM; i++) {
m_vertex_data.emplace_back(m_world);
}
}
Chunk::~Chunk() {}
Chunk::Chunk(Chunk&& other) noexcept
: m_dirty(other.is_dirty()), m_need_upload(other.m_need_upload.load()),
m_is_on_gen_vertex_data(other.m_is_on_gen_vertex_data.load()),
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_vertex_data(std::move(other.m_vertex_data)), m_seed(other.m_seed),
m_conditions(other.m_conditions), m_info(std::move(other.m_info)) {}
Chunk& Chunk::operator=(Chunk&& 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<const void*>(&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_dirty = other.is_dirty();
m_vertex_data = std::move(other.m_vertex_data);
m_biome = other.m_biome.load();
m_is_on_gen_vertex_data = other.m_is_on_gen_vertex_data.load();
m_need_upload = other.m_need_upload.load();
m_seed = other.m_seed;
m_conditions = other.m_conditions;
m_info = std::move(other.m_info);
return *this;
}
std::tuple<int, int, int> Chunk::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<int, int, int> Chunk::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<int, int, int> Chunk::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<int, int, int> Chunk::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 Chunk::get_biome() const { return m_biome.load(); }
ChunkPos Chunk::get_chunk_pos() const { return m_chunk_pos; }
const std::vector<BlockType>& Chunk::get_chunk_blocks() const {
return m_blocks;
}
HeightMapArray Chunk::get_heightmap() const {
// Logger::info("Chunk pos {} {} in get_heightmap this {}", m_chunk_pos.x,
// m_chunk_pos.z, static_cast<const void*>(this));
return m_heightmap;
}
int Chunk::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 Chunk::index(const glm::vec3& pos) {
return Chunk::index(pos.x, pos.y, pos.z);
}
void Chunk::gen_vertex_data(const OptionalBlockVectorArray& neighbor_block) {
if (m_is_on_gen_vertex_data) {
return;
}
m_is_on_gen_vertex_data = true;
std::lock_guard lk(m_vertexs_data_mutex);
for (auto& data : m_vertex_data) {
data.m_vertices.clear();
}
gen_vertices(neighbor_block);
for (auto& data : m_vertex_data) {
data.update_sum();
}
m_need_upload = true;
m_is_on_gen_vertex_data = false;
}
GLuint Chunk::get_normal_vao() const { return m_vertex_data[0].m_vao; }
size_t Chunk::get_normal_vertices_sum() const {
if (m_vertex_data[0].m_sum == 0) {
Logger::warn("m_normal_vertices_sum is 0");
}
return m_vertex_data[0].m_sum.load();
}
GLuint Chunk::get_cross_vao() const { return m_vertex_data[1].m_vao; }
size_t Chunk::get_cross_vertices_sum() const {
return m_vertex_data[1].m_sum.load();
}
GLuint Chunk::get_normal_discard_vao() const { return m_vertex_data[2].m_vao; }
size_t Chunk::get_normal_discard_vertices_sum() const {
return m_vertex_data[2].m_sum.load();
}
GLuint Chunk::get_normal_blend_vao() const { return m_vertex_data[3].m_vao; }
size_t Chunk::get_normal_blend_vertices_sum() const {
return m_vertex_data[3].m_sum.load();
}
GLuint Chunk::get_water_vao() const { return m_vertex_data[4].m_vao; }
size_t Chunk::get_water_vertices_sum() const {
return m_vertex_data[4].m_sum.load();
}
void Chunk::gen_phase_one() {
// m_generator = std::make_unique<ChunkGenerator>(*this);
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
m_generator->assign_chunk_biome();
m_seed = m_generator->chunk_seed();
}
void Chunk::gen_phase_two(const std::array<const Chunk*, 8>& adj_chunks) {
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
// m_generator->resolve_biome_adjacency_conflict(adj_chunks);
}
void Chunk::gen_phase_three() {
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
m_generator->generate_heightmap();
}
void Chunk::gen_phase_four(
const std::array<std::optional<HeightMapArray>, 8>& neighbor_heightmap,
const std::array<BiomeType, 8>& neighbor_biome) {
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
// m_generator->blend_heightmap_boundaries(neighbor_heightmap,
// neighbor_biome);
}
void Chunk::gen_phase_five() {
if (!m_generator) {
Logger::error("ChunkGenerator is Nullptr");
return;
}
m_generator->generate_terrain_blocks();
}
void Chunk::gen_phase_six(
const std::array<std::optional<std::vector<BlockType>>, 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 Chunk::gen_phase_seven() {
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();
mark_dirty();
m_generator = nullptr;
}
void Chunk::upload_to_gpu() {
ASSERT(is_need_upload());
std::lock_guard lk(m_vertexs_data_mutex);
for (auto& data : m_vertex_data) {
data.upload();
}
// after fininshed it, can use
clear_dirty();
m_need_upload = false;
}
bool Chunk::is_dirty() const { return m_dirty.load(); }
void Chunk::mark_dirty() { m_dirty = true; }
void Chunk::clear_dirty() { m_dirty = false; }
bool Chunk::is_need_upload() const { return m_need_upload.load(); }
void Chunk::need_upload() { m_need_upload = true; }
void Chunk::set_chunk_block(int index, unsigned id) {
m_blocks[index] = id;
mark_dirty();
}
ChunkPos Chunk::chunk_pos() const { return m_chunk_pos; }
BiomeType Chunk::biome() const { return m_biome; }
void Chunk::biome(BiomeType b) { m_biome = b; }
HeightMapArray& Chunk::heightmap() { return m_heightmap; }
std::vector<BlockType>& Chunk::blocks() { return m_blocks; }
World& Chunk::world() { return m_world; }
unsigned Chunk::seed() const {
if (m_seed == 0) {
Logger::warn("Seed Not Generator");
}
return m_seed;
}
BiomeConditions& Chunk::conditions() { return m_conditions; }
ChunkInfo Chunk::get_info() const {
if (m_gening) {
return ChunkInfo{};
}
return m_info;
}
/*
void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
static const glm::ivec3 DIR[6] = {{0, 0, 1}, {1, 0, 0}, {0, 0, -1},
{-1, 0, 0}, {0, 1, 0}, {0, -1, 0}};
for (int x = 0; x < SIZE_X; x++) {
for (int y = 0; y < SIZE_Y; y++) {
for (int z = 0; z < SIZE_Z; z++) {
int world_x = x + m_chunk_pos.x * CHUNK_SIZE;
int world_z = z + m_chunk_pos.z * CHUNK_SIZE;
int world_y = y;
int cur_id = m_blocks[index(x, y, z)];
// air
if (cur_id == 0) {
continue;
}
for (int face = 0; face < 6; face++) {
int nx = x + DIR[face].x;
int ny = y + DIR[face].y;
int nz = z + DIR[face].z;
bool neighbor_culled = false;
if (nx < 0 || nx >= SIZE_X || ny < 0 || ny >= SIZE_Y ||
nz < 0 || nz >= SIZE_Z) {
int world_nx = world_x + DIR[face].x;
int world_ny = world_y + DIR[face].y;
int world_nz = world_z + DIR[face].z;
auto [neighbor_x, neighbor_z] =
World::get_chunk_pos(world_nx, world_nz);
auto is_culled =
[&](const std::optional<std::vector<BlockType>>&
chunk_blocks) {
if (chunk_blocks == std::nullopt) {
return true;
}
int x, y, z;
y = world_ny;
x = world_nx - neighbor_x * CHUNK_SIZE;
z = world_nz - neighbor_z * CHUNK_SIZE;
if (x < 0 || y < 0 || z < 0 ||
x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return false;
}
int idx = Chunk::index(x, y, z);
// not init
if (static_cast<size_t>(idx) >=
chunk_blocks->size()) {
// Logger::warn("not init");
return true;
}
auto id = (*chunk_blocks)[idx];
// transparent
if (BlockManager::is_transparent(id)) {
if (id == cur_id) {
return true;
} else {
return false;
}
} else {
return true;
}
};
if (m_chunk_pos.x + 1 == neighbor_x) {
neighbor_culled = is_culled(neighbor_block[0]);
} else if (m_chunk_pos.x - 1 == neighbor_x) {
neighbor_culled = is_culled(neighbor_block[1]);
} else if (m_chunk_pos.z + 1 == neighbor_z) {
neighbor_culled = is_culled(neighbor_block[2]);
} else if (m_chunk_pos.z - 1 == neighbor_z) {
neighbor_culled = is_culled(neighbor_block[3]);
}
// neighbor_cull = m_world.is_block(glm::ivec3(world_x,
// world_y, world_z) + DIR[face]);
} else {
auto neighbor_id = m_blocks[index(nx, ny, nz)];
// transparent block
if (!BlockManager::is_transparent(neighbor_id)) {
neighbor_culled = true;
} else {
if (neighbor_id == cur_id) {
neighbor_culled = true;
} else {
neighbor_culled = false;
}
}
}
if (neighbor_culled) {
continue;
}
if (BlockManager::is_cross_plane(cur_id)) {
gen_cross_plane_vertices(world_x, world_y, world_z,
cur_id);
}
for (int i = 0; i < 6; i++) {
Vertex3D vex = {
VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
TEX_COORDS[face][i][0],
TEX_COORDS[face][i][1],
static_cast<float>(cur_id * 6 + face),
NORMALS[face][i][0],
NORMALS[face][i][1],
NORMALS[face][i][2],
BlockManager::roughness(cur_id),
TANGENTS[face][i][0],
TANGENTS[face][i][1],
TANGENTS[face][i][2]
};
if (BlockManager::is_transparent(cur_id)) {
if (BlockManager::is_discard(cur_id) &&
BlockManager::is_blend(cur_id)) {
Logger::warn(
"Block id {} is both discard and blend is "
"must only one can true !!!",
cur_id);
}
if (BlockManager::is_discard(cur_id)) {
m_vertex_data[2].m_vertices.emplace_back(vex);
} else if (BlockManager::is_blend(cur_id)) {
if (cur_id == 7) {
m_vertex_data[4].m_vertices.emplace_back(
vex);
} else {
m_vertex_data[3].m_vertices.emplace_back(
vex);
}
} else {
Logger::warn("Id {} is transparent but not "
"discard or blend",
cur_id);
m_vertex_data[3].m_vertices.emplace_back(vex);
}
} else {
m_vertex_data[0].m_vertices.emplace_back(vex);
}
}
}
}
}
}
}
*/
void Chunk::gen_vertices(const OptionalBlockVectorArray& neighbor_block) {
// SIZE_X=SIZE_Z=CHUNK_SIZE=16, SIZE_Y=WORLD_SIZE_Y=256
// Axis order: axis 0=X, 1=Y, 2=Z
// Two slice dimensions of each axis
const int DIMS[3] = {CHUNK_SIZE, WORLD_SIZE_Y, CHUNK_SIZE};
// Maximum mask size: max(16*256, 16*16) = 4096
static thread_local FaceKey mask[CHUNK_SIZE * WORLD_SIZE_Y];
static thread_local bool visited[CHUNK_SIZE * WORLD_SIZE_Y];
for (int axis = 0; axis < 3; axis++) {
int u_axis = (axis + 1) % 3; // horizontal
int v_axis = (axis + 2) % 3; // vertical
int u = DIMS[u_axis];
int v = DIMS[v_axis];
int d = DIMS[axis]; // Depth along the normal axis
for (int face_dir : {1, -1}) {
int face_idx = axis_dir_to_face(axis, face_dir);
for (int layer = 0; layer < d; layer++) {
// ── 1. Build mask ──────────────────────────────────────────
for (int vi = 0; vi < v; vi++) {
for (int ui = 0; ui < u; ui++) {
// Current cell local coordinates
int lpos[3];
lpos[axis] = layer;
lpos[u_axis] = ui;
lpos[v_axis] = vi;
// Neighbor (offset one cell along the normal direction)
int npos[3];
npos[axis] = layer + face_dir;
npos[u_axis] = ui;
npos[v_axis] = vi;
BlockType cur_id = get_block_safe(
lpos[0], lpos[1], lpos[2], m_chunk_pos, m_blocks,
neighbor_block);
// Air / cross plane are not involved in greedy meshing
if (cur_id == 0 ||
BlockManager::is_cross_plane(cur_id)) {
mask[vi * u + ui] = {};
continue;
}
BlockType nb_id = get_block_safe(
npos[0], npos[1], npos[2], m_chunk_pos, m_blocks,
neighbor_block);
if (is_face_culled(cur_id, nb_id)) {
mask[vi * u + ui] = {};
} else {
mask[vi * u + ui] = {cur_id, face_idx};
}
}
}
// ── 2. Greedy Merge ──────────────────────────────────────
std::fill(visited, visited + u * v, false);
for (int vi = 0; vi < v; vi++) {
for (int ui = 0; ui < u; ui++) {
if (visited[vi * u + ui])
continue;
FaceKey cur = mask[vi * u + ui];
if (!cur.valid())
continue;
// Extend width in the u direction
int w = 1;
while (ui + w < u && !visited[vi * u + (ui + w)] &&
mask[vi * u + (ui + w)] == cur) {
w++;
}
// Extend height in the v direction
int h = 1;
bool can_expand = true;
while (vi + h < v && can_expand) {
for (int k = 0; k < w; k++) {
int idx = (vi + h) * u + (ui + k);
if (visited[idx] || mask[idx] != cur) {
can_expand = false;
break;
}
}
if (can_expand)
h++;
}
// mark visited
for (int dv = 0; dv < h; dv++)
for (int du = 0; du < w; du++)
visited[(vi + dv) * u + (ui + du)] = true;
// output quad
emit_quad(axis, face_dir, layer, ui, vi, w, h, u_axis,
v_axis, cur);
}
}
}
}
}
for (int x = 0; x < CHUNK_SIZE; x++) {
for (int y = 0; y < WORLD_SIZE_Y; y++) {
for (int z = 0; z < CHUNK_SIZE; z++) {
BlockType id = m_blocks[index(x, y, z)];
if (id != 0 && BlockManager::is_cross_plane(id)) {
int world_x = x + m_chunk_pos.x * CHUNK_SIZE;
int world_z = z + m_chunk_pos.z * CHUNK_SIZE;
gen_cross_plane_vertices(world_x, y, world_z, id);
}
}
}
}
}
void Chunk::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) {
float axis_val = (float)(layer + (face_dir > 0 ? 1 : 0));
float wx_base = (float)(m_chunk_pos.x * CHUNK_SIZE);
float wz_base = (float)(m_chunk_pos.z * CHUNK_SIZE);
// Offsets of the four corners along the u_axis/v_axis
int su[4] = {0, w, w, 0};
int sv[4] = {0, 0, h, h};
// Each face's UV: directly read from the four corners of TEX_COORDS, then
// scaled by w/h TEX_COORDS vertex order: 0=BL, 1=TL, 2=TR, 3=TR, 4=BR, 5=BL
// (two triangles) Four unique corners correspond to indices: BL=0, TL=1,
// TR=2, BR=4 Extract the UVs of the four corners from TEX_COORDS (unique
// corners after removing duplicate vertices) Vertices 0,1,2,4 correspond to
// BL, TL, TR, BR
float u0 = TEX_COORDS[key.face][0][0]; // BL.u
float v0 = TEX_COORDS[key.face][0][1]; // BL.v
float u1 = TEX_COORDS[key.face][4][0]; // BR.u
float v1 = TEX_COORDS[key.face][4][1]; // BR.v
float u3 = TEX_COORDS[key.face][1][0]; // TL.u
float v3 = TEX_COORDS[key.face][1][1]; // TL.v
float du_u = u1 - u0; // Change in u when su increases (per block)
float dv_u = v1 - v0;
float du_v = u3 - u0; // Change in u when sv increases
float dv_v = v3 - v0;
float uvs[4][2] = {
{u0, v0}, // (0, 0 )
{u0 + du_u * (float)w, v0 + dv_u * (float)w}, // (w, 0 )
{u0 + du_u * (float)w + du_v * (float)h,
v0 + dv_u * (float)w + dv_v * (float)h}, // (w, h )
{u0 + du_v * (float)h, v0 + dv_v * (float)h}, // (0, h )
};
int tri[6] = {0, 1, 2, 0, 2, 3};
float pos[4][3];
for (int c = 0; c < 4; c++) {
pos[c][axis] = axis_val;
pos[c][u_axis] = (float)(i + su[c]);
pos[c][v_axis] = (float)(j + sv[c]);
pos[c][0] += wx_base;
pos[c][2] += wz_base;
}
float layer_id = (float)(key.block_id * 6 + key.face);
float roughness = BlockManager::roughness(key.block_id);
int buf = choose_buf(key.block_id);
for (int vi = 0; vi < 6; vi++) {
int c = tri[vi];
Vertex3D vex = {
pos[c][0],
pos[c][1],
pos[c][2],
uvs[c][0],
uvs[c][1],
layer_id,
NORMALS[key.face][0][0],
NORMALS[key.face][0][1],
NORMALS[key.face][0][2],
roughness,
TANGENTS[key.face][0][0],
TANGENTS[key.face][0][1],
TANGENTS[key.face][0][2],
};
m_vertex_data[buf].m_vertices.emplace_back(vex);
}
}
void Chunk::gen_cross_plane_vertices(int world_x, int world_y, int world_z,
BlockType id) {
if (!BlockManager::is_cross_plane(id)) {
Logger::warn("Block {} {} {} id {} is not cross plane", world_x,
world_y, world_z, id);
return;
}
for (int face = 0; face < 2; face++) {
for (int i = 0; i < 6; i++) {
Vertex3D vex = {
CROSS_VERTICES_POS[face][i][0] + (float)world_x * 1.0f,
CROSS_VERTICES_POS[face][i][1] + (float)world_y * 1.0f,
CROSS_VERTICES_POS[face][i][2] + (float)world_z * 1.0f,
CROSS_TEX_COORDS[face][i][0],
CROSS_TEX_COORDS[face][i][1],
static_cast<float>(BlockManager::cross_plane_index(id)),
CROSS_NORMALS[face][i][0],
CROSS_NORMALS[face][i][1],
CROSS_NORMALS[face][i][2],
BlockManager::roughness(id),
CROSS_TANGENTS[face][i][0],
CROSS_TANGENTS[face][i][1],
CROSS_TANGENTS[face][i][2]
};
m_vertex_data[1].m_vertices.emplace_back(vex);
}
}
}
void Chunk::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<Chunk> 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_three();
chunk.gen_phase_five();
chunk.gen_phase_seven();
}
gen_phase_one();
gen_phase_three();
gen_phase_five();
OptionalBlockVectorArray neightbor_blocks;
for (int i = 0; i < 4; i++) {
neightbor_blocks[i] = neighbor[i].get_chunk_blocks();
}
gen_phase_six(neightbor_blocks);
gen_phase_seven();
for (int i = 0; i < 4; i++) {
neightbor_blocks[i] = neighbor[i].get_chunk_blocks();
}
gen_vertex_data(neightbor_blocks);
// collect chunk info for debugging
m_info.biome = m_biome;
m_info.pos = m_chunk_pos;
m_info.seed = m_seed;
Random r(m_seed);
unsigned first = r.engine()();
m_info.first_random = first;
r.init(m_seed);
m_info.has_cave_start = r.random_bool(DEFAULT_CAVE_PROBABILITY);
m_info.has_cave = m_has_cave;
}
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());
bool Chunk::is_temp_chunk() const { return m_temp_chunk.load(); }
bool& Chunk::has_cave() { return m_has_cave; }
} // namespace Cubed

View File

@@ -1,544 +0,0 @@
#include "Cubed/config.hpp"
#include "Cubed/debug_collector.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/log.hpp"
#include <GLFW/glfw3.h>
namespace Cubed {
Player::Player(World& world, const std::string& name)
: m_name(name), m_world(world) {
hot_reload();
}
Player::~Player() {}
AABB Player::get_aabb() const {
float half_width = m_size.x / 2.0f;
float half_depth = m_size.z / 2.0f;
glm::vec3 min{m_player_pos.x - half_width, m_player_pos.y,
m_player_pos.z - half_depth};
glm::vec3 max{m_player_pos.x + half_width, m_player_pos.y + m_size.y,
m_player_pos.z + half_depth};
return AABB{min, max};
}
const glm::vec3& Player::get_front() const { return m_front; }
const Gait& Player::get_gait() const { return m_gait; }
const std::optional<LookBlock>& Player::get_look_block_pos() const {
return m_look_block;
}
const glm::vec3& Player::get_player_pos() const { return m_player_pos; }
const MoveState& Player::get_move_state() const { return m_move_state; }
bool Player::ray_cast(const glm::vec3& start, const glm::vec3& front,
glm::ivec3& block_pos, glm::vec3& normal,
float distance) {
glm::vec3 dir = glm::normalize(front);
// float step = 0.1f;
glm::ivec3 cur = glm::floor(start);
int ix = cur.x;
int iy = cur.y;
int iz = cur.z;
// step direction
int step_x = (dir.x > 0) ? 1 : ((dir.x < 0) ? -1 : 0);
int step_y = (dir.y > 0) ? 1 : ((dir.y < 0) ? -1 : 0);
int step_z = (dir.z > 0) ? 1 : ((dir.z < 0) ? -1 : 0);
static const float INF = std::numeric_limits<float>::infinity();
float t_delta_x = (dir.x != 0) ? std::fabs(1.0f / dir.x) : INF;
float t_delta_y = (dir.y != 0) ? std::fabs(1.0f / dir.y) : INF;
float t_delta_z = (dir.z != 0) ? std::fabs(1.0f / dir.z) : INF;
float t_max_x, t_max_y, t_max_z;
if (dir.x > 0) {
t_max_x = (static_cast<float>(ix) + 1.0f - start.x) / dir.x;
} else if (dir.x < 0) {
t_max_x = (start.x - static_cast<float>(ix)) / (-dir.x);
} else {
t_max_x = INF;
}
if (dir.y > 0) {
t_max_y = (static_cast<float>(iy) + 1.0f - start.y) / dir.y;
} else if (dir.y < 0) {
t_max_y = (start.y - static_cast<float>(iy)) / (-dir.y);
} else {
t_max_y = INF;
}
if (dir.z > 0) {
t_max_z = (static_cast<float>(iz) + 1.0f - start.z) / dir.z;
} else if (dir.z < 0) {
t_max_z = (start.z - static_cast<float>(iz)) / (-dir.z);
} else {
t_max_z = INF;
}
float t = 0.0f;
normal = glm::vec3(0.0f, 0.0f, 0.0f);
while (t <= distance) {
if (m_world.is_solid(glm::ivec3(ix, iy, iz))) {
block_pos = glm::ivec3(ix, iy, iz);
return true;
}
if (t_max_x < t_max_y && t_max_x < t_max_z) {
t = t_max_x;
t_max_x += t_delta_x;
normal = glm::vec3(-step_x, 0.0f, 0.0f);
ix += step_x;
} else if (t_max_y < t_max_z) {
t = t_max_y;
t_max_y += t_delta_y;
normal = glm::vec3(0.0f, -step_y, 0.0f);
iy += step_y;
} else {
t = t_max_z;
t_max_z += t_delta_z;
normal = glm::vec3(0.0f, 0.0f, -step_z);
iz += step_z;
}
}
return false;
}
void Player::change_mode(GameMode mode) {
m_game_mode = mode;
Logger::info("Change GameMode to {}", to_str(mode));
if (mode == CREATIVE) {
is_fly = false;
m_gait = Gait::WALK;
} else if (mode == SPECTATOR) {
is_fly = true;
m_gait = Gait::RUN;
m_max_speed = m_max_run_speed;
}
}
void Player::hot_reload() {
auto& config = Config::get();
m_sensitivity =
static_cast<float>(config.get<double>("player.mouse_sensitivity"));
}
void Player::set_player_pos(const glm::vec3& pos) { m_player_pos = pos; }
void Player::set_place_block(unsigned id) { m_place_block = id; }
void Player::update(float delta_time) {
update_move(delta_time);
update_lookup_block();
check_player_chunk_transition();
DebugCollector::get().report("player_pos",
std::format("x: {:.2f} y: {:.2f} z: {:.2f}",
m_player_pos.x, m_player_pos.y,
m_player_pos.z));
DebugCollector::get().report("speed",
std::format("Speed: {:.2} m/s", m_xz_speed));
}
void Player::update_player_move_state(int key, int action) {
switch (key) {
case GLFW_KEY_W:
if (action == GLFW_PRESS) {
m_move_state.forward = true;
}
if (action == GLFW_RELEASE) {
m_move_state.forward = false;
if (m_game_mode != SPECTATOR) {
m_gait = Gait::WALK;
}
}
break;
case GLFW_KEY_S:
if (action == GLFW_PRESS) {
m_move_state.back = true;
}
if (action == GLFW_RELEASE) {
m_move_state.back = false;
}
break;
case GLFW_KEY_A:
if (action == GLFW_PRESS) {
m_move_state.left = true;
}
if (action == GLFW_RELEASE) {
m_move_state.left = false;
}
break;
case GLFW_KEY_D:
if (action == GLFW_PRESS) {
m_move_state.right = true;
}
if (action == GLFW_RELEASE) {
m_move_state.right = false;
}
break;
case GLFW_KEY_SPACE:
if (action == GLFW_PRESS) {
m_move_state.up = true;
if (space_on) {
if (m_game_mode == CREATIVE) {
is_fly = !is_fly ? true : false;
m_y_speed = 0.0f;
}
space_on = false;
space_on_time = 0.0f;
} else {
space_on = true;
}
}
if (action == GLFW_RELEASE) {
m_move_state.up = false;
}
break;
case GLFW_KEY_LEFT_SHIFT:
if (action == GLFW_PRESS) {
m_move_state.down = true;
}
if (action == GLFW_RELEASE) {
m_move_state.down = false;
}
break;
case GLFW_KEY_LEFT_CONTROL:
if (action == GLFW_PRESS) {
m_gait = Gait::RUN;
}
break;
case GLFW_KEY_F4:
if (action == GLFW_PRESS) {
if (m_game_mode == CREATIVE) {
change_mode(SPECTATOR);
} else {
change_mode(CREATIVE);
}
}
break;
}
}
void Player::update_front_vec(float offset_x, float offset_y) {
m_yaw += offset_x * m_sensitivity;
m_pitch += offset_y * m_sensitivity;
m_yaw = std::fmod(m_yaw, 360.0);
m_pitch = std::clamp(m_pitch, -89.0f, 89.0f);
m_front.x = sin(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
m_front.y = sin(glm::radians(m_pitch));
m_front.z = -cos(glm::radians(m_yaw)) * cos(glm::radians(m_pitch));
m_front = glm::normalize(m_front);
}
void Player::check_player_chunk_transition() {
ChunkPos cur_pos = m_world.get_chunk_pos(m_player_pos.x, m_player_pos.z);
if (cur_pos != m_player_chunk_pos) {
m_world.need_gen();
m_player_chunk_pos = cur_pos;
}
}
void Player::update_direction() {
m_right = glm::normalize(glm::cross(m_front, glm::vec3(0.0f, 1.0f, 0.0f)));
glm::vec3 move_dir_front = glm::vec3(0.0f);
glm::vec3 move_dir_right = glm::vec3(0.0f);
glm::vec3 move_dir = glm::vec3(0.0f);
if (m_move_state.forward) {
move_dir_front += glm::normalize(glm::vec3(m_front.x, 0.0f, m_front.z));
}
if (m_move_state.back) {
move_dir_front -= glm::normalize(glm::vec3(m_front.x, 0.0f, m_front.z));
}
if (m_move_state.left) {
move_dir_right -= glm::normalize(glm::vec3(m_right.x, 0.0f, m_right.z));
}
if (m_move_state.right) {
move_dir_right += glm::normalize(glm::vec3(m_right.x, 0.0f, m_right.z));
}
move_dir = move_dir_front + move_dir_right;
if (glm::length(move_dir) > 0.001f) {
direction = glm::normalize(move_dir);
}
}
void Player::update_lookup_block() {
// calculate the block that is looked
glm::ivec3 block_pos;
glm::vec3 block_normal;
if (ray_cast(
glm::vec3(m_player_pos.x, (m_player_pos.y + 1.6f), m_player_pos.z),
m_front, block_pos, block_normal)) {
m_look_block = LookBlock{block_pos, glm::floor(block_normal)};
} else {
m_look_block = std::nullopt;
}
if (m_look_block != std::nullopt) {
if (Input::get_input_state().mouse_state.left) {
if (m_world.is_solid(m_look_block->pos)) {
m_world.set_block(m_look_block->pos, 0);
}
Input::get_input_state().mouse_state.left = false;
}
if (Input::get_input_state().mouse_state.right) {
glm::ivec3 near_pos = m_look_block->pos + m_look_block->normal;
if (!m_world.is_solid(near_pos)) {
auto x = near_pos.x;
auto y = near_pos.y;
auto z = near_pos.z;
AABB block_box = {glm::vec3{static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
AABB player_box = get_aabb();
if (!player_box.intersects(block_box)) {
m_world.set_block(near_pos, m_place_block);
}
}
Input::get_input_state().mouse_state.right = false;
}
}
}
void Player::update_move(float delta_time) {
// if frame rate less than 1 frame per second, don't update
if (delta_time > 1.0f) {
return;
}
if (m_game_mode != SPECTATOR) {
if (m_gait == Gait::RUN) {
m_max_speed = m_max_run_speed;
}
if (m_gait == Gait::WALK) {
m_max_speed = m_max_walk_speed;
}
}
if (space_on) {
space_on_time += delta_time;
if (space_on_time >= MAX_SPACE_ON_TIME) {
space_on = false;
space_on_time = 0.0f;
}
}
// calculate speed
if (m_move_state.forward || m_move_state.back || m_move_state.left ||
m_move_state.right || m_move_state.up) {
direction = glm::vec3(0.0f, 0.0f, 0.0f);
m_xz_speed += m_acceleration * delta_time;
if (m_xz_speed > m_max_speed) {
m_xz_speed = m_max_speed;
}
} else {
m_xz_speed += -m_deceleration * delta_time;
if (m_xz_speed < 0) {
m_xz_speed = 0;
direction = glm::vec3(0.0f, 0.0f, 0.0f);
}
}
update_direction();
move_distance = {direction.x * m_xz_speed * delta_time, 0.0f,
direction.z * m_xz_speed * delta_time};
if (is_fly) {
if (m_move_state.up) {
m_y_speed = m_fly_y_speed;
}
if (m_move_state.down) {
m_y_speed = -m_fly_y_speed;
}
if (!m_move_state.down && !m_move_state.up) {
m_y_speed = 0.0f;
}
} else {
if (m_move_state.up && can_up) {
m_y_speed = 7.5;
can_up = false;
}
m_y_speed += -m_g * delta_time;
}
move_distance.y = m_y_speed * delta_time;
// y
update_y_move();
// x
update_x_move();
update_z_move();
if (m_player_pos.y < -15.0f) {
Logger::warn("y is tow low");
m_player_pos += glm::vec3(1.0f, 100.0f, 1.0f);
}
}
void Player::update_x_move() {
m_player_pos.x += move_distance.x;
if (m_game_mode == SPECTATOR) {
return;
}
AABB player_box = get_aabb();
int minx = std::floor(player_box.min.x);
int maxx = std::floor(player_box.max.x);
int miny = std::floor(player_box.min.y);
int maxy = std::floor(player_box.max.y);
int minz = std::floor(player_box.min.z);
int maxz = std::floor(player_box.max.z);
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
if (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
AABB block_box = {glm::vec3{static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
if (player_box.intersects(block_box)) {
m_gait = Gait::WALK;
m_player_pos.x -= move_distance.x;
return;
}
}
}
}
}
}
void Player::update_y_move() {
m_player_pos.y += move_distance.y;
if (m_game_mode == SPECTATOR) {
return;
}
AABB player_box = get_aabb();
int minx = std::floor(player_box.min.x);
int maxx = std::floor(player_box.max.x);
int miny = std::floor(player_box.min.y);
int maxy = std::floor(player_box.max.y);
int minz = std::floor(player_box.min.z);
int maxz = std::floor(player_box.max.z);
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
if (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
AABB block_box = {glm::vec3{static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
if (player_box.intersects(block_box)) {
m_player_pos.y -= move_distance.y;
m_y_speed = 0.0f;
if (move_distance.y < 0) {
can_up = true;
is_fly = false;
}
return;
}
}
}
}
}
}
void Player::update_z_move() {
m_player_pos.z += move_distance.z;
if (m_game_mode == SPECTATOR) {
return;
}
AABB player_box = get_aabb();
int minx = std::floor(player_box.min.x);
int maxx = std::floor(player_box.max.x);
int miny = std::floor(player_box.min.y);
int maxy = std::floor(player_box.max.y);
int minz = std::floor(player_box.min.z);
int maxz = std::floor(player_box.max.z);
for (int x = minx; x <= maxx; ++x) {
for (int y = miny; y <= maxy; ++y) {
for (int z = minz; z <= maxz; ++z) {
if (!m_world.can_pass_block(glm::ivec3{x, y, z})) {
AABB block_box = {glm::vec3{static_cast<float>(x),
static_cast<float>(y),
static_cast<float>(z)},
glm::vec3{static_cast<float>(x + 1),
static_cast<float>(y + 1),
static_cast<float>(z + 1)}};
if (player_box.intersects(block_box)) {
m_gait = Gait::WALK;
m_player_pos.z -= move_distance.z;
return;
}
}
}
}
}
}
void Player::update_scroll(double yoffset) {
if (m_game_mode == SPECTATOR) {
if (yoffset > 0) {
if (m_max_speed < 500.0f) {
m_max_speed += 1.0f;
}
} else {
if (m_max_speed > 1.0f) {
m_max_speed -= 1.0f;
}
}
}
if (m_game_mode == CREATIVE) {
if (yoffset < 0) {
m_place_block += 1;
if (m_place_block >= BlockManager::sums()) {
m_place_block = 1;
}
} else {
m_place_block -= 1;
if (m_place_block <= 0) {
m_place_block = BlockManager::sums() - 1;
}
}
}
}
float& Player::max_walk_speed() { return m_max_walk_speed; }
float& Player::max_run_speed() { return m_max_run_speed; }
float& Player::max_speed() { return m_max_speed; }
float& Player::acceleration() { return m_acceleration; }
float& Player::deceleration() { return m_deceleration; }
float& Player::g() { return m_g; }
float& Player::fly_y_speed() { return m_fly_y_speed; }
unsigned Player::place_block() const { return m_place_block; };
Gait& Player::gait() { return m_gait; }
GameMode& Player::game_mode() { return m_game_mode; }
const World& Player::get_world() const { return m_world; }
} // namespace Cubed

View File

@@ -1,702 +0,0 @@
#include "Cubed/config.hpp"
#include "Cubed/gameplay/player.hpp"
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include "Cubed/tools/cubed_hash.hpp"
#include <glm/gtc/constants.hpp>
#include <numbers>
#include <utility>
using namespace std::chrono;
using namespace std::chrono_literals;
namespace Cubed {
struct ChunkRenderData {
std::array<const std::vector<BlockType>*, 4> neighbor_block;
Chunk* chunk;
};
World::World() {}
World::~World() {
stop_gen_thread();
stop_server_thread();
wait_all_chunk_tasks();
stop_thread_pool();
m_chunks.clear();
{
std::lock_guard lk(m_delete_vbo_mutex);
for (auto x : m_pending_delete_vbo) {
glDeleteBuffers(1, &x);
}
m_pending_delete_vbo.clear();
}
{
std::lock_guard lk(m_delete_vao_mutex);
for (auto x : m_pending_delete_vao) {
glDeleteVertexArrays(1, &x);
}
m_pending_delete_vao.clear();
}
}
void World::wait_all_chunk_tasks() {
for (auto& [pos, task] : new_chunks) {
task.future.get();
}
}
bool World::can_move(const AABB& player_box) const { return true; }
const std::optional<LookBlock>&
World::get_look_block_pos(const std::string& name) const {
static std::optional<LookBlock> null_look_block = std::nullopt;
auto it = m_players.find(HASH::str(name));
if (it == m_players.end()) {
Logger::error("Can't find player {}", name);
ASSERT(0);
return null_look_block;
}
return it->second.get_look_block_pos();
}
/*
const Chunk* World::get_chunk(const ChunkPos& pos) const {
std::lock_guard lk(m_chunks_mutex);
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
return nullptr;
}
return &it->second;
}*/
Player& World::get_player(const std::string& name) {
auto it = m_players.find(HASH::str(name));
if (it == m_players.end()) {
Logger::error("Can't find player {}", name);
ASSERT(0);
}
return it->second;
}
void World::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<std::chrono::milliseconds>(t2 - t1);
Logger::info("Chunk Block Init Finish, Time Consuming: {}", d);
start_server_thread();
Logger::info("TestPlayer Create Finish");
}
void World::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));
}
}
ChunkPos World::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};
}
#pragma region ChunkGenerate
void World::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<ChunkPos> 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, Chunk(*this, pos));
}
submit_new_chunks();
m_chunk_gen_finished = true;
}
void World::sync_player_pos(glm::vec3& player_pos) {
std::lock_guard lk(m_gen_player_pos_mutex);
player_pos = m_gen_player_pos;
}
void World::compute_required_chunks(ChunkPosSet& required_chunks) {
glm::vec3 player_pos;
sync_player_pos(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 World::sync_and_collect_missing_chunks(
std::vector<ChunkPos>& 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 World::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<std::pair<ChunkPos, PendingChunk*>> 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 World::poll_finished_chunks() {
m_new_finished_chunk.clear();
std::lock_guard lock(m_new_chunk_mutex);
std::erase_if(
new_chunks, [&](std::pair<const ChunkPos, PendingChunk>& 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;
});
}
#pragma endregion
void World::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<std::mutex> 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 World::start_server_thread() {
m_server_thread = std::thread(
[this]() { serever_run(m_server_stop_source.get_token()); });
}
void World::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 World::stop_server_thread() {
m_server_stop_source.request_stop();
if (m_server_thread.joinable()) {
m_server_thread.join();
}
}
void World::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 World::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 World::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;
}
}
Logger::info("Server Thread Stopped!");
}
void World::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();
}
m_need_gen_chunk = true;
m_gen_cv.notify_one();
}
int World::get_block(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (it == m_chunks.end()) {
return 0;
}
const auto& chunk_blocks = it->second.get_chunk_blocks();
auto [x, y, z] = Chunk::world_to_block(block_pos, {chunk_x, chunk_z});
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return 0;
}
return chunk_blocks[Chunk::index(x, y, z)];
}
bool World::is_solid(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (it == m_chunks.end()) {
return false;
}
const auto& chunk_blocks = it->second.get_chunk_blocks();
auto [x, y, z] = Chunk::world_to_block(block_pos, {chunk_x, chunk_z});
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return false;
}
auto id = chunk_blocks[Chunk::index(x, y, z)];
if (BlockManager::is_gas(id) || BlockManager::is_liquid(id)) {
return false;
} else {
return true;
}
}
bool World::can_pass_block(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (it == m_chunks.end()) {
return true;
}
const auto& chunk_blocks = it->second.get_chunk_blocks();
auto [x, y, z] = Chunk::world_to_block(block_pos, {chunk_x, chunk_z});
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
return true;
}
auto id = chunk_blocks[Chunk::index(x, y, z)];
return BlockManager::is_passable(id);
}
BlockType World::get_block_tpye(const glm::ivec3& block_pos) const {
auto [chunk_x, chunk_z] = get_chunk_pos(block_pos.x, block_pos.z);
std::shared_lock lk(m_chunks_mutex);
auto it = m_chunks.find(ChunkPos{chunk_x, chunk_z});
if (it == m_chunks.end()) {
// Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y,
// block_pos.z);
return 0;
}
const auto& chunk_blocks = it->second.get_chunk_blocks();
auto [x, y, z] = Chunk::world_to_block(block_pos, {chunk_x, chunk_z});
if (x < 0 || y < 0 || z < 0 || x >= CHUNK_SIZE || y >= WORLD_SIZE_Y ||
z >= CHUNK_SIZE) {
// Logger::error("Can't Find Block {} {} {}", block_pos.x, block_pos.y,
// block_pos.z);
return 0;
}
return chunk_blocks[Chunk::index(x, y, z)];
}
void World::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] =
Chunk::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(Chunk::index(x, y, z), id);
static const glm::ivec3 NEIGHBOR_DIRS[] = {
{1, 0, 0}, {-1, 0, 0}, {0, 0, -1}, {0, 0, 1}};
for (const auto& dir : NEIGHBOR_DIRS) {
glm::ivec3 neighbor = block_pos + dir;
auto [cx, cz] = get_chunk_pos(neighbor.x, neighbor.z);
auto it = m_chunks.find({cx, cz});
if (it != m_chunks.end()) {
it->second.mark_dirty();
}
}
}
void World::update(float delta_time) {
for (auto& player : m_players) {
player.second.update(delta_time);
}
{
std::lock_guard lk(m_delete_vbo_mutex);
for (auto x : m_pending_delete_vbo) {
glDeleteBuffers(1, &x);
}
m_pending_delete_vbo.clear();
}
{
std::lock_guard lk(m_delete_vao_mutex);
for (auto x : m_pending_delete_vao) {
glDeleteVertexArrays(1, &x);
}
m_pending_delete_vao.clear();
}
poll_finished_chunks();
for (auto& x : m_new_finished_chunk) {
x.second.upload_to_gpu();
}
// unified compute vertex data before rendering
{
std::lock_guard lk(m_chunks_mutex);
bool consumed = false;
for (auto& x : m_new_finished_chunk) {
m_chunks.insert_or_assign(x.first, std::move(x.second));
consumed = true;
}
if (consumed) {
m_could_gen = true;
}
m_render_snapshots.clear();
for (auto& [pos, chunk] : m_chunks) {
if (chunk.is_dirty()) {
// the curial fator influence
OptionalBlockVectorArray neighbor_block;
for (int i = 0; i < 4; i++) {
auto it = m_chunks.find(pos + CHUNK_DIR[i]);
if (it != m_chunks.end()) {
neighbor_block[i] = (it->second.get_chunk_blocks());
} else {
neighbor_block[i] = std::nullopt;
}
}
chunk.gen_vertex_data(neighbor_block);
chunk.upload_to_gpu();
}
if (!chunk.is_dirty()) {
if (chunk.is_need_upload()) {
chunk.upload_to_gpu();
}
m_render_snapshots.push_back(
{chunk.get_normal_vao(), chunk.get_normal_vertices_sum(),
chunk.get_cross_vao(), chunk.get_cross_vertices_sum(),
chunk.get_normal_discard_vao(),
chunk.get_normal_discard_vertices_sum(),
chunk.get_normal_blend_vao(),
chunk.get_normal_blend_vertices_sum(),
chunk.get_water_vao(), chunk.get_water_vertices_sum(),
glm::vec3(static_cast<float>(pos.x * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(pos.z * CHUNK_SIZE) +
static_cast<float>(CHUNK_SIZE / 2)),
glm::vec3(static_cast<float>(CHUNK_SIZE / 2),
static_cast<float>(WORLD_SIZE_Y / 2),
static_cast<float>(CHUNK_SIZE / 2))});
}
}
}
}
void World::push_delete_vbo(GLuint vbo) {
std::lock_guard lk(m_delete_vbo_mutex);
m_pending_delete_vbo.push_back(vbo);
}
void World::push_delete_vao(GLuint vao) {
std::lock_guard lk(m_delete_vao_mutex);
m_pending_delete_vao.push_back(vao);
}
void World::hot_reload() {
auto& config = Config::get();
int dist = config.get<int>("world.rendering_distance");
m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE;
need_gen();
}
void World::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;
}
/*
glm::vec3 World::sunlight_dir() const {
float t = static_cast<float>(m_day_tick) / DAY_TIME;
float azimuth = glm::radians(90.0f - t * 360.0f);
float altitude =
glm::half_pi<float>() * sin((t - 0.25f) * glm::two_pi<float>());
glm::vec3 dir{cos(altitude) * cos(azimuth), sin(altitude),
cos(altitude) * sin(azimuth)};
return glm::normalize(-dir);
}
*/
glm::vec3 World::sunlight_dir() const {
float altitude = sin((m_day_tick - 6 * PER_HOUR) /
static_cast<float>(DAY_TIME / 2) * std::numbers::pi) *
90.0f;
float t = static_cast<float>(m_day_tick) / DAY_TIME;
float azimuth = 90.0f - 360.0f * (t - 0.25f);
float alt = glm::radians(altitude);
float az = glm::radians(azimuth);
glm::vec3 dir;
dir.x = cos(alt) * sin(az);
dir.y = sin(alt);
dir.z = cos(alt) * cos(az);
return glm::normalize(-dir);
}
int World::rendering_distance() const { return m_rendering_distance.load(); }
void World::rendering_distance(int rendering_distance) {
m_rendering_distance = rendering_distance;
}
CaveCarver& World::cave_carcer() { return m_cave_carcer; }
RiverWorm& World::river_worm() { return m_river_worm; }
std::vector<glm::vec4>& World::planes() { return m_planes; }
std::vector<ChunkRenderSnapshot>& World::render_snapshots() {
return m_render_snapshots;
};
TickType World::game_tick() const { return m_game_ticks.load(); }
TickType World::day_tick() const { return m_day_tick.load(); }
void World::day_tick(TickType tick) {
tick %= DAY_TIME;
m_day_tick = tick;
}
int World::per_tick_time() const { return m_per_tick_time.load(); }
void World::per_tick_time(int ms) { m_per_tick_time = ms; }
bool World::is_tick_running() const { return m_tick_running.load(); }
void World::tick_running(bool run) { m_tick_running = run; }
int World::pool_threads() const { return m_pool_threads.load(); }
int World::max_threads() const { return m_max_threads.load(); }
void World::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<ThreadPool>(used_thread));
m_pool_threads = used_thread;
}
int World::chunk_load_style() const {
return std::to_underlying(m_chunk_load_style.load());
}
void World::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);
}
ChunkInfo World::get_chunk_info(const glm::vec3& world_pos) const {
ChunkPos pos = get_chunk_pos(world_pos.x, world_pos.z);
std::shared_lock lock(m_chunks_mutex);
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
return ChunkInfo{};
}
return it->second.get_info();
}
} // namespace Cubed

View File

@@ -13,7 +13,9 @@ ServerChunk::ServerChunk(ServerChunk&& other) noexcept
m_world(other.m_world), m_heightmap(std::move(other.m_heightmap)),
m_blocks(std::move(other.m_blocks)),
m_neightbor_blocks(std::move(other.m_neightbor_blocks)),
m_seed(other.m_seed), m_conditions(other.m_conditions) {}
m_seed(other.m_seed), m_conditions(other.m_conditions) {
ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move");
}
ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept {
// Logger::info("other Chunk pos {} {} in Chunk& Chunk::operator=(Chunk&&
@@ -22,6 +24,7 @@ ServerChunk& ServerChunk::operator=(ServerChunk&& other) noexcept {
if (this == &other) {
return *this;
}
ASSERT_MSG(!other.m_gening, "Other is Gening Can't Move");
m_chunk_pos = std::move(other.m_chunk_pos);
m_heightmap = std::move(other.m_heightmap);
m_blocks = std::move(other.m_blocks);
@@ -145,10 +148,13 @@ void ServerChunk::gen_chunk() {
if (m_gening.exchange(true))
return;
m_gening = true;
ASSERT_MSG(m_blocks.empty(),
"Blocks isn't Empty, chunk already generated!");
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);
return;
}
std::vector<ServerChunk> neighbor;
for (int i = 0; i < 4; i++) {
@@ -169,6 +175,7 @@ void ServerChunk::gen_chunk() {
}
gen_phase_four(m_neightbor_blocks);
gen_phase_five();
m_gening = false;
}
// Logger::info("Cross Sum {}", m_cross_vertices_sum.load());

View File

@@ -34,4 +34,23 @@ bool ServerPlayer::is_disconnect(TickType current_gametick) const {
int ServerPlayer::task_id() const { return m_chunk_task_id.load(); }
void ServerPlayer::task_id(int id) { m_chunk_task_id = id; }
bool ServerPlayer::has_player(ChunkPos pos) const {
std::shared_lock lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set.find(pos) != m_player_chunk_pos_set.end();
}
void ServerPlayer::update_chunk_set(const ChunkPosSet& set) {
std::lock_guard lock(m_chunk_pos_mutex);
m_player_chunk_pos_set.clear();
m_player_chunk_pos_set.insert(set.begin(), set.end());
}
const ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() const {
std::shared_lock lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set;
}
ServerPlayer::ChunkPosSet& ServerPlayer::get_chunk_pos_set() {
std::lock_guard lock(m_chunk_pos_mutex);
return m_player_chunk_pos_set;
}
} // namespace Cubed

View File

@@ -7,6 +7,7 @@
#include "Cubed/tools/log.hpp"
#include "Cubed/tools/uuid.hpp"
#include <ranges>
#include <utility>
using namespace std::chrono;
using namespace std::chrono_literals;
@@ -15,10 +16,16 @@ using namespace google::protobuf;
namespace Cubed {
ServerWorld::ServerWorld() {}
ServerWorld::~ServerWorld() {
ServerWorld::~ServerWorld() { stop(); }
void ServerWorld::stop() {
if (!m_init) {
return;
}
if (m_stopped.exchange(true)) {
return;
}
send_server_stop();
stop_gen_thread();
stop_server_thread();
wait_all_chunk_tasks();
@@ -32,7 +39,56 @@ ServerWorld::~ServerWorld() {
void ServerWorld::wait_all_chunk_tasks() {
std::lock_guard lock(m_new_chunk_mutex);
for (auto& [pos, task] : m_new_chunks) {
task.future.get();
if (task.future.valid()) {
try {
task.future.get();
} catch (const std::exception& e) {
Logger::error("Chunk generation failed: {}", e.what());
continue;
}
} else {
Logger::error("Chunk {} {} not started gen task", pos.x, pos.z);
}
}
}
void ServerWorld::update_ref_count(const ChunkPosSet& old,
const ChunkPosSet& now) {
std::lock_guard lock(m_chunks_mutex);
// Elements in the old set that are not contained in now are not needed by
// the current player.
for (auto& pos : old) {
if (!now.contains(pos)) {
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
Logger::warn(
"Update Ref Count Error, can't Find old pos in m_chunks");
continue;
}
if (it->second.ref_count == 0) {
Logger::error("Chunk {} {} error, ref count is 0", pos.x,
pos.z);
m_chunks.erase(pos);
continue;
}
if (--it->second.ref_count == 0) {
m_chunks.erase(pos);
}
}
}
for (auto& pos : now) {
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
Logger::warn(
"Update Ref Count Error, can't Find now pos in m_chunks");
continue;
}
if (!old.contains(pos)) {
++it->second.ref_count;
}
}
}
@@ -48,6 +104,89 @@ void ServerWorld::send_time() {
}
}
void ServerWorld::send_chunk(int task_id, const std::string& uuid,
ChunkPos pos) {
{
std::shared_lock lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it == m_players.end()) {
return;
}
if (task_id < it->second.task_id()) {
// Old chunk requests are simply discarded
return;
}
}
Arena arean;
ChunkDataRsp* rsp = Arena::Create<ChunkDataRsp>(&arean);
auto* rsq_pos = rsp->mutable_pos();
rsq_pos->set_x(pos.x);
rsq_pos->set_z(pos.z);
{
std::shared_lock lock(m_chunks_mutex);
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
// No chunk found and not generating
Logger::error("Chunk {} {} neither pending nor ready", pos.x,
pos.z);
return;
}
if (it->second.state == ChunkState::GENERATING) {
m_waiting_chunk_requests.emplace(uuid, task_id, pos);
return;
}
if (it->second.state != ChunkState::READY) {
Logger::error("Chunk {} {} is invaild", pos.x, pos.z);
return;
}
rsp->set_chunk_seed(it->second.chunk->seed());
rsp->set_biome_type(std::to_underlying(it->second.chunk->biome()));
auto* blocks = rsp->mutable_chunk_blocks();
auto& chunk_blocks = it->second.chunk->get_chunk_blocks();
blocks->Assign(chunk_blocks.begin(), chunk_blocks.end());
auto& neighbor_blocks = it->second.chunk->get_neightbor_blocks();
auto assign = [](auto* nb,
const std::optional<std::vector<BlockType>>& blocks) {
if (!blocks) {
return;
}
if (!nb) {
return;
}
nb->Assign(blocks->begin(), blocks->end());
};
auto* nb1 = rsp->mutable_neighbor_blocks_1();
auto* nb2 = rsp->mutable_neighbor_blocks_2();
auto* nb3 = rsp->mutable_neighbor_blocks_3();
auto* nb4 = rsp->mutable_neighbor_blocks_4();
assign(nb1, neighbor_blocks[0]);
assign(nb2, neighbor_blocks[1]);
assign(nb3, neighbor_blocks[2]);
assign(nb4, neighbor_blocks[3]);
}
std::shared_ptr<Session> s;
{
std::shared_lock lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it != m_players.end()) {
s = it->second.get_session();
it->second.update_sync_gametick(m_game_ticks);
}
}
if (!s) {
Logger::error("Player {} session not exist", uuid);
return;
}
rsp->set_task_id(task_id);
s->send(make_packet(*rsp));
}
void ServerWorld::init_world() {
register_timer("player disconnect", 5, [this]() {
@@ -64,6 +203,13 @@ void ServerWorld::init_world() {
handle_player_exit(uuid);
}
});
// Periodically process pending players
register_timer("player chunk send", 1, [this]() {
PendingRequest request;
if (m_waiting_chunk_requests.try_pop(request)) {
handle_chunk_req(request.task_id, request.uuid, request.pos);
}
});
m_cave_carcer.init(ChunkGenerator::seed());
m_river_worm.init(ChunkGenerator::seed());
@@ -84,30 +230,42 @@ void ServerWorld::init_world() {
void ServerWorld::init_chunks() { hot_reload(); }
void ServerWorld::gen_chunks_internal(std::optional<std::string> uuid) {
void ServerWorld::gen_chunks_internal(const std::string& uuid) {
// Logger::info("gen_chunks_internal");
m_chunk_gen_finished = false;
ChunkPosSet required_chunks;
compute_required_chunks(required_chunks, uuid);
ASSERT_MSG(!required_chunks.empty(), "required chunks is empty!!");
ChunkPosSet required_chunks_set;
compute_required_chunks(required_chunks_set, uuid);
std::vector<ChunkPos> need_gen_chunks_pos;
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks);
ChunkPosSet old_set;
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks_set);
{
std::lock_guard lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it == m_players.end()) {
return;
}
old_set = std::move(it->second.get_chunk_pos_set());
it->second.update_chunk_set(required_chunks_set);
}
update_ref_count(old_set, required_chunks_set);
ASSERT_MSG(!required_chunks_set.empty(), "required chunks is empty!!");
Logger::info("New Gen Chunks Sum: {}", need_gen_chunks_pos.size());
if (need_gen_chunks_pos.empty()) {
if (need_gen_chunks_pos.empty() && m_new_chunks.empty()) {
m_could_gen = true;
return;
}
{
// Create new chunk
std::lock_guard lock(m_new_chunk_mutex);
for (auto& pos : need_gen_chunks_pos) {
m_new_chunks.emplace(pos, ServerChunk(*this, pos));
m_new_chunks.emplace(
pos, std::make_unique<ServerChunk>(ServerChunk(*this, pos)));
}
}
@@ -138,26 +296,24 @@ void ServerWorld::compute_required_chunks(
}
}
}
void ServerWorld::sync_and_collect_missing_chunks(
std::vector<ChunkPos>& 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.unsafe_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);
{
std::lock_guard lock(m_chunks_mutex);
for (auto pos : required_chunks) {
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
need_gen_chunks_pos.push_back(pos);
m_chunks.emplace(
pos, ChunkEntity{ChunkState::GENERATING, nullptr, 0});
}
}
}
}
void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
void ServerWorld::submit_new_chunks(const std::string& uuid) {
using enum ChunkLoadStyle;
std::lock_guard lock(m_new_chunk_mutex);
auto pool_ptr = m_gen_thread_pool.load();
@@ -166,10 +322,11 @@ void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
}
switch (m_chunk_load_style) {
case RANDOM:
// Enqueue directly in random order
for (auto& [pos, task] : m_new_chunks) {
if (!task.future.valid()) {
task.future =
pool_ptr->enqueue([&task]() { task.chunk.gen_chunk(); });
pool_ptr->enqueue([&task]() { task.chunk->gen_chunk(); });
}
}
break;
@@ -180,12 +337,8 @@ void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
tasks.emplace_back(pos, &task);
}
}
glm::vec3 player_pos;
if (uuid == std::nullopt) {
player_pos = glm::vec3{0.0f};
} else {
player_pos = get_player_pos(uuid.value());
}
glm::vec3 player_pos = get_player_pos(uuid);
auto dist2 = [player_pos](ChunkPos chunk_pos) {
ChunkPos player_chunk_pos =
get_chunk_pos(player_pos.x, player_pos.z);
@@ -201,7 +354,7 @@ void ServerWorld::submit_new_chunks(const std::optional<std::string>& uuid) {
for (auto& [pos, task] : tasks) {
if (!task->future.valid()) {
task->future =
pool_ptr->enqueue([task]() { task->chunk.gen_chunk(); });
pool_ptr->enqueue([task]() { task->chunk->gen_chunk(); });
}
}
}
@@ -220,10 +373,16 @@ void ServerWorld::poll_finished_chunks() {
if (pending.future.wait_for(0ms) != std::future_status::ready) {
return false;
}
pending.future.get();
try {
pending.future.get();
} catch (const std::exception& e) {
Logger::error("Chunk generation failed: {}", e.what());
return true;
}
// Spawn complete, move away
m_new_finished_chunk.emplace_back(pair.first,
std::move(pending.chunk));
return true;
});
}
@@ -246,7 +405,7 @@ void ServerWorld::start_gen_thread() {
break;
}
m_need_gen_chunk = false;
std::optional<std::string> uuid{std::nullopt};
std::string uuid;
if (!m_need_gen_queue.empty()) {
uuid = m_need_gen_queue.front();
m_need_gen_queue.pop();
@@ -264,10 +423,19 @@ void ServerWorld::start_server_thread() {
void ServerWorld::start_thread_pool() {
int max_thread = std::thread::hardware_concurrency();
if (m_pool_threads == 0) {
change_pool_threads(max_thread - RESERVED_THREADS);
if (m_gen_pool_threads == 0) {
m_gen_pool_threads = change_pool_threads(m_gen_thread_pool,
max_thread - RESERVED_THREADS);
} else {
change_pool_threads(m_pool_threads);
m_gen_pool_threads =
change_pool_threads(m_gen_thread_pool, m_gen_pool_threads);
}
if (m_net_pool_threads == 0) {
m_net_pool_threads = change_pool_threads(m_net_thread_pool, 4);
} else {
m_net_pool_threads =
change_pool_threads(m_net_thread_pool, m_net_pool_threads);
}
}
@@ -294,7 +462,14 @@ void ServerWorld::stop_thread_pool() {
pool_ptr->stop();
}
m_gen_thread_pool.store(nullptr);
Logger::info("Thread Pool Stopped");
Logger::info("Gen Thread Pool Stopped");
auto p = m_net_thread_pool.load();
if (p) {
p->stop();
}
m_net_thread_pool.store(nullptr);
Logger::info("Net Thread Pool Stopped");
}
void ServerWorld::serever_run(std::stop_token stoken) {
@@ -316,7 +491,7 @@ void ServerWorld::serever_run(std::stop_token stoken) {
Logger::info("Server Thread Stopped!");
}
void ServerWorld::need_gen(std::optional<std::string> uuid) {
void ServerWorld::need_gen(std::string uuid) {
// if (!m_could_gen) {
// Logger::warn("It is generating or consuming new chunks");
@@ -325,9 +500,9 @@ void ServerWorld::need_gen(std::optional<std::string> uuid) {
m_could_gen = false;
if (uuid) {
{
std::lock_guard lock(m_need_gen_queue_mutex);
m_need_gen_queue.enqueue(*uuid);
m_need_gen_queue.enqueue(std::move(uuid));
}
// m_gen_player_pos = get_player("TestPlayer").get_player_pos();
@@ -351,7 +526,9 @@ bool ServerWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
if (it == m_chunks.end()) {
return false;
}
if (it->second.state != ChunkState::READY) {
return false;
}
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 ||
@@ -359,7 +536,7 @@ bool ServerWorld::set_block(const glm::ivec3& block_pos, unsigned id) {
return false;
}
it->second.set_chunk_block(ServerChunk::index(x, y, z), id);
it->second.chunk->set_chunk_block(ServerChunk::index(x, y, z), id);
return true;
}
@@ -367,7 +544,6 @@ void ServerWorld::hot_reload() {
auto& config = Config::get();
int dist = config.get<int>("world.rendering_distance");
m_rendering_distance = dist <= MAX_DISTANCE ? dist : MAX_DISTANCE;
need_gen(std::nullopt);
}
void ServerWorld::rebuild_world() {
@@ -391,7 +567,6 @@ void ServerWorld::rebuild_world() {
ChunkGenerator::reload();
start_thread_pool();
start_gen_thread();
need_gen(std::nullopt);
Arena arena;
auto* rsp = Arena::Create<S2C_ClearAllChunks>(&arena);
rsp->set_clear(true);
@@ -412,13 +587,22 @@ void ServerWorld::update() {
bool consumed = false;
for (auto& x : m_new_finished_chunk) {
m_chunks.emplace(x.first, std::move(x.second));
auto it = m_chunks.find(x.pos);
if (it == m_chunks.end()) {
Logger::error(
"New Chunk {} {} not Find, don't move to m_chunks", x.pos.x,
x.pos.z);
continue;
}
it->second.chunk = std::move(x.chunk);
it->second.state = ChunkState::READY;
consumed = true;
}
if (consumed) {
m_could_gen = true;
}
}
send_time();
for (auto& [id, timer] : m_timers) {
timer.update();
@@ -469,14 +653,43 @@ void ServerWorld::handle_player_login(const std::string& name,
std::shared_ptr<Session> session) {
std::string uuid = generate_uuid();
Logger::info("Player {} (uuid {}) join the world", name, uuid);
bool sucess = true;
{
std::lock_guard lock(m_player_mutex);
m_players.emplace(
auto [_, inserted] = m_players.emplace(
std::piecewise_construct, std::forward_as_tuple(std::string(uuid)),
std::forward_as_tuple(name, uuid, *this, session, m_game_ticks));
if (!inserted) {
Logger::error("Player insert Fail");
}
sucess = inserted;
}
m_uuid_to_name.emplace(uuid, name);
Arena arena;
if (!sucess) {
auto* rsp = Arena::Create<LoginRsp>(&arena);
rsp->set_success(false);
session->send(make_packet(*rsp));
return;
}
m_uuid_to_name.emplace(uuid, name);
// Pre-insert into new_chunks to ensure correct addition to waiting_player
/*ChunkPosSet required_chunks;
compute_required_chunks(required_chunks, uuid);
std::vector<ChunkPos> need_gen_chunks_pos;
sync_and_collect_missing_chunks(need_gen_chunks_pos, required_chunks);
{
std::lock_guard lock(m_new_chunk_mutex);
for (auto& pos : need_gen_chunks_pos) {
m_new_chunks.emplace(pos, ServerChunk(*this, pos));
}
}
*/
need_gen(uuid);
auto* rsp = Arena::Create<LoginRsp>(&arena);
rsp->set_success(true);
rsp->set_uuid(uuid);
@@ -484,11 +697,15 @@ void ServerWorld::handle_player_login(const std::string& name,
}
void ServerWorld::handle_player_exit(const std::string& uuid) {
std::shared_ptr<Session> exit_session;
ChunkPosSet old_set;
{
std::lock_guard lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it != m_players.end()) {
Logger::info("Player {} Exit the Server", it->second.get_name());
exit_session = it->second.get_session();
old_set = std::move(it->second.get_chunk_pos_set());
m_players.erase(it);
} else {
Logger::error("Player {} isn't in Server", uuid);
@@ -498,6 +715,14 @@ void ServerWorld::handle_player_exit(const std::string& uuid) {
m_uuid_to_name.erase(uuid);
update_ref_count(old_set, {});
Arena arena;
auto* rsp = Arena::Create<LogoutRsp>(&arena);
rsp->set_uuid(uuid);
rsp->set_server_stop(false);
exit_session->send(make_packet(*rsp));
std::vector<std::shared_ptr<Session>> sessions;
{
std::shared_lock lock(m_player_mutex);
@@ -507,9 +732,6 @@ void ServerWorld::handle_player_exit(const std::string& uuid) {
}
for (auto& s : sessions) {
Arena arena;
auto* rsp = Arena::Create<LogoutRsp>(&arena);
rsp->set_uuid(uuid);
s->send(make_packet(*rsp));
}
}
@@ -537,72 +759,9 @@ void ServerWorld::handle_chunk_req(int task_id, const std::string& uuid,
it->second.task_id(task_id);
}
}
auto pool = m_gen_thread_pool.load();
pool->enqueue([task_id, uuid, pos, this]() {
{
std::shared_lock lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it == m_players.end()) {
return;
}
if (task_id < it->second.task_id()) {
// Old chunk requests are simply discarded
return;
}
}
Arena arean;
ChunkDataRsp* rsp = Arena::Create<ChunkDataRsp>(&arean);
auto* rsq_pos = rsp->mutable_pos();
rsq_pos->set_x(pos.x);
rsq_pos->set_z(pos.z);
{
std::shared_lock lock(m_chunks_mutex);
auto it = m_chunks.find(pos);
if (it == m_chunks.end()) {
return;
}
rsp->set_chunk_seed(it->second.seed());
rsp->set_biome_type(std::to_underlying(it->second.biome()));
auto* blocks = rsp->mutable_chunk_blocks();
auto& chunk_blocks = it->second.get_chunk_blocks();
blocks->Assign(chunk_blocks.begin(), chunk_blocks.end());
auto& neighbor_blocks = it->second.get_neightbor_blocks();
auto assign =
[](auto* nb,
const std::optional<std::vector<BlockType>>& blocks) {
if (!blocks) {
return;
}
if (!nb) {
return;
}
nb->Assign(blocks->begin(), blocks->end());
};
auto* nb1 = rsp->mutable_neighbor_blocks_1();
auto* nb2 = rsp->mutable_neighbor_blocks_2();
auto* nb3 = rsp->mutable_neighbor_blocks_3();
auto* nb4 = rsp->mutable_neighbor_blocks_4();
assign(nb1, neighbor_blocks[0]);
assign(nb2, neighbor_blocks[1]);
assign(nb3, neighbor_blocks[2]);
assign(nb4, neighbor_blocks[3]);
}
std::shared_ptr<Session> s;
{
std::shared_lock lock(m_player_mutex);
auto it = m_players.find(uuid);
if (it != m_players.end()) {
s = it->second.get_session();
it->second.update_sync_gametick(m_game_ticks);
}
}
if (!s) {
Logger::error("Player {} session not exist", uuid);
return;
}
rsp->set_task_id(task_id);
s->send(make_packet(*rsp));
});
auto pool = m_net_thread_pool.load();
pool->enqueue(
[task_id, uuid, pos, this]() { send_chunk(task_id, uuid, pos); });
}
void ServerWorld::handle_block_change(const BlockChangeReq& req) {
@@ -653,19 +812,44 @@ 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::gen_pool_threads() const { return m_gen_pool_threads.load(); }
int ServerWorld::max_threads() const { return m_max_threads.load(); }
void ServerWorld::change_pool_threads(int threads) {
void ServerWorld::change_pool_threads(ThreadPoolKind kind, int threads) {
switch (kind) {
case ThreadPoolKind::NET:
m_net_pool_threads = change_pool_threads(m_net_thread_pool, threads);
break;
case ThreadPoolKind::GEN:
m_gen_pool_threads = change_pool_threads(m_gen_thread_pool, threads);
break;
}
}
int ServerWorld::change_pool_threads(
std::atomic<std::shared_ptr<ThreadPool>>& thread_pool, 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;
m_max_threads = 1;
}
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<ThreadPool>(used_thread));
m_pool_threads = used_thread;
thread_pool.store(std::make_shared<ThreadPool>(used_thread));
return used_thread;
}
void ServerWorld::send_server_stop() {
Arena arena;
auto* rsp = Arena::Create<LogoutRsp>(&arena);
rsp->set_server_stop(true);
std::shared_lock lock(m_player_mutex);
for (auto& [uuid, player] : m_players) {
player.get_session()->send(make_packet(*rsp));
}
Logger::info("Send Server Mesaage Success");
}
int ServerWorld::chunk_load_style() const {
return std::to_underlying(m_chunk_load_style.load());
}
@@ -683,4 +867,9 @@ void ServerWorld::set_chunk_load_style(int id) {
Logger::error("Can,t Find Chunk Load Style Id {}, Nothing Will Do", id);
}
int ServerWorld::chunk_size() const {
std::shared_lock lock(m_chunks_mutex);
return m_chunks.size();
}
} // namespace Cubed

View File

@@ -15,5 +15,6 @@ message LogoutReq {
message LogoutRsp {
string uuid = 1;
bool server_stop = 2;
}

View File

@@ -115,8 +115,8 @@ void Renderer::init() {
#ifdef DEBUG_MODE
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageCallback(
[](GLenum source, GLenum type, GLuint id, GLenum severity,
GLsizei length, const GLchar* message, const void* user_param) {
[](GLenum, GLenum, GLuint, GLenum, GLsizei, const GLchar* message,
const void*) {
Logger::log(Logger::Level::L_DEBUG, std::source_location::current(),
"GL Debug: {}", reinterpret_cast<const char*>(message));
},
@@ -276,7 +276,7 @@ void Renderer::render_outline() {
const auto& shader = get_shader("outline");
shader.use();
const auto& block_pos = m_world.get_look_block_pos("TestPlayer");
const auto& block_pos = m_world.get_look_block_pos();
if (block_pos != std::nullopt) {