diff --git a/LICENSE b/LICENSE
index 0790b9c08..a0e2033f9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2020 Mine In Abyss
+Copyright (c) 2020 Danielle Voznyy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index b87f285f0..ae1ad9482 100644
--- a/README.md
+++ b/README.md
@@ -9,18 +9,7 @@
## Overview
-
- :warning: Notice: Looking for a new maintainer
-
-> This project was built from the ground up by myself, but its scope has finally caught up and I wish to move on and leave it in the hands of a bigger team that can get Geary to a state where others outside of Mine in Abyss can use it.\
-> \
-> If you're interested in building an ECS and find Geary's syntax interesting, I'm currently working on some major cleanup, and documenting usage + backend decisions that will hopefully make it easier to take over or build your own. If you just want a working engine (not using Kotlin), I recommend looking at [flecs](https://github.com/SanderMertens/flecs), otherwise browse through some established Java engines! \
-> \
-> \- Offz
-
-
-
-Geary is an Entity Component System (ECS) written in Kotlin. The engine design is inspired by [flecs](https://github.com/SanderMertens/flecs). It is currently NOT optimized for performance. We use Geary internally for our Minecraft plugins, see [geary-papermc](https://github.com/MineInAbyss/geary-papermc) for more info.
+Geary is an Entity Component System (ECS) written in Kotlin. The engine design is inspired by [flecs](https://github.com/SanderMertens/flecs). Core parts of the engine (ex. system iteration, entity creation) are quite optimized, with the main exception being our event system. We use Geary internally for our Minecraft plugins, see [geary-papermc](https://github.com/MineInAbyss/geary-papermc) for more info.
## Features
- Null safe component access
@@ -32,16 +21,17 @@ Geary is an Entity Component System (ECS) written in Kotlin. The engine design i
## Example
+A simple ssytem that iterates over all entities with a position and velocity, updating the position every engine tick.
```kotlin
data class Position(var x: Double, var y: Double)
data class Velocity(var x: Double, var y: Double)
class UpdatePositionSystem : TickingSystem(interval = 20.milliseconds) {
// Specify all components we want (Geary also supports branched AND/OR/NOT statements for selection)
- val TargetScope.position by get()
- val TargetScope.velocity by get()
+ val Pointer.position by get()
+ val Pointer.velocity by get()
- override fun TargetScope.tick() {
+ override fun Pointer.tick() {
// We can access our components like regular variables!
position.x += velocity.x
position.y += velocity.y
@@ -61,17 +51,13 @@ fun main() {
entity {
setAll(Position(0.0, 0.0), Velocity(1.0, 0.0))
}
-
- // Systems are queries!
- val positions: List = UpdatePositionSystem.run {
- filter { it.velocity != Velocity(0.0, 0.0) }
- .map { it.position }
- }
}
```
## Usage
+A WIP wiki can be found at [wiki.mineinabyss.com](https://wiki.mineinabyss.com/geary/)
+
### Gradle
```kotlin
repositories {
@@ -85,13 +71,10 @@ dependencies {
}
```
-### Wiki
-A WIP wiki can be found at [wiki.mineinabyss.com](https://wiki.mineinabyss.com/geary/)
-
## Roadmap
As the project matures, our primary goal is to make it useful to more people. Here are a handful of features we hope to achieve:
-- (Ongoing) Multiplatform support, with js, jvm, and native targets
-- Optimize key bottlenecks and benchmark the engine
+- Multiplatform support, with js, jvm, and native targets
+- Publish numbers for benchmarks and cover more parts of the engine with them
- Component data migrations
- Complex queries (including relations like parent/child)
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 f2f5ce5e4..f6b48be97 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
@@ -34,7 +34,7 @@ interface AutoScanner {
}
override fun AutoScanner.install() {
- geary.pipeline.intercept(GearyPhase.INIT_SYSTEMS) {
+ geary.pipeline.runOnOrAfter(GearyPhase.INIT_SYSTEMS) {
installSystems()
}
}
diff --git a/addons/geary-common-features/src/main/kotlin/com/mineinabyss/geary/game/systems/ExpiringComponentSystem.kt b/addons/geary-common-features/src/main/kotlin/com/mineinabyss/geary/game/systems/ExpiringComponentSystem.kt
index 965fcca7f..0443c647f 100644
--- a/addons/geary-common-features/src/main/kotlin/com/mineinabyss/geary/game/systems/ExpiringComponentSystem.kt
+++ b/addons/geary-common-features/src/main/kotlin/com/mineinabyss/geary/game/systems/ExpiringComponentSystem.kt
@@ -1,19 +1,24 @@
package com.mineinabyss.geary.game.systems
+import com.mineinabyss.geary.annotations.optin.UnsafeAccessors
import com.mineinabyss.geary.game.components.Expiry
import com.mineinabyss.geary.systems.RepeatingSystem
-import com.mineinabyss.geary.systems.accessors.TargetScope
+import com.mineinabyss.geary.systems.accessors.Pointer
+
/**
* Handles removing components when an [Expiry] relation exists with another component.
*/
class ExpiringComponentSystem : RepeatingSystem() {
- private val TargetScope.expiry by getRelations()
+ private val Pointer.expiry by getRelationsWithData()
- override fun TargetScope.tick() {
- if (expiry.data.timeOver()) {
- entity.remove(expiry.kind.id)
- entity.remove(expiry.relation.id)
+ @OptIn(UnsafeAccessors::class)
+ override fun Pointer.tick() {
+ expiry.forEach {
+ if (it.data.timeOver()) {
+ entity.remove(it.kind.id)
+ entity.remove(it.relation.id)
+ }
}
}
}
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 dbacabd38..6228599d1 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
@@ -33,7 +33,7 @@ interface Prefabs {
ParseRelationWithDataSystem(),
TrackPrefabsByKeySystem(),
)
- geary.pipeline.intercept(GearyPhase.INIT_ENTITIES) {
+ geary.pipeline.runOnOrAfter(GearyPhase.INIT_ENTITIES) {
loader.loadPrefabs()
}
}
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 897e63d90..2e248e500 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
@@ -1,43 +1,44 @@
package com.mineinabyss.geary.prefabs.configuration.systems
-import com.mineinabyss.geary.annotations.Handler
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.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.TargetScope
+import com.mineinabyss.geary.systems.accessors.Pointers
+
class ParseChildOnPrefab : Listener() {
- private val TargetScope.child by onSet().onTarget()
+ private var Pointers.child by get().removable().whenSetOnTarget()
- @Handler
- private fun TargetScope.convertToRelation() {
+ @OptIn(UnsafeAccessors::class)
+ override fun Pointers.handle() {
entity {
- addParent(entity)
- setAll(child.components)
+ addParent(target.entity)
+ setAll(child!!.components)
}
- entity.remove()
+ child = null
}
}
class ParseChildrenOnPrefab : Listener() {
- private val TargetScope.children by onSet().onTarget()
+ private var Pointers.children by get().removable().whenSetOnTarget()
- @Handler
- private fun TargetScope.convertToRelation() {
- children.nameToComponents.forEach { (name, components) ->
+ @OptIn(UnsafeAccessors::class)
+ override fun Pointers.handle() {
+ children!!.nameToComponents.forEach { (name, components) ->
entity {
set(EntityName(name))
set(Prefab())
- addParent(entity)
+ addParent(target.entity)
addRelation()
setAll(components)
}
}
- entity.remove()
+ children = null
}
}
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 204245043..c21f15929 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.annotations.Handler
import com.mineinabyss.geary.prefabs.configuration.components.RelationOnPrefab
import com.mineinabyss.geary.systems.Listener
-import com.mineinabyss.geary.systems.accessors.TargetScope
+import com.mineinabyss.geary.systems.accessors.Pointers
+
class ParseRelationOnPrefab : Listener() {
- private val TargetScope.relation by onSet().onTarget()
+ private var Pointers.relation by get().removable().whenSetOnTarget()
- @Handler
- private fun TargetScope.convertToRelation() {
+ override fun Pointers.handle() {
try {
- val rel: RelationOnPrefab = relation
+ val rel: RelationOnPrefab = relation!!
// entity.setRelation(relation.value, entity.parseEntity(relation.key).id)
} finally {
- entity.remove()
+ relation = null
}
}
}
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 8b2a37d1c..530a383a4 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,15 +1,18 @@
package com.mineinabyss.geary.prefabs.configuration.systems
-import com.mineinabyss.geary.annotations.Handler
+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.systems.accessors.RelationWithData
-import com.mineinabyss.geary.systems.accessors.TargetScope
+
class ParseRelationWithDataSystem : Listener() {
- private val TargetScope.relationWithData by onSet>().onTarget()
+ private val Records.relationWithData by get>().whenSetOnTarget()
- @Handler
- private fun TargetScope.convertToRelation() {
+ @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)
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 02426c851..e73fc8c68 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,19 @@
package com.mineinabyss.geary.prefabs.systems
-import com.mineinabyss.geary.annotations.Handler
+import com.mineinabyss.geary.annotations.optin.UnsafeAccessors
import com.mineinabyss.geary.datatypes.family.family
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.EventScope
-import com.mineinabyss.geary.systems.accessors.TargetScope
+import com.mineinabyss.geary.systems.accessors.Pointers
+
class InheritPrefabsOnLoad : Listener() {
- private val EventScope.loaded by family { has() }.onEvent()
+ private val Pointers.loaded by family { has() }.on(event)
- @Handler
- private fun TargetScope.inheritOnLoad() {
- entity.inheritPrefabs()
+ @OptIn(UnsafeAccessors::class)
+ override fun Pointers.handle() {
+ target.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 82e8ad578..105d5c869 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,18 +1,20 @@
package com.mineinabyss.geary.prefabs.systems
-import com.mineinabyss.geary.annotations.Handler
import com.mineinabyss.geary.components.relations.NoInherit
+import com.mineinabyss.geary.datatypes.Records
+import com.mineinabyss.geary.annotations.optin.UnsafeAccessors
import com.mineinabyss.geary.prefabs.PrefabKey
import com.mineinabyss.geary.prefabs.prefabs
import com.mineinabyss.geary.systems.Listener
-import com.mineinabyss.geary.systems.accessors.TargetScope
+import com.mineinabyss.geary.systems.accessors.Pointers
+
class TrackPrefabsByKeySystem : Listener() {
- private val TargetScope.key by onSet().onTarget()
+ private val Records.key by get().whenSetOnTarget()
- @Handler
- private fun TargetScope.registerOnSet() {
- prefabs.manager.registerPrefab(key, entity)
- entity.addRelation()
+ @OptIn(UnsafeAccessors::class)
+ override fun Pointers.handle() {
+ prefabs.manager.registerPrefab(key, target.entity)
+ target.entity.addRelation()
}
}
diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/SerializableComponents.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/SerializableComponents.kt
index b41020511..8a3e81db8 100644
--- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/SerializableComponents.kt
+++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/SerializableComponents.kt
@@ -27,7 +27,7 @@ interface SerializableComponents {
}
override fun Builder.install() {
- geary.pipeline.intercept(GearyPhase.ADDONS_CONFIGURED) {
+ geary.pipeline.runOnOrAfter(GearyPhase.ADDONS_CONFIGURED) {
DI.add(object : SerializableComponents {
override val serializers = serializersBuilder.build()
override val formats = formatsBuilder.build(serializers)
diff --git a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/builders/FormatsBuilder.kt b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/builders/FormatsBuilder.kt
index da5fcd3ad..f9e7e5f94 100644
--- a/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/builders/FormatsBuilder.kt
+++ b/addons/geary-serialization/src/commonMain/kotlin/com/mineinabyss/geary/serialization/dsl/builders/FormatsBuilder.kt
@@ -21,6 +21,7 @@ class FormatsBuilder {
binaryFormat = Cbor {
serializersModule = serializers.module
encodeDefaults = false
+ ignoreUnknownKeys = true
},
formats = formats.mapValues { it.value(serializers.module) },
)
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 2f3181580..b16e6d672 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,25 +2,27 @@ package com.mineinabyss.geary.uuid.systems
import com.benasher44.uuid.Uuid
import com.benasher44.uuid.uuid4
-import com.mineinabyss.geary.annotations.Handler
+import com.mineinabyss.geary.annotations.optin.UnsafeAccessors
import com.mineinabyss.geary.systems.GearyListener
-import com.mineinabyss.geary.systems.accessors.TargetScope
+import com.mineinabyss.geary.systems.accessors.Pointers
+
import com.mineinabyss.geary.uuid.components.RegenerateUUIDOnClash
import com.mineinabyss.geary.uuid.uuid2Geary
class TrackUuidOnAdd : GearyListener() {
- private val TargetScope.uuid by onSet().onTarget()
+ var Pointers.uuid by get().whenSetOnTarget()
+ val Pointers.regenerateUUIDOnClash by get().orNull().on(target)
- @Handler
- private fun TargetScope.track() {
+ @OptIn(UnsafeAccessors::class)
+ override fun Pointers.handle() {
if (uuid in uuid2Geary)
- if (entity.has()) {
+ if (regenerateUUIDOnClash != null) {
val newUuid = uuid4()
- entity.set(newUuid)
- uuid2Geary[newUuid] = entity
- } else error("Tried tracking entity $entity with already existing uuid $uuid")
+ uuid = newUuid
+ uuid2Geary[newUuid] = target.entity
+ } else error("Tried tracking entity $target.entity with already existing uuid $uuid")
else
- uuid2Geary[uuid] = entity
+ uuid2Geary[uuid] = target.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 8e816c655..cb5cd0c12 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
@@ -1,20 +1,19 @@
package com.mineinabyss.geary.uuid.systems
import com.benasher44.uuid.Uuid
-import com.mineinabyss.geary.annotations.Handler
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.EventScope
-import com.mineinabyss.geary.systems.accessors.TargetScope
+import com.mineinabyss.geary.systems.accessors.Pointers
+
import com.mineinabyss.geary.uuid.uuid2Geary
class UnTrackUuidOnRemove : GearyListener() {
- private val TargetScope.uuid by get().onTarget()
- private val EventScope.removed by family { has() }.onEvent()
+ private val Pointers.uuid by get().on(target)
+ private val Pointers.removed by family { has() }.on(event)
- @Handler
- private fun TargetScope.untrack() {
+ override fun Pointers.handle() {
uuid2Geary.remove(uuid)
}
}
diff --git a/build.gradle.kts b/build.gradle.kts
index ed24a32e3..87132dec0 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -17,6 +17,7 @@ allprojects {
subprojects {
tasks.withType().configureEach {
kotlinOptions.freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
+// kotlinOptions.freeCompilerArgs += "-Xno-param-assertions"
}
}
diff --git a/geary-benchmarks/build.gradle.kts b/geary-benchmarks/build.gradle.kts
new file mode 100644
index 000000000..b8b79e9cb
--- /dev/null
+++ b/geary-benchmarks/build.gradle.kts
@@ -0,0 +1,62 @@
+import kotlinx.benchmark.gradle.JvmBenchmarkTarget
+import org.jetbrains.kotlin.allopen.gradle.AllOpenExtension
+
+@Suppress("DSL_SCOPE_VIOLATION")
+plugins {
+ id(libs.plugins.mia.kotlin.jvm.get().pluginId)
+// id(libs.plugins.mia.publication.get().pluginId)
+// alias(libs.plugins.kotlinx.serialization)
+ id("org.jetbrains.kotlinx.benchmark") version "0.4.9"
+ kotlin("plugin.allopen") version "1.9.10"
+}
+
+configure {
+ annotation("org.openjdk.jmh.annotations.State")
+}
+
+dependencies {
+ implementation(project(":geary-core"))
+ implementation("org.jetbrains.kotlinx:kotlinx-benchmark-runtime:0.4.9")
+}
+
+benchmark {
+ configurations {
+ named("main") {
+ exclude("jvmTesting")
+ warmups = 3
+ iterations = 3
+ iterationTime = 5
+ iterationTimeUnit = "sec"
+ }
+
+ create("fast") {
+ exclude("jvmTesting")
+ warmups = 1
+ iterations = 1
+ iterationTime = 3
+ iterationTimeUnit = "sec"
+ }
+
+ create("fastest") {
+ exclude("jvmTesting")
+ warmups = 1
+ iterations = 1
+ iterationTime = 3
+ iterationTimeUnit = "sec"
+ }
+
+ create("specific") {
+ include("Unpack")
+ warmups = 1
+ iterations = 1
+ iterationTime = 3
+ iterationTimeUnit = "sec"
+ }
+ }
+ targets {
+ register("main") {
+ this as JvmBenchmarkTarget
+ jmhVersion = "1.21"
+ }
+ }
+}
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
new file mode 100644
index 000000000..eff2b1a89
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/VelocitySystemBenchmark.kt
@@ -0,0 +1,56 @@
+package com.mineinabyss.geary.benchmarks
+
+import com.mineinabyss.geary.benchmarks.helpers.oneMil
+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.RepeatingSystem
+import com.mineinabyss.geary.systems.accessors.Pointer
+import org.openjdk.jmh.annotations.Benchmark
+import org.openjdk.jmh.annotations.Scope
+import org.openjdk.jmh.annotations.Setup
+import org.openjdk.jmh.annotations.State
+
+@State(Scope.Benchmark)
+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
+ }
+ }
+
+ @Setup
+ fun setUp() {
+ geary(TestEngineModule)
+
+ repeat(tenMil) {
+ entity {
+ set(Velocity(it.toFloat() / oneMil, it.toFloat() / oneMil))
+ set(Position(0f, 0f))
+ }
+ }
+ }
+
+ @Benchmark
+ fun velocitySystem() {
+ VelocitySystem.tickAll()
+ }
+}
+
+fun main() {
+ VelocitySystemBenchmark().apply {
+ setUp()
+
+ repeat(400) {
+ velocitySystem()
+ }
+ }
+}
diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/helpers/TestComponents.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/helpers/TestComponents.kt
new file mode 100644
index 000000000..0ce4ffd3b
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/helpers/TestComponents.kt
@@ -0,0 +1,8 @@
+package com.mineinabyss.geary.benchmarks.helpers
+
+data class Comp1(val id: Int)
+data class Comp2(val id: Int)
+data class Comp3(val id: Int)
+data class Comp4(val id: Int)
+data class Comp5(val id: Int)
+data class Comp6(val id: Int)
diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/helpers/Values.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/helpers/Values.kt
new file mode 100644
index 000000000..95d53b592
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/helpers/Values.kt
@@ -0,0 +1,4 @@
+package com.mineinabyss.geary.benchmarks.helpers
+
+const val oneMil = 1000000
+const val tenMil = 10000000
diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ArchetypeBenchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ArchetypeBenchmark.kt
new file mode 100644
index 000000000..3e04b1b73
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ArchetypeBenchmark.kt
@@ -0,0 +1,13 @@
+package com.mineinabyss.geary.benchmarks.instantiation
+
+import com.mineinabyss.geary.datatypes.EntityType
+import com.mineinabyss.geary.engine.archetypes.Archetype
+import org.openjdk.jmh.annotations.Scope
+import org.openjdk.jmh.annotations.State
+
+@State(Scope.Benchmark)
+class ArchetypeBenchmark {
+ var arch: Archetype = Archetype(EntityType(listOf(1uL, 1uL shl 10, 1uL shl 32 or 1uL)), 0)
+ val type = EntityType(listOf(1uL, 1uL shl 10, 1uL shl 32 or 1uL))
+
+}
diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ManyComponentsBenchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ManyComponentsBenchmark.kt
new file mode 100644
index 000000000..95ed9bae4
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/ManyComponentsBenchmark.kt
@@ -0,0 +1,42 @@
+package com.mineinabyss.geary.benchmarks.instantiation
+
+import co.touchlab.kermit.Logger
+import co.touchlab.kermit.Severity
+import com.mineinabyss.geary.helpers.entity
+import com.mineinabyss.geary.helpers.toGeary
+import com.mineinabyss.geary.modules.TestEngineModule
+import com.mineinabyss.geary.modules.geary
+import com.mineinabyss.idofront.di.DI
+import org.openjdk.jmh.annotations.*
+
+@State(Scope.Benchmark)
+class ManyComponentsBenchmark {
+ @Setup
+ fun setLoggingLevel() {
+ Logger.setMinSeverity(Severity.Warn)
+ }
+
+ @Setup(Level.Invocation)
+ fun setupPerInvocation() {
+ geary(TestEngineModule)
+ }
+
+ @TearDown(Level.Invocation)
+ fun teardown() {
+ DI.clear()
+ }
+
+ @Benchmark
+ fun create1MilEntitiesWithUniqueComponentEach() {
+ repeat(10000) {
+ entity {
+ addRelation(it.toLong().toGeary())
+ }
+ }
+ }
+}
+
+fun main() {
+ geary(TestEngineModule)
+ ManyComponentsBenchmark().create1MilEntitiesWithUniqueComponentEach()
+}
diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/NewEntityBenchmark.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/NewEntityBenchmark.kt
new file mode 100644
index 000000000..4bbbbe346
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/instantiation/NewEntityBenchmark.kt
@@ -0,0 +1,94 @@
+package com.mineinabyss.geary.benchmarks.instantiation
+
+import co.touchlab.kermit.Logger
+import co.touchlab.kermit.Severity
+import com.mineinabyss.geary.benchmarks.helpers.*
+import com.mineinabyss.geary.helpers.componentId
+import com.mineinabyss.geary.helpers.entity
+import com.mineinabyss.geary.modules.TestEngineModule
+import com.mineinabyss.geary.modules.geary
+import com.mineinabyss.idofront.di.DI
+import org.openjdk.jmh.annotations.*
+
+@State(Scope.Benchmark)
+class NewEntityBenchmark {
+ @Setup
+ fun setLoggingLevel() {
+ Logger.setMinSeverity(Severity.Warn)
+ }
+
+ @Setup(Level.Invocation)
+ fun setupPerInvocation() {
+ geary(TestEngineModule)
+ }
+
+ @TearDown(Level.Invocation)
+ fun teardown() {
+ DI.clear()
+ }
+
+ @Benchmark
+ fun create1MilEntitiesWith0Components() {
+ repeat(oneMil) {
+ entity()
+ }
+ }
+
+ @Benchmark
+ fun create1MilEntitiesWith1ComponentNoEvent() {
+ repeat(oneMil) {
+ entity {
+ set(Comp1(0), noEvent = true)
+ }
+ }
+ }
+
+ @Benchmark
+ fun create1MilEntitiesWith1ComponentYesEvent() {
+ repeat(oneMil) {
+ entity {
+ set(Comp1(0))
+ }
+ }
+ }
+
+ @Benchmark
+ fun create1MilEntitiesWith6Components() {
+ repeat(oneMil) {
+ entity {
+ set(Comp1(0), noEvent = true)
+ set(Comp2(0), noEvent = true)
+ set(Comp3(0), noEvent = true)
+ set(Comp4(0), noEvent = true)
+ set(Comp5(0), noEvent = true)
+ set(Comp6(0), noEvent = true)
+ }
+ }
+ }
+
+ @Benchmark
+ fun create1MilEntitiesWith6ComponentsWithoutComponentIdCalls() {
+ val comp1Id = componentId()
+ val comp2Id = componentId()
+ val comp3Id = componentId()
+ val comp4Id = componentId()
+ val comp5Id = componentId()
+ val comp6Id = componentId()
+
+ repeat(oneMil) {
+ entity {
+ set(Comp1(0), comp1Id)
+ set(Comp2(0), comp2Id)
+ set(Comp3(0), comp3Id)
+ set(Comp4(0), comp4Id)
+ set(Comp5(0), comp5Id)
+ set(Comp6(0), comp6Id)
+ }
+ }
+ }
+}
+
+fun main() {
+ geary(TestEngineModule)
+ NewEntityBenchmark().create1MilEntitiesWith0Components()
+}
diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/jvmTesting/MemoryAccessBenchmarks.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/jvmTesting/MemoryAccessBenchmarks.kt
new file mode 100644
index 000000000..b8ec84677
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/jvmTesting/MemoryAccessBenchmarks.kt
@@ -0,0 +1,157 @@
+package com.mineinabyss.geary.benchmarks.jvmTesting
+
+import org.openjdk.jmh.annotations.Benchmark
+import org.openjdk.jmh.annotations.Scope
+import org.openjdk.jmh.annotations.Setup
+import org.openjdk.jmh.annotations.State
+import kotlin.experimental.or
+import kotlin.properties.ReadOnlyProperty
+import kotlin.reflect.KProperty
+
+data class TestData(val int: Int, val double: Double)
+
+@State(Scope.Benchmark)
+class MemoryAccessBenchmarks {
+ private final val oneMil = 1000000
+ var intArr = intArrayOf()
+ var byteArr = byteArrayOf()
+ var objArr = arrayOf()
+ var anyArr = mutableListOf()
+ var accessorWithScope = AccessorWithScope(intArrayOf())
+
+ var nDimensionalArr = Array(8) { IntArray(oneMil) { it } }
+
+ @Setup
+ fun setup() {
+ intArr = IntArray(oneMil) { it }
+ byteArr = ByteArray(oneMil) { it.toByte() }
+ objArr = Array(oneMil) { TestData(it, it.toDouble()) }
+ anyArr = MutableList(oneMil) { TestData(it, it.toDouble()) }
+ accessorWithScope = AccessorWithScope(intArr)
+ }
+
+ @Benchmark
+ fun emptyLoop() {
+ for (i in 0 until oneMil) {
+ }
+ }
+
+ @Benchmark
+ fun readIntArrDirectly() {
+ for (i in 0 until oneMil) {
+ intArr[i] + 1
+ }
+ }
+
+ @Benchmark
+ fun readObjArrDirectly() {
+ for (i in 0 until oneMil) {
+ objArr[i]
+ }
+ }
+
+ @Benchmark
+ fun readObjArrWithAny() {
+ for (i in 0 until oneMil) {
+ anyArr[i]
+ }
+ }
+
+ @Benchmark
+ fun read2ArrDirectly() {
+ for (i in 0 until oneMil) {
+ intArr[i] + 1
+ byteArr[i] or 1
+ }
+ }
+
+ @Benchmark
+ fun read8ArrDirectly() {
+ for (i in 0 until oneMil) {
+ nDimensionalArr[0][i] + 1
+ nDimensionalArr[1][i] + 1
+ nDimensionalArr[2][i] + 1
+ nDimensionalArr[3][i] + 1
+ nDimensionalArr[4][i] + 1
+ nDimensionalArr[5][i] + 1
+ nDimensionalArr[6][i] + 1
+ nDimensionalArr[7][i] + 1
+ }
+ }
+
+ @Benchmark
+ fun readWriteSingleIntArrDirectly() {
+ for (i in 0 until oneMil) {
+ intArr[i] = intArr[i] + 1
+ }
+ }
+
+
+ @Benchmark
+ fun readWriteTwoArrDirectly() {
+ for (i in 0 until oneMil) {
+ intArr[i] = intArr[i] + 1
+ byteArr[i] = byteArr[i] or 1
+ }
+ }
+
+ @Benchmark
+ fun readObjPackToLocalArr() {
+ for (i in 0 until oneMil) {
+ val localArr = intArrayOf(intArr[i])
+ }
+ }
+
+ @Benchmark
+ fun readObjPackManyToLocalArr() {
+ for (i in 0 until oneMil) {
+ val localArr = arrayOf(intArr[i], byteArr[i], objArr[i])
+ }
+ }
+
+ @Benchmark
+ fun readPackToSeparateArr() {
+ val arr = IntArray(oneMil)
+ for (i in 0 until oneMil) {
+ arr[i] = intArr[i]
+ }
+ }
+
+ @Benchmark
+ fun readIntArrWithIndirection() {
+ for (i in 0 until oneMil) {
+ val acc by Accessor(i)
+ acc + 1
+ }
+ }
+
+
+ @Benchmark
+ fun readIntArrWithIndirectionAndScope() {
+ val accessors = object {
+ val Int.acc by accessorWithScope
+ }
+
+ for (i in 0 until oneMil) {
+ with(accessors) {
+ with(i) {
+ acc + 1
+ }
+ }
+ }
+ }
+
+ inner class Accessor(val index: Int) : ReadOnlyProperty {
+ override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
+ return intArr[index]
+ }
+ }
+
+ @JvmInline
+ value class AccessorWithScope(private val array: IntArray) : ReadOnlyProperty {
+ override fun getValue(thisRef: Int, property: KProperty<*>): Int {
+ return array[thisRef]
+ }
+ }
+}
+
diff --git a/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/misc/ComponentIdTest.kt b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/misc/ComponentIdTest.kt
new file mode 100644
index 000000000..9ffb45eb9
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/misc/ComponentIdTest.kt
@@ -0,0 +1,41 @@
+package com.mineinabyss.geary.benchmarks.misc
+
+import co.touchlab.kermit.Logger
+import co.touchlab.kermit.Severity
+import com.mineinabyss.geary.benchmarks.helpers.*
+import com.mineinabyss.geary.helpers.componentId
+import com.mineinabyss.geary.modules.TestEngineModule
+import com.mineinabyss.geary.modules.geary
+import org.openjdk.jmh.annotations.Benchmark
+import org.openjdk.jmh.annotations.Scope
+import org.openjdk.jmh.annotations.Setup
+import org.openjdk.jmh.annotations.State
+
+@State(Scope.Benchmark)
+class ComponentIdTest {
+ @Setup
+ fun setup() {
+ Logger.setMinSeverity(Severity.Warn)
+ geary(TestEngineModule)
+ }
+
+ @Benchmark
+ fun componentIdFor6Comp() {
+ repeat(tenMil) {
+ componentId()
+ componentId()
+ componentId()
+ componentId()
+ componentId()
+ componentId()
+ }
+ }
+}
+
+fun main() {
+ geary(TestEngineModule)
+ ComponentIdTest().apply {
+ setup()
+ componentIdFor6Comp()
+ }
+}
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
new file mode 100644
index 000000000..557a4c7c3
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack1Benchmark.kt
@@ -0,0 +1,40 @@
+package com.mineinabyss.geary.benchmarks.unpacking
+
+import com.mineinabyss.geary.benchmarks.helpers.Comp1
+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
+import org.openjdk.jmh.annotations.State
+
+@State(Scope.Benchmark)
+class Unpack1Benchmark {
+ private object SystemOf1 : GearyQuery() {
+ val Pointer.comp1 by get()
+ }
+
+ @Setup
+ fun setUp() {
+ geary(TestEngineModule)
+
+ repeat(tenMil) {
+ entity {
+ set(Comp1(1))
+ }
+ }
+ }
+
+ @Benchmark
+ fun unpack1of1Comp() {
+ SystemOf1.run {
+ forEach {
+ it.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
new file mode 100644
index 000000000..340e0a13f
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack2Benchmark.kt
@@ -0,0 +1,58 @@
+package com.mineinabyss.geary.benchmarks.unpacking
+
+import com.mineinabyss.geary.benchmarks.helpers.Comp1
+import com.mineinabyss.geary.benchmarks.helpers.Comp2
+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
+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) {
+ }
+
+ repeat(tenMil) {
+ entity {
+ set(Comp1(1))
+ set(Comp2(1))
+ }
+ }
+ }
+
+ @Benchmark
+ fun unpack1of2Comp() {
+ SystemOf1.run {
+ forEach {
+ it.comp1
+ }
+ }
+ }
+
+ @Benchmark
+ fun unpack2of2Comp() {
+ SystemOf2.run {
+ forEach {
+ it.comp1
+ it.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
new file mode 100644
index 000000000..1c225b916
--- /dev/null
+++ b/geary-benchmarks/src/main/kotlin/com/mineinabyss/geary/benchmarks/unpacking/Unpack6Benchmark.kt
@@ -0,0 +1,112 @@
+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
+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) {
+ }
+
+ repeat(tenMil) {
+ entity {
+ set(Comp1(0))
+ set(Comp2(0))
+ set(Comp3(0))
+ set(Comp4(0))
+ set(Comp5(0))
+ set(Comp6(0))
+ }
+ }
+ }
+
+ @Benchmark
+ fun unpack1of6Comp() {
+ SystemOf1.run {
+ forEach {
+ it.comp1
+ }
+ }
+ }
+
+ @Benchmark
+ fun unpack6of6Comp() {
+ SystemOf6.run {
+ forEach {
+ it.comp1
+ it.comp2
+ it.comp3
+ it.comp4
+ it.comp5
+ it.comp6
+ }
+ }
+ }
+
+ @Benchmark
+ fun unpack6of6CompNoDelegate() {
+ SystemOf6WithoutDelegate.run {
+ forEach {
+ comp1[it]
+ comp2[it]
+ comp3[it]
+ comp4[it]
+ comp5[it]
+ comp6[it]
+ }
+ }
+ }
+}
+
+
+fun main() {
+ Unpack6Benchmark().apply {
+ setUp()
+ repeat(100) {
+ unpack6of6Comp()
+ }
+ }
+}
diff --git a/geary-core/build.gradle.kts b/geary-core/build.gradle.kts
index 12e560ad9..65f01d8c2 100644
--- a/geary-core/build.gradle.kts
+++ b/geary-core/build.gradle.kts
@@ -30,8 +30,8 @@ kotlin {
implementation(mylibs.atomicfu)
implementation(libs.kotlin.reflect)
implementation(libs.kotlinx.serialization.cbor)
- implementation(libs.idofront.di)
+ api(libs.idofront.di)
api(mylibs.kds)
api(mylibs.kermit)
api(libs.kotlinx.coroutines)
@@ -52,6 +52,7 @@ kotlin {
val jvmMain by getting {
dependencies {
implementation(libs.kotlinx.serialization.kaml)
+ implementation(libs.fastutil)
implementation(mylibs.roaringbitmap)
}
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/annotations/Handler.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/annotations/Handler.kt
deleted file mode 100644
index e45f3c83a..000000000
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/annotations/Handler.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.mineinabyss.geary.annotations
-
-import com.mineinabyss.geary.events.Handler
-import com.mineinabyss.geary.systems.Listener
-import com.mineinabyss.geary.systems.accessors.EventScope
-import com.mineinabyss.geary.systems.accessors.SourceScope
-import com.mineinabyss.geary.systems.accessors.TargetScope
-
-/**
- * Indicates a function within a [Listener] should be registered as a [Handler]
- *
- * The function can read from different accessors by adding arguments [SourceScope], [TargetScope], [EventScope].
- * They may appear in any order, be omitted, or used as a receiver.
- *
- * If [SourceScope] is nullable or omitted, the handler will not be called when there is no source present on the event.
- *
- * Example:
- *
- * ```kotlin
- * @Handler
- * fun TargetScope.doSomething(source: SourceScope, event: EventScope) {
- * // Within here, you may use accessors defined for all three.
- * }
- * ```
- */
-@Target(AnnotationTarget.FUNCTION)
-annotation class Handler
-
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/annotations/optin/UnsafeAccessors.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/annotations/optin/UnsafeAccessors.kt
new file mode 100644
index 000000000..df20f9221
--- /dev/null
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/annotations/optin/UnsafeAccessors.kt
@@ -0,0 +1,8 @@
+package com.mineinabyss.geary.annotations.optin
+
+@RequiresOptIn(
+ level = RequiresOptIn.Level.ERROR,
+ message = "Reading and writing entity data without an accessor reduces speed and does not enforce null safety for accessors." +
+ " Be careful when manually removing components used by other accessors."
+)
+annotation class UnsafeAccessors
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/events/FailedCheck.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/events/FailedCheck.kt
index 6321b2a20..965e36c4e 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/events/FailedCheck.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/events/FailedCheck.kt
@@ -2,7 +2,5 @@ package com.mineinabyss.geary.components.events
/**
* A component that gets added to events that failed a check.
- *
- * @see RequestCheck
*/
sealed class FailedCheck
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt
index 5ac92537c..8602de49e 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Entity.kt
@@ -2,16 +2,12 @@ package com.mineinabyss.geary.datatypes
import com.mineinabyss.geary.annotations.optin.DangerousComponentOperation
import com.mineinabyss.geary.components.events.AddedComponent
-import com.mineinabyss.geary.components.relations.ChildOf
import com.mineinabyss.geary.components.relations.InstanceOf
import com.mineinabyss.geary.components.relations.Persists
-import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.datatypes.family.family
import com.mineinabyss.geary.engine.Engine
-import com.mineinabyss.geary.helpers.component
-import com.mineinabyss.geary.helpers.componentId
-import com.mineinabyss.geary.helpers.componentIdWithNullable
-import com.mineinabyss.geary.helpers.temporaryEntity
+import com.mineinabyss.geary.helpers.*
+import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.systems.accessors.AccessorOperations
import com.mineinabyss.geary.systems.accessors.RelationWithData
import kotlinx.serialization.Serializable
@@ -40,12 +36,12 @@ value class Entity(val id: EntityId) {
val children: List
get() = queryManager.getEntitiesMatching(family {
- hasRelation(this@Entity)
+ hasRelation(geary.components.childOf, this@Entity.id)
})
val instances: List
get() = queryManager.getEntitiesMatching(family {
- hasRelation(this@Entity)
+ hasRelation(geary.components.instanceOf, this@Entity.id)
})
/** Remove this entity from the ECS. */
@@ -212,7 +208,6 @@ value class Entity(val id: EntityId) {
inline fun has(kClass: KClass): Boolean =
has(componentId(kClass))
-
/** Checks whether this entity has a [component], regardless of it holding data. */
fun has(component: ComponentId): Boolean =
read.hasComponentFor(this, component)
@@ -274,6 +269,10 @@ value class Entity(val id: EntityId) {
geary.write.addComponentFor(this, Relation.of(target).id, noEvent)
}
+ fun addRelation(kind: ComponentId, target: EntityId, noEvent: Boolean = false) {
+ geary.write.addComponentFor(this, Relation.of(kind, target).id, noEvent)
+ }
+
/** Removes a relation key of type [K] and value of type [V]. */
inline fun removeRelation(): Boolean {
return removeRelation(component())
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityStack.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityStack.kt
index f1c42aa58..1578181df 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityStack.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityStack.kt
@@ -1,7 +1,7 @@
package com.mineinabyss.geary.datatypes
import com.mineinabyss.geary.helpers.toGeary
-import com.soywiz.kds.DoubleQueue
+import korlibs.datastructure.DoubleQueue
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
@@ -13,7 +13,8 @@ class EntityStack(private val stack: DoubleQueue = DoubleQueue()) {
}
}
- fun pop(): Entity = synchronized(removedEntitiesLock) {
- stack.dequeue().toRawBits().toGeary()
+ fun pop(): Entity? = synchronized(removedEntitiesLock) {
+ if (stack.isEmpty()) null
+ else stack.dequeue().toRawBits().toGeary()
}
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityType.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityType.kt
index 1957309ea..51e2537e6 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityType.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/EntityType.kt
@@ -1,37 +1,31 @@
package com.mineinabyss.geary.datatypes
-import com.mineinabyss.geary.components.relations.InstanceOf
-import com.mineinabyss.geary.helpers.componentId
import com.mineinabyss.geary.helpers.readableString
-import kotlin.jvm.JvmInline
+
/**
* An inlined class used for tracking the components an entity/archetype has.
*
* It provides fast (no boxing) functions backed by FastUtil sorted sets to do operations with [ComponentId]s.
*/
-@JvmInline
-value class EntityType private constructor(
+class EntityType private constructor(
@PublishedApi
internal val inner: ULongArray
) {
constructor() : this(ULongArray(0))
- constructor(ids: Collection) : this(inner = ids.toULongArray().apply { sort() })
- val size: Int get() = inner.size
+ constructor(ids: Collection) : this(inner = ids.toULongArray().apply { sort() })
- val prefabs: EntityType
- get() = EntityType(filter { contains(Relation.of(componentId(), it).id) }
- .map { Relation.of(it).target })
+ val size: Int = inner.size
operator fun contains(id: ComponentId): Boolean = indexOf(id) != -1
fun indexOf(id: ComponentId): Int {
- return binarySearch(id).coerceAtLeast(-1)
+ return inner.indexOf(id)
}
- tailrec fun binarySearch(id: ComponentId, fromIndex: Int = 0, toIndex: Int = inner.lastIndex): Int {
+ tailrec fun binarySearch(id: ComponentId, fromIndex: Int = 0, toIndex: Int = size - 1): Int {
if (fromIndex > toIndex) return -fromIndex - 1
val mid = (fromIndex + toIndex) / 2
val found = inner[mid]
@@ -46,11 +40,9 @@ value class EntityType private constructor(
fun last(): ComponentId = inner.last()
inline fun forEach(run: (ComponentId) -> Unit) {
- inner.forEach(run)
-// val iterator = inner.iterator()
-// while (iterator.hasNext()) {
-// run(iterator.nextLong().toULong())
-// }
+ for(i in 0..inner.lastIndex) {
+ run(inner[i])
+ }
}
inline fun any(predicate: (ComponentId) -> Boolean): Boolean {
@@ -60,16 +52,10 @@ value class EntityType private constructor(
inline fun forEachIndexed(run: (Int, ComponentId) -> Unit) {
inner.forEachIndexed(run)
-// val iterator = inner.iterator()
-// var i = 0
-// forEach { run(i++, iterator.nextLong().toULong()) }
}
inline fun filter(predicate: (ComponentId) -> Boolean): EntityType {
return EntityType(inner.filter(predicate))
-// val type = LongAVLTreeSet()
-// forEach { if (predicate(it)) type.add(it.toLong()) }
-// return GearyType(type)
}
inline fun map(transform: (ULong) -> T): List {
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 de7111323..5df043af2 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
@@ -5,6 +5,7 @@ import kotlinx.serialization.Polymorphic
typealias GearyEntity = Entity
typealias GearyEntityType = EntityType
typealias GearyRecord = Record
+typealias GearyRecords = Records
typealias GearyRelation = Relation
/**
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/IdList.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/IdList.kt
index 53e37e12f..af1bc8afd 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/IdList.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/IdList.kt
@@ -1,4 +1,33 @@
package com.mineinabyss.geary.datatypes
-// TODO more efficient type, no boxing
-typealias IdList = ArrayList
+import com.mineinabyss.geary.helpers.toGeary
+
+private const val initialSize: Int = 4
+private const val growFactor: Int = 2
+
+class IdList {
+ var backingArr = ULongArray(initialSize)
+ var size = 0
+ val lastIndex get() = size - 1
+
+ operator fun get(index: Int): ULong = backingArr[index]
+ operator fun set(index: Int, value: ULong) {
+ backingArr[index] = value
+ }
+
+ fun add(value: ULong) {
+ if (size == backingArr.size) {
+ backingArr = backingArr.copyOf(size * growFactor)
+ }
+ backingArr[size++] = value
+ }
+
+ fun removeLastOrNull(): ULong? {
+ if (size == 0) return null
+ return backingArr[--size]
+ }
+
+ fun getEntities(): Sequence {
+ return backingArr.asSequence().take(size).map { it.toGeary() }
+ }
+}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Record.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Record.kt
index b8b6c56f6..40661d9f9 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Record.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Record.kt
@@ -3,7 +3,8 @@ package com.mineinabyss.geary.datatypes
import com.mineinabyss.geary.engine.archetypes.Archetype
import kotlinx.atomicfu.locks.SynchronizedObject
-class Record internal constructor(
+
+class Record @PublishedApi internal constructor(
archetype: Archetype,
row: Int
) : SynchronizedObject() {
@@ -17,7 +18,7 @@ class Record internal constructor(
this.row = row
}
- internal val entity: Entity get() = archetype.getEntity(row)
+ val entity: Entity get() = archetype.getEntity(row)
operator fun component1(): Archetype = archetype
operator fun component2(): Int = row
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
new file mode 100644
index 000000000..a1882bb3a
--- /dev/null
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/RecordPointer.kt
@@ -0,0 +1,42 @@
+package com.mineinabyss.geary.datatypes
+
+import com.mineinabyss.geary.annotations.optin.UnsafeAccessors
+import com.mineinabyss.geary.engine.archetypes.Archetype
+import com.mineinabyss.geary.modules.archetypes
+
+/** A record created in place that delegates to the real entity pointer the first time [entity] gets accessed. */
+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
new file mode 100644
index 000000000..d53647f08
--- /dev/null
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/Records.kt
@@ -0,0 +1,23 @@
+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/TinyBitSet.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/TinyBitSet.kt
deleted file mode 100644
index 064d50763..000000000
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/TinyBitSet.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.mineinabyss.geary.datatypes
-
-// Extra Long operations to be able to have a really fast tiny bitset
-
-fun Long.pop1(): Long {
- val c = countTrailingZeroBits()
- if (c == 64) return this
- return unsetBit(c)
-}
-
-fun Long.setBit(index: Int): Long {
- return this or (1L shl index)
-}
-
-fun Long.unsetBit(index: Int): Long {
- return this and (1L shl index).inv()
-}
-
-inline fun Long.forEachBit(run: (Int) -> Unit) {
- var remaining = this
- while (remaining != 0L) {
- val hi = remaining.takeHighestOneBit()
- remaining = remaining and hi.inv()
- run(hi.countTrailingZeroBits())
- }
-}
-
-fun Long.toIntArray(): IntArray {
- val arr = IntArray(countOneBits())
- var i = 0
- forEachBit { arr[i] = it; i++ }
- return arr
-}
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 e4d8aee19..6df1d66dd 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,8 +2,13 @@ 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 kotlin.reflect.KProperty
+
+sealed interface Family : ReadOnlyAccessor, FamilyMatching {
-sealed interface Family {
sealed class Leaf : Family {
sealed interface Component : Family {
val component: ComponentId
@@ -36,4 +41,10 @@ sealed interface Family {
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/maps/HashTypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt
similarity index 70%
rename from geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/HashTypeMap.kt
rename to geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt
index 30cf620b4..49f2b1642 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/HashTypeMap.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/ArrayTypeMap.kt
@@ -2,13 +2,12 @@ package com.mineinabyss.geary.datatypes.maps
import com.mineinabyss.geary.datatypes.Entity
import com.mineinabyss.geary.datatypes.Record
-import com.soywiz.kds.*
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
-class HashTypeMap : TypeMap {
+class ArrayTypeMap : TypeMap {
private val lock = SynchronizedObject()
- private val map: FastIntMap = FastIntMap()
+ private val map: ArrayList = arrayListOf()
// We don't return nullable record to avoid boxing.
// Accessing an entity that doesn't exist is indicative of a problem elsewhere and should be made obvious.
@@ -16,15 +15,22 @@ class HashTypeMap : TypeMap {
?: error("Tried to access components on an entity that no longer exists (${entity.id})")
override fun set(entity: Entity, record: Record): Unit = synchronized(lock) {
+ val id = entity.id.toInt()
+ if(map.size == id) {
+ map.add(record)
+ return@synchronized
+ }
if (contains(entity)) error("Tried setting the record of an entity that already exists.")
- map[entity.id.toInt()] = record
+ while(map.size <= id) map.add(null)
+ map[id] = record
}
override fun remove(entity: Entity): Unit = synchronized(lock) {
- map.remove(entity.id.toInt())
+ map[entity.id.toInt()] = null
}
override operator fun contains(entity: Entity): Boolean = synchronized(lock) {
- map.contains(entity.id.toInt())
+ val id = entity.id.toInt()
+ map.size < id && map[id] != null
}
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt
index b68041b8a..b75f1ba11 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/CompId2ArchetypeMap.kt
@@ -6,12 +6,25 @@ import com.mineinabyss.geary.engine.archetypes.Archetype
/**
* Inlined class that acts as a map of components to archetypes. Uses archetype ids for better performance.
*/
-class CompId2ArchetypeMap {
- val inner: MutableMap = mutableMapOf()
- operator fun get(id: GearyComponentId): Archetype? = inner[id.toLong()]
+expect class CompId2ArchetypeMap() {
+ operator fun get(id: GearyComponentId): Archetype?
+ operator fun set(id: GearyComponentId, archetype: Archetype)
+
+ operator fun contains(id: GearyComponentId): Boolean
+
+ fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype
+}
+
+class CompId2ArchetypeMapViaMutableMap {
+ val inner: MutableMap = mutableMapOf()
+ operator fun get(id: GearyComponentId): Archetype? = inner[id]
operator fun set(id: GearyComponentId, archetype: Archetype) {
- inner[id.toLong()] = archetype
+ inner[id] = archetype
}
- operator fun contains(id: GearyComponentId): Boolean = inner.containsKey(id.toLong())
+ operator fun contains(id: GearyComponentId): Boolean = inner.containsKey(id)
+
+ fun getOrSet(id: GearyComponentId, put: () -> Archetype): Archetype {
+ return inner[id] ?: put().also { inner[id] = it }
+ }
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt
index 392605210..f8c3ee0a6 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Components.kt
@@ -1,5 +1,9 @@
package com.mineinabyss.geary.engine
+import com.mineinabyss.geary.components.CouldHaveChildren
+import com.mineinabyss.geary.components.events.*
+import com.mineinabyss.geary.components.relations.ChildOf
+import com.mineinabyss.geary.components.relations.InstanceOf
import com.mineinabyss.geary.components.relations.Persists
import com.mineinabyss.geary.datatypes.ComponentId
import com.mineinabyss.geary.helpers.componentId
@@ -7,4 +11,12 @@ import com.mineinabyss.geary.helpers.componentId
class Components {
val any: ComponentId = componentId()
val persists: ComponentId = componentId()
+ val suppressRemoveEvent = componentId()
+ val couldHaveChildren = componentId()
+ val addedComponent = componentId()
+ val setComponent = componentId()
+ val updatedComponent = componentId()
+ val entityRemoved = componentId()
+ val childOf = componentId()
+ val instanceOf = componentId()
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Engine.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Engine.kt
index 60087a208..a55daeda9 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Engine.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/Engine.kt
@@ -10,5 +10,5 @@ import kotlinx.coroutines.CoroutineScope
interface Engine : CoroutineScope {
/** Ticks the entire engine. Implementations may call at different speeds. */
- suspend fun tick(currentTick: Long)
+ fun tick(currentTick: Long)
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityProvider.kt
index 5e6db8460..56c9ffcbc 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityProvider.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/EntityProvider.kt
@@ -6,7 +6,7 @@ import com.mineinabyss.geary.datatypes.EntityType
interface EntityProvider {
/** Creates a new entity. */
- fun create(initialComponents: Collection = emptyList()): Entity
+ fun create(): Entity
/** Removes an entity, freeing up its entity id for later reuse. */
fun remove(entity: Entity)
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 d4782d979..9cf6c3cb0 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
@@ -5,7 +5,7 @@ import com.mineinabyss.geary.systems.RepeatingSystem
import com.mineinabyss.geary.systems.System
interface Pipeline {
- fun intercept(phase: GearyPhase, block: () -> Unit)
+ fun runOnOrAfter(phase: GearyPhase, block: () -> Unit)
fun interceptSystemAddition(run: (System) -> System?)
fun runStartupTasks()
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 f4dde38c0..188cfedc5 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
@@ -5,7 +5,7 @@ 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.soywiz.kds.sortedMapOf
+import korlibs.datastructure.sortedMapOf
class PipelineImpl : Pipeline {
private val queryManager get() = geary.queryManager
@@ -14,10 +14,12 @@ class PipelineImpl : Pipeline {
private val registeredSystems: MutableSet = mutableSetOf()
private val registeredListeners: MutableSet = mutableSetOf()
- private val actions = sortedMapOf Unit>>()
+ private val scheduled = sortedMapOf Unit>>()
+ private var currentPhase = GearyPhase.entries.first()
- override fun intercept(phase: GearyPhase, block: () -> Unit) {
- actions.getOrPut(phase) { mutableListOf() }.add(block)
+ override fun runOnOrAfter(phase: GearyPhase, block: () -> Unit) {
+ if (currentPhase > phase) block()
+ else scheduled.getOrPut(phase) { mutableListOf() }.add(block)
}
override fun interceptSystemAddition(run: (System) -> System?) {
@@ -25,10 +27,11 @@ class PipelineImpl : Pipeline {
}
override fun runStartupTasks() {
- actions.values.forEach { actions ->
+ scheduled.values.forEach { actions ->
actions.forEach { it() }
}
}
+
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
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 8107baab2..d9790aa6f 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
@@ -1,21 +1,13 @@
package com.mineinabyss.geary.engine.archetypes
-import com.mineinabyss.geary.components.events.AddedComponent
-import com.mineinabyss.geary.components.events.SetComponent
-import com.mineinabyss.geary.components.events.UpdatedComponent
-import com.mineinabyss.geary.modules.archetypes
-import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.datatypes.*
import com.mineinabyss.geary.datatypes.maps.CompId2ArchetypeMap
-import com.mineinabyss.geary.datatypes.maps.Long2ObjectMap
-import com.mineinabyss.geary.engine.Engine
-import com.mineinabyss.geary.events.Handler
import com.mineinabyss.geary.helpers.temporaryEntity
import com.mineinabyss.geary.helpers.toGeary
+import com.mineinabyss.geary.modules.archetypes
+import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.systems.Listener
-import com.mineinabyss.geary.systems.query.GearyQuery
-import kotlinx.atomicfu.locks.SynchronizedObject
-import kotlinx.atomicfu.locks.synchronized
+import com.mineinabyss.geary.systems.accessors.RelationWithData
/**
* Archetypes store a list of entities with the same [EntityType], and provide functions to
@@ -32,15 +24,11 @@ data class Archetype(
private val archetypeProvider get() = archetypes.archetypeProvider
private val eventRunner get() = archetypes.eventRunner
- /** A mutex for anything which needs the size of ids to remain unchanged. */
- private val entityAddition = SynchronizedObject()
- val entities: List get() = ids.map { it.toGeary() }
+ val entities: Sequence get() = ids.getEntities()
/** The entity ids in this archetype. Indices are the same as [componentData]'s sub-lists. */
private val ids: IdList = IdList()
- private val queuedRemoval = mutableListOf()
- private val queueRemoval = SynchronizedObject()
@PublishedApi
internal var isIterating: Boolean = false
@@ -62,17 +50,12 @@ data class Archetype(
internal val relations = type.inner.mapNotNull { it.toRelation() }
internal val relationsWithData = relations.filter { it.id.holdsData() }
- /** Map of relation [Relation.target] id to a list of relations with that [Relation.target]. */
- internal val relationsByTarget: Long2ObjectMap> = relations
- .groupBy { it.target.toLong() }
-
- /** Map of relation [Relation.kind] id to a list of relations with that [Relation.kind]. */
- internal val relationsByKind: Long2ObjectMap> = relations
- .groupBy { it.kind.toLong() }
+ fun getRelationsByTarget(target: EntityId): List {
+ return relations.filter { it.target.toLong() == target.toLong() }
+ }
- /** A map of component ids to index used internally in this archetype (ex. in [componentData])*/
- private val comp2indices: Map = buildMap {
- dataHoldingType.forEachIndexed { i, compId -> put(compId.toLong(), i) }
+ fun getRelationsByKind(kind: ComponentId): List {
+ return relations.filter { it.kind.toLong() == kind.toLong() }
}
/** The amount of entities stored in this archetype. */
@@ -84,23 +67,20 @@ data class Archetype(
private val _targetListeners = mutableSetOf()
val targetListeners: Set = _targetListeners
- private val _eventHandlers = mutableSetOf()
-
- //TODO update doc
- /** A map of event class type to a set of event handlers which fire on that event. */
- val eventHandlers: Set = _eventHandlers
+ private val _eventListeners = mutableSetOf()
+ val eventListeners: Set = _eventListeners
// ==== Helper functions ====
- fun getEntity(row: Int): Entity = synchronized(entityAddition) {
+ fun getEntity(row: Int): Entity {
return ids[row].toGeary()
}
/**
* Used to pack data closer together and avoid having hashmaps throughout the archetype.
*
- * @return The internally used index for this component [id].
+ * @return The internally used index for this component [id], or 0 if not present. Use contains to check for presence.
*/
- internal fun indexOf(id: ComponentId): Int = comp2indices[id.toLong()] ?: -1
+ fun indexOf(id: ComponentId): Int = dataHoldingType.indexOf(id)
/**
* @return The data under a [componentId] for an entity at [row].
@@ -118,12 +98,18 @@ data class Archetype(
/** Returns the archetype associated with adding [componentId] to this archetype's [type]. */
operator fun plus(componentId: ComponentId): Archetype =
- componentAddEdges[componentId] ?: archetypeProvider.getArchetype(type.plus(componentId))
+ componentAddEdges.getOrSet(componentId) {
+ archetypeProvider.getArchetype(
+ if (componentId.hasRole(HOLDS_DATA))
+ type + componentId.withoutRole(HOLDS_DATA) + componentId
+ else type + componentId
+ )
+ }
/** Returns the archetype associated with removing [componentId] to this archetype's [type]. */
operator fun minus(componentId: ComponentId): Archetype =
- componentRemoveEdges[componentId] ?: archetypeProvider.getArchetype(type.minus(componentId)).also {
- componentRemoveEdges[componentId] = it
+ componentRemoveEdges.getOrSet(componentId) {
+ archetypeProvider.getArchetype(type.minus(componentId))
}
// ==== Entity mutation ====
@@ -134,20 +120,88 @@ data class Archetype(
*
* @return The new [Record] to be associated with this entity from now on.
*/
- internal fun addEntityWithData(
+ private fun moveWithNewComponent(
+ record: Record,
+ newComponent: Component,
+ newComponentId: ComponentId,
+ entity: EntityId,
+ ) = move(record, entity) {
+ val (oldArc, oldRow) = record
+ val newCompIndex = indexOf(newComponentId)
+
+ // Add before new comp
+ for (i in 0 until newCompIndex) {
+ componentData[i].add(oldArc.componentData[i][oldRow])
+ }
+
+ // Add new comp
+ componentData[newCompIndex].add(newComponent)
+
+ // Add after new comp
+ for (i in newCompIndex + 1..componentData.lastIndex) {
+ // Offset by one since this new comp didn't exist
+ componentData[i].add(oldArc.componentData[i - 1][oldRow])
+ }
+ }
+
+ private fun moveOnlyAdding(
record: Record,
- data: Array,
- entity: Entity,
- ) = synchronized(entityAddition) {
- synchronized(record) {
- ids.add(entity.id.toLong())
- componentData.forEachIndexed { i, compArray ->
- compArray.add(data[i])
+ entity: EntityId
+ ) = move(record, entity) {
+ val (oldArc, oldRow) = record
+ for (i in 0..componentData.lastIndex) {
+ componentData[i].add(oldArc.componentData[i][oldRow])
+ }
+ }
+
+ private fun moveWithoutComponent(
+ record: Record,
+ withoutComponentId: ComponentId,
+ entity: EntityId,
+ ) = move(record, entity) {
+ val (oldArc, oldRow) = record
+ val withoutCompIndex = oldArc.indexOf(withoutComponentId)
+
+ // If removing a component that's added and not set, we just copy all data
+ if (withoutCompIndex == -1) {
+ for (i in 0..componentData.lastIndex) {
+ componentData[i].add(oldArc.componentData[i][oldRow])
}
- record.row = -1
- record.archetype = this
- record.row = ids.lastIndex
+ return@move
}
+
+ // Add before without comp
+ for (i in 0 until withoutCompIndex) {
+ componentData[i].add(oldArc.componentData[i][oldRow])
+ }
+
+ // Add after without comp
+ for (i in withoutCompIndex + 1..oldArc.componentData.lastIndex) {
+ componentData[i - 1].add(oldArc.componentData[i][oldRow])
+ }
+ }
+
+ internal fun createWithoutData(entity: Entity, existingRecord: Record) {
+ move(existingRecord, entity.id) {}
+ }
+
+ internal fun createWithoutData(entity: Entity): Record {
+ ids.add(entity.id)
+ return Record(this, ids.lastIndex)
+ }
+
+ internal inline fun move(
+ record: Record,
+ entity: EntityId,
+ copyData: () -> Unit
+ ) {
+ ids.add(entity)
+
+ copyData()
+
+ record.row = -1
+ record.archetype = this
+ record.row = ids.lastIndex
}
// For the following few functions, both entity and row are passed to avoid doing several array look-ups
@@ -168,13 +222,13 @@ data class Archetype(
val moveTo = this + (componentId.withoutRole(HOLDS_DATA))
- val componentData = getComponents(record.row)
- val entity = record.entity
- removeEntity(record.row)
- moveTo.addEntityWithData(record, componentData, entity)
+ val row = record.row
+ val entityId = ids[row]
+ moveTo.moveOnlyAdding(record, entityId)
+ removeEntity(row)
if (callEvent) temporaryEntity { componentAddEvent ->
- componentAddEvent.addRelation(componentId.toGeary(), noEvent = true)
+ componentAddEvent.addRelation(geary.components.addedComponent, componentId, noEvent = true)
eventRunner.callEvent(record, records[componentAddEvent], null)
}
return true
@@ -200,27 +254,36 @@ data class Archetype(
if (addIndex != -1) {
componentData[addIndex][row] = data
if (callEvent) temporaryEntity { componentAddEvent ->
- componentAddEvent.addRelation(componentId.toGeary(), noEvent = true)
+ componentAddEvent.addRelation(geary.components.updatedComponent, componentId, noEvent = true)
eventRunner.callEvent(record, records[componentAddEvent], null)
}
return false
}
//if component is not already added, add it, then set
- val moveTo =
- if (contains(dataComponent.withoutRole(HOLDS_DATA)))
- this + dataComponent
- else this + dataComponent.withoutRole(HOLDS_DATA) + dataComponent
- val newCompIndex = moveTo.dataHoldingType.indexOf(dataComponent)
- val componentData = getComponents(row, add = data to newCompIndex)
-
- val entity = record.entity
+ val entityId = ids[row]
+ val moveTo = this + dataComponent
+ moveTo.moveWithNewComponent(record, data, dataComponent, entityId)
removeEntity(row)
- moveTo.addEntityWithData(record, componentData, entity)
- if (callEvent) temporaryEntity { componentAddEvent ->
- componentAddEvent.addRelation(componentId.toGeary(), noEvent = true)
- eventRunner.callEvent(record, records[componentAddEvent], null)
+ if (callEvent && moveTo.targetListeners.isNotEmpty()) {
+ // Archetype for the set event
+ val eventArc = archetypeProvider.getArchetype(
+ GearyEntityType(
+ ulongArrayOf(
+ Relation.of(
+ geary.components.setComponent,
+ componentId
+ ).id
+ )
+ )
+ )
+ if (eventArc.eventListeners.isNotEmpty()) {
+ temporaryEntity { componentAddEvent ->
+ componentAddEvent.addRelation(geary.components.setComponent, componentId, noEvent = true)
+ eventRunner.callEvent(record, records[componentAddEvent], null)
+ }
+ }
}
return true
}
@@ -233,45 +296,34 @@ data class Archetype(
internal fun removeComponent(
record: Record,
component: ComponentId
- ): Boolean = synchronized(record) {
+ ): Boolean {
with(record.archetype) {
val row = record.row
+ val entityId = ids[row]
- if (component !in type) return@synchronized false
+ if (component !in type) return false
val moveTo = this - component
- val skipData = indexOf(component)
- val copiedData =
- if (component.holdsData())
- (Array((componentData.size - 1).coerceAtLeast(0)) {}).also { data ->
- for (i in 0 until skipData) data[i] = componentData[i][row]
- for (i in (skipData + 1)..componentData.lastIndex) data[i - 1] = componentData[i][row]
- }
- else (Array(componentData.size) {}).also { data ->
- for (i in 0..componentData.lastIndex) data[i] = componentData[i][row]
- }
- val entity = record.entity
+ moveTo.moveWithoutComponent(record, component, entityId)
removeEntity(row)
- moveTo.addEntityWithData(record, copiedData, entity)
}
- return@synchronized true
+ return true
}
/** Gets all the components associated with an entity at a [row]. */
- internal fun getComponents(row: Int, add: Pair? = null): Array =
- synchronized(entityAddition) {
- if (add != null) {
- val arr = Array(componentData.size + 1) { null }
- val (addElement, addIndex) = add
- for (i in 0 until addIndex) arr[i] = componentData[i][row]
- arr[addIndex] = addElement
- for (i in addIndex..componentData.lastIndex) arr[i + 1] = componentData[i][row]
- @Suppress("UNCHECKED_CAST") // For loop above ensures no nulls
- return arr as Array
- } else
- return Array(componentData.size) { i: Int -> componentData[i][row] }
- }
+ internal fun getComponents(row: Int, add: Pair? = null): Array {
+ if (add != null) {
+ val arr = Array(componentData.size + 1) { null }
+ val (addElement, addIndex) = add
+ for (i in 0 until addIndex) arr[i] = componentData[i][row]
+ arr[addIndex] = addElement
+ for (i in addIndex..componentData.lastIndex) arr[i + 1] = componentData[i][row]
+ @Suppress("UNCHECKED_CAST") // For loop above ensures no nulls
+ return arr as Array
+ } else
+ return Array(componentData.size) { i: Int -> componentData[i][row] }
+ }
/**
* Queries for specific relations or by kind/target.
@@ -288,21 +340,30 @@ data class Archetype(
val specificTarget = target and ENTITY_MASK != geary.components.any
return when {
specificKind && specificTarget -> listOf(Relation.of(kind, target))
- specificTarget -> relationsByTarget[target.toLong()]
- specificKind -> relationsByKind[kind.toLong()]
+ specificTarget -> getRelationsByTarget(target)
+ specificKind -> getRelationsByKind(kind)
else -> relations
- }?.run { //TODO this technically doesnt need to run when specificKind is set
+ }.run { //TODO this technically doesnt need to run when specificKind is set
if (kind.hasRole(HOLDS_DATA)) filter { it.hasRole(HOLDS_DATA) } else this
- }?.run {
+ }.run {
if (target.holdsData()) filter { it.target.withRole(HOLDS_DATA) in type } else this
- } ?: emptyList()
+ }
}
-// internal fun scheduleRemoveRow(row: Int) {
-// synchronized(queueRemoval) {
-// queuedRemoval.add(row)
-// }
-// }
+ internal fun readRelationDataFor(
+ row: Int,
+ kind: ComponentId,
+ target: EntityId,
+ relations: List
+ ): List> {
+ return relations.map { relation ->
+ RelationWithData(
+ data = if (kind.hasRole(HOLDS_DATA)) this[row, relation.id] else null,
+ targetData = if (target.hasRole(HOLDS_DATA)) this[row, relation.target.withRole(HOLDS_DATA)] else null,
+ relation = relation
+ )
+ }
+ }
/**
* Removes the entity at a [row] in this archetype, notifying running archetype iterators.
@@ -331,8 +392,8 @@ data class Archetype(
// ==== Event listeners ====
/** Adds an event [handler] that listens to certain events relating to entities in this archetype. */
- fun addEventHandler(handler: Handler) {
- _eventHandlers += handler
+ fun addEventListener(handler: Listener) {
+ _eventListeners += handler
}
fun addSourceListener(handler: Listener) {
@@ -342,32 +403,4 @@ data class Archetype(
fun addTargetListener(handler: Listener) {
_targetListeners += handler
}
-
- // ==== Iterators ====
-
-// /** Stops tracking a running [iterator]. */
-// internal fun finalizeIterator(iterator: ArchetypeIterator) {
-// runningIterators.remove(iterator)
-// }
-
- /** Creates and tracks an [ArchetypeIterator] for a query. */
- @PublishedApi
- internal fun iteratorFor(query: GearyQuery): ArchetypeIterator {
- return ArchetypeIterator(this, query)
- }
-
-// /** Removes any queued up entity deletions. */
-// @PublishedApi
-// internal fun cleanup() {
-// synchronized(queueRemoval) {
-// if (!isIterating)
-// queuedRemoval.sort()
-// // Since the rows were added in order while iterating, the list is always sorted,
-// // so we don't worry about moving rows
-// while (queuedRemoval.isNotEmpty()) {
-// val last = queuedRemoval.removeLast()
-// removeEntity(last)
-// }
-// }
-// }
}
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 6e490c7a3..4cb928452 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
@@ -24,8 +24,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 suspend fun RepeatingSystem.runSystem() {
- doTick()
+ protected open fun RepeatingSystem.runSystem() {
+ tickAll()
}
override fun scheduleSystemTicking() {
@@ -38,23 +38,18 @@ open class ArchetypeEngine(override val tickDuration: Duration) : TickingEngine(
}
}
- override suspend fun tick(currentTick: Long): Unit = coroutineScope {
+ override fun tick(currentTick: Long) {
// Create a job but don't start it
- val tickJob = launch(start = CoroutineStart.LAZY) {
- pipeline.getRepeatingInExecutionOrder()
- .filter { currentTick % (it.interval / tickDuration).toInt().coerceAtLeast(1) == 0L }
- .also { logger.v("Ticking engine with systems $it") }
- .forEach { system ->
- runCatching {
- system.runSystem()
- }.onFailure {
- logger.e("Error while running system ${system::class.simpleName}")
- it.printStackTrace()
- }
+ pipeline.getRepeatingInExecutionOrder()
+ .filter { currentTick % (it.interval / tickDuration).toInt().coerceAtLeast(1) == 0L }
+ .also { logger.v("Ticking engine with systems $it") }
+ .forEach { system ->
+ runCatching {
+ system.runSystem()
+ }.onFailure {
+ logger.e("Error while running system ${system::class.simpleName}")
+ it.printStackTrace()
}
- }
-
- // Tick all systems
- tickJob.join()
+ }
}
}
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 09859f5a5..2704f6660 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
@@ -1,12 +1,13 @@
package com.mineinabyss.geary.engine.archetypes
-import com.mineinabyss.geary.modules.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.contains
-import com.mineinabyss.geary.systems.accessors.RawAccessorDataScope
+import com.mineinabyss.geary.modules.archetypes
+import com.mineinabyss.geary.systems.Listener
class ArchetypeEventRunner : EventRunner {
private val records: TypeMap get() = archetypes.records
@@ -20,49 +21,20 @@ class ArchetypeEventRunner : EventRunner {
val origTargetArc = target.archetype
val origSourceArc = source?.archetype
- //TODO performance upgrade will come when we figure out a solution in QueryManager as well.
- for (handler in origEventArc.eventHandlers) {
- // If an event handler has moved the entity to a new archetype, make sure we follow it.
- val (targetArc, targetRow) = target
- val (eventArc, eventRow) = event
- val sourceArc = source?.archetype
- val sourceRow = source?.row
-
- // If there's no source but the handler needs a source, skip
- if (source == null && !handler.parentListener.source.isEmpty) continue
-
- // Check that this handler has a listener associated with it.
- if (!handler.parentListener.target.isEmpty && handler.parentListener !in targetArc.targetListeners) continue
- if (sourceArc != null && !handler.parentListener.source.isEmpty && handler.parentListener !in sourceArc.sourceListeners) continue
-
- // Check that we still match the data if archetype of any involved entities changed.
- if (targetArc != origTargetArc && targetArc.type !in handler.parentListener.target.family) continue
- if (eventArc != origEventArc && eventArc.type !in handler.parentListener.event.family) continue
- if (sourceArc != origSourceArc && eventArc.type !in handler.parentListener.source.family) continue
+ // triple intersection of listeners
+ val listeners: Set = origTargetArc.targetListeners.toMutableSet().apply {
+ retainAll(origEventArc.eventListeners)
+ retainAll { it.source.isEmpty || (origSourceArc != null && it in origSourceArc.sourceListeners) }
+ }
- val listenerName = handler.parentListener::class.simpleName
- val targetScope = runCatching {
- RawAccessorDataScope(
- archetype = targetArc,
- perArchetypeData = handler.parentListener.target.cacheForArchetype(targetArc),
- row = targetRow,
- )
- }.getOrElse { throw IllegalStateException("Failed while reading target scope on $listenerName", it) }
- val eventScope = runCatching {
- RawAccessorDataScope(
- archetype = eventArc,
- perArchetypeData = handler.parentListener.event.cacheForArchetype(eventArc),
- row = eventRow,
- )
- }.getOrElse { throw IllegalStateException("Failed while reading event scope on $listenerName", it) }
- val sourceScope = if (source == null) null else runCatching {
- RawAccessorDataScope(
- archetype = sourceArc!!,
- perArchetypeData = handler.parentListener.source.cacheForArchetype(sourceArc),
- row = sourceRow!!,
- )
- }.getOrElse { throw IllegalStateException("Failed while reading source scope on $listenerName", it) }
- handler.processAndHandle(sourceScope, targetScope, eventScope)
+ for (listener in listeners) {
+ val pointers: Records = when (source) {
+ null -> Records(RecordPointer(target), RecordPointer(event), null)
+ else -> Records(RecordPointer(target), RecordPointer(event), RecordPointer(source))
+ }
+ with(listener) {
+ pointers.handle()
+ }
}
}
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeIterator.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeIterator.kt
deleted file mode 100644
index f6a3d31f6..000000000
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ArchetypeIterator.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-package com.mineinabyss.geary.engine.archetypes
-
-import com.mineinabyss.geary.systems.accessors.AccessorHolder
-import com.mineinabyss.geary.systems.accessors.RawAccessorDataScope
-import com.mineinabyss.geary.systems.accessors.TargetScope
-
-@PublishedApi
-internal data class ArchetypeIterator(
- val archetype: Archetype,
- val holder: AccessorHolder,
-) {
- val perArchCache = holder.cacheForArchetype(archetype)
- var row: Int = 0
-
- inline fun forEach(upTo: Int, crossinline run: (TargetScope) -> Unit) {
- while (row < archetype.size && row <= upTo) {
- val dataScope = RawAccessorDataScope(
- archetype = archetype,
- row = row++,
- perArchetypeData = perArchCache
- )
- holder.forEachCombination(dataScope) { data ->
- run(
- TargetScope(
- entity = dataScope.entity,
- data = data
- )
- )
- }
- }
- //FIXME clean up removed components
- }
-}
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 b45fe99c1..285a17d0b 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
@@ -4,7 +4,6 @@ import com.mineinabyss.geary.datatypes.Entity
import com.mineinabyss.geary.datatypes.family.Family
import com.mineinabyss.geary.datatypes.maps.Family2ObjectArrayMap
import com.mineinabyss.geary.engine.QueryManager
-import com.mineinabyss.geary.events.Handler
import com.mineinabyss.geary.helpers.contains
import com.mineinabyss.geary.systems.Listener
import com.mineinabyss.geary.systems.query.GearyQuery
@@ -13,18 +12,26 @@ class ArchetypeQueryManager : QueryManager {
private val queries = mutableListOf()
private val sourceListeners = mutableListOf()
private val targetListeners = mutableListOf()
- private val eventHandlers = mutableListOf()
+ private val eventListeners = mutableListOf()
private val archetypes = Family2ObjectArrayMap()
override fun trackEventListener(listener: Listener) {
- com.mineinabyss.geary.systems.trackEventListener(
- listener,
- sourceListeners,
- targetListeners,
- archetypes,
- eventHandlers
- )
+ val eventFamilyMatch = archetypes.match(listener.event.family)
+ for (archetype in eventFamilyMatch) archetype.addEventListener(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)
+ for (archetype in sourcesMatched) archetype.addSourceListener(listener)
+ sourceListeners.add(listener)
+ }
+ if (!listener.target.isEmpty) {
+ val targetsMatched = archetypes.match(listener.target.family)
+ for (archetype in targetsMatched) archetype.addTargetListener(listener)
+ targetListeners.add(listener)
+ }
}
override fun trackQuery(query: GearyQuery) {
@@ -40,11 +47,11 @@ class ArchetypeQueryManager : QueryManager {
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 matchedHandlers = eventHandlers.filter { archetype.type in it.parentListener.event.family }
+ val matchedEvents = eventListeners.filter { archetype.type in it.event.family }
matchedSources.forEach { archetype.addSourceListener(it) }
matchedTargets.forEach { archetype.addTargetListener(it) }
- matchedHandlers.forEach { archetype.addEventHandler(it) }
+ matchedEvents.forEach { archetype.addEventListener(it) }
matched.forEach { it.matchedArchetypes += archetype }
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ComponentAsEntityProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ComponentAsEntityProvider.kt
index 45c7d40a6..5cc8d1013 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ComponentAsEntityProvider.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/ComponentAsEntityProvider.kt
@@ -1,10 +1,10 @@
package com.mineinabyss.geary.engine.archetypes
import com.mineinabyss.geary.components.ComponentInfo
-import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.datatypes.ComponentId
import com.mineinabyss.geary.datatypes.maps.ClassToComponentMap
import com.mineinabyss.geary.engine.ComponentProvider
+import com.mineinabyss.geary.modules.geary
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
import kotlin.reflect.KClass
@@ -33,7 +33,8 @@ class ComponentAsEntityProvider : ComponentProvider {
private fun registerComponentIdForClass(kClass: KClass<*>): ComponentId {
logger.v("Registering new component: ${kClass.simpleName}")
- val compEntity = entityProvider.create(initialComponents = listOf(ComponentInfo(kClass)))
+ val compEntity = entityProvider.create()
+ compEntity.set(ComponentInfo(kClass), noEvent = true)
classToComponentMap[kClass] = compEntity.id
return compEntity.id
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt
index 67efa15e2..56a49ff9c 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/EntityByArchetypeProvider.kt
@@ -1,44 +1,43 @@
package com.mineinabyss.geary.engine.archetypes
-import com.mineinabyss.geary.components.CouldHaveChildren
-import com.mineinabyss.geary.components.events.EntityRemoved
-import com.mineinabyss.geary.components.events.SuppressRemoveEvent
-import com.mineinabyss.geary.datatypes.*
+import com.mineinabyss.geary.datatypes.Entity
+import com.mineinabyss.geary.datatypes.EntityStack
+import com.mineinabyss.geary.datatypes.EntityType
+import com.mineinabyss.geary.datatypes.GearyEntity
import com.mineinabyss.geary.datatypes.maps.TypeMap
import com.mineinabyss.geary.engine.EntityProvider
-import com.mineinabyss.geary.helpers.componentId
import com.mineinabyss.geary.helpers.parents
import com.mineinabyss.geary.helpers.removeParent
import com.mineinabyss.geary.helpers.toGeary
import com.mineinabyss.geary.modules.archetypes
+import com.mineinabyss.geary.modules.geary
import kotlinx.atomicfu.atomic
class EntityByArchetypeProvider(
private val reuseIDsAfterRemoval: Boolean = true,
) : EntityProvider {
- private val records: TypeMap get() = archetypes.records
- private val archetypeProvider: ArchetypeProvider get() = archetypes.archetypeProvider
+ private val records: TypeMap by lazy { archetypes.records }
+ private val archetypeProvider: ArchetypeProvider by lazy { archetypes.archetypeProvider }
private val removedEntities: EntityStack = EntityStack()
private val currId = atomic(0L)
- override fun create(initialComponents: Collection): GearyEntity {
+ override fun create(): GearyEntity {
val entity: GearyEntity = if (reuseIDsAfterRemoval) {
- runCatching { removedEntities.pop() }
- .getOrElse { currId.getAndIncrement().toGeary() }
+ removedEntities.pop() ?: currId.getAndIncrement().toGeary()
} else currId.getAndIncrement().toGeary()
- createRecord(entity, initialComponents)
+ createRecord(entity)
return entity
}
override fun remove(entity: Entity) {
- if (!entity.has()) entity.callEvent {
- add()
+ if (!entity.has(geary.components.suppressRemoveEvent)) entity.callEvent {
+ add(geary.components.entityRemoved)
}
// remove all children of this entity from the ECS as well
- if (entity.has()) entity.apply {
+ if (entity.has(geary.components.couldHaveChildren)) entity.apply {
children.forEach {
// Remove self from the child's parents or remove the child if it no longer has parents
if (it.parents == setOf(this)) it.removeEntity()
@@ -55,18 +54,9 @@ class EntityByArchetypeProvider(
override fun getType(entity: Entity): EntityType = records[entity].archetype.type
- private fun createRecord(entity: Entity, initialComponents: Collection) {
- val ids =
- initialComponents.map { componentId(it::class) } +
- initialComponents.map { componentId(it::class) or HOLDS_DATA }
-
- val addTo = archetypeProvider.getArchetype(EntityType(ids))
- val record = Record(archetypeProvider.rootArchetype, -1)
- addTo.addEntityWithData(
- record,
- initialComponents.toTypedArray().apply { sortBy { addTo.indexOf(componentId(it::class)) } },
- entity,
- )
- records[entity] = record
+ private fun createRecord(entity: Entity) {
+ val root = archetypeProvider.rootArchetype
+ val createdRecord = root.createWithoutData(entity)
+ records[entity] = createdRecord
}
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt
index 3fe35f614..d0687a896 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/SimpleArchetypeProvider.kt
@@ -1,8 +1,8 @@
package com.mineinabyss.geary.engine.archetypes
-import com.mineinabyss.geary.modules.archetypes
import com.mineinabyss.geary.datatypes.ComponentId
import com.mineinabyss.geary.datatypes.EntityType
+import com.mineinabyss.geary.modules.archetypes
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt
index b6330745e..7d23ddd40 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeMutateOperations.kt
@@ -1,10 +1,10 @@
package com.mineinabyss.geary.engine.archetypes.operations
-import com.mineinabyss.geary.modules.archetypes
import com.mineinabyss.geary.datatypes.*
import com.mineinabyss.geary.datatypes.maps.TypeMap
import com.mineinabyss.geary.engine.EntityMutateOperations
import com.mineinabyss.geary.engine.archetypes.ArchetypeProvider
+import com.mineinabyss.geary.modules.archetypes
class ArchetypeMutateOperations : EntityMutateOperations {
private val records: TypeMap get() = archetypes.records
@@ -42,8 +42,8 @@ class ArchetypeMutateOperations : EntityMutateOperations {
}
override fun clearEntity(entity: Entity) {
- val rec = records[entity]
- rec.archetype.removeEntity(rec.row)
- archetypeProvider.rootArchetype.addEntityWithData(rec, arrayOf(), entity)
+ val record = records[entity]
+ record.archetype.removeEntity(record.row)
+ archetypeProvider.rootArchetype.createWithoutData(entity, record)
}
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt
index 63ea08834..8f5bb1a25 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/archetypes/operations/ArchetypeReadOperations.kt
@@ -1,9 +1,9 @@
package com.mineinabyss.geary.engine.archetypes.operations
-import com.mineinabyss.geary.modules.archetypes
import com.mineinabyss.geary.datatypes.*
import com.mineinabyss.geary.datatypes.maps.TypeMap
import com.mineinabyss.geary.engine.EntityReadOperations
+import com.mineinabyss.geary.modules.archetypes
import com.mineinabyss.geary.systems.accessors.RelationWithData
class ArchetypeReadOperations : EntityReadOperations {
@@ -30,13 +30,8 @@ class ArchetypeReadOperations : EntityReadOperations {
target: EntityId,
): List> {
val (arc, row) = records[entity]
- return arc.getRelations(kind, target).map { relation ->
- RelationWithData(
- data = if (kind.hasRole(HOLDS_DATA)) arc[row, relation.id] else null,
- targetData = if (target.hasRole(HOLDS_DATA)) arc[row, relation.target.withRole(HOLDS_DATA)] else null,
- relation = relation
- )
- }
+
+ return arc.readRelationDataFor(row, kind, target, arc.getRelations(kind, target))
}
override fun getRelationsFor(entity: Entity, kind: ComponentId, target: EntityId): List =
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/CheckHandler.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/CheckHandler.kt
deleted file mode 100644
index 656023136..000000000
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/CheckHandler.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.mineinabyss.geary.events
-
-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.EventScope
-import com.mineinabyss.geary.systems.accessors.SourceScope
-import com.mineinabyss.geary.systems.accessors.TargetScope
-
-/**
- * A handler which will run a check on an event that requests one.
- */
-abstract class CheckHandler(
- parentListener: Listener,
- sourceNullable: Boolean
-) : Handler(parentListener, sourceNullable) {
- init {
- parentListener.event._family.has()
- }
-
- abstract fun check(source: SourceScope?, target: TargetScope, event: EventScope): Boolean
-
- override fun handle(source: SourceScope?, target: TargetScope, event: EventScope) {
- if (!check(source, target, event)) event.entity.apply {
- remove()
- add()
- }
- }
-}
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
new file mode 100644
index 000000000..17407836e
--- /dev/null
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/CheckingListener.kt
@@ -0,0 +1,26 @@
+package com.mineinabyss.geary.events
+
+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/events/Handler.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/Handler.kt
deleted file mode 100644
index 854893175..000000000
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/Handler.kt
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.mineinabyss.geary.events
-
-import com.mineinabyss.geary.modules.geary
-import com.mineinabyss.geary.systems.Listener
-import com.mineinabyss.geary.systems.accessors.EventScope
-import com.mineinabyss.geary.systems.accessors.RawAccessorDataScope
-import com.mineinabyss.geary.systems.accessors.SourceScope
-import com.mineinabyss.geary.systems.accessors.TargetScope
-
-/**
- * Generated within a [Listener]. Will handle events matching specified components on source/target/event entities.
- */
-abstract class Handler(
- val parentListener: Listener,
- val sourceNullable: Boolean,
-) {
- private val logger get() = geary.logger
-
- /** Runs when a matching event is fired. */
- abstract fun handle(source: SourceScope?, target: TargetScope, event: EventScope)
-
- /** Reads necessary data and iterates over combinations as appropriate, calling the [handle] function on each. */
- open fun processAndHandle(
- sourceScope: RawAccessorDataScope?,
- targetScope: RawAccessorDataScope,
- eventScope: RawAccessorDataScope,
- ) {
- if (!sourceNullable && sourceScope == null) return
- try {
- // Handle all combinations of data as needed
- parentListener.event.forEachCombination(eventScope) { eventData ->
- parentListener.target.forEachCombination(targetScope) { targetData ->
- val eventResult = EventScope(eventScope.entity, eventData)
- val targetResult = TargetScope(targetScope.entity, targetData)
- if (sourceScope != null) {
- parentListener.source.forEachCombination(sourceScope) { sourceData ->
- val sourceResult = SourceScope(sourceScope.entity, sourceData)
- handle(sourceResult, targetResult, eventResult)
- }
- } else handle(null, targetResult, eventResult)
- }
- }
- } catch (e: Exception) {
- logger.e("Failed to run event ${parentListener::class.simpleName}")
- e.printStackTrace()
- }
- }
-}
-
-
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/HandlerGearyAliases.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/HandlerGearyAliases.kt
deleted file mode 100644
index b9d5941e0..000000000
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/events/HandlerGearyAliases.kt
+++ /dev/null
@@ -1,4 +0,0 @@
-package com.mineinabyss.geary.events
-
-typealias GearyHandler = Handler
-typealias GearyCheckHandler = CheckHandler
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EngineHelpers.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EngineHelpers.kt
index c8749c3de..cf5877967 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EngineHelpers.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/EngineHelpers.kt
@@ -1,9 +1,8 @@
package com.mineinabyss.geary.helpers
import com.mineinabyss.geary.components.ComponentInfo
-import com.mineinabyss.geary.components.events.SuppressRemoveEvent
-import com.mineinabyss.geary.modules.geary
import com.mineinabyss.geary.datatypes.*
+import com.mineinabyss.geary.modules.geary
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.typeOf
@@ -19,7 +18,7 @@ inline fun temporaryEntity(
run: (Entity) -> T
): T {
val entity = entity {
- add(noEvent = true)
+ add(geary.components.suppressRemoveEvent, noEvent = true)
}
return try {
run(entity)
@@ -56,12 +55,3 @@ fun componentId(kClass: KClass): Nothing =
/** Gets the [ComponentInfo] component from a component's id. */
fun ComponentId.getComponentInfo(): ComponentInfo? =
this.toGeary().get()
-//@ExperimentalAsyncGearyAPI
-//public inline fun runSafely(
-// scope: CoroutineScope = globalContext.engine,
-// crossinline run: suspend () -> T
-//): Deferred {
-// val deferred = globalContext.engine.async(start = CoroutineStart.LAZY) { run() }
-// globalContext.engine.runSafely(scope, deferred)
-// return deferred
-//}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt
index 7db5b1f35..4a8da2c04 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/ArchetypeEngineModule.kt
@@ -2,7 +2,7 @@ package com.mineinabyss.geary.modules
import co.touchlab.kermit.Logger
import com.mineinabyss.geary.addons.GearyPhase
-import com.mineinabyss.geary.datatypes.maps.HashTypeMap
+import com.mineinabyss.geary.datatypes.maps.ArrayTypeMap
import com.mineinabyss.geary.engine.Components
import com.mineinabyss.geary.engine.PipelineImpl
import com.mineinabyss.geary.engine.archetypes.*
@@ -28,8 +28,9 @@ open class ArchetypeEngineModule(
override val write = ArchetypeMutateOperations()
override val entityProvider = EntityByArchetypeProvider()
override val componentProvider = ComponentAsEntityProvider()
+ override val defaults: Defaults = Defaults()
- val records = HashTypeMap()
+ val records = ArrayTypeMap()
val archetypeProvider = SimpleArchetypeProvider()
override val components by lazy { Components() }
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/Defaults.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/Defaults.kt
new file mode 100644
index 000000000..8c6db1774
--- /dev/null
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/Defaults.kt
@@ -0,0 +1,8 @@
+package com.mineinabyss.geary.modules
+
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+
+class Defaults(
+ val repeatingSystemInterval: Duration = 50.milliseconds,
+)
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 5cfcd7938..c93c82cdc 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
@@ -43,6 +43,6 @@ class GearyConfiguration {
* ```
*/
fun on(phase: GearyPhase, run: () -> Unit) {
- geary.pipeline.intercept(phase, run)
+ geary.pipeline.runOnOrAfter(phase, run)
}
}
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 11bf57f35..63712e924 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
@@ -41,6 +41,8 @@ interface GearyModule {
val eventRunner: EventRunner
val pipeline: Pipeline
+ val defaults: Defaults
+
operator fun invoke(configure: GearyConfiguration.() -> Unit) {
GearyConfiguration().apply(configure)
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt
index 3c7c28a8a..e99fc956d 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/modules/TestEngineModule.kt
@@ -10,10 +10,10 @@ import com.mineinabyss.geary.engine.archetypes.EntityByArchetypeProvider
*/
class TestEngineModule(
reuseIDsAfterRemoval: Boolean = true,
-): ArchetypeEngineModule() {
+) : ArchetypeEngineModule() {
override val entityProvider = EntityByArchetypeProvider(reuseIDsAfterRemoval)
- companion object: GearyModuleProviderWithDefault {
+ companion object : GearyModuleProviderWithDefault {
override fun init(module: TestEngineModule) {
ArchetypeEngineModule.init(module)
}
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 36693a187..a516c752a 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,14 +1,8 @@
package com.mineinabyss.geary.systems
-import com.mineinabyss.geary.datatypes.Component
-import com.mineinabyss.geary.datatypes.family.Family
-import com.mineinabyss.geary.datatypes.family.family
-import com.mineinabyss.geary.events.Handler
import com.mineinabyss.geary.helpers.componentId
import com.mineinabyss.geary.systems.accessors.*
-import com.mineinabyss.geary.systems.accessors.types.ComponentAccessor
-import com.mineinabyss.geary.systems.accessors.types.DirectAccessor
-import kotlin.reflect.KProperty
+import com.mineinabyss.geary.systems.accessors.type.ComponentAccessor
/**
* #### [Guide: Listeners](https://wiki.mineinabyss.com/geary/guide/listeners)
@@ -18,58 +12,52 @@ import kotlin.reflect.KProperty
* [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(), GearySystem, AccessorScopeSelector {
- val source: AccessorHolder = AccessorHolder()
+abstract class Listener : AccessorOperations(), System {
val target: AccessorHolder = AccessorHolder()
val event: AccessorHolder = AccessorHolder()
+ val source: AccessorHolder = AccessorHolder()
fun start() {
onStart()
}
- operator fun Accessor.getValue(thisRef: SourceScope, property: KProperty<*>): T = access(thisRef)
- operator fun Accessor.getValue(thisRef: TargetScope, property: KProperty<*>): T = access(thisRef)
- operator fun Accessor.getValue(thisRef: EventScope, property: KProperty<*>): T = access(thisRef)
-
- fun AccessorBuilder>.onSource(): ComponentAccessor =
- source.addAccessor { build(source, it) }
-
- fun AccessorBuilder>.onTarget(): ComponentAccessor =
- target.addAccessor { build(target, it) }
-
- fun AccessorBuilder>.onEvent(): ComponentAccessor =
- event.addAccessor { build(event, it) }
-
- fun Family.onSource(): DirectAccessor =
- source._family.add(this).let { DirectAccessor(this) }
+ 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 Family.onTarget(): DirectAccessor =
- target._family.add(this).let { DirectAccessor(this) }
+ 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 Family.onEvent(): DirectAccessor =
- event._family.add(this).let { DirectAccessor(this) }
+ 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 onSet(): AccessorBuilder> {
- return AccessorBuilder { holder, index ->
- event._family.onSet(componentId())
- get().build(holder, index)
- }
+ 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 onFirstSet(): AccessorBuilder> {
- return AccessorBuilder { holder, index ->
- event._family.onFirstSet(componentId())
- get().build(holder, index)
- }
+ inline fun , reified A> T.whenFirstSetOnTarget(): ReadWriteEntitySelectingAccessor {
+ event.mutableFamily.onFirstSet(componentId())
+ return this.on(target)
}
- //TODO support onAdd for relations
/** Fires when an entity has a component of type [T] added, updates are not considered since no data changes. */
- inline fun onAdd(): Family {
- event._family.onAdd(componentId())
- return family { has() }
+ inline fun , reified A> T.whenAddedOnTarget(): ReadWriteEntitySelectingAccessor {
+ event.mutableFamily.onAdd(componentId())
+ return this.on(event)
}
+
+ abstract fun Pointers.handle()
}
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
index 36161c8e8..cfbf0d887 100644
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/RepeatingSystem.kt
+++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/RepeatingSystem.kt
@@ -1,10 +1,9 @@
package com.mineinabyss.geary.systems
-import com.mineinabyss.geary.engine.archetypes.ArchetypeIterator
-import com.mineinabyss.geary.systems.accessors.TargetScope
-import com.mineinabyss.geary.systems.query.GearyQuery
+import com.mineinabyss.geary.modules.geary
+import com.mineinabyss.geary.systems.accessors.Pointer
+import com.mineinabyss.geary.systems.query.Query
import kotlin.time.Duration
-import kotlin.time.Duration.Companion.milliseconds
/**
* #### [Guide: Ticking systems](https://wiki.mineinabyss.com/geary/guide/ticking-systems)
@@ -12,34 +11,15 @@ import kotlin.time.Duration.Companion.milliseconds
* A system for the ECS that will run every [interval] ticks.
*
* @param interval How often to run this system in ticks.
- *
- * @see [ArchetypeIterator]
*/
abstract class RepeatingSystem(
- val interval: Duration = 50.milliseconds // 1 tick in Minecraft
-) : GearyQuery(), GearySystem {
- protected var iteration: Int = 0
- private set
-
+ val interval: Duration = geary.defaults.repeatingSystemInterval
+) : Query(), System {
override fun onStart() {}
- //TODO better name differentiation between this and tick
- fun doTick() {
- iteration++
- tick()
- }
-
- protected open fun tick() {
- fastForEach(run = { it.tick() })
+ open fun tickAll() {
+ forEach(run = { it.tick() })
}
- protected open fun TargetScope.tick() {}
-
- protected fun every(iterations: Int): Boolean =
- iteration.mod(iterations) == 0
-
- protected inline fun every(iterations: Int, run: () -> T): T? {
- if (every(iterations)) return run()
- return null
- }
+ protected open fun Pointer.tick() {}
}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/TrackEventListener.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/TrackEventListener.kt
deleted file mode 100644
index 333c5616a..000000000
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/TrackEventListener.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.mineinabyss.geary.systems
-
-import com.mineinabyss.geary.datatypes.maps.Family2ObjectArrayMap
-import com.mineinabyss.geary.engine.archetypes.Archetype
-import com.mineinabyss.geary.events.Handler
-
-internal expect fun trackEventListener(
- listener: Listener,
- sourceListeners: MutableList,
- targetListeners: MutableList,
- archetypes: Family2ObjectArrayMap,
- eventHandlers: MutableList,
-)
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/Accessor.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/Accessor.kt
deleted file mode 100644
index 86692ee0f..000000000
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/Accessor.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.mineinabyss.geary.systems.accessors
-
-interface Accessor {
- fun access(scope: ResultScope): T
-}
diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorBuilder.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorBuilder.kt
deleted file mode 100644
index 9cc5475f0..000000000
--- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/systems/accessors/AccessorBuilder.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.mineinabyss.geary.systems.accessors
-
-/**
- * A builder that can provide an accessor for [AccessorHolder]s.
- *
- * @see Accessor
- */
-fun interface AccessorBuilder> {
- //TODO we shouldn't expose index here since not all Accessors have one
- /** Provides an [Accessor] to a [holder] and [index] this accessor should be placed in for that holder. */
- fun build(holder: AccessorHolder, index: Int): T
-}
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
index 454cd93c8..32c0e7e50 100644
--- 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
@@ -2,12 +2,6 @@ package com.mineinabyss.geary.systems.accessors
import com.mineinabyss.geary.datatypes.family.Family
import com.mineinabyss.geary.datatypes.family.MutableFamily
-import com.mineinabyss.geary.engine.archetypes.Archetype
-import com.mineinabyss.geary.systems.accessors.types.DirectAccessor
-import com.mineinabyss.geary.systems.accessors.types.IndexedAccessor
-import com.soywiz.kds.FastIntMap
-import com.soywiz.kds.getOrPut
-import kotlin.reflect.KProperty
/**
@@ -15,56 +9,12 @@ import kotlin.reflect.KProperty
*
* @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() = _family
+open class AccessorHolder : AccessorOperations() {
+ val family: Family.Selector.And get() = mutableFamily
@PublishedApi
- internal val _family: MutableFamily.Selector.And = MutableFamily.Selector.And()
+ internal val mutableFamily: MutableFamily.Selector.And = MutableFamily.Selector.And()
- @PublishedApi
- internal open val accessors: MutableList> = mutableListOf()
- private val perArchetypeCache = FastIntMap>>()
-
- operator fun > AccessorBuilder.provideDelegate(
- thisRef: Any,
- property: KProperty<*>
- ): T = addAccessor { build(this@AccessorHolder, it) }
-
- inline fun > addAccessor(create: (index: Int) -> T): T {
- val accessor = create(accessors.size)
- when (accessor) {
- is IndexedAccessor<*> -> accessors += accessor
- is DirectAccessor<*> -> {}
- else -> error("Accessor from unknown backend, ignoring.")
- }
- return accessor
- }
-
- /** Calculates, or gets cached values for an [archetype] */
- fun cacheForArchetype(archetype: Archetype): List> =
- perArchetypeCache.getOrPut(archetype.id) {
- val accessorCache: List> = accessors.map { it.cached.mapTo(mutableListOf()) { null } }
- val cache = ArchetypeCacheScope(archetype, accessorCache)
-
- for (accessor in accessors)
- for (it in accessor.cached)
- accessorCache[accessor.index][it.cacheIndex] =
- it.run { cache.calculate() }
-
- accessorCache
- }
-
- /** Iterates over data in [dataScope] with all possible combinations calculated by accessors in this holder. */
- @PublishedApi
- internal inline fun forEachCombination(dataScope: RawAccessorDataScope, run: (List<*>) -> Unit) {
- // All sets of data each accessor wants. Will iterate over all combinations of items from each list.
- val data: List> = accessors.map { with(it) { dataScope.readData() } }
- // The total number of combinations that can be made with all elements in each list.
- val totalCombinations = data.fold(1) { acc, b -> acc * b.size }
- for (permutation in 0 until totalCombinations) {
- run(data.map { it[permutation % it.size] })
- }
- }
/** 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 dc35b663a..6c17fafbe 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
@@ -2,39 +2,55 @@ 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.helpers.componentId
import com.mineinabyss.geary.helpers.componentIdWithNullable
-import com.mineinabyss.geary.systems.accessors.types.ComponentAccessor
-import com.mineinabyss.geary.systems.accessors.types.ComponentOrDefaultAccessor
-import com.mineinabyss.geary.systems.accessors.types.RelationWithDataAccessor
+import com.mineinabyss.geary.systems.accessors.type.*
+import kotlin.reflect.KProperty
-/**
- * An empty interface that limits [AccessorBuilder] helper functions only to classes that use [Accessor]s.
- */
open class AccessorOperations {
- /** Gets a component, ensuring it is on the entity. */
- inline fun get(): AccessorBuilder> {
- return AccessorBuilder { holder, index ->
- val component = componentId().withRole(HOLDS_DATA)
- holder._family.has(component)
- ComponentAccessor(index, component)
- }
+ /** Accesses a component, ensuring it is on the entity. */
+ inline fun get(): ComponentAccessor {
+ return NonNullComponentAccessor(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)
+ }
+
+ /**
+ * 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)
}
- /** Gets a component or provides a [default] if the entity doesn't have it. */
- inline fun getOrDefault(
- default: T
- ): AccessorBuilder> {
- return AccessorBuilder { _, index ->
- val component = componentId().withRole(HOLDS_DATA)
- ComponentOrDefaultAccessor(index, component, default)
+ /**
+ * 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)
+ }
+
+ /** 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
+
+ override fun getValue(thisRef: Pointer, property: KProperty<*>): U {
+ val value = this@map.getValue(thisRef, property)
+ return mapping(value)
+ }
}
}
- /** Gets a component or `null` if the entity doesn't have it. */
- inline fun getOrNull(): AccessorBuilder> {
- return getOrDefault(null)
+ /** Accesses a component or `null` if the entity doesn't have it. */
+ fun ComponentAccessor