Skip to content

Commit

Permalink
Merge pull request #94 from MineInAbyss/develop
Browse files Browse the repository at this point in the history
Benchmarks, performance improvements
  • Loading branch information
0ffz authored Dec 9, 2023
2 parents 5309cdc + 0629df9 commit b71c7eb
Show file tree
Hide file tree
Showing 125 changed files with 2,055 additions and 1,492 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -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
Expand Down
35 changes: 9 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,7 @@

## Overview

<details>
<summary> :warning: Notice: Looking for a new maintainer </summary>

> 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
</details>

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
Expand All @@ -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<Position>()
val TargetScope.velocity by get<Velocity>()
val Pointer.position by get<Position>()
val Pointer.velocity by get<Velocity>()

override fun TargetScope.tick() {
override fun Pointer.tick() {
// We can access our components like regular variables!
position.x += velocity.x
position.y += velocity.y
Expand All @@ -61,17 +51,13 @@ fun main() {
entity {
setAll(Position(0.0, 0.0), Velocity(1.0, 0.0))
}

// Systems are queries!
val positions: List<Position> = 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 {
Expand All @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ interface AutoScanner {
}

override fun AutoScanner.install() {
geary.pipeline.intercept(GearyPhase.INIT_SYSTEMS) {
geary.pipeline.runOnOrAfter(GearyPhase.INIT_SYSTEMS) {
installSystems()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Expiry, Any?>()
private val Pointer.expiry by getRelationsWithData<Expiry, Any?>()

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)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ interface Prefabs {
ParseRelationWithDataSystem(),
TrackPrefabsByKeySystem(),
)
geary.pipeline.intercept(GearyPhase.INIT_ENTITIES) {
geary.pipeline.runOnOrAfter(GearyPhase.INIT_ENTITIES) {
loader.loadPrefabs()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ChildOnPrefab>().onTarget()
private var Pointers.child by get<ChildOnPrefab>().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<ChildOnPrefab>()
child = null
}
}

class ParseChildrenOnPrefab : Listener() {
private val TargetScope.children by onSet<ChildrenOnPrefab>().onTarget()
private var Pointers.children by get<ChildrenOnPrefab>().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<NoInherit, Prefab>()
setAll(components)
}
}
entity.remove<ChildrenOnPrefab>()
children = null
}
}
Original file line number Diff line number Diff line change
@@ -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<RelationOnPrefab>().onTarget()
private var Pointers.relation by get<RelationOnPrefab>().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<RelationOnPrefab>()
relation = null
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<RelationWithData<*, *>>().onTarget()
private val Records.relationWithData by get<RelationWithData<*, *>>().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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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<PrefabLoaded>() }.onEvent()
private val Pointers.loaded by family { has<PrefabLoaded>() }.on(event)

@Handler
private fun TargetScope.inheritOnLoad() {
entity.inheritPrefabs()
@OptIn(UnsafeAccessors::class)
override fun Pointers.handle() {
target.entity.inheritPrefabs()
}
}

Original file line number Diff line number Diff line change
@@ -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<PrefabKey>().onTarget()
private val Records.key by get<PrefabKey>().whenSetOnTarget()

@Handler
private fun TargetScope.registerOnSet() {
prefabs.manager.registerPrefab(key, entity)
entity.addRelation<NoInherit, PrefabKey>()
@OptIn(UnsafeAccessors::class)
override fun Pointers.handle() {
prefabs.manager.registerPrefab(key, target.entity)
target.entity.addRelation<NoInherit, PrefabKey>()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ interface SerializableComponents {
}

override fun Builder.install() {
geary.pipeline.intercept(GearyPhase.ADDONS_CONFIGURED) {
geary.pipeline.runOnOrAfter(GearyPhase.ADDONS_CONFIGURED) {
DI.add<SerializableComponents>(object : SerializableComponents {
override val serializers = serializersBuilder.build()
override val formats = formatsBuilder.build(serializers)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class FormatsBuilder {
binaryFormat = Cbor {
serializersModule = serializers.module
encodeDefaults = false
ignoreUnknownKeys = true
},
formats = formats.mapValues { it.value(serializers.module) },
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Uuid>().onTarget()
var Pointers.uuid by get<Uuid>().whenSetOnTarget()
val Pointers.regenerateUUIDOnClash by get<RegenerateUUIDOnClash>().orNull().on(target)

@Handler
private fun TargetScope.track() {
@OptIn(UnsafeAccessors::class)
override fun Pointers.handle() {
if (uuid in uuid2Geary)
if (entity.has<RegenerateUUIDOnClash>()) {
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
}
}

Original file line number Diff line number Diff line change
@@ -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<Uuid>().onTarget()
private val EventScope.removed by family { has<EntityRemoved>() }.onEvent()
private val Pointers.uuid by get<Uuid>().on(target)
private val Pointers.removed by family { has<EntityRemoved>() }.on(event)

@Handler
private fun TargetScope.untrack() {
override fun Pointers.handle() {
uuid2Geary.remove(uuid)
}
}
1 change: 1 addition & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ allprojects {
subprojects {
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions.freeCompilerArgs += "-opt-in=kotlinx.serialization.ExperimentalSerializationApi"
// kotlinOptions.freeCompilerArgs += "-Xno-param-assertions"
}
}

Expand Down
Loading

0 comments on commit b71c7eb

Please sign in to comment.