158 lines
5.0 KiB
GDScript
158 lines
5.0 KiB
GDScript
## System Processing Hotpath Breakdown Tests
|
|
## Detailed profiling of where time is spent during system processing
|
|
extends GdUnitTestSuite
|
|
|
|
var runner: GdUnitSceneRunner
|
|
var world: World
|
|
|
|
|
|
func before():
|
|
runner = scene_runner("res://addons/gecs/tests/test_scene.tscn")
|
|
world = runner.get_property("world")
|
|
ECS.world = world
|
|
|
|
|
|
func after_test():
|
|
if world:
|
|
world.purge(false)
|
|
|
|
|
|
## Setup entities with velocity components (like real example)
|
|
func setup_velocity_entities(count: int) -> void:
|
|
for i in count:
|
|
var entity = Entity.new()
|
|
entity.name = "Entity_%d" % i
|
|
entity.add_component(C_Velocity.new(Vector3(randf(), randf(), randf())))
|
|
world.add_entity(entity, null, false)
|
|
|
|
|
|
## Test 1: Pure query execution (no processing)
|
|
func test_query_execution_only(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_velocity_entities(scale)
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var _result = world.query.with_all([C_Velocity]).execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("hotpath_query_execution", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
|
|
## Test 2: Query + component access (no actual work)
|
|
func test_component_access(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_velocity_entities(scale)
|
|
|
|
var entities = world.query.with_all([C_Velocity]).execute()
|
|
var c_velocity_path = C_Velocity.resource_path
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
for entity in entities:
|
|
var _component = entity.components.get(c_velocity_path, null) as C_Velocity
|
|
)
|
|
|
|
PerfHelpers.record_result("hotpath_component_access", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
|
|
## Test 3: Query + component access + data read
|
|
func test_component_data_read(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_velocity_entities(scale)
|
|
|
|
var entities = world.query.with_all([C_Velocity]).execute()
|
|
var c_velocity_path = C_Velocity.resource_path
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
for entity in entities:
|
|
var component = entity.components.get(c_velocity_path, null) as C_Velocity
|
|
if component:
|
|
# Read the velocity data
|
|
var _vel = component.velocity
|
|
)
|
|
|
|
PerfHelpers.record_result("hotpath_data_read", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
|
|
## Test 4: Simulate full system processing loop (manual)
|
|
func test_simulated_system_loop(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_velocity_entities(scale)
|
|
|
|
var c_velocity_path = C_Velocity.resource_path
|
|
var delta = 0.016
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
# Simulate what a system does: query + iterate + component access + work
|
|
var entities = world.query.with_all([C_Velocity]).execute()
|
|
for entity in entities:
|
|
var component = entity.components.get(c_velocity_path, null) as C_Velocity
|
|
if component:
|
|
# Simulate typical work (reading velocity, calculating new position)
|
|
var _new_pos = component.velocity * delta
|
|
)
|
|
|
|
PerfHelpers.record_result("hotpath_simulated_system", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
|
|
## Test 5: Using actual PerformanceTestSystem (available in tests)
|
|
func test_actual_system_processing(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
# Use C_TestA instead since PerformanceTestSystem uses it
|
|
for i in scale:
|
|
var entity = Entity.new()
|
|
entity.name = "Entity_%d" % i
|
|
entity.add_component(C_TestA.new())
|
|
world.add_entity(entity, null, false)
|
|
|
|
var test_system = PerformanceTestSystem.new()
|
|
world.add_system(test_system)
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
world.process(0.016)
|
|
)
|
|
|
|
PerfHelpers.record_result("hotpath_actual_system", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
|
|
## Test 6: Multiple query executions per frame (simulating multiple systems)
|
|
func test_multiple_queries_per_frame(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_velocity_entities(scale)
|
|
|
|
# Add multiple components to entities
|
|
for entity in world.entities:
|
|
entity.add_component(C_TestA.new())
|
|
entity.add_component(C_TestB.new())
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var _r1 = world.query.with_all([C_Velocity]).execute()
|
|
var _r2 = world.query.with_all([C_TestA]).execute()
|
|
var _r3 = world.query.with_all([C_TestB]).execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("hotpath_multiple_queries", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
|
|
## Test 7: Component access patterns - dictionary vs cached path
|
|
func test_component_access_patterns(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_velocity_entities(scale)
|
|
|
|
var entities = world.query.with_all([C_Velocity]).execute()
|
|
|
|
# Test with cached path (current best practice)
|
|
var c_velocity_path = C_Velocity.resource_path
|
|
var time_cached = PerfHelpers.time_it(func():
|
|
for entity in entities:
|
|
var _component = entity.components.get(c_velocity_path, null) as C_Velocity
|
|
)
|
|
|
|
# Test with get_component() helper
|
|
var time_helper = PerfHelpers.time_it(func():
|
|
for entity in entities:
|
|
var _component = entity.get_component(C_Velocity)
|
|
)
|
|
|
|
PerfHelpers.record_result("hotpath_component_access_cached", scale, time_cached)
|
|
PerfHelpers.record_result("hotpath_component_access_helper", scale, time_helper)
|
|
world.purge(false)
|