basic ECS spawner

This commit is contained in:
2026-01-15 15:27:48 +01:00
parent 24a781f36a
commit eb737b469c
860 changed files with 58621 additions and 32 deletions

View File

@@ -0,0 +1,5 @@
class_name C_TestOrderComponent
extends Component
@export var execution_log: Array = []
@export var value: int = 0

View File

@@ -0,0 +1 @@
uid://botsbhmpawg2q

View File

@@ -0,0 +1,37 @@
## Observer that watches C_ObserverHealth component with a query filter
class_name O_HealthObserver
extends Observer
var health_changed_count: int = 0
var health_added_count: int = 0
var health_removed_count: int = 0
var low_health_alerts: Array[Entity] = []
func watch() -> Resource:
return C_ObserverHealth
func match() -> QueryBuilder:
# Only watch entities that have both C_ObserverHealth and C_ObserverTest
return q.with_all([C_ObserverHealth, C_ObserverTest])
func on_component_added(entity: Entity, component: Resource) -> void:
health_added_count += 1
func on_component_removed(entity: Entity, component: Resource) -> void:
health_removed_count += 1
func on_component_changed(
entity: Entity, component: Resource, property: String, new_value: Variant, old_value: Variant
) -> void:
if property == "health":
health_changed_count += 1
# Track entities with low health
if new_value < 30:
if not low_health_alerts.has(entity):
low_health_alerts.append(entity)
func reset() -> void:
health_changed_count = 0
health_added_count = 0
health_removed_count = 0
low_health_alerts.clear()

View File

@@ -0,0 +1 @@
uid://grwgxjngteim

View File

@@ -0,0 +1,48 @@
## Observer that watches C_ObserverTest component for add/remove/change events
class_name O_ObserverTest
extends Observer
var added_count: int = 0
var removed_count: int = 0
var changed_count: int = 0
var last_added_entity: Entity = null
var last_removed_entity: Entity = null
var last_changed_entity: Entity = null
var last_changed_property: String = ""
var last_old_value: Variant = null
var last_new_value: Variant = null
func watch() -> Resource:
return C_ObserverTest
func match() -> QueryBuilder:
# Match all entities with C_ObserverTest
return q.with_all([C_ObserverTest])
func on_component_added(entity: Entity, component: Resource) -> void:
added_count += 1
last_added_entity = entity
func on_component_removed(entity: Entity, component: Resource) -> void:
removed_count += 1
last_removed_entity = entity
func on_component_changed(
entity: Entity, component: Resource, property: String, new_value: Variant, old_value: Variant
) -> void:
changed_count += 1
last_changed_entity = entity
last_changed_property = property
last_old_value = old_value
last_new_value = new_value
func reset() -> void:
added_count = 0
removed_count = 0
changed_count = 0
last_added_entity = null
last_removed_entity = null
last_changed_entity = null
last_changed_property = ""
last_old_value = null
last_new_value = null

View File

@@ -0,0 +1 @@
uid://cux6842x440ef

View File

@@ -0,0 +1,31 @@
## Simple observer for performance benchmarking
class_name O_PerformanceTest
extends Observer
var added_count: int = 0
var removed_count: int = 0
var changed_count: int = 0
func watch() -> Resource:
return C_ObserverTest
func match() -> QueryBuilder:
return q.with_all([C_ObserverTest])
func on_component_added(entity: Entity, component: Resource) -> void:
added_count += 1
func on_component_removed(entity: Entity, component: Resource) -> void:
removed_count += 1
func on_component_changed(
entity: Entity, component: Resource, property: String, old_value: Variant, new_value: Variant
) -> void:
changed_count += 1
# Simulate some processing
var _val = component.get("value")
func reset_counts():
added_count = 0
removed_count = 0
changed_count = 0

View File

@@ -0,0 +1 @@
uid://cm7nijht4sro

View File

@@ -0,0 +1,16 @@
## Observer that watches relationships
class_name O_RelationshipObserver
extends Observer
# Note: Observers don't currently have relationship_added/removed callbacks
# This is a placeholder for when relationship observing is implemented
var relationship_events: Array = []
func watch() -> Resource:
return C_ObserverTest
func match() -> QueryBuilder:
return q.with_all([C_ObserverTest])
func reset() -> void:
relationship_events.clear()

