feat(gameplay): add run mode based thread pool config

Introduce RunMode enum and thread pool sizing helpers for client,
server, and hybrid modes. Add compute pool to server world and pass
mode through server/client initialization.
This commit is contained in:
2026-08-05 19:49:06 +08:00
parent 8ff789c576
commit 2aa9d07e43
9 changed files with 202 additions and 37 deletions

View File

@@ -32,7 +32,7 @@ public:
ClientWorld(AudioEngine& auido, Config& config, WorldScene& scene); ClientWorld(AudioEngine& auido, Config& config, WorldScene& scene);
~ClientWorld(); ~ClientWorld();
void init(std::string_view player_name, void init(std::string_view player_name,
std::shared_ptr<NetworkClient> client); std::shared_ptr<NetworkClient> client, RunMode mode);
void update(float dt); void update(float dt);
bool handle_event(const Event& e); bool handle_event(const Event& e);
const std::optional<LookBlock>& get_look_block_pos() const; const std::optional<LookBlock>& get_look_block_pos() const;
@@ -132,6 +132,7 @@ private:
static constexpr int WORLD_EXIT_TIMEOUT = 200; static constexpr int WORLD_EXIT_TIMEOUT = 200;
static constexpr int MAX_UPLOAD_CHUNK_SUM = 16; static constexpr int MAX_UPLOAD_CHUNK_SUM = 16;
std::atomic<RunMode> m_runmode = RunMode::HYBRID;
ClientEntityManager m_entity_manager; ClientEntityManager m_entity_manager;
ClientPlayerManager m_player_manager; ClientPlayerManager m_player_manager;
ChunkHashMap m_chunks; ChunkHashMap m_chunks;

View File

@@ -14,8 +14,7 @@ public:
void stop(); void stop();
// Run in another thread after initialization is complete // Run in another thread after initialization is complete
void start_server(int port); void start_server(int port, RunMode mode);
void start_server();
int port() const; int port() const;
ServerWorld& server_world(); ServerWorld& server_world();

View File

@@ -34,7 +34,7 @@ public:
~ServerWorld(); ~ServerWorld();
void stop(); void stop();
void handle_player_exit(const std::string& uuid); void handle_player_exit(const std::string& uuid);
void init_world(); void init_world(RunMode mode);
void need_gen(std::string uuid); void need_gen(std::string uuid);
void update(); void update();
void hot_reload(); void hot_reload();
@@ -96,7 +96,7 @@ public:
uint32_t get_chunk_ref_count(const glm::vec3& pos) const; uint32_t get_chunk_ref_count(const glm::vec3& pos) const;
ServerEntityManager& entity_manager(); ServerEntityManager& entity_manager();
std::shared_ptr<ThreadPool> get_compute_pool();
int get_block(const glm::ivec3& block_pos) const override; int get_block(const glm::ivec3& block_pos) const override;
bool is_solid(const glm::ivec3& block_pos) const override; bool is_solid(const glm::ivec3& block_pos) const override;
bool can_pass_block(const glm::ivec3& block_pos) const override; bool can_pass_block(const glm::ivec3& block_pos) const override;
@@ -142,6 +142,7 @@ private:
using uuid_cacc = PlayerUUIDMap::const_accessor; using uuid_cacc = PlayerUUIDMap::const_accessor;
Config& m_config; Config& m_config;
std::atomic<RunMode> m_runmode{RunMode::HYBRID};
ServerEntityManager m_entity_manager; ServerEntityManager m_entity_manager;
// key = uuid // key = uuid
PlayerHashMap m_players; PlayerHashMap m_players;
@@ -163,8 +164,9 @@ private:
std::atomic<bool> m_init{false}; std::atomic<bool> m_init{false};
std::atomic<bool> m_stopped{false}; std::atomic<bool> m_stopped{false};
std::atomic<int> m_rendering_distance{24}; std::atomic<int> m_rendering_distance{24};
std::atomic<int> m_gen_pool_threads{0}; std::atomic<int> m_gen_threads{0};
std::atomic<int> m_net_pool_threads{0}; std::atomic<int> m_net_threads{0};
std::atomic<int> m_compute_threads{0};
std::atomic<int> m_max_threads{1}; std::atomic<int> m_max_threads{1};
std::atomic<size_t> m_player_sum{0}; std::atomic<size_t> m_player_sum{0};
std::atomic<TickType> m_game_ticks{0}; std::atomic<TickType> m_game_ticks{0};
@@ -180,6 +182,7 @@ private:
std::atomic<std::shared_ptr<PriorityThreadPool>> m_gen_thread_pool; std::atomic<std::shared_ptr<PriorityThreadPool>> m_gen_thread_pool;
std::atomic<std::shared_ptr<ThreadPool>> m_net_thread_pool; std::atomic<std::shared_ptr<ThreadPool>> m_net_thread_pool;
std::atomic<std::shared_ptr<ThreadPool>> m_compute_thread_pool;
std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::CENTER}; std::atomic<ChunkLoadStyle> m_chunk_load_style{ChunkLoadStyle::CENTER};

