refactor: chunk load (#21)

* feat(renderer): add fog effect based on render distance

* refactor(gameplay): remove dead code and simplify chunk neighbor context

Remove the large commented-out `init_chunks()` function, and eliminate the `affected_neighbor` tracking in `gen_chunks_internal()`. This simplifies the neighbor context building and removes unused vertex data regeneration for affected neighbors.

* refactor(gameplay): move chunk generation phases into gen_chunk method

Consolidate multiple phase generation calls into a single gen_chunk() method on Chunk, which handles neighbor generation and ensures thread safety. Simplify World::gen_chunks_internal by using gen_chunk() instead of manual phase orchestration.

* fix(gameplay): use gen_phase_one to get seed

* feat(world): integrate thread pool and async chunk generation

* fix(renderer): correct shader uniform name and remove unused uniform

* feat(gameplay): add temporary chunk flag to prevent path clearing

* fix: add thread safety for cave and river path mutexes

* refactor: remove unused uniforms and set cameraPos uniform outside lambda

* feat(world): add dynamic thread pool resizing

* feat(world): add thread pool size management and UI controls

* feat(world): add ChunkLoadStyle enum and rename chunk_pos to get_chunk_pos

* feat(player): add configurable fly Y speed

* refactor(world): add thread pool start/stop and auto-detect threads

Extract thread pool starting and stopping into dedicated methods. Set default pool threads to 0 to enable automatic detection of available cores, and ensure thread pool is properly managed during world rebuild.

* fix: include shared_mutex header

* fix(gameplay): simplify set_chunk_load_style switch and fix fallthrough bug

* fix(world): add missing include for <utility>
This commit is contained in:
zhenyan121
2026-06-22 13:34:45 +08:00
committed by GitHub
parent 5385876a8a
commit 7ffc349eb3
19 changed files with 501 additions and 601 deletions

View File

@@ -26,7 +26,7 @@ constexpr float DEFAULT_G = 22.5f;
constexpr int SIZE_X = CHUNK_SIZE;
constexpr int SIZE_Y = WORLD_SIZE_Y;
constexpr int SIZE_Z = CHUNK_SIZE;
constexpr int RESERVED_THREADS = 3;
constexpr ChunkPos CHUNK_DIR[]{{1, 0}, {-1, 0}, {0, 1}, {0, -1},
{1, 1}, {-1, 1}, {1, -1}, {-1, -1}};

View File

@@ -48,6 +48,8 @@ private:
int m_pre_set_tick_speed = 1;
bool m_tick_frezze = false;
int m_samples_idx = 1;
int m_threads = 1;
int m_chunk_style = 0;
void show_about_table_bar();
void show_biome_table_bar();
void show_time_table_bar();

View File

@@ -1,7 +1,9 @@
#pragma once
#include "Cubed/gameplay/cave_path.hpp"
#include <shared_mutex>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class CaveCarver {
using CaveHashMap = tbb::concurrent_hash_map<unsigned, CavePath>;
@@ -17,11 +19,13 @@ public:
int cave_sum() const;
float& cave_probability();
std::shared_mutex& path_mutex();
private:
CaveHashMap m_paths;
unsigned m_seed = 0;
Random m_random;
float m_cave_probability = 0.035f;
std::shared_mutex m_path_mutex;
};
} // namespace Cubed

View File

@@ -23,6 +23,9 @@ private:
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};
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
std::mutex m_vertexs_data_mutex;
@@ -54,7 +57,7 @@ private:
BlockType id);
public:
Chunk(World& world, ChunkPos chunk_pos);
Chunk(World& world, ChunkPos chunk_pos, bool temp_chunk = false);
~Chunk();
Chunk(const Chunk&) = delete;
Chunk& operator=(const Chunk&) = delete;
@@ -124,7 +127,9 @@ public:
void need_upload();
void set_chunk_block(int index, unsigned id);
// ensure thread safe!
void gen_chunk();
bool is_temp_chunk() const;
ChunkPos chunk_pos() const;
BiomeType biome() const;
void biome(BiomeType b);

View File

@@ -34,6 +34,7 @@ private:
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;
@@ -99,6 +100,7 @@ public:
float& acceleration();
float& deceleration();
float& g();
float& fly_y_speed();
unsigned place_block() const;

View File

