// Unit test for megaglest-0001: Attack boost unit membership lookup // Defect: std::find on vector inside per-frame loop = O(C * B) // Fix: std::unordered_set for O(1) membership test // // Simulates our attack boost update loop where we check if each candidate // unit is already in our boosted-units list, and check if each boosted unit // is still a valid candidate. #include #include #include #include #include #include // --- DEFECTIVE: O(C * B) linear scan --- struct AttackBoostDefective { std::vector currentAttackBoostUnits; void updateBoost(const std::vector &candidateIds) { std::vector candidateValidIdList; candidateValidIdList.reserve(candidateIds.size()); for (size_t i = 0; i < candidateIds.size(); ++i) { int id = candidateIds[i]; candidateValidIdList.push_back(id); // O(B) linear scan per candidate auto iterFound = std::find(currentAttackBoostUnits.begin(), currentAttackBoostUnits.end(), id); if (iterFound == currentAttackBoostUnits.end()) { // New unit: apply boost currentAttackBoostUnits.push_back(id); } } // Remove units no longer in range: O(B * C) linear scan for (int i = (int)currentAttackBoostUnits.size() - 1; i >= 0; --i) { int findUnitId = currentAttackBoostUnits[i]; auto iterFound = std::find(candidateValidIdList.begin(), candidateValidIdList.end(), findUnitId); if (iterFound == candidateValidIdList.end()) { currentAttackBoostUnits.erase( currentAttackBoostUnits.begin() + i); } } } }; // --- FIXED: O(C + B) with hash sets --- struct AttackBoostFixed { std::vector currentAttackBoostUnits; void updateBoost(const std::vector &candidateIds) { std::unordered_set candidateValidIdSet; candidateValidIdSet.reserve(candidateIds.size()); // Build hash set of currently boosted IDs for O(1) lookup std::unordered_set boostedIdSet( currentAttackBoostUnits.begin(), currentAttackBoostUnits.end()); for (size_t i = 0; i < candidateIds.size(); ++i) { int id = candidateIds[i]; candidateValidIdSet.insert(id); // O(1) hash lookup if (boostedIdSet.count(id) == 0) { currentAttackBoostUnits.push_back(id); boostedIdSet.insert(id); } } // Remove units no longer in range: O(1) hash lookup per boosted unit for (int i = (int)currentAttackBoostUnits.size() - 1; i >= 0; --i) { int findUnitId = currentAttackBoostUnits[i]; if (candidateValidIdSet.count(findUnitId) == 0) { currentAttackBoostUnits.erase( currentAttackBoostUnits.begin() + i); } } } }; // Verify correctness: both produce same result void test_correctness() { printf("test_correctness... "); // Initial boosted set std::vector initialBoosted = {10, 20, 30, 40, 50}; // New candidates: some overlap, some new, some old ones gone std::vector candidates = {20, 30, 60, 70, 80}; AttackBoostDefective defective; defective.currentAttackBoostUnits = initialBoosted; defective.updateBoost(candidates); AttackBoostFixed fixed; fixed.currentAttackBoostUnits = initialBoosted; fixed.updateBoost(candidates); // Sort both for comparison std::vector defResult = defective.currentAttackBoostUnits; std::vector fixResult = fixed.currentAttackBoostUnits; std::sort(defResult.begin(), defResult.end()); std::sort(fixResult.begin(), fixResult.end()); assert(defResult == fixResult); // Should contain: 20, 30, 60, 70, 80 (10, 40, 50 removed; 60, 70, 80 added) std::vector expected = {20, 30, 60, 70, 80}; assert(defResult == expected); printf("PASS\n"); } // Verify empty candidates removes all boosted void test_empty_candidates() { printf("test_empty_candidates... "); std::vector initialBoosted = {1, 2, 3, 4, 5}; std::vector candidates = {}; AttackBoostDefective defective; defective.currentAttackBoostUnits = initialBoosted; defective.updateBoost(candidates); AttackBoostFixed fixed; fixed.currentAttackBoostUnits = initialBoosted; fixed.updateBoost(candidates); assert(defective.currentAttackBoostUnits.empty()); assert(fixed.currentAttackBoostUnits.empty()); printf("PASS\n"); } // Benchmark: measure O(C*B) vs O(C+B) void test_performance() { printf("test_performance... "); const int B = 2000; // boosted units const int C = 2000; // candidates const int ITERS = 20; // Build initial boosted list (1..B) std::vector initialBoosted; for (int i = 1; i <= B; ++i) initialBoosted.push_back(i); // Candidates: half overlap (1..C/2), half new (B+1..B+C/2) std::vector candidates; for (int i = 1; i <= C / 2; ++i) candidates.push_back(i); for (int i = B + 1; i <= B + C / 2; ++i) candidates.push_back(i); // Defective auto t0 = std::chrono::high_resolution_clock::now(); for (int iter = 0; iter < ITERS; ++iter) { AttackBoostDefective d; d.currentAttackBoostUnits = initialBoosted; d.updateBoost(candidates); } auto t1 = std::chrono::high_resolution_clock::now(); // Fixed auto t2 = std::chrono::high_resolution_clock::now(); for (int iter = 0; iter < ITERS; ++iter) { AttackBoostFixed f; f.currentAttackBoostUnits = initialBoosted; f.updateBoost(candidates); } auto t3 = std::chrono::high_resolution_clock::now(); double defective_ms = std::chrono::duration(t1 - t0).count(); double fixed_ms = std::chrono::duration(t3 - t2).count(); double ratio = defective_ms / fixed_ms; printf("defective=%.1fms fixed=%.1fms ratio=%.1fx ", defective_ms, fixed_ms, ratio); assert(ratio > 2.0); printf("PASS\n"); } int main() { printf("=== megaglest-0001: Attack boost unit membership O(C*B) -> O(C+B) ===\n"); test_correctness(); test_empty_candidates(); test_performance(); printf("All tests passed.\n"); return 0; }