mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-05-03 16:03:52 +00:00
Throttle player equipment compositing and spawning to fix walking stutter
Defer equipment texture compositing to a queue processed max 1 per frame instead of compositing immediately on callback (each compositeWithRegions call does file I/O + CPU blit + GPU upload on the main thread). Reduce MAX_SPAWNS_PER_FRAME from 96 to 8 and increase inspect rate limit from 0.75s to 2s. Demote noisy per-frame logs to DEBUG level.
This commit is contained in:
parent
6b1f3e3637
commit
e04f824d74
3 changed files with 23 additions and 7 deletions
|
|
@ -224,7 +224,7 @@ private:
|
||||||
float x, y, z, orientation;
|
float x, y, z, orientation;
|
||||||
};
|
};
|
||||||
std::vector<PendingCreatureSpawn> pendingCreatureSpawns_;
|
std::vector<PendingCreatureSpawn> pendingCreatureSpawns_;
|
||||||
static constexpr int MAX_SPAWNS_PER_FRAME = 96;
|
static constexpr int MAX_SPAWNS_PER_FRAME = 8;
|
||||||
static constexpr uint16_t MAX_CREATURE_SPAWN_RETRIES = 300;
|
static constexpr uint16_t MAX_CREATURE_SPAWN_RETRIES = 300;
|
||||||
std::unordered_set<uint64_t> pendingCreatureSpawnGuids_;
|
std::unordered_set<uint64_t> pendingCreatureSpawnGuids_;
|
||||||
std::unordered_map<uint64_t, uint16_t> creatureSpawnRetryCounts_;
|
std::unordered_map<uint64_t, uint16_t> creatureSpawnRetryCounts_;
|
||||||
|
|
@ -244,6 +244,9 @@ private:
|
||||||
};
|
};
|
||||||
std::unordered_map<uint64_t, OnlinePlayerAppearanceState> onlinePlayerAppearance_;
|
std::unordered_map<uint64_t, OnlinePlayerAppearanceState> onlinePlayerAppearance_;
|
||||||
std::unordered_map<uint64_t, std::pair<std::array<uint32_t, 19>, std::array<uint8_t, 19>>> pendingOnlinePlayerEquipment_;
|
std::unordered_map<uint64_t, std::pair<std::array<uint32_t, 19>, std::array<uint8_t, 19>>> pendingOnlinePlayerEquipment_;
|
||||||
|
// Deferred equipment compositing queue — processes max 1 per frame to avoid stutter
|
||||||
|
std::vector<std::pair<uint64_t, std::pair<std::array<uint32_t, 19>, std::array<uint8_t, 19>>>> deferredEquipmentQueue_;
|
||||||
|
void processDeferredEquipmentQueue();
|
||||||
// Cache base player model geometry by (raceId, genderId)
|
// Cache base player model geometry by (raceId, genderId)
|
||||||
std::unordered_map<uint32_t, uint32_t> playerModelCache_; // key=(race<<8)|gender → modelId
|
std::unordered_map<uint32_t, uint32_t> playerModelCache_; // key=(race<<8)|gender → modelId
|
||||||
struct PlayerTextureSlots { int skin = -1; int hair = -1; int underwear = -1; };
|
struct PlayerTextureSlots { int skin = -1; int hair = -1; int underwear = -1; };
|
||||||
|
|
|
||||||
|
|
@ -584,6 +584,8 @@ void Application::update(float deltaTime) {
|
||||||
processPlayerSpawnQueue();
|
processPlayerSpawnQueue();
|
||||||
// Process deferred online creature spawns (throttled)
|
// Process deferred online creature spawns (throttled)
|
||||||
processCreatureSpawnQueue();
|
processCreatureSpawnQueue();
|
||||||
|
// Process deferred equipment compositing (max 1 per frame to avoid stutter)
|
||||||
|
processDeferredEquipmentQueue();
|
||||||
auto cq2 = std::chrono::high_resolution_clock::now();
|
auto cq2 = std::chrono::high_resolution_clock::now();
|
||||||
creatureQTime += std::chrono::duration<float, std::milli>(cq2 - cq1).count();
|
creatureQTime += std::chrono::duration<float, std::milli>(cq2 - cq1).count();
|
||||||
|
|
||||||
|
|
@ -1209,7 +1211,10 @@ void Application::setupUICallbacks() {
|
||||||
gameHandler->setPlayerEquipmentCallback([this](uint64_t guid,
|
gameHandler->setPlayerEquipmentCallback([this](uint64_t guid,
|
||||||
const std::array<uint32_t, 19>& displayInfoIds,
|
const std::array<uint32_t, 19>& displayInfoIds,
|
||||||
const std::array<uint8_t, 19>& inventoryTypes) {
|
const std::array<uint8_t, 19>& inventoryTypes) {
|
||||||
setOnlinePlayerEquipment(guid, displayInfoIds, inventoryTypes);
|
// Queue equipment compositing instead of doing it immediately —
|
||||||
|
// compositeWithRegions is expensive (file I/O + CPU blit + GPU upload)
|
||||||
|
// and causes frame stutters if multiple players update at once.
|
||||||
|
deferredEquipmentQueue_.push_back({guid, {displayInfoIds, inventoryTypes}});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Creature despawn callback (online mode) - remove creature models
|
// Creature despawn callback (online mode) - remove creature models
|
||||||
|
|
@ -4496,13 +4501,21 @@ void Application::processPlayerSpawnQueue() {
|
||||||
// Apply any equipment updates that arrived before the player was spawned.
|
// Apply any equipment updates that arrived before the player was spawned.
|
||||||
auto pit = pendingOnlinePlayerEquipment_.find(s.guid);
|
auto pit = pendingOnlinePlayerEquipment_.find(s.guid);
|
||||||
if (pit != pendingOnlinePlayerEquipment_.end()) {
|
if (pit != pendingOnlinePlayerEquipment_.end()) {
|
||||||
setOnlinePlayerEquipment(s.guid, pit->second.first, pit->second.second);
|
deferredEquipmentQueue_.push_back({s.guid, pit->second});
|
||||||
pendingOnlinePlayerEquipment_.erase(pit);
|
pendingOnlinePlayerEquipment_.erase(pit);
|
||||||
}
|
}
|
||||||
processed++;
|
processed++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::processDeferredEquipmentQueue() {
|
||||||
|
if (deferredEquipmentQueue_.empty()) return;
|
||||||
|
// Process at most 1 per frame — compositeWithRegions is expensive
|
||||||
|
auto [guid, equipData] = deferredEquipmentQueue_.front();
|
||||||
|
deferredEquipmentQueue_.erase(deferredEquipmentQueue_.begin());
|
||||||
|
setOnlinePlayerEquipment(guid, equipData.first, equipData.second);
|
||||||
|
}
|
||||||
|
|
||||||
void Application::processGameObjectSpawnQueue() {
|
void Application::processGameObjectSpawnQueue() {
|
||||||
if (pendingGameObjectSpawns_.empty()) return;
|
if (pendingGameObjectSpawns_.empty()) return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -314,8 +314,8 @@ void GameHandler::update(float deltaTime) {
|
||||||
if (guid != 0 && guid != playerGuid && entityManager.hasEntity(guid)) {
|
if (guid != 0 && guid != playerGuid && entityManager.hasEntity(guid)) {
|
||||||
auto pkt = InspectPacket::build(guid);
|
auto pkt = InspectPacket::build(guid);
|
||||||
socket->send(pkt);
|
socket->send(pkt);
|
||||||
inspectRateLimit_ = 0.75f; // keep it gentle
|
inspectRateLimit_ = 2.0f; // throttle to avoid compositing stutter
|
||||||
LOG_INFO("Sent CMSG_INSPECT for player 0x", std::hex, guid, std::dec);
|
LOG_DEBUG("Sent CMSG_INSPECT for player 0x", std::hex, guid, std::dec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4040,7 +4040,7 @@ void GameHandler::handleDestroyObject(network::Packet& packet) {
|
||||||
LOG_INFO("Destroyed entity: 0x", std::hex, data.guid, std::dec,
|
LOG_INFO("Destroyed entity: 0x", std::hex, data.guid, std::dec,
|
||||||
" (", (data.isDeath ? "death" : "despawn"), ")");
|
" (", (data.isDeath ? "death" : "despawn"), ")");
|
||||||
} else {
|
} else {
|
||||||
LOG_WARNING("Destroy object for unknown entity: 0x", std::hex, data.guid, std::dec);
|
LOG_DEBUG("Destroy object for unknown entity: 0x", std::hex, data.guid, std::dec);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up auto-attack and target if destroyed entity was our target
|
// Clean up auto-attack and target if destroyed entity was our target
|
||||||
|
|
@ -6033,7 +6033,7 @@ void GameHandler::updateOtherPlayerVisibleItems(uint64_t guid, const std::map<ui
|
||||||
// Layout not detected yet — queue this player for inspect as fallback.
|
// Layout not detected yet — queue this player for inspect as fallback.
|
||||||
if (socket && state == WorldState::IN_WORLD) {
|
if (socket && state == WorldState::IN_WORLD) {
|
||||||
pendingAutoInspect_.insert(guid);
|
pendingAutoInspect_.insert(guid);
|
||||||
LOG_INFO("Queued player 0x", std::hex, guid, std::dec, " for auto-inspect (layout not detected)");
|
LOG_DEBUG("Queued player 0x", std::hex, guid, std::dec, " for auto-inspect (layout not detected)");
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue