From d2199d93bbe67156c3cc9b888755e6c8b20f2033 Mon Sep 17 00:00:00 2001 From: 0ffz Date: Sun, 7 Aug 2022 15:53:49 -0400 Subject: [PATCH] Improve performance with entity removal until child queries are improved --- README.md | 14 +++++++++----- .../geary/components/CouldHaveChildren.kt | 3 +++ .../com/mineinabyss/geary/datatypes/BitSet.kt | 1 + .../geary/datatypes/maps/Family2ObjectArrayMap.kt | 7 +++---- .../mineinabyss/geary/datatypes/maps/TypeMap.kt | 11 +++++++---- .../com/mineinabyss/geary/engine/GearyEngine.kt | 5 ++++- .../com/mineinabyss/geary/helpers/Relationship.kt | 2 ++ .../com/mineinabyss/geary/datatypes/BitSet.kt | 4 ++++ 8 files changed, 33 insertions(+), 14 deletions(-) create mode 100644 geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/CouldHaveChildren.kt diff --git a/README.md b/README.md index 9e5ed01b5..ecb3502cd 100644 --- a/README.md +++ b/README.md @@ -12,11 +12,6 @@ Geary is an Entity Component System (ECS) written in Kotlin and designed for Minecraft server plugins. The engine design is inspired by [flecs](https://github.com/SanderMertens/flecs) and uses archetypes for data storage. -We have two PaperMC plugins using this ECS to add Minecraft-specific functionality: -- [Mobzy](https://github.com/MineInAbyss/Mobzy) - Custom entities that bridge ECS and Minecraft's very inheritance based entities. -- [Looty](https://github.com/MineInAbyss/Looty) - Custom, highly configurable items. -- [Blocky](https://github.com/MineInAbyss/Blocky) - Custom blocks, furniture and more. - ## Features ### kotlinx.serialization backend @@ -33,6 +28,15 @@ Idiomatic Kotlin syntax for instantiating entities, iterating over them in syste Prefabs allow you to reuse components between multiple entities of the same type. The addon DSL allows you to specify a folder to automatically load prefabs from. These may be written in YAML, JSON, and eventually any other backend supported by ktx.serialization. +### Plugins using geary + +Consider having a look at our other projects currently using Geary. + +- [Mobzy](https://github.com/MineInAbyss/Mobzy) - Custom entities that bridge ECS and Minecraft's very inheritance based entities +- [Looty](https://github.com/MineInAbyss/Looty) - Custom, highly configurable items +- [Blocky](https://github.com/MineInAbyss/Blocky) - Custom blocks, furniture and more +- [Chatty](https://github.com/MineInAbyss/Chatty) - Customizes chat messages with MiniMessage support + ## Examples Start with some sample components (note we can make them persistent by adding some annotations): diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/CouldHaveChildren.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/CouldHaveChildren.kt new file mode 100644 index 000000000..100f4cc0f --- /dev/null +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/components/CouldHaveChildren.kt @@ -0,0 +1,3 @@ +package com.mineinabyss.geary.components + +public sealed class CouldHaveChildren diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/BitSet.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/BitSet.kt index 7b9403142..ae5fd1b91 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/BitSet.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/BitSet.kt @@ -16,6 +16,7 @@ public expect class BitSet() { public fun or(other: BitSet) public fun xor(other: BitSet) public fun clear() + public val cardinality: Int public inline fun forEachBit(crossinline loop: (Int) -> Unit) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/Family2ObjectArrayMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/Family2ObjectArrayMap.kt index e45bb4a59..f3571ca55 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/Family2ObjectArrayMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/Family2ObjectArrayMap.kt @@ -89,10 +89,9 @@ internal class Family2ObjectArrayMap { } fun match(family: Family): List { - val matchingElements = mutableListOf() - getMatchingBits(family, null)?.forEachBit { matchingElements += elements[it] } - ?: return elements.toList() - + val bits = getMatchingBits(family, null) ?: return elements.toList() + val matchingElements = ArrayList(bits.cardinality) + bits.forEachBit { matchingElements += elements[it] } return matchingElements } } diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt index e9542b857..39d4dc81f 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/datatypes/maps/TypeMap.kt @@ -3,25 +3,28 @@ package com.mineinabyss.geary.datatypes.maps import com.mineinabyss.geary.datatypes.GearyEntity import com.mineinabyss.geary.datatypes.Record import com.soywiz.kds.* +import kotlinx.atomicfu.locks.SynchronizedObject +import kotlinx.atomicfu.locks.synchronized public class TypeMap { -// private val lock = SynchronizedObject() + private val lock = SynchronizedObject() private val map: FastIntMap = FastIntMap() // 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. @PublishedApi - internal fun get(entity: GearyEntity): Record = map[entity.id.toInt()] + internal fun get(entity: GearyEntity): Record = synchronized(lock) { map[entity.id.toInt()] } ?: error("Tried to access components on an entity that no longer exists (${entity.id})") @PublishedApi - internal fun set(entity: GearyEntity, record: Record) { + internal fun set(entity: GearyEntity, record: Record): Unit = synchronized(lock) { if (contains(entity)) error("Tried setting the record of an entity that already exists.") map[entity.id.toInt()] = record } - internal fun remove(entity: GearyEntity) = + internal fun remove(entity: GearyEntity) = synchronized(lock) { map.remove(entity.id.toInt()) + } public operator fun contains(entity: GearyEntity): Boolean = map.contains(entity.id.toInt()) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/GearyEngine.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/GearyEngine.kt index 85df03196..601be509b 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/GearyEngine.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/engine/GearyEngine.kt @@ -1,6 +1,7 @@ package com.mineinabyss.geary.engine import com.mineinabyss.geary.components.ComponentInfo +import com.mineinabyss.geary.components.CouldHaveChildren import com.mineinabyss.geary.components.events.EntityRemoved import com.mineinabyss.geary.context.QueryContext import com.mineinabyss.geary.datatypes.* @@ -123,12 +124,14 @@ public open class GearyEngine(override val tickDuration: Duration) : TickingEngi queryManager.trackQuery(system) registeredSystems.add(system) } + is GearyListener -> { if (system in registeredListeners) return system.start() queryManager.trackEventListener(system) registeredListeners.add(system) } + else -> system.onStart() } } @@ -219,7 +222,7 @@ public open class GearyEngine(override val tickDuration: Duration) : TickingEngi if (event) entity.callEvent(EntityRemoved()) // remove all children of this entity from the ECS as well - entity.apply { + if (entity.has()) 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(event) diff --git a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/Relationship.kt b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/Relationship.kt index 95db187ee..721313882 100644 --- a/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/Relationship.kt +++ b/geary-core/src/commonMain/kotlin/com/mineinabyss/geary/helpers/Relationship.kt @@ -1,10 +1,12 @@ package com.mineinabyss.geary.helpers +import com.mineinabyss.geary.components.CouldHaveChildren import com.mineinabyss.geary.components.relations.ChildOf import com.mineinabyss.geary.datatypes.GearyEntity /** Adds a [parent] entity to this entity. */ public fun GearyEntity.addParent(parent: GearyEntity) { + parent.add() // TODO temporarily in place until child queries are faster addRelation(parent) } diff --git a/geary-core/src/jvmMain/kotlin/com/mineinabyss/geary/datatypes/BitSet.kt b/geary-core/src/jvmMain/kotlin/com/mineinabyss/geary/datatypes/BitSet.kt index ecd038bec..b67fc6def 100644 --- a/geary-core/src/jvmMain/kotlin/com/mineinabyss/geary/datatypes/BitSet.kt +++ b/geary-core/src/jvmMain/kotlin/com/mineinabyss/geary/datatypes/BitSet.kt @@ -56,6 +56,10 @@ public actual class BitSet { inner.clear() } + public actual val cardinality: Int + get() = inner.cardinality + + public actual inline fun forEachBit(crossinline loop: (Int) -> Unit) { // Roaring bitsets run into concurrent modification issues where clearing a bit might skip iterating another // so we have to clone the set.