From c6a75e709e52cd2094786a45705ef8829820dfb0 Mon Sep 17 00:00:00 2001 From: zhenyan121 <3367366583@qq.com> Date: Thu, 6 Aug 2026 16:48:18 +0800 Subject: [PATCH] perf(gameplay): batch entity updates into single packet Aggregate per-entity update messages into S2CEntityUpdateBatch and broadcast once per tick. Reuse serialized packets in several broadcast loops to avoid repeated make_packet calls. --- .../Cubed/gameplay/client_entity_manager.hpp | 4 +- include/Cubed/gameplay/packet.hpp | 4 + .../Cubed/gameplay/server_entity_manager.hpp | 14 +++- src/gameplay/client_entity_manager.cpp | 8 +- src/gameplay/network_client.cpp | 6 ++ src/gameplay/server_entity_manager.cpp | 50 ++++++++----- src/gameplay/server_world.cpp | 74 +++++++++---------- src/proto/world/entity.proto | 4 + 8 files changed, 100 insertions(+), 64 deletions(-) diff --git a/include/Cubed/gameplay/client_entity_manager.hpp b/include/Cubed/gameplay/client_entity_manager.hpp index 6330f05..427e898 100644 --- a/include/Cubed/gameplay/client_entity_manager.hpp +++ b/include/Cubed/gameplay/client_entity_manager.hpp @@ -19,8 +19,8 @@ public: void receive_entity_create(S2CEntityCreate& msg); void receive_entity_destory(EntityID id); - void receive_entity_update(S2CEntityUpdate& msg); - + void receive_entity_update(const S2CEntityUpdate& msg); + void receive_entity_update(S2CEntityUpdateBatch& msg); void destory(EntityID id); void create(std::string_view name, const glm::vec3& pos); diff --git a/include/Cubed/gameplay/packet.hpp b/include/Cubed/gameplay/packet.hpp index aa4f3f8..a2a3ac7 100644 --- a/include/Cubed/gameplay/packet.hpp +++ b/include/Cubed/gameplay/packet.hpp @@ -68,6 +68,7 @@ enum class PacketEnum : uint16_t { C2S_ENTITY_CREATE_REQUEST = 3009, C2S_ENTITY_DESTORY_REQUEST = 3010, S2C_ENTITY_UPDATE = 3011, + S2C_ENTITY_UPDATE_BATCH = 3012, CHAT_MSG = 4001, VOICE_MSG = 4002, @@ -153,6 +154,9 @@ template <> constexpr uint16_t get_packet_id() { template <> constexpr uint16_t get_packet_id() { return std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE); } +template <> constexpr uint16_t get_packet_id() { + return std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE_BATCH); +} template requires std::derived_from diff --git a/include/Cubed/gameplay/server_entity_manager.hpp b/include/Cubed/gameplay/server_entity_manager.hpp index 41a8e4a..2830cca 100644 --- a/include/Cubed/gameplay/server_entity_manager.hpp +++ b/include/Cubed/gameplay/server_entity_manager.hpp @@ -1,5 +1,6 @@ #pragma once #include "Cubed/gameplay/ecs/entity.hpp" +#include "Cubed/gameplay/gait.hpp" #include "glm/ext/vector_float3.hpp" #include @@ -26,6 +27,14 @@ private: std::string name; glm::vec3 pos; }; + + struct EntitySendData { + EntityID id; + glm::vec3 pos; + glm::vec3 dir; + Gait gait; + }; + using EntityMap = tbb::concurrent_hash_map; using acc = EntityMap::accessor; using cacc = EntityMap::const_accessor; @@ -47,9 +56,8 @@ private: void send_all_entities(std::shared_ptr& session); void update_ai(entt::entity e); void update_move(entt::entity e); - void - update_send(entt::entity e, - tbb::concurrent_vector>& sessions); + void update_send(entt::entity e, + tbb::concurrent_vector& sessions); template EntityID create_entity_in_factory(Args&&... args) { auto entity = m_registry.create(); diff --git a/src/gameplay/client_entity_manager.cpp b/src/gameplay/client_entity_manager.cpp index e1abd2b..356bf0f 100644 --- a/src/gameplay/client_entity_manager.cpp +++ b/src/gameplay/client_entity_manager.cpp @@ -87,7 +87,7 @@ void ClientEntityManager::receive_entity_destory(EntityID id) { m_tasks.emplace(Command::DESTORY, id); } -void ClientEntityManager::receive_entity_update(S2CEntityUpdate& msg) { +void ClientEntityManager::receive_entity_update(const S2CEntityUpdate& msg) { UpdateInfo e; e.id = msg.id(); e.pos = Tools::get_net_vec3(msg.pos()); @@ -96,6 +96,12 @@ void ClientEntityManager::receive_entity_update(S2CEntityUpdate& msg) { m_tasks.emplace(Command::UPDATE, std::move(e)); } +void ClientEntityManager::receive_entity_update(S2CEntityUpdateBatch& msg) { + for (auto& u : msg.updates()) { + receive_entity_update(u); + } +} + void ClientEntityManager::destory(EntityID id) { auto client = m_world.get_client(); Arena arena; diff --git a/src/gameplay/network_client.cpp b/src/gameplay/network_client.cpp index 7791b6f..e6a59bf 100644 --- a/src/gameplay/network_client.cpp +++ b/src/gameplay/network_client.cpp @@ -162,6 +162,12 @@ asio::awaitable NetworkClient::read_loop() { m_world.entity_manager().receive_entity_update(*msg); } } break; + case std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE_BATCH): { + auto* msg = Arena::Create(&arena); + if (decode_packet(*msg, body_data, header)) { + m_world.entity_manager().receive_entity_update(*msg); + } + } break; } } } catch (const asio::system_error& e) { diff --git a/src/gameplay/server_entity_manager.cpp b/src/gameplay/server_entity_manager.cpp index 1a8483e..4b70cf8 100644 --- a/src/gameplay/server_entity_manager.cpp +++ b/src/gameplay/server_entity_manager.cpp @@ -45,11 +45,12 @@ void ServerEntityManager::update() { for (auto e : view) { entities.push_back(e); } + tbb::concurrent_vector send_data; // parallel block touches disjoint entities only; // structural registry changes stay on the server thread via m_tasks. parallel_do( *pool, entities.begin(), entities.end(), pool->thread_sum(), - [this, &sessions](entt::entity e) { + [this, &send_data](entt::entity e) { const auto& c = m_registry.get(e); if (!m_world.get_chunk_ref_count(c.transform.position.value)) { const auto& entity = m_registry.get(e); @@ -58,8 +59,23 @@ void ServerEntityManager::update() { } update_ai(e); update_move(e); - update_send(e, sessions); + update_send(e, send_data); }); + if (!send_data.empty()) { + Arena arena; + auto* msg = Arena::Create(&arena); + for (auto& data : send_data) { + auto* u = msg->add_updates(); + u->set_id(data.id); + Tools::set_net_pos(u, data.pos); + Tools::set_net_vec3(u->mutable_direction(), data.dir); + u->set_gait(get_gait_id(data.gait)); + } + auto packet = make_packet(msg); + for (auto& player : sessions) { + player->send(packet); + } + } } void ServerEntityManager::update_ai(entt::entity e) { @@ -74,8 +90,7 @@ void ServerEntityManager::update_move(entt::entity e) { } void ServerEntityManager::update_send( - entt::entity e, - tbb::concurrent_vector>& sessions) { + entt::entity e, tbb::concurrent_vector& send_data) { if (!m_registry.all_of(e)) { return; @@ -83,23 +98,17 @@ void ServerEntityManager::update_send( const auto [entity, creature] = m_registry.get(e); - Arena arena; - auto* p = Arena::Create(&arena); - p->set_id(entity.id); - Tools::set_net_pos(p, creature.transform.position.value); - - Tools::set_net_vec3(p->mutable_direction(), - creature.transform.direction.value); + EntitySendData data; + data.id = entity.id; + data.pos = creature.transform.position.value; + data.dir = creature.transform.direction.value; const auto& v = creature.velocity.value; if (v.x * v.x + v.z * v.z > 1e-4f) { - p->set_gait(get_gait_id(Gait::WALK)); + data.gait = Gait::WALK; } else { - p->set_gait(get_gait_id(Gait::STOP)); - } - - for (auto& s : sessions) { - s->send(make_packet(p)); + data.gait = Gait::STOP; } + send_data.emplace_back(std::move(data)); } void ServerEntityManager::handle_task() { @@ -177,9 +186,9 @@ void ServerEntityManager::handle_entity_create(EntityID id, s2c->set_id(id); s2c->set_name(name); Tools::set_net_pos(s2c, pos); - + auto packet = make_packet(*s2c); for (auto& s : sessions) { - s->send(make_packet(*s2c)); + s->send(packet); } } @@ -194,8 +203,9 @@ void ServerEntityManager::handle_entity_destory(EntityID id) { Arena arena; auto* s2c = Arena::Create(&arena); s2c->set_id(id); + auto packet = make_packet(*s2c); for (auto& s : sessions) { - s->send(make_packet(*s2c)); + s->send(packet); } } diff --git a/src/gameplay/server_world.cpp b/src/gameplay/server_world.cpp index 48e10f8..b1340a1 100644 --- a/src/gameplay/server_world.cpp +++ b/src/gameplay/server_world.cpp @@ -89,11 +89,10 @@ void ServerWorld::send_time() { rsp->set_day_tick(m_day_tick); rsp->set_game_tick(m_game_ticks); - { - std::shared_lock lock(m_players_mutex); - for (auto& [uuid, player] : m_players) { - player.get_session()->send(make_packet(*rsp), 3); - } + auto sessions = get_all_session(); + auto packet = make_packet(*rsp); + for (auto& s : sessions) { + s->send(packet, 3); } } @@ -608,7 +607,7 @@ void ServerWorld::sync_player_pos(const C2S_PlayerInfo& prsp) { name = it->second.get_name(); } - ChunkPos pos = get_chunk_pos(x, z); + ChunkPos c_pos = get_chunk_pos(x, z); // update other player pos; std::vector> other; { @@ -617,28 +616,31 @@ void ServerWorld::sync_player_pos(const C2S_PlayerInfo& prsp) { if (o_uuid == uuid) { continue; } - if (player.has_player(pos)) { + if (player.has_player(c_pos)) { other.emplace_back(player.get_session()); } } } + Arena arena; + auto* rsp = Arena::Create(&arena); + rsp->set_uuid(uuid); + rsp->set_name(name); + auto* pos = rsp->mutable_pos(); + pos->set_x(x); + pos->set_y(y); + pos->set_z(z); + rsp->set_yaw(yaw); + rsp->set_pitch(pitch); + rsp->set_gait(prsp.gait()); + auto packet = make_packet(*rsp); + for (auto& session : other) { if (!session) { continue; } - Arena arena; - auto* rsp = Arena::Create(&arena); - rsp->set_uuid(uuid); - rsp->set_name(name); - auto* pos = rsp->mutable_pos(); - pos->set_x(x); - pos->set_y(y); - pos->set_z(z); - rsp->set_yaw(yaw); - rsp->set_pitch(pitch); - rsp->set_gait(prsp.gait()); - session->send(make_packet(*rsp), 0); + + session->send(packet, 0); } } @@ -673,13 +675,13 @@ void ServerWorld::sync_player_water_sound(const PlayerWaterSound& rsp) { p->set_x(x); p->set_y(y); p->set_z(z); - + auto packet = make_packet(*r); for (auto& session : other) { if (!session) { continue; } - session->send(make_packet(*r), 5); + session->send(packet, 5); } } @@ -765,17 +767,12 @@ void ServerWorld::handle_player_exit(const std::string& uuid) { rsp->set_server_stop(false); exit_session->send(make_packet(*rsp), 0); - std::vector> sessions; - { - std::shared_lock lock(m_players_mutex); - for (auto& [uuid, player] : m_players) { - sessions.emplace_back(player.get_session()); - } - } + auto sessions = get_all_session(); + auto packet = make_packet(*rsp); for (auto& s : sessions) { if (s) { - s->send(make_packet(*rsp), 0); + s->send(packet, 0); } } @@ -856,9 +853,9 @@ void ServerWorld::handle_voice_message(VoiceMsg& msg) { pos->set_x(p.x); pos->set_y(p.y); pos->set_z(p.z); - + auto packet = make_packet(*msg); for (auto& s : session) { - s->send(make_packet(*msg), 5); + s->send(packet, 5); } }); } @@ -889,10 +886,10 @@ void ServerWorld::handle_block_change(const BlockChangeReq& req) { } } } - + auto packet = make_packet(*rsp); for (auto& x : sessions) { if (x) { - x->send(make_packet(*rsp), 1); + x->send(packet, 1); } } } @@ -971,9 +968,10 @@ void ServerWorld::send_server_stop() { Arena arena; auto* rsp = Arena::Create(&arena); rsp->set_server_stop(true); - std::shared_lock lock(m_players_mutex); - for (auto& [uuid, player] : m_players) { - player.get_session()->send(make_packet(*rsp), 0); + auto sessions = get_all_session(); + auto packet = make_packet(*rsp); + for (auto& s : sessions) { + s->send(packet, 0); } Logger::info("Send Server Mesaage Success"); } @@ -1002,9 +1000,9 @@ void ServerWorld::boardcast_message(const std::string& name, msg->set_color(std::to_underlying(color)); msg->set_system_msg(system_msg); - + auto packet = make_packet(*msg); for (auto& s : m_session) { - s->send(make_packet(*msg)); + s->send(packet); } } diff --git a/src/proto/world/entity.proto b/src/proto/world/entity.proto index 43a82e4..312d079 100644 --- a/src/proto/world/entity.proto +++ b/src/proto/world/entity.proto @@ -28,4 +28,8 @@ message S2CEntityUpdate { Vec3 pos = 2; Vec3 direction = 3; int32 gait = 4; +} + +message S2CEntityUpdateBatch { + repeated S2CEntityUpdate updates = 1; } \ No newline at end of file