226 lines
7.1 KiB
GDScript
226 lines
7.1 KiB
GDScript
## Query Performance Tests
|
|
## Tests query building and execution performance
|
|
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 diverse entities with various component combinations
|
|
func setup_diverse_entities(count: int) -> void:
|
|
for i in count:
|
|
var entity = Entity.new()
|
|
entity.name = "QueryEntity_%d" % i
|
|
|
|
# Create diverse component combinations
|
|
if i % 2 == 0:
|
|
entity.add_component(C_TestA.new())
|
|
if i % 3 == 0:
|
|
entity.add_component(C_TestB.new())
|
|
if i % 5 == 0:
|
|
entity.add_component(C_TestC.new())
|
|
if i % 7 == 0:
|
|
entity.add_component(C_TestD.new())
|
|
|
|
world.add_entity(entity, null, false)
|
|
|
|
|
|
## Test simple query with_all performance
|
|
func test_query_with_all(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_diverse_entities(scale)
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var entities = world.query.with_all([C_TestA]).execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_with_all", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Test query with_any performance
|
|
func test_query_with_any(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_diverse_entities(scale)
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var entities = world.query.with_any([C_TestA, C_TestB, C_TestC]).execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_with_any", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Test query with_none performance
|
|
func test_query_with_none(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_diverse_entities(scale)
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var entities = world.query.with_none([C_TestD]).execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_with_none", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Test complex combined query
|
|
func test_query_complex(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_diverse_entities(scale)
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var entities = world.query\
|
|
.with_all([C_TestA])\
|
|
.with_any([C_TestB, C_TestC])\
|
|
.with_none([C_TestD])\
|
|
.execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_complex", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Test query with component query (property filtering)
|
|
func test_query_with_component_query(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
# Setup entities with varying property values
|
|
for i in scale:
|
|
var entity = Entity.new()
|
|
var comp = C_TestA.new()
|
|
comp.value = i
|
|
entity.add_component(comp)
|
|
world.add_entity(entity, null, false)
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var entities = world.query\
|
|
.with_all([{C_TestA: {'value': {"_gte": scale / 2}}}])\
|
|
.execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_with_component_query", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Test query caching performance
|
|
func test_query_caching(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
setup_diverse_entities(scale)
|
|
|
|
# Execute same query multiple times to test cache
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
for i in 100:
|
|
var entities = world.query.with_all([C_TestA, C_TestB]).execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_caching", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Test query on empty world
|
|
func test_query_empty_world(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
# Don't setup any entities - testing empty world query
|
|
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
for i in scale:
|
|
var entities = world.query.with_all([C_TestA]).execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_empty_world", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Test that disabled entities don't contribute to query time
|
|
## Creates many disabled entities with only a few enabled ones
|
|
## Query time should be similar to querying with only the enabled count
|
|
func test_query_disabled_entities_no_impact(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
# Create mostly disabled entities
|
|
var enabled_count = 10 # Always use 10 enabled entities regardless of scale
|
|
|
|
# First, create disabled entities (scale - enabled_count)
|
|
for i in (scale - enabled_count):
|
|
var entity = Entity.new()
|
|
entity.name = "DisabledEntity_%d" % i
|
|
entity.enabled = false
|
|
entity.add_component(C_TestA.new())
|
|
world.add_entity(entity, null, false)
|
|
|
|
# Then create the few enabled entities
|
|
for i in enabled_count:
|
|
var entity = Entity.new()
|
|
entity.name = "EnabledEntity_%d" % i
|
|
entity.enabled = true
|
|
entity.add_component(C_TestA.new())
|
|
world.add_entity(entity, null, false)
|
|
|
|
# Time querying only enabled entities
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var entities = world.query.with_all([C_TestA]).enabled().execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_disabled_entities_no_impact", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Baseline test: query with only enabled entities (no disabled ones)
|
|
## This should have similar performance to test_query_disabled_entities_no_impact
|
|
func test_query_only_enabled_baseline(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
var enabled_count = 10 # Same as test_query_disabled_entities_no_impact
|
|
|
|
# Create only enabled entities
|
|
for i in enabled_count:
|
|
var entity = Entity.new()
|
|
entity.name = "EnabledEntity_%d" % i
|
|
entity.enabled = true
|
|
entity.add_component(C_TestA.new())
|
|
world.add_entity(entity, null, false)
|
|
|
|
# Time querying enabled entities
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var entities = world.query.with_all([C_TestA]).enabled().execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_only_enabled_baseline", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Test group query performance using Godot's optimized get_nodes_in_group()
|
|
## This should be very fast since it uses Godot's native group indexing
|
|
func test_query_with_group(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
# Create entities and add them to a group
|
|
for i in scale:
|
|
var entity = Entity.new()
|
|
entity.name = "GroupEntity_%d" % i
|
|
entity.add_component(C_TestA.new())
|
|
world.add_entity(entity, null, true) # Must be in tree for groups
|
|
entity.add_to_group("test_group")
|
|
|
|
# Time querying by group
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var entities = world.query.with_group(["test_group"]).execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_with_group", scale, time_ms)
|
|
world.purge(false)
|
|
|
|
## Test group query combined with component filtering
|
|
## This tests the common case of filtering entities by both group and components
|
|
func test_query_group_with_components(scale: int, test_parameters := [[100], [1000], [10000]]):
|
|
# Create diverse entities in a group
|
|
for i in scale:
|
|
var entity = Entity.new()
|
|
entity.name = "GroupEntity_%d" % i
|
|
|
|
# Add various components
|
|
if i % 2 == 0:
|
|
entity.add_component(C_TestA.new())
|
|
if i % 3 == 0:
|
|
entity.add_component(C_TestB.new())
|
|
|
|
world.add_entity(entity, null, true) # Must be in tree for groups
|
|
entity.add_to_group("test_group")
|
|
|
|
# Time querying by group + components
|
|
var time_ms = PerfHelpers.time_it(func():
|
|
var entities = world.query.with_group(["test_group"]).with_all([C_TestA]).execute()
|
|
)
|
|
|
|
PerfHelpers.record_result("query_group_with_components", scale, time_ms)
|
|
world.purge(false)
|