refactor(gameplay): replace client thread with timer-based system and add entity manager

This commit is contained in:
2026-07-29 18:17:15 +08:00
parent 3a9ab6a14a
commit efa49a98b7
4 changed files with 31 additions and 22 deletions

View File

@@ -5,6 +5,7 @@
#include "Cubed/gameplay/chat_message.hpp" #include "Cubed/gameplay/chat_message.hpp"
#include "Cubed/gameplay/chunk_pos.hpp" #include "Cubed/gameplay/chunk_pos.hpp"
#include "Cubed/gameplay/client_chunk.hpp" #include "Cubed/gameplay/client_chunk.hpp"
#include "Cubed/gameplay/client_entity_manager.hpp"
#include "Cubed/gameplay/client_player_manager.hpp" #include "Cubed/gameplay/client_player_manager.hpp"
#include "Cubed/gameplay/game_time.hpp" #include "Cubed/gameplay/game_time.hpp"
#include "Cubed/gameplay/local_player.hpp" #include "Cubed/gameplay/local_player.hpp"
@@ -84,6 +85,7 @@ public:
Config& get_config(); Config& get_config();
WorldScene& world_scene(); WorldScene& world_scene();
ClientPlayerManager& player_manager(); ClientPlayerManager& player_manager();
ClientEntityManager& entity_manager();
void set_direct_exit(); void set_direct_exit();
void receive_chat_message(ChatMsg& msg); void receive_chat_message(ChatMsg& msg);
@@ -92,9 +94,9 @@ public:
bool enable_voice_chat() const; bool enable_voice_chat() const;
int get_per_tick_time() const override; int get_per_tick_time() const override;
template <typename Fn> template <typename Fn>
void register_ticktimer(std::string_view id, TickType threshold, Fn&& f) { void register_timer(std::string_view id, float threshold, Fn&& f) {
m_ticktimers.emplace( m_timers.emplace(std::piecewise_construct,
std::piecewise_construct, std::forward_as_tuple(std::string(id)), std::forward_as_tuple(std::string(id)),
std::forward_as_tuple(threshold, std::forward<Fn>(f))); std::forward_as_tuple(threshold, std::forward<Fn>(f)));
} }
@@ -126,14 +128,13 @@ 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;
ClientEntityManager m_entity_manager;
ClientPlayerManager m_player_manager; ClientPlayerManager m_player_manager;
ChunkHashMap m_chunks; ChunkHashMap m_chunks;
AudioEngine& m_audio; AudioEngine& m_audio;
Config& m_config; Config& m_config;
WorldScene& m_world_scene; WorldScene& m_world_scene;
std::vector<glm::vec4> m_planes; std::vector<glm::vec4> m_planes;
std::jthread m_client_thread;
tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue; tbb::concurrent_queue<std::unique_ptr<ClientChunk>> m_pending_upload_queue;
tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue; tbb::concurrent_queue<ChunkPos> m_dirty_chunk_queue;
@@ -144,7 +145,6 @@ private:
std::deque<ChunkPos> m_dirty_queue; std::deque<ChunkPos> m_dirty_queue;
std::vector<const ChunkRenderSnapshot*> m_render_snapshots; std::vector<const ChunkRenderSnapshot*> m_render_snapshots;
tbb::concurrent_unordered_map<std::string, TickTimer> m_ticktimers;
std::unordered_map<std::string, Timer> m_timers; std::unordered_map<std::string, Timer> m_timers;
std::atomic<bool> m_exit_direct{false}; std::atomic<bool> m_exit_direct{false};
std::atomic<bool> m_game_running{false}; std::atomic<bool> m_game_running{false};
@@ -164,8 +164,6 @@ private:
Random m_random; Random m_random;
void client_run(std::stop_token token);
void set_block(const glm::ivec3& pos, unsigned id); void set_block(const glm::ivec3& pos, unsigned id);
void update_chunk(const ChunkPosSet& old, const ChunkPosSet& now); void update_chunk(const ChunkPosSet& old, const ChunkPosSet& now);

View File

@@ -60,8 +60,8 @@ enum class PacketEnum : uint16_t {
BLOCK_CHANGE_REQ = 3003, BLOCK_CHANGE_REQ = 3003,
BLOCK_CHANGE_RSP = 3004, BLOCK_CHANGE_RSP = 3004,
S2C_CLEAR_ALL_CHUNKS = 3005, S2C_CLEAR_ALL_CHUNKS = 3005,
S2C_ENTITY_CREATE = 3006,
UPDATE_TIME = 3006, UPDATE_TIME = 3006,
S2C_ENTITY_CREATE = 3007,
CHAT_MSG = 4001, CHAT_MSG = 4001,
VOICE_MSG = 4002, VOICE_MSG = 4002,
PING = 9001, PING = 9001,

View File

@@ -23,8 +23,8 @@ struct ChunkRenderData {
} // namespace } // namespace
ClientWorld::ClientWorld(AudioEngine& auido, Config& config, WorldScene& scene) ClientWorld::ClientWorld(AudioEngine& auido, Config& config, WorldScene& scene)
: m_player_manager(*this), m_audio(auido), m_config(config), : m_entity_manager(*this), m_player_manager(*this), m_audio(auido),
m_world_scene(scene) {} m_config(config), m_world_scene(scene) {}
ClientWorld::~ClientWorld() { ClientWorld::~ClientWorld() {
m_client->close(); m_client->close();
@@ -51,7 +51,7 @@ ClientWorld::~ClientWorld() {
std::lock_guard lk(m_delete_vao_mutex); std::lock_guard lk(m_delete_vao_mutex);
m_pending_delete_vao.clear(); m_pending_delete_vao.clear();
} }
m_ticktimers.clear(); m_timers.clear();
} }
const std::optional<LookBlock>& ClientWorld::get_look_block_pos() const { const std::optional<LookBlock>& ClientWorld::get_look_block_pos() const {
@@ -363,6 +363,7 @@ 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) {
m_entity_manager.init();
m_player_manager.init(player_name); m_player_manager.init(player_name);
m_client = client; m_client = client;
@@ -371,10 +372,10 @@ void ClientWorld::init(std::string_view player_name,
m_random.init(ChunkGenerator::seed()); m_random.init(ChunkGenerator::seed());
// timer // timer
register_ticktimer("player_pos", 1, [this]() { register_timer("player_pos", 0.05f, [this]() {
m_player_manager.report_player_info(m_client.get()); m_player_manager.report_player_info(m_client.get());
}); });
m_timers.try_emplace("Birds Sound", 60.0f, [this]() { register_timer("Birds Sound", 60.0f, [this]() {
auto player_pos = m_player_manager.get_local().get_player_pos(); auto player_pos = m_player_manager.get_local().get_player_pos();
if (player_pos.y < SEA_LEVEL) { if (player_pos.y < SEA_LEVEL) {
return; return;
@@ -390,7 +391,7 @@ void ClientWorld::init(std::string_view player_name,
} }
}); });
m_timers.try_emplace("Ocean Wave", 3.0f, [this]() { register_timer("Ocean Wave", 3.0f, [this]() {
auto player_pos = m_player_manager.get_local().get_player_pos(); auto player_pos = m_player_manager.get_local().get_player_pos();
if (player_pos.y < SEA_LEVEL - 10 || player_pos.y > SEA_LEVEL + 10) { if (player_pos.y < SEA_LEVEL - 10 || player_pos.y > SEA_LEVEL + 10) {
return; return;
@@ -410,7 +411,7 @@ void ClientWorld::init(std::string_view player_name,
} }
}); });
m_timers.try_emplace("under water bubble", 1.5f, [this]() { register_timer("under water bubble", 1.5f, [this]() {
if (m_player_manager.get_local().is_underwater()) { if (m_player_manager.get_local().is_underwater()) {
auto ans = m_random.random_int(1, 2); auto ans = m_random.random_int(1, 2);
std::string sound = std::string sound =
@@ -420,7 +421,7 @@ void ClientWorld::init(std::string_view player_name,
} }
}); });
m_timers.try_emplace("bgm change", 350.0f, [this]() { register_timer("bgm change", 350.0f, [this]() {
if (m_day_tick >= 17000 || m_day_tick < 5000) { if (m_day_tick >= 17000 || m_day_tick < 5000) {
m_audio.change_bgm("bgm/bgm002.ogg"); m_audio.change_bgm("bgm/bgm002.ogg");
} else { } else {
@@ -455,11 +456,11 @@ void ClientWorld::start_client_thread(std::string_view uuid) {
} }
// response // response
m_player_manager.get_local().set_uuid(uuid); m_player_manager.get_local().set_uuid(uuid);
/*
m_client_thread = std::jthread([this](std::stop_token token) { m_client_thread = std::jthread([this](std::stop_token token) {
m_game_running = true; m_game_running = true;
client_run(token); client_run(token);
}); });*/
// Wait for 20 ticks, after the server's central chunk is generated, then // Wait for 20 ticks, after the server's central chunk is generated, then
// request chunks // request chunks
@@ -470,10 +471,11 @@ void ClientWorld::start_client_thread(std::string_view uuid) {
} }
void ClientWorld::stop_client_thread() { void ClientWorld::stop_client_thread() {
/*
m_client_thread.request_stop(); m_client_thread.request_stop();
if (m_client_thread.joinable()) { if (m_client_thread.joinable()) {
m_client_thread.join(); m_client_thread.join();
} }*/
m_game_running = false; m_game_running = false;
} }
void ClientWorld::start_thread_pool() { void ClientWorld::start_thread_pool() {
@@ -512,6 +514,7 @@ void ClientWorld::reload_config(bool chunk_build) {
m_player_manager.reload_config(); m_player_manager.reload_config();
} }
/*
void ClientWorld::client_run(std::stop_token stoken) { void ClientWorld::client_run(std::stop_token stoken) {
Logger::info("Client Thread Started"); Logger::info("Client Thread Started");
using Clock = std::chrono::steady_clock; using Clock = std::chrono::steady_clock;
@@ -527,6 +530,7 @@ void ClientWorld::client_run(std::stop_token stoken) {
std::this_thread::sleep_until(next); std::this_thread::sleep_until(next);
} }
} }
*/
void ClientWorld::update_chunk(const ChunkPosSet& old, const ChunkPosSet& now) { void ClientWorld::update_chunk(const ChunkPosSet& old, const ChunkPosSet& now) {
@@ -677,6 +681,7 @@ const AudioEngine& ClientWorld::get_audio() const { return m_audio; }
Config& ClientWorld::get_config() { return m_config; } Config& ClientWorld::get_config() { return m_config; }
WorldScene& ClientWorld::world_scene() { return m_world_scene; } WorldScene& ClientWorld::world_scene() { return m_world_scene; }
ClientPlayerManager& ClientWorld::player_manager() { return m_player_manager; } ClientPlayerManager& ClientWorld::player_manager() { return m_player_manager; }
ClientEntityManager& ClientWorld::entity_manager() { return m_entity_manager; }
void ClientWorld::set_direct_exit() { m_exit_direct = true; } void ClientWorld::set_direct_exit() { m_exit_direct = true; }
void ClientWorld::request_exit() { void ClientWorld::request_exit() {
if (m_receive_exit) { if (m_receive_exit) {

View File

@@ -142,7 +142,13 @@ asio::awaitable<void> NetworkClient::read_loop() {
if (decode_packet(*msg, body_data, header)) { if (decode_packet(*msg, body_data, header)) {
m_world.receive_voice_message(*msg); m_world.receive_voice_message(*msg);
} }
} break;
case std::to_underlying(PacketEnum::S2C_ENTITY_CREATE): {
auto* msg = Arena::Create<S2CEntityCreate>(&arena);
if (decode_packet(*msg, body_data, header)) {
m_world.entity_manager().receive_entity_create(*msg);
} }
} break;
} }
} }
} catch (const asio::system_error& e) { } catch (const asio::system_error& e) {