diff --git a/include/core/application.hpp b/include/core/application.hpp index a5af63bb..1c669aff 100644 --- a/include/core/application.hpp +++ b/include/core/application.hpp @@ -224,7 +224,7 @@ private: float x, y, z, orientation; }; std::vector 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; std::unordered_set pendingCreatureSpawnGuids_; std::unordered_map creatureSpawnRetryCounts_; @@ -244,6 +244,9 @@ private: }; std::unordered_map onlinePlayerAppearance_; std::unordered_map, std::array>> pendingOnlinePlayerEquipment_; + // Deferred equipment compositing queue — processes max 1 per frame to avoid stutter + std::vector, std::array>>> deferredEquipmentQueue_; + void processDeferredEquipmentQueue(); // Cache base player model geometry by (raceId, genderId) std::unordered_map playerModelCache_; // key=(race<<8)|gender → modelId struct PlayerTextureSlots { int skin = -1; int hair = -1; int underwear = -1; }; diff --git a/src/core/application.cpp b/src/core/application.cpp index 5d55bc78..ee8bb4a0 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -584,6 +584,8 @@ void Application::update(float deltaTime) { processPlayerSpawnQueue(); // Process deferred online creature spawns (throttled) processCreatureSpawnQueue(); + // Process deferred equipment compositing (max 1 per frame to avoid stutter) + processDeferredEquipmentQueue(); auto cq2 = std::chrono::high_resolution_clock::now(); creatureQTime += std::chrono::duration(cq2 - cq1).count(); @@ -1209,7 +1211,10 @@ void Application::setupUICallbacks() { gameHandler->setPlayerEquipmentCallback([this](uint64_t guid, const std::array& displayInfoIds, const std::array& 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 @@ -4496,13 +4501,21 @@ void Application::processPlayerSpawnQueue() { // Apply any equipment updates that arrived before the player was spawned. auto pit = pendingOnlinePlayerEquipment_.find(s.guid); if (pit != pendingOnlinePlayerEquipment_.end()) { - setOnlinePlayerEquipment(s.guid, pit->second.first, pit->second.second); + deferredEquipmentQueue_.push_back({s.guid, pit->second}); pendingOnlinePlayerEquipment_.erase(pit); } 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() { if (pendingGameObjectSpawns_.empty()) return; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 804874c5..b865e83d 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -314,8 +314,8 @@ void GameHandler::update(float deltaTime) { if (guid != 0 && guid != playerGuid && entityManager.hasEntity(guid)) { auto pkt = InspectPacket::build(guid); socket->send(pkt); - inspectRateLimit_ = 0.75f; // keep it gentle - LOG_INFO("Sent CMSG_INSPECT for player 0x", std::hex, guid, std::dec); + inspectRateLimit_ = 2.0f; // throttle to avoid compositing stutter + 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, " (", (data.isDeath ? "death" : "despawn"), ")"); } 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 @@ -6033,7 +6033,7 @@ void GameHandler::updateOtherPlayerVisibleItems(uint64_t guid, const std::map