mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-04 04:03:52 +00:00
fix: use expansion context for spline parsing; preload DBC caches at world entry
Spline parsing: remove Classic format fallback from the WotLK parser. The PacketParsers hierarchy already dispatches to expansion-specific parsers (Classic/TBC/WotLK/Turtle), so the WotLK parseMovementBlock should only attempt WotLK spline format. The Classic fallback could false-positive when durationMod bytes resembled a valid point count, corrupting downstream parsing. Preload DBC caches: call loadSpellNameCache() and 5 other lazy DBC caches during handleLoginVerifyWorld() on initial world entry. This moves the ~170ms Spell.csv load from the first SMSG_SPELL_GO handler to the loading screen, eliminating the mid-gameplay stall. WMO portal culling: move per-instance portalVisibleGroups vector and portalVisibleGroupSet to reusable member variables, eliminating heap allocations per WMO instance per frame.
This commit is contained in:
parent
a795239e77
commit
6f2c8962e5
5 changed files with 54 additions and 48 deletions
|
|
@ -3406,6 +3406,7 @@ private:
|
|||
std::vector<TrainerTab> trainerTabs_;
|
||||
void handleTrainerList(network::Packet& packet);
|
||||
void loadSpellNameCache() const;
|
||||
void preloadDBCCaches() const;
|
||||
void categorizeTrainerSpells();
|
||||
|
||||
// Callbacks
|
||||
|
|
|
|||
|
|
@ -737,6 +737,8 @@ private:
|
|||
std::vector<std::future<void>> cullFutures_;
|
||||
std::vector<size_t> visibleInstances_; // reused per frame
|
||||
std::vector<InstanceDrawList> drawLists_; // reused per frame
|
||||
std::vector<uint32_t> portalVisibleGroups_; // reused per frame (portal culling scratch)
|
||||
std::unordered_set<uint32_t> portalVisibleGroupSet_; // reused per frame (portal culling scratch)
|
||||
|
||||
// Collision query profiling (per frame).
|
||||
mutable double queryTimeMs = 0.0;
|
||||
|
|
|
|||
|
|
@ -8739,6 +8739,13 @@ void GameHandler::handleLoginVerifyWorld(network::Packet& packet) {
|
|||
}
|
||||
}
|
||||
|
||||
// Pre-load DBC name caches during world entry so the first packet that
|
||||
// needs spell/title/achievement data doesn't stall mid-gameplay (the
|
||||
// Spell.dbc cache alone is ~170ms on a cold load).
|
||||
if (initialWorldEntry) {
|
||||
preloadDBCCaches();
|
||||
}
|
||||
|
||||
// Fire PLAYER_ENTERING_WORLD — THE most important event for addon initialization.
|
||||
// Fires on initial login, teleports, instance transitions, and zone changes.
|
||||
if (addonEventCallback_) {
|
||||
|
|
@ -21907,6 +21914,22 @@ void GameHandler::closeTrainer() {
|
|||
trainerTabs_.clear();
|
||||
}
|
||||
|
||||
void GameHandler::preloadDBCCaches() const {
|
||||
LOG_INFO("Pre-loading DBC caches during world entry...");
|
||||
auto t0 = std::chrono::steady_clock::now();
|
||||
|
||||
loadSpellNameCache(); // Spell.dbc — largest, ~170ms cold
|
||||
loadTitleNameCache(); // CharTitles.dbc
|
||||
loadFactionNameCache(); // Faction.dbc
|
||||
loadAreaNameCache(); // WorldMapArea.dbc
|
||||
loadMapNameCache(); // Map.dbc
|
||||
loadLfgDungeonDbc(); // LFGDungeons.dbc
|
||||
|
||||
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::steady_clock::now() - t0).count();
|
||||
LOG_INFO("DBC cache pre-load complete in ", elapsed, " ms");
|
||||
}
|
||||
|
||||
void GameHandler::loadSpellNameCache() const {
|
||||
if (spellNameCacheLoaded_) return;
|
||||
spellNameCacheLoaded_ = true;
|
||||
|
|
|
|||
|
|
@ -975,21 +975,16 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock
|
|||
/*float finalAngle =*/ packet.readFloat();
|
||||
}
|
||||
|
||||
// Spline data layout varies by expansion:
|
||||
// Classic/Vanilla: timePassed(4)+duration(4)+splineId(4)+pointCount(4)+points+mode(1)+endPoint(12)
|
||||
// WotLK: timePassed(4)+duration(4)+splineId(4)+durationMod(4)+durationModNext(4)
|
||||
// +[ANIMATION(5)]+[PARABOLIC(8)]+pointCount(4)+points+mode(1)+endPoint(12)
|
||||
// Since the parser has no expansion context, auto-detect by trying Classic first.
|
||||
if (!bytesAvailable(16)) return false; // minimum: 12 common + 4 pointCount
|
||||
// WotLK spline data layout:
|
||||
// timePassed(4)+duration(4)+splineId(4)+durationMod(4)+durationModNext(4)
|
||||
// +[ANIMATION(5)]+verticalAccel(4)+effectStartTime(4)+pointCount(4)+points+mode(1)+endPoint(12)
|
||||
if (!bytesAvailable(12)) return false;
|
||||
/*uint32_t timePassed =*/ packet.readUInt32();
|
||||
/*uint32_t duration =*/ packet.readUInt32();
|
||||
/*uint32_t splineId =*/ packet.readUInt32();
|
||||
const size_t afterSplineId = packet.getReadPos();
|
||||
|
||||
// Helper: parse spline points + splineMode + endPoint.
|
||||
// WotLK uses compressed points by default (first=12 bytes, rest=4 bytes packed).
|
||||
// Classic/Turtle uses all uncompressed (12 bytes each).
|
||||
// The 'compressed' parameter selects which format.
|
||||
auto tryParseSplinePoints = [&](bool compressed, const char* tag) -> bool {
|
||||
if (!bytesAvailable(4)) return false;
|
||||
size_t prePointCount = packet.getReadPos();
|
||||
|
|
@ -1019,44 +1014,29 @@ bool UpdateObjectParser::parseMovementBlock(network::Packet& packet, UpdateBlock
|
|||
return true;
|
||||
};
|
||||
|
||||
// --- Try 1: WotLK format (durationMod+durationModNext+parabolic+compressed points) ---
|
||||
// Try WotLK first since this is a WotLK parser; Classic auto-detect can false-positive
|
||||
// when durationMod bytes happen to look like a valid Classic pointCount.
|
||||
bool splineParsed = false;
|
||||
{
|
||||
bool wotlkOk = bytesAvailable(8); // durationMod + durationModNext
|
||||
if (wotlkOk) {
|
||||
// WotLK format: durationMod+durationModNext+[ANIMATION]+vertAccel+effectStart+points
|
||||
if (!bytesAvailable(8)) return false; // durationMod + durationModNext
|
||||
/*float durationMod =*/ packet.readFloat();
|
||||
/*float durationModNext =*/ packet.readFloat();
|
||||
if (splineFlags & 0x00400000) { // SPLINEFLAG_ANIMATION
|
||||
if (!bytesAvailable(5)) { wotlkOk = false; }
|
||||
else { packet.readUInt8(); packet.readUInt32(); }
|
||||
}
|
||||
if (!bytesAvailable(5)) return false;
|
||||
packet.readUInt8(); packet.readUInt32();
|
||||
}
|
||||
// AzerothCore/ChromieCraft always writes verticalAcceleration(float)
|
||||
// + effectStartTime(uint32) unconditionally — NOT gated by PARABOLIC flag.
|
||||
if (wotlkOk) {
|
||||
if (!bytesAvailable(8)) { wotlkOk = false; }
|
||||
else { /*float vertAccel =*/ packet.readFloat(); /*uint32_t effectStart =*/ packet.readUInt32(); }
|
||||
}
|
||||
if (wotlkOk) {
|
||||
// + effectStartTime(uint32) unconditionally -- NOT gated by PARABOLIC flag.
|
||||
if (!bytesAvailable(8)) return false;
|
||||
/*float vertAccel =*/ packet.readFloat();
|
||||
/*uint32_t effectStart =*/ packet.readUInt32();
|
||||
|
||||
// WotLK: compressed unless CYCLIC(0x80000) or ENTER_CYCLE(0x2000) set
|
||||
bool useCompressed = (splineFlags & (0x00080000 | 0x00002000)) == 0;
|
||||
splineParsed = tryParseSplinePoints(useCompressed, "wotlk-compressed");
|
||||
bool splineParsed = tryParseSplinePoints(useCompressed, "wotlk-compressed");
|
||||
// Fallback: try uncompressed WotLK if compressed didn't work
|
||||
if (!splineParsed) {
|
||||
splineParsed = tryParseSplinePoints(false, "wotlk-uncompressed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Try 2: Classic format (uncompressed points immediately after splineId) ---
|
||||
if (!splineParsed) {
|
||||
packet.setReadPos(afterSplineId);
|
||||
splineParsed = tryParseSplinePoints(false, "classic");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (updateFlags & UPDATEFLAG_POSITION) {
|
||||
// Transport position update (UPDATEFLAG_POSITION = 0x0100)
|
||||
if (rem() < 1) return false;
|
||||
|
|
|
|||
|
|
@ -1395,21 +1395,21 @@ void WMORenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
result.portalCulled = 0;
|
||||
result.distanceCulled = 0;
|
||||
|
||||
// Portal-based visibility — use a flat sorted vector instead of unordered_set
|
||||
std::vector<uint32_t> portalVisibleGroups;
|
||||
// Portal-based visibility — reuse member scratch buffers (avoid per-frame alloc)
|
||||
portalVisibleGroups_.clear();
|
||||
bool usePortalCulling = doPortalCull && !model.portals.empty() && !model.portalRefs.empty();
|
||||
if (usePortalCulling) {
|
||||
std::unordered_set<uint32_t> pvgSet;
|
||||
portalVisibleGroupSet_.clear();
|
||||
glm::vec4 localCamPos = instance.invModelMatrix * glm::vec4(portalViewerPos, 1.0f);
|
||||
getVisibleGroupsViaPortals(model, glm::vec3(localCamPos), frustum,
|
||||
instance.modelMatrix, pvgSet);
|
||||
portalVisibleGroups.assign(pvgSet.begin(), pvgSet.end());
|
||||
std::sort(portalVisibleGroups.begin(), portalVisibleGroups.end());
|
||||
instance.modelMatrix, portalVisibleGroupSet_);
|
||||
portalVisibleGroups_.assign(portalVisibleGroupSet_.begin(), portalVisibleGroupSet_.end());
|
||||
std::sort(portalVisibleGroups_.begin(), portalVisibleGroups_.end());
|
||||
}
|
||||
|
||||
for (size_t gi = 0; gi < model.groups.size(); ++gi) {
|
||||
if (usePortalCulling &&
|
||||
!std::binary_search(portalVisibleGroups.begin(), portalVisibleGroups.end(),
|
||||
!std::binary_search(portalVisibleGroups_.begin(), portalVisibleGroups_.end(),
|
||||
static_cast<uint32_t>(gi))) {
|
||||
result.portalCulled++;
|
||||
continue;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue