Fleks ECS

repository·master·Indexed 42 days ago

https://github.com/quillraven/fleks

A fast, lightweight Entity Component System (ECS) library written in Kotlin. Designed for high performance and idiomatic Kotlin usage, version 2.15 provides a null-safe, reflection-free alternative to libraries like Ashley and Artemis-odb, supporting both Kotlin Multiplatform (KMP) and JVM.

Tokens
6.9K
Snippets
27
Records
33
Agent score
54%

What's inside Fleks

  1. Overview of Fleks ECS

    master

    Fleks is a fast, lightweight Entity Component System (ECS) library written in Kotlin. It is designed to be more concise and idiomatic than older ECS libraries like Ashley.

    Key improvements in version 2.x include:

    • Unified API: Combines KMP and JVM flavors into one.
    • No Reflection: Completely removed reflection usage for better performance and easier debugging.
    • Simplified API: ComponentMapper is no longer necessary, making the API more concise and readable.
    • Null-Safety: Improved null-safety compared to older libraries.
  2. Install Fleks via Maven or Gradle

    master

    To use Fleks in your project, add it as a dependency. Version 2.x combines Kotlin Multiplatform (KMP) and JVM flavors into a single dependency. For the latest development builds, you must also include the Sonatype snapshot repository.

    #### Apache Maven
    ```xml
    <dependency>
      <groupId>io.github.quillraven.fleks</groupId>
      <artifactId>Fleks-jvm</artifactId>
      <version>2.15</version>
    </dependency>

    Gradle (Groovy)

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

    Gradle (Kotlin)

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

    KorGE

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

    Snapshot Repository (Required for Snapshot versions)

    // Groovy DSL
    maven { url 'https://central.sonatype.com/repository/maven-snapshots/' }
    
    // Kotlin DSL
    maven { url = uri("https://central.sonatype.com/repository/maven-snapshots/") }
  3. Implement or configure an EntityProvider

    master

    An EntityProvider defines how entities are created, removed, and tracked within a World. It is a core component used by an EntityService to manage the lifecycle of Entity objects.

    Users can specify a custom provider via WorldConfiguration. If no provider is specified, Fleks defaults to using DefaultEntityProvider.

    Key responsibilities of an EntityProvider include:

    • Creating new entities (create())
    • Removing entities (entity -= entity)
    • Tracking active entities (contains(entity))
    • Iterating over active entities (forEach { ... })
    • Resetting the provider (reset())

    If you are implementing a custom provider, you must provide a reference to the World it operates on.

  4. Use EntityRef for stable entity references in components

    master

    An EntityRef is a stable reference to an Entity that can be safely stored within components. Unlike raw Entity values, an EntityRef tracks its own validity. When the referenced Entity is removed from the World, the EntityRef becomes invalid, allowing you to detect stale references.

    Key properties:

    • entity: The underlying Entity being referenced.
    • id: The integer ID of the referenced entity.
    • valid: A boolean indicating if the reference is still active in the world.

    Use the companion object extension functions to check validity safely, even on nullable references.

    // Checking validity of a potentially null EntityRef
    if (myEntityRef.isValid()) {
        // The entity is still alive in the world
        val id = myEntityRef.id
    }
    
    // Using the NONE constant for an invalid reference
    val emptyRef = EntityRef.NONE
    println(emptyRef.isValid()) // false
  5. How ComponentsHolder manages component data

    master

    A ComponentsHolder<T> is responsible for storing all instances of a specific component type for all entities in a World. It uses an internal array where the index corresponds to the Entity.id.

    Key Operations

    • Set (operator fun set): Assigns a component to an entity. If a component of that type already existed, its onRemove lifecycle method is called first. Then, the new component is assigned and its onAdd method is called.
    • Remove (operator fun minusAssign): Removes the component from the entity and triggers its onRemove lifecycle method.
    • Get (operator fun get): Retrieves the component. Throws FleksNoSuchEntityComponentException if the entity does not have the component.
    • Get Or Null (fun getOrNull): Returns the component or null if not present.
    • Contains (operator fun contains): Returns true if the entity has the component.
  6. How EntityTags and Enums work in Fleks

    master

    An EntityTag is a specialized, data-less ComponentType used to tag entities. They are more memory-efficient than standard components.

    You can use entityTagOf() to create a tag, or use the EntityTags type alias to make an enum class behave as a collection of tags. This is done by delegating to entityTagOf().

    enum class MyTags : EntityTags by entityTagOf() {
        TAG_A, TAG_B
    }
  7. Manage the ECS lifecycle with the World class

    master

    The World class is the central hub of the Fleks ECS. It manages the lifecycle of entities, components, and systems. You use it to create entities, add/remove systems, and drive the simulation loop via the update method.

    Key responsibilities:

    • Entity Management: Create, remove, and iterate over entities.
    • System Management: Add and remove IntervalSystem instances that run during the update loop.
    • Simulation Loop: Advance the world state by calling update(deltaTime).
    • Family Management: Define groups of entities (Families) based on component requirements.
    • Dependency Injection: Register and retrieve Injectable objects.
    val world = World(entityCapacity = 1000)
    
    // Add a system
    world += MySystem()
    
    // Create an entity
    val entity = world.entity {
        it += Position(x = 10f, y = 20f)
        it += Velocity(x = 1f, y = 1f)
    }
    
    // Simulation loop
    while (running) {
        world.update(0.016f) // update with 16ms delta
    }
    
    // Cleanup
    world.dispose()
  8. Define a Component in Fleks

    master

    To use a class as a component in Fleks, implement the Component<T> interface. You must provide a ComponentType<T> via the type() function.

    A common and convenient pattern is to use the class's unnamed companion object as its ComponentType by having the companion object inherit from ComponentType<T>.

    data class Position(
        var x: Float,
        var y: Float,
    ) : Component<Position> {
        override fun type(): ComponentType<Position> = Position
    
        companion object : ComponentType<Position>()
    }
  9. Configure a World using configureWorld

    master

    To set up a Fleks World, use the configureWorld function. This function provides a DSL context via WorldConfiguration to define systems, injectables, entity hooks, and family hooks.

    Note that configure() is called internally by configureWorld, which executes the configuration in a specific order:

    1. Injectables
    2. Families
    3. Systems

    This order ensures that hooks are correctly triggered during the world's lifecycle.

    val world = configureWorld(entityCapacity = 1024) {
        // Configuration goes here
    }
  10. Remove all entities with EntityService.removeAll()

    master

    Clears all entities from the service.

    • If clearRecycled is false (default): Only the active entities are removed.
    • If clearRecycled is true: All entities are removed, and the internal state (recycled IDs, component masks, and references) is reset so that new entities start with ID 0.
    // Remove active entities only
    entityService.removeAll()
    
    // Completely reset the service
    entityService.removeAll(clearRecycled = true)
  11. Add and retrieve systems from a World

    master

    Systems in Fleks are instances of IntervalSystem. They are updated during the world.update() call if they are enabled.

    Operations:

    • Add a system: world += system or world.add(system)
    • Remove a system: world -= system or world.remove(system)
    • Retrieve a system: Use world.system<T>() to get a specific type, or world.systemOrNull<T>() if the system might not be present.
    • Check for system: world.contains<T>()

    Note: Adding the same system class twice will throw a FleksSystemAlreadyAddedException.

    // Adding a system
    world += MovementSystem()
    
    // Retrieving a system later
    val movementSystem = world.system<MovementSystem>()
    
    // Checking if a system exists
    if (world.contains<MovementSystem>()) {
        // ...
    }