View File

@@ -26,4 +26,7 @@ public:
glm::vec3{0.5f, 0.5f, 0.5f}}; glm::vec3{0.5f, 0.5f, 0.5f}};
} }
}; };
enum class RunMode { CLIENT_ONLY, SERVER_ONLY, HYBRID };
} // namespace Cubed } // namespace Cubed

View File

@@ -0,0 +1,130 @@
#pragma once
#include "Cubed/gameplay/world.hpp"
#include "Cubed/tools/cubed_assert.hpp"
#include <algorithm>
#include <thread>
namespace Cubed {
namespace Tools {
constexpr size_t SERVER_RESERVED_THREADS = 3; // tick + netio + gen scheduler
constexpr size_t CLIENT_RESERVED_THREADS =
3; // main/render + netio + system reserved
constexpr size_t safe_sub(size_t a, size_t b) { return a > b ? a - b : 0; }
inline size_t get_hardware_threads() {
auto hc = std::thread::hardware_concurrency();
return hc == 0 ? 4 : static_cast<size_t>(hc);
}
inline size_t get_server_available_threads() {
return std::max<size_t>(
1, safe_sub(get_hardware_threads(), SERVER_RESERVED_THREADS));
}
inline size_t get_client_available_threads() {
return std::max<size_t>(
1, safe_sub(get_hardware_threads(), CLIENT_RESERVED_THREADS));
}
inline size_t get_client_threads(RunMode mode) {
switch (mode) {
case RunMode::SERVER_ONLY:
ASSERT_MSG(false, "Server Only don't need client pool");
return 1;
case RunMode::CLIENT_ONLY: {
auto available = get_client_available_threads();
return std::clamp<size_t>(available, 1, 16);
}
case RunMode::HYBRID: {
auto available = get_client_available_threads();
return std::clamp<size_t>(available / 2, 1, 4);
}
}
return 1;
}
inline size_t get_server_net_pool_threads(RunMode mode) {
switch (mode) {
case RunMode::SERVER_ONLY: {
auto available = get_server_available_threads();
return std::clamp<size_t>(available / 4, 1,
std::min<size_t>(4, available));
}
case RunMode::CLIENT_ONLY:
ASSERT_MSG(false, "Client Only don't need net pool");
return 1;
case RunMode::HYBRID: {
auto available = get_server_available_threads();
return std::clamp<size_t>(available / 8, 1,
std::min<size_t>(4, available));
}
}
return 1;
}
inline size_t get_server_compute_treads(RunMode mode) {
switch (mode) {
case RunMode::HYBRID:
case RunMode::SERVER_ONLY: {
auto available = get_server_available_threads();
return std::clamp<size_t>(available / 4, 1,
std::min<size_t>(4, available));
}
case RunMode::CLIENT_ONLY:
ASSERT_MSG(false, "Client Only don't need update pool");
return 1;
}
return 1;
}
inline size_t get_server_gen_threads(RunMode mode) {
switch (mode) {
case RunMode::SERVER_ONLY: {
auto available = get_server_available_threads();
auto net_pool = get_server_net_pool_threads(mode);
auto update_pool = get_server_compute_treads(mode);
size_t remain = available;
remain -= std::min(remain, net_pool);
remain -= std::min(remain, update_pool);
return std::max<size_t>(1, remain);
}
case RunMode::CLIENT_ONLY:
ASSERT_MSG(false, "Client Only don't need gen pool");
return 1;
case RunMode::HYBRID: {
auto available = get_server_available_threads();
auto net_pool = get_server_net_pool_threads(mode);
auto update_pool = get_server_compute_treads(mode);
auto client_pool = get_client_threads(mode);
size_t remain = available;
remain -= std::min(remain, net_pool);
remain -= std::min(remain, update_pool);
remain -= std::min(remain, client_pool);
return std::max<size_t>(1, remain);
}
}
return 1;
}
} // namespace Tools
} // namespace Cubed

