Giter VIP home page Giter VIP logo

fleks's Introduction

IMG_20231220_205745

Fleks

LTS Snapshot

Build Master Kotlin

MIT license

A fast, lightweight, entity component system library written in Kotlin.

Motivation

When developing my hobby games using LibGDX, I always used Ashley as an Entity Component System since it comes out of the box with LibGDX and performance wise it was always good enough for me.

When using Kotlin and LibKTX you even get nice extension functions for it, but I was never fully happy with how it felt because:

  • Defining ComponentMapper for every Component felt very redundant
  • Ashley is not null-safe, and therefore you get e.g. Entity? passed in as default to an IteratingSystem, although it will never be null (or at least shouldn't πŸ˜‰)
  • Creating Families as constructor arguments felt weird to me
  • The creator seems to not work on this project anymore or at least reacts super slowly to change requests, bugs or improvement ideas
  • Although for me, it was never a problem, I heard from other people that the performance is sometimes bad especially with a lot of entities that get their components updated each frame

Those are the reasons why I wanted to build my own ECS-library and of course out of interest to dig deep into the details of this topic and to learn something new!

Who should use this library?

If you need a lightweight and fast ECS in your Kotlin application then feel free to use Fleks.

If you are looking for a long time verified ECS that supports Java then use Artemis-odb or Ashley.

Current Status

After about one year of the first release of Fleks, we are now at version 2.x. This version combines the KMP and JVM flavors into a single one. The history of the 1.6 version is kept in separate branches. Also, the wiki will contain a separate section for 1.6 for users who don't want to migrate to 2.x or prefer the other API:

I want to make a big shout-out to jobe-m who helped with the first Kotlin multiplatform version and who also helped throughout the development of 2.0. Thank you!

With version 2.0 I tried to simplify the API and usage of Fleks for new users. This means that e.g. ComponentMapper are no longer necessary and also the API is more in style with typical Kotlin libraries which means more concise and easier to read (imho). And of course the big goal was to combine the JVM and KMP branch which was also achieved by completely removing reflection usage. This hopefully also makes the code easier to understand and debug.

To use Fleks add it as a dependency to your project:

Apache Maven

<dependency>
  <groupId>io.github.quillraven.fleks</groupId>
  <artifactId>Fleks-jvm</artifactId>
  <version>2.8</version>
</dependency>

Gradle (Groovy)

implementation 'io.github.quillraven.fleks:Fleks:2.8'

Gradle (Kotlin)

implementation("io.github.quillraven.fleks:Fleks:2.8")

KorGE

dependencyMulti("io.github.quillraven.fleks:Fleks:2.8", registerPlugin = false)

If you want to use the Snapshot version then you need to add the snapshot repository as well:

// Groovy DSL
maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }

// Kotlin DSL
maven { url = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/") }

API and examples

The API is documented in the wiki that also contains an example section for JVM and KMP projects.

Performance

One important topic for me throughout the development of Fleks was performance. For that I compared Fleks with Artemis-odb and Ashley in three scenarios which you can find in the jvmBenchmarks source set:

  1. AddRemove: Creates 10_000 entities with a single component each and removes those entities.
  2. Simple: Steps the world 1_000 times for 10_000 entities with an IteratingSystem for a single component that gets a Float counter increased by one every tick.
  3. Complex: Steps the world 1_000 times for 10_000 entities with two IteratingSystem and three components. It is a time-consuming benchmark because all entities get added and removed from the first system each tick.
    • Each entity gets initialized with ComponentA and ComponentC.
    • The first system requires ComponentA, ComponentC and not ComponentB. It switches between creating ComponentB or removing ComponentA. That way every entity gets removed from this system each tick.
    • The second system requires any ComponentA/B/C and removes ComponentB and adds ComponentA. That way every entity gets added again to the first system.

I used kotlinx-benchmark to create the benchmarks with a measurement that represents the number of executed operations within three seconds.

All Benchmarks are run within IntelliJ using the benchmarksBenchmark gradle task on my local computer. The hardware is:

  • Windows 10 64-bit
  • 16 GB Ram
  • Intel i7-5820K @ 3.30Ghz
  • Java 8 target

Here is the result (the higher the Score the better):

Library Benchmark Mode Cnt Score Error Units
Ashley AddRemove thrpt 3 207,007 Β± 39,121 ops/s
Artemis AddRemove thrpt 3 677,231 Β± 473,361 ops/s
Fleks AddRemove thrpt 3 841,916 Β± 75,492 ops/s
Ashley Simple thrpt 3 3,986 Β± 1,390 ops/s
Artemis Simple thrpt 3 32,830 Β± 2,965 ops/s
Fleks Simple thrpt 3 33,017 Β± 3,089 ops/s
Ashley Complex thrpt 3 0,056 Β± 0,117 ops/s
Artemis Complex thrpt 3 1,452 Β± 0,452 ops/s
Fleks Complex thrpt 3 1,326 Β± 0,269 ops/s

I am not an expert for performance measurement, that's why you should take those numbers with a grain of salt but as you can see in the table:

  • Ashley is the slowest of the three libraries by far
  • Fleks is ~1.2x the speed of Artemis in the AddRemove benchmark
  • Fleks is ~the same speed as Artemis in the Simple benchmark
  • Fleks is ~0.9x the speed of Artemis in the Complex benchmark

fleks's People

Contributors

asemy avatar dragbone avatar geist-2501 avatar jobe-m avatar lobbydivinus avatar mariorezdev avatar metaphore avatar quillraven avatar refux avatar scotthamper avatar tommyettinger 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  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  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fleks's Issues

removeAll should not call cleanupDelays

The newly introduced removeAll function calls cleanupDelays at the end which is not correct when calling this function while a system is iterating over entities.

I think this can simply be removed but we should also add a test case to cover that. My scenario was:

  1. IteratingSystem is firing a game event
  2. This event calls removeAll and also spawns a new entity
  3. The system then also calls world.remove in onTickEntity on the entity which caused a bug because the entity passed to this function was already linking to the newly spawned entity in step 2

The reason is the cleanupDelays call.

 fun removeAll() {
        for (id in 0 until nextId) {
            val entity = Entity(id)
            if (entity in recycledEntities) {
                continue
            }
            remove(entity)
        }
        cleanupDelays()
    }

one additional thing that I need to check is when an entity gets removed multiple times. For example remove(entity(0)) followed by a removeAll call. Entity(0) should only get recycled once and not multiple times.

Feature Request: API to get a family of entities.

I want a way to be able to easily get a family of entities with certain components that's decoupled from the systems.

Example:

        val familyContainer = world.familyContainer(
            allOf = ...,
            anyOf = ...,
            noneOf = ...,
        )
        val entities: List<Entity> = family.getEntities()

The family container should be automatically updated on addition/removal of entities and addition/removal of their components.

Support for Tags / empty components

Hi, I am not sure in which regard these fit into a light weight ECS after all, but they are a great feature to "tag" entities without using the memory a traditional component incurrs.

What are tags?

Basically empty components that could be defined using Kotlin's objects. There can only be one instance per Tag type and all the ECS has to store about it is whether a given entity has said tag. Implementation wise this would work like the ComponentMapper but with an internal bit array instead of a full blown component array (comparable to how ComponentMappers worked before the recent change).

Motivation

The advantage over using components for tagging (we could reuse a single object to share at least that part): Right now each component mapper will hold on array of up to size N for N being the max ever used enttiy id. Let's say that takes 8 bytes per array entry leading to a size of 8N bytes. A tag mapper implemented by a bit array would only use 0.125N bytes (that's a factor of 64).

