summaryrefslogtreecommitdiff
path: root/composeApp/src/commonTest/kotlin/coffee/liz/ecs/DAGWorldTest.kt
diff options
context:
space:
mode:
Diffstat (limited to 'composeApp/src/commonTest/kotlin/coffee/liz/ecs/DAGWorldTest.kt')
-rw-r--r--composeApp/src/commonTest/kotlin/coffee/liz/ecs/DAGWorldTest.kt242
1 files changed, 242 insertions, 0 deletions
diff --git a/composeApp/src/commonTest/kotlin/coffee/liz/ecs/DAGWorldTest.kt b/composeApp/src/commonTest/kotlin/coffee/liz/ecs/DAGWorldTest.kt
new file mode 100644
index 0000000..737e386
--- /dev/null
+++ b/composeApp/src/commonTest/kotlin/coffee/liz/ecs/DAGWorldTest.kt
@@ -0,0 +1,242 @@
+package coffee.liz.ecs
+
+import kotlin.reflect.KClass
+import kotlin.test.*
+
+// Test systems for circular dependency test
+class CircularSystemA : System {
+ override val dependencies: Set<KClass<out System>>
+ get() = setOf(CircularSystemB::class)
+ override fun update(world: World, deltaTime: Float) {}
+}
+
+class CircularSystemB : System {
+ override val dependencies: Set<KClass<out System>>
+ get() = setOf(CircularSystemA::class)
+ override fun update(world: World, deltaTime: Float) {}
+}
+
+class DAGWorldTest {
+
+ @Test
+ fun `can create entities with unique ids`() {
+ val world = DAGWorld(emptyList())
+
+ val entity1 = world.createEntity()
+ val entity2 = world.createEntity()
+
+ assertNotEquals(entity1.id, entity2.id)
+ }
+
+ @Test
+ fun `can destroy entity`() {
+ val world = DAGWorld(emptyList())
+ val entity = world.createEntity()
+ entity.add(Position(10f, 20f))
+
+ world.destroyEntity(entity)
+
+ val results = world.query<Position>()
+ assertFalse(results.contains(entity))
+ }
+
+ @Test
+ fun `query returns entities with matching components`() {
+ val world = DAGWorld(emptyList())
+ val entity1 = world.createEntity().add(Position(10f, 20f))
+ val entity2 = world.createEntity().add(Position(30f, 40f))
+ val entity3 = world.createEntity().add(Velocity(1f, 2f))
+
+ world.update(0f) // Trigger cache update
+
+ val results = world.query<Position>()
+
+ assertEquals(2, results.size)
+ assertTrue(results.contains(entity1))
+ assertTrue(results.contains(entity2))
+ assertFalse(results.contains(entity3))
+ }
+
+ @Test
+ fun `query with multiple components returns intersection`() {
+ val world = DAGWorld(emptyList())
+ val entity1 = world.createEntity()
+ .add(Position(10f, 20f))
+ .add(Velocity(1f, 2f))
+ val entity2 = world.createEntity()
+ .add(Position(30f, 40f))
+ val entity3 = world.createEntity()
+ .add(Velocity(3f, 4f))
+
+ world.update(0f) // Trigger cache update
+
+ val results = world.query<Position, Velocity>()
+
+ assertEquals(1, results.size)
+ assertTrue(results.contains(entity1))
+ }
+
+ @Test
+ fun `systems execute in dependency order`() {
+ val executionOrder = mutableListOf<String>()
+
+ val systemA = object : System {
+ override fun update(world: World, deltaTime: Float) {
+ executionOrder.add("A")
+ }
+ }
+
+ val systemB = object : System {
+ override val dependencies = setOf(systemA::class)
+ override fun update(world: World, deltaTime: Float) {
+ executionOrder.add("B")
+ }
+ }
+
+ val systemC = object : System {
+ override val dependencies = setOf(systemB::class)
+ override fun update(world: World, deltaTime: Float) {
+ executionOrder.add("C")
+ }
+ }
+
+ val world = DAGWorld(listOf(systemC, systemA, systemB))
+ world.update(0f)
+
+ assertEquals(listOf("A", "B", "C"), executionOrder)
+ }
+
+ @Test
+ fun `systems with no dependencies can execute in any order`() {
+ val executionOrder = mutableListOf<String>()
+
+ val systemA = object : System {
+ override fun update(world: World, deltaTime: Float) {
+ executionOrder.add("A")
+ }
+ }
+
+ val systemB = object : System {
+ override fun update(world: World, deltaTime: Float) {
+ executionOrder.add("B")
+ }
+ }
+
+ val world = DAGWorld(listOf(systemA, systemB))
+ world.update(0f)
+
+ assertEquals(2, executionOrder.size)
+ assertTrue(executionOrder.contains("A"))
+ assertTrue(executionOrder.contains("B"))
+ }
+
+ @Test
+ fun `circular dependency throws exception`() {
+ val systemA = CircularSystemA()
+ val systemB = CircularSystemB()
+
+ assertFailsWith<IllegalArgumentException> {
+ DAGWorld(listOf(systemA, systemB))
+ }
+ }
+
+ @Test
+ fun `system receives correct deltaTime`() {
+ var receivedDelta = 0f
+
+ val system = object : System {
+ override fun update(world: World, deltaTime: Float) {
+ receivedDelta = deltaTime
+ }
+ }
+
+ val world = DAGWorld(listOf(system))
+ world.update(0.016f)
+
+ assertEquals(0.016f, receivedDelta)
+ }
+
+ @Test
+ fun `systems can query entities during update`() {
+ var queriedEntities: Set<Entity>? = null
+
+ val system = object : System {
+ override fun update(world: World, deltaTime: Float) {
+ queriedEntities = world.query<Position>()
+ }
+ }
+
+ val world = DAGWorld(listOf(system))
+ val entity = world.createEntity().add(Position(10f, 20f))
+
+ world.update(0f)
+
+ assertNotNull(queriedEntities)
+ assertEquals(1, queriedEntities!!.size)
+ assertTrue(queriedEntities!!.contains(entity))
+ }
+
+ @Test
+ fun `component cache updates after entity changes`() {
+ val world = DAGWorld(emptyList())
+ val entity = world.createEntity()
+
+ world.update(0f)
+ assertEquals(0, world.query<Position>().size)
+
+ entity.add(Position(10f, 20f))
+ world.update(0f)
+ assertEquals(1, world.query<Position>().size)
+
+ entity.remove<Position>()
+ world.update(0f)
+ assertEquals(0, world.query<Position>().size)
+ }
+
+ @Test
+ fun `diamond dependency resolves correctly`() {
+ val executionOrder = mutableListOf<String>()
+
+ // A
+ // / \
+ // B C
+ // \ /
+ // D
+
+ val systemA = object : System {
+ override fun update(world: World, deltaTime: Float) {
+ executionOrder.add("A")
+ }
+ }
+
+ val systemB = object : System {
+ override val dependencies = setOf(systemA::class)
+ override fun update(world: World, deltaTime: Float) {
+ executionOrder.add("B")
+ }
+ }
+
+ val systemC = object : System {
+ override val dependencies = setOf(systemA::class)
+ override fun update(world: World, deltaTime: Float) {
+ executionOrder.add("C")
+ }
+ }
+
+ val systemD = object : System {
+ override val dependencies = setOf(systemB::class, systemC::class)
+ override fun update(world: World, deltaTime: Float) {
+ executionOrder.add("D")
+ }
+ }
+
+ val world = DAGWorld(listOf(systemD, systemC, systemB, systemA))
+ world.update(0f)
+
+ // A must come first, D must come last, B and C in between
+ assertEquals("A", executionOrder.first())
+ assertEquals("D", executionOrder.last())
+ assertTrue(executionOrder.indexOf("B") < executionOrder.indexOf("D"))
+ assertTrue(executionOrder.indexOf("C") < executionOrder.indexOf("D"))
+ }
+}