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

16 KiB

Phase 2.5: Update IdleStrategy - Observer Pattern

Duration: 1 week (2025-02-17 to 2025-02-24) Status: ⏳ PENDING Owner: Development Team


Objectives

Transform IdleStrategy from heavyweight delegator to lightweight observer:

  1. Remove all Automation::instance() calls
  2. Replace with fast manager state queries
  3. Eliminate throttling timers (no longer needed)
  4. Achieve <0.1ms per update (from 100ms+)
  5. Implement true observer pattern

Background

Current Problem: Strategy Does Heavyweight Work

IdleStrategy.cpp (Current):

void IdleStrategy::UpdateBehavior(BotAI* ai, uint32 diff)
{
    Player* bot = ai->GetBot();
    uint32 currentTime = getMSTime();

    // ❌ Calls expensive singleton operations
    if (currentTime - _lastQuestUpdate > 2000)
    {
        QuestAutomation::instance()->UpdateBotAutomation(bot, diff);  // 50-100ms!
        _lastQuestUpdate = currentTime;
    }

    if (currentTime - _lastGatheringUpdate > 1000)
    {
        GatheringAutomation::instance()->Update(bot, diff);  // 50-100ms!
        _lastGatheringUpdate = currentTime;
    }

    // ... etc for Trade, Auction
}

Problems:

  1. Performance: Even throttled, takes 0-144ms per update
  2. Architecture: Strategy shouldn't do heavyweight work
  3. Complexity: Strategy manages timers for external systems
  4. Redundancy: Managers already have their own throttling in Phase 2.4
  5. Scalability: Doesn't scale to 5000+ bots

Solution: Lightweight Observer Pattern

IdleStrategy.cpp (New):

void IdleStrategy::UpdateBehavior(BotAI* ai, uint32 diff)
{
    // ✅ Fast state queries only (atomic reads, <0.001ms each)
    QuestManager* questMgr = ai->GetQuestManager();
    GatheringManager* gatherMgr = ai->GetGatheringManager();
    TradeManager* tradeMgr = ai->GetTradeManager();

    // Priority 1: Quest behavior (observe state only)
    if (questMgr && questMgr->IsQuestingActive())
    {
        // Quest movement/behavior handled by managers
        // Strategy just observes and coordinates
        return;  // Questing has priority
    }

    // Priority 2: Gathering behavior
    if (gatherMgr && gatherMgr->HasNearbyResources())
    {
        // Gathering handled by manager
        return;
    }

    // Priority 3: Trading behavior
    if (tradeMgr && tradeMgr->NeedsRepair())
    {
        // Trading handled by manager
        return;
    }

    // Priority 4: Fallback - Simple wander
    DoWander(ai, diff);
}

Benefits:

  • ✅ <0.1ms per update (vs 100ms+)
  • ✅ No throttling needed (queries are instant)
  • ✅ True observer pattern (strategies observe, managers work)
  • ✅ Clean separation of concerns
  • ✅ Scalable to 5000+ bots

Technical Requirements

Performance Constraints

  • IdleStrategy::UpdateBehavior(): <0.1ms per call
  • Manager state query: <0.001ms per call (atomic read)
  • Total idle strategy overhead: <0.1ms per bot per frame
  • Must call every frame (no throttling)

Observer Pattern Principles

  1. Strategies Observe: Read state from managers (fast queries)
  2. Managers Work: Do heavyweight operations (throttled)
  3. No Cross-Calling: Strategies never call manager Update() methods
  4. Atomic State: All state queries are atomic (thread-safe, lock-free)

Integration Points

  • Requires all 4 managers from Phase 2.4 (Quest, Trade, Gathering, Auction)
  • Managers already updating via BotAI::UpdateManagers()
  • IdleStrategy just observes results

Deliverables

1. Refactored IdleStrategy.h

Location: src/modules/Playerbot/AI/Strategy/IdleStrategy.h

Before (with throttling timers):

class IdleStrategy : public Strategy
{
private:
    uint32 _lastWanderTime = 0;
    uint32 _wanderInterval = 30000;

    // ❌ Throttling timers - not needed anymore
    uint32 _lastQuestUpdate = 0;
    uint32 _questUpdateInterval = 2000;
    uint32 _lastGatheringUpdate = 0;
    uint32 _gatheringUpdateInterval = 1000;
    uint32 _lastTradeUpdate = 0;
    uint32 _tradeUpdateInterval = 5000;
    uint32 _lastAuctionUpdate = 0;
    uint32 _auctionUpdateInterval = 10000;
};