Let's assume N = 1024 and 10 components, of which 5 could be replaced by simple tags.

N = 1024 with Size
10 components 10 * 8 * 1024 bytes = 80 kb
5 components + 5 tags 5 * 8 * 1024 bytes + 5 * 0.125 * 1024bytes = 40 kb + 640 bytes

I omitted some details like that regular components will use extra memory for each instance since that's not part of the consideration here.

How it could be done

It could be implemented along side component mappers. I am not sure if it would make sense to use a common interface for component and tag types to allow to use them interchangeably in family definitions.

Heap allocations in family.forEach due to use of virtual EntityBag.forEach

Hi, for my current project I try to reduce memory allocations as much as possible. From my testing Fleks behaves actually pretty good in this regard with one exception: I noticed that whenever you call family.forEach {} a bit of memeory gets allocated which accumulates over time as this method gets usually called often (e.g. in IteratingSystems or when iterating over the entities of a family on your own). For my testing I did not add or remove any entities or components after initial setup.

Some investigation revealed that the issue is that said function calls MutableEntityBag.forEach {} internally which is virtual as it implements the EntityBag interface. This is an issue here as virtual functions cannot be inlined (although, technically the call is static here) which in turn means the function parameters will have to be passed as actual lambda objects that store state on the heap.

For iteration without potential modification I came up with this solution that I can put in my own code:

inline fun Family.fastForEach(block: World.(Entity) -> Unit) {
    val bag = entities
    for (i in 0..<bag.size) {
        world.block(bag[i])
    }
} 

However, it may make sense to introduce something like this into MutableEntityBag and similar classes and use them internally instead of virtual methods.

I just noticed that you actually are allowed to use the inline keyword for non open methods although it gives you a warning. When going for this approach you would have to make sure to not call the method on the interface whenever possible: https://stackoverflow.com/questions/53188558/override-by-inline-in-kotlin

Introduce a "dispose" functionality for systems

I think it would be nice if you can "dispose" the world which then calls a method of each system. A use-case would be a LibGDX game where a system is creating Disposable objects internally like a Texture or Box2DDebugRenderer.

It should be optional because you do not always need that. The method should remove all entities and then call the new method (maybe call it onDispose?) of each system no matter if it is active or not.

Use coroutines to fasten up the initialization process in the SystemService

I guess it should be feasible to use coroutines for the system creation in the SystemService. We just need to make sure that the order of the systems remains the same.
Also, we should verify if there is a big performance boost or not. I usually have ~20-30 systems in my games but I think we should do the benchmark with different values:

  • 10 systems
  • 50 systems
  • 100 systems

Feature Request: Family listener

Would like the ability to create a listener for a family.

e.g:

class SelectionFamilyListener : FamilyListener(
    // This listener only affects entities that have these components associated with it.
    requiredComponents = arrayOf(EntityUIComponent::class, EntityRenderComponent::class)
    // This listener will only be triggered by the addition/removal of these components.
    triggerComponents = arrayOf(SelectionComponent::class)
) {
    val entityUIComponentMapper = Inject.componentMapper<EntityUIComponent>()
    val entityRenderComponentMapper = Inject.componentMapper<EntityRenderComponent>()
    val selectionComponentMapper = Inject.componentMapper<SelectionComponent>()

    // 
    onAdd(entity: Entity) {
       ... 
    }

    onRemove(entity: Entity) {
        ...
    }
}

onAdd scenario:

So imagine if an entity only had a EntityUIComponent and EntityRenderComponent associated with it. After adding the SelectionComponent to the entity, I would expect the SelectionFamilyListener::onAdd to be triggered.

onRemove scenario:

If an entity has a <EntityUIComponent, EntityRenderComponent, SelectionComponent>. Removing SelectionComponent should result in SelectionFamilyListener::onRemove being triggered.

More dynamic world and entity building

My game is heavily based on large amounts of content, and as such, I'm using a combination of config files and a SQLite database to manage it all. On top of that, I also want my game to have good mod support, so I really need a way to do things dynamically like adding component onAdd and onRemove listeners, or adding components to entities without access to their exact types.

