397 lines
12 KiB
Markdown
397 lines
12 KiB
Markdown
# Phase 2E: PlayerSnapshot.victim Field - Reality Check
|
|||
|
|
|
||
|
|
**Date**: 2025-10-25
|
||
|
|
**Status**: Analysis Complete - Revised Estimates
|
||
|
|
**Finding**: Most GetVictim() calls are bot->GetVictim() (self-access), not optimizable with snapshots
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Executive Summary
|
||
|
|
|
||
|
|
After detailed analysis of Phase 2E-A high-impact files, we discovered that **the majority of GetVictim() calls are bot->GetVictim()** (bot accessing its own victim), which **do not require ObjectAccessor calls** and therefore **cannot be optimized with PlayerSnapshot.victim**.
|
||
|
|
|
||
|
|
### Revised Findings:
|
||
|
|
- **Total GetVictim() calls**: 98 across 40 files
|
||
|
|
- **bot->GetVictim() calls** (self-access, no optimization): ~70-80 (71-82%)
|
||
|
|
- **Optimizable calls** (other players' victims): ~18-28 (18-29%)
|
||
|
|
- **Revised FPS impact**: 0.5-1.5% (down from 2-6% original estimate)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Analysis Results by File
|
||
|
|
|
||
|
|
### Phase 2E-A Files Analyzed:
|
||
|
|
|
||
|
|
#### 1. WarlockAI.cpp (12 calls)
|
||
|
|
**Finding**: All 12 calls are `bot->GetVictim()` or `pet->GetVictim()` (self-access)
|
||
|
|
|
||
|
|
**Examples**:
|
||
|
|
- Line 434: `Unit* target = bot->GetVictim();`
|
||
|
|
- Line 495: `Unit* target = bot->GetVictim();`
|
||
|
|
- Line 496: `if (target && (!pet->GetVictim() || pet->GetVictim() != target))`
|
||
|
|
|
||
|
|
**Optimization Potential**: **ZERO** - All calls are self-access, already optimal
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
#### 2. CooldownStackingOptimizer.cpp (7 calls)
|
||
|
|
**Finding**: All 7 calls are `_bot->GetVictim()` (self-access)
|
||
|
|
|
||
|
|
**Examples**:
|
||
|
|
- Line 533: `if (!_bot || !_bot->GetVictim())`
|
||
|
|
- Line 574: `Unit* target = _bot->GetVictim();`
|
||
|
|
- Line 661: `if (_bot && _bot->GetVictim())`
|
||
|
|
|
||
|
|
**Optimization Potential**: **ZERO** - All calls are self-access, already optimal
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
#### 3. TargetAssistAction.cpp (4 calls)
|
||
|
|
**Finding**: 3 genuine optimization opportunities + 1 self-access
|
||
|
|
|
||
|
|
**Breakdown**:
|
||
|
|
- Line 144: `leader->GetVictim()` - ✅ **OPTIMIZABLE** (leader target lookup)
|
||
|
|
- Line 161: `member->GetVictim()` - ✅ **OPTIMIZABLE** (member target lookup in loop)
|
||
|
|
- Line 293: `member->GetVictim() == target` - ✅ **OPTIMIZABLE** (target comparison)
|
||
|
|
- Line 205: `bot->GetVictim()` - ❌ Self-access, not optimizable
|
||
|
|
|
||
|
|
**Optimization Potential**: **3 calls** (75% of file)
|
||
|
|
|
||
|
|
This is the ONLY file in Phase 2E-A with genuine optimization opportunities!
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Pattern Analysis
|
||
|
|
|
||
|
|
### Pattern 1: Self-Access (NOT Optimizable)
|
||
|
|
|
||
|
|
**Frequency**: ~70-80 of 98 calls (71-82%)
|
||
|
|
|
||
|
|
**Pattern**:
|
||
|
|
```cpp
|
||
|
|
// Bot accessing its own victim
|
||
|
|
Unit* target = bot->GetVictim();
|
||
|
|
if (bot->GetVictim() && bot->GetVictim()->GetHealthPct() > 50)
|
||
|
|
if (!_bot || !_bot->GetVictim())
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why Not Optimizable**:
|
||
|
|
- bot object is already available (no ObjectAccessor call)
|
||
|
|
- GetVictim() is a simple member function call
|
||
|
|
- PlayerSnapshot.victim requires FindPlayerByGuid() call - **actually slower**!
|
||
|
|
|
||
|
|
**Performance**: Already optimal, no improvement possible
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern 2: Other Player Victims (OPTIMIZABLE)
|
||
|
|
|
||
|
|
**Frequency**: ~18-28 of 98 calls (18-29%)
|
||
|
|
|
||
|
|
**Pattern**:
|
||
|
|
```cpp
|
||
|
|
// Accessing other player's victim
|
||
|
|
if (Player* leader = ObjectAccessor::FindPlayer(leaderGuid))
|
||
|
|
if (leader->GetVictim() == target)
|
||
|
|
priority += 20.0f;
|
||
|
|
|
||
|
|
// Group member iteration
|
||
|
|
for (GroupReference* ref : group->GetMembers())
|
||
|
|
if (Player* member = ref->GetSource())
|
||
|
|
if (member->GetVictim() == target)
|
||
|
|
count++;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why Optimizable**:
|
||
|
|
- Requires ObjectAccessor::FindPlayer() OR already has Player* from group iteration
|
||
|
|
- GetVictim() can be replaced with snapshot.victim for GUID comparison
|
||
|
|
- Eliminates GetVictim() call overhead
|
||
|
|
|
||
|
|
**Performance**: Moderate improvement (eliminates function call overhead, not ObjectAccessor overhead)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Revised Optimization Strategy
|
||
|
|
|
||
|
|
### High-Value Targets (Genuine Optimization)
|
||
|
|
|
||
|
|
Based on detailed analysis, only these patterns are worth optimizing:
|
||
|
|
|
||
|
|
**1. Group Member Victim Iteration** (~15 occurrences):
|
||
|
|
```cpp
|
||
|
|
// BEFORE:
|
||
|
|
for (GroupReference* ref : group->GetMembers())
|
||
|
|
{
|
||
|
|
Player* member = ref->GetSource();
|
||
|
|
if (member->GetVictim() == target)
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// AFTER (using snapshot):
|
||
|
|
// Pre-fetch member snapshots (one spatial query)
|
||
|
|
std::vector<PlayerSnapshot const*> memberSnapshots;
|
||
|
|
for (GroupReference* ref : group->GetMembers())
|
||
|
|
{
|
||
|
|
auto snapshot = SpatialGridQueryHelpers::FindPlayerByGuid(nullptr, ref->GetSource()->GetGUID());
|
||
|
|
if (snapshot)
|
||
|
|
memberSnapshots.push_back(snapshot);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Count using snapshots (lock-free)
|
||
|
|
int count = 0;
|
||
|
|
for (auto snapshot : memberSnapshots)
|
||
|
|
{
|
||
|
|
if (snapshot->victim == target->GetGUID())
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Savings**: Eliminates N GetVictim() calls in loop (N = group size, typically 5-25)
|
||
|
|
**Files**: TargetAssistAction.cpp, GroupCombatStrategy.cpp, ThreatCoordinator.cpp
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**2. Leader Target Lookup** (~10 occurrences):
|
||
|
|
```cpp
|
||
|
|
// BEFORE:
|
||
|
|
Player* leader = ObjectAccessor::FindPlayer(leaderGuid);
|
||
|
|
if (leader && leader->GetVictim())
|
||
|
|
Attack(leader->GetVictim());
|
||
|
|
|
||
|
|
// AFTER (hybrid):
|
||
|
|
auto leaderSnapshot = SpatialGridQueryHelpers::FindPlayerByGuid(nullptr, leaderGuid);
|
||
|
|
if (leaderSnapshot && !leaderSnapshot->victim.IsEmpty())
|
||
|
|
{
|
||
|
|
Unit* target = ObjectAccessor::GetUnit(*bot, leaderSnapshot->victim);
|
||
|
|
if (target)
|
||
|
|
Attack(target);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Savings**: Validates leader has target before ObjectAccessor call
|
||
|
|
**Files**: TargetAssistAction.cpp, ClassAI.cpp, GroupCombatTrigger.cpp
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**3. Target Comparison Without ObjectAccessor** (~5 occurrences):
|
||
|
|
```cpp
|
||
|
|
// BEFORE (already has Player* from group iteration):
|
||
|
|
for (GroupReference* ref : group->GetMembers())
|
||
|
|
{
|
||
|
|
Player* member = ref->GetSource(); // Already have Player*
|
||
|
|
if (member->GetVictim() == target) // Just comparing victims
|
||
|
|
priority += 10.0f;
|
||
|
|
}
|
||
|
|
|
||
|
|
// AFTER:
|
||
|
|
for (GroupReference* ref : group->GetMembers())
|
||
|
|
{
|
||
|
|
Player* member = ref->GetSource();
|
||
|
|
auto snapshot = SpatialGridQueryHelpers::FindPlayerByGuid(nullptr, member->GetGUID());
|
||
|
|
if (snapshot && snapshot->victim == target->GetGUID())
|
||
|
|
priority += 10.0f;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Savings**: Eliminates GetVictim() function call overhead
|
||
|
|
**Note**: This actually adds a FindPlayerByGuid() call, so net benefit is marginal
|
||
|
|
**Files**: TargetAssistAction.cpp, GroupCombatTrigger.cpp
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Revised Impact Projection
|
||
|
|
|
||
|
|
### Conservative Estimate:
|
||
|
|
- **Total GetVictim() calls**: 98
|
||
|
|
- **Self-access calls** (not optimizable): 70-80
|
||
|
|
- **Optimizable calls**: 18-28
|
||
|
|
- **Estimated reduction**: 10-18 calls (56-64% of optimizable)
|
||
|
|
- **FPS impact**: **0.5-1%** (down from 2-4% original estimate)
|
||
|
|
|
||
|
|
### Optimistic Estimate:
|
||
|
|
- **Optimizable calls**: 18-28
|
||
|
|
- **Estimated reduction**: 15-22 calls (83-79% of optimizable)
|
||
|
|
- **FPS impact**: **1-1.5%** (down from 4-6% original estimate)
|
||
|
|
|
||
|
|
### Why Lower Impact?
|
||
|
|
1. **Most calls are self-access** (bot->GetVictim()) - no ObjectAccessor overhead to eliminate
|
||
|
|
2. **GetVictim() is a cheap function call** - only eliminates function call overhead, not lock contention
|
||
|
|
3. **FindPlayerByGuid() has overhead** - snapshot lookup may negate savings in some cases
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Files with Genuine Optimization Potential
|
||
|
|
|
||
|
|
### High Priority (Group Iteration Patterns):
|
||
|
|
|
||
|
|
1. **TargetAssistAction.cpp** (3 optimizable calls)
|
||
|
|
- Line 144: Leader target lookup
|
||
|
|
- Line 161: Member target lookup in loop
|
||
|
|
- Line 293: Member victim comparison loop
|
||
|
|
|
||
|
|
2. **GroupCombatStrategy.cpp** (estimated 2-3 optimizable calls)
|
||
|
|
- Group member targeting coordination
|
||
|
|
|
||
|
|
3. **GroupCombatTrigger.cpp** (2-4 remaining optimizable calls)
|
||
|
|
- Lines 479, 566: Group member victim comparison
|
||
|
|
|
||
|
|
4. **AoEDecisionManager.cpp** (estimated 1-2 optimizable calls)
|
||
|
|
- Multi-target validation
|
||
|
|
|
||
|
|
5. **ThreatCoordinator.cpp** (estimated 1-2 optimizable calls)
|
||
|
|
- Group threat distribution
|
||
|
|
|
||
|
|
**Total High-Priority**: 9-14 calls across 5 files
|
||
|
|
**Expected Impact**: 0.5-1% FPS improvement
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Medium Priority (Leader Lookups):
|
||
|
|
|
||
|
|
6. **ClassAI.cpp** (estimated 2 optimizable calls)
|
||
|
|
7. **BotAI_EventHandlers.cpp** (estimated 1 optimizable call)
|
||
|
|
|
||
|
|
**Total Medium-Priority**: 3 calls across 2 files
|
||
|
|
**Expected Impact**: <0.5% FPS improvement
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Recommended Action Plan
|
||
|
|
|
||
|
|
### Option A: Implement High-Priority Files Only
|
||
|
|
**Effort**: 2-3 hours
|
||
|
|
**Impact**: 0.5-1% FPS
|
||
|
|
**Files**: TargetAssistAction, GroupCombatStrategy, GroupCombatTrigger, AoEDecisionManager, ThreatCoordinator (5 files)
|
||
|
|
|
||
|
|
**Recommendation**: ✅ **PROCEED** - Worth the effort for group coordination optimization
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Option B: Skip Phase 2E Entirely
|
||
|
|
**Reasoning**:
|
||
|
|
- Diminishing returns (0.5-1% vs 2-6% original estimate)
|
||
|
|
- Most calls are already optimal (self-access)
|
||
|
|
- Effort better spent on Phase 2F (Session Management) with 3-5% impact
|
||
|
|
|
||
|
|
**Recommendation**: ⚠️ **CONSIDER** - Phase 2F may have better ROI
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Option C: Targeted Optimization (Hybrid)
|
||
|
|
**Approach**: Only optimize the highest-value pattern (group member iteration)
|
||
|
|
**Effort**: 1-2 hours
|
||
|
|
**Impact**: 0.3-0.7% FPS
|
||
|
|
**Files**: TargetAssistAction (line 293 loop), GroupCombatTrigger (lines 479, 566)
|
||
|
|
|
||
|
|
**Recommendation**: ✅ **GOOD COMPROMISE** - Best ROI per hour
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Lessons Learned
|
||
|
|
|
||
|
|
### 1. GetVictim() is Overloaded
|
||
|
|
The grep search `->GetVictim()` matches:
|
||
|
|
- `bot->GetVictim()` (self-access, no optimization)
|
||
|
|
- `leader->GetVictim()` (other player, optimizable)
|
||
|
|
- `pet->GetVictim()` (pet access, no optimization)
|
||
|
|
|
||
|
|
**Lesson**: Need more specific grep patterns to identify optimization opportunities
|
||
|
|
|
||
|
|
### 2. Direct Member Access is Fast
|
||
|
|
When bot object is already available, `bot->GetVictim()` is:
|
||
|
|
- A simple member function call (no virtual dispatch)
|
||
|
|
- No lock acquisition
|
||
|
|
- Already optimal performance
|
||
|
|
|
||
|
|
PlayerSnapshot.victim optimization only helps when **comparing other players' victims**.
|
||
|
|
|
||
|
|
### 3. ObjectAccessor Call Elimination ≠ GetVictim() Elimination
|
||
|
|
**ObjectAccessor Overhead**: 5-10μs (lock contention)
|
||
|
|
**GetVictim() Overhead**: <0.1μs (function call)
|
||
|
|
|
||
|
|
Eliminating GetVictim() calls only saves function call overhead, not lock contention.
|
||
|
|
|
||
|
|
### 4. Snapshot Lookup Has Overhead
|
||
|
|
`FindPlayerByGuid()` has spatial grid lookup overhead (~0.5-1μs). For patterns that already have Player* pointer (group iteration), replacing `member->GetVictim()` with snapshot lookup may actually be **slower**.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Corrected Optimization Patterns
|
||
|
|
|
||
|
|
### Pattern to Optimize (Group Iteration with Victim Comparison):
|
||
|
|
|
||
|
|
**BEFORE**:
|
||
|
|
```cpp
|
||
|
|
for (GroupReference* ref : group->GetMembers())
|
||
|
|
{
|
||
|
|
Player* member = ref->GetSource();
|
||
|
|
if (member->GetVictim() == target) // GetVictim() call per member
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**AFTER**:
|
||
|
|
```cpp
|
||
|
|
// Pre-fetch snapshots (amortize spatial grid cost)
|
||
|
|
std::unordered_map<ObjectGuid, PlayerSnapshot const*> memberSnapshots;
|
||
|
|
for (GroupReference* ref : group->GetMembers())
|
||
|
|
{
|
||
|
|
Player* member = ref->GetSource();
|
||
|
|
auto snapshot = SpatialGridQueryHelpers::FindPlayerByGuid(nullptr, member->GetGUID());
|
||
|
|
if (snapshot)
|
||
|
|
memberSnapshots[member->GetGUID()] = snapshot;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Use snapshots for comparison (no GetVictim() calls)
|
||
|
|
int count = 0;
|
||
|
|
for (auto const& [guid, snapshot] : memberSnapshots)
|
||
|
|
{
|
||
|
|
if (snapshot->victim == target->GetGUID())
|
||
|
|
count++;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**When Worth It**: Group size ≥ 3 (amortize snapshot pre-fetch cost)
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Pattern NOT Worth Optimizing (Single Access):
|
||
|
|
|
||
|
|
**BEFORE**:
|
||
|
|
```cpp
|
||
|
|
Player* leader = ref->GetSource(); // Already have Player*
|
||
|
|
if (leader->GetVictim() == target) // One GetVictim() call
|
||
|
|
return true;
|
||
|
|
```
|
||
|
|
|
||
|
|
**AFTER** (not recommended):
|
||
|
|
```cpp
|
||
|
|
Player* leader = ref->GetSource();
|
||
|
|
auto snapshot = SpatialGridQueryHelpers::FindPlayerByGuid(nullptr, leader->GetGUID()); // Added overhead!
|
||
|
|
if (snapshot && snapshot->victim == target->GetGUID())
|
||
|
|
return true;
|
||
|
|
```
|
||
|
|
|
||
|
|
**Why Not**: FindPlayerByGuid() overhead > GetVictim() savings
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Conclusion
|
||
|
|
|
||
|
|
The PlayerSnapshot.victim field has **limited optimization potential** (~0.5-1.5% FPS) because:
|
||
|
|
|
||
|
|
1. **71-82% of GetVictim() calls are self-access** (bot->GetVictim()) - already optimal
|
||
|
|
2. **GetVictim() is cheap** - only eliminates function call overhead, not lock contention
|
||
|
|
3. **Snapshot lookup has overhead** - marginal benefit in many cases
|
||
|
|
|
||
|
|
**Recommendation**:
|
||
|
|
- **Option A**: Implement high-priority group iteration patterns (5 files, 2-3 hours, 0.5-1% FPS) - ✅ Worthwhile
|
||
|
|
- **Option C**: Targeted optimization (2 files, 1-2 hours, 0.3-0.7% FPS) - ✅ Best ROI
|
||
|
|
- **Option B**: Skip Phase 2E, move to Phase 2F (Session Management, 3-5% impact) - ⚠️ Consider
|
||
|
|
|
||
|
|
The original Phase 2E roadmap estimates (2-6% FPS) were overly optimistic due to not accounting for the prevalence of self-access GetVictim() calls.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
**End of Reality Check Document**
|