/Fleks

Fast, lightweight, multi-platform entity component system in Kotlin

Primary LanguageKotlinMIT LicenseMIT

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.10</version>
</dependency>

Gradle (Groovy)

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

Gradle (Kotlin)

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

KorGE

dependencyMulti("io.github.quillraven.fleks:Fleks:2.10", 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