View File

@@ -6,6 +6,7 @@
#include "Cubed/gameplay/game_time.hpp" #include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/packet.hpp" #include "Cubed/gameplay/packet.hpp"
#include "Cubed/scene/world_scene.hpp" #include "Cubed/scene/world_scene.hpp"
#include "Cubed/tools/threas_utils.hpp"
#include "Cubed/tools/time_tools.hpp" #include "Cubed/tools/time_tools.hpp"
#include <absl/container/inlined_vector.h> #include <absl/container/inlined_vector.h>
@@ -363,7 +364,8 @@ void ClientWorld::send_player_water_sound(bool underwater,
} }
void ClientWorld::init(std::string_view player_name, void ClientWorld::init(std::string_view player_name,
std::shared_ptr<NetworkClient> client) { std::shared_ptr<NetworkClient> client, RunMode mode) {
m_runmode = mode;
m_entity_manager.init(); m_entity_manager.init();
m_player_manager.init(player_name); m_player_manager.init(player_name);
m_client = client; m_client = client;
@@ -480,8 +482,8 @@ void ClientWorld::stop_client_thread() {
m_game_running = false; m_game_running = false;
} }
void ClientWorld::start_thread_pool() { void ClientWorld::start_thread_pool() {
int max_threads = std::thread::hardware_concurrency(); auto threads = Tools::get_client_threads(m_runmode);
int threads = std::min<size_t>(max_threads, 4); Logger::info("Client pool threads {}", threads);
change_pool_threads(threads); change_pool_threads(threads);
} }
void ClientWorld::stop_thread_pool() { void ClientWorld::stop_thread_pool() {
@@ -500,7 +502,6 @@ void ClientWorld::change_pool_threads(int threads) {
m_max_threads = 1; m_max_threads = 1;
} }
int used_thread = std::clamp(threads, 1, m_max_threads); int used_thread = std::clamp(threads, 1, m_max_threads);
Logger::info("Create New Thread Pool Use {} Threads", used_thread);
m_thread_pool.store(std::make_shared<PriorityThreadPool>(used_thread)); m_thread_pool.store(std::make_shared<PriorityThreadPool>(used_thread));
} }

View File

@@ -92,14 +92,10 @@ void NetworkServer::net_run() {
Logger::info("Server Started!"); Logger::info("Server Started!");
} }
void NetworkServer::start_server(int port) { void NetworkServer::start_server(int port, RunMode mode) {
m_port = port; m_port = port;
m_config.set("port", m_port); m_config.set("port", m_port);
start_server(); m_world.init_world(mode);
}
void NetworkServer::start_server() {
m_world.init_world();
net_run(); net_run();
m_started = true; m_started = true;
} }

View File