View File

@@ -0,0 +1 @@
uid://b0cjecwfpdl1u

View File

@@ -0,0 +1,31 @@
## This system runs when an entity that is not dead and has it's transform component changed
## This is a simple example of a reactive system that updates the entity's transform ONLY when the transform component changes if it's not dead
class_name TestAObserver
extends Observer
var event_count := 0
var added_count := 0
## The component to watch for changes
func watch() -> Resource:
return C_TestA
# What the entity needs to match for the system to run
func match() -> QueryBuilder:
# The query the entity needs to match
return ECS.world.query.with_none([C_TestB])
# What to do when a property on C_Transform just changed on an entity that matches the query
func on_component_changed(
entity: Entity, component: Resource, property: String, old_value: Variant, new_value: Variant
) -> void:
# Set the transfrom from the component to the entity
print("We changed!", entity.name, component.value)
event_count += 1
func on_component_added(entity: Entity, component: Resource) -> void:
added_count += 1

View File

@@ -0,0 +1 @@
uid://dhr3ptijatg7y

View File

@@ -0,0 +1,29 @@
## Observer approach for velocity-based movement (reactive)
class_name O_VelocityObserver
extends Observer
var process_count: int = 0
func watch() -> Resource:
return C_TestVelocity
func match() -> QueryBuilder:
return q.with_all([C_TestPosition, C_TestVelocity])
func on_component_changed(
entity: Entity, component: Resource, property: String, old_value: Variant, new_value: Variant
) -> void:
if property == "velocity":
process_count += 1
# React to velocity changes by updating position
var pos = entity.get_component(C_TestPosition)
if pos:
# Example: Could apply immediate velocity change
# In reality, observers are better for reactions than continuous updates
pass
func on_component_added(entity: Entity, component: Resource) -> void:
process_count += 1
func reset_count():
process_count = 0

View File

@@ -0,0 +1 @@
uid://b2q8xge0b6r37

View File

@@ -0,0 +1 @@
uid://csrumrsjajut4

View File

@@ -0,0 +1,16 @@
## Test system that verifies column data
class_name ArchetypeColumnDataTestSystem
extends System
var values_seen = []
func query() -> QueryBuilder:
return ECS.world.query.with_all([C_TestA]).iterate([C_TestA])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
var test_a_components = components[0]
for comp in test_a_components:
if comp:
values_seen.append(comp.value)

View File

@@ -0,0 +1 @@
uid://ctia33u4i24xw

View File

@@ -0,0 +1,14 @@
## Test system that modifies components
class_name ArchetypeModifyTestSystem
extends System
func query() -> QueryBuilder:
return ECS.world.query.with_all([C_TestA]).iterate([C_TestA])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
var test_a_components = components[0]
for comp in test_a_components:
if comp:
comp.value += 1

View File

@@ -0,0 +1 @@
uid://cyfyyfy5pe0qx

View File

@@ -0,0 +1,15 @@
## Test system that tracks multiple archetype calls
class_name ArchetypeMultipleArchetypesTestSystem
extends System
var archetype_call_count = 0
var total_entities_processed = 0
func query() -> QueryBuilder:
return ECS.world.query.with_all([C_TestA, C_TestB]).iterate([C_TestA, C_TestB])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
archetype_call_count += 1
total_entities_processed += entities.size()

View File

@@ -0,0 +1 @@
uid://crwuhtu227238

View File

@@ -0,0 +1,13 @@
## Test system that doesn't call iterate() (should error)
class_name ArchetypeNoIterateSystem
extends System
var processed = 0
func query() -> QueryBuilder:
return ECS.world.query.with_all([C_TestA]) # Missing .iterate()!
func process(entities: Array[Entity], components: Array, delta: float) -> void:
processed += 1

View File

@@ -0,0 +1 @@
uid://dsgb6ogulr0ui

View File

@@ -0,0 +1,20 @@
## Test system that verifies component order
class_name ArchetypeOrderTestSystem
extends System
var order_correct = false
func query() -> QueryBuilder:
# Intentionally reverse order from with_all to test iterate() controls order
return ECS.world.query.with_all([C_TestA, C_TestB]).iterate([C_TestB, C_TestA])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
if components.size() == 2:
# First should be C_TestB, second should be C_TestA
var first = components[0][0] if components[0].size() > 0 else null
var second = components[1][0] if components[1].size() > 0 else null
if first is C_TestB and second is C_TestA:
order_correct = true

