#include "manager/Mediator.h" #include "system/ParticleSystem.h" #include "system/PhysicsSystem.h" #include "system/RenderSystem.h" #include <chrono> #include <cmath> #include <gtest/gtest.h> #define private public #define protected public #include <crepe/api/Event.h> #include <crepe/api/GameObject.h> #include <crepe/api/ParticleEmitter.h> #include <crepe/api/Rigidbody.h> #include <crepe/api/Script.h> #include <crepe/api/Transform.h> #include <crepe/manager/ComponentManager.h> #include <crepe/manager/EventManager.h> #include <crepe/system/CollisionSystem.h> #include <crepe/system/ScriptSystem.h> #include <crepe/types.h> #include <crepe/util/Log.h> 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<CollisionEvent>( [this](const CollisionEvent & ev) -> bool { return this->oncollision(ev); }); } void update() { // Retrieve component from the same GameObject this script is on } }; class DISABLED_ProfilingTest : 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<std::string, std::chrono::microseconds> 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<Camera>(ivec2{1080, 720}, vec2{2000, 2000}, Camera::Data{ .bg_color = Color::WHITE, .zoom = 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 <typename Func> 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<std::chrono::microseconds>(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(DISABLED_ProfilingTest, 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(DISABLED_ProfilingTest, Profiling_2) { while (this->total_time / this->average < this->duration) { { //define gameobject used for testing GameObject gameobject = mgr.new_object( "gameobject", "", {static_cast<float>(game_object_count * 2), 0}); gameobject.add_component<Rigidbody>(Rigidbody::Data{ .gravity_scale = 0.0, .body_type = Rigidbody::BodyType::STATIC, }); gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1}); gameobject.add_component<BehaviorScript>().set_script<TestScript>(); auto img = Texture("asset/texture/square.png"); Sprite & test_sprite = gameobject.add_component<Sprite>( img, Sprite::Data{ .color = {0, 0, 0, 0}, .flip = {.flip_x = false, .flip_y = false}, .sorting_in_layer = 1, .order_in_layer = 1, .size = {.y = 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(DISABLED_ProfilingTest, Profiling_3) { while (this->total_time / this->average < this->duration) { { //define gameobject used for testing GameObject gameobject = mgr.new_object( "gameobject", "", {static_cast<float>(game_object_count * 2), 0}); gameobject.add_component<Rigidbody>(Rigidbody::Data{ .gravity_scale = 0, .body_type = Rigidbody::BodyType::STATIC, }); gameobject.add_component<BoxCollider>(vec2{0, 0}, vec2{1, 1}); gameobject.add_component<BehaviorScript>().set_script<TestScript>(); auto img = Texture("asset/texture/square.png"); Sprite & test_sprite = gameobject.add_component<Sprite>( img, Sprite::Data{ .color = {0, 0, 0, 0}, .flip = {.flip_x = false, .flip_y = false}, .sorting_in_layer = 1, .order_in_layer = 1, .size = {.y = 500}, }); auto & test = gameobject.add_component<ParticleEmitter>(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); }