From 83918ce020050f0dfd231603e23becdb6c5feb79 Mon Sep 17 00:00:00 2001 From: Chris Miles Date: Wed, 25 Jun 2025 13:19:29 -0500 Subject: [PATCH] [Performance] Wearchange Packet Send Deduplication (#4916) * [Performance] Wearchange Packet Send Deduplication * Update mob_appearance.cpp * Reduce one allocation * Update mob_appearance.cpp * Change * Update mob_appearance.cpp * Update mob_appearance.cpp * Update mob_appearance.cpp * Update mob_appearance.cpp * Update mob_appearance.cpp * Wut * ffs --- zone/mob.h | 4 ++++ zone/mob_appearance.cpp | 52 ++++++++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/zone/mob.h b/zone/mob.h index c963dc3fc..18541857e 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -208,6 +208,10 @@ public: Timer m_see_close_mobs_timer; Timer m_mob_check_moving_timer; + uint16 m_last_wearchange_race_id = 0; + // client_id -> slot_id -> key + std::unordered_map> m_last_seen_wearchange; + // Bot attack flag Timer bot_attack_flag_timer; diff --git a/zone/mob_appearance.cpp b/zone/mob_appearance.cpp index 4f6b2b509..430247443 100644 --- a/zone/mob_appearance.cpp +++ b/zone/mob_appearance.cpp @@ -381,10 +381,6 @@ void Mob::SendWearChange(uint8 material_slot, Client *one_client) auto packet = new EQApplicationPacket(OP_WearChange, sizeof(WearChange_Struct)); auto w = (WearChange_Struct *) packet->pBuffer; - Log(Logs::Detail, Logs::MobAppearance, "[%s]", - GetCleanName() - ); - w->spawn_id = GetID(); w->material = static_cast(GetEquipmentMaterial(material_slot)); w->elite_material = IsEliteMaterialItem(material_slot); @@ -399,10 +395,50 @@ void Mob::SendWearChange(uint8 material_slot, Client *one_client) w->wear_slot_id = material_slot; - if (!one_client) { - entity_list.QueueClients(this, packet); - } else { - one_client->QueuePacket(packet, false, Client::CLIENT_CONNECTED); + if (GetRace() != m_last_wearchange_race_id) { + m_last_seen_wearchange.clear(); + m_last_wearchange_race_id = GetRace(); + } + + // this is a hash-like key to deduplicate packets sent to clients + // it includes spawn_id, material, elite_material, hero_forge_model, wear_slot_id, and color + // we send an enormous amount of wearchange packets in brute-force fashion and this is a low cost way to deduplicate them + // we could remove all the extra wearchanges at the expense of tracing down intermittent visual bugs over a long time + auto build_key = [&](const WearChange_Struct& s) -> uint64_t { + uint64_t key = 0; + + key |= static_cast(s.material & 0xFFF) << 0; // 12 bits + key |= static_cast(s.elite_material & 0x1) << 12; // 1 bit + key |= static_cast(s.hero_forge_model & 0xFFFFF) << 13; // 20 bits + key |= static_cast(GetRace() & 0xFFFF) << 33; // 16 bits + + // Optional: Fold in color for appearance differences + uint8_t folded_color = static_cast( + (s.color.Color * 17ull) & 0xFF + ); + key |= static_cast(folded_color) << 49; + + return key; + }; + + + auto dedupe_key = build_key(*w); + auto send_if_changed = [&](Client* client) { + auto& last_key = m_last_seen_wearchange[client->GetID()][material_slot]; + if (last_key == dedupe_key) { + return; + } + last_key = dedupe_key; + client->QueuePacket(packet, true, Client::CLIENT_CONNECTED); + }; + + if (one_client) { + send_if_changed(one_client); + } + else { + for (auto& [_, client] : entity_list.GetClientList()) { + send_if_changed(client); + } } safe_delete(packet);