415 lines
13 KiB
Markdown
415 lines
13 KiB
Markdown
# Phase 2D: TargetScanner.cpp Analysis - Already Fully Optimized ✅
|
||
|
||
**Date**: 2025-10-25
|
||
**Status**: ✅ COMPLETE - Already fully optimized in PHASE 1
|
||
**File**: `src/modules/Playerbot/AI/Combat/TargetScanner.cpp`
|
||
**Build Result**: N/A (no changes required)
|
||
|
||
---
|
||
|
||
## Executive Summary
|
||
|
||
TargetScanner.cpp was found to be **already 100% optimized** in PHASE 1 - the fundamental lock-free spatial grid migration. This file contains **ZERO ObjectAccessor calls** and operates entirely on snapshot-based queries.
|
||
|
||
### Key Results:
|
||
- **ObjectAccessor calls**: 0 → 0 (already eliminated in PHASE 1)
|
||
- **Changes made**: 1 comment label updated (PHASE 1 → PHASE 1 & 2D for consistency)
|
||
- **Optimization level**: 100% snapshot-based validation
|
||
- **Build status**: No changes required
|
||
|
||
---
|
||
|
||
## Analysis Findings
|
||
|
||
### Zero ObjectAccessor Calls Found
|
||
|
||
**Grep Results**:
|
||
```bash
|
||
grep "ObjectAccessor::" TargetScanner.cpp
|
||
```
|
||
|
||
**Output**: Only comments documenting OLD code patterns
|
||
- Line 323: Comment about OLD CODE (removed in PHASE 1)
|
||
- Line 348: Comment about NO ObjectAccessor call
|
||
- Line 376: Comment about deferring ObjectAccessor call to main thread
|
||
|
||
**Actual Code**: Zero ObjectAccessor calls in executable code.
|
||
|
||
---
|
||
|
||
## Architecture Overview
|
||
|
||
TargetScanner.cpp is a **model implementation** of the Phase 1 lock-free architecture:
|
||
|
||
### 1. FindAllHostiles() - Pure Snapshot Query
|
||
|
||
**Implementation** (Lines 268-357):
|
||
```cpp
|
||
std::vector<ObjectGuid> TargetScanner::FindAllHostiles(float range)
|
||
{
|
||
std::vector<ObjectGuid> hostileGuids;
|
||
|
||
// Get spatial grid for this map
|
||
DoubleBufferedSpatialGrid* spatialGrid = sSpatialGridManager.GetGrid(map);
|
||
|
||
// Query nearby creature SNAPSHOTS (lock-free, thread-safe!)
|
||
std::vector<DoubleBufferedSpatialGrid::CreatureSnapshot> nearbyCreatures =
|
||
spatialGrid->QueryNearbyCreatures(m_bot->GetPosition(), range);
|
||
|
||
// Process snapshots - validation done WITHOUT ObjectAccessor/Map calls!
|
||
for (DoubleBufferedSpatialGrid::CreatureSnapshot const& creature : nearbyCreatures)
|
||
{
|
||
if (!IsValidTargetSnapshot(creature))
|
||
continue;
|
||
|
||
// Store GUID - main thread will validate hostility and queue attack action
|
||
// NO ObjectAccessor::GetUnit() call → THREAD-SAFE!
|
||
hostileGuids.push_back(creature.guid);
|
||
}
|
||
|
||
return hostileGuids; // Returns GUIDs only, not Unit* pointers
|
||
}
|
||
```
|
||
|
||
**Key Design**:
|
||
- Returns `std::vector<ObjectGuid>` instead of `std::vector<Unit*>`
|
||
- Main thread resolves GUIDs to Unit* pointers
|
||
- Worker thread NEVER calls ObjectAccessor or accesses Map
|
||
- 100% thread-safe, lock-free operation
|
||
|
||
---
|
||
|
||
### 2. FindNearestHostile() - Snapshot-Based Distance Calculation
|
||
|
||
**Implementation** (Lines 128-181):
|
||
```cpp
|
||
ObjectGuid TargetScanner::FindNearestHostile(float range)
|
||
{
|
||
std::vector<ObjectGuid> hostileGuids = FindAllHostiles(range);
|
||
if (hostileGuids.empty())
|
||
return ObjectGuid::Empty;
|
||
|
||
// Find nearest hostile using snapshot data (NO ObjectAccessor calls!)
|
||
ObjectGuid nearestGuid = ObjectGuid::Empty;
|
||
float nearestDist = range + 1.0f;
|
||
|
||
std::vector<DoubleBufferedSpatialGrid::CreatureSnapshot> nearbyCreatures =
|
||
spatialGrid->QueryNearbyCreatures(m_bot->GetPosition(), range);
|
||
|
||
for (ObjectGuid const& guid : hostileGuids)
|
||
{
|
||
// Find snapshot for this GUID
|
||
auto it = std::find_if(nearbyCreatures.begin(), nearbyCreatures.end(),
|
||
[&guid](DoubleBufferedSpatialGrid::CreatureSnapshot const& c) { return c.guid == guid; });
|
||
|
||
if (it == nearbyCreatures.end())
|
||
continue;
|
||
|
||
// Calculate distance using snapshot data
|
||
float dist = it->position.GetExactDist(m_bot->GetPosition());
|
||
if (dist < nearestDist)
|
||
{
|
||
nearestDist = dist;
|
||
nearestGuid = guid;
|
||
}
|
||
}
|
||
|
||
return nearestGuid; // Returns GUID, not Unit*
|
||
}
|
||
```
|
||
|
||
**Key Design**:
|
||
- Distance calculations use `snapshot->position` field
|
||
- No GetDistance() calls on Unit* objects
|
||
- Entirely lock-free distance computation
|
||
|
||
---
|
||
|
||
### 3. FindBestTarget() - Snapshot-Based Priority Calculation
|
||
|
||
**Implementation** (Lines 183-266):
|
||
```cpp
|
||
ObjectGuid TargetScanner::FindBestTarget(float range)
|
||
{
|
||
std::vector<ObjectGuid> hostileGuids = FindAllHostiles(range);
|
||
|
||
// Build priority list using snapshot data only (NO ObjectAccessor calls!)
|
||
struct PriorityTarget
|
||
{
|
||
ObjectGuid guid;
|
||
float distance;
|
||
uint8 priority;
|
||
};
|
||
|
||
std::vector<PriorityTarget> priorityTargets;
|
||
std::vector<DoubleBufferedSpatialGrid::CreatureSnapshot> nearbyCreatures =
|
||
spatialGrid->QueryNearbyCreatures(m_bot->GetPosition(), range);
|
||
|
||
for (ObjectGuid const& guid : hostileGuids)
|
||
{
|
||
auto it = std::find_if(nearbyCreatures.begin(), nearbyCreatures.end(),
|
||
[&guid](DoubleBufferedSpatialGrid::CreatureSnapshot const& c) { return c.guid == guid; });
|
||
|
||
// Calculate priority using snapshot data
|
||
uint8 priority = PRIORITY_NORMAL;
|
||
|
||
// Prioritize creatures attacking bot or group members
|
||
if (it->victim == m_bot->GetGUID())
|
||
priority = PRIORITY_CRITICAL;
|
||
else if (it->isInCombat)
|
||
priority = PRIORITY_NORMAL;
|
||
|
||
// Prioritize elites and world bosses
|
||
if (it->isWorldBoss)
|
||
priority = PRIORITY_CRITICAL;
|
||
else if (it->isElite)
|
||
priority = std::min<uint8>(priority + 2, PRIORITY_ELITE);
|
||
|
||
PriorityTarget pt;
|
||
pt.guid = guid;
|
||
pt.distance = it->position.GetExactDist(m_bot->GetPosition());
|
||
pt.priority = priority;
|
||
priorityTargets.push_back(pt);
|
||
}
|
||
|
||
std::sort(priorityTargets.begin(), priorityTargets.end());
|
||
return priorityTargets.front().guid; // Returns GUID, not Unit*
|
||
}
|
||
```
|
||
|
||
**Key Design**:
|
||
- Priority calculations use snapshot fields: `victim`, `isInCombat`, `isWorldBoss`, `isElite`
|
||
- Distance calculations use `snapshot->position`
|
||
- No Unit* object access required
|
||
|
||
---
|
||
|
||
### 4. IsValidTargetSnapshot() - Pure Snapshot Validation
|
||
|
||
**Implementation** (Lines 365-394):
|
||
```cpp
|
||
bool TargetScanner::IsValidTargetSnapshot(DoubleBufferedSpatialGrid::CreatureSnapshot const& creature) const
|
||
{
|
||
// Basic validation
|
||
if (!creature.IsValid() || creature.isDead || creature.health == 0)
|
||
return false;
|
||
|
||
// Check if blacklisted (uses thread-safe GUID check)
|
||
if (this->IsBlacklisted(creature.guid))
|
||
return false;
|
||
|
||
// Don't attack creatures already in combat with someone else
|
||
if (creature.isInCombat && creature.victim != m_bot->GetGUID() &&
|
||
!m_bot->GetGroup())
|
||
return false;
|
||
|
||
// Level check - don't attack creatures too high level
|
||
if (creature.level > m_bot->GetLevel() + 10)
|
||
return false;
|
||
|
||
return true;
|
||
}
|
||
```
|
||
|
||
**Key Design**:
|
||
- Validates using ONLY snapshot fields: `IsValid()`, `isDead`, `health`, `isInCombat`, `victim`, `level`
|
||
- No Unit* object access
|
||
- Defers hostility check to main thread (requires Unit* pointer)
|
||
|
||
---
|
||
|
||
## PHASE 1 Documentation Found
|
||
|
||
**Key Comment** (Lines 278-329):
|
||
```cpp
|
||
// PHASE 1 FIX: Use lock-free double-buffered spatial grid instead of Cell::VisitAllObjects
|
||
// Cell::VisitAllObjects caused deadlocks with 100+ bots due to:
|
||
// - Main thread holds grid locks while updating objects
|
||
// - Worker threads acquire grid locks for spatial queries
|
||
// - Lock ordering conflicts → 60-second hang → crash
|
||
//
|
||
// NEW APPROACH:
|
||
// - Background worker thread updates inactive grid buffer
|
||
// - Atomic buffer swap after update complete
|
||
// - Bots query active buffer with ZERO lock contention
|
||
// - Scales to 10,000+ bots with 1-5μs query latency
|
||
```
|
||
|
||
**Additional Comments**:
|
||
- Lines 320-329: Detailed explanation of deadlock fix (return GUIDs, not Unit* pointers)
|
||
- Lines 359-364: Thread-safe snapshot-based validation documentation
|
||
- Lines 396-400: Legacy Unit-based validation kept for compatibility
|
||
|
||
---
|
||
|
||
## Why Zero ObjectAccessor Calls?
|
||
|
||
### Design Philosophy: GUID-Only Returns
|
||
|
||
All public methods return **GUIDs**, not **Unit* pointers**:
|
||
|
||
**Public API**:
|
||
```cpp
|
||
ObjectGuid FindNearestHostile(float range); // Returns GUID
|
||
ObjectGuid FindBestTarget(float range); // Returns GUID
|
||
std::vector<ObjectGuid> FindAllHostiles(float range); // Returns GUIDs
|
||
```
|
||
|
||
**Caller Responsibility**:
|
||
Main thread (BotAI) resolves GUID → Unit* and queues actions:
|
||
```cpp
|
||
// Main thread (BotAI.cpp):
|
||
ObjectGuid targetGuid = targetScanner->FindBestTarget(range);
|
||
if (!targetGuid.IsEmpty())
|
||
{
|
||
Unit* target = ObjectAccessor::GetUnit(*bot, targetGuid); // Main thread only
|
||
if (target && bot->IsValidAttackTarget(target))
|
||
bot->Attack(target, true);
|
||
}
|
||
```
|
||
|
||
**Why This Works**:
|
||
- Worker threads: Lock-free GUID queries (Phase 1 architecture)
|
||
- Main thread: Unit* pointer access (safe, single-threaded)
|
||
- Zero lock contention, zero deadlocks
|
||
|
||
---
|
||
|
||
## Comparison to Other Files
|
||
|
||
### Files with ObjectAccessor Calls (Partially Optimized)
|
||
|
||
| File | ObjectAccessor Calls | Why Not Eliminated |
|
||
|------|---------------------|-------------------|
|
||
| BotThreatManager.cpp | 14 | Returns `std::vector<Unit*>` |
|
||
| TargetSelector.cpp | 4 | Returns `std::vector<Unit*>` or `Unit*` |
|
||
| GroupCombatTrigger.cpp | 3 | Needs GetVictim() return value |
|
||
|
||
### TargetScanner.cpp (100% Optimized)
|
||
|
||
| Function | Return Type | ObjectAccessor Calls |
|
||
|----------|-------------|---------------------|
|
||
| FindNearestHostile() | ObjectGuid | 0 |
|
||
| FindBestTarget() | ObjectGuid | 0 |
|
||
| FindAllHostiles() | std::vector<ObjectGuid> | 0 |
|
||
|
||
**Key Difference**: Returns GUIDs instead of Unit* pointers.
|
||
|
||
---
|
||
|
||
## Changes Made This Session
|
||
|
||
**Single Change**:
|
||
- Updated line 278 comment label: "PHASE 1 FIX" → "PHASE 1 & 2D" for consistency
|
||
|
||
**No Code Changes Required**: File already 100% optimized.
|
||
|
||
---
|
||
|
||
## Performance Analysis
|
||
|
||
### ObjectAccessor Call Frequency
|
||
|
||
**Before PHASE 1**: ~20-30 calls per scan × 5-10 Hz = **100-300 calls/sec** (estimated)
|
||
**After PHASE 1**: 0 calls per scan × 5-10 Hz = **0 calls/sec**
|
||
**Reduction**: 100% (all calls eliminated in PHASE 1)
|
||
|
||
### FPS Impact
|
||
|
||
**PHASE 1 Impact** (already achieved):
|
||
- Eliminated 100-300 ObjectAccessor calls/sec per bot
|
||
- 100-bot scenario: 10,000-30,000 calls/sec eliminated
|
||
- Measured FPS improvement: Significant (PHASE 1 fixed deadlocks)
|
||
|
||
**Phase 2D Impact**: None (file already optimized)
|
||
|
||
---
|
||
|
||
## Roadmap Discrepancy
|
||
|
||
### Expected vs. Actual
|
||
|
||
**Roadmap Estimate** (PHASE2_NEXT_STEPS_ROADMAP.md):
|
||
- Expected reduction: 2-3 calls (66-100%)
|
||
- Expected FPS impact: Part of 8-12% Phase 2D gain
|
||
|
||
**Actual Result**:
|
||
- Actual reduction: 0 calls (already 100% optimized in PHASE 1)
|
||
- Actual FPS impact: None (already achieved in PHASE 1)
|
||
|
||
**Root Cause**: File was fully optimized in PHASE 1 (lock-free spatial grid migration).
|
||
|
||
---
|
||
|
||
## Lessons Learned
|
||
|
||
### 1. PHASE 1 vs. PHASE 2D Distinction
|
||
|
||
**PHASE 1**: Fundamental architecture migration
|
||
- Cell::VisitAllObjects → DoubleBufferedSpatialGrid
|
||
- Lock-heavy grid queries → Lock-free snapshot queries
|
||
- Unit* returns → ObjectGuid returns
|
||
- **Result**: 100% ObjectAccessor elimination in scanner code
|
||
|
||
**PHASE 2D**: Incremental snapshot adoption
|
||
- ObjectAccessor::FindPlayer → SpatialGridQueryHelpers::FindPlayerByGuid
|
||
- Unit* validation → Snapshot validation
|
||
- Hybrid patterns (snapshot first, Unit* when needed)
|
||
- **Result**: 40-65% ObjectAccessor reduction (where return type permits)
|
||
|
||
### 2. Return Type Determines Optimization Ceiling
|
||
|
||
**GUID Return Types** (100% optimizable):
|
||
```cpp
|
||
ObjectGuid FindNearestHostile(float range); // Can be 100% snapshot-based
|
||
```
|
||
|
||
**Unit* Return Types** (cannot eliminate ObjectAccessor):
|
||
```cpp
|
||
Unit* GetNearestEnemy(); // Must call ObjectAccessor to return Unit*
|
||
```
|
||
|
||
**Lesson**: TargetScanner.cpp is fully optimized because it returns GUIDs, not Unit* pointers.
|
||
|
||
### 3. TargetScanner.cpp is the Model Implementation
|
||
|
||
This file demonstrates the **ideal PHASE 1 architecture**:
|
||
- All queries return GUIDs
|
||
- All validation uses snapshots
|
||
- Main thread resolves GUIDs to Unit* pointers
|
||
- Worker threads NEVER call ObjectAccessor
|
||
|
||
**Recommendation**: Use TargetScanner.cpp as reference for future scanner/query implementations.
|
||
|
||
---
|
||
|
||
## Conclusion
|
||
|
||
TargetScanner.cpp is **already 100% optimized** with zero ObjectAccessor calls. The file was fully migrated in PHASE 1 as part of the lock-free spatial grid architecture. No further optimization is possible or required.
|
||
|
||
**Key Takeaway**: PHASE 1 files (returning GUIDs) are fundamentally different from PHASE 2D files (returning Unit*). Check for GUID return types to identify already-optimized files.
|
||
|
||
---
|
||
|
||
## Next Steps
|
||
|
||
Move to the next file in Phase 2D priority:
|
||
|
||
1. ✅ **ThreatCoordinator.cpp** (23 calls) - COMPLETE (65% reduction)
|
||
2. ✅ **BotThreatManager.cpp** (14 calls) - COMPLETE (cleanup only)
|
||
3. ✅ **GroupCombatTrigger.cpp** (5 calls) - COMPLETE (40% reduction)
|
||
4. ✅ **TargetSelector.cpp** (4 calls) - COMPLETE (cleanup only)
|
||
5. ✅ **TargetScanner.cpp** (3 calls) - COMPLETE (already optimized in PHASE 1)
|
||
6. ⏳ **LineOfSightManager.cpp** (3 calls) - NEXT
|
||
7. ⏳ **InterruptAwareness.cpp** (3 calls)
|
||
8. ⏳ **CombatBehaviorIntegration.cpp** (3 calls)
|
||
|
||
---
|
||
|
||
**Status**: ✅ TARGETSCANNER COMPLETE (Already 100% Optimized in PHASE 1)
|
||
**Next**: LineOfSightManager.cpp (3 calls)
|
||
|
||
---
|
||
|
||
**End of TargetScanner.cpp Summary**
|