mirror of
https://github.com/zhenyan121/Cubed.git
synced 2026-06-22 02:27:01 +08:00
feat(world): integrate thread pool and async chunk generation
This commit is contained in:
@@ -23,6 +23,8 @@ 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_gen_finish{false};
|
||||
std::atomic<BiomeType> m_biome = BiomeType::PLAIN;
|
||||
std::mutex m_vertexs_data_mutex;
|
||||
|
||||
@@ -127,6 +129,7 @@ public:
|
||||
// ensure thread safe!
|
||||
void gen_chunk();
|
||||
|
||||
bool is_gen_finish() const;
|
||||
ChunkPos chunk_pos() const;
|
||||
BiomeType biome() const;
|
||||
void biome(BiomeType b);
|
||||
|
||||
@@ -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,22 @@ class Player;
|
||||
class TextureManager;
|
||||
class World {
|
||||
private:
|
||||
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 +58,7 @@ private:
|
||||
|
||||
std::thread m_gen_thread;
|
||||
std::thread m_server_thread;
|
||||
|
||||
std::unique_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 +67,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;
|
||||
@@ -79,8 +87,9 @@ private:
|
||||
|
||||
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,15 +97,13 @@ 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,
|
||||
const ChunkPairVector& new_chunks);
|
||||
|
||||
void submit_new_chunks();
|
||||
void poll_finished_chunks();
|
||||
|
||||
public:
|
||||
World();
|
||||
|
||||
115
include/Cubed/tools/thread_pool.hpp
Normal file
115
include/Cubed/tools/thread_pool.hpp
Normal file
@@ -0,0 +1,115 @@
|
||||
#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() {
|
||||
m_stopping = true;
|
||||
for (auto& w : m_workers) {
|
||||
w.request_stop();
|
||||
}
|
||||
m_cv.notify_all();
|
||||
}
|
||||
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;
|
||||
}
|
||||
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
|
||||
Reference in New Issue
Block a user