diff --git a/cpp/src/math.hpp b/cpp/src/math.hpp index f6c45f7..fea1c92 100644 --- a/cpp/src/math.hpp +++ b/cpp/src/math.hpp @@ -37,6 +37,11 @@ static inline bool equalEpsilon(const T &a, const T &b) { struct Any {}; template class vec { + + // Friend declaration for move constructor from different tag types + template + friend class vec; + public: vec() : m_Array{} {} @@ -143,6 +148,13 @@ public: return c; } + friend vec operator/(const vec &a, const vec& b) { + vec c; + std::ranges::transform(a.m_Array, b.m_Array, + c.m_Array.begin(), std::divides{}); + return c; + } + // // compound-assignment operators // diff --git a/cpp/src/positional_container.hpp b/cpp/src/positional_container.hpp index e8fd57e..51419b1 100644 --- a/cpp/src/positional_container.hpp +++ b/cpp/src/positional_container.hpp @@ -138,11 +138,6 @@ public: const WorldPos corner_1 = center + radius; const WorldPos corner_2 = center - radius; - if (!CheckBounds(corner_1) || !CheckBounds(corner_2)) - { - return; - } - const auto A = GetCoords(corner_1); const auto B = GetCoords(corner_2); @@ -154,12 +149,30 @@ public: size_t y_min = static_cast(std::floor(y_min_f)); size_t y_max = static_cast(std::ceil(y_max_f)); - // TODO this goes through more positions than we need for (size_t x = x_min; x <= x_max; x++) { for (size_t y = y_min; y <= y_max; y++) { + if (!CheckBounds(x, y)) + { + continue; + } +#if 0 + // TODO this is approx 2x faster, but inserts items outside of radius; + // We can use this is a Get(rectangle) function std::ranges::copy(m_Grid[x][y], std::back_inserter(output_vec)); +#else + for (auto item_wptr : m_Grid[x][y]) + { + if (auto shared = item_wptr.lock()) + { + if (center.DistanceTo(shared->GetPosition()) < radius) + { + output_vec.push_back(item_wptr); + } + } + } +#endif } } } @@ -202,14 +215,21 @@ private: coord_type GetCoords(const WorldPos &wp) { - auto coord_float = wp / m_ChunksPerAxis; + auto coord_float = wp / m_GridStep.ChangeTag(); return coord_type{ static_cast(coord_float.x()), static_cast(coord_float.y()) }; } - bool CheckBounds(const WorldPos& pos) + bool CheckBounds(size_t x, size_t y) const + { + bool x_in_bounds = x < m_Grid.size(); + bool y_in_bounds = y < m_Grid.size(); + return x_in_bounds && y_in_bounds; + } + + bool CheckBounds(const WorldPos& pos) const { auto [x,y] = pos; bool x_in_bounds = 0.0f < x && x < m_GridSize.x(); diff --git a/cpp/test/collision_performance.cpp b/cpp/test/collision_performance.cpp index 3db6c73..8a2179d 100644 --- a/cpp/test/collision_performance.cpp +++ b/cpp/test/collision_performance.cpp @@ -2,14 +2,12 @@ #include #include #include - +#include +#include +#include #include "positional_container.hpp" -// TODO: Add necessary includes for collision testing -// #include "collision_shapes.hpp" -// #include "entities.hpp" - /** * @file collision_performance.cpp * @brief Performance tests for collision detection systems @@ -83,48 +81,171 @@ void benchmark_function(const std::string& name, int iterations, Func func) { */ class Dummy { public: - Dummy() : m_Position(0.0f, 0.0f) {} - Dummy(float x, float y) : m_Position(x, y) {} - Dummy(WorldPos pos) : m_Position(pos) {} + Dummy() : m_Position{0.0f, 0.0f}, m_Id(next_id++) {} + Dummy(float x, float y) : m_Position{x, y}, m_Id(next_id++) {} + Dummy(WorldPos pos) : m_Position(pos), m_Id(next_id++) {} WorldPos GetPosition() const { return m_Position; } void SetPosition(WorldPos pos) { m_Position = pos; } + int GetId() const { return m_Id; } private: WorldPos m_Position; + int m_Id; + static int next_id; }; -// Example test function 1 -void test_function_1() { - - PositionalContainer pos_cont{WorldSize{10.0f, 10.0f}, 10}; - SimpleContainer simp_cont; +int Dummy::next_id = 0; - // TODO: Implement actual collision test - volatile int sum = 0; - for (int i = 0; i < 1000; ++i) { - sum += i; - } +/** + * @brief Helper function to generate random float in range [min, max] + */ +float random_float(std::mt19937& gen, float min, float max) { + std::uniform_real_distribution dist(min, max); + return dist(gen); } -// Example test function 2 -void test_function_2() { - // TODO: Implement actual collision test - volatile int product = 1; - for (int i = 1; i < 100; ++i) { - product *= (i % 10 + 1); +/** + * @brief Compare two sets of weak_ptrs by comparing the IDs of the objects they point to + */ +bool compare_results(const std::vector>& a, + const std::vector>& b) { + std::set ids_a, ids_b; + + for (const auto& weak : a) { + if (auto shared = weak.lock()) { + ids_a.insert(shared->GetId()); + } } + + for (const auto& weak : b) { + if (auto shared = weak.lock()) { + ids_b.insert(shared->GetId()); + } + } + + return ids_a == ids_b; } -TEST(CollisionPerformance, CompareAlgorithms) { +TEST(CollisionPerformance, CompareContainers) { std::cout << "\n=== Collision Performance Comparison ===\n" << std::endl; - const int iterations = 10000; + // Configuration + const int NUM_OBJECTS = 1000; + const int NUM_LOOKUPS = 100; + const float WORLD_SIZE = 1000.0f; + const float LOOKUP_RADIUS = 50.0f; + const size_t CHUNKS = 20; - benchmark_function("Algorithm 1 (test_function_1)", iterations, test_function_1); - benchmark_function("Algorithm 2 (test_function_2)", iterations, test_function_2); + // Random number generator + std::random_device rd; + std::mt19937 gen(rd()); + + // Create containers + PositionalContainer pos_cont{WorldSize{WORLD_SIZE, WORLD_SIZE}, CHUNKS}; + SimpleContainer simp_cont; + + // Create and add dummy objects with random positions + std::vector> objects; + objects.reserve(NUM_OBJECTS); + + std::cout << "Creating " << NUM_OBJECTS << " objects with random positions..." << std::endl; + for (int i = 0; i < NUM_OBJECTS; ++i) { + float x = random_float(gen, 10.0f, WORLD_SIZE - 10.0f); + float y = random_float(gen, 10.0f, WORLD_SIZE - 10.0f); + auto obj = std::make_shared(x, y); + objects.push_back(obj); + pos_cont.Add(obj); + simp_cont.Add(obj); + } + std::cout << "Objects created and added to containers." << std::endl; + + // Generate random lookup positions + std::vector lookup_positions; + lookup_positions.reserve(NUM_LOOKUPS); + for (int i = 0; i < NUM_LOOKUPS; ++i) { + float x = random_float(gen, 0.0f, WORLD_SIZE); + float y = random_float(gen, 0.0f, WORLD_SIZE); + lookup_positions.push_back(WorldPos{x, y}); + } + + // Benchmark SimpleContainer + double simple_total_time = 0.0; + std::vector>> simple_results; + simple_results.reserve(NUM_LOOKUPS); + + std::cout << "\nBenchmarking SimpleContainer with " << NUM_LOOKUPS << " lookups..." << std::endl; + for (const auto& pos : lookup_positions) { + auto start = PerformanceTimer::Clock::now(); + auto result = simp_cont.Get(pos, LOOKUP_RADIUS); + auto end = PerformanceTimer::Clock::now(); + + PerformanceTimer::Duration duration = end - start; + simple_total_time += duration.count(); + simple_results.push_back(result); + } + + double simple_avg_time = simple_total_time / NUM_LOOKUPS; + std::cout << std::fixed << std::setprecision(6) + << "[BENCHMARK] SimpleContainer:\n" + << " Total time: " << simple_total_time << " ms\n" + << " Average time per lookup: " << simple_avg_time << " ms\n" + << " Throughput: " << (NUM_LOOKUPS / (simple_total_time / 1000.0)) + << " lookups/sec" << std::endl; + + // Benchmark PositionalContainer + double positional_total_time = 0.0; + std::vector>> positional_results; + positional_results.reserve(NUM_LOOKUPS); + + std::cout << "\nBenchmarking PositionalContainer with " << NUM_LOOKUPS << " lookups..." << std::endl; + for (const auto& pos : lookup_positions) { + auto start = PerformanceTimer::Clock::now(); + auto result = pos_cont.Get(pos, LOOKUP_RADIUS); + auto end = PerformanceTimer::Clock::now(); + + PerformanceTimer::Duration duration = end - start; + positional_total_time += duration.count(); + positional_results.push_back(result); + } + + double positional_avg_time = positional_total_time / NUM_LOOKUPS; + std::cout << std::fixed << std::setprecision(6) + << "[BENCHMARK] PositionalContainer:\n" + << " Total time: " << positional_total_time << " ms\n" + << " Average time per lookup: " << positional_avg_time << " ms\n" + << " Throughput: " << (NUM_LOOKUPS / (positional_total_time / 1000.0)) + << " lookups/sec" << std::endl; + + // Verify results match + std::cout << "\nVerifying results correctness..." << std::endl; + int mismatches = 0; + for (size_t i = 0; i < NUM_LOOKUPS; ++i) { + if (!compare_results(simple_results[i], positional_results[i])) { + mismatches++; + std::cout << "Mismatch at lookup " << i + << " (pos: " << lookup_positions[i] << ")" << std::endl; + } + } + + if (mismatches == 0) { + std::cout << "✓ All " << NUM_LOOKUPS << " lookups produced identical results!" << std::endl; + } else { + std::cout << "✗ Found " << mismatches << " mismatches out of " + << NUM_LOOKUPS << " lookups" << std::endl; + } + + // Performance comparison + std::cout << "\n=== Performance Summary ===\n"; + double speedup = simple_avg_time / positional_avg_time; + std::cout << std::fixed << std::setprecision(2) + << "PositionalContainer is " << speedup << "x " + << (speedup > 1.0 ? "faster" : "slower") + << " than SimpleContainer" << std::endl; std::cout << "\n======================================\n" << std::endl; - SUCCEED(); + // Assertions + EXPECT_EQ(mismatches, 0) << "Results should match between containers"; + EXPECT_GT(speedup, 1.0) << "PositionalContainer should be faster than SimpleContainer"; }