View File

@@ -0,0 +1 @@
uid://drq50t10hcpe6

View File

@@ -0,0 +1,14 @@
## Test system that queries for subset of entity components
class_name ArchetypeSubsetTestSystem
extends System
var entities_processed = 0
func query() -> QueryBuilder:
# Query for A and B, even though entity has A, B, C
return ECS.world.query.with_all([C_TestA, C_TestB]).iterate([C_TestA, C_TestB])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
entities_processed += entities.size()

View File

@@ -0,0 +1 @@
uid://c3e54vjnv3eqx

View File

@@ -0,0 +1,15 @@
## Basic archetype test system
class_name ArchetypeTestSystem
extends System
var archetype_call_count = 0
var entities_processed = 0
func query() -> QueryBuilder:
return ECS.world.query.with_all([C_TestA]).iterate([C_TestA])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
archetype_call_count += 1
entities_processed += entities.size()

View File

@@ -0,0 +1 @@
uid://bvrm33mibdifu

View File

@@ -0,0 +1,33 @@
## Complex test system for performance benchmarking
class_name ComplexPerformanceTestSystem
extends System
var process_count: int = 0
func query():
return ECS.world.query.with_all([C_TestA, C_TestB])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
process_count += 1
# Simulate more complex processing
var comp_a = entity.get_component(C_TestA)
var comp_b = entity.get_component(C_TestB)
if comp_a and comp_b:
# Simulate some computation
var _result = comp_a.serialize()
var _result2 = comp_b.serialize()
# Simulate conditional logic
if process_count % 10 == 0:
# Occasionally add a component
if not entity.has_component(C_TestC):
entity.add_component(C_TestC.new())
func reset_count():
process_count = 0

View File

@@ -0,0 +1 @@
uid://d1g2tv3n6uipx

View File

@@ -0,0 +1,12 @@
## No-op system for measuring overhead
class_name NoOpSystem
extends System
func query():
return ECS.world.query.with_all([C_Velocity])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
pass # Do nothing - used for measuring pure framework overhead

View File

@@ -0,0 +1 @@
uid://by11uhl2ifkc7

View File

@@ -0,0 +1,24 @@
## Simple test system for performance benchmarking
class_name PerformanceTestSystem
extends System
var process_count: int = 0
func query():
return ECS.world.query.with_all([C_TestA])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
process_count += 1
# Simulate some light processing
var component = entity.get_component(C_TestA)
if component:
# Access component data (simulates typical system work)
var _value = component.value # Read property directly, not via reflection
func reset_count():
process_count = 0

View File

@@ -0,0 +1 @@
uid://cgccpsm0e01fb

View File

@@ -0,0 +1,33 @@
## Simple test system for performance benchmarking
# test batch processing
class_name ProcessTestSystem_A
extends System
var process_count: int = 0
func _init(_process_empty: bool = false):
process_empty = _process_empty
func query():
return ECS.world.query.with_all([C_TestA])
# Unified process function for batch processing
func process(entities: Array[Entity], components: Array, delta: float):
process_count += 1
var c_test_a_components = ECS.get_components(entities, C_TestA)
for i in range(entities.size()):
# Simulate some light processing
var component = c_test_a_components[i]
if component:
# Access component data (simulates typical system work)
var _data = component.serialize()
# Simulates a task/action execution system, it clears some task-specific
# components after completing the task for better performance.
entities[i].remove_component(C_TestA)
return true
func reset_count():
process_count = 0

View File

@@ -0,0 +1 @@
uid://5dt3o04qd5pq

View File

@@ -0,0 +1,30 @@
# test overriding process()
class_name ProcessTestSystem_B
extends System
var process_count: int = 0
func _init(_process_empty: bool = false):
process_empty = _process_empty
func query():
return ECS.world.query.with_all([C_TestB])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
if entity:
process_count += 1
# Simulate some light processing
var component = entity.get_component(C_TestB)
if component:
# Access component data (simulates typical system work)
var _data = component.serialize()
# Simulates a task/action execution system, it clears some task-specific
# components after completing the task for better performance.
entity.remove_component(C_TestB)
func reset_count():
process_count = 0

