Coder Social home page Coder Social logo

mineinabyss / geary Goto Github PK

View Code? Open in Web Editor NEW
47.0 5.0 11.0 2.04 MB

ECS framework made for Kotlin

Home Page: https://wiki.mineinabyss.com/geary/

License: MIT License

Kotlin 100.00%
ecs kotlinx-serialization spigot minecraft entity-component-system

geary's Introduction

Geary

Package Package Wiki Contribute

Overview

Geary is an Entity Component System (ECS) written in Kotlin. The engine design is inspired by flecs. Core parts of the engine like system iteration and entity creation are quite optimized, the main exception being our observer system. We use Geary internally for our Minecraft plugins, see geary-papermc for more info.

Features

  • Archetype based engine optimized for many entities with similar components
  • Type safe systems, queries, and event listeners
  • Null safe component access
  • Flecs-style entity relationships alice.addRelation<FriendsWith>(bob)
  • Observers for listening to component changes and custom events
  • Prefabs that reuse components across entities
  • Persistent components and loading prefabs from files thanks to kotlinx.serialization
  • Addon system to use only what you need

Usage

Read our Quickstart guide to see Geary in action, here's an excerpt, a simple velocity system:

data class Position(var x: Double, var y: Double)
data class Velocity(var x: Double, var y: Double)

fun GearyModule.updatePositionSystem() = system(query<Position, Velocity>())
    .every(interval = 20.milliseconds)
    .exec { (position, velocity) ->
        // We can access our components like regular variables!
        position.x += velocity.x
        position.y += velocity.y
    }


fun main() {
    // Set up geary
    geary(ArchetypeEngineModule) {
        // example engine configuration
        install(Prefabs)
    }

    val posSystem = geary.updatePositionSystem()

    // Create an entity the system can run on
    entity {
        setAll(Position(0.0, 0.0), Velocity(1.0, 0.0))
    }
    
    posSystem.tick() // exec just this system
    geary.engine.tick() // exec all registered repeating systems, interval used to calculate every n ticks to run
    
    val positions: List<Position> = posSystem.map { (pos) -> pos }
}

Gradle

repositories {
    maven("https://repo.mineinabyss.com/releases")
}

dependencies {
    val gearyVersion = "x.y.z"
    implementation("com.mineinabyss:geary-core:$gearyVersion")
    implementation("com.mineinabyss:geary-<addon-name>:$gearyVersion")
}

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:

  • Multiplatform support, with js, jvm, and native targets
  • Publish numbers for benchmarks and cover more parts of the engine with them
  • Relation queries, ex. entities with a parent that has X component.

geary's People

Contributors

0ffz avatar aidanf86 avatar boy0000 avatar derongan avatar devscyu avatar norazan avatar pixelfac avatar saloeater avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

geary's Issues

Looping and tweening values

Sometimes we want to iterate over the same action several times while also tweening certain values (especially for animations!).

For example we may want to fire an explosion that blasts out from the direction the player is looking.

Especially for particles, we could have many fancy functions (a sine wave for example) that define where to spawn them.

We should be able to easily reuse these patterns with composition, but there start to be issues regarding how to actually tween primitives without having a wrapper class around them.

Idea: Storing tween info on the entity

The main issue is that actions are deserialized once, after which we can only pass a reference of an entity to execute the action on. Thus, we can't tween a value unless we make a wrapper around it which has a getter that determines the value for a specific entity at a specific time.

This is really similar to how ConfigurableLocation works right now, but people may find creating these wrappers for primitive types like Int annoying, and avoid doing so.

