Skip to content
Simon edited this page Dec 17, 2023 · 9 revisions

There are two systems in Fleks:

  • IntervalSystem: system without relation to entities
  • IteratingSystem: system with relation to entities of a specific component configuration

IntervalSystem has two optional arguments:

  • interval: defines the time in milliseconds when the system should be updated. Default is EachFrame which means that it gets called every time world.update is called
    • The other interval option is Fixed which takes a step time in milliseconds and runs the system with that fixed time step
  • enabled: defines if the system will be processed or not. Default value is true

There is a dedicated onEnable and onDisable method for an IntervalSystem that can be overridden in case you need react on enabled changes. Also, an onInit method can be used to add initialization logic to a system after the world's configuration is finished.


IteratingSystem extends IntervalSystem but in addition it requires you to specify the family of entities which the system will iterate over. There are three possibilities to define a family:

  • all: entity must have all the components specified
  • none: entity must not have any component specified
  • any: entity must have at least one of the components specified

Usually, your systems depend on certain other things like a SpriteBatch or Viewport. Fleks provides the possibility to use dependency injection for that to make it easier to adjust arguments of your systems later on without touching the code of the caller side. This is optional and not mandatory to use but I personally suggest to use it. Also, the examples below will use it but can of course be written without DI.

First, let's have a look on how to create a simple IntervalSystem that gets called every time world.update is called. It is a made up example of a Day-Night-Cycle system which switches between day and night every second and dispatches a game event via an EventManager. Note the usage of the inject function in the constructor which is how the DI mechanism works. Because we add an EventManager as injectable in our World, we can then inject it wherever we need it:

class EventManager {
    // code omitted
}

class DayNightSystem(
    // inject the EventManager instance that is registered below to our world
    private val eventMgr: EventManager = inject()
) : IntervalSystem() {
    private var currentTime = 0f
    private var isDay = false

    override fun onTick() {
        // deltaTime is not needed in every system that's why it is not a parameter of "onTick".
        // However, if you need it, you can still access it via the IteratingSystem's deltaTime property
        currentTime += deltaTime
        if (currentTime >= 1000 && !isDay) {
            isDay = true
            eventMgr.publishDayEvent()
        } else if (currentTime >= 2000 && isDay) {
            isDay = false
            currentTime = 0f
            eventMgr.publishNightEvent()
        }
    }
}

fun main() {
    val world = configureWorld {
        injectables {
            // add an EventManager instance as a dependency to the world
            add(EventManager())
        }

        systems {
            // here we use DI to create the system but you can of course pass the EventManager instance directly. It is up to you ;)
            add(DayNightSystem())
        }
    }
}

There might be cases where you need multiple dependencies of the same type. In Fleks this can be solved via named dependencies. When adding an injectable then per default, if you don't provide a name, it uses the KClass.simpleName as an identifier. However, it is also possible to provide your own name to avoid name clashes e.g. when you need the same class but different instances. Here is an example of a system that takes two String parameters. They are registered by name HighscoreKey and LevelKey:

class NamedDependenciesSystem(
    val hsKey: String = inject("HighscoreKey"), // will have the value hs-key
    val levelKey: String = inject("LevelKey")   // will have the value Level001
) : IntervalSystem() {
    // ...
}

fun main() {
    configureWorld {
        injectables {
            // add two String dependencies via a specific name
            add("HighscoreKey", "hs-key")
            add("LevelKey", "Level001")
        }

        systems {
            add(NamedDependenciesSystem())
        }
    }
}

Let's create an IteratingSystem that iterates over all entities with a PositionComponent, PhysicComponent and at least a SpriteComponent or AnimationComponent but without a DeadComponent. We can do that via the family function:

class AnimationSystem : IteratingSystem(
    family { all(Position, Physic).none(Dead).any(Sprite, Animation) }
) {
    override fun onTickEntity(entity: Entity) {
        // update entities in here
    }
}

Often, an IteratingSystem needs access to the components of an entity. Let's see how we can access the PositionComponent of an entity in the system above. For more details on what other functions are available, please check out the Component wiki section:

class Position : Component<Position> {
    override fun type() = Position

    companion object : ComponentType<Position>()
}

class AnimationSystem : IteratingSystem(
    family { all(Position, Physic).none(Dead).any(Sprite, Animation) }
) {
    override fun onTickEntity(entity: Entity) {
        val entityPosition: Position = entity[Position]
    }
}

If you need to modify the component configuration of an entity then this can be done via the Entity.configure function. Inside configure you get access to three special entity extension functions:

  • +=: adds a component to an entity
  • -=: removes a component from an entity
  • getOrAdd: returns an existing component or, if it does not exist yet, then it adds it

The reason why the modification of components (=add and remove) is happening in a separate function is performance. Adding/removing components of an entity defines to which family it belongs. Checking if an entity is part of a family is a rather expensive operation. To avoid this calculation each time a component gets added/removed, we can optimize that to only do it once at the end when all modifications are done (=end of configure block). This is similar to how the world.entity function is working. It does this calculation also only once at the end of the entity block when all components are added to the newly created entity.