View File

@@ -0,0 +1 @@
uid://ba3lvvufdygps

View File

@@ -0,0 +1,21 @@
class_name TestASystem
extends System
func deps():
return {
Runs.After: [], # Doesn't run after any other system
Runs.Before: [ECS.wildcard], # This system runs before all other systems
}
func query():
return ECS.world.query.with_all([C_TestA])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var a = entity.get_component(C_TestA) as C_TestA
a.value += 1
a.property_changed.emit(a, 'value', null, null)
print("TestASystem: ", entity.name, a.value)

View File

@@ -0,0 +1 @@
uid://cwtjamm4iqado

View File

@@ -0,0 +1,20 @@
class_name TestBSystem
extends System
func deps():
return {
Runs.After: [TestASystem], # Runs after SystemA
Runs.Before: [TestCSystem], # This system rubs before SystemC
}
func query():
return ECS.world.query.with_all([C_TestB])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var a = entity.get_component(C_TestB)
a.value += 1
print("TestBSystem: ", a.value)

View File

@@ -0,0 +1 @@
uid://d0qrblp21kurk

View File

@@ -0,0 +1,20 @@
class_name TestCSystem
extends System
func deps():
return {
Runs.After: [TestBSystem], # Runs after SystemA
Runs.Before: [TestDSystem], # This system rubs before SystemC
}
func query():
return ECS.world.query.with_all([C_TestC])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var a = entity.get_component(C_TestC)
a.value += 1
print("TestASystem: ", a.value)

View File

@@ -0,0 +1 @@
uid://chohwnsy3i4s0

View File

@@ -0,0 +1,21 @@
class_name TestDSystem
extends System
func deps():
return {
Runs.After: [ECS.wildcard], # Runs after all other systems
# If we exclude Rubs.Before it will be ignored
# Runs.Before: [], # We could also set it to an empty array
}
func query():
return ECS.world.query.with_all([C_TestC])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var a = entity.get_component(C_TestC)
a.value += 1
print("TestASystem: ", a.value)

View File

@@ -0,0 +1 @@
uid://cj0qhir58bt6k

View File

@@ -0,0 +1,12 @@
class_name TestSystemNonexistentGroup
extends System
var entities_found := []
func query():
return q.with_group(["NonexistentGroup"])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
entities_found = entities.duplicate()

View File

@@ -0,0 +1 @@
uid://bktsskx8mcole

View File

@@ -0,0 +1,17 @@
class_name S_TestOrderA
extends System
const NAME = 'S_TestOrderA'
func deps():
return {
Runs.Before: [ECS.wildcard], # Run before all other systems
Runs.After: [],
}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var comp = entity.get_component(C_TestOrderComponent)
comp.execution_log.append("A")
comp.value += 1

View File

@@ -0,0 +1 @@
uid://buariuvsnvjb2

View File

@@ -0,0 +1,17 @@
class_name S_TestOrderB
extends System
const NAME = 'S_TestOrderB'
func deps():
return {
Runs.After: [S_TestOrderA],
Runs.Before: [S_TestOrderC],
}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var comp = entity.get_component(C_TestOrderComponent)
comp.execution_log.append("B")
comp.value += 10

View File

@@ -0,0 +1 @@
uid://bkenjhqhoausd

View File

@@ -0,0 +1,17 @@
class_name S_TestOrderC
extends System
const NAME = 'S_TestOrderC'
func deps():
return {
Runs.After: [S_TestOrderB],
Runs.Before: [S_TestOrderD],
}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var comp = entity.get_component(C_TestOrderComponent)
comp.execution_log.append("C")
comp.value += 100

View File

@@ -0,0 +1 @@
uid://cqaxiyxffk4q3

View File

@@ -0,0 +1,19 @@
class_name S_TestOrderD
extends System
const NAME = 'S_TestOrderD'
func deps():
return {
Runs.After: [ECS.wildcard], # Run after all other systems
Runs.Before: [],
}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var comp = entity.get_component(C_TestOrderComponent)
comp.execution_log.append("D")
comp.value += 1000

View File

@@ -0,0 +1 @@
uid://ccy8n3p1rf4fp

View File

