Skip to content

Poolable Components

jobeGameDev edited this page Jul 18, 2025 · 44 revisions

KorGE-Fleks is using a slightly expanded version of Fleks Components. Components need to be poolable to avoid garbage collection and cloneable for the SnapshotSerializerSystem (rewind/forward snapshot and save-game (de-)serialization). Thus, KorGE-Fleks Components are derived from PoolableComponent<T>.

Properties in Components

All provided components in KorGE-Fleks contain only basic property types like:

  • String
  • Number (Int, Float, Double)
  • Enum class (like Easing from KorGE)
  • Entity (static data class)
  • Collections of above types in MutableLists and MutableMaps are also supported

For simplicity all properties are independent of any KorGE-specific complex classes. Components do not contain any KorGE-related complex objects like Views, Image, Camera, etc.

Also basic types from KorGE were taken over like the Easing enum class for the TweenAnimationSystem.

Component Life Cycle functions

Poolable Components can override the following functions:

  • initComponent
  • cleanupComponent
  • initPrefabs
  • cleanupPrefabs

initComponent and cleanupComponent functions are called when the component was added or removed from an entity. This is transparent when the rewind/forward snapshot feature is used. Snapshots are full copies of a world configuration and contain already initialized components. Thus, both functions are not invoked when rewinding or forwarding the game state. I.e. when snapshots are loaded into a fleks world.

In most cases at least the cleanupComponent function has to be implemented in order to reset all property values to initial state for reusing the component after it was put back into the component pool.

initPrefabs and cleanupPrefabs are meant to initialize and cleanup external prefabs which a component/entity needs. Therefore these functions are called when components get added and removed to entities. But also they are called when rewinding or forwarding the game state after restoring a snapshot.

All four functions will be automatically called by KorGe-Fleks as part of component life cycle.

The functions are called in this order:

On Component Added

  • initComponent
  • initPrefabs

On Component Removed

  • cleanupPrefabs
  • cleanupComponent

On Rewind/Forward Snapshot

  • cleanupPrefabs
  • initPrefabs

On Load a Save-Game (deserialization)

  • initPrefabs

Additional init and cleanup functions

Two additional functions init(from) and cleanup needs to be used when a component class is not used as Fleks component. Thus, both functions are not called as part of component life cycle. They are ment to manually initialize and cleanup a "component" object which is set as a property of another component. This is called Poolable Data below.

Rules

There are some rules that needs to be followed when writing or extending Components for KorGE-Fleks:

  • Add only primitive types and mutable collections of primitive types
  • Add primitive types as var, so they can be reset by the cleanupComponent function before being reused through the pooling mechanism
  • Add only mutable types of collections and as val, they should be cleared in cleanupComponent function
  • Own data classes (see Poolable Data below) which are used as properties needs to implement Poolable<T> interface, too

By following these rules all components will be easily serializable and poolable in KorGE-Fleks.

Poolable Data

Poolable (data) classes are not directly used as components but as properties of components. For a consistent way of setting up Pools for poolable (data) classes they are also derived from Poolable<T> interface.

Make sure to call init(from) function from Poolable Data in the clone function of your components. Call either cleanup or free functions in the cleanupComponent of the components. The cleanup function should be used when the data class is added as value property of a component. The free function has to be used to return a data object to the pool when the component is going to be recycled (put back into its pool, too). Those data objects which needs to be freed are mostly used in lists or maps of components.

Live Template

For saving some time to write Component classes and also to make that process less error prone it is possible to use live templates in Intellij IDEA. Just open in Intellij Settings -> Live Templates. In the list choose Kotlin and press the + button at the top of the list box. Fill in below mentioned fields:

PoolableComponent

  • Abbreviation: flekscomponent
  • Description: Creates a new Component class for an entity in Korge-fleks
  • Edit Variables...: Add Expression decapitalize(FLEKS_COMPONENT) to variable LOWER_FLEKS_COMPONENT
  • Template text:
import com.github.quillraven.fleks.*
import korlibs.korge.fleks.utils.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable


/**
 * This component is used to ...
 *
 * Author's hint: When adding new properties to the component, make sure to reset them in the
 *                [cleanup] function and initialize them in the [init] function.
 */
@Serializable @SerialName("$FLEKS_COMPONENT$")
class $FLEKS_COMPONENT$ private constructor(
    var answer: Int = 42
) : PoolableComponent<$FLEKS_COMPONENT$>() {
    // Init an existing component data instance with data from another component
    // This is used for component instances when they are a value property of another component
    fun init(from: $FLEKS_COMPONENT$) {
        answer = from.answer
    }

    // Cleanup the component data instance manually
    // This is used for component instances when they are a value property of another component
    fun cleanup() {
        answer = 42
    }

    override fun type() = $FLEKS_COMPONENT$Component

    companion object {
        val $FLEKS_COMPONENT$Component = componentTypeOf<$FLEKS_COMPONENT$>()

        // Use this function to create a new instance of component data as val inside another component
        fun static$FLEKS_COMPONENT$Component(config: $FLEKS_COMPONENT$.() -> Unit): $FLEKS_COMPONENT$ =
            $FLEKS_COMPONENT$().apply(config)

        // Use this function to get a new instance of a component from the pool and add it to an entity
        fun $LOWER_FLEKS_COMPONENT$Component(config: $FLEKS_COMPONENT$.() -> Unit): $FLEKS_COMPONENT$ =
            pool.alloc().apply(config)

        private val pool = Pool(AppConfig.POOL_PREALLOCATE, "$FLEKS_COMPONENT$") { $FLEKS_COMPONENT$() }
    }

    // Clone a new instance of the component from the pool
    override fun clone(): $FLEKS_COMPONENT$ = $LOWER_FLEKS_COMPONENT$Component { init(from = this@$FLEKS_COMPONENT$) }

    // Initialize the component automatically when it is added to an entity
    override fun World.initComponent(entity: Entity) {
    }

    // Cleanup/Reset the component automatically when it is removed from an entity (component will be returned to the pool eventually)
    override fun World.cleanupComponent(entity: Entity) {
        cleanup()
    }

    // Initialize an external prefab when the component is added to an entity
    override fun World.initPrefabs(entity: Entity) {
    }

    // Cleanup/Reset an external prefab when the component is removed from an entity
    override fun World.cleanupPrefabs(entity: Entity) {
    }

    // Free the component and return it to the pool - this is called directly by the SnapshotSerializerSystem
    override fun free() {
        cleanup()
        pool.free(this)
    }
}

Then click on Define and choose Kotlin from the drop-down menu. Finally press the Apply button to save the changes.

Poolable (data)

  • Abbreviation: fleksdata
  • Description: Creates a new data class for usage in poolable components
  • Edit Variables...: Add Expression decapitalize(DATA) to variable LOWER_DATA
  • Template text:
import korlibs.korge.fleks.utils.*
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable


/**
 * This class is used to ...
 */
@Serializable @SerialName("$DATA$")
class $DATA$ private constructor(
    var value: Int = 42
) : Poolable<$DATA$> {
    // Init an existing data instance with data from another one
    override fun init(from: $DATA$) {
        value = from.value
    }

    // Cleanup data instance manually
    // This is used for data instances when they are a value property of a component
    override fun cleanup() {
        value = 0
    }

    // Clone a new data instance from the pool
    override fun clone(): $DATA$ = $LOWER_DATA$ { init(from = this@$DATA$) }

    // Cleanup the tween data instance manually
    override fun free() {
        cleanup()
        pool.free(this)
    }

    companion object {
        // Use this function to create a new instance of data as value property inside a component
        fun static$DATA$(config: $DATA$.() -> Unit): $DATA$ =
            $DATA$().apply(config)

        // Use this function to get a new instance of the data object from the pool
        fun $LOWER_DATA$(config: $DATA$.() -> Unit): $DATA$ =
            pool.alloc().apply(config)

        private val pool = Pool(AppConfig.POOL_PREALLOCATE, "$DATA$") { $DATA$() }
    }
}

Then click on Define and choose Kotlin from the drop-down menu. Finally press the Apply button to save the changes.

Clone this wiki locally