Let's see how a system can look like that adds a DeadComponent to an entity and removes a LifeComponent when its hitpoints are <= 0. In addition it adds 5 damage if it has more than 0 hitpoints:

data class Life(var hitpoints: Float) : Component<Life> {
    override fun type() = Life

    companion object : ComponentType<Life>()
}

data class Damage(var amount: Float) : Component<Damage> {
    override fun type() = Damage

    companion object : ComponentType<Damage>()
}

class Dead : Component<Dead> {
    override fun type() = Dead

    companion object : ComponentType<Dead>()
}

class DeathSystem : IteratingSystem(
    family { all(Life).none(Dead) }
) {
    override fun onTickEntity(entity: Entity) {
        if (entity[Life].hitpoints <= 0f) {
            // call 'configure' before changing an entity's components
            entity.configure {
                it += Dead()
                it -= Life
            }
        } else {
            entity.configure {
                // the Damage(0f) instance only gets created, if the entity does not have a damage component yet
                it.getOrAdd(Damage){ Damage(0f) }.amount += 5f
            }
        }
    }
}

Sometimes it might be necessary to sort entities before iterating over them like e.g. in a RenderSystem that needs to render entities by their y or z-coordinate. In Fleks this can be achieved by passing an EntityComparator to an IteratingSystem. Entities are then sorted automatically every time the system gets updated. The compareEntity and compareEntityBy functions help to create such a comparator in a concise way.

Here is an example of a RenderSystem that sorts entities by their y-coordinate:

// 1) via the 'compareEntity' function
class RenderSystem : IteratingSystem(
    family = family { all(Position, Render) },
    comparator = compareEntity { entA, entB -> entA[Position].y.compareTo(entB[Position].y) }
) {
    override fun onTickEntity(entity: Entity) {
        // render entities: entities are sorted by their y-coordinate
    }
}

// 2) via the 'compareEntityBy' function which requires the component to implement the Comparable interface
data class Position(
    var x: Float,
    var y: Float,
) : Component<Position>, Comparable<Position> {
    override fun type() = Position

    override fun compareTo(other: Position): Int {
        return y.compareTo(other.y)
    }

    companion object : ComponentType<Position>()
}

class RenderSystem : IteratingSystem(
    family = family { all(Position, Render) },
    comparator = compareEntityBy(Position)
) {
    override fun onTickEntity(entity: Entity) {
        // render entities: entities are sorted by their y-coordinate
    }
}

The default SortingType is Automatic which means that the IteratingSystem is sorting automatically each time it gets updated. This can be changed to Manual by setting the sortingType parameter accordingly. In that case the doSort flag of the IteratingSystem needs to be set programmatically whenever sorting should be done. The flag gets cleared after the sorting.

This is how the example above could be written with a Manual SortingType:

class RenderSystem : IteratingSystem(
    family = family { all(Position, Render) },
    comparator = compareEntityBy(Position),
    sortingType = Manual
) {
    override fun onTick() {
        doSort = true
        super.onTick()
    }

    override fun onTickEntity(entity: Entity) {
        // render entities: entities are sorted by their y-coordinate
    }
}

Sometimes a system might allocate special resources that you want to free before closing your application. An example would be a LibGDX game where a system might create a disposable resource internally.

For this purpose the world's dispose function can be used which first removes all entities of the world and afterwards calls the onDispose function of each system.

Here is an example of a DebugSystem that creates a Box2D debug renderer for the physics internally and disposes it:

class DebugSystem(
    private val box2dWorld: World = inject(),
    private val camera: Camera = inject(),
    stage: Stage = inject()
) : IntervalSystem() {
    private val renderer = Box2DDebugRenderer()

    override fun onTick() {
        physicRenderer.render(box2dWorld, camera.combined)
    }

    // this is an optional function that can be used to free specific resources
    override fun onDispose() {
        renderer.dispose()
    }
}

fun main() {
    val world = configureWorld { /* configuration omitted */ }

    // following call disposes the DebugSystem
    world.dispose()
}

If you ever need to iterate over entities outside a system then this is also possible but please note that systems are always the preferred way of iteration in an entity component system. The world's forEach function allows you to iterate over all active entities:

fun main() {
    val world = configureWorld {}
    val e1 = world.entity()
    val e2 = world.entity()
    val e3 = world.entity()
    world -= e2

    // this will iterate over entities e1 and e3
    world.forEach { entity ->
        // do something with the entity
    }
}

You can also create families outside a system and use them in a similar way like systems:

fun main() {
    val world = configureWorld {}

    // Retrieve a family. If a family with such a configuration already exists, then it will be returned.
    // There are no duplicate families.
    val family = world.family { all(Position) }

    family.forEach { entity ->
        // do something with the entity
    }
}
Clone this wiki locally