Skip to content

Commit

Permalink
Improve performance with entity removal until child queries are improved
Browse files Browse the repository at this point in the history
  • Loading branch information
0ffz committed Aug 7, 2022
1 parent b385351 commit d2199d9
Show file tree
Hide file tree
Showing 8 changed files with 33 additions and 14 deletions.
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.mineinabyss.geary.components

public sealed class CouldHaveChildren
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,9 @@ internal class Family2ObjectArrayMap<T> {
}

fun match(family: Family): List<T> {
val matchingElements = mutableListOf<T>()
getMatchingBits(family, null)?.forEachBit { matchingElements += elements[it] }
?: return elements.toList()

val bits = getMatchingBits(family, null) ?: return elements.toList()
val matchingElements = ArrayList<T>(bits.cardinality)
bits.forEachBit { matchingElements += elements[it] }
return matchingElements
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Record> = 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())
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*
Expand Down Expand Up @@ -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()
}
}
Expand Down Expand Up @@ -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<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(event)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CouldHaveChildren>() // TODO temporarily in place until child queries are faster
addRelation<ChildOf>(parent)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit d2199d9

Please sign in to comment.