System (old) - Quillraven/Fleks GitHub Wiki

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

IteratingSystem extends IntervalSystem but in addition it requires you to specify the relevant components of entities which the system will iterate over. There are three types to define this component configuration:

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

Usually, your systems depend on certain other things like a SpriteBatch or Viewport. Fleks uses dependency injection for that to make it easier to adjust arguments of your systems later on without touching the code of the caller side.

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.

JVM snippet

class DayNightSystem(
    private val eventMgr: EventManager
) : 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()
        }
    }
}

KMP snippet

class DayNightSystem : IntervalSystem() {
    private var currentTime = 0f
    private var isDay = false

    private val eventMgr: EventManager = Inject.dependency()

    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()
        }
    }
}

The DayNightSystem requires an EventManager which we need to inject. To achieve that we can define it when creating our world by using the injectables function:

JVM snippet

val eventManager = EventManager()
val world = world {
    injectables {
        add(eventManager)
    }

    systems {
        add<DayNightSystem>()
    }
}

KMP snippet

val eventManager = EventManager()
val world = world {
    injectables {
        add(eventManager)
    }

    systems {
        add(::DayNightSystem)
    }
}

There might be cases where you need multiple dependencies of the same type. In Fleks this can be solved via named dependencies using the Qualifier annotation. Here is an example of a system that takes two String parameters. They are registered by name HighscoreKey and LevelKey:

JVM snippet

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

fun main() {
    val world = world {
        systems {
            add<NamedDependenciesSystem>()
        }
        
        injectables {
            // inject String dependencies from above via their type names
            add("HighscoreKey", "hs-key")
            add("LevelKey", "Level001")
        }
    }
}

KMP snippet

private class NamedDependenciesSystem : IntervalSystem() {

    private val hsKey: String = Inject.dependency("HighscoreKey") // will have the value "hs-key"
    private val levelKey: String = Inject.dependency("LevelKey")  // will have the value "Level001"

    // ...
}

fun main() {
    val world = world {
        systems {
            add(::NamedDependenciesSystem)
        }
        
        injectables {
            // inject String dependencies from above via their type names
            add("HighscoreKey", "hs-key")
            add("LevelKey", "Level001")
        }
    }
}

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:

JVM snippet

@AllOf([Position::class, Physic::class])
@NoneOf([Dead::class])
@AnyOf([Sprite::class, Animation::class])
class AnimationSystem : IteratingSystem() {
    override fun onTickEntity(entity: Entity) {
        // update entities in here
    }
}

KMP snippet

class AnimationSystem : IteratingSystem(
    allOfComponents = arrayOf(Position::class, Physic::class),
    noneOfComponents = arrayOf(Dead::class),
    anyOfComponents = arrayOf(Sprite::class, Animation::class)
) {
    override fun onTickEntity(entity: Entity) {
        // update entities in here
    }
}

Often, an IteratingSystem needs access to the components of an entity. In Fleks this is done via so called ComponentMapper. ComponentMapper are automatically injected into a system and do not need to be defined in the world's configuration.

Note that for the KMP version you need to register the components that your world requires because it needs to know how to create a component (=factory method). In the JVM version this is not needed because it uses reflection to identify the no-args constructor of a component which is not possible in KMP.

Let's see how we can access the PositionComponent of an entity in the system above:

JVM snippet

@AllOf([Position::class, Physic::class])
@NoneOf([Dead::class])
@AnyOf([Sprite::class, Animation::class])
class AnimationSystem(
    private val positions: ComponentMapper<Position>
) : IteratingSystem() {
    override fun onTickEntity(entity: Entity) {
        val entityPosition: Position = positions[entity]
    }
}

KMP snippet

class AnimationSystem : IteratingSystem(
    allOfComponents = arrayOf(Position::class, Physic::class),
    noneOfComponents = arrayOf(Dead::class),
    anyOfComponents = arrayOf(Sprite::class, Animation::class)
) {

    private val positions: ComponentMapper<Position> = Inject.componentMapper()

    override fun onTickEntity(entity: Entity) {
        val entityPosition: Position = positions[entity]
    }
}

fun main() {
    val world = world {
        components {
            // this is necessary in order to create and inject the ComponentMapper above
            add(::Position)
        }
    }
}

There is also a getOrNull version available in case a component is not mandatory for every entity that gets processed by a system. An example is:

@AllOf([Position::class, Physic::class])
@NoneOf([Dead::class])
@AnyOf([Sprite::class, Animation::class])
class AnimationSystem(
    private val animations: ComponentMapper<Animation>
) : IteratingSystem() {
    override fun onTickEntity(entity: Entity) {
        animations.getOrNull(entity)?.let { animation ->
            // entity has animation component which can be modified inside this block
        }
    }
}

If you need to modify the component configuration of an entity then this can be done via the configureEntity function of an IteratingSystem. The purpose of this function is performance reasons to trigger internal calculations of Fleks only once instead of each time a component gets added or removed. Inside configureEntity you get access to three special ComponentMapper functions:

  • add: adds a component to an entity
  • remove: removes a component from an entity
  • addOrUpdate: adds a component only if it does not exist yet. Otherwise, it just updates the existing component

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:

@AllOf([Life::class])
@NoneOf([Dead::class])
class DeathSystem(
    private val lives: ComponentMapper<Life>,
    private val deads: ComponentMapper<Dead>
) : IteratingSystem() {
    override fun onTickEntity(entity: Entity) {
        if (lives[entity].hitpoints <= 0f) {
            configureEntity(entity) {
                deads.add(it)
                lives.remove(it)
            }
        }
    }
}

ComponentMapper are not restricted to systems. You can get a mapper also from your world whenever needed. Here is an example that gets the LifeComponent mapper of the snippet above:

fun main() {
    val world = world { /* config omitted */ }
    val lives = world.mapper<Life>()
}

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 function helps to create such a comparator in a concise way.

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

JVM snippet

@AllOf([Position::class, Render::class])
class RenderSystem(
    private val positions: ComponentMapper<Position>
) : IteratingSystem(compareEntity { entA, entB -> positions[entA].y.compareTo(positions[entB].y) }) {
    override fun onTickEntity(entity: Entity) {
        // render entities: entities are sorted by their y-coordinate
    }
}

KMP snippet

class RenderSystem : IteratingSystem(
    allOfComponents = arrayOf(Position::class, Render::class),
    comparator = compareEntity { entA, entB -> positions[entA].y.compareTo(positions[entB].y) }
) {

    private val positions: ComponentMapper<Position> = Inject.componentMapper()

    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:

@AllOf([Position::class, Render::class])
class RenderSystem(
    private val positions: ComponentMapper<Position>
) : IteratingSystem(
    compareEntity { entA, entB -> positions[entA].y.compareTo(positions[entB].y) },
    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:

JVM snippet

class DebugSystem(
    private val box2dWorld: World,
    private val camera: Camera,
    stage: Stage
) : 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 = world { /* configuration omitted */ }

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

KMP snippet

class DebugSystem : IntervalSystem() {

    private val box2dWorld: World = Inject.dependency()
    private val camera: Camera = Inject.dependency()
    private val stage: Stage = Inject.dependency()

    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 = world { /* 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 = world {}
    val e1 = world.entity()
    val e2 = world.entity()
    val e3 = world.entity()
    world.remove(e2)

    // this will iterate over entities e1 and e3
    world.forEach { entity ->
        // do something with the entity
    }
}
⚠️ **GitHub.com Fallback** ⚠️