summaryrefslogtreecommitdiff
path: root/composeApp/src/commonMain/kotlin/coffee/liz/ecs/DAGWorld.kt
blob: d31406578c08194878209e32a8e8af53eaf5588b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
    }
}