For instance, this doesn't work, as it complains that not enough type information is given:

abstract class GameComponent<T>: Component<T> {
    abstract fun onComponentAdded(entity: Entity)
    abstract fun onComponentRemoved()
}

private val gameComponents: MutableList<GameComponent> = mutableListOf()

world {
  components {
    gameComponents.forEach {
      onAdd(it.type()) { entity, component ->
        it.onComponentAdded(entity)
      }
      onRemove(it.type()) { entity, component ->
        it.onComponentRemoved()
      }
    }
  }
}

Or this:

val someComponents: MutableList<Component<*>> = mutableListOf()
        
world.entity { entity ->
    someComponents.forEach {
        entity += it
    }
}

How big of a breaking change would that be to make things like this possible? I basically want to build my own "world builder" object that I can pass around to various places that add new features to the world, and then build it all at once and start it. The entity building problem I can solve by adding an abstract method to GameComponent to delegate adding the component, but I'm stuck on adding component listeners dynamically.

Missing BitArray toString function

In general we should improve the way to debug and analyze Fleks. Imo it is currently impossible to understand which bit of a BitArray is set which makes it hard to e.g. debug entities of a family before the bag gets updated or the component mask of an entity.

In addition, it is not comfortable to immediately see which entities have which components because of the performant and efficient way things are stored internally.

I am thinking of a toMap function for the world where the key is the entity and the value is a list of components of that entity.

Such a map can e.g. easily be converted to a JSON string for serializing but it also allows an easy overview of the current state of the world, it's entities and its components.

That should not be hard to implement with the information that the EntityService already has. It knows the active entities and the component mask of each entity which is the ComponentMapper ID of each component of the entity. The mapper can already be retrieved via the ID with the ComponentService.

Family with only noneOf configuration does not work properly when removing entities

Just discovered a weird bug :D

    private val nonPlayerEntities by lazy { eWorld.family(noneOf = arrayOf(Player::class)) }

When creating such a family and then calling

                eWorld.remove(entity)

Then the entity remains in the family because onEntityCfgChanged of the family is correctly called but since the family only requires entities without a Player component it will keep the entity because a removed entity has no components and therefore is valid for the family.

However, this is not intentional and unintuitive. I guess we should add a third parameter to onEntityCfgChanged like isRemoved or something to distinguish between when an entity really gets removed and when an entity simply has no components.

For my use case I can workaround it for now by simply adding an anyOf condition to the family as well, which is fine in my case but we should still fix that behavior in an upcoming version.

Family numEntities returns a wrong value

I just saw that numEntities of a Family is returning a wrong value. This method is not really used inside Fleks, that's why it is not a critical bug imo but still we need to fix it.

The reason is that it returns the length of the BitArray which is the index of the highest set bit. This is working for our test cases with just one Entity but it is wrong in other cases.
E.g. adding just one entity with ID 7 will return a size of 8 instead of 1.

 @Test
fun foo() {
    val bits = BitArray()
    bits.set(7)
    println(bits.length())  // prints 8
}

I see two possible solutions:

  • add a size function to BitArray that returns the amount of set bits
  • use the family's entitiesBag and return the size of that bag

The second solution is faster but has a certain delay until it represents the correct number. This happens inside updateActiveEntities:

internal fun updateActiveEntities() {
    if (isDirty) {
        isDirty = false
        entities.toIntBag(entitiesBag)
    }
}

The first solution always returns the correct number but can be quite expensive in case of a lot of entities. One implementation could look like this:

val numBits: Int
	get() {
		var sum = 0
		for (word in bits.size - 1 downTo 0) {
			val bitsAtWord = bits[word]
			if (bitsAtWord != 0L) {
				for (bit in 63 downTo 0) {
					if ((bitsAtWord and (1L shl (bit % 64))) != 0L) {
						sum++
					}
				}
			}
		}
		return sum
	}

I personally prefer the first option because I think numEntities will not get called by users a lot per frame and it returns the expected result immediately all the time.

@jobe-m : What do you think?

Change gradle setup from jvm to kmp

As a first step to correctly move to a single kotlin multiplatform setup, we need to change the gradle setup.

Change from

kotlin("jvm") version "1.6.10"

to

kotlin("multiplatform") version "1.6.10"

Simply changing that won't work. I assume we also need to adjust the benchmark sourcset, dependencies and dokka configuration. An empty kmp project created by Intellij looks like this:

plugins {
    kotlin("multiplatform") version "1.6.10"
}

group = "io.github.quillraven.fleks"
version = "1.0-KMP-RC1"

repositories {
    mavenCentral()
}

kotlin {
    jvm {
        compilations.all {
            kotlinOptions.jvmTarget = "1.8"
        }
        withJava()
        testRuns["test"].executionTask.configure {
            useJUnitPlatform()
        }
    }
    js(BOTH) {
        browser {
            commonWebpackConfig {
                cssSupport.enabled = true
            }
        }
    }
    val hostOs = System.getProperty("os.name")
    val isMingwX64 = hostOs.startsWith("Windows")
    val nativeTarget = when {
        hostOs == "Mac OS X" -> macosX64("native")
        hostOs == "Linux" -> linuxX64("native")
        isMingwX64 -> mingwX64("native")
        else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
    }

    
    sourceSets {
        val commonMain by getting
        val commonTest by getting {
            dependencies {
                implementation(kotlin("test"))
            }
        }
        val jvmMain by getting
        val jvmTest by getting
        val jsMain by getting
        val jsTest by getting
        val nativeMain by getting
        val nativeTest by getting
    }
}

The goal is to switch to an official kmp setup that still works in Dinoleon (jvm game with libgdx) and Korge-fleks (example application in Korge).

Give access to entity property of EntityCreateCfg