After (clean, no timers):

class IdleStrategy : public Strategy
{
public:
    IdleStrategy();
    ~IdleStrategy() override = default;

    void InitializeActions() override;
    void InitializeTriggers() override;
    void InitializeValues() override;

    void OnActivate(BotAI* ai) override;
    void OnDeactivate(BotAI* ai) override;
    bool IsActive(BotAI* ai) const override;

    void UpdateBehavior(BotAI* ai, uint32 diff) override;

private:
    // Wander behavior
    void DoWander(BotAI* ai, uint32 diff);

    // State
    uint32 _lastWanderTime = 0;
    uint32 _wanderInterval = 30000;
};

2. Refactored IdleStrategy.cpp

Location: src/modules/Playerbot/AI/Strategy/IdleStrategy.cpp

Complete New Implementation:

#include "IdleStrategy.h"
#include "BotAI.h"
#include "Player.h"
#include "Log.h"
#include "Game/QuestManager.h"
#include "Social/TradeManager.h"
#include "Professions/GatheringManager.h"
#include "Economy/AuctionManager.h"

namespace Playerbot
{

IdleStrategy::IdleStrategy() : Strategy("idle")
{
    SetPriority(50); // Lower than group strategies
}

void IdleStrategy::InitializeActions()
{
    // TODO: Add idle actions if needed
}

void IdleStrategy::InitializeTriggers()
{
    // TODO: Add idle triggers if needed
}

void IdleStrategy::InitializeValues()
{
    // TODO: Add idle values if needed
}

void IdleStrategy::OnActivate(BotAI* ai)
{
    if (!ai || !ai->GetBot())
        return;

    TC_LOG_INFO("module.playerbot", "Idle strategy activated for bot {}", ai->GetBot()->GetName());
    SetActive(true);
}

void IdleStrategy::OnDeactivate(BotAI* ai)
{
    if (!ai || !ai->GetBot())
        return;

    TC_LOG_INFO("module.playerbot", "Idle strategy deactivated for bot {}", ai->GetBot()->GetName());
    SetActive(false);
}

bool IdleStrategy::IsActive(BotAI* ai) const
{
    if (!ai || !ai->GetBot())
        return false;

    // Active when not in a group
    return _active && !ai->GetBot()->GetGroup();
}

void IdleStrategy::UpdateBehavior(BotAI* ai, uint32 diff)
{
    if (!ai || !ai->GetBot())
        return;

    // OBSERVER PATTERN: Query manager state (fast atomic reads)
    // Managers already updating via BotAI::UpdateManagers()
    // Strategy just observes and coordinates

    // Get manager references
    QuestManager* questMgr = ai->GetQuestManager();
    GatheringManager* gatherMgr = ai->GetGatheringManager();
    TradeManager* tradeMgr = ai->GetTradeManager();
    AuctionManager* auctionMgr = ai->GetAuctionManager();

    // Priority 1: Active questing (highest priority)
    if (questMgr && questMgr->IsQuestingActive())
    {
        // Quest manager is handling quest logic
        // Bot is moving to objectives, killing mobs, etc.
        // Strategy just needs to observe - don't interfere
        static uint32 questCounter = 0;
        if (++questCounter % 100 == 0)
        {
            TC_LOG_DEBUG("module.playerbot", "🎯 IdleStrategy: Bot {} actively questing",
                        ai->GetBot()->GetName());
        }
        return;
    }

    // Priority 2: Gathering resources (if available)
    if (gatherMgr && gatherMgr->HasNearbyResources())
    {
        // Gathering manager is handling resource collection
        // Strategy just observes
        static uint32 gatherCounter = 0;
        if (++gatherCounter % 100 == 0)
        {
            TC_LOG_DEBUG("module.playerbot", "⛏️ IdleStrategy: Bot {} gathering resources",
                        ai->GetBot()->GetName());
        }
        return;
    }

    // Priority 3: Trading/repair (if needed)
    if (tradeMgr && tradeMgr->NeedsRepair())
    {
        // Trade manager is handling vendor interaction
        // Strategy just observes
        static uint32 tradeCounter = 0;
        if (++tradeCounter % 100 == 0)
        {
            TC_LOG_DEBUG("module.playerbot", "🔧 IdleStrategy: Bot {} needs repair",
                        ai->GetBot()->GetName());
        }
        return;
    }

    // Priority 4: Auction house (if near and has items to sell)
    if (auctionMgr && auctionMgr->HasActiveAuctions())
    {
        // Auction manager is handling AH interaction
        static uint32 auctionCounter = 0;
        if (++auctionCounter % 100 == 0)
        {
            TC_LOG_DEBUG("module.playerbot", "💰 IdleStrategy: Bot {} using auction house",
                        ai->GetBot()->GetName());
        }
        return;
    }

    // Priority 5: Fallback - Simple wandering behavior
    DoWander(ai, diff);
}

void IdleStrategy::DoWander(BotAI* ai, uint32 diff)
{
    if (!ai || !ai->GetBot())
        return;

    uint32 currentTime = getMSTime();
    if (currentTime - _lastWanderTime < _wanderInterval)
        return;

    _lastWanderTime = currentTime;

    // TODO: Implement proper wandering with pathfinding
    // For now, just log that bot is idle
    static uint32 idleCounter = 0;
    if (++idleCounter % 100 == 0)
    {
        TC_LOG_DEBUG("module.playerbot", "💤 IdleStrategy: Bot {} is wandering (truly idle)",
                    ai->GetBot()->GetName());
    }
}

} // namespace Playerbot

