summaryrefslogtreecommitdiff
path: root/composeApp/src/commonMain/kotlin/coffee/liz/ecs/DAGWorld.kt
diff options
context:
space:
mode:
authorElizabeth Hunt <me@liz.coffee>2025-10-23 21:59:37 -0700
committerElizabeth Hunt <me@liz.coffee>2025-10-24 20:00:58 -0700
commit64f825465de9fa30c4dfe2707067efdb96110db8 (patch)
tree5241385e316e2f4ceede5018603103d71be75202 /composeApp/src/commonMain/kotlin/coffee/liz/ecs/DAGWorld.kt
downloadabstraction-engine-kt-64f825465de9fa30c4dfe2707067efdb96110db8.tar.gz
abstraction-engine-kt-64f825465de9fa30c4dfe2707067efdb96110db8.zip
Init
Diffstat (limited to 'composeApp/src/commonMain/kotlin/coffee/liz/ecs/DAGWorld.kt')
-rw-r--r--composeApp/src/commonMain/kotlin/coffee/liz/ecs/DAGWorld.kt127
1 files changed, 127 insertions, 0 deletions
diff --git a/composeApp/src/commonMain/kotlin/coffee/liz/ecs/DAGWorld.kt b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/DAGWorld.kt
new file mode 100644
index 0000000..d314065
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/coffee/liz/ecs/DAGWorld.kt
@@ -0,0 +1,127 @@
+package coffee.liz.ecs
+
+import kotlin.reflect.KClass
+
+/**
+ * World implementation that executes systems in dependency order using a DAG.
+ */
+class DAGWorld(systems: List<System>) : World {
+ private val entities = mutableSetOf<Entity>()
+ private val componentCache = mutableMapOf<KClass<out Component>, MutableSet<Entity>>()
+ private val systemExecutionOrder: List<System>
+ private var nextEntityId = 0
+
+ init {
+ systemExecutionOrder = buildExecutionOrder(systems)
+ }
+
+ override fun createEntity(): Entity {
+ val entity = Entity(nextEntityId++)
+ entities.add(entity)
+ return entity
+ }
+
+ override fun destroyEntity(entity: Entity) {
+ // Remove from all component caches
+ entity.componentTypes().forEach { componentType ->
+ componentCache[componentType]?.remove(entity)
+ }
+ entities.remove(entity)
+ }
+
+ override fun query(vararg componentTypes: KClass<out Component>): Set<Entity> {
+ if (componentTypes.isEmpty()) return entities.toSet()
+
+ // Start with entities that have the first component type
+ val firstType = componentTypes[0]
+ val candidates = componentCache[firstType] ?: return emptySet()
+
+ // Filter to entities that have all component types
+ return candidates.filter { entity ->
+ entity.hasAll(*componentTypes)
+ }.toSet()
+ }
+
+ override fun update(deltaTime: Float) {
+ // Update component cache based on current entity state
+ updateComponentCache()
+
+ // Execute systems in dependency order
+ systemExecutionOrder.forEach { system ->
+ system.update(this, deltaTime)
+ }
+ }
+
+ /**
+ * Rebuild the component cache based on current entity components.
+ * Call this after components have been added/removed from entities.
+ */
+ private fun updateComponentCache() {
+ componentCache.clear()
+ entities.forEach { entity ->
+ entity.componentTypes().forEach { componentType ->
+ componentCache.getOrPut(componentType) { mutableSetOf() }.add(entity)
+ }
+ }
+ }
+
+ /**
+ * Build a topologically sorted execution order from system dependencies.
+ * Uses Kahn's algorithm for topological sorting.
+ */
+ private fun buildExecutionOrder(systems: List<System>): List<System> {
+ if (systems.isEmpty()) return emptyList()
+
+ val systemMap = systems.associateBy { it::class }
+ val inDegree = mutableMapOf<KClass<out System>, Int>()
+ val adjacencyList = mutableMapOf<KClass<out System>, MutableSet<KClass<out System>>>()
+
+ // Initialize graph
+ systems.forEach { system ->
+ val systemClass = system::class
+ inDegree[systemClass] = 0
+ adjacencyList[systemClass] = mutableSetOf()
+ }
+
+ // Build dependency graph (reversed - dependencies point to dependents)
+ systems.forEach { system ->
+ system.dependencies.forEach { dependency ->
+ if (systemMap.containsKey(dependency)) {
+ adjacencyList[dependency]!!.add(system::class)
+ inDegree[system::class] = inDegree[system::class]!! + 1
+ }
+ }
+ }
+
+ // Kahn's algorithm
+ val queue = ArrayDeque<KClass<out System>>()
+ val result = mutableListOf<System>()
+
+ // Add all systems with no dependencies to queue
+ inDegree.forEach { (systemClass, degree) ->
+ if (degree == 0) {
+ queue.add(systemClass)
+ }
+ }
+
+ while (queue.isNotEmpty()) {
+ val currentClass = queue.removeFirst()
+ result.add(systemMap[currentClass]!!)
+
+ // Process dependents
+ adjacencyList[currentClass]?.forEach { dependent ->
+ inDegree[dependent] = inDegree[dependent]!! - 1
+ if (inDegree[dependent] == 0) {
+ queue.add(dependent)
+ }
+ }
+ }
+
+ // Check for cycles
+ if (result.size != systems.size) {
+ throw IllegalArgumentException("Circular dependency detected in systems")
+ }
+
+ return result
+ }
+}