diff --git a/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScanner.kt b/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScanner.kt index f23d328a7..a334e8060 100644 --- a/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScanner.kt +++ b/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoScanner.kt @@ -26,7 +26,7 @@ interface AutoScanner { override fun installSystems() { scannedSystems.asSequence() .mapNotNull { it.objectInstance ?: runCatching { it.createInstance() }.getOrNull() } - .filterIsInstance() + .filterIsInstance>() .onEach { geary.pipeline.addSystem(it) } .map { it::class.simpleName } .let { diff --git a/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoscanAnnotations.kt b/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoscanAnnotations.kt index 278f18742..4ba6bb476 100644 --- a/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoscanAnnotations.kt +++ b/addons/geary-autoscan/src/main/kotlin/com/mineinabyss/geary/autoscan/AutoscanAnnotations.kt @@ -2,7 +2,6 @@ package com.mineinabyss.geary.autoscan import com.mineinabyss.geary.systems.GearySystem import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.RepeatingSystem import com.mineinabyss.geary.systems.query.GearyQuery /** diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt index 2e1c0c5ae..f86334c55 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/Prefabs.kt @@ -6,7 +6,8 @@ import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault import com.mineinabyss.geary.addons.dsl.GearyDSL import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.prefabs.configuration.systems.* -import com.mineinabyss.geary.prefabs.systems.TrackPrefabsByKeySystem +import com.mineinabyss.geary.prefabs.systems.createInheritPrefabsOnLoadListener +import com.mineinabyss.geary.prefabs.systems.createTrackPrefabsByKeyListener import com.mineinabyss.idofront.di.DI val prefabs by DI.observe() @@ -23,14 +24,13 @@ interface Prefabs { override fun Prefabs.install() { - geary.pipeline.addSystems( - ParseChildOnPrefab(), - ParseChildrenOnPrefab(), - ParseRelationOnPrefab(), - ParseRelationWithDataSystem(), - TrackPrefabsByKeySystem(), - CopyToInstancesSystem(), - ) + createInheritPrefabsOnLoadListener() + createParseChildOnPrefabListener() + createParseChildrenOnPrefabListener() + createParseRelationOnPrefabListener() + createParseRelationWithDataListener() + createTrackPrefabsByKeyListener() + createCopyToInstancesSystem() geary.pipeline.runOnOrAfter(GearyPhase.INIT_ENTITIES) { loader.loadOrUpdatePrefabs() } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/ChildrenOnPrefab.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/ChildrenOnPrefab.kt index 287b50dda..222df7d7f 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/ChildrenOnPrefab.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/components/ChildrenOnPrefab.kt @@ -2,8 +2,8 @@ package com.mineinabyss.geary.prefabs.configuration.components import com.mineinabyss.geary.components.EntityName import com.mineinabyss.geary.datatypes.Component -import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import com.mineinabyss.geary.serialization.serializers.InnerSerializer +import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import kotlinx.serialization.Polymorphic import kotlinx.serialization.PolymorphicSerializer import kotlinx.serialization.Serializable diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt index e9737014e..47c3fc9b8 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/CopyToInstancesSystem.kt @@ -1,16 +1,15 @@ package com.mineinabyss.geary.prefabs.configuration.systems import com.mineinabyss.geary.annotations.optin.UnsafeAccessors +import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.prefabs.configuration.components.CopyToInstances -import com.mineinabyss.geary.systems.GearyListener -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery -class CopyToInstancesSystem : GearyListener() { - private val Pointers.baseEntity by whenExtendedEntity() - - @OptIn(UnsafeAccessors::class) - override fun Pointers.handle() { - val copy = baseEntity.get() ?: return - copy.decodeComponentsTo(target.entity) - } +@OptIn(UnsafeAccessors::class) +fun createCopyToInstancesSystem() = geary.listener(object : ListenerQuery() { + val baseEntity by event.extendedEntity() +}).exec { + val copy = baseEntity.get() ?: return@exec + copy.decodeComponentsTo(entity) } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseChildOnPrefab.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseChildOnPrefab.kt index 2e248e500..e290805fe 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseChildOnPrefab.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseChildOnPrefab.kt @@ -2,43 +2,39 @@ package com.mineinabyss.geary.prefabs.configuration.systems import com.mineinabyss.geary.components.EntityName import com.mineinabyss.geary.components.relations.NoInherit -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.helpers.addParent import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.prefabs.configuration.components.ChildOnPrefab import com.mineinabyss.geary.prefabs.configuration.components.ChildrenOnPrefab import com.mineinabyss.geary.prefabs.configuration.components.Prefab -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery -class ParseChildOnPrefab : Listener() { - private var Pointers.child by get().removable().whenSetOnTarget() - - @OptIn(UnsafeAccessors::class) - override fun Pointers.handle() { - entity { - addParent(target.entity) - setAll(child!!.components) - } - child = null +fun createParseChildOnPrefabListener() = geary.listener(object : ListenerQuery() { + val child by get() + override fun ensure() = event.anySet(::child) +}).exec { + entity { + addParent(entity) + setAll(child.components) } + entity.remove() } -class ParseChildrenOnPrefab : Listener() { - private var Pointers.children by get().removable().whenSetOnTarget() - - @OptIn(UnsafeAccessors::class) - override fun Pointers.handle() { - children!!.nameToComponents.forEach { (name, components) -> - entity { - set(EntityName(name)) - set(Prefab()) - addParent(target.entity) - addRelation() - setAll(components) - } +fun createParseChildrenOnPrefabListener() = geary.listener(object : ListenerQuery() { + var children by get() + override fun ensure() = event.anySet(::children) +}).exec { + children.nameToComponents.forEach { (name, components) -> + entity { + set(EntityName(name)) + set(Prefab()) + addParent(entity) + addRelation() + setAll(components) } - children = null } + entity.remove() } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationOnPrefab.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationOnPrefab.kt index c21f15929..1ef955df1 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationOnPrefab.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationOnPrefab.kt @@ -1,20 +1,19 @@ package com.mineinabyss.geary.prefabs.configuration.systems +import com.mineinabyss.geary.helpers.componentId +import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.prefabs.configuration.components.RelationOnPrefab -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery - -class ParseRelationOnPrefab : Listener() { - private var Pointers.relation by get().removable().whenSetOnTarget() - - override fun Pointers.handle() { - try { - val rel: RelationOnPrefab = relation!! -// entity.setRelation(relation.value, entity.parseEntity(relation.key).id) - } finally { - relation = null - } +fun createParseRelationOnPrefabListener() = geary.listener(object : ListenerQuery() { + val relation by get() + override fun ensure() = event.anySet(::relation) +}).exec { + try { + val target = entity.lookup(relation.target)?.id ?: return@exec + entity.setRelation(componentId(relation.data::class), target, relation.data) + } finally { + entity.remove() } } - diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationWithDataSystem.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationWithDataSystem.kt index 530a383a4..0f5d108f5 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationWithDataSystem.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/configuration/systems/ParseRelationWithDataSystem.kt @@ -1,23 +1,21 @@ package com.mineinabyss.geary.prefabs.configuration.systems -import com.mineinabyss.geary.datatypes.Records import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.systems.accessors.RelationWithData +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery - -class ParseRelationWithDataSystem : Listener() { - private val Records.relationWithData by get>().whenSetOnTarget() - - @OptIn(UnsafeAccessors::class) - override fun Pointers.handle() { - val entity = target.entity - val data = relationWithData.data - val targetData = relationWithData.targetData - if (data != null) entity.set(data, relationWithData.relation.id) - else entity.add(relationWithData.relation.id) - if (targetData != null) entity.set(targetData, relationWithData.target.id) - entity.remove>() - } +@OptIn(UnsafeAccessors::class) +fun createParseRelationWithDataListener() = geary.listener(object : ListenerQuery() { + val relationWithData by get>() + override fun ensure() = event.anySet(::relationWithData) +}).exec { + val entity = entity + val data = relationWithData.data + val targetData = relationWithData.targetData + if (data != null) entity.set(data, relationWithData.relation.id) + else entity.add(relationWithData.relation.id) + if (targetData != null) entity.set(targetData, relationWithData.target.id) + entity.remove>() } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/InheritPrefabsOnLoad.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/InheritPrefabsOnLoad.kt index e73fc8c68..12243d9b0 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/InheritPrefabsOnLoad.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/InheritPrefabsOnLoad.kt @@ -1,19 +1,13 @@ package com.mineinabyss.geary.prefabs.systems import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.datatypes.family.family +import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.prefabs.events.PrefabLoaded import com.mineinabyss.geary.prefabs.helpers.inheritPrefabs -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers - - -class InheritPrefabsOnLoad : Listener() { - private val Pointers.loaded by family { has() }.on(event) - - @OptIn(UnsafeAccessors::class) - override fun Pointers.handle() { - target.entity.inheritPrefabs() - } -} +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery +@OptIn(UnsafeAccessors::class) +fun createInheritPrefabsOnLoadListener() = geary.listener(object : ListenerQuery() { + override fun ensure() = event { has() } +}).exec { entity.inheritPrefabs() } diff --git a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/TrackPrefabsByKeySystem.kt b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/TrackPrefabsByKeySystem.kt index 105d5c869..c2792092b 100644 --- a/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/TrackPrefabsByKeySystem.kt +++ b/addons/geary-prefabs/src/commonMain/kotlin/com/mineinabyss/geary/prefabs/systems/TrackPrefabsByKeySystem.kt @@ -1,20 +1,18 @@ package com.mineinabyss.geary.prefabs.systems -import com.mineinabyss.geary.components.relations.NoInherit -import com.mineinabyss.geary.datatypes.Records import com.mineinabyss.geary.annotations.optin.UnsafeAccessors +import com.mineinabyss.geary.components.relations.NoInherit +import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.prefabs.PrefabKey import com.mineinabyss.geary.prefabs.prefabs -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers - - -class TrackPrefabsByKeySystem : Listener() { - private val Records.key by get().whenSetOnTarget() +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery - @OptIn(UnsafeAccessors::class) - override fun Pointers.handle() { - prefabs.manager.registerPrefab(key, target.entity) - target.entity.addRelation() - } +@OptIn(UnsafeAccessors::class) +fun createTrackPrefabsByKeyListener() = geary.listener(object : ListenerQuery() { + val key by get() + override fun ensure() = event.anySet(::key) +}).exec { + prefabs.manager.registerPrefab(key, entity) + entity.addRelation() } diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/PrefabTests.kt b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/PrefabTests.kt index eb2bf2248..5be3c5c10 100644 --- a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/PrefabTests.kt +++ b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/PrefabTests.kt @@ -34,7 +34,7 @@ class PrefabTests { } @Test - fun `should track prefabs when key added`(){ + fun `should track prefabs when key added`() { // arrange & act val prefab = entity { set(testKey) } diff --git a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt index 911865b18..93bd8a48a 100644 --- a/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt +++ b/addons/geary-prefabs/src/jvmTest/kotlin/com/mineinabyss/geary/prefabs/SerializerTest.kt @@ -1,7 +1,7 @@ package com.mineinabyss.geary.prefabs -import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import com.mineinabyss.geary.serialization.formats.YamlFormat +import com.mineinabyss.geary.serialization.serializers.PolymorphicListAsMapSerializer import io.kotest.matchers.shouldBe import kotlinx.serialization.Polymorphic import kotlinx.serialization.PolymorphicSerializer diff --git a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUIDTracking.kt b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUIDTracking.kt index d3c15a3ee..c414ca3ec 100644 --- a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUIDTracking.kt +++ b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/UUIDTracking.kt @@ -1,9 +1,8 @@ package com.mineinabyss.geary.uuid import com.mineinabyss.geary.addons.dsl.GearyAddonWithDefault -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.uuid.systems.TrackUuidOnAdd -import com.mineinabyss.geary.uuid.systems.UnTrackUuidOnRemove +import com.mineinabyss.geary.uuid.systems.createTrackUUIDOnAddListener +import com.mineinabyss.geary.uuid.systems.createUntrackUuidOnRemoveListener import com.mineinabyss.idofront.di.DI val uuid2Geary by DI.observe() @@ -12,9 +11,7 @@ object UUIDTracking : GearyAddonWithDefault { override fun default() = SimpleUUID2GearyMap() override fun UUID2GearyMap.install() { - geary.pipeline.addSystems( - TrackUuidOnAdd(), - UnTrackUuidOnRemove() - ) + createTrackUUIDOnAddListener() + createUntrackUuidOnRemoveListener() } } diff --git a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/TrackUuidOnAdd.kt b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/TrackUuidOnAdd.kt index b16e6d672..8d39ffb9e 100644 --- a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/TrackUuidOnAdd.kt +++ b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/TrackUuidOnAdd.kt @@ -2,27 +2,23 @@ package com.mineinabyss.geary.uuid.systems import com.benasher44.uuid.Uuid import com.benasher44.uuid.uuid4 -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.systems.GearyListener -import com.mineinabyss.geary.systems.accessors.Pointers - +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery import com.mineinabyss.geary.uuid.components.RegenerateUUIDOnClash import com.mineinabyss.geary.uuid.uuid2Geary -class TrackUuidOnAdd : GearyListener() { - var Pointers.uuid by get().whenSetOnTarget() - val Pointers.regenerateUUIDOnClash by get().orNull().on(target) - - @OptIn(UnsafeAccessors::class) - override fun Pointers.handle() { - if (uuid in uuid2Geary) - if (regenerateUUIDOnClash != null) { - val newUuid = uuid4() - uuid = newUuid - uuid2Geary[newUuid] = target.entity - } else error("Tried tracking entity $target.entity with already existing uuid $uuid") - else - uuid2Geary[uuid] = target.entity - } +fun createTrackUUIDOnAddListener() = geary.listener(object : ListenerQuery() { + var uuid by get() + val regenerateUUIDOnClash by get().orNull() + override fun ensure() = event.anySet(::uuid) +}).exec { + if (uuid in uuid2Geary) + if (regenerateUUIDOnClash != null) { + val newUuid = uuid4() + uuid = newUuid + uuid2Geary[newUuid] = entity + } else error("Tried tracking entity $entity with already existing uuid $uuid") + else + uuid2Geary[uuid] = entity } - diff --git a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/UnTrackUuidOnRemove.kt b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/UnTrackUuidOnRemove.kt index cb5cd0c12..ae4f7d82c 100644 --- a/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/UnTrackUuidOnRemove.kt +++ b/addons/geary-uuid/src/commonMain/kotlin/com/mineinabyss/geary/uuid/systems/UnTrackUuidOnRemove.kt @@ -2,18 +2,12 @@ package com.mineinabyss.geary.uuid.systems import com.benasher44.uuid.Uuid import com.mineinabyss.geary.components.events.EntityRemoved -import com.mineinabyss.geary.datatypes.Records -import com.mineinabyss.geary.datatypes.family.family -import com.mineinabyss.geary.systems.GearyListener -import com.mineinabyss.geary.systems.accessors.Pointers - +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery import com.mineinabyss.geary.uuid.uuid2Geary -class UnTrackUuidOnRemove : GearyListener() { - private val Pointers.uuid by get().on(target) - private val Pointers.removed by family { has() }.on(event) - - override fun Pointers.handle() { - uuid2Geary.remove(uuid) - } -} +fun createUntrackUuidOnRemoveListener() = geary.listener(object : ListenerQuery() { + val uuid by get() + override fun ensure() = event.invoke { has() } +}).exec { uuid2Geary.remove(uuid) } diff --git a/geary-benchmarks/build.gradle.kts b/geary-benchmarks/build.gradle.kts index c863a1496..b264a8cae 100644 --- a/geary-benchmarks/build.gradle.kts +++ b/geary-benchmarks/build.gradle.kts @@ -45,7 +45,7 @@ benchmark { } create("specific") { - include("NewEntity") + include("Unpack6Benchmark") warmups = 1 iterations = 1 iterationTime = 3 diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/VelocitySystemBenchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/VelocitySystemBenchmark.kt index 34aba6c4d..e9905f8a3 100644 --- a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/VelocitySystemBenchmark.kt +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/VelocitySystemBenchmark.kt @@ -2,12 +2,11 @@ package com.mineinabyss.geary.benchmarks import com.mineinabyss.geary.benchmarks.helpers.oneMil import com.mineinabyss.geary.benchmarks.helpers.tenMil -import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.RepeatingSystem -import com.mineinabyss.geary.systems.accessors.Pointer +import com.mineinabyss.geary.systems.query.Query +import com.mineinabyss.geary.systems.builders.system import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.Setup @@ -18,32 +17,19 @@ class VelocitySystemBenchmark { data class Velocity(val x: Float, val y: Float) data class Position(var x: Float, var y: Float) - object VelocitySystem : RepeatingSystem() { - private val Pointer.velocity by get() - private var Pointer.position by get() - - override fun Pointer.tick() { - position.x += velocity.x - position.y += velocity.y - } + fun createVelocitySystem() = geary.system(object : Query() { + val velocity by get() + var position by get() + }).exec { + position.x += velocity.x + position.y += velocity.y } - - // sanity check that `by` and a function call is not causing slowdowns - object VelocitySystemNoBoxing : RepeatingSystem() { - private val velocity = get() - private var position = get() - - val test by family { - hasSet() - hasSet() - } - - override fun tickAll() { - forEach { - position[it].x += velocity[it].x - position[it].y += velocity[it].y - } - } + fun createVelocitySystemNoDelegates() = geary.system(object : Query() { + val velocity = get() + var position = get() + }).exec { + position().x += velocity().x + position().y += velocity().y } val velocities = Array(tenMil) { Velocity(it.toFloat() / oneMil, it.toFloat() / oneMil) } @@ -62,25 +48,25 @@ class VelocitySystemBenchmark { } @Benchmark - fun velocitySystem() { - VelocitySystem.tickAll() - } - - @Benchmark - fun velocitySystemNoBoxing() { - VelocitySystemNoBoxing.tickAll() + fun velocitySystemNoDelegates() { + createVelocitySystemNoDelegates().tick() } // Theoretical performance with zero ECS overhead @Benchmark fun pureArrays() { var i = 0 - while(i < tenMil) { + while (i < tenMil) { positions[i].x += velocities[i].x positions[i].y += velocities[i].y i++ } } + + @Benchmark + fun velocitySystem() { + createVelocitySystem().tick() + } } fun main() { @@ -88,7 +74,7 @@ fun main() { setUp() repeat(400) { -// velocitySystem() + createVelocitySystem() } } } diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/events/EventCalls.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/events/EventCalls.kt index d1e8458ca..ae3fd0e1c 100644 --- a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/events/EventCalls.kt +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/events/EventCalls.kt @@ -5,8 +5,8 @@ import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.GearyListener -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery import com.mineinabyss.idofront.di.DI import org.openjdk.jmh.annotations.* @@ -14,6 +14,7 @@ import org.openjdk.jmh.annotations.* class EventCalls { var targets = emptyList() + @Setup(Level.Invocation) fun setupPerInvocation() { geary(TestEngineModule) @@ -25,13 +26,13 @@ class EventCalls { DI.clear() } - private class Listener: GearyListener() { - val Pointers.int by get().on(target) - val Pointers.event by get().on(event) - var count = 0 - override fun Pointers.handle() { - count++ - } + var count = 0 + + fun createListener() = geary.listener(object : ListenerQuery() { + val int by get() + val eventComp by event.get() + }).exec { + count++ } private class Event diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Systems.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Systems.kt new file mode 100644 index 000000000..423d15ece --- /dev/null +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Systems.kt @@ -0,0 +1,48 @@ +package com.mineinabyss.geary.benchmarks.unpacking + +import com.mineinabyss.geary.benchmarks.helpers.* +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.systems.builders.cachedQuery +import com.mineinabyss.geary.systems.query.GearyQuery + +class Query1 : GearyQuery() { + val comp1 by get() +} + +class Query2 : GearyQuery() { + val comp1 by get() + val comp2 by get() +} + +class Query6 : GearyQuery() { + val comp1 by get() + val comp2 by get() + val comp3 by get() + val comp4 by get() + val comp5 by get() + val comp6 by get() +} + + +class Query6WithoutDelegate : GearyQuery() { + val comp1 = get() + val comp2 = get() + val comp3 = get() + val comp4 = get() + val comp5 = get() + val comp6 = get() + + override fun ensure() = this { + hasSet() + hasSet() + hasSet() + hasSet() + hasSet() + hasSet() + } +} + +fun systemOf1() = geary.cachedQuery(Query1()) +fun systemOf2() = geary.cachedQuery(Query2()) +fun systemOf6() = geary.cachedQuery(Query6()) +fun systemOf6WithoutDelegate() = geary.cachedQuery(Query6WithoutDelegate()) diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack1Benchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack1Benchmark.kt index 557a4c7c3..63b00bf44 100644 --- a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack1Benchmark.kt +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack1Benchmark.kt @@ -5,8 +5,6 @@ import com.mineinabyss.geary.benchmarks.helpers.tenMil import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.accessors.Pointer -import com.mineinabyss.geary.systems.query.GearyQuery import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.Setup @@ -14,9 +12,6 @@ import org.openjdk.jmh.annotations.State @State(Scope.Benchmark) class Unpack1Benchmark { - private object SystemOf1 : GearyQuery() { - val Pointer.comp1 by get() - } @Setup fun setUp() { @@ -31,10 +26,8 @@ class Unpack1Benchmark { @Benchmark fun unpack1of1Comp() { - SystemOf1.run { - forEach { - it.comp1 - } + systemOf1().forEach { + comp1 } } } diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack2Benchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack2Benchmark.kt index 340e0a13f..99d77c145 100644 --- a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack2Benchmark.kt +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack2Benchmark.kt @@ -6,8 +6,6 @@ import com.mineinabyss.geary.benchmarks.helpers.tenMil import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.accessors.Pointer -import com.mineinabyss.geary.systems.query.GearyQuery import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.Setup @@ -15,15 +13,6 @@ import org.openjdk.jmh.annotations.State @State(Scope.Benchmark) class Unpack2Benchmark { - private object SystemOf2 : GearyQuery() { - val Pointer.comp1 by get() - val Pointer.comp2 by get() - } - - private object SystemOf1 : GearyQuery() { - val Pointer.comp1 by get() - } - @Setup fun setUp() { geary(TestEngineModule) { @@ -39,20 +28,16 @@ class Unpack2Benchmark { @Benchmark fun unpack1of2Comp() { - SystemOf1.run { - forEach { - it.comp1 - } + systemOf1().forEach { + comp1 } } @Benchmark fun unpack2of2Comp() { - SystemOf2.run { - forEach { - it.comp1 - it.comp2 - } + systemOf2().forEach { + comp1 + comp2 } } } diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt index 1c225b916..20ec5b274 100644 --- a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt +++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt @@ -1,12 +1,9 @@ package com.mineinabyss.geary.benchmarks.unpacking import com.mineinabyss.geary.benchmarks.helpers.* -import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.modules.TestEngineModule import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.accessors.Pointer -import com.mineinabyss.geary.systems.query.GearyQuery import org.openjdk.jmh.annotations.Benchmark import org.openjdk.jmh.annotations.Scope import org.openjdk.jmh.annotations.Setup @@ -14,42 +11,10 @@ import org.openjdk.jmh.annotations.State @State(Scope.Benchmark) class Unpack6Benchmark { - private object SystemOf6 : GearyQuery() { - val Pointer.comp1 by get() - val Pointer.comp2 by get() - val Pointer.comp3 by get() - val Pointer.comp4 by get() - val Pointer.comp5 by get() - val Pointer.comp6 by get() - - } - - private object SystemOf6WithoutDelegate : GearyQuery() { - val comp1 = get() - val comp2 = get() - val comp3 = get() - val comp4 = get() - val comp5 = get() - val comp6 = get() - - val test by family { - hasSet() - hasSet() - hasSet() - hasSet() - hasSet() - hasSet() - } - } - - private object SystemOf1 : GearyQuery() { - val Pointer.comp1 by get() - } @Setup fun setUp() { - geary(TestEngineModule) { - } + geary(TestEngineModule) repeat(tenMil) { entity { @@ -65,48 +30,50 @@ class Unpack6Benchmark { @Benchmark fun unpack1of6Comp() { - SystemOf1.run { - forEach { - it.comp1 - } + systemOf1().forEach { + comp1 } } @Benchmark fun unpack6of6Comp() { - SystemOf6.run { - forEach { - it.comp1 - it.comp2 - it.comp3 - it.comp4 - it.comp5 - it.comp6 - } + systemOf6().forEach { + comp1 + comp2 + comp3 + comp4 + comp5 + comp6 + } + } + + @Benchmark + fun unpack1of6CompNoDelegate() { + systemOf6WithoutDelegate().forEach { + comp1() } } + // This test gives ridiculous numbers, I think kotlin might just be optimizing some calls away that it can't with a delegate? @Benchmark fun unpack6of6CompNoDelegate() { - SystemOf6WithoutDelegate.run { - forEach { - comp1[it] - comp2[it] - comp3[it] - comp4[it] - comp5[it] - comp6[it] - } + systemOf6WithoutDelegate().forEach { + comp1() + comp2() + comp3() + comp4() + comp5() + comp6() } } } - fun main() { Unpack6Benchmark().apply { setUp() repeat(100) { unpack6of6Comp() +// unpack6of6CompNoDelegate() } } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/GearyAliases.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/GearyAliases.kt index 8c9f6127d..1adb38e3c 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/GearyAliases.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/GearyAliases.kt @@ -4,7 +4,6 @@ import kotlinx.serialization.Polymorphic typealias GearyEntityType = EntityType typealias GearyRecord = Record -typealias GearyRecords = Records typealias GearyRelation = Relation /** diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/RecordPointer.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/RecordPointer.kt index a1882bb3a..64928efb6 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/RecordPointer.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/RecordPointer.kt @@ -9,34 +9,4 @@ class RecordPointer @PublishedApi internal constructor( archetype: Archetype, row: Int ) { - constructor(record: Record) : this(record.archetype, record.row) { - delegated = true - delegate = record - } - - private val originalArchetype = archetype - private val originalRow = row - - @UnsafeAccessors - val archetype: Archetype get() = if (delegated) delegate!!.archetype else originalArchetype - val row: Int get() = if (delegated) delegate!!.row else originalRow - - private var delegate: Record? = null - private var delegated = false - - @UnsafeAccessors - val entity: Entity - get() { - val entity = archetype.getEntity(row) - if (!delegated) { - delegate = archetypes.records[entity] - } - delegated = true - return entity - } - - @UnsafeAccessors - operator fun component1(): Archetype = archetype - - operator fun component2(): Int = row } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Records.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Records.kt deleted file mode 100644 index d53647f08..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Records.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.mineinabyss.geary.datatypes - -import com.mineinabyss.geary.systems.accessors.Pointer - -/** - * A collection of records used for queries involving multiple entities. - * - * Currently built for our event system but will support arbitrary entities once we improve the query system. - */ -class Records( - val target: Pointer, - val event: Pointer, - val source: Pointer?, -) { - fun getByIndex(index: Int): Pointer { - return when (index) { - 0 -> target - 1 -> event - 2 -> source ?: error("Source is null") - else -> error("Index out of bounds") - } - } -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/Family.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/Family.kt index 6df1d66dd..81d256d40 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/Family.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/Family.kt @@ -2,13 +2,12 @@ package com.mineinabyss.geary.datatypes.family import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.EntityId -import com.mineinabyss.geary.systems.accessors.Pointer import com.mineinabyss.geary.systems.accessors.FamilyMatching import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor +import com.mineinabyss.geary.systems.query.Query import kotlin.reflect.KProperty -sealed interface Family : ReadOnlyAccessor, FamilyMatching { - +sealed interface Family { sealed class Leaf : Family { sealed interface Component : Family { val component: ComponentId @@ -41,10 +40,4 @@ sealed interface Family : ReadOnlyAccessor, FamilyMatching { val or: List } } - - // Helpers for writing queries - override val family: Family? get() = this - override fun getValue(thisRef: Pointer, property: KProperty<*>): Family { - return this - } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt index 1ff3624d8..ab56f3270 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/family/MutableFamily.kt @@ -9,7 +9,7 @@ import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.componentIdWithNullable import com.mineinabyss.geary.modules.geary -inline fun family(init: MutableFamily.Selector.And.() -> Unit): Family { +inline fun family(init: MutableFamily.Selector.And.() -> Unit): Family.Selector.And { return MutableFamily.Selector.And().apply(init) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Pipeline.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Pipeline.kt index 8f5914ea5..5fee33380 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Pipeline.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Pipeline.kt @@ -1,19 +1,23 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.addons.GearyPhase -import com.mineinabyss.geary.systems.RepeatingSystem +import com.mineinabyss.geary.systems.Listener import com.mineinabyss.geary.systems.System +import com.mineinabyss.geary.systems.TrackedSystem +import com.mineinabyss.geary.systems.query.Query interface Pipeline { fun runOnOrAfter(phase: GearyPhase, block: () -> Unit) - fun interceptSystemAddition(run: (System) -> System?) + fun onSystemAdd(run: (System<*>) -> Unit) fun runStartupTasks() /** Adds a [system] to the engine, which will be ticked appropriately by the engine. */ - fun addSystem(system: System) + fun addSystem(system: System): TrackedSystem<*> - fun addSystems(vararg systems: System) + fun addSystems(vararg systems: System<*>) + + fun addListener(listener: Listener<*>): Listener<*> /** Gets all registered systems in the order they should be executed during an engine tick. */ - fun getRepeatingInExecutionOrder(): Iterable + fun getRepeatingInExecutionOrder(): Iterable> } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/PipelineImpl.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/PipelineImpl.kt index da7e0bd16..9e77cef5a 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/PipelineImpl.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/PipelineImpl.kt @@ -3,15 +3,16 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.addons.GearyPhase import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.RepeatingSystem import com.mineinabyss.geary.systems.System +import com.mineinabyss.geary.systems.TrackedSystem +import com.mineinabyss.geary.systems.query.Query class PipelineImpl : Pipeline { private val queryManager get() = geary.queryManager - private val onSystemRegister = mutableListOf<(System) -> System?>() - private val registeredSystems: MutableSet = mutableSetOf() - private val registeredListeners: MutableSet = mutableSetOf() + private val onSystemAdd = mutableListOf<(System<*>) -> Unit>() + private val repeatingSystems: MutableSet> = mutableSetOf() + private val registeredListeners: MutableSet> = mutableSetOf() private val scheduled = Array(GearyPhase.entries.size) { mutableListOf<() -> Unit>() } private var currentPhase = GearyPhase.entries.first() @@ -21,8 +22,8 @@ class PipelineImpl : Pipeline { else scheduled[phase.ordinal].add(block) } - override fun interceptSystemAddition(run: (System) -> System?) { - onSystemRegister.add(run) + override fun onSystemAdd(run: (System<*>) -> Unit) { + onSystemAdd.add(run) } override fun runStartupTasks() { @@ -31,33 +32,28 @@ class PipelineImpl : Pipeline { } } - override fun addSystem(system: System) { - val resultSystem = onSystemRegister.fold(system) { acc, func -> func(acc) ?: return } - // Track systems right at startup since they are likely going to tick very soon anyway, and we don't care about - // any hiccups at that point. - when (resultSystem) { - is RepeatingSystem -> { - if (resultSystem in registeredSystems) return - queryManager.trackQuery(resultSystem) - registeredSystems.add(resultSystem) - } - - is Listener -> { - if (resultSystem in registeredListeners) return - resultSystem.start() - queryManager.trackEventListener(resultSystem) - registeredListeners.add(resultSystem) - } - - else -> resultSystem.onStart() + override fun addSystem(system: System): TrackedSystem<*> { + onSystemAdd.forEach { it(system) } + val runner = queryManager.trackQuery(system.query) + val tracked = TrackedSystem(system, runner) + if (system.interval != null) { + repeatingSystems.add(tracked) } + return TrackedSystem(system, runner) } - override fun addSystems(vararg systems: System) { + override fun addSystems(vararg systems: System<*>) { systems.forEach { addSystem(it) } } - override fun getRepeatingInExecutionOrder(): Iterable { - return registeredSystems + override fun addListener(listener: Listener<*>): Listener<*> { + if (listener in registeredListeners) return listener + queryManager.trackEventListener(listener) + registeredListeners.add(listener) + return listener + } + + override fun getRepeatingInExecutionOrder(): Iterable> { + return repeatingSystems } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/QueryManager.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/QueryManager.kt index 6bc9b4260..794ce19b6 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/QueryManager.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/QueryManager.kt @@ -3,11 +3,13 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.systems.Listener +import com.mineinabyss.geary.systems.query.CachedQueryRunner import com.mineinabyss.geary.systems.query.GearyQuery +import com.mineinabyss.geary.systems.query.Query interface QueryManager { - fun trackEventListener(listener: Listener) - fun trackQuery(query: GearyQuery) + fun trackEventListener(listener: Listener<*>) + fun trackQuery(query: T): CachedQueryRunner /** Returns a list of entities matching the given family. */ fun getEntitiesMatching(family: Family): List diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt index 4663986e8..768b15141 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/Archetype.kt @@ -66,11 +66,11 @@ class Archetype internal constructor( /** The amount of entities stored in this archetype. */ val size: Int get() = ids.size - val sourceListeners = mutableListOf() + val sourceListeners = mutableListOf>() - val targetListeners = mutableListOf() + val targetListeners = mutableListOf>() - val eventListeners = mutableListOf() + val eventListeners = mutableListOf>() // ==== Helper functions ==== fun getEntity(row: Int): Entity { diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEngine.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEngine.kt index 4cb928452..bc6696278 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEngine.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEngine.kt @@ -3,7 +3,8 @@ package com.mineinabyss.geary.engine.archetypes import com.mineinabyss.geary.datatypes.* import com.mineinabyss.geary.engine.* import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.RepeatingSystem +import com.mineinabyss.geary.systems.TrackedSystem +import com.mineinabyss.geary.systems.query.Query import kotlinx.coroutines.* import kotlin.coroutines.CoroutineContext import kotlin.time.Duration @@ -24,8 +25,8 @@ open class ArchetypeEngine(override val tickDuration: Duration) : TickingEngine( (CoroutineScope(Dispatchers.Default) + CoroutineName("Geary Engine")).coroutineContext /** Describes how to individually tick each system */ - protected open fun RepeatingSystem.runSystem() { - tickAll() + protected open fun TrackedSystem.runSystem() { + system.onTick(runner) } override fun scheduleSystemTicking() { @@ -41,7 +42,10 @@ open class ArchetypeEngine(override val tickDuration: Duration) : TickingEngine( override fun tick(currentTick: Long) { // Create a job but don't start it pipeline.getRepeatingInExecutionOrder() - .filter { currentTick % (it.interval / tickDuration).toInt().coerceAtLeast(1) == 0L } + .filter { + it.system.interval != null + && (currentTick % (it.system.interval / tickDuration).toInt().coerceAtLeast(1) == 0L) + } .also { logger.v("Ticking engine with systems $it") } .forEach { system -> runCatching { diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEventRunner.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEventRunner.kt index 98e157296..6d2a9f41e 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEventRunner.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeEventRunner.kt @@ -2,13 +2,12 @@ package com.mineinabyss.geary.engine.archetypes import com.mineinabyss.geary.datatypes.Entity import com.mineinabyss.geary.datatypes.Record -import com.mineinabyss.geary.datatypes.RecordPointer -import com.mineinabyss.geary.datatypes.Records import com.mineinabyss.geary.datatypes.maps.TypeMap import com.mineinabyss.geary.engine.EventRunner import com.mineinabyss.geary.helpers.fastForEach import com.mineinabyss.geary.modules.archetypes import com.mineinabyss.geary.systems.Listener +import com.mineinabyss.geary.systems.query.QueriedEntity class ArchetypeEventRunner : EventRunner { private val records: TypeMap get() = archetypes.records @@ -17,39 +16,42 @@ class ArchetypeEventRunner : EventRunner { callEvent(records[target], records[event], source?.let { records[source] }) } - fun callEvent(target: Record, event: Record, source: Record?) { val eventArc = event.archetype val targetArc = target.archetype val sourceArc = source?.archetype - fun callListener(listener: Listener) { - val pointers: Records = when (source) { - null -> Records(RecordPointer(target), RecordPointer(event), null) - else -> Records(RecordPointer(target), RecordPointer(event), RecordPointer(source)) - } - with(listener) { - pointers.handle() - } + fun QueriedEntity.reset(record: Record) { + originalArchetype = record.archetype + originalRow = record.row + delegated = false + } + + fun callListener(listener: Listener<*>) { + val query = listener.query + query.event.reset(event) + query.reset(target) + source?.let { query.source.reset(it) } + listener.run() } targetArc.targetListeners.fastForEach { - if ((it.event.isEmpty || it in eventArc.eventListeners) && - (it.source.isEmpty || it in (sourceArc?.sourceListeners ?: emptySet())) + if ((it.event.and.isEmpty() || it in eventArc.eventListeners) && + (it.source.and.isEmpty() || it in (sourceArc?.sourceListeners ?: emptySet())) ) callListener(it) } eventArc.eventListeners.fastForEach { // Check empty target to not double call listeners - if (it.target.isEmpty && - (it.event.isEmpty || it in eventArc.eventListeners) && - (it.source.isEmpty || it in (sourceArc?.sourceListeners ?: emptySet())) + if (it.target.and.isEmpty() && + (it.event.and.isEmpty() || it in eventArc.eventListeners) && + (it.source.and.isEmpty() || it in (sourceArc?.sourceListeners ?: emptySet())) ) callListener(it) } sourceArc?.sourceListeners?.fastForEach { // Likewise both target and event must be empty to not double call listeners - if (it.target.isEmpty && it.event.isEmpty && - (it.target.isEmpty || it in targetArc.targetListeners) && - (it.event.isEmpty || it in (eventArc.eventListeners)) + if (it.target.and.isEmpty() && it.event.and.isEmpty() && + (it.target.and.isEmpty() || it in targetArc.targetListeners) && + (it.event.and.isEmpty() || it in (eventArc.eventListeners)) ) callListener(it) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeQueryManager.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeQueryManager.kt index a3df536c3..37b179ef6 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeQueryManager.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeQueryManager.kt @@ -5,14 +5,15 @@ import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.datatypes.maps.Family2ObjectArrayMap import com.mineinabyss.geary.engine.QueryManager import com.mineinabyss.geary.helpers.contains -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.query.GearyQuery +import com.mineinabyss.geary.systems.query.CachedQueryRunner import com.mineinabyss.geary.systems.query.Query import kotlinx.atomicfu.locks.SynchronizedObject import kotlinx.atomicfu.locks.synchronized +typealias Listener = com.mineinabyss.geary.systems.Listener<*> + class ArchetypeQueryManager : QueryManager { - private val queries = mutableListOf() + private val queries = mutableListOf>() private val sourceListeners = mutableListOf() private val targetListeners = mutableListOf() private val eventListeners = mutableListOf() @@ -22,35 +23,37 @@ class ArchetypeQueryManager : QueryManager { setIndex = { it, index -> it.id = index } ) - val archetypeRegistryLock = SynchronizedObject() + private val archetypeRegistryLock = SynchronizedObject() val archetypeCount get() = archetypes.elements.size override fun trackEventListener(listener: Listener) { - if (!listener.event.isEmpty) { - val eventFamilyMatch = archetypes.match(listener.event.family) + if (listener.event.and.isNotEmpty()) { + val eventFamilyMatch = archetypes.match(listener.event) for (archetype in eventFamilyMatch) archetype.eventListeners += listener eventListeners.add(listener) } // Only start tracking a listener for the parts it actually cares for - if (!listener.source.isEmpty) { - val sourcesMatched = archetypes.match(listener.source.family) + if (listener.source.and.isNotEmpty()) { + val sourcesMatched = archetypes.match(listener.source) for (archetype in sourcesMatched) archetype.sourceListeners += listener sourceListeners.add(listener) } - if (!listener.target.isEmpty) { - val targetsMatched = archetypes.match(listener.target.family) + if (listener.target.and.isNotEmpty()) { + val targetsMatched = archetypes.match(listener.target) for (archetype in targetsMatched) archetype.targetListeners += listener targetListeners.add(listener) } } - override fun trackQuery(query: GearyQuery) { - val matched = archetypes.match(query.family) - query.matchedArchetypes += matched - queries.add(query) - query.registered = true + override fun trackQuery(query: T): CachedQueryRunner { + query.initialize() + val queryRunner = CachedQueryRunner(query) + val matched = archetypes.match(queryRunner.family) + queryRunner.matchedArchetypes += matched + queries.add(queryRunner) + return queryRunner } internal fun registerArchetype(archetype: Archetype) = synchronized(archetypeRegistryLock) { @@ -71,7 +74,7 @@ class ArchetypeQueryManager : QueryManager { } data class MatchedQueries( - val queries: List, + val queries: List>, val sourceListeners: List, val targetListeners: List, val eventListeners: List @@ -79,9 +82,9 @@ class ArchetypeQueryManager : QueryManager { fun getQueriesMatching(archetype: Archetype): MatchedQueries { val matched = queries.filter { archetype.type in it.family } - val matchedSources = sourceListeners.filter { archetype.type in it.source.family } - val matchedTargets = targetListeners.filter { archetype.type in it.target.family } - val matchedEvents = eventListeners.filter { archetype.type in it.event.family } + val matchedSources = sourceListeners.filter { archetype.type in it.source } + val matchedTargets = targetListeners.filter { archetype.type in it.target } + val matchedEvents = eventListeners.filter { archetype.type in it.event } return MatchedQueries(matched, matchedSources, matchedTargets, matchedEvents) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/CheckingListener.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/CheckingListener.kt index 17407836e..c7f7395fd 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/CheckingListener.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/CheckingListener.kt @@ -4,23 +4,3 @@ import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.components.RequestCheck import com.mineinabyss.geary.components.events.FailedCheck import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers - -/** - * A listener that runs a check on matched events, adding [FailedCheck] to the event when the check fails. - */ -abstract class CheckingListener() : Listener() { - init { - event.mutableFamily.has() - } - - abstract fun Pointers.check(): Boolean - - @OptIn(UnsafeAccessors::class) - override fun Pointers.handle() { - if (!check()) event.entity.apply { - remove() - add() - } - } -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ArchetypeHelpers.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ArchetypeHelpers.kt index 788572af0..fcb8d500c 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ArchetypeHelpers.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/ArchetypeHelpers.kt @@ -1,10 +1,8 @@ package com.mineinabyss.geary.helpers -import com.mineinabyss.geary.modules.ArchetypeEngineModule -import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.modules.archetypes -// TODO context to avoid cast fun EntityType.getArchetype(): Archetype = - (geary as ArchetypeEngineModule).archetypeProvider.getArchetype(this) + archetypes.archetypeProvider.getArchetype(this) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyConfiguration.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyConfiguration.kt index c93c82cdc..df762c38a 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyConfiguration.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyConfiguration.kt @@ -9,13 +9,15 @@ import com.mineinabyss.idofront.di.DI import kotlin.reflect.KClass @GearyDSL -class GearyConfiguration { +class GearyConfiguration( + val module: GearyModule +) { val installedAddons = mutableMapOf>, Any>() inline fun , reified Module : Any> install( addon: T, ): Module { val module = DI.getOrNull() - if(module != null) return module + if (module != null) return module return install(addon, addon.default()) } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModule.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModule.kt index 63712e924..73ca1276f 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModule.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/GearyModule.kt @@ -44,7 +44,7 @@ interface GearyModule { val defaults: Defaults operator fun invoke(configure: GearyConfiguration.() -> Unit) { - GearyConfiguration().apply(configure) + GearyConfiguration(this).apply(configure) } companion object diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/dsl/QueryAPI.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/dsl/QueryAPI.kt new file mode 100644 index 000000000..28828bfc3 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/dsl/QueryAPI.kt @@ -0,0 +1,8 @@ +package com.mineinabyss.geary.modules.dsl + +import com.mineinabyss.geary.modules.GearyConfiguration +import com.mineinabyss.geary.systems.query.Query + +fun GearyConfiguration.track(query: Query) { + +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/GearyAliases.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/GearyAliases.kt index 154960c8c..41ddb4820 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/GearyAliases.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/GearyAliases.kt @@ -1,5 +1,4 @@ package com.mineinabyss.geary.systems -typealias GearyListener = Listener -typealias GearySystem = System -typealias GearyRepeatingSystem = RepeatingSystem +typealias GearyListener = Listener +typealias GearySystem = System diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/Listener.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/Listener.kt index 8b6b5bdea..5cd93e31a 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/Listener.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/Listener.kt @@ -1,72 +1,15 @@ package com.mineinabyss.geary.systems -import com.mineinabyss.geary.components.events.ExtendedEntity -import com.mineinabyss.geary.datatypes.GearyEntity -import com.mineinabyss.geary.helpers.componentId -import com.mineinabyss.geary.helpers.toGeary -import com.mineinabyss.geary.systems.accessors.* -import com.mineinabyss.geary.systems.accessors.type.ComponentAccessor - -/** - * #### [Guide: Listeners](https://wiki.mineinabyss.com/geary/guide/listeners) - * - * Exposes a way to match against certain combinations of [source]/[target]/[event] entities present on a fired event. - * - * [Handler]s can be defined inside by annotating a function with [Handler], these - * are the actual functions that run when a matching event is found. - */ -abstract class Listener : AccessorOperations(), System { - val target: AccessorHolder = AccessorHolder() - val event: AccessorHolder = AccessorHolder() - val source: AccessorHolder = AccessorHolder() - - fun start() { - onStart() - } - - private fun getIndexForHolder(holder: AccessorHolder): Int = when (holder) { - target -> 0 - event -> 1 - source -> 2 - else -> error("Holder is not a part of this listener: $holder") - } - - fun , A> T.on(holder: AccessorHolder): ReadOnlyEntitySelectingAccessor { - val index = getIndexForHolder(holder) - if (this is FamilyMatching) this.family?.let { holder.mutableFamily.add(it) } - return ReadOnlyEntitySelectingAccessor(this, index) - } - - fun , A> T.on(holder: AccessorHolder): ReadWriteEntitySelectingAccessor { - val index = getIndexForHolder(holder) - if (this is FamilyMatching) this.family?.let { holder.mutableFamily.add(it) } - return ReadWriteEntitySelectingAccessor(this, index) - } - - /** Fires when an entity has a component of type [T] set or updated. */ - inline fun , reified A> T.whenSetOnTarget(): ReadWriteEntitySelectingAccessor { - event.mutableFamily.onSet(componentId()) - return this.on(target) - } - - /** Fires when an entity has a component of type [T] set, only if it was not set before. */ - inline fun , reified A> T.whenFirstSetOnTarget(): ReadWriteEntitySelectingAccessor { - event.mutableFamily.onFirstSet(componentId()) - return this.on(target) - } - - /** Fires when an entity has a component of type [T] added, updates are not considered since no data changes. */ - inline fun , reified A> T.whenAddedOnTarget(): ReadWriteEntitySelectingAccessor { - event.mutableFamily.onAdd(componentId()) - return this.on(event) - } - - /** Fires when an entity has a component of type [T] added, updates are not considered since no data changes. */ - fun whenExtendedEntity(): ReadOnlyEntitySelectingAccessor, GearyEntity> { - event.mutableFamily.onExtendedEntity() - return getRelations().map { it.single().target.toGeary() }.on(event) - } - - abstract fun Pointers.handle() +import com.mineinabyss.geary.datatypes.family.Family +import com.mineinabyss.geary.systems.query.ListenerQuery + +class Listener internal constructor( + val query: T, + val families: ListenerQuery.Families, + val handle: T.() -> Unit, +) { + fun run() = handle(query) + val event: Family.Selector.And get() = families.event + val source: Family.Selector.And get() = families.source + val target: Family.Selector.And get() = families.target } - diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/RepeatingSystem.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/RepeatingSystem.kt deleted file mode 100644 index cfbf0d887..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/RepeatingSystem.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.mineinabyss.geary.systems - -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.accessors.Pointer -import com.mineinabyss.geary.systems.query.Query -import kotlin.time.Duration - -/** - * #### [Guide: Ticking systems](https://wiki.mineinabyss.com/geary/guide/ticking-systems) - * - * A system for the ECS that will run every [interval] ticks. - * - * @param interval How often to run this system in ticks. - */ -abstract class RepeatingSystem( - val interval: Duration = geary.defaults.repeatingSystemInterval -) : Query(), System { - override fun onStart() {} - - open fun tickAll() { - forEach(run = { it.tick() }) - } - - protected open fun Pointer.tick() {} -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/System.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/System.kt index cefca98fb..763dad152 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/System.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/System.kt @@ -1,9 +1,11 @@ package com.mineinabyss.geary.systems -/** - * An interface representing all types of systems that can be registered with the engine. - * Includes [Listener] and [RepeatingSystem]. - */ -interface System { - fun onStart() {} -} +import com.mineinabyss.geary.systems.query.CachedQueryRunner +import com.mineinabyss.geary.systems.query.Query +import kotlin.time.Duration + +class System @PublishedApi internal constructor( + val query: T, + val onTick: CachedQueryRunner.() -> Unit, + val interval: Duration?, +) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/TrackedSystem.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/TrackedSystem.kt new file mode 100644 index 000000000..d289191a8 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/TrackedSystem.kt @@ -0,0 +1,13 @@ +package com.mineinabyss.geary.systems + +import com.mineinabyss.geary.systems.query.CachedQueryRunner +import com.mineinabyss.geary.systems.query.Query + +class TrackedSystem( + val system: System, + val runner: CachedQueryRunner +) { + fun tick() { + system.onTick(runner) + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorHolder.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorHolder.kt deleted file mode 100644 index 32c0e7e50..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorHolder.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.mineinabyss.geary.systems.accessors - -import com.mineinabyss.geary.datatypes.family.Family -import com.mineinabyss.geary.datatypes.family.MutableFamily - - -/** - * A holder of [IndexedAccessor]s that provides logic for reading data off them and calculating their per-archetype cache. - * - * @property family A lazily built immutable family that represents all data this holder needs to function. - */ -open class AccessorHolder : AccessorOperations() { - val family: Family.Selector.And get() = mutableFamily - - @PublishedApi - internal val mutableFamily: MutableFamily.Selector.And = MutableFamily.Selector.And() - - - /** Is the family of this holder not restricted in any way? */ - val isEmpty: Boolean get() = family.and.isEmpty() -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorOperations.kt index 6c17fafbe..96e1e1480 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorOperations.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorOperations.kt @@ -1,55 +1,69 @@ package com.mineinabyss.geary.systems.accessors -import com.mineinabyss.geary.datatypes.Component -import com.mineinabyss.geary.datatypes.HOLDS_DATA -import com.mineinabyss.geary.datatypes.Relation -import com.mineinabyss.geary.datatypes.withRole +import com.mineinabyss.geary.components.events.ExtendedEntity +import com.mineinabyss.geary.datatypes.* +import com.mineinabyss.geary.datatypes.family.MutableFamily import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.componentIdWithNullable +import com.mineinabyss.geary.helpers.toGeary import com.mineinabyss.geary.systems.accessors.type.* -import kotlin.reflect.KProperty +import com.mineinabyss.geary.systems.query.EventQueriedEntity +import com.mineinabyss.geary.systems.query.QueriedEntity + +abstract class AccessorOperations { + abstract val cacheAccessors: Boolean -open class AccessorOperations { /** Accesses a component, ensuring it is on the entity. */ - inline fun get(): ComponentAccessor { - return NonNullComponentAccessor(componentId().withRole(HOLDS_DATA)) + protected inline fun QueriedEntity.get(): ComponentAccessor { + return addAccessor { + NonNullComponentAccessor(cacheAccessors, null, this, componentId().withRole(HOLDS_DATA)) + } } /** Accesses a data stored in a relation with kind [K] and target type [T], ensuring it is on the entity. */ - inline fun getRelation(): ComponentAccessor { - return NonNullComponentAccessor(Relation.of().id) + protected inline fun QueriedEntity.getRelation(): ComponentAccessor { + return addAccessor { NonNullComponentAccessor(cacheAccessors, null, this, Relation.of().id) } + } + + inline fun QueriedEntity.addAccessor(create: () -> T): T { + val accessor = create() + accessors.add(accessor) + if (accessor is ComponentAccessor<*>) cachingAccessors.add(accessor) + if (accessor.originalAccessor != null) accessors.remove(accessor.originalAccessor) + return accessor } /** * Accesses a component, allows removing it by setting to null. * As a result, the type is nullable since it may be removed during system runtime. */ - fun ComponentAccessor.removable(): RemovableComponentAccessor { - return RemovableComponentAccessor(id) - } +// @Deprecated("Removing") +// fun ComponentAccessor.removable(): RemovableComponentAccessor { +// return queriedEntity.addAccessor { RemovableComponentAccessor(this, queriedEntity, id) } +// } /** * Accesses a component or provides a [default] if the entity doesn't have it. * Default gets recalculated on every call to the accessor. */ fun ComponentAccessor.orDefault(default: () -> T): ComponentOrDefaultAccessor { - return ComponentOrDefaultAccessor(id, default) + return queriedEntity.addAccessor { ComponentOrDefaultAccessor(this, queriedEntity, id, default) } } /** Maps an accessor, will recalculate on every call. */ fun > A.map(mapping: (T) -> U): ReadOnlyAccessor { - return object : ReadOnlyAccessor, FamilyMatching { - override val family = (this@map as? FamilyMatching)?.family + return queriedEntity.addAccessor { + when (this) { + is FamilyMatching -> object : ReadOnlyAccessor by MappedAccessor(this, mapping), + FamilyMatching by this {} - override fun getValue(thisRef: Pointer, property: KProperty<*>): U { - val value = this@map.getValue(thisRef, property) - return mapping(value) + else -> MappedAccessor(this, mapping) } } } /** Accesses a component or `null` if the entity doesn't have it. */ - fun ComponentAccessor.orNull(): ComponentOrDefaultAccessor { + fun ComponentAccessor.orNull(): ComponentOrDefaultAccessor { return orDefault { null } } @@ -65,12 +79,30 @@ open class AccessorOperations { * - One of [K] or [T] is [Any] => gets all relations matching the other (specified) type. * - Note: nullability rules are still upheld with [Any]. */ - inline fun getRelations(): RelationsAccessor { - return RelationsAccessor(componentIdWithNullable(), componentIdWithNullable()) + protected inline fun QueriedEntity.getRelations(): RelationsAccessor { + return addAccessor { RelationsAccessor(null, this, componentIdWithNullable(), componentIdWithNullable()) } } /** @see getRelations */ - inline fun getRelationsWithData(): RelationsWithDataAccessor { - return RelationsWithDataAccessor(componentIdWithNullable(), componentIdWithNullable()) + protected inline fun QueriedEntity.getRelationsWithData(): RelationsWithDataAccessor { + return addAccessor { + RelationsWithDataAccessor( + null, + this, + componentIdWithNullable(), + componentIdWithNullable() + ) + } + } + + protected operator fun QueriedEntity.invoke(init: MutableFamily.Selector.And.() -> Unit) { + val family = com.mineinabyss.geary.datatypes.family.family(init) + extraFamilies.add(family) + } + + /** Fires when an entity has a component of type [T] added, updates are not considered since no data changes. */ + protected fun EventQueriedEntity.extendedEntity(): ReadOnlyAccessor { + invoke { onExtendedEntity() } + return getRelations().map { it.single().target.toGeary() } } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/Aliases.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/Aliases.kt index 9c9250725..93f6c6042 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/Aliases.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/Aliases.kt @@ -1,19 +1,20 @@ package com.mineinabyss.geary.systems.accessors -import com.mineinabyss.geary.datatypes.RecordPointer -import com.mineinabyss.geary.datatypes.Records +import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.systems.query.CachedQueryRunner +import com.mineinabyss.geary.systems.query.QueriedEntity +import com.mineinabyss.geary.systems.query.Query import kotlin.properties.ReadOnlyProperty import kotlin.properties.ReadWriteProperty +interface Accessor { + val originalAccessor: Accessor? + val queriedEntity: QueriedEntity +} +interface ReadOnlyAccessor : Accessor, ReadOnlyProperty { -typealias ReadOnlyAccessor = ReadOnlyProperty -typealias ReadWriteAccessor = ReadWriteProperty +} -/** A pointer to where a specific entity's data is stored for use by accessors. */ -typealias Pointer = RecordPointer +interface ReadWriteAccessor : ReadOnlyAccessor, ReadWriteProperty { -/** A list of [Pointer]s, currently used to select between the target, source, and event entity in listeners. */ -typealias Pointers = Records - -typealias GearyPointer = RecordPointer -typealias GearyPointers = Records +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/FamilyMatching.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/FamilyMatching.kt index 0aed8ed13..1aa4d887f 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/FamilyMatching.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/FamilyMatching.kt @@ -1,11 +1,12 @@ package com.mineinabyss.geary.systems.accessors import com.mineinabyss.geary.datatypes.family.Family +import com.mineinabyss.geary.systems.query.QueriedEntity /** * Used for accessors that require a family to be matched against to work * (ex a component accessor needs the component present on the entity.) */ interface FamilyMatching { - val family: Family? + val family: Family.Selector? } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/ReadOnlyEntitySelectingAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/ReadOnlyEntitySelectingAccessor.kt deleted file mode 100644 index c319354ae..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/ReadOnlyEntitySelectingAccessor.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.mineinabyss.geary.systems.accessors - -import com.mineinabyss.geary.datatypes.Records -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty - -/** Wrapper for a [ReadOnlyAccessor] that selects a specific entity when multiple can be chosen from. */ -open class ReadOnlyEntitySelectingAccessor, A>( - protected val accessor: T, - protected val pointerIndex: Int, -) : ReadOnlyProperty { - override fun getValue(thisRef: Records, property: KProperty<*>): A { - return accessor.getValue(thisRef.getByIndex(pointerIndex), property) - } -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/ReadWriteEntitySelectingAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/ReadWriteEntitySelectingAccessor.kt deleted file mode 100644 index 260351d8a..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/ReadWriteEntitySelectingAccessor.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.mineinabyss.geary.systems.accessors - -import com.mineinabyss.geary.datatypes.Records -import kotlin.properties.ReadWriteProperty -import kotlin.reflect.KProperty - -/** Wrapper for a [ReadWriteAccessor] that selects a specific entity when multiple can be chosen from. */ -class ReadWriteEntitySelectingAccessor, A>( - accessor: T, - pointerIndex: Int -) : ReadOnlyEntitySelectingAccessor(accessor, pointerIndex), ReadWriteProperty { - override fun setValue(thisRef: Records, property: KProperty<*>, value: A) { - accessor.setValue(thisRef.getByIndex(pointerIndex), property, value) - } -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentAccessor.kt index 9c0e2dc74..97874cde5 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentAccessor.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentAccessor.kt @@ -1,56 +1,66 @@ package com.mineinabyss.geary.systems.accessors.type -import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.datatypes.family.Family +import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.systems.accessors.Accessor import com.mineinabyss.geary.systems.accessors.FamilyMatching -import com.mineinabyss.geary.systems.accessors.Pointer import com.mineinabyss.geary.systems.accessors.ReadWriteAccessor +import com.mineinabyss.geary.systems.query.QueriedEntity +import com.mineinabyss.geary.systems.query.Query import kotlin.reflect.KProperty @OptIn(UnsafeAccessors::class) abstract class ComponentAccessor( + val cacheArchetypeInfo: Boolean, + override val originalAccessor: Accessor?, + final override val queriedEntity: QueriedEntity, val id: ComponentId ) : ReadWriteAccessor, FamilyMatching { - override val family: Family = family { hasSet(id) } + override val family = family { hasSet(id) } protected var cachedIndex = -1 protected var cachedDataArray: MutableList = mutableListOf() - protected var cachedArchetype: Archetype? = null - abstract operator fun get(thisRef: Pointer): T + fun updateCache(archetype: Archetype) { + cachedIndex = archetype.indexOf(id) + if (cachedIndex != -1) cachedDataArray = archetype.componentData[cachedIndex] as MutableList + } - internal inline fun get(thisRef: Pointer, beforeRead: () -> Unit): T { - val archetype = thisRef.archetype - if (archetype !== cachedArchetype) { - cachedArchetype = archetype - cachedIndex = archetype.indexOf(id) - if (cachedIndex != -1) cachedDataArray = archetype.componentData[cachedIndex] as MutableList - } + abstract fun get(thisRef: Query): T + + internal inline fun get(query: Query, beforeRead: () -> Unit): T { beforeRead() - return cachedDataArray[thisRef.row] + if (!cacheArchetypeInfo) { + updateCache(queriedEntity.archetype) + return cachedDataArray[queriedEntity.row] + } + return cachedDataArray[query.row] } - abstract operator fun set(thisRef: Pointer, value: T) + abstract fun set(query: Query, value: T) - internal inline fun set(thisRef: Pointer, value: T, beforeWrite: () -> Unit) { - val archetype = thisRef.archetype - if (archetype !== cachedArchetype) { - cachedArchetype = archetype - cachedIndex = archetype.indexOf(id) - if (cachedIndex != -1) cachedDataArray = archetype.componentData[cachedIndex] as MutableList - } + internal inline fun set(query: Query, value: T, beforeWrite: () -> Unit) { beforeWrite() - cachedDataArray[thisRef.row] = value + if (!cacheArchetypeInfo) { + updateCache(query.archetype) + if (value == null) { + queriedEntity.unsafeEntity.remove(id) + return + } + if (cachedIndex == -1) queriedEntity.unsafeEntity.set(value, id) + else cachedDataArray[queriedEntity.row] = value + return + } + cachedDataArray[query.row] = value } - final override fun getValue(thisRef: Pointer, property: KProperty<*>): T { + final override fun getValue(thisRef: Query, property: KProperty<*>): T { return get(thisRef) } - final override fun setValue(thisRef: Pointer, property: KProperty<*>, value: T) { + final override fun setValue(thisRef: Query, property: KProperty<*>, value: T) { return set(thisRef, value) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentOrDefaultAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentOrDefaultAccessor.kt index 15f61048b..56ec7f6c1 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentOrDefaultAccessor.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/ComponentOrDefaultAccessor.kt @@ -3,20 +3,25 @@ package com.mineinabyss.geary.systems.accessors.type import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.engine.archetypes.Archetype -import com.mineinabyss.geary.systems.accessors.Pointer +import com.mineinabyss.geary.systems.accessors.Accessor +import com.mineinabyss.geary.systems.accessors.AccessorOperations import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor +import com.mineinabyss.geary.systems.query.QueriedEntity +import com.mineinabyss.geary.systems.query.Query import kotlin.reflect.KProperty @OptIn(UnsafeAccessors::class) class ComponentOrDefaultAccessor( + override val originalAccessor: Accessor?, + override val queriedEntity: QueriedEntity, val id: ComponentId, val default: () -> T, ) : ReadOnlyAccessor { private var cachedIndex = -1 private var cachedArchetype: Archetype? = null - override fun getValue(thisRef: Pointer, property: KProperty<*>): T { - val archetype = thisRef.archetype + override fun getValue(thisRef: Query, property: KProperty<*>): T { + val archetype = queriedEntity.archetype if (archetype !== cachedArchetype) { cachedArchetype = archetype cachedIndex = archetype.indexOf(id) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/MappedAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/MappedAccessor.kt new file mode 100644 index 000000000..f5d0bd64f --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/MappedAccessor.kt @@ -0,0 +1,19 @@ +package com.mineinabyss.geary.systems.accessors.type + +import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.systems.accessors.AccessorOperations +import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor +import com.mineinabyss.geary.systems.query.QueriedEntity +import com.mineinabyss.geary.systems.query.Query +import kotlin.reflect.KProperty + +class MappedAccessor( + override val originalAccessor: ReadOnlyAccessor, + val mapping: (T) -> U, +) : ReadOnlyAccessor { + override val queriedEntity: QueriedEntity = originalAccessor.queriedEntity + override fun getValue(thisRef: Query, property: KProperty<*>): U { + val value = originalAccessor.getValue(thisRef, property) + return mapping(value) + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/NonNullComponentAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/NonNullComponentAccessor.kt index 51a1c3dcd..bca6a8dba 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/NonNullComponentAccessor.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/NonNullComponentAccessor.kt @@ -1,22 +1,20 @@ package com.mineinabyss.geary.systems.accessors.type import com.mineinabyss.geary.datatypes.ComponentId -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.systems.accessors.Pointer +import com.mineinabyss.geary.systems.accessors.Accessor +import com.mineinabyss.geary.systems.query.QueriedEntity +import com.mineinabyss.geary.systems.query.Query -@OptIn(UnsafeAccessors::class) class NonNullComponentAccessor( + cacheArchetypeInfo: Boolean, + originalAccessor: Accessor?, + entity: QueriedEntity, id: ComponentId, -) : ComponentAccessor(id) { - override operator fun get(thisRef: Pointer): T = +) : ComponentAccessor(cacheArchetypeInfo, originalAccessor, entity, id) { + override fun get(thisRef: Query): T = get(thisRef, beforeRead = {}) - override operator fun set(thisRef: Pointer, value: T) { - set(thisRef, value, beforeWrite = { - if (cachedIndex == -1) { - thisRef.entity.set(value, id) - return - } - }) + override fun set(query: Query, value: T) { + set(query, value, beforeWrite = {}) } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsAccessor.kt index 3ece96668..7d6bce6b0 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsAccessor.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsAccessor.kt @@ -1,29 +1,34 @@ package com.mineinabyss.geary.systems.accessors.type +import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.Relation -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.engine.archetypes.Archetype -import com.mineinabyss.geary.systems.accessors.Pointer +import com.mineinabyss.geary.systems.accessors.Accessor +import com.mineinabyss.geary.systems.accessors.AccessorOperations import com.mineinabyss.geary.systems.accessors.FamilyMatching import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor +import com.mineinabyss.geary.systems.query.QueriedEntity +import com.mineinabyss.geary.systems.query.Query import kotlin.reflect.KProperty -@OptIn(UnsafeAccessors::class) class RelationsAccessor( + override val originalAccessor: Accessor?, + override val queriedEntity: QueriedEntity, val kind: ComponentId, val target: EntityId, ) : ReadOnlyAccessor>, FamilyMatching { - override val family: Family = family { hasRelation(kind, target) } + override val family = family { hasRelation(kind, target) } private var cachedRelations = emptyList() private var cachedArchetype: Archetype? = null - override fun getValue(thisRef: Pointer, property: KProperty<*>): List { - val archetype = thisRef.archetype + @OptIn(UnsafeAccessors::class) + override fun getValue(thisRef: Query, property: KProperty<*>): List { + val archetype = queriedEntity.archetype if (archetype != cachedArchetype) { cachedArchetype = archetype cachedRelations = archetype.getRelations(kind, target) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsWithDataAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsWithDataAccessor.kt index e352750a8..a4d50db77 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsWithDataAccessor.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RelationsWithDataAccessor.kt @@ -1,35 +1,40 @@ package com.mineinabyss.geary.systems.accessors.type +import com.mineinabyss.geary.annotations.optin.UnsafeAccessors import com.mineinabyss.geary.datatypes.ComponentId import com.mineinabyss.geary.datatypes.EntityId import com.mineinabyss.geary.datatypes.Relation -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.engine.archetypes.Archetype -import com.mineinabyss.geary.systems.accessors.Pointer -import com.mineinabyss.geary.systems.accessors.FamilyMatching -import com.mineinabyss.geary.systems.accessors.ReadOnlyAccessor -import com.mineinabyss.geary.systems.accessors.RelationWithData +import com.mineinabyss.geary.systems.accessors.* +import com.mineinabyss.geary.systems.query.QueriedEntity +import com.mineinabyss.geary.systems.query.Query import kotlin.reflect.KProperty @OptIn(UnsafeAccessors::class) class RelationsWithDataAccessor( + override val originalAccessor: Accessor?, + override val queriedEntity: QueriedEntity, val kind: ComponentId, val target: EntityId, ) : ReadOnlyAccessor>>, FamilyMatching { - override val family: Family = family { hasRelation(kind, target) } + override val family = family { hasRelation(kind, target) } private var cachedRelations = emptyList() private var cachedArchetype: Archetype? = null - override fun getValue(thisRef: Pointer, property: KProperty<*>): List> { - val archetype = thisRef.archetype + override fun getValue(thisRef: Query, property: KProperty<*>): List> { + val archetype = queriedEntity.archetype if (archetype != cachedArchetype) { cachedArchetype = archetype cachedRelations = archetype.getRelations(kind, target) } - return archetype.readRelationDataFor(thisRef.row, kind, target, cachedRelations) as List> + return archetype.readRelationDataFor( + queriedEntity.row, + kind, + target, + cachedRelations + ) as List> } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RemovableComponentAccessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RemovableComponentAccessor.kt deleted file mode 100644 index 0d2ce7d84..000000000 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/type/RemovableComponentAccessor.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.mineinabyss.geary.systems.accessors.type - -import com.mineinabyss.geary.datatypes.ComponentId -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.systems.accessors.Pointer - -@OptIn(UnsafeAccessors::class) -class RemovableComponentAccessor( - id: ComponentId, -) : ComponentAccessor(id) { - override fun get(thisRef: Pointer): T? = - get(thisRef, beforeRead = { - if (cachedIndex == -1) return null - }) - - override fun set(thisRef: Pointer, value: T?) = - set(thisRef, value, beforeWrite = { - if (cachedIndex == -1) { - if (value == null) return - else thisRef.entity.set(value, id) - return - } - if (value == null) { - thisRef.entity.remove(id) - return - } - }) -} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/DeferredSystemBuilder.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/DeferredSystemBuilder.kt new file mode 100644 index 000000000..7a108e126 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/DeferredSystemBuilder.kt @@ -0,0 +1,21 @@ +package com.mineinabyss.geary.systems.builders + +import com.mineinabyss.geary.datatypes.GearyEntity +import com.mineinabyss.geary.systems.System +import com.mineinabyss.geary.systems.TrackedSystem +import com.mineinabyss.geary.systems.query.CachedQueryRunner +import com.mineinabyss.geary.systems.query.Query +import com.mineinabyss.geary.systems.query.execOnFinish + +class DeferredSystemBuilder( + val systemBuilder: SystemBuilder, + val mapping: CachedQueryRunner.() -> List> +) { + inline fun onFinish(crossinline run: (data: R, entity: GearyEntity) -> Unit): TrackedSystem<*> { + val onTick: CachedQueryRunner.() -> Unit = { + mapping().execOnFinish(run) + } + val system = System(systemBuilder.query, onTick, systemBuilder.interval) + return systemBuilder.pipeline.addSystem(system) + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/GlobalFunctions.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/GlobalFunctions.kt new file mode 100644 index 000000000..d7f31ed10 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/GlobalFunctions.kt @@ -0,0 +1,26 @@ +package com.mineinabyss.geary.systems.builders + +import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.systems.query.CachedQueryRunner +import com.mineinabyss.geary.systems.query.ListenerQuery +import com.mineinabyss.geary.systems.query.Query + + +fun GearyModule.cachedQuery( + query: T, +): CachedQueryRunner { + return queryManager.trackQuery(query) +} + +fun GearyModule.listener( + query: T +): ListenerBuilder { + return ListenerBuilder(query, pipeline) +} + + +fun GearyModule.system( + query: T +): SystemBuilder { + return SystemBuilder(query, pipeline) +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/ListenerBuilder.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/ListenerBuilder.kt new file mode 100644 index 000000000..760d14eec --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/ListenerBuilder.kt @@ -0,0 +1,42 @@ +package com.mineinabyss.geary.systems.builders + +import com.mineinabyss.geary.components.RequestCheck +import com.mineinabyss.geary.components.events.FailedCheck +import com.mineinabyss.geary.datatypes.family.family +import com.mineinabyss.geary.engine.Pipeline +import com.mineinabyss.geary.modules.GearyModule +import com.mineinabyss.geary.systems.Listener +import com.mineinabyss.geary.systems.query.ListenerQuery + +class ListenerBuilder( + val query: T, + val pipeline: Pipeline, +) { + fun exec(handle: T.() -> Unit): Listener<*> { + query.initialize() + val listener = Listener( + query, + query.buildFamilies(), + handle + ) + return pipeline.addListener(listener) + } + + fun check(check: T.() -> Boolean): Listener<*> { + query.initialize() + val families = query.buildFamilies() + val listener = Listener( + query, + families.copy(event = family { + has() + add(families.event) + }), + ) { + if (!check()) event.entity.apply { + remove() + add() + } + } + return pipeline.addListener(listener) + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/SystemBuilder.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/SystemBuilder.kt new file mode 100644 index 000000000..df1e18dd4 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/builders/SystemBuilder.kt @@ -0,0 +1,37 @@ +package com.mineinabyss.geary.systems.builders + +import com.mineinabyss.geary.engine.Pipeline +import com.mineinabyss.geary.systems.System +import com.mineinabyss.geary.systems.TrackedSystem +import com.mineinabyss.geary.systems.query.CachedQueryRunner +import com.mineinabyss.geary.systems.query.Query +import kotlin.time.Duration + +class SystemBuilder( + val query: T, + val pipeline: Pipeline, + val interval: Duration? = null +) { + fun every(interval: Duration): SystemBuilder { + return SystemBuilder(query, pipeline, interval) + } + + inline fun exec(crossinline run: T.() -> Unit): TrackedSystem<*> { + val onTick: CachedQueryRunner.() -> Unit = { forEach(run) } + val system = System(query, onTick, interval) + return pipeline.addSystem(system) + } + + inline fun defer(crossinline run: T.() -> R): DeferredSystemBuilder { + val onTick: CachedQueryRunner.() -> List> = { + mapWithEntity { run() } + } + val system = DeferredSystemBuilder(this, onTick) + return system + } + + fun execOnAll(run: CachedQueryRunner.() -> Unit): TrackedSystem<*> { + val system = System(query, run, interval) + return pipeline.addSystem(system) + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/CachedQueryRunner.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/CachedQueryRunner.kt new file mode 100644 index 000000000..ef6013107 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/CachedQueryRunner.kt @@ -0,0 +1,73 @@ +package com.mineinabyss.geary.systems.query + +import com.mineinabyss.geary.annotations.optin.UnsafeAccessors +import com.mineinabyss.geary.datatypes.GearyEntity +import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.helpers.fastForEach + +class CachedQueryRunner internal constructor(val query: T) { + val matchedArchetypes: MutableList = mutableListOf() + val family = query.buildFamily() + val cachingAccessors = query.cachingAccessors.toTypedArray() + + /** + * Quickly iterates over all matched entities, running [run] for each. + * + * Use [apply] on the query to use its accessors. + * */ + inline fun forEach(crossinline run: T.() -> Unit) { + val matched = matchedArchetypes + var n = 0 + val size = matched.size // Get size ahead of time to avoid rerunning on entities that end up in new archetypes + val accessors = cachingAccessors + while (n < size) { + val archetype = matched[n] + archetype.isIterating = true + + // We disallow entity archetype modifications while iterating, but allow creating new entities. + // These will always end up at the end of the archetype list so we just don't iterate over them. + val upTo = archetype.size + var row = 0 + query.originalArchetype = archetype + accessors.fastForEach { it.updateCache(archetype) } + while (row < upTo) { + query.originalRow = row + run(query) + row++ + } + archetype.isIterating = false + n++ + } + } + + inline fun map(crossinline run: T.() -> R): List { + val deferred = mutableListOf() + forEach { deferred.add(run()) } + return deferred + } + + data class Deferred( + val data: R, + val entity: GearyEntity + ) + + @OptIn(UnsafeAccessors::class) + inline fun mapWithEntity(crossinline run: T.() -> R): List> { + val deferred = mutableListOf>() + forEach { + deferred.add(Deferred(run(), unsafeEntity)) + } + return deferred + } + + @OptIn(UnsafeAccessors::class) + fun entities(): List { + val entities = mutableListOf() + forEach { entities.add(unsafeEntity) } + return entities + } +} + +inline fun List>.execOnFinish(run: (data: R, entity: GearyEntity) -> Unit) { + fastForEach { run(it.data, it.entity) } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/ListenerQuery.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/ListenerQuery.kt new file mode 100644 index 000000000..156691c84 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/ListenerQuery.kt @@ -0,0 +1,69 @@ +package com.mineinabyss.geary.systems.query + +import com.mineinabyss.geary.annotations.optin.UnsafeAccessors +import com.mineinabyss.geary.datatypes.ComponentId +import com.mineinabyss.geary.datatypes.family.Family +import com.mineinabyss.geary.datatypes.family.MutableFamily +import com.mineinabyss.geary.systems.accessors.Accessor +import com.mineinabyss.geary.systems.accessors.FamilyMatching +import kotlin.reflect.KProperty + +abstract class ListenerQuery : Query() { + override val cacheAccessors: Boolean = false + val event: EventQueriedEntity = EventQueriedEntity() + val source: QueriedEntity = QueriedEntity(false) + + data class Families( + val event: Family.Selector.And, + val target: Family.Selector.And, + val source: Family.Selector.And, + ) + + fun buildFamilies(): Families = Families( + event = event.buildFamily(), + target = this.buildFamily(), + source = source.buildFamily(), + ) + + @OptIn(UnsafeAccessors::class) + val QueriedEntity.entity get() = unsafeEntity + + internal fun getAccessorsFor(vararg props: KProperty<*>): Array { + return props.mapNotNull { this@ListenerQuery.props[it.name] }.toTypedArray() + } + protected fun EventQueriedEntity.anySet(vararg props: KProperty<*>) = anySet(*getAccessorsFor(*props)) + + protected fun EventQueriedEntity.anyAdded(vararg props: KProperty<*>) = anyAdded(*getAccessorsFor(*props)) + + protected fun EventQueriedEntity.anyFirstSet(vararg props: KProperty<*>) = anyFirstSet(*getAccessorsFor(*props)) + + protected inline fun EventQueriedEntity.forEachAccessorComponent( + props: Collection, + crossinline run: MutableFamily.Selector.And.(ComponentId) -> Unit + ) { + invoke { + this@ListenerQuery.accessors.intersect(props.toSet()) + .asSequence() + .filterIsInstance() + .mapNotNull { it.family } + .flatMap { it.components } + .forEach { run(it) } + } + } + + /** Fires when an entity has a component of type [T] set or updated. */ + protected fun EventQueriedEntity.anySet(vararg props: Accessor) { + forEachAccessorComponent(props.toSet()) { onSet(it) } + } + + /** Fires when an entity has a component of type [T] added, updates are not considered since no data changes. */ + protected fun EventQueriedEntity.anyAdded(vararg props: Accessor) { + forEachAccessorComponent(props.toSet()) { onAdd(it) } + } + + /** Fires when an entity has a component of type [T] set, only if it was not set before. */ + protected fun EventQueriedEntity.anyFirstSet(vararg props: Accessor) { + forEachAccessorComponent(props.toSet()) { onFirstSet(it) } + } + +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt new file mode 100644 index 000000000..06f63b7c3 --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/QueriedEntity.kt @@ -0,0 +1,63 @@ +package com.mineinabyss.geary.systems.query + +import com.mineinabyss.geary.annotations.optin.UnsafeAccessors +import com.mineinabyss.geary.datatypes.Entity +import com.mineinabyss.geary.datatypes.Record +import com.mineinabyss.geary.datatypes.family.Family +import com.mineinabyss.geary.datatypes.family.family +import com.mineinabyss.geary.engine.archetypes.Archetype +import com.mineinabyss.geary.modules.archetypes +import com.mineinabyss.geary.systems.accessors.Accessor +import com.mineinabyss.geary.systems.accessors.AccessorOperations +import com.mineinabyss.geary.systems.accessors.FamilyMatching +import com.mineinabyss.geary.systems.accessors.type.ComponentAccessor + +open class EventQueriedEntity : QueriedEntity(cacheAccessors = false) +open class QueriedEntity( + override val cacheAccessors: Boolean +) : AccessorOperations() { + + internal val extraFamilies: MutableList = mutableListOf() + + internal val props: MutableMap = mutableMapOf() + + @PublishedApi + internal val accessors: MutableSet = mutableSetOf() + + @PublishedApi + internal val cachingAccessors: MutableSet> = mutableSetOf() + + fun buildFamily(): Family.Selector.And = family { + accessors + .filterIsInstance() + .mapNotNull { it.family } + .union(extraFamilies) + .forEach(::add) + } + + @PublishedApi + internal var originalArchetype = archetypes.archetypeProvider.rootArchetype + + @PublishedApi + internal var originalRow = 0 + + @UnsafeAccessors + val archetype: Archetype get() = if (delegated) delegate!!.archetype else originalArchetype + val row: Int get() = if (delegated) delegate!!.row else originalRow + + private var delegate: Record? = null + + @PublishedApi + internal var delegated = false + + @UnsafeAccessors + val unsafeEntity: Entity + get() { + val entity = archetype.getEntity(row) + if (!delegated) { + delegate = archetypes.records[entity] + } + delegated = true + return entity + } +} diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/Query.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/Query.kt index 00972c623..05501edc1 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/Query.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/query/Query.kt @@ -1,76 +1,27 @@ package com.mineinabyss.geary.systems.query -import com.mineinabyss.geary.datatypes.GearyEntity -import com.mineinabyss.geary.datatypes.family.Family -import com.mineinabyss.geary.engine.archetypes.Archetype -import com.mineinabyss.geary.helpers.fastForEachWithIndex -import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.accessors.AccessorHolder -import com.mineinabyss.geary.systems.accessors.FamilyMatching -import com.mineinabyss.geary.systems.accessors.Pointer -import kotlin.properties.ReadOnlyProperty +import com.mineinabyss.geary.systems.accessors.Accessor +import com.mineinabyss.geary.systems.accessors.type.ComponentAccessor import kotlin.reflect.KProperty -/**com.mineinabyss.geary.ecs.engine.iteration.accessors - * @property matchedArchetypes A set of archetypes which have been matched to this query. - */ -abstract class Query : AccessorHolder() { - @PublishedApi - internal val matchedArchetypes: MutableSet = mutableSetOf() - - @PublishedApi - internal var registered: Boolean = false - - val matchedEntities - get(): List { - registerIfNotRegistered() - return matchedArchetypes.flatMap { it.entities } - } - - fun registerIfNotRegistered() { - if (!registered) { - geary.queryManager.trackQuery(this) - } - } - - inline fun toList(crossinline map: (Pointer) -> T): List { - val list = mutableListOf() - forEach { list.add(map(it)) } - return list - } - - /** - * Quickly iterates over all matched entities, running [run] for each. - * - * Use [apply] on the query to use its accessors. - * */ - inline fun forEach(crossinline run: (Pointer) -> Unit) { - registerIfNotRegistered() - val matched = matchedArchetypes.toList() - matched.fastForEachWithIndex { i, archetype -> - archetype.isIterating = true - val upTo = archetype.size - // TODO upTo isn't perfect for cases where entities may be added or removed in the same iteration - for (entityIndex in 0 until upTo) { - run(Pointer(archetype, entityIndex)) - } - archetype.isIterating = false - } - } - - operator fun Family.provideDelegate(thisRef: GearyQuery, property: KProperty<*>): ReadOnlyProperty { - mutableFamily.add(this) - return ReadOnlyProperty { thisRef, prop -> - this@provideDelegate - } - } - +abstract class Query : QueriedEntity(cacheAccessors = true) { /** Automatically matches families for any accessor that's supposed to match a family. */ - operator fun T.provideDelegate( + operator fun T.provideDelegate( thisRef: Any, prop: KProperty<*> ): T { - family?.let { mutableFamily.add(it) } + queriedEntity.props[prop.name] = this return this } + + protected open fun ensure() {} + + internal fun initialize() { + ensure() + } + + // Optional helpers for avoiding delegates in accessors + + inline operator fun ComponentAccessor.invoke(): T = get(this@Query) + inline fun ComponentAccessor.set(value: T) = set(this@Query, value) } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/CheckingListenerTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/CheckingListenerTest.kt index ff61b1385..3e42247fa 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/CheckingListenerTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/CheckingListenerTest.kt @@ -1,30 +1,23 @@ package com.mineinabyss.geary.events -import com.mineinabyss.geary.datatypes.Records import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.tests.GearyTest import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery import io.kotest.matchers.shouldBe import kotlin.test.Test class CheckingListenerTest : GearyTest() { class MyEvent() - class MyListener : CheckingListener() { - var called = 0 - - val Records.data by get().on(target) - - override fun Pointers.check(): Boolean { - return data > 10 - } - } + fun myListener() = geary.listener(object : ListenerQuery() { + val data by get() + }).check { data > 10 } @Test fun `simple set listener`() { - val listener = MyListener() - geary.pipeline.addSystem(listener) + myListener() val entityFail = entity { set(1) diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/ComponentAddEventTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/ComponentAddEventTest.kt index 1952bc690..5c0db3ad4 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/ComponentAddEventTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/ComponentAddEventTest.kt @@ -1,34 +1,27 @@ package com.mineinabyss.geary.events -import com.mineinabyss.geary.datatypes.Records import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.getArchetype import com.mineinabyss.geary.helpers.tests.GearyTest import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test internal class ComponentAddEventTest : GearyTest() { var inc = 0 - //TODO write test for all methods of checking for added - inner class OnStringAdd : Listener() { - // All three get added - val Records.string by get().whenSetOnTarget() - val Records.int by get().whenSetOnTarget() - val Records.double by get().whenSetOnTarget() - - override fun Pointers.handle() { - inc++ - } - } + fun onStringAdd() = geary.listener(object : ListenerQuery() { + val string by get() + val int by get() + val double by get() + override fun ensure() = event.anySet(::string, ::int, ::double) + }).exec { inc++ } @Test fun componentAddEvent() { - val listener = OnStringAdd() - geary.pipeline.addSystem(listener) + val listener = onStringAdd() entity { fun addedListeners() = type.getArchetype().targetListeners.count { it === listener } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SimpleComponentAddListenerTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SimpleComponentAddListenerTest.kt index 8db93fbfb..f9645237e 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SimpleComponentAddListenerTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SimpleComponentAddListenerTest.kt @@ -1,35 +1,29 @@ package com.mineinabyss.geary.events -import com.mineinabyss.geary.datatypes.Records import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.tests.GearyTest import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery import io.kotest.matchers.shouldBe import kotlin.test.Test class SimpleComponentAddListenerTest : GearyTest() { - class MyListener : Listener() { - var called = 0 - - val Records.data by get().whenSetOnTarget() - - override fun Pointers.handle() { - called += 1 - } - } + var called = 0 + fun myListener() = geary.listener(object : ListenerQuery() { + val data by get() + override fun ensure() = event.anySet(::data) + }).exec { called += 1 } @Test fun `simple event listener`() { - val listener = MyListener() - geary.pipeline.addSystem(listener) + myListener() val entity = entity() - listener.called shouldBe 0 + called shouldBe 0 entity.set(1.0) - listener.called shouldBe 0 + called shouldBe 0 entity.set(1) - listener.called shouldBe 1 + called shouldBe 1 } } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SimpleEventTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SimpleEventTest.kt index c2b3465d6..a9ccbc9cd 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SimpleEventTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SimpleEventTest.kt @@ -1,43 +1,32 @@ package com.mineinabyss.geary.events -import com.mineinabyss.geary.datatypes.Records import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.tests.GearyTest import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery import io.kotest.matchers.shouldBe import kotlin.test.Test class SimpleEventTest : GearyTest() { class MyEvent() - class MyListener : Listener() { - var called = 0 + var called = 0 - val Records.data by get().on(target) - val Records.event by get().on(event) + fun myListener() = geary.listener(object : ListenerQuery() { + val data by get() + val myEvent by event.get() + }).exec { called++ } - override fun Pointers.handle() { - called += 1 - } - } - - class SourceOnlyListener: Listener() { - var called = 0 - val Records.event by get().on(event) - val Records.data by get().on(source) - - override fun Pointers.handle() { - called += 1 - } - } + fun sourceOnlyListener() = geary.listener(object : ListenerQuery() { + val data by source.get() + val myEvent by event.get() + }).exec { called++ } @Test fun `simple set listener`() { - val listener = MyListener() - geary.pipeline.addSystem(listener) - + called = 0 + val listener = myListener() val entity = entity { set(1) } @@ -45,19 +34,19 @@ class SimpleEventTest : GearyTest() { set(MyEvent()) } - listener.called shouldBe 0 + called shouldBe 0 entity.callEvent(event) - listener.called shouldBe 1 + called shouldBe 1 entity.callEvent(entity()) - listener.called shouldBe 1 + called shouldBe 1 entity().callEvent(event) - listener.called shouldBe 1 + called shouldBe 1 } @Test fun `source only simple set listener`() { - val listener = SourceOnlyListener() - geary.pipeline.addSystem(listener) + called = 0 + val listener = sourceOnlyListener() val target = entity() val source = entity { @@ -67,8 +56,8 @@ class SimpleEventTest : GearyTest() { set(MyEvent()) } - listener.called shouldBe 0 + called shouldBe 0 target.callEvent(event, source = source) - listener.called shouldBe 1 + called shouldBe 1 } } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SourceTargetEventTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SourceTargetEventTest.kt index 2acae2260..f25918c27 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SourceTargetEventTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/events/SourceTargetEventTest.kt @@ -1,12 +1,10 @@ package com.mineinabyss.geary.events -import com.mineinabyss.geary.datatypes.Records -import com.mineinabyss.geary.datatypes.family.family import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.tests.GearyTest import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.Listener -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test @@ -15,22 +13,18 @@ class SourceTargetEventTest : GearyTest() { class Attack data class Health(val amount: Int) - inner class Interaction : Listener() { - val Records.strength by get().on(source) - var Records.health by get().on(target) - - init { - event.mutableFamily.add(family { has() }) - } - - override fun Pointers.handle() { - health = Health(health.amount - strength.amount) - } + fun interactionListener() = geary.listener(object : ListenerQuery() { + val strength by source.get() + var health by get() + override fun ensure() = event { has()} + }).exec { + health = Health(health.amount - strength.amount) } @Test fun interactions() { - geary.pipeline.addSystem(Interaction()) + resetEngine() + interactionListener() val source = entity { set(Strength(10)) } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/SimpleQueryTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/SimpleQueryTest.kt similarity index 58% rename from geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/SimpleQueryTest.kt rename to geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/SimpleQueryTest.kt index 5b917745e..e7d4941ec 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/SimpleQueryTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/queries/SimpleQueryTest.kt @@ -1,35 +1,33 @@ -package com.mineinabyss.geary.systems +package com.mineinabyss.geary.queries import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.systems.accessors.Pointer +import com.mineinabyss.geary.modules.geary import com.mineinabyss.geary.systems.query.Query import io.kotest.matchers.shouldBe import kotlin.test.Test class SimpleQueryTest : GearyTest() { class MyQuery : Query() { - val Pointer.int by get() + val int by get() } @Test fun `simple query`() { + val query = geary.queryManager.trackQuery(MyQuery()) repeat(10) { entity { - set(1) + set(it) } entity { set("Not this!") } } - var count = 0 - MyQuery().run { - forEach { - it.int shouldBe 1 - count++ - } + val nums = mutableListOf() + query.forEach { + nums.add(int) } - count shouldBe 10 + nums.sorted() shouldBe (0..9).toList() } } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/FamilyMatchingTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/FamilyMatchingTest.kt index 833a84865..f5fa7c60e 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/FamilyMatchingTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/FamilyMatchingTest.kt @@ -1,13 +1,16 @@ package com.mineinabyss.geary.systems +import com.mineinabyss.geary.datatypes.EntityType import com.mineinabyss.geary.datatypes.HOLDS_DATA -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors +import com.mineinabyss.geary.datatypes.family.Family import com.mineinabyss.geary.helpers.componentId import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.helpers.getArchetype import com.mineinabyss.geary.helpers.tests.GearyTest import com.mineinabyss.geary.modules.archetypes import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.accessors.Pointer +import com.mineinabyss.geary.systems.builders.system +import com.mineinabyss.geary.systems.query.Query import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.collections.shouldContainAll import io.kotest.matchers.shouldBe @@ -17,37 +20,20 @@ class FamilyMatchingTest : GearyTest() { val stringId = componentId() or HOLDS_DATA val intId = componentId() - val system = object : RepeatingSystem() { - val Pointer.string by get() - - // val GearyRecord.int by family { has() } - init { - mutableFamily.has() - } - - @OptIn(UnsafeAccessors::class) - override fun Pointer.tick() { - string shouldBe entity.get() - entity.has() shouldBe true - } + val system = geary.system(object : Query() { + val string by get() + override fun ensure() = this { has() } + }).defer { string }.onFinish { data, entity -> + data shouldBe entity.get() + entity.has() shouldBe true } val root = archetypes.archetypeProvider.rootArchetype val correctArchetype = root + stringId + intId - init { - geary.queryManager.trackQuery(system) - } - - @Test - fun `family type is correct`() { - // TODO families can are wrapped by accessors now, so components won't be directly on it -// EntityType(system.family.components).getArchetype() shouldBe root + stringId - } - @Test fun `archetypes have been matched correctly`() { - system.matchedArchetypes shouldContain correctArchetype + system.runner.matchedArchetypes shouldContain correctArchetype } @Test @@ -60,11 +46,11 @@ class FamilyMatchingTest : GearyTest() { set("Test") set(1) } - geary.queryManager.getEntitiesMatching(system.family).shouldContainAll(entity, entity2) + geary.queryManager.getEntitiesMatching(system.runner.family).shouldContainAll(entity, entity2) } @Test fun `accessors in system correctly read data`() { - system.tickAll() + system.tick() } } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/QueryManagerTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/QueryManagerTest.kt index 5b13730fb..a188d78d1 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/QueryManagerTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/QueryManagerTest.kt @@ -1,35 +1,31 @@ package com.mineinabyss.geary.systems -import com.mineinabyss.geary.datatypes.Records import com.mineinabyss.geary.helpers.contains import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.tests.GearyTest import com.mineinabyss.geary.modules.archetypes import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.accessors.Pointers +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test internal class QueryManagerTest : GearyTest() { private class TestComponent - private class EventListener : Listener() { - var ran = 0 - private val Records.testComponent by get().on(target) - override fun Pointers.handle() { - ran++ - } - } + var ran = 0 + fun myListener() = geary.listener(object : ListenerQuery() { + val testComponent by get() + }).exec { ran++ } @Test fun `empty event handler`() { - val listener = EventListener() - geary.pipeline.addSystem(listener) - (archetypes.archetypeProvider.rootArchetype.type in listener.event.family) shouldBe true + val listener = myListener() + (archetypes.archetypeProvider.rootArchetype.type in listener.families.event) shouldBe true entity { set(TestComponent()) }.callEvent() - listener.ran shouldBe 1 + ran shouldBe 1 } } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/RelationMatchingSystemTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/RelationMatchingSystemTest.kt index 907131a1a..7b13196ba 100644 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/RelationMatchingSystemTest.kt +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/RelationMatchingSystemTest.kt @@ -2,13 +2,13 @@ package com.mineinabyss.geary.systems import com.mineinabyss.geary.components.relations.InstanceOf import com.mineinabyss.geary.components.relations.Persists -import com.mineinabyss.geary.datatypes.RecordPointer import com.mineinabyss.geary.helpers.contains import com.mineinabyss.geary.helpers.entity import com.mineinabyss.geary.helpers.getArchetype import com.mineinabyss.geary.helpers.tests.GearyTest import com.mineinabyss.geary.modules.geary -import com.mineinabyss.geary.systems.accessors.Pointer +import com.mineinabyss.geary.systems.builders.system +import com.mineinabyss.geary.systems.query.Query import io.kotest.inspectors.forAll import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.collections.shouldNotContain @@ -21,18 +21,14 @@ import kotlin.test.Test class RelationMatchingSystemTest : GearyTest() { @Test fun relations() { + var ran = 0 resetEngine() - val system = object: RepeatingSystem() { - val Pointer.persists by getRelationsWithData() - - var ran = 0 - - override fun Pointer.tick() { - ran++ - persists.forAll { it.data.shouldBeInstanceOf() } - } + val system = geary.system(object : Query() { + val persists by getRelationsWithData() + }).exec { + ran++ + persists.forAll { it.data.shouldBeInstanceOf() } } - geary.pipeline.addSystem(system) val entity = entity { addRelation() @@ -46,12 +42,12 @@ class RelationMatchingSystemTest : GearyTest() { setRelation(Persists()) add() } - (entity3.type in system.family) shouldBe true - system.matchedArchetypes.shouldNotContainAll(entity.type.getArchetype(), entity2.type.getArchetype()) - system.matchedArchetypes.shouldContain(entity3.type.getArchetype()) + (entity3.type in system.runner.family) shouldBe true + system.runner.matchedArchetypes.shouldNotContainAll(entity.type.getArchetype(), entity2.type.getArchetype()) + system.runner.matchedArchetypes.shouldContain(entity3.type.getArchetype()) - geary.engine.tick(0) - system.ran shouldBe 1 + system.tick() + ran shouldBe 1 } @Test @@ -60,20 +56,17 @@ class RelationMatchingSystemTest : GearyTest() { var ran = 0 var persistsCount = 0 var instanceOfCount = 0 - val system = object : RepeatingSystem() { - val RecordPointer.persists by getRelationsWithData() - val RecordPointer.instanceOf by getRelationsWithData() - - override fun RecordPointer.tick() { - ran++ - persistsCount += persists.size - instanceOfCount += instanceOf.size - persists.forAll { it.data.shouldBeInstanceOf() } - persists.forAll { it.targetData shouldNotBe null } - instanceOf.forAll { it.data shouldBe null } - } + val system = geary.system(object : Query() { + val persists by getRelationsWithData() + val instanceOf by getRelationsWithData() + }).exec { + ran++ + persistsCount += persists.size + instanceOfCount += instanceOf.size + persists.forAll { it.data.shouldBeInstanceOf() } + persists.forAll { it.targetData shouldNotBe null } + instanceOf.forAll { it.data shouldBe null } } - geary.pipeline.addSystem(system) entity { setRelation(Persists()) // Yes @@ -93,7 +86,7 @@ class RelationMatchingSystemTest : GearyTest() { addRelation() // No } - geary.engine.tick(0) + system.tick() // Only two of the Persists relations are valid, times both InstanceOf are valid ran shouldBe 1 @@ -104,14 +97,7 @@ class RelationMatchingSystemTest : GearyTest() { @Test fun relationsWithData() { resetEngine() - val system = object : RepeatingSystem() { - val RecordPointer.withData by getRelationsWithData() - override fun RecordPointer.tick() { - withData.forAll { it.data shouldBe Persists() } - withData.forAll { it.targetData shouldBe "Test" } - } - } val entity = entity { setRelation(Persists()) @@ -123,10 +109,15 @@ class RelationMatchingSystemTest : GearyTest() { set("Test") } - geary.queryManager.trackQuery(system) + val system = geary.system(object : Query() { + val withData by getRelationsWithData() + }).exec { + withData.forAll { it.data shouldBe Persists() } + withData.forAll { it.targetData shouldBe "Test" } + } - system.matchedArchetypes.shouldNotContain(entity.type.getArchetype()) - system.matchedArchetypes.shouldContain(entityWithData.type.getArchetype()) + system.runner.matchedArchetypes.shouldNotContain(entity.type.getArchetype()) + system.runner.matchedArchetypes.shouldContain(entityWithData.type.getArchetype()) geary.engine.tick(0) } diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/AccessorHolderTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/AccessorHolderTest.kt deleted file mode 100644 index 17d0e7632..000000000 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/AccessorHolderTest.kt +++ /dev/null @@ -1,29 +0,0 @@ -package com.mineinabyss.geary.systems.accessors - -import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.systems.query.GearyQuery -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.jupiter.api.Test - -internal class AccessorHolderTest : GearyTest() { - class FancyQuery : GearyQuery() { - val Pointer.default by get().orDefault { "empty!" } - val Pointer.mapped by get().map { it.toString() } - } - - @ExperimentalCoroutinesApi - @Test - fun fancyAccessors() { - val entity = entity() - entity.set(1) -// FancyQuery.firstOrNull { it.entity == entity } shouldBe null -// entity.set(1) -// FancyQuery.run { -// first { it.entity == entity }.apply { -// default shouldBe "empty!" -// mapped shouldBe "1" -// } -// } - } -} diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/ListenerLiveEntityModificationTests.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/ListenerLiveEntityModificationTests.kt new file mode 100644 index 000000000..c06f82259 --- /dev/null +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/ListenerLiveEntityModificationTests.kt @@ -0,0 +1,81 @@ +package com.mineinabyss.geary.systems.accessors + +import com.mineinabyss.geary.annotations.optin.UnsafeAccessors +import com.mineinabyss.geary.helpers.Comp1 +import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.systems.builders.cachedQuery +import com.mineinabyss.geary.systems.builders.listener +import com.mineinabyss.geary.systems.query.ListenerQuery +import com.mineinabyss.geary.systems.query.Query +import io.kotest.matchers.shouldBe +import kotlin.test.Test + +class ListenerLiveEntityModificationTests : GearyTest() { + private fun registerQuery() = geary.cachedQuery(object : Query() { + var data by get() + }) + + @Test + fun `should allow data modify via accessor`() { + resetEngine() + entity { + set(Comp1(1)) + } + + var count = 0 + registerQuery().forEach { + data shouldBe Comp1(1) + data = Comp1(10) + data shouldBe Comp1(10) + count++ + } + count shouldBe 1 + } + + @Test + fun `should allow data modify when entity archetype changed by SET`() { + resetEngine() + var count = 0 + + geary.listener(object : ListenerQuery() { + var data by get() + }).exec { + data shouldBe Comp1(1) + data = Comp1(10) + entity.set("Other comp") + entity.add() + data shouldBe Comp1(10) + count++ + } + + entity { + set(Comp1(1)) + }.callEvent() + count shouldBe 1 + } + + @OptIn(UnsafeAccessors::class) + @Test + fun `should allow data modify when entity archetype changed by REMOVE`() { + resetEngine() + var count = 0 + + geary.listener(object : ListenerQuery() { + var data by get() + }).exec { + data shouldBe Comp1(1) + entity.remove() + data = Comp1(10) + data shouldBe Comp1(10) + entity.set("Other comp") + data shouldBe Comp1(10) + count++ + } + entity { + set(Comp1(1)) + }.callEvent() + count shouldBe 1 + } +} diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/MappedAccessorTests.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/MappedAccessorTests.kt new file mode 100644 index 000000000..6d4489e82 --- /dev/null +++ b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/MappedAccessorTests.kt @@ -0,0 +1,45 @@ +package com.mineinabyss.geary.systems.accessors + +import com.mineinabyss.geary.helpers.entity +import com.mineinabyss.geary.helpers.tests.GearyTest +import com.mineinabyss.geary.modules.geary +import com.mineinabyss.geary.systems.builders.cachedQuery +import com.mineinabyss.geary.systems.query.Query +import io.kotest.matchers.collections.shouldContainExactly +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +internal class MappedAccessorTests : GearyTest() { + private class Marker + + private fun mappedQuery() = geary.cachedQuery(object : Query() { + val mapped by get().map { it.toString() } + }) + + private fun defaultingQuery() = geary.cachedQuery(object : Query() { + val default by get().orDefault { "empty!" } + override fun ensure() = this { has() } + }) + + @Test + fun `should correctly get mapped accessors`() { + entity { + set(1) + } + mappedQuery().forEach { + mapped shouldBe "1" + } + } + + @Test + fun `should correctly get default accessors`() { + entity { + set("Hello") + add() + } + entity { + add() + } + defaultingQuery().map { default }.shouldContainExactly("Hello", "empty!") + } +} diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/RemovableAccessorTest.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/RemovableAccessorTest.kt deleted file mode 100644 index f727150ed..000000000 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/RemovableAccessorTest.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.mineinabyss.geary.systems.accessors - -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.helpers.Comp1 -import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.systems.query.Query -import io.kotest.matchers.shouldBe -import kotlin.test.Test - -class RemovableAccessorTest: GearyTest() { - class MyQueryRemovable : Query() { - var Pointer.data by get().removable() - } - - @OptIn(UnsafeAccessors::class) - @Test - fun `should allow removing component via removable accessor`() { - resetEngine() - entity { - set(Comp1(1)) - } - var count = 0 - - MyQueryRemovable().run { - forEach { - it.data shouldBe Comp1(1) - it.data = null - it.data shouldBe null - it.entity.has() shouldBe false - count++ - } - } - count shouldBe 1 - } -} diff --git a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/UnsafeQueryAccessTests.kt b/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/UnsafeQueryAccessTests.kt deleted file mode 100644 index d73a70462..000000000 --- a/geary-core/src/jvmTest/kotlin/com/mineinabyss/geary/systems/accessors/UnsafeQueryAccessTests.kt +++ /dev/null @@ -1,81 +0,0 @@ -package com.mineinabyss.geary.systems.accessors - -import com.mineinabyss.geary.annotations.optin.UnsafeAccessors -import com.mineinabyss.geary.helpers.Comp1 -import com.mineinabyss.geary.helpers.entity -import com.mineinabyss.geary.helpers.tests.GearyTest -import com.mineinabyss.geary.systems.query.Query -import io.kotest.matchers.shouldBe -import kotlin.test.Test - -class UnsafeQueryAccessTests : GearyTest() { - class MyQuery : Query() { - var Pointer.data by get() - } - - - @Test - fun `should allow data modify via accessor`() { - resetEngine() - entity { - set(Comp1(1)) - } - - var count = 0 - MyQuery().run { - forEach { - it.data shouldBe Comp1(1) - it.data = Comp1(10) - it.data shouldBe Comp1(10) - count++ - } - } - count shouldBe 1 - } - - @OptIn(UnsafeAccessors::class) - @Test - fun `should allow data modify when entity changed archetype by setting`() { - resetEngine() - entity { - set(Comp1(1)) - } - - var count = 0 - MyQuery().run { - forEach { - it.data shouldBe Comp1(1) - it.data = Comp1(10) - it.entity.set("Other comp") - it.entity.add() - it.data shouldBe Comp1(10) - count++ - } - } - count shouldBe 1 - } - - @OptIn(UnsafeAccessors::class) - @Test - fun `should allow data modify when entity changed archetype by removing`() { - resetEngine() - entity { - set(Comp1(1)) - } - - var count = 0 - MyQuery().run { - forEach { - it.data shouldBe Comp1(1) - it.entity.remove() - it.data = Comp1(10) - it.data shouldBe Comp1(10) - it.entity.set("Other comp") - it.data shouldBe Comp1(10) - count++ - } - } - count shouldBe 1 - } - -} diff --git a/gradle.properties b/gradle.properties index ca379dcb7..20824fd4f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ group=com.mineinabyss -version=0.25 +version=0.26 # Workaround for dokka builds failing on CI, see https://github.com/Kotlin/dokka/issues/1405 #org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m idofrontVersion=0.22.0