#include "manager/Mediator.h" #include "system/ParticleSystem.h" #include "system/PhysicsSystem.h" #include "system/RenderSystem.h" #include #include #include #define private public #define protected public #include #include #include #include #include #include #include #include #include #include #include #include using namespace std; using namespace std::chrono_literals; using namespace crepe; using namespace testing; class TestScript : public Script { bool oncollision(const CollisionEvent & test) { Log::logf("Box {} script on_collision()", test.info.this_collider.game_object_id); return true; } void init() { subscribe( [this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); } void update() { // Retrieve component from the same GameObject this script is on } }; class Profiling : public Test { public: // Config for test // Minimum amount to let test pass const int min_gameobject_count = 100; // Maximum amount to stop test const int max_gameobject_count = 150; // Amount of times a test runs to calculate average const int average = 5; // Maximum duration to stop test const std::chrono::microseconds duration = 16000us; Mediator m; ComponentManager mgr{m}; // Add system used for profling tests CollisionSystem collision_sys{m}; PhysicsSystem physics_sys{m}; ParticleSystem particle_sys{m}; RenderSystem render_sys{m}; ScriptSystem script_sys{m}; // Test data std::map timings; int game_object_count = 0; std::chrono::microseconds total_time = 0us; void SetUp() override { GameObject do_not_use = mgr.new_object("DO_NOT_USE", "", {0, 0}); do_not_use.add_component(Color::WHITE, ivec2{1080, 720}, vec2{2000, 2000}, 1.0f); // initialize systems here: //calls init script_sys.update(); //creates window render_sys.update(); } // Helper function to time an update call and store its duration template std::chrono::microseconds time_function(const std::string & name, Func && func) { auto start = std::chrono::steady_clock::now(); func(); auto end = std::chrono::steady_clock::now(); std::chrono::microseconds duration = std::chrono::duration_cast(end - start); timings[name] += duration; return duration; } // Run and profile all systems, return the total time in milliseconds std::chrono::microseconds run_all_systems() { std::chrono::microseconds total_microseconds = 0us; total_microseconds += time_function("PhysicsSystem", [&]() { physics_sys.update(); }); total_microseconds += time_function("CollisionSystem", [&]() { collision_sys.update(); }); total_microseconds += time_function("ParticleSystem", [&]() { particle_sys.update(); }); total_microseconds += time_function("RenderSystem", [&]() { render_sys.update(); }); return total_microseconds; } // Print timings of all functions void log_timings() const { std::string result = "\nFunction timings:\n"; for (const auto & [name, duration] : timings) { result += name + " took " + std::to_string(duration.count() / 1000.0 / average) + " ms (" + std::to_string(duration.count() / average) + " µs).\n"; } result += "Total time: " + std::to_string(this->total_time.count() / 1000.0 / average) + " ms (" + std::to_string(this->total_time.count() / average) + " µs)\n"; result += "Amount of gameobjects: " + std::to_string(game_object_count) + "\n"; GTEST_LOG_(INFO) << result; } void clear_timings() { for (auto & [key, value] : timings) { value = std::chrono::microseconds(0); } } }; TEST_F(Profiling, Profiling_1) { while (this->total_time / this->average < this->duration) { { //define gameobject used for testing GameObject gameobject = mgr.new_object("gameobject", "", {0, 0}); } this->game_object_count++; this->total_time = 0us; clear_timings(); for (int amount = 0; amount < this->average; amount++) { this->total_time += run_all_systems(); } if (this->game_object_count >= this->max_gameobject_count) break; } log_timings(); EXPECT_GE(this->game_object_count, this->min_gameobject_count); } TEST_F(Profiling, Profiling_2) { while (this->total_time / this->average < this->duration) { { //define gameobject used for testing GameObject gameobject = mgr.new_object( "gameobject", "", {static_cast(game_object_count * 2), 0}); gameobject.add_component(Rigidbody::Data{ .gravity_scale = 0.0, .body_type = Rigidbody::BodyType::STATIC, }); gameobject.add_component(vec2{0, 0}, vec2{1, 1}); gameobject.add_component().set_script(); Color color(0, 0, 0, 0); auto img = Texture("asset/texture/green_square.png"); Sprite & test_sprite = gameobject.add_component( img, color, Sprite::FlipSettings{false, false}, 1, 1, 500); } this->game_object_count++; this->total_time = 0us; clear_timings(); for (int amount = 0; amount < this->average; amount++) { this->total_time += run_all_systems(); } if (this->game_object_count >= this->max_gameobject_count) break; } log_timings(); EXPECT_GE(this->game_object_count, this->min_gameobject_count); } TEST_F(Profiling, Profiling_3) { while (this->total_time / this->average < this->duration) { { //define gameobject used for testing GameObject gameobject = mgr.new_object( "gameobject", "", {static_cast(game_object_count * 2), 0}); gameobject.add_component(Rigidbody::Data{ .gravity_scale = 0, .body_type = Rigidbody::BodyType::STATIC, }); gameobject.add_component(vec2{0, 0}, vec2{1, 1}); gameobject.add_component().set_script(); Color color(0, 0, 0, 0); auto img = Texture("asset/texture/green_square.png"); Sprite & test_sprite = gameobject.add_component( img, color, Sprite::FlipSettings{false, false}, 1, 1, 500); auto & test = gameobject.add_component(ParticleEmitter::Data{ .max_particles = 10, .emission_rate = 100, .end_lifespan = 100000, .boundary{ .width = 1000, .height = 1000, .offset = vec2{0, 0}, .reset_on_exit = false, }, .sprite = test_sprite, }); } render_sys.update(); this->game_object_count++; this->total_time = 0us; clear_timings(); for (int amount = 0; amount < this->average; amount++) { this->total_time += run_all_systems(); } if (this->game_object_count >= this->max_gameobject_count) break; } log_timings(); EXPECT_GE(this->game_object_count, this->min_gameobject_count); }