Files
ThordekkCore/PHASE2D_TARGETSCANNER_SUMMARY.md
T
2026-01-20 21:33:16 -03:00

13 KiB
Raw Blame History

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:

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):

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):

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):

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):

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):

// 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:

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:

// 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 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):

ObjectGuid FindNearestHostile(float range);  // Can be 100% snapshot-based

Unit Return Types* (cannot eliminate ObjectAccessor):

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