@@ -7,6 +7,7 @@
#include "Cubed/tools/log.hpp" #include "Cubed/tools/log.hpp"
#include "Cubed/tools/math_tools.hpp" #include "Cubed/tools/math_tools.hpp"
#include "Cubed/tools/net_utils.hpp" #include "Cubed/tools/net_utils.hpp"
#include "Cubed/tools/threas_utils.hpp"
#include "Cubed/tools/uuid.hpp" #include "Cubed/tools/uuid.hpp"
#include <ranges> #include <ranges>
@@ -178,8 +179,8 @@ void ServerWorld::send_chunk(int task_id, const std::string& uuid,
s->send(make_packet(*rsp)); s->send(make_packet(*rsp));
} }
void ServerWorld::init_world() { void ServerWorld::init_world(RunMode mode) {
m_runmode = mode;
m_entity_manager.init(); m_entity_manager.init();
m_entity_manager.add_entity("cubed:pig", {0, 225, 0}); m_entity_manager.add_entity("cubed:pig", {0, 225, 0});
register_timer("player disconnect", 5, [this]() { register_timer("player disconnect", 5, [this]() {
@@ -361,7 +362,7 @@ void ServerWorld::submit_new_chunks(const std::string& uuid,
return dist2(a.first) < dist2(b.first); return dist2(a.first) < dist2(b.first);
}); });
const int CHUNKS_PER_PRIORITY = m_gen_pool_threads; const int CHUNKS_PER_PRIORITY = m_gen_threads;
for (size_t i = 0; i < tasks.size(); ++i) { for (size_t i = 0; i < tasks.size(); ++i) {
int priority = 10 + static_cast<int>(i / CHUNKS_PER_PRIORITY); int priority = 10 + static_cast<int>(i / CHUNKS_PER_PRIORITY);
@@ -411,20 +412,30 @@ void ServerWorld::start_server_thread() {
} }
void ServerWorld::start_thread_pool() { void ServerWorld::start_thread_pool() {
int max_thread = std::thread::hardware_concurrency(); if (m_gen_threads == 0) {
if (m_gen_pool_threads == 0) { auto gen_threads = Tools::get_server_gen_threads(m_runmode);
m_gen_pool_threads = change_pool_threads(m_gen_thread_pool, Logger::info("Server Gen pool threads {}", gen_threads);
max_thread - RESERVED_THREADS); m_gen_threads = change_pool_threads(m_gen_thread_pool, gen_threads);
} else { } else {
m_gen_pool_threads = m_gen_threads = change_pool_threads(m_gen_thread_pool, m_gen_threads);
change_pool_threads(m_gen_thread_pool, m_gen_pool_threads);
} }
if (m_net_pool_threads == 0) { if (m_net_threads == 0) {
m_net_pool_threads = change_pool_threads(m_net_thread_pool, 4); auto net_threads = Tools::get_server_net_pool_threads(m_runmode);
Logger::info("Server Net pool threads {}", net_threads);
m_net_threads = change_pool_threads(m_net_thread_pool, net_threads);
} else { } else {
m_net_pool_threads = m_net_threads = change_pool_threads(m_net_thread_pool, m_net_threads);
change_pool_threads(m_net_thread_pool, m_net_pool_threads); }
if (m_compute_threads == 0) {
auto compute_threads = Tools::get_server_compute_treads(m_runmode);
Logger::info("Server compute pool threads {}", compute_threads);
m_compute_threads =
change_pool_threads(m_compute_thread_pool, compute_threads);
} else {
m_compute_threads =
change_pool_threads(m_compute_thread_pool, m_compute_threads);
} }
} }
@@ -459,6 +470,13 @@ void ServerWorld::stop_thread_pool() {
} }
m_net_thread_pool.store(nullptr); m_net_thread_pool.store(nullptr);
Logger::info("Net Thread Pool Stopped"); Logger::info("Net Thread Pool Stopped");
auto c = m_compute_thread_pool.load();
if (c) {
c->stop();
}
m_compute_thread_pool.store(nullptr);
Logger::info("Compute Thread Pool Stopped");
} }
void ServerWorld::serever_run(std::stop_token stoken) { void ServerWorld::serever_run(std::stop_token stoken) {
@@ -910,16 +928,16 @@ void ServerWorld::per_tick_time(int ms) { m_per_tick_time = ms; }
bool ServerWorld::is_tick_running() const { return m_tick_running.load(); } bool ServerWorld::is_tick_running() const { return m_tick_running.load(); }
void ServerWorld::tick_running(bool run) { m_tick_running = run; } void ServerWorld::tick_running(bool run) { m_tick_running = run; }
int ServerWorld::gen_pool_threads() const { return m_gen_pool_threads.load(); } int ServerWorld::gen_pool_threads() const { return m_gen_threads.load(); }
int ServerWorld::max_threads() const { return m_max_threads.load(); } int ServerWorld::max_threads() const { return m_max_threads.load(); }
void ServerWorld::change_pool_threads(ThreadPoolKind kind, int threads) { void ServerWorld::change_pool_threads(ThreadPoolKind kind, int threads) {
switch (kind) { switch (kind) {
case ThreadPoolKind::NET: case ThreadPoolKind::NET:
m_net_pool_threads = change_pool_threads(m_net_thread_pool, threads); m_net_threads = change_pool_threads(m_net_thread_pool, threads);
break; break;
case ThreadPoolKind::GEN: case ThreadPoolKind::GEN:
m_gen_pool_threads = change_pool_threads(m_gen_thread_pool, threads); m_gen_threads = change_pool_threads(m_gen_thread_pool, threads);
break; break;
} }
} }
@@ -946,7 +964,6 @@ int ServerWorld::change_pool_threads(
m_max_threads = 1; m_max_threads = 1;
} }
int used_thread = std::clamp(threads, 1, m_max_threads.load()); int used_thread = std::clamp(threads, 1, m_max_threads.load());
Logger::info("Create New Thread Pool Use {} Threads", used_thread);
thread_pool.store(std::make_shared<PriorityThreadPool>(used_thread)); thread_pool.store(std::make_shared<PriorityThreadPool>(used_thread));
return used_thread; return used_thread;
} }
@@ -1115,4 +1132,7 @@ BlockType ServerWorld::get_block_tpye(const glm::ivec3& block_pos) const {
int ServerWorld::get_per_tick_time() const { return m_per_tick_time; } int ServerWorld::get_per_tick_time() const { return m_per_tick_time; }
ServerEntityManager& ServerWorld::entity_manager() { return m_entity_manager; } ServerEntityManager& ServerWorld::entity_manager() { return m_entity_manager; }
std::shared_ptr<ThreadPool> ServerWorld::get_compute_pool() {
return m_compute_thread_pool.load();
}
} // namespace Cubed } // namespace Cubed