@@ -0,0 +1,13 @@
class_name S_TestOrderE
extends System
func deps():
return {Runs.After: [], Runs.Before: []}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var c = entity.get_component(C_TestOrderComponent)
c.execution_log.append("E")

View File

@@ -0,0 +1 @@
uid://cpkgwgw1i378e

View File

@@ -0,0 +1,13 @@
class_name S_TestOrderF
extends System
func deps():
return {Runs.After: [S_TestOrderE], Runs.Before: []}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var c = entity.get_component(C_TestOrderComponent)
c.execution_log.append("F")

View File

@@ -0,0 +1 @@
uid://dnwk2tuyggk6g

View File

@@ -0,0 +1,13 @@
class_name S_TestOrderG
extends System
func deps():
return {Runs.After: [S_TestOrderE], Runs.Before: []}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var c = entity.get_component(C_TestOrderComponent)
c.execution_log.append("G")

View File

@@ -0,0 +1 @@
uid://t8yllp42u864

View File

@@ -0,0 +1,13 @@
class_name S_TestOrderH
extends System
func deps():
return {Runs.After: [S_TestOrderF, S_TestOrderG], Runs.Before: []}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var c = entity.get_component(C_TestOrderComponent)
c.execution_log.append("H")

View File

@@ -0,0 +1 @@
uid://cdpmy2ltl88jr

View File

@@ -0,0 +1,13 @@
class_name S_TestOrderX
extends System
func deps():
return {Runs.After: [], Runs.Before: []}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var comp = entity.get_component(C_TestOrderComponent)
comp.execution_log.append("X")

View File

@@ -0,0 +1 @@
uid://cuslkawfgeyiq

View File

@@ -0,0 +1,13 @@
class_name S_TestOrderY
extends System
func deps():
return {Runs.After: [], Runs.Before: []}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var comp = entity.get_component(C_TestOrderComponent)
comp.execution_log.append("Y")

View File

@@ -0,0 +1 @@
uid://b844sl3oew2vc

View File

@@ -0,0 +1,13 @@
class_name S_TestOrderZ
extends System
func deps():
return {Runs.After: [], Runs.Before: []}
func query():
return ECS.world.query.with_all([C_TestOrderComponent])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
var comp = entity.get_component(C_TestOrderComponent)
comp.execution_log.append("Z")

View File

@@ -0,0 +1 @@
uid://cbucoavgh1vky

View File

@@ -0,0 +1,12 @@
class_name TestSystemWithGroup
extends System
var entities_found := []
func query():
return q.with_group(["TestGroup"])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
entities_found = entities.duplicate()

View File

@@ -0,0 +1 @@
uid://balljjtww1lc1

View File

@@ -0,0 +1,12 @@
class_name TestSystemWithRelationship
extends System
var entities_found := []
func query():
return q.with_relationship([Relationship.new(C_TestA.new(), null)])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
entities_found = entities.duplicate()

View File

@@ -0,0 +1 @@
uid://duoso032fxxt2

View File

@@ -0,0 +1,12 @@
class_name TestSystemWithoutGroup
extends System
var entities_found := []
func query():
return q.without_group(["TestGroup"])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
entities_found = entities.duplicate()

View File

@@ -0,0 +1 @@
uid://bjm8r3h0frnlr

View File

@@ -0,0 +1,12 @@
class_name TestSystemWithoutRelationship
extends System
var entities_found := []
func query():
return q.without_relationship([Relationship.new(C_TestA.new(), null)])
func process(entities: Array[Entity], components: Array, delta: float) -> void:
entities_found = entities.duplicate()

View File

@@ -0,0 +1 @@
uid://d1u7ie74wc3kn

View File

@@ -0,0 +1,21 @@
## Traditional system approach for velocity-based movement
class_name S_VelocitySystem
extends System
var process_count: int = 0
func query():
return ECS.world.query.with_all([C_TestPosition, C_TestVelocity])
func process(entities: Array[Entity], components: Array, delta: float):
for entity in entities:
process_count += 1
var pos = entity.get_component(C_TestPosition)
var vel = entity.get_component(C_TestVelocity)
# Update position based on velocity
# Note: Direct assignment without using setter to avoid triggering observers
pos.position = pos.position + vel.velocity * delta
func reset_count():
process_count = 0

View File

@@ -0,0 +1 @@
uid://c1r6ewwcgy8fq