From 2aa9d07e435aae3f7e481fd0af3b528daba44471 Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Wed, 5 Aug 2026 19:49:06 +0800 Subject: [PATCH] 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. --- include/Cubed/gameplay/client_world.hpp | 3 +- include/Cubed/gameplay/network_server.hpp | 3 +- include/Cubed/gameplay/server_world.hpp | 11 +- include/Cubed/gameplay/world.hpp | 3 + include/Cubed/tools/threas_utils.hpp | 130 ++++++++++++++++++++++ src/gameplay/client_world.cpp | 9 +- src/gameplay/network_server.cpp | 8 +- src/gameplay/server_world.cpp | 54 ++++++--- src/scene/world_scene.cpp | 18 ++- 9 files changed, 202 insertions(+), 37 deletions(-) create mode 100644 include/Cubed/tools/threas_utils.hpp diff --git a/include/Cubed/gameplay/client_world.hpp b/include/Cubed/gameplay/client_world.hpp index 6b63faa..3040b22 100644 --- a/include/Cubed/gameplay/client_world.hpp +++ b/include/Cubed/gameplay/client_world.hpp @@ -32,7 +32,7 @@ public: ClientWorld(AudioEngine& auido, Config& config, WorldScene& scene); ~ClientWorld(); void init(std::string_view player_name, - std::shared_ptr client); + std::shared_ptr client, RunMode mode); void update(float dt); bool handle_event(const Event& e); const std::optional& get_look_block_pos() const; @@ -132,6 +132,7 @@ private: static constexpr int WORLD_EXIT_TIMEOUT = 200; static constexpr int MAX_UPLOAD_CHUNK_SUM = 16; + std::atomic m_runmode = RunMode::HYBRID; ClientEntityManager m_entity_manager; ClientPlayerManager m_player_manager; ChunkHashMap m_chunks; diff --git a/include/Cubed/gameplay/network_server.hpp b/include/Cubed/gameplay/network_server.hpp index f12fbd6..7abf197 100644 --- a/include/Cubed/gameplay/network_server.hpp +++ b/include/Cubed/gameplay/network_server.hpp @@ -14,8 +14,7 @@ public: void stop(); // Run in another thread after initialization is complete - void start_server(int port); - void start_server(); + void start_server(int port, RunMode mode); int port() const; ServerWorld& server_world(); diff --git a/include/Cubed/gameplay/server_world.hpp b/include/Cubed/gameplay/server_world.hpp index b82dfe1..84b3263 100644 --- a/include/Cubed/gameplay/server_world.hpp +++ b/include/Cubed/gameplay/server_world.hpp @@ -34,7 +34,7 @@ public: ~ServerWorld(); void stop(); void handle_player_exit(const std::string& uuid); - void init_world(); + void init_world(RunMode mode); void need_gen(std::string uuid); void update(); void hot_reload(); @@ -96,7 +96,7 @@ public: uint32_t get_chunk_ref_count(const glm::vec3& pos) const; ServerEntityManager& entity_manager(); - + std::shared_ptr get_compute_pool(); int get_block(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; @@ -142,6 +142,7 @@ private: using uuid_cacc = PlayerUUIDMap::const_accessor; Config& m_config; + std::atomic m_runmode{RunMode::HYBRID}; ServerEntityManager m_entity_manager; // key = uuid PlayerHashMap m_players; @@ -163,8 +164,9 @@ private: std::atomic m_init{false}; std::atomic m_stopped{false}; std::atomic m_rendering_distance{24}; - std::atomic m_gen_pool_threads{0}; - std::atomic m_net_pool_threads{0}; + std::atomic m_gen_threads{0}; + std::atomic m_net_threads{0}; + std::atomic m_compute_threads{0}; std::atomic m_max_threads{1}; std::atomic m_player_sum{0}; std::atomic m_game_ticks{0}; @@ -180,6 +182,7 @@ private: std::atomic> m_gen_thread_pool; std::atomic> m_net_thread_pool; + std::atomic> m_compute_thread_pool; std::atomic m_chunk_load_style{ChunkLoadStyle::CENTER}; diff --git a/include/Cubed/gameplay/world.hpp b/include/Cubed/gameplay/world.hpp index 69ffd7c..710166a 100644 --- a/include/Cubed/gameplay/world.hpp +++ b/include/Cubed/gameplay/world.hpp @@ -26,4 +26,7 @@ public: glm::vec3{0.5f, 0.5f, 0.5f}}; } }; + +enum class RunMode { CLIENT_ONLY, SERVER_ONLY, HYBRID }; + } // namespace Cubed \ No newline at end of file diff --git a/include/Cubed/tools/threas_utils.hpp b/include/Cubed/tools/threas_utils.hpp new file mode 100644 index 0000000..fd01fad --- /dev/null +++ b/include/Cubed/tools/threas_utils.hpp @@ -0,0 +1,130 @@ +#pragma once +#include "Cubed/gameplay/world.hpp" +#include "Cubed/tools/cubed_assert.hpp" + +#include +#include +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(hc); +} + +inline size_t get_server_available_threads() { + return std::max( + 1, safe_sub(get_hardware_threads(), SERVER_RESERVED_THREADS)); +} + +inline size_t get_client_available_threads() { + return std::max( + 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(available, 1, 16); + } + + case RunMode::HYBRID: { + auto available = get_client_available_threads(); + return std::clamp(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(available / 4, 1, + std::min(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(available / 8, 1, + std::min(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(available / 4, 1, + std::min(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(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(1, remain); + } + } + return 1; +} + +} // namespace Tools +} // namespace Cubed diff --git a/src/gameplay/client_world.cpp b/src/gameplay/client_world.cpp index 7fe4da9..7f1b645 100644 --- a/src/gameplay/client_world.cpp +++ b/src/gameplay/client_world.cpp @@ -6,6 +6,7 @@ #include "Cubed/gameplay/game_time.hpp" #include "Cubed/gameplay/packet.hpp" #include "Cubed/scene/world_scene.hpp" +#include "Cubed/tools/threas_utils.hpp" #include "Cubed/tools/time_tools.hpp" #include @@ -363,7 +364,8 @@ void ClientWorld::send_player_water_sound(bool underwater, } void ClientWorld::init(std::string_view player_name, - std::shared_ptr client) { + std::shared_ptr client, RunMode mode) { + m_runmode = mode; m_entity_manager.init(); m_player_manager.init(player_name); m_client = client; @@ -480,8 +482,8 @@ void ClientWorld::stop_client_thread() { m_game_running = false; } void ClientWorld::start_thread_pool() { - int max_threads = std::thread::hardware_concurrency(); - int threads = std::min(max_threads, 4); + auto threads = Tools::get_client_threads(m_runmode); + Logger::info("Client pool threads {}", threads); change_pool_threads(threads); } void ClientWorld::stop_thread_pool() { @@ -500,7 +502,6 @@ void ClientWorld::change_pool_threads(int threads) { m_max_threads = 1; } 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(used_thread)); } diff --git a/src/gameplay/network_server.cpp b/src/gameplay/network_server.cpp index d024470..26fdf35 100644 --- a/src/gameplay/network_server.cpp +++ b/src/gameplay/network_server.cpp @@ -92,14 +92,10 @@ void NetworkServer::net_run() { Logger::info("Server Started!"); } -void NetworkServer::start_server(int port) { +void NetworkServer::start_server(int port, RunMode mode) { m_port = port; m_config.set("port", m_port); - start_server(); -} - -void NetworkServer::start_server() { - m_world.init_world(); + m_world.init_world(mode); net_run(); m_started = true; } diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp index f359603..90778a1 100644 --- a/src/gameplay/server_world.cpp +++ b/src/gameplay/server_world.cpp @@ -7,6 +7,7 @@ #include "Cubed/tools/log.hpp" #include "Cubed/tools/math_tools.hpp" #include "Cubed/tools/net_utils.hpp" +#include "Cubed/tools/threas_utils.hpp" #include "Cubed/tools/uuid.hpp" #include @@ -178,8 +179,8 @@ void ServerWorld::send_chunk(int task_id, const std::string& uuid, 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.add_entity("cubed:pig", {0, 225, 0}); 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); }); - 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) { int priority = 10 + static_cast(i / CHUNKS_PER_PRIORITY); @@ -411,20 +412,30 @@ void ServerWorld::start_server_thread() { } void ServerWorld::start_thread_pool() { - int max_thread = std::thread::hardware_concurrency(); - if (m_gen_pool_threads == 0) { - m_gen_pool_threads = change_pool_threads(m_gen_thread_pool, - max_thread - RESERVED_THREADS); + if (m_gen_threads == 0) { + auto gen_threads = Tools::get_server_gen_threads(m_runmode); + Logger::info("Server Gen pool threads {}", gen_threads); + m_gen_threads = change_pool_threads(m_gen_thread_pool, gen_threads); } else { - m_gen_pool_threads = - change_pool_threads(m_gen_thread_pool, m_gen_pool_threads); + m_gen_threads = change_pool_threads(m_gen_thread_pool, m_gen_threads); } - if (m_net_pool_threads == 0) { - m_net_pool_threads = change_pool_threads(m_net_thread_pool, 4); + if (m_net_threads == 0) { + 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 { - m_net_pool_threads = - change_pool_threads(m_net_thread_pool, m_net_pool_threads); + m_net_threads = change_pool_threads(m_net_thread_pool, m_net_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); 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) { @@ -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(); } 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(); } 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); + m_net_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); + m_gen_threads = change_pool_threads(m_gen_thread_pool, threads); break; } } @@ -946,7 +964,6 @@ int ServerWorld::change_pool_threads( 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); thread_pool.store(std::make_shared(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; } ServerEntityManager& ServerWorld::entity_manager() { return m_entity_manager; } +std::shared_ptr ServerWorld::get_compute_pool() { + return m_compute_thread_pool.load(); +} } // namespace Cubed \ No newline at end of file diff --git a/src/scene/world_scene.cpp b/src/scene/world_scene.cpp index 84199a5..6ba1c82 100644 --- a/src/scene/world_scene.cpp +++ b/src/scene/world_scene.cpp @@ -129,10 +129,21 @@ void WorldScene::on_enter() { load_config(); m_error_ui.init(); m_client = std::make_shared(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.ip) { 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); } else { m_client->start(*m_argument.ip, *m_argument.port); @@ -148,7 +159,7 @@ void WorldScene::on_enter() { } else { ChunkGenerator::init(); } - m_server.start_server(param.port); + m_server.start_server(param.port, mode); } m_client->start(param.ip, param.port); @@ -157,7 +168,8 @@ void WorldScene::on_enter() { // init will send packet 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"); m_camera.camera_init(&m_client_world.get_player());