For actually storing the loop data on the entity, we can use a similar approach to the CooldownManager and have a component that stores loop info (what iteration it's on, what sort of animation to play, etc...) under a certain key. Not sure if we could somehow autogenerate these keys for loops, but forcing users to define it shouldn't be too much of an issue.

refactor: Systems as entities

Track systems by creating entities for them. This allows us to hold data about how they should be executed (ex order) and react to changes live via listeners.

Also allows us to make temporary systems which may be useful for things like item or mob behaviour that should only be active while those things exist in the world (Could be useful for future scripting support.)

Add migrations for geary entities

In geary-1.0 we had the concept of migrating entities when the definition updated. This was put in place to prevent existing items breaking on an upgrade.

(see https://github.com/MineInAbyss/geary-1.0/blob/ebcfaa4deed2f28c523db9384b16375282eac344/src/main/java/com/mineinabyss/geary/ecs/entity/migration/GearyEntityMigration.java#L11)

Something to support the same use case is necessary for Geary.

Basic outline:

  1. Attach a (non static, persistent) component to every entity containing a simple version number for each component. (if version #0 do not serialize to keep component from bloating)
  2. Component providers provide a migration specific to component whenever a component needs to change. Migration handles updating from serialized form (because the class definition has probably changed). This might be a bit tricky.
  3. On loading an entity for the first time on a server restart (be it from PDC, or what have you) compare the version number for each component with the version number of the latest migration.
  4. On mismatch apply all registered migrations in order starting from the first one with a higher version #.

Improve issue templates

Move all the component specific ones to geary-addons and use new fancy github features for fancy templates

Random stat alterations

We want some way for specific values to be randomly chosen when an entity gets instantiated. For instance, we may want a relic to have slightly differing lengths so that players can constantly strive to find a slightly better version of a relic.

The main options I see right now is

  • Pre-parsing the yaml and deserializing from that (this makes it impossible to use static types for randomized values but is a quick and easy solution)
  • A component which stores the chosen values somehow and other components may essentially use wrappers for primitives which will store their value through that component.
  • Instantiating from a prefab, then using reflection to modify values based on info stored in another component.

Whatever we do, I'd really like to only store data on the chosen values in our entites, rather than serialize the entire component, since this means we can't use static types and have most of the migrations done automatically.

feat(pipeline): System and observer pipeline

A way to organize in what order systems should run.

I'd like:

  • Around 5 Stages for different types of tasks
  • Potentially requesting to run before/after another system, handling recursive dependencies.

setEngineServiceProvider should either be made obsolete or public

Any test cases requiring the geary engine need to call this test method or implement something similar.

This should be exposed.

Additionally, the fact that using a manually constructed concrete implementation of the geary engine requires a service provider is kind of silly. This should be refactored away.

Engines should provide a check for if an entity is present, at least in tests

I noticed in your code you do some hacky stuff by using Geary.getNextId to figure out if an entity has been removed. This is downright barbaric. It only makes sense if we assume that the engine always uses the lowest free slot for the next entity. You are relying on a hidden implementation detail in tests.

Additionally the use of Engine vs the constructed engine is really confusing and smelly.

BossBarDisplaySystem for entity health

Purpose

Displays entity health using Minecraft's BossBars.

Match

  • BukkitEntity
  • DisplayBossBar

DisplayBossBar (geary:bossbar)

  • colour
  • style
  • range (distance away from the entity a player can be to see the bossbar)

Other notes

  • System should not run too often to minimize the more expensive range checking calculations.

Support multiple instances of component per relation.

This is a pretty big proposition and I'd like to discuss it more before going ahead with it.

Relations should be able to store data in the parent, and/or child part, or neither. This leads to interesting outcomes like being able to attach a component with an enum several times for different values, (ex for different potion effects) and handle cases for each type of effect.

This would require restructuring relations to store a list of components of one type, where instances may be added/removed from that type on demand, as well as the relation as a whole.

I am considering a class that stores a single parent and list of children, which gets some special code for reading off of it.

This abstraction also lets us remove the idea of relations with and without data, as that property becomes inherent to the data in the relation.

Error spam

This is currently spamming console but i can find no entry for kuongatari_abdomen in the config, just with _raw and _cooked as intended

Increases the logfile size with about 10mb per minute
image

[11:40:07] [Server thread/ERROR]: [Geary] Error while running system ItemTrackerSystem
[11:40:07] [Server thread/WARN]: java.lang.IllegalStateException: Requested non null prefab entity for mineinabyss:kuongatari_abdomen, but it does not exist.
[11:40:07] [Server thread/WARN]: 	at geary-papermc-0.18.106-all.jar//com.mineinabyss.geary.prefabs.PrefabKey.toEntity-lvC7dM0(PrefabKey.kt:20)
[11:40:07] [Server thread/WARN]: 	at looty-0.7.58.jar//com.mineinabyss.looty.LootyFactory.getItemState(LootyFactory.kt:80)
[11:40:07] [Server thread/WARN]: 	at looty-0.7.58.jar//com.mineinabyss.looty.ecs.systems.tracking.ItemTrackerSystem$Companion.refresh$calculateForItem(ItemTrackerSystem.kt:118)
[11:40:07] [Server thread/WARN]: 	at looty-0.7.58.jar//com.mineinabyss.looty.ecs.systems.tracking.ItemTrackerSystem$Companion.refresh(ItemTrackerSystem.kt:136)
[11:40:07] [Server thread/WARN]: 	at looty-0.7.58.jar//com.mineinabyss.looty.ecs.systems.tracking.ItemTrackerSystem.tick(ItemTrackerSystem.kt:46)
[11:40:07] [Server thread/WARN]: 	at geary-papermc-0.18.106-all.jar//com.mineinabyss.geary.systems.TickingSystem.tick(TickingSystem.kt:33)
[11:40:07] [Server thread/WARN]: 	at geary-papermc-0.18.106-all.jar//com.mineinabyss.geary.systems.TickingSystem.doTick(TickingSystem.kt:29)
[11:40:07] [Server thread/WARN]: 	at geary-papermc-0.18.106-all.jar//com.mineinabyss.geary.papermc.engine.PaperMCEngine.runSystem(PaperMCEngine.kt:24)
[11:40:07] [Server thread/WARN]: 	at geary-papermc-0.18.106-all.jar//com.mineinabyss.geary.engine.GearyEngine$tick$2$job$1.invokeSuspend(GearyEngine.kt:296)
[11:40:07] [Server thread/WARN]: 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
[11:40:07] [Server thread/WARN]: 	at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith(DispatchedContinuation.kt:367)
[11:40:07] [Server thread/WARN]: 	at kotlinx.coroutines.internal.DispatchedContinuationKt.resumeCancellableWith$default(DispatchedContinuation.kt:278)
[11:40:07] [Server thread/WARN]: 	at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:39)
[11:40:07] [Server thread/WARN]: 	at kotlinx.coroutines.LazyStandaloneCoroutine.onStart(Builders.common.kt:205)
[11:40:07] [Server thread/WARN]: 	at kotlinx.coroutines.JobSupport.startInternal(JobSupport.kt:396)
[11:40:07] [Server thread/WARN]: 	at kotlinx.coroutines.JobSupport.start(JobSupport.kt:380)
[11:40:07] [Server thread/WARN]: 	at geary-papermc-0.18.106-all.jar//com.mineinabyss.geary.engine.GearyEngine$tick$2.invokeSuspend(GearyEngine.kt:311)
[11:40:07] [Server thread/WARN]: 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
[11:40:07] [Server thread/WARN]: 	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
[11:40:07] [Server thread/WARN]: 	at org.bukkit.craftbukkit.v1_19_R1.scheduler.CraftTask.run(CraftTask.java:101)
[11:40:07] [Server thread/WARN]: 	at org.bukkit.craftbukkit.v1_19_R1.scheduler.CraftScheduler.mainThreadHeartbeat(CraftScheduler.java:483)
[11:40:07] [Server thread/WARN]: 	at net.minecraft.server.MinecraftServer.b(MinecraftServer.java:1490)
[11:40:07] [Server thread/WARN]: 	at net.minecraft.server.dedicated.DedicatedServer.b(DedicatedServer.java:446)
[11:40:07] [Server thread/WARN]: 	at net.minecraft.server.MinecraftServer.a(MinecraftServer.java:1414)
[11:40:07] [Server thread/WARN]: 	at net.minecraft.server.MinecraftServer.v(MinecraftServer.java:1187)
[11:40:07] [Server thread/WARN]: 	at net.minecraft.server.MinecraftServer.lambda$spin$0(MinecraftServer.java:302)
[11:40:07] [Server thread/WARN]: 	at java.base/java.lang.Thread.run(Thread.java:833)

Disabled entity component for prefabs

Once applied, this component will prevent systems from iterating over this entity. All prefabs automatically get this component, adding a disabled entity as a prefab does not add this component to children.

Perhaps also some functions in GearyEntity to .disable() or .enable() it. Though some people may expect this to also disable children of this entity, so perhaps it may be better to keep this a more internal feature.

Cannot get listeners to fire in tests

This test code should throw an exception, yet does not. Listeners do not seem to fire in tests.

class Example : GearyListener() {
    private val ResultScope.duration by get<EffectDuration>()

    override fun GearyHandlerScope.init() {
        onComponentAdd {
            requireNotNull(null)
        }
    }
}

@Test
fun `does this even work`() {
    engine.tick(0)

    val target = engine.entity {
        set(EffectDuration(3))
    }
    engine.addSystem(Example())
    engine.tick(1)
}

feat(engine): Entity id versioning

Entities can come and go quickly for plugins like Looty, where references to itemstacks are very unstable.

We may wish for one entity to reference another, and be sure that this reference corresponds to only that entity. Currently, if the reference entity gets removed, another will take its index (ID).

One solution to this is to reserve some bits of an entity ID to be tied to the version. There is a great page on this in the ECS back and forth blog: https://skypjack.github.io/2019-05-06-ecs-baf-part-3/. The implicit list idea is also pretty good, though performance wise really doesn't matter to us right now.

I'd like to essentially implement the exact system outlined in that blog, though how the syntax will look for end users needs to be decided.

We should also start thinking about how we'll be storing permanent entity references. Do we want to use UUIDs? What do we do when a reference is in unloaded chunks? There are enough questions there that I think it should be its own issue for later.

Rethink actions

Actions should not be configurable, components do that. Limit the current design of the events block or if actions to be code exclusive, then introduce kotlin script support in the future. If a specific item or entity needs something to be configurable, it should either use relations or its own component to specify data.

Support suspendable systems

Instead of running every x ticks, a suspending system will sleep for at least x ticks before running again.

This should be designed in a way that allows non-interfering systems to run asynchronously, i.e. a system may have to sleep for more than x ticks if another system is currently running that mutates the same components.

feat(engine): Archetype garbage collection

Our use of relations and events means at all times, there are far more archetypes than entities, somewhat defeating the purpose of an archetypal ECS.

No archetypes

  • Ditch archetypes altogether and store data in individual entity arrays. Lookup stays the same, adding and removing components becomes slower, bottlenecked by tracking which systems should act upon this entity. Will depend on #67
  • We don't really have the ability to tightly pack data anyways (we want to support adding arbitrary objects to entities), so cache misses when iterating are already inevitable
  • Less garbage generated as there are never any remnants to clean up

Archetypes as a temporary cache

  • Focus on archetypes as a caching layer for system matching and helper for the case of many many of an identical entity
  • Empty archetypes can be cleared after they become unused for long enough
  • This puts more strain on the garbage collector, and extra overhead for a system that clears out old archetypes
  • Realistically archetypes that don't get used often shouldn't contain much data anyways (ex the ids array won't grow big), though more thought should be put into immediately clearing unused archetypes when they empty or keeping them longer

Archetypes as a permanent cache

  • Do nothing, or be extremely lenient with how things get cleaned. Benchmark how much memory we really use and treat the entire archetype tree as a slowly built cache (when an app runs for long enough, it could be that it generates pretty much every possible archetype it will need, so storing them all becomes worth it)

Automatically registering components or systems by reading annotations

We currently need to register all subclasses of the base component interface for kotlinx.serialization. Similarly systems need to be registered to run.

  • Instead, we should be able to annotate our components and systems to automatically register them all in one place.
  • This should likely be done at runtime and not compile time since we don't want to increase compile time for other projects using this api.
  • It may be worth passing a plugin class reference in each annotation. Realistically if two plugins register components with the same serial names there will be issues anyways, but it's useful information to have anyways if we'd like to, say reload only a specific plugin.

I don't know the correct way to do this kind of annotation processing at runtime, feel free to leave suggestions below.

encodeDefaults for the serialization formats

Right now, cborFormat, jsonFormat and yamlFormat have encodeDefaults set to false. This essentially prevents them from encoding default values assigned in geary components. Consider setting this option to true for all formats.

feat(events): Cancellable events

Provide a clean way to cancel an event and for listeners to ignore cancelled events, most likely by adding a Cancelled component to it and an ignoreCancelled function that requires Cancelled not be present.

NoReread component for other components

A component that can be added to component entities that will skip rereading components of that type when a prefab reload is performed. This can be used to allow reloads for prefabs that register things into the server, which can't be re-done after startup.

As well, this requires some way for us to add components to component entities in code.

feat(engine): Fast system matching

Currently when a new archetype is created, it iterates over all systems and caches which ones match it.

A more clever idea involves creating graphs similar to the archetype tree, for what systems should be tracked, untracked, or triggered when a component gets added or removed.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.