Sometimes it is necessary to already access the entity that will be created during initial entity creation.
One use-case is e.g. to set the userData of a Box2D body.

Currently entity of EntityCreateCfg is internal. It should be public get but private/internal set instead to support the following:

        eWorld.entity {
            val transform = add<TransformComponent> {
                position.set(0f, 0f)
                size.set(0.75f, 1f)
            }
            add<PhysicComponent> {
                body = physicWorld.body(BodyType.DynamicBody) {
                    position.set(
                        transform.position.x + transform.size.x * 0.5f,
                        transform.position.y + transform.size.y * 0.5f
                    )
                    fixedRotation = true
                    allowSleep = false
                    box(transform.size.x, transform.size.y) {
                        friction = 0f
                        isSensor = false
                    }

                    // TODO give access to internal entity of EntityCreateCfg
                    // userData = [email protected]
                }
            }
        }

Remove "used" parameter from inject function

Since references to the inject objects and mappers are removed after initialization of the world and all systems we can remove this parameter again.
The idea for that parameter was that an injected object could be referenced (the first time) at a later time after initialization. But I think that it is much cleaner (how it is done now) to delete the inject object references after they where checked if they are all used.
->

fun <T : Any> inject(type: String, dependency: T, used: Boolean = false) {

Problem if two dependencies of the same type are needed by systems

I had now a case where I needed two TextureAtlas instances by the systems. Currently this is not supported because the key is only the KClass of the dependency.

We either need to introduce named dependencies like e.g. Spring does it with @Qualifier OR we remove the DI part and users need to call the constructors correctly by themselves like e.g. Ashley does it.

In my case I was able to create the second TextureAtlas within the system I needed it and thanks to the onDispose method it was easy to clean it up correctly. However, I can imagine that this scenario might happen to other users as well and you cannot always use such a workaround.

Entity creation in a system init block does not work properly

A user in Discord (Hafu) contacted me that he has a problem creating an entity in the onInit block of a system.
This function got removed in the last update but I think the problem still remains and makes sense to me because families are created together with their system and therefore if a system creates an entity in its init block then not all families will be notified at that time.

One solution could be to iterate over all entities after system creation and trigger an update for each family to correctly update them. This is a one time operation during world creation and should not be a big problem.

We also need to check the ComponentListener that they also get notified correctly during world creation.

Proposal for component lifecycle methods

Hi all!

I've been tinkering with Fleks and, while the component hooks are lovely and work perfectly, I wondered why there isn't lifecycle methods on components instead.

My specific use-case is that my components can raise events in order to talk to the UI. To do this they need an event manager which I inject - however, components don't have the whole Fleks context thing so I can't inject directly. So instead, I have to initialise it via a hook (which do have a Fleks context), which works great, but took me a while to think of πŸ—Ώ.

Wouldn't lifecycle methods be more developer friendly? For instance;

class ToolbarComponent : Component<ToolbarComponent> {

    private lateinit var eventManager: EventManager
    
    override fun World.onAddComponent() {
        eventManager = inject()
    }

    // Instead of;
    
    companion object : ComponentType<ToolbarComponent>() {
        val onAddComponent: ComponentHook<ToolbarComponent> = { entity, toolbarComponent ->
            toolbarComponent.eventManager = inject()
        }
    }
}

I've forked this repo and got the lifecycle methods working, but I'd be interested to know peoples opinions on whether this is actually a better approach :)

Also Fleks is awesome! Very happy that I made the switch from Ashley halfway through my project.

Allow getting a ComponentMapper via World

Currently it is only possible via injection to a system and for me that turned out to be just fine.

But I guess opening it up is not bad and allows for more possibilities for users and is therefore more flexible.

A simple getter on a world instance should be sufficient.

Use Java reflection instead of Kotlin reflection

Kotlin's reflection is super slow. E.g. calling createInstance of a KClass is ~4 times slower than calling newInstance of a Java constructor.
Also, it is an additional dependency that makes Fleks bigger than necessary which might not be relevant for Desktop but is for sure not ideal for mobile backend.

Therefore, we should remove this dependency an convert everything to "classic" reflection. Not sure if that is in conflict with #2

Check needed changes and clarify open points to use Fleks directly in Korge

As you know we have currently a copy of Fleks in Korge as Korge-fleks module. The sources in Korge-fleks are a copy of the "next" branch here.

In Korge there is a PR to remove korge-Fleks and use Fleks directly -> korlibs-archive/korge-next#718

I will use this space here to discuss any open points for the standalone integration/usage of Fleks with Korge.

  • check Fleks-ecs sample in Korge with locally released Fleks for targets JVM, js, native (linux x64, mingw x64), Android
    • jvm
    • js
    • native (linux x86)
    • native (mingw x86)
    • Android (it compiles, could not test it on device yet)
  • check all needed build targets for Korge in a Korge game
    • Fleks (common code)
    • Fleks-android
    • Fleks-js
    • Fleks-jvm
    • Fleks-linuxx64
    • Fleks-mingwx64
  • Do final Fleks release

Documentation issue (?)

In below line the "DeadComponent" is added to an entity:

Fleks/ReadMe.md

Line 579 in 06e0950

add<DeadComponent>()

But earlier in line 572 the family is created as "noneOf" DeadComponent:

Fleks/ReadMe.md

Line 572 in 06e0950

noneOf = arrayOf(DeadComponent::class),

Question: In my understanding the family listener will not react.
In order to make the family listener react on creation and removal of that entity the "DeadComponent" should not be added. Is that right? So line 579 should be deleted?

Fleks/ReadMe.md

Line 576 in 06e0950

// this will notify the listener via its onEntityAdded function

New name for "world" function

I got some mails and comments in my YouTube videos where users got confused and got errors when calling World. The reason is that the World constructor cannot be used because it is internal. Instead, users should use the world function.