View File

@@ -129,10 +129,21 @@ void WorldScene::on_enter() {
load_config(); load_config();
m_error_ui.init(); m_error_ui.init();
m_client = std::make_shared<NetworkClient>(m_client_world); m_client = std::make_shared<NetworkClient>(m_client_world);
RunMode mode = RunMode::HYBRID;
if (m_argument.direct_enter) {
if (m_argument.ip) {
mode = RunMode::CLIENT_ONLY;
}
} else {
if (!m_scene_manager.world_scene_param().host_game) {
mode = RunMode::CLIENT_ONLY;
}
}
if (m_argument.direct_enter) { if (m_argument.direct_enter) {
if (!m_argument.ip) { if (!m_argument.ip) {
ChunkGenerator::init(); ChunkGenerator::init();
m_server.start_server(*m_argument.port); m_server.start_server(*m_argument.port, mode);
m_client->start("127.0.0.1", *m_argument.port); m_client->start("127.0.0.1", *m_argument.port);
} else { } else {
m_client->start(*m_argument.ip, *m_argument.port); m_client->start(*m_argument.ip, *m_argument.port);
@@ -148,7 +159,7 @@ void WorldScene::on_enter() {
} else { } else {
ChunkGenerator::init(); ChunkGenerator::init();
} }
m_server.start_server(param.port); m_server.start_server(param.port, mode);
} }
m_client->start(param.ip, param.port); m_client->start(param.ip, param.port);
@@ -157,7 +168,8 @@ void WorldScene::on_enter() {
// init will send packet // init will send packet
try { try {
m_client_world.init(m_argument.player.value_or("Unknown"), m_client); m_client_world.init(m_argument.player.value_or("Unknown"), m_client,
mode);
Logger::info("World Init Success"); Logger::info("World Init Success");
m_camera.camera_init(&m_client_world.get_player()); m_camera.camera_init(&m_client_world.get_player());