summaryrefslogtreecommitdiff
path: root/composeApp/src/commonMain/kotlin/coffee/liz/ecs/DAGWorld.kt
blob: c6303d3d6c42931fe69fc27635a5266023b9b142 (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
package coffee.liz.ecs

import kotlin.concurrent.atomics.AtomicInt
import kotlin.concurrent.atomics.ExperimentalAtomicApi
import kotlin.concurrent.atomics.incrementAndFetch
import kotlin.reflect.KClass
import kotlin.time.Duration

/**
 * [World] that updates in [System.dependencies] topological order.
 */
@OptIn(ExperimentalAtomicApi::class)
class DAGWorld<Outside>(
    systems: List<System<Outside>>,
) : World<Outside> {
    private val entities = mutableSetOf<Entity>()
    private val componentCache = mutableMapOf<KClass<out Component>, MutableSet<Entity>>()
    private val systemExecutionOrder: List<System<Outside>> = buildExecutionOrder(systems)
    private val nextEntityId = AtomicInt(0)

    override fun createEntity(): Entity =
        Entity(nextEntityId.incrementAndFetch())
            .also { entities.add(it) }

    override fun removeEntity(entity: Entity) {
        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()

        val firstType = componentTypes[0]
        val candidates = componentCache[firstType] ?: return emptySet()

        return candidates
            .filter { entity -> componentTypes.all { entity.has(it) } }
            .toSet()
    }

    override fun update(
        state: Outside,
        deltaTime: Duration,
    ) {
        refreshComponentCache()
        systemExecutionOrder.forEach { system ->
            system.update(this, state, deltaTime)
        }
    }

    private fun refreshComponentCache() {
        componentCache.clear()
        entities.forEach { entity ->
            entity.componentTypes().forEach { componentType ->
                componentCache.getOrPut(componentType) { mutableSetOf() }.add(entity)
            }
        }
    }

    private fun buildExecutionOrder(systems: List<System<Outside>>): List<System<Outside>> {
        if (systems.isEmpty()) return emptyList()

        val systemMap = systems.associateBy { it::class }
        val inDegree = mutableMapOf<KClass<out System<Outside>>, Int>()
        val adjacencyList = mutableMapOf<KClass<out System<Outside>>, MutableSet<KClass<out System<Outside>>>>()

        // 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<Outside>>>()
        val result = mutableListOf<System<Outside>>()

        queue.addAll(inDegree.filterValues { it == 0 }.keys)

        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)
                }
            }
        }

        if (result.size != systems.size) {
            error("Circular dependency detected in systems")
        }

        return result
    }
}