From 0631b9f5dc4b2c296e772ef4df5ceada69edade2 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 22 Feb 2026 08:37:02 -0800 Subject: [PATCH] Reduce update-object and inventory update overhead --- src/game/game_handler.cpp | 114 ++++++++++++++++++++++--------------- src/game/world_packets.cpp | 5 +- 2 files changed, 71 insertions(+), 48 deletions(-) diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index c7e893e1..e26589da 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -4750,48 +4750,46 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { // Process out-of-range objects first for (uint64_t guid : data.outOfRangeGuids) { - if (entityManager.hasEntity(guid)) { - const bool isKnownTransport = transportGuids_.count(guid) > 0; - if (isKnownTransport) { - // Keep transports alive across out-of-range flapping. - // Boats/zeppelins are global movers and removing them here can make - // them disappear until a later movement snapshot happens to recreate them. - const bool playerAboardNow = (playerTransportGuid_ == guid); - const bool stickyAboard = (playerTransportStickyGuid_ == guid && playerTransportStickyTimer_ > 0.0f); - const bool movementSaysAboard = (movementInfo.transportGuid == guid); - LOG_INFO("Preserving transport on out-of-range: 0x", - std::hex, guid, std::dec, - " now=", playerAboardNow, - " sticky=", stickyAboard, - " movement=", movementSaysAboard); - continue; - } + auto entity = entityManager.getEntity(guid); + if (!entity) continue; - LOG_DEBUG("Entity went out of range: 0x", std::hex, guid, std::dec); - // Trigger despawn callbacks before removing entity - auto entity = entityManager.getEntity(guid); - if (entity) { - if (entity->getType() == ObjectType::UNIT && creatureDespawnCallback_) { - creatureDespawnCallback_(guid); - } else if (entity->getType() == ObjectType::PLAYER && playerDespawnCallback_) { - playerDespawnCallback_(guid); - otherPlayerVisibleItemEntries_.erase(guid); - otherPlayerVisibleDirty_.erase(guid); - otherPlayerMoveTimeMs_.erase(guid); - inspectedPlayerItemEntries_.erase(guid); - pendingAutoInspect_.erase(guid); - } else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) { - gameObjectDespawnCallback_(guid); - } - } - transportGuids_.erase(guid); - serverUpdatedTransportGuids_.erase(guid); - clearTransportAttachment(guid); - if (playerTransportGuid_ == guid) { - clearPlayerTransport(); - } - entityManager.removeEntity(guid); + const bool isKnownTransport = transportGuids_.count(guid) > 0; + if (isKnownTransport) { + // Keep transports alive across out-of-range flapping. + // Boats/zeppelins are global movers and removing them here can make + // them disappear until a later movement snapshot happens to recreate them. + const bool playerAboardNow = (playerTransportGuid_ == guid); + const bool stickyAboard = (playerTransportStickyGuid_ == guid && playerTransportStickyTimer_ > 0.0f); + const bool movementSaysAboard = (movementInfo.transportGuid == guid); + LOG_INFO("Preserving transport on out-of-range: 0x", + std::hex, guid, std::dec, + " now=", playerAboardNow, + " sticky=", stickyAboard, + " movement=", movementSaysAboard); + continue; } + + LOG_DEBUG("Entity went out of range: 0x", std::hex, guid, std::dec); + // Trigger despawn callbacks before removing entity + if (entity->getType() == ObjectType::UNIT && creatureDespawnCallback_) { + creatureDespawnCallback_(guid); + } else if (entity->getType() == ObjectType::PLAYER && playerDespawnCallback_) { + playerDespawnCallback_(guid); + otherPlayerVisibleItemEntries_.erase(guid); + otherPlayerVisibleDirty_.erase(guid); + otherPlayerMoveTimeMs_.erase(guid); + inspectedPlayerItemEntries_.erase(guid); + pendingAutoInspect_.erase(guid); + } else if (entity->getType() == ObjectType::GAMEOBJECT && gameObjectDespawnCallback_) { + gameObjectDespawnCallback_(guid); + } + transportGuids_.erase(guid); + serverUpdatedTransportGuids_.erase(guid); + clearTransportAttachment(guid); + if (playerTransportGuid_ == guid) { + clearPlayerTransport(); + } + entityManager.removeEntity(guid); } // Process update blocks @@ -5466,7 +5464,12 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { } // Update XP / inventory slot / skill fields for player entity if (block.guid == playerGuid) { - std::map oldFieldsSnapshot = lastPlayerFields_; + const bool needCoinageDetectSnapshot = + (pendingMoneyDelta_ != 0 && pendingMoneyDeltaTimer_ > 0.0f); + std::map oldFieldsSnapshot; + if (needCoinageDetectSnapshot) { + oldFieldsSnapshot = lastPlayerFields_; + } if (block.hasMovement && block.runSpeed > 0.1f && block.runSpeed < 100.0f) { serverRunSpeed_ = block.runSpeed; // Some server dismount paths update run speed without updating mount display field. @@ -5480,10 +5483,13 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { } } } + auto mergeHint = lastPlayerFields_.end(); for (const auto& [key, val] : block.fields) { - lastPlayerFields_[key] = val; + mergeHint = lastPlayerFields_.insert_or_assign(mergeHint, key, val); + } + if (needCoinageDetectSnapshot) { + maybeDetectCoinageIndex(oldFieldsSnapshot, lastPlayerFields_); } - maybeDetectCoinageIndex(oldFieldsSnapshot, lastPlayerFields_); maybeDetectVisibleItemLayout(); detectInventorySlotBases(block.fields); bool slotsChanged = false; @@ -5545,17 +5551,33 @@ void GameHandler::handleUpdateObject(network::Packet& packet) { // Update item stack count for online items if (entity->getType() == ObjectType::ITEM || entity->getType() == ObjectType::CONTAINER) { + bool inventoryChanged = false; + const uint16_t itemStackField = fieldIndex(UF::ITEM_FIELD_STACK_COUNT); + const uint16_t containerNumSlotsField = fieldIndex(UF::CONTAINER_FIELD_NUM_SLOTS); + const uint16_t containerSlot1Field = fieldIndex(UF::CONTAINER_FIELD_SLOT_1); for (const auto& [key, val] : block.fields) { - if (key == fieldIndex(UF::ITEM_FIELD_STACK_COUNT)) { + if (key == itemStackField) { auto it = onlineItems_.find(block.guid); - if (it != onlineItems_.end()) it->second.stackCount = val; + if (it != onlineItems_.end() && it->second.stackCount != val) { + it->second.stackCount = val; + inventoryChanged = true; + } } } // Update container slot GUIDs on bag content changes if (entity->getType() == ObjectType::CONTAINER) { + for (const auto& [key, _] : block.fields) { + if ((containerNumSlotsField != 0xFFFF && key == containerNumSlotsField) || + (containerSlot1Field != 0xFFFF && key >= containerSlot1Field && key < containerSlot1Field + 72)) { + inventoryChanged = true; + break; + } + } extractContainerFields(block.guid, block.fields); } - rebuildOnlineInventory(); + if (inventoryChanged) { + rebuildOnlineInventory(); + } } if (block.hasMovement && entity->getType() == ObjectType::GAMEOBJECT) { if (transportGuids_.count(block.guid) && transportMoveCallback_) { diff --git a/src/game/world_packets.cpp b/src/game/world_packets.cpp index 6c820804..68073633 100644 --- a/src/game/world_packets.cpp +++ b/src/game/world_packets.cpp @@ -1117,7 +1117,8 @@ bool UpdateObjectParser::parseUpdateFields(network::Packet& packet, UpdateBlock& highestSetBit = fieldIndex; } uint32_t value = packet.readUInt32(); - block.fields[fieldIndex] = value; + // fieldIndex is monotonically increasing here, so end() is a good insertion hint. + block.fields.emplace_hint(block.fields.end(), fieldIndex, value); valuesReadCount++; LOG_DEBUG(" Field[", fieldIndex, "] = 0x", std::hex, value, std::dec); @@ -1256,7 +1257,7 @@ bool UpdateObjectParser::parse(network::Packet& packet, UpdateObjectData& data) return false; } - data.blocks.push_back(block); + data.blocks.emplace_back(std::move(block)); }