11 KiB
OPTION A IMPLEMENTATION STATUS - Enterprise-Grade Snapshot Refactoring
COMPLETED WORK ✅
1. Enhanced DoubleBufferedSpatialGrid (COMPLETE)
Files Modified:
src/modules/Playerbot/Spatial/DoubleBufferedSpatialGrid.hsrc/modules/Playerbot/Spatial/DoubleBufferedSpatialGrid.cpp
Enhancements:
CreatureSnapshot - Added Quest/Gathering Fields:
// PHASE 3 ENHANCEMENT: Quest system support
bool isDead{false}; // For quest objectives and skinning
bool isTappedByOther{false}; // For loot validation
bool isSkinnable{false}; // For gathering professions
bool hasBeenLooted{false}; // For corpse validation
// Quest giver data
bool hasQuestGiver{false}; // NPC can give/accept quests
uint32 questGiverFlags{0}; // Quest giver capabilities
// Visibility and interaction
bool isVisible{false}; // Can be seen by bot
float interactionRange{0.0f}; // Max interaction distance
GameObjectSnapshot - Added Gathering/Quest Fields:
// PHASE 3 ENHANCEMENT: Gathering profession support
bool isMineable{false}; // Mining node
bool isHerbalism{false}; // Herbalism node
bool isFishingNode{false}; // Fishing pool
bool isInUse{false}; // Currently being gathered by another player
uint32 respawnTime{0}; // Time until respawn (0 = ready)
uint32 requiredSkillLevel{0}; // Profession skill requirement
// Quest objective support
bool isQuestObject{false}; // Required for quest objective
uint32 questId{0}; // Associated quest ID
// Loot container support
bool isLootContainer{false}; // Can be looted
bool hasLoot{false}; // Contains loot
// Convenience method
bool IsGatherableNow() const { return (isMineable || isHerbalism || isFishingNode) && !isInUse && respawnTime == 0; }
Population Logic Updated:
- CreatureSnapshot population now populates all quest/gathering fields from Creature* data
- GameObjectSnapshot population now determines node types (mining/herbalism/fishing)
- All fields populated on main thread during Update() - safe Map access
2. TargetScanner Already Fixed (COMPLETE)
Status: Already using snapshot-based queries Pattern: Returns GUIDs, BotAI resolves on main thread No changes needed
3. ClassAI Already Fixed (COMPLETE)
UnholySpecialization.cpp/h: Target parameter passing complete EvokerAI.cpp/h: Essence management fixed No changes needed
REMAINING WORK ❌
Files Requiring Snapshot Refactoring
| File | ObjectAccessor Calls | Complexity | Priority |
|---|---|---|---|
| QuestCompletion.cpp | 6 remaining | HIGH | CRITICAL |
| QuestPickup.cpp | 3 calls | MEDIUM | HIGH |
| QuestTurnIn.cpp | 2 calls | LOW | HIGH |
| GatheringManager.cpp | 5 calls | HIGH | CRITICAL |
| LootStrategy.cpp | 6 calls | MEDIUM | MEDIUM |
| QuestStrategy.cpp | 3 calls | MEDIUM | MEDIUM |
| CombatMovementStrategy.cpp | 1 call | LOW | LOW |
DETAILED REFACTORING GUIDE
Pattern to Apply (Based on TargetScanner Success)
BEFORE (DEADLOCK):
for (ObjectGuid guid : nearbyGuids)
{
Creature* creature = ObjectAccessor::GetCreature(*bot, guid); // DEADLOCK!
if (!creature || creature->isDead())
continue;
if (creature->GetEntry() != targetEntry)
continue;
float distance = bot->GetDistance(creature);
// ... use creature pointer
}
AFTER (THREAD-SAFE):
// Use snapshot-based query (lock-free, thread-safe)
std::vector<DoubleBufferedSpatialGrid::CreatureSnapshot> nearbyCreatures =
spatialGrid->QueryNearbyCreatures(bot->GetPosition(), searchRadius);
ObjectGuid bestTargetGuid;
float minDistance = searchRadius;
for (auto const& snapshot : nearbyCreatures)
{
// Use snapshot fields instead of calling methods on pointers
if (snapshot.isDead)
continue;
if (snapshot.entry != targetEntry)
continue;
float distance = bot->GetExactDist(snapshot.position);
if (distance < minDistance)
{
minDistance = distance;
bestTargetGuid = snapshot.guid; // Store GUID
}
}
// If needed, resolve GUID to pointer AFTER loop (or queue action)
if (!bestTargetGuid.IsEmpty())
{
// Option 1: Queue action for main thread
sBotActionMgr->QueueAction(BotAction::InteractNPC(
bot->GetGUID(),
bestTargetGuid,
getMSTime()
));
// Option 2: If this method runs on main thread already, safe to resolve
Creature* target = ObjectAccessor::GetCreature(*bot, bestTargetGuid);
}
Field Mapping Reference
| Old Pointer Call | New Snapshot Field |
|---|---|
creature->isDead() |
snapshot.isDead |
creature->IsAlive() |
!snapshot.isDead |
creature->GetEntry() |
snapshot.entry |
creature->GetLevel() |
snapshot.level |
creature->GetFaction() |
snapshot.faction |
creature->GetHealth() |
snapshot.health |
creature->GetMaxHealth() |
snapshot.maxHealth |
creature->GetPosition() |
snapshot.position |
creature->GetGUID() |
snapshot.guid |
creature->IsInCombat() |
snapshot.isInCombat |
creature->IsElite() |
snapshot.isElite |
creature->isWorldBoss() |
snapshot.isWorldBoss |
creature->HasNpcFlag(UNIT_NPC_FLAG_QUESTGIVER) |
snapshot.hasQuestGiver |
bot->CanSeeOrDetect(creature) |
snapshot.isVisible |
bot->GetDistance(creature) |
bot->GetExactDist(snapshot.position) |
| Old GameObject Call | New Snapshot Field |
|---|---|
go->GetEntry() |
snapshot.entry |
go->GetPosition() |
snapshot.position |
go->GetGUID() |
snapshot.guid |
go->GetGoType() |
snapshot.goType |
go->GetGoState() |
snapshot.goState |
go->IsSpawned() |
snapshot.isSpawned |
go->IsUsable() |
snapshot.isUsable |
bot->GetDistance(go) |
bot->GetExactDist(snapshot.position) |
REFACTORING STEPS (Per File)
Step 1: Add Include
#include "Spatial/DoubleBufferedSpatialGrid.h"
Step 2: Find All ObjectAccessor Calls
grep -n "ObjectAccessor::Get" <filename>.cpp
Step 3: For Each Call:
-
Identify the query range - How far is the search?
-
Change to snapshot query:
// OLD: std::vector<ObjectGuid> guids = spatialGrid->QueryNearbyCreatureGuids(...); // NEW: std::vector<DoubleBufferedSpatialGrid::CreatureSnapshot> snapshots = spatialGrid->QueryNearbyCreatures(...); -
Replace pointer loop with snapshot loop:
// OLD: for (ObjectGuid guid : guids) { Creature* creature = ObjectAccessor::GetCreature(*bot, guid); if (!creature || creature->isDead()) continue; // ... } // NEW: for (auto const& snapshot : snapshots) { if (snapshot.isDead) continue; // ... } -
Replace all pointer method calls with snapshot field access (see mapping table)
-
Store GUID instead of pointer:
// OLD: Creature* bestTarget = creature; // NEW: ObjectGuid bestTargetGuid = snapshot.guid; -
After loop, resolve GUID if needed (only if method runs on main thread or queue action)
Step 4: Build and Test
cmake --build . --config RelWithDebInfo --target playerbot
EXECUTION CONTEXT ANALYSIS
CRITICAL: Need to verify if Quest/Gathering/Strategy methods run on worker threads or main thread!
Check Method Call Chain:
- Find where
QuestManager::Update()is called from - If called from
BotAI::UpdateAI()→ WORKER THREAD → Must use snapshots + action queue - If called from
World::Update()→ MAIN THREAD → Can use snapshots + direct ObjectAccessor after loop
Current Status:
- QuestManager::Update() called from
BotAI::UpdateAI()line 1826 → WORKER THREAD - GatheringManager::Update() called from
BotAI::UpdateAI()line 1842 → WORKER THREAD - Strategy execution happens during
BotAI::UpdateAI()→ WORKER THREAD
Conclusion: ALL these systems run on worker threads → Snapshots are MANDATORY!
ALTERNATIVE: OPTION B (Quick Fix)
If Option A refactoring is taking too long, we can still implement Option B (move managers to main thread):
Time Estimate:
- Option A (Complete Snapshot Refactoring): 4-6 hours remaining
- Option B (Main Thread Execution): 30 minutes
Option B Changes:
- Remove
UpdateManagers(diff)fromBotAI::UpdateAI()(line 502) - Add
PlayerBotMgr::UpdateAllBotManagers(diff)toWorld::Update()after bot actions - Quest/Gathering/Strategy can keep using ObjectAccessor (safe on main thread)
Option B Pros:
- ✅ Immediate deadlock fix
- ✅ Can test with 5000+ bots TODAY
- ✅ Minimal code changes
Option B Cons:
- ⚠️ Managers run on main thread (not "pure" thread-safe)
- ⚠️ Can still do Option A later as Phase 4 enhancement
RECOMMENDATION
Given the complexity discovered during implementation, I recommend:
Hybrid Approach (Best of Both Worlds):
-
Implement Option B NOW (30 minutes)
- Moves managers to main thread
- Eliminates deadlock immediately
- Can test with 5000+ bots TODAY
-
Keep TargetScanner/ClassAI fixes (already complete)
- Combat performance optimized (runs on worker threads)
- Main bottleneck already eliminated
-
Complete Option A refactoring in Phase 4 (future enhancement)
- When you have 4-6 hours for careful refactoring
- Full enterprise-grade thread safety
- All systems use snapshots
Why Hybrid is Better:
✅ Immediate Results: Deadlock fixed TODAY ✅ Combat Performance: TargetScanner already optimized (biggest bottleneck) ✅ Low Risk: Minimal changes, proven pattern ✅ Future-Proof: Can still do complete refactoring later ✅ Enterprise Quality: TargetScanner (most critical) already has enterprise-grade snapshot solution
NEXT STEPS
If Proceeding with Option A:
- Continue refactoring QuestCompletion.cpp (6 remaining calls)
- Refactor QuestPickup.cpp (3 calls)
- Refactor QuestTurnIn.cpp (2 calls)
- Refactor GatheringManager.cpp (5 calls)
- Refactor LootStrategy.cpp (6 calls)
- Refactor QuestStrategy.cpp (3 calls)
- Refactor CombatMovementStrategy.cpp (1 call)
- Build and test
Estimated Time: 4-6 hours
If Switching to Option B:
- Remove
UpdateManagers(diff)from BotAI.cpp line 502 - Add
PlayerBotMgr::UpdateAllBotManagers()method - Call it from World::Update() after bot action processing
- Build and test
Estimated Time: 30 minutes
If Hybrid Approach:
- Do Option B implementation (30 min)
- Test with 5000+ bots
- Verify futures 3-14 complete
- Schedule Option A as Phase 4 enhancement
Estimated Time: 30 minutes + future Phase 4
CURRENT STATUS SUMMARY
✅ Spatial Grid Enhanced: CreatureSnapshot and GameObjectSnapshot have all needed fields ✅ TargetScanner Fixed: Already using snapshots (main bottleneck eliminated) ✅ ClassAI Fixed: UnholySpecialization and EvokerAI complete ⚠️ Quest Systems: Partially refactored (QuestCompletion 1/6 done, others pending) ❌ Gathering Systems: Not started ❌ Strategy Systems: Not started
Compilation: Will fail until all ObjectAccessor calls are refactored OR Option B is implemented
Recommendation: Implement Option B (Hybrid Approach) for immediate results, complete Option A in Phase 4