3. Remove Automation Includes

Location: src/modules/Playerbot/AI/Strategy/IdleStrategy.cpp

Before:

#include "Quest/QuestAutomation.h"
#include "Professions/GatheringAutomation.h"
#include "Social/TradeAutomation.h"
#include "Social/AuctionAutomation.h"

After:

#include "Game/QuestManager.h"
#include "Social/TradeManager.h"
#include "Professions/GatheringManager.h"
#include "Economy/AuctionManager.h"

4. Performance Tests

Location: tests/performance/IdleStrategyPerformanceTest.cpp

Test Benchmark:

TEST(IdleStrategyPerformance, UpdateBehaviorUnder100Microseconds)
{
    // Setup: 100 bots with idle strategy
    std::vector<Bot*> bots;
    for (int i = 0; i < 100; ++i)
    {
        Bot* bot = CreateTestBot(CLASS_WARRIOR, 30);
        bot->GetAI()->ActivateStrategy("idle");
        bots.push_back(bot);
    }

    // Benchmark: 1000 update cycles
    auto start = std::chrono::high_resolution_clock::now();

    for (int cycle = 0; cycle < 1000; ++cycle)
    {
        for (Bot* bot : bots)
        {
            bot->GetAI()->GetStrategy("idle")->UpdateBehavior(bot->GetAI(), 100);
        }
    }

    auto end = std::chrono::high_resolution_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);

    // Calculate average per-bot update time
    double avgPerUpdate = duration.count() / (100.0 * 1000.0);

    TC_LOG_INFO("test", "IdleStrategy avg update time: {} microseconds", avgPerUpdate);

    // Assert: Must be under 100 microseconds (0.1ms)
    ASSERT_LT(avgPerUpdate, 100.0) << "IdleStrategy update took " << avgPerUpdate << "us, exceeds 100us limit";
}

5. Integration Tests

Location: tests/integration/IdleStrategyIntegrationTest.cpp

Test Scenarios:

TEST(IdleStrategyIntegration, PrioritizesQuesting)
{
    // Setup: Bot with active quest
    Bot* bot = CreateTestBot(CLASS_WARRIOR, 20);
    bot->GetAI()->GetQuestManager()->AddQuest(12345);
    bot->GetAI()->ActivateStrategy("idle");

    // Execute 100 updates
    for (int i = 0; i < 100; ++i)
        bot->GetAI()->UpdateAI(100);

    // Verify: Bot is following quest logic
    ASSERT_TRUE(bot->GetAI()->GetQuestManager()->IsQuestingActive());
}

TEST(IdleStrategyIntegration, FallsBackToWander)
{
    // Setup: Bot with no active tasks
    Bot* bot = CreateTestBot(CLASS_WARRIOR, 20);
    bot->GetAI()->ActivateStrategy("idle");

    // Verify: No active systems
    ASSERT_FALSE(bot->GetAI()->GetQuestManager()->IsQuestingActive());
    ASSERT_FALSE(bot->GetAI()->GetGatheringManager()->HasNearbyResources());

    // Execute updates
    for (int i = 0; i < 100; ++i)
        bot->GetAI()->UpdateAI(100);

    // Verify: Bot executes wander behavior (logged)
    // (Check logs for wander messages)
}

6. Documentation

Location: docs/OBSERVER_PATTERN_GUIDE.md