@@ -4,7 +4,9 @@
#include "Cubed/tools/cubed_random.hpp"
#include <glm/glm.hpp>
#include <shared_mutex>
#include <tbb/concurrent_hash_map.h>
namespace Cubed {
class RiverWorm {
@@ -12,6 +14,7 @@ class RiverWorm {
public:
RiverWorm();
~RiverWorm();
RiverHashMap& paths();
void init(unsigned world_seed);
void reload(unsigned world_seed);
@@ -21,12 +24,14 @@ public:
int river_sum() const;
float& river_probability();
std::shared_mutex& paths_mutex();
private:
RiverHashMap m_paths;
unsigned m_seed = 0;
Random m_random;
float m_probability = 0.01f;
std::shared_mutex m_paths_mutex;
};
}; // namespace Cubed

View File

@@ -4,6 +4,7 @@
#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>
@@ -34,15 +35,24 @@ 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;
@@ -50,7 +60,7 @@ private:
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
@@ -59,7 +69,7 @@ private:
mutable std::mutex m_chunks_mutex;
std::mutex m_gen_signal_mutex;
std::mutex m_new_chunk_queue_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;
@@ -74,13 +84,15 @@ private:
std::atomic<bool> m_tick_running{true};
std::atomic<int> m_rendering_distance{24};
std::atomic<float> m_chunk_gen_fraction{0.0f};
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_chunk;
std::vector<std::pair<ChunkPos, Chunk>> m_new_chunk_queue;
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;
@@ -88,18 +100,14 @@ private:
void gen_chunks_internal();
void sync_player_pos(glm::vec3& player_pos);
void
compute_required_chunks(ChunkPosSet& required_chunks,
ChunkPairVector& temp_neighbor,
std::vector<ChunkPos>& need_gen_temp_chunks_pos);
void compute_required_chunks(ChunkPosSet& required_chunks,
ChunkPairVector& temp_neighbor);
void sync_and_collect_missing_chunks(std::vector<ChunkPos>&,
const ChunkPosSet&);
void
build_neighbor_context_for_new_chunks(ConstChunkMap& new_chunks_neighbor,
ChunkPtrUpdateList& affected_neighbor,
const ChunkPairVector& new_chunks);
void build_neighbor_context_for_affected_neighbors(ChunkPtrUpdateList&,
ConstChunkMap&);
void submit_new_chunks();
void poll_finished_chunks();
void wait_all_chunk_tasks();
public:
World();
@@ -118,7 +126,7 @@ public:
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 chunk_pos(int world_x, int world_z);
static ChunkPos get_chunk_pos(int world_x, int world_z);
void need_gen();
@@ -138,6 +146,8 @@ public:
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();
@@ -154,6 +164,11 @@ public:
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);
};
} // namespace Cubed

View File

@@ -0,0 +1,123 @@
#pragma once
#include <condition_variable>
#include <cstddef>
#include <functional>
#include <future>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>
namespace Cubed {
class ThreadPool {
private:
std::vector<std::jthread> m_workers;
std::queue<std::function<void()>> m_tasks;
std::mutex m_mtx;
std::condition_variable_any m_cv;
std::atomic<bool> m_stopping{false};
std::atomic<size_t> m_thread_sum{0};
public:
ThreadPool(const ThreadPool&) = delete;
ThreadPool(ThreadPool&&) = delete;
ThreadPool& operator=(const ThreadPool&) = delete;
ThreadPool& operator=(ThreadPool&&) = delete;
explicit ThreadPool(size_t thread_sum) : m_thread_sum(thread_sum) {
for (size_t i = 0; i < thread_sum; i++) {
m_workers.emplace_back([this](std::stop_token stoken) {
while (true) {
std::function<void()> task;
{
std::unique_lock lock(m_mtx);
m_cv.wait(lock, stoken,
[this, stoken] { return !m_tasks.empty(); });
if (stoken.stop_requested() && m_tasks.empty()) {
return;
}
task = std::move(m_tasks.front());
m_tasks.pop();
}
task();
}
});
}
}
~ThreadPool() { stop(); }
template <typename F> auto enqueue(F&& f) {
using R = std::invoke_result_t<F>;
auto task =
std::make_shared<std::packaged_task<R()>>(std::forward<F>(f));
auto fut = task->get_future();
{
std::lock_guard lock(m_mtx);
if (m_stopping)
throw std::runtime_error("thread pool stopped");
m_tasks.emplace([task] { (*task)(); });
}
m_cv.notify_one();
return fut;
}
void stop() {
m_stopping = true;
for (auto& w : m_workers) {
w.request_stop();
}
m_cv.notify_all();
for (auto& w : m_workers) {
if (w.joinable()) {
w.join();
}
}
}
size_t thread_sum() const { return m_thread_sum.load(); }
};
template <std::random_access_iterator Iter, typename F>
void parallel_do(ThreadPool& pool, Iter first, Iter last, size_t max_threads,
F&& f) {
max_threads = std::max<size_t>(1, max_threads);
max_threads = std::min(max_threads, pool.thread_sum());
std::decay_t<F> fn(std::forward<F>(f));
size_t length = std::distance(first, last);
if (!length) {
return;
}
constexpr size_t MIN_PER_THREAD = 25;
size_t num_blocks =
std::min(max_threads, (length + MIN_PER_THREAD - 1) / MIN_PER_THREAD);
num_blocks = std::max<size_t>(1, num_blocks);
size_t block_size = (length + num_blocks - 1) / num_blocks;
std::vector<std::future<void>> futures;
futures.reserve(num_blocks - 1);
Iter block_start = first;
for (size_t i = 0; i < num_blocks - 1; ++i) {
Iter block_end = block_start;
auto remain = std::distance(block_start, last);
std::advance(block_end, std::min<size_t>(block_size, remain));
futures.emplace_back(pool.enqueue([block_start, block_end, &fn]() {
for (auto it = block_start; it != block_end; ++it) {
fn(*it);
}
}));
block_start = block_end;
}
for (auto it = block_start; it != last; ++it) {
fn(*it);
}
for (auto& fut : futures) {
fut.get();
}
};
} // namespace Cubed