I think the naming is not ideal because they are the same word. I also don't want to call it createWorld since that clashes with LibKTX's Box2D world creation function.

Any ideas about some good namings?

My ideas:

  • configureWorld
  • newWorld
  • cfgWorld
  • worldCfg
  • newEntityWorld

Add functionality to iterate over all active entities

In my opinion this is a useful functionality for exceptional cases. Usually you have your systems to iterate over entities with a specific configuration but this might not be sufficient in all cases.

I could imagine a Save-/Loadsystem where you want to iterate over all entities at specific events like reaching a checkpoint.

I am not saying that this is the only solution to this problem but this functionality will allow the support of exceptional cases where systems might be not sufficient/cumbersome.

2.1 family doesn't get updated

Hi Simon,
I think I found a bug in the 2.1 version.
So I have a extension that check if a certain family is in a position:

fun Position.findEntitiesInPosition(family: Family): List<Entity> {
    val entities = mutableListOf<Entity>()
    family.forEach {
        if (it.has(Position)
            && it[Position].x == x
            && it[Position].y == y
        ) {
            entities.add(it)
        }
    }
    return entities
}

I use this to see if I can move to a target position

class MoveSystem(
    private val maxWidth:Int = inject("maxWidth"),
    private val maxHeight:Int = inject("maxHeight")
) : IteratingSystem(family { all(Position, Moving).none(Dead)}) {
    override fun onTickEntity(entity: Entity) {
        val originalPosition = entity[Position]
        val targetPosition = entity[Moving].target
        // position is within the limit and position is not occupied
        if (targetPosition.positionWithin(maxWidth, maxHeight)
            && targetPosition.findEntitiesInPosition(world.family { all(Position, Solid) }).isEmpty()){
            originalPosition.x = targetPosition.x
            originalPosition.y = targetPosition.y
        }

        entity.configure { it -= Moving }
    }

So for the version 2.0 this was working fine but I recently upgraded to 2.1 and now it is failing the test below:

    @Test
    fun shouldMoveIntoADeadObject(){
        val x = 5
        val y = 5
        val original = Position(x, y)
        val target = Position(8,8)
        val entity = world.entity {
            it += original
            it += Moving(target)
        }

        val toDie = world.entity{
            it += target
            it += Solid()
        }

        world.update(1f)
        // Check I cannot move
        Assertions.assertEquals(original, Position(x,y))
        Assertions.assertNotEquals(original, target)

        with(world){
            toDie.configure {
                it -= Solid
                it += Dead()
            }
            entity.configure { it += Moving(target) }
        }

        world.update(1f)
        // Check it should have moved
        Assertions.assertEquals(original, target)
    }

    

Safe way to reference entities?

I'm working on a game prototype using fleks and I'm struggling to find a good way to reference an entity from a component. A simple example would be one entity following another entity and therefore needing a reference.
It seems that the Entity value class is not a safe reference because entities get reused after removal from the world, so a reference might reference a completely different entity in the future.
Is there some safe way to reference entities? Do I need to set a remove entity hook and search for all components that might reference that entity?

Family entity add/remove events dynamic listeners

Hi @Quillraven and thanks for the amazing lib! It's pure joy to use it alongside libGDX/KTX.

The question is rather a matter of preference, but still, I find it's lacking that there's no way to subscribe for family entity add/remove events from an arbitrary place. In particular, I'm looking to receive those events inside of an IteratingSystem, similar to Ashley's Engine#addEntityListener(Family, EntityListener). In Ashley, it's very handy to opt-in for those events for the lifetime of the system that operates over the related family. One common use case with me is that a system may use internal wrappers over the family's entities, to unite multiple components, store internal data along it, and eventually iterate over the wrappers (and not over the entities directly).

This might seem off and counter-intuitive, as ECS encourages storing ALL the data in components, but most often, having internal wrappers is much more efficient and cleaner, than creating extra components just for the internal needs of one system.

I'm aware of FamilyConfiguration.onAdd/onRemove hooks, but the current workaround is rather wacky:

  • Those can only be set during the world initialization
  • It requires duplication of the family definition (once for the hook setup, and then in the respective IteratingSystem)
  • Manual routing of those global hooks back to the related system instances is quite ugly too.

Looking at the Family.addHook/removeHook fields it seems like turning those into mutable collections and exposing them for subscribers won't hurt that much, but will provide that flexibility for dynamic listeners.

Enabled property of System

Hi, Symon!
What are your thoughts on changing Enabled property of a systems from Boolean to lambda, that returns boolean?
With this change controlling states of systems becomes more manageable in my opinion.

world is not accessible in init block of a system

world is not correctly set in an init block of a system. It points to the EMPTY_WORLD and is only set to the real world after the constructor invocation.

This is necessary because otherwise systems need to pass in the real world instance already during constructor invocation.

This is confusing in my opinion and can cause bugs which are hard to identify.

Either we find a way to pass in the correct world instance already correctly to the super constructor (which might not be possible?) or create an onInit method for users where they can put in their initialization logic for a system if it requires already the correct world instance.

Combine Fleks master/kmp version into a single version

Fleks has two separate versions at the moment:

  • JVM backend - master branch
  • Kotlin Multiplatform - kmp branch

The future, in my opinion, is the kmp branch which currently has a slightly different API than the master branch.

The things that we should have a look at is:

  • How to avoid that users need to register components to the world as this is a little bit redundant and tidious setup
  • In general, is it possible to get rid off the WorldConfiguration? If we can remove the component setup from above then it might be possible to also remove ComponentListener and System setup. The only thing left would be the entity capacity which can be directly passed to the world and the dependency injection (see next point).
    • As an idea: introduce new @Component and @System(priority : int) annotations. Users can add them to their component/system classes and via annotation processing we can maybe automatically register them to the world.
  • If we go for the annotation processing route then it is maybe also possible to replace Inject.dependency and Inject.componentMapper with an annotation to be consistent.
    • Idea: here we can maybe go the other route and mark injectables with an @Injectable annotation. They will then be collected during the processing step and can be directly passed to the system constructors?
  • Optional: bring back the family annotations instead of using properties in the system

As an alternative, if we do not want to go for the annotations route, we could think about a declarative api but then we should maybe remove the dependency injection part to create something like:

val world = World {
  lateinit var boundaryMapper : ComponentMapper<BoundaryComponent>
  lateinit var moveMapper: ComponentMapper<MoveComponent>
  lateinit var renderMapper: ComponentMapper<RenderComponent>

  components {
    boundaryMapper  = add<BoundaryComponent>()
    moveMapper = add<MoveComponent>()
    renderMapper = add<RenderComponent>()
  }

  systems {
    MoveSystem(textureAtlas, eventManager, moveMapper, boundaryMapper ) {
      allOf<MoveComponent, BoundaryComponent>
    }
    RenderSystem(spriteBatch, camera, boundaryMapper, renderMapper) {
      allOf<BoundaryComponent, RenderComponent>
    }
  }
}

The advantage of this version is that no magic is happening and it describes everything you need to know in an understandable way. The disadvantage is that a few things are redundant like e.g. I personally do not like that I have to add components or create ComponentMapper. It would be great if defining a Component class would be sufficient somehow.

IntervalSystem should take a sealed class for the tick rate

Grüß Gott πŸ‘‹πŸ˜„

IntervalSystem currently takes a float for the tick rate and assumes that zero means "every frame". IMO What it should do instead is take a sealed class as input for the tick rate.

It could look like the following:

sealed interface TickRate
object EveryFrame : TickRate
data class FixedRate(val rate: Float) : TickRate

abstract class IntervalSystem(private val tickRate: TickRate = EveryFrame...

Now the update function becomes a little cleaner. Instead of checking if (tickRate == 0f) { we can do

when(tickRate) {
  (is) EveryFrame -> { /* handle this */ }
  is FixedRate -> { /* handle this, but with smart-casting and easy access to the "rate" property of FixedRate 😎 */ }
}

Aside from the code being more readable, this is also self-documenting.

Proposal: Component add/remove hooks that don't rely on Component.onAdd/onRemove

We can detect component addition / removal by overriding the onAdd / onRemove methods in our component class. However, by doing that our components become effectively more than simple data objects. Sure, we could include a reference to another handler for these events in the component and then call that handler's methods instead, but that kind of defeats the purpose of data only component classes. I expect this to also make serialization / deserialization harder when non serializable data is involved.

In my use case I am using a physics library that uses physics bodies which require manual cleanup logic when they get removed. My only option right now is to detect the removal via the onRemove method in the component to trigger the cleanup logic that depends on state stored outside of the ECS. In general I think it's a good design if systems (more broadly code that manages components) depend on components not the other way round.

I tried to use the family onRemove hook but noticed that these hooks get called after the component was removed already so I cannot use it for clean-up.

So what I propose is to add methods similar to the family onAdd / onRemove hooks that would internally register to ComponentsHolder and provide the component as a parameter when called.

Signing does not work

@aSemy seems like the environment variables in the publish workflow are not properly read from the secrets? Looks like they are all null and that's why the signing fails?

Can you please have a look when you have time?

Add utility function for addOrUpdate component

I always have use cases where you either want to create a component and set some values or update an existing one if it already exists.

E.g. a damage or healcomponent where within a single frame multiple heal/damage events can occur.

We could either support mutiple components of the same type for a single entity, which I personally do not like because you would need to work with lists all the time or have a utility function to get a single component if you are sure there is only one.

OR we add a simple utility function which converts

if (dino in damageCmps) {
    damageCmps[dino].damage++
} else {
    configureEntity(dino) { damageCmps.add(it) }
}

to something like

configureEntity(dino) { 
  damageCmps.addOrUpdate(it){ damage++ } 
}

remove the same entity multiple times behaves incorrectly

as mentioned in #23 at the end, it is indeed a wrong behavior when the same entity gets removed multiple times. Right now, it gets added to the recycled list in the EntityService multiple times.

We need to restrict that, that it only gets recycled once.

Partial world update by network

Hello, I am trying to make a network game using fleks.

I only found the snapshot api for mass updating the world. But I don't want to transmit the whole world over the network. It would be convenient to be able to add entities received over the network with their components.

Could you tell me the best way to implement this by using fleks?

[Help Request] Updating Box2D ContactListener to Fleks 2.0 Entities and Components

I have a PlayerContactListener to handle collisions in a box2d world. The physics bodies are assigned their relative fleks Entity in the body.userData property.

The following excerpt was working using Fleks 1.6. gameManager.entityWorld is the Fleks world.

class PlayerContactListener(
    val gameManager: GameManager,
) : ContactListener {
    companion object {
        val log = logger<PlayerContactListener>()
    }

    val contacts = mutableListOf<Pair<Fixture, Fixture>>()

    val idComponentMapper = gameManager.entityWorld.mapper<IdComponent>()
    val quantityComponentMapper = gameManager.entityWorld.mapper<QuantityComponent>()

    override fun beginContact(contact: Contact?) {
        contact?.let {
            val fixtureA = it.fixtureA
            val fixtureB = it.fixtureB

            if (isPlayerContact(fixtureA, fixtureB)) {
                contacts.add(Pair(fixtureA, fixtureB))

                val physicsComponentMapper = gameManager.entityWorld.mapper<PhysicsComponent>()
                val storageComponentMapper = gameManager.entityWorld.mapper<StorageComponent>()

                val physicsComponent = physicsComponentMapper[gameManager.playerEntity]

                if (physicsComponent.targetEntity == fixtureB.body.userData || physicsComponent.targetEntity == fixtureA.body.userData) {
                    val targetEntity = (if (physicsComponent.targetEntity == fixtureA.body.userData) fixtureA.body.userData else fixtureB.body.userData) as Entity
                    val storageComponent = storageComponentMapper[targetEntity] ?: return
                    
                    // AND SO ON...

Now I'm trying to update the whole system to Fleks 2.0, and I've hit a snag with the storageComponent and I'm wondering what the correct approach might be.

The following in my current conversion attempt of the previous excerpt:

class PlayerContactListener(
    val gameManager: GameManager,
) : ContactListener {
    companion object {
        val log = logger<PlayerContactListener>()
    }

    val contacts = mutableListOf<Pair<Fixture, Fixture>>()

    override fun beginContact(contact: Contact?) {
        contact?.let {
            val fixtureA = it.fixtureA
            val fixtureB = it.fixtureB

            if (isPlayerContact(fixtureA, fixtureB)) {
                contacts.add(Pair(fixtureA, fixtureB))

                if (gameManager.playerPhysicsBodyComponent.targetEntity == fixtureB.body.userData || gameManager.playerPhysicsBodyComponent.targetEntity == fixtureA.body.userData) {
                    val targetEntity = (if (gameManager.playerPhysicsBodyComponent.targetEntity == fixtureA.body.userData) fixtureA.body.userData else fixtureB.body.userData) as Entity
                    val storageComponent = targetEntity.getOrNull(Storage) ?: return
                    
                    // AND SO ON...

The IDE is saying that the getOrNull method is an unresolved reference.

I thank you for any help or guidance you can provide.

  • Browncoat

P.S. As a side note I pinged you on the LibGDX discord this morning, just mentioning an unexpected behaviour when creating entities in Fleks 1.6. You may not have seen it so I thought I'd mention it here as well.

P.P.S. I just noticed the discussions tab so if this is not an appropriate pace for this I can move it over there, just ask.

Basic Example Request

Hello!

I wanted to submit a request for a very simple written guide for starting ECS from scratch with Fleks.

No need to get in to any graphics or anything. It'd be incredibly helpful to understand how to set up the structure.

I have an idea for an ECS but can't code it up.

The game has a world.

A world has regions.

A region has locations.

Locations can have objects.

Objects can be animate or inanimate and can be moved between locations and/or manipulated/viewed.

Appreciate your consideration (and love the Youtube videos)!

Make Fleks multi platform

I am not an expert when it comes to Kotlin MPP but I guess it should not be super difficult to convert the current code to MPP?
Would be great if we can achieve this to support any backend.

Please share good articles/tutorials about MPP or create a PR yourself! Would be much appreciated.

Better serialization support (KorGE)

As discussed in #86 it seems to be problematic that the Fleks entity does not have the @Serialization annotation which makes it more complicated on user side to serialize/deserialize the snapshots of a world.

The commit of #86 is part of a new branch: 2.3-korge-serialization


Things that we need to consider:

  • is kotlinx-serialization the standard to serialize/deserialize stuff? Or are there other libraries out there that people might use.
    • What is LibGDX using? I know that I was able to simply convert the snapshot to a JSON string in LibGDX and store that in a preference (MysticGarden project)
  • if we add kotlinx-serialization as dependency, how much bigger does the library become? Currently it is very small because it has no dependencies at all besides standard kotlin. I'd like to keep it that way and ideally don't require the strong dependency at all.
  • Can we replace the entity value class with a typealias? What downsides does that have? Does normal Int then also get the entity extensions? If so, I don't like that solution because it polutes standard classes.
  • Entity as an interface: what impact does that have to performance?
  • Can we "generalize" the Fleks world and allow the user to override the used entity class and entity service? Per default it will use the current implementation.
    • Maybe something like:
    val world = world {
      entityService = useDefault<UserCustomEntity>() // <-- default entity service with custom entity class
      // OR
      entityService = UserEntityService() // <-- custom service by the user with either his own entity class or the Fleks entity
    }
    
    // any custom entity class must provide an 'id' property to still work with the component index
    // any custom entity service must provide the current entity service functions like create/remove/...
    • not sure how easy that is and if it is even possible because entity configuration and therefore also component configuration is happening in the current entity service. Not sure how a user could still provide this functionality in his own service.
      • Maybe don't allow the complete service "replacement". Just allow the ID creation/removal and keep all the other things like component assignment, etc. in the Fleks service. So just a part of the current Fleks entity service can be customized by users.
    • However, this approach might be good for some other issues that were discussed lately where people needed their own way to create/assign IDs.

Release pre-release version

  • Finalize API
  • Comments to document API
  • Unit Tests
  • Remove test.kt files
  • Add github actions to automatically build / verify PRs
  • Update ReadMe.md
  • Add Pre-Release to GitHub
  • Add mavenCentral artifact

`EntityBag` interface does not have a `get` operator function

Hey Quillraven,

While prototyping a naive collision system, I've discovered that the get operator function on MutableEntityBag is not part of the EntityBag interface. Is this intentional?

My current approach is to create an IntervalSystem that manually iterates over a family, in order to avoid duplicating collision checks between entities. Something like the following:

class CollisionSystem(interval: Interval) : IntervalSystem(interval) {
    private val family = world.family { all(PositionComponent, HitcircleComponent) }

    override fun onTick() {
        for (i in 0 until family.numEntities) {
            for (j in i + 1 until family.numEntities) {
                val first = family.entities[i] // Error; `EntityBag` interface does not provide a direct way to retrieve an item
                val second = family.entities[j] // Error

                // Check for a collision between `first` and `second`
            }
        }
    }
}

Since the get operator does not mutate the EntityBag, should it be added to the EntityBag interface rather than be an addition specific to MutableEntityBag? If there's a reason to leave it out of EntityBag, do you have any suggestions for how I might prevent a collision system from checking the same pair of entities more than once? This is my first foray into using an ECS, so perhaps I am approaching the situation in a less than ideal way.

Family is not updated in onAlpha

Systems with a fixed interval do not update the family correctly in onAlpha. This can lead to FleksNoSuchComponentException and other unwanted behavior.

I think we simply need to add the isDirty + update check there as well like we do it in onUpdate.

Exception in thread "main" com.github.quillraven.fleks.FleksNoSuchComponentException: Entity Entity(id=1) has no component of type com.github.quillraven.dinoleon.component.TransformComponent
	at com.github.quillraven.fleks.ComponentMapper.get-_zL0x1I(component.kt:83)
	at com.github.quillraven.dinoleon.system.PhysicSystem.onAlphaEntity-nJivbwQ(PhysicSystem.kt:52)
	at com.github.quillraven.fleks.IteratingSystem.onAlpha(system.kt:203)
	at com.github.quillraven.fleks.IntervalSystem.onUpdate(system.kt:78)
	at com.github.quillraven.dinoleon.system.PhysicSystem.onUpdate(PhysicSystem.kt:24)
	at com.github.quillraven.fleks.SystemService.update(system.kt:422)
	at com.github.quillraven.fleks.World.update(world.kt:151)
	at com.github.quillraven.dinoleon.screen.GameScreen.render(GameScreen.kt:67)
	at ktx.app.KtxGame.render(game.kt:55)
	at com.badlogic.gdx.backends.lwjgl3.Lwjgl3Window.update(Lwjgl3Window.java:403)
	at com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application.loop(Lwjgl3Application.java:143)
	at com.badlogic.gdx.backends.lwjgl3.Lwjgl3Application.<init>(Lwjgl3Application.java:116)
	at com.github.quillraven.dinoleon.lwjgl3.Lwjgl3Launcher.main(Lwjgl3Launcher.kt:10)
	at com.github.quillraven.dinoleon.lwjgl3.Lwjgl3Launcher.main(Lwjgl3Launcher.kt)

Support component removal in a more flexible way to call custom user actions

While thinking about a small but still "complex" game to test Fleks in a real example I come up with a typical use case for Box2D which is removing bodies of the world which are linked to an entity.

Right now it will already work but I think not in a nice way. We somehow need a way to call a user action to cleanup specific component data if necessary like removing a body of a world. Therefore, this action also needs access to component mappers for fast component retrieval. It could be a function of a system or we allow users to get mappers via the world instance e.g..


It could be a separate onRemove function for the EntityListener. If I remember correctly I did not introduce it because I was able to work around it with the existing onEntityCfgChanged. I needed families to get notified when an entity gets removed to remove them of any family. This was achieved by calling onEntityCfgChanged with an empty component mask.

If a new method gets introduced then we should also rewrite the related family code.


Right now I am still on holiday and will not look into it. Also, I want to start the game mentioned above first to see if there are other use cases that should be supported better.

But to not forget about that use-case, let's open this issue ;)

Multithreading support

I was wondering about the possibility of multithreading support by making use of the fact that not all systems will depend on the same entities, so why not allow unrelated systems to run in parallel on separate threads?

It could look something like this:

world {
  systems {
    add(FirstSystem())
    parallel {
      add(ParallelSystem1())
      add(ParallelSystem2())
    }
    add(FinalSystem())
  }
}

Of course, you would have to use this at your own digression and make sure they don't share any dependencies, unless it can be auto-calculated by comparing their families and injectables.

Is something like this achievable in the current architecture?

Bring DSL to JVM version

We added a world creation DSL to the KMP version (next branch). I think it makes sense to also align that with the JVM version.

@jobe-m : I was thinking of also introducing a world DSL function instead of using the World constructor meaning that we write something like:

world {
  systems {
    add<Sys1>()
    add<Sys2>()
  }

  injectables {
    add("42")
    add(eventMgr)
  }
}

It is a minor detail but I see two benefits:

  1. since everything is lower case in the current DSL it makes sense to also have the world in lowercase
  2. when using Box2D we have a nameclash since Box2D also calls its "core object" World. You have to be careful when importing to choose the correct World. When we have a unique world DSL function and a private constructor then this nameclash is avoided and importing the wrong World, at least in that scenario, cannot happen anymore

What do you think? I can add this small adjustment to the next branch as well of course!

Unexpected(?) behavior with nested `entity {}` calls

This is an issue I also encountered with 1.6 but it was just before starting the update to 2.0 and I was hoping it wouldn't happen there.

var playerEntity: Entity?
var subEntity: Entity?

val entityWorld = world(entityCapacity = 1000) {
    // World things
}

with(entityWorld) {
    playerEntity = entity {
        it += Player()
        it += Storage().apply {
            subEntity = entity {
                it += Identifier("SUPPLIES")
                it += Quantity(210)
            }

            contents += subEntity!!
        }
        it += StageActor()
        it += TextureAnimation()
        it += PhysicsBody()
    }
}

entityWorld.snapshotOf(playerEntity!!)
entityWorld.snapshotOf(subEntity!!)

The internal details of PhysicsBody, TextureAnimation, StageActor and Player shouldn't be relevant. Storage has one property contents (a MutableList of Entity).

The issue is that snapshotOf(playerEntity!!) shows it having only the Player() component, but it should have Storage, StageActor, TextureAnimation and PhysicsBody as well. While snapshotOf(subEntity!!) crashes with this error:

Exception in thread "main" com.github.quillraven.fleks.FleksNoSuchEntityComponentException: Entity 'Entity(id=1)' has no component of type 'TextureAnimation'.
	at com.github.quillraven.fleks.ComponentsHolder.get(component.kt:131)
	at com.github.quillraven.fleks.World.snapshotOf(world.kt:448)

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.