Content:

  • Observer pattern explanation
  • Manager/Strategy separation principles
  • State query best practices
  • Performance benefits explanation
  • Migration from delegation to observation
  • Troubleshooting guide

Implementation Steps

Day 1: Remove Automation Calls

  1. Open IdleStrategy.cpp
  2. Delete all Automation::instance() calls
  3. Delete all throttling timer logic
  4. Add manager state queries
  5. Verify compilation

Day 2: Implement Observer Pattern

  1. Rewrite UpdateBehavior() with priority logic
  2. Use manager state queries only
  3. Add debug logging (temporary)
  4. Test with 10 bots
  5. Verify <0.1ms per update

Day 3: Test Priority System

  1. Test quest priority (highest)
  2. Test gathering priority
  3. Test trade priority
  4. Test auction priority
  5. Test wander fallback

Day 4: Performance Testing

  1. Benchmark with 100 bots
  2. Benchmark with 500 bots
  3. Benchmark with 1000 bots
  4. Profile UpdateBehavior()
  5. Verify <0.1ms per update

Day 5: Integration Testing

  1. Test with all managers active
  2. Test priority transitions (quest → wander)
  3. Test with mixed bot activities
  4. Verify no regressions
  5. Test 5000 bots (stress test)

Day 6: Cleanup and Optimization

  1. Remove temporary debug logging
  2. Optimize state query order
  3. Clean up code comments
  4. Final code review
  5. Performance validation

Day 7: Documentation

  1. Write observer pattern guide
  2. Document priority system
  3. Create migration examples
  4. Update architecture docs
  5. Final review

Success Criteria

Performance Requirements

  • ✅ IdleStrategy::UpdateBehavior() <0.1ms per call
  • ✅ Manager state queries <0.001ms each
  • ✅ No throttling needed (called every frame)
  • ✅ 100 bots: No performance degradation
  • ✅ 500 bots: <1% CPU increase for idle strategy
  • ✅ 1000 bots: <2% CPU increase
  • ✅ 5000 bots: <10% CPU increase

Functional Requirements

  • ✅ All Automation::instance() calls removed
  • ✅ All throttling timers removed
  • ✅ Priority system works correctly
  • ✅ Quest priority is highest
  • ✅ Wander is fallback
  • ✅ State queries are atomic
  • ✅ No blocking operations

Architecture Quality

  • ✅ True observer pattern implemented
  • ✅ Clean separation: strategies observe, managers work
  • ✅ No cross-calling between systems
  • ✅ Follows Phase 2.1 architecture design
  • ✅ All managers from Phase 2.4 integrated

Code Quality

  • ✅ Clean, readable code
  • ✅ Full documentation
  • ✅ No compiler warnings
  • ✅ Comprehensive tests
  • ✅ No memory leaks

Dependencies

Requires

  • Phase 2.1 complete (BehaviorManager base class)
  • Phase 2.4 complete (All 4 managers refactored)
  • Manager state query methods implemented
  • BotAI::UpdateManagers() working

Blocks

  • Phase 2.6 (Integration testing needs observer pattern working)
  • Full idle bot functionality
  • Performance scalability to 5000+ bots

Risk Mitigation

Risk: Breaking idle bot behavior

Mitigation:

  • Implement new code alongside old code temporarily
  • Test extensively before removing old code
  • Create rollback plan

Risk: Performance regression

Mitigation:

  • Benchmark before/after
  • Profile every step
  • Test with 5000 bots

Risk: Priority system too complex

Mitigation:

  • Keep priority logic simple (if/return pattern)
  • Document priority order clearly
  • Test each priority level independently

Risk: State queries not thread-safe

Mitigation:

  • Use atomic state flags in managers (from Phase 2.4)
  • No locks needed (single-threaded access)
  • Test with high bot counts

Before/After Comparison

Performance

Metric Before After Improvement
Avg update time 100-144ms <0.1ms 1000x faster
Min update time 0ms (throttled) <0.1ms Consistent
Max update time 600ms <0.1ms 6000x faster
CPU usage (100 bots) 15-20% <1% 95% reduction

Code Complexity

Metric Before After Improvement
LOC in UpdateBehavior() 83 lines 60 lines Simpler
Timer variables 8 timers 1 timer Cleaner
External dependencies 4 singletons 4 managers Better
Blocking calls 4 calls 0 calls No blocking

Next Phase

After completion, proceed to Phase 2.6: Integration Testing


Last Updated: 2025-01-13 Next Review: 2025-02-24