From 64f825465de9fa30c4dfe2707067efdb96110db8 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Thu, 23 Oct 2025 21:59:37 -0700 Subject: Init --- .../kotlin/coffee/liz/ecs/DAGWorldTest.kt | 242 +++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 composeApp/src/commonTest/kotlin/coffee/liz/ecs/DAGWorldTest.kt (limited to 'composeApp/src/commonTest/kotlin/coffee/liz/ecs/DAGWorldTest.kt') 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> + get() = setOf(CircularSystemB::class) + override fun update(world: World, deltaTime: Float) {} +} + +class CircularSystemB : System { + override val dependencies: Set> + 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() + 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() + + 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() + + assertEquals(1, results.size) + assertTrue(results.contains(entity1)) + } + + @Test + fun `systems execute in dependency order`() { + val executionOrder = mutableListOf() + + 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() + + 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 { + 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? = null + + val system = object : System { + override fun update(world: World, deltaTime: Float) { + queriedEntities = world.query() + } + } + + 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().size) + + entity.add(Position(10f, 20f)) + world.update(0f) + assertEquals(1, world.query().size) + + entity.remove() + world.update(0f) + assertEquals(0, world.query().size) + } + + @Test + fun `diamond dependency resolves correctly`() { + val executionOrder = mutableListOf() + + // 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")) + } +} -- cgit v1.2.3-70-g09d2