mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-08-08 17:57:02 +08:00
Compare commits
10 Commits
e1e9efb86c
...
12b1107923
| Author | SHA1 | Date | |
|---|---|---|---|
| 12b1107923 | |||
| 9788f68b12 | |||
| cbe3548dcd | |||
| 4073664624 | |||
| a016e08a2a | |||
| a5764e901b | |||
| 429520b1f7 | |||
| d2636189a4 | |||
| 3d4c41a76e | |||
| 28b66ee275 |
@@ -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>
|
||||
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -93,4 +93,5 @@ private:
|
||||
// Generate biome-specific vegetation/structures
|
||||
void gen_phase_five();
|
||||
};
|
||||
|
||||
} // namespace Cubed
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
17
src/app.cpp
17
src/app.cpp
@@ -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() {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -15,5 +15,6 @@ message LogoutReq {
|
||||
|
||||
message LogoutRsp {
|
||||
string uuid = 1;
|
||||
bool server_stop = 2;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user