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.
This commit is contained in:
2026-08-06 16:48:18 +08:00
parent 3534acd90f
commit c6a75e709e
8 changed files with 100 additions and 64 deletions

View File

@@ -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);

View File

@@ -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<VoiceMsg>() {
template <> constexpr uint16_t get_packet_id<S2CEntityUpdate>() {
return std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE);
}
template <> constexpr uint16_t get_packet_id<S2CEntityUpdateBatch>() {
return std::to_underlying(PacketEnum::S2C_ENTITY_UPDATE_BATCH);
}
template <typename T>
requires std::derived_from<T, google::protobuf::Message>

View File

@@ -1,5 +1,6 @@
#pragma once
#include "Cubed/gameplay/ecs/entity.hpp"
#include "Cubed/gameplay/gait.hpp"
#include "glm/ext/vector_float3.hpp"
#include <entt/entt.hpp>
@@ -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<EntityID, entt::entity>;
using acc = EntityMap::accessor;
using cacc = EntityMap::const_accessor;
@@ -47,9 +56,8 @@ private:
void send_all_entities(std::shared_ptr<Session>& session);
void update_ai(entt::entity e);
void update_move(entt::entity e);
void
update_send(entt::entity e,
tbb::concurrent_vector<std::shared_ptr<Session>>& sessions);
void update_send(entt::entity e,
tbb::concurrent_vector<EntitySendData>& sessions);
template <typename... Args>
EntityID create_entity_in_factory(Args&&... args) {
auto entity = m_registry.create();

View File

@@ -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;

View File

@@ -162,6 +162,12 @@ asio::awaitable<void> 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<S2CEntityUpdateBatch>(&arena);
if (decode_packet(*msg, body_data, header)) {
m_world.entity_manager().receive_entity_update(*msg);
}
} break;
}
}
} catch (const asio::system_error& e) {

View File

@@ -45,11 +45,12 @@ void ServerEntityManager::update() {
for (auto e : view) {
entities.push_back(e);
}
tbb::concurrent_vector<EntitySendData> 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<BaseServerCreature>(e);
if (!m_world.get_chunk_ref_count(c.transform.position.value)) {
const auto& entity = m_registry.get<Entity>(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<S2CEntityUpdateBatch>(&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<std::shared_ptr<Session>>& sessions) {
entt::entity e, tbb::concurrent_vector<EntitySendData>& send_data) {
if (!m_registry.all_of<Entity, BaseServerCreature>(e)) {
return;
@@ -83,23 +98,17 @@ void ServerEntityManager::update_send(
const auto [entity, creature] =
m_registry.get<Entity, BaseServerCreature>(e);
Arena arena;
auto* p = Arena::Create<S2CEntityUpdate>(&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<S2CEntityDestory>(&arena);
s2c->set_id(id);
auto packet = make_packet(*s2c);
for (auto& s : sessions) {
s->send(make_packet(*s2c));
s->send(packet);
}
}

View File

@@ -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<std::shared_ptr<Session>> other;
{
@@ -617,16 +616,12 @@ 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());
}
}
}
for (auto& session : other) {
if (!session) {
continue;
}
Arena arena;
auto* rsp = Arena::Create<PlayerInfoRsp>(&arena);
rsp->set_uuid(uuid);
@@ -638,7 +633,14 @@ void ServerWorld::sync_player_pos(const C2S_PlayerInfo& prsp) {
rsp->set_yaw(yaw);
rsp->set_pitch(pitch);
rsp->set_gait(prsp.gait());
session->send(make_packet(*rsp), 0);
auto packet = make_packet(*rsp);
for (auto& session : other) {
if (!session) {
continue;
}
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<std::shared_ptr<Session>> 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<LogoutRsp>(&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);
}
}

View File

@@ -29,3 +29,7 @@ message S2CEntityUpdate {
Vec3 direction = 3;
int32 gait = 4;
}
message S2CEntityUpdateBatch {
repeated S2CEntityUpdate updates = 1;
}