Skip to content

Serialization

Simon edited this page Oct 16, 2023 · 14 revisions

Serialization and Deserialization in Fleks can be achieved via the snapshot functionality of the world. A Snapshot is a container for the components and tags of an entity.

data class Snapshot(
    val components: List<Component<*>>,
    val tags: List<UniqueId<*>>,
)

LibGDX

LibGDX has a built-in way of reading and writing JSON. However, there are a few things to note:

  • keys in JSON are always stored as a string
  • serializing/deserializing collections requires to pass additional information to the JSON writer/reader

The first point can be solved easily by converting an Entity to its id. That way we only store the int value in the json instead of the string representation. In the example code below this is done via world.snapshot().mapKeys { it.key.id }:

  • int = 0
  • string = Entity(id=0)

The second point is also not difficult. We simply tell the fromJson method that we want to load a map and that the values are Snapshot instances. Since we converted entities to their ids before, we also need to convert them back. The example below does that via jsonSnapshot.mapKeys { Entity(it.key.toInt(), 0u) }.

Here is the code:

import com.badlogic.gdx.utils.Json
import com.github.quillraven.fleks.Component
import com.github.quillraven.fleks.ComponentType
import com.github.quillraven.fleks.Entity
import com.github.quillraven.fleks.configureWorld

data class Position(val x: Int = 0, val y: Int = 0) : Component<Position> {
    override fun type(): ComponentType<Position> = Position

    companion object : ComponentType<Position>()
}

data class Speed(val value: Float = 1f) : Component<Speed> {
    override fun type(): ComponentType<Speed> = Speed

    companion object : ComponentType<Speed>()
}

fun main() {
    val world = configureWorld { }

    // create two entities with components
    world.entity {
        it += Position(3, 3)
        it += Speed()
    }
    world.entity {
        it += Position(1, 2)
        it += Speed(2.5f)
    }

    // serialize world snapshot
    var snapshot = world.snapshot().mapKeys { it.key.id }
    val json = Json().toJson(snapshot)
    println(json) // prints: {0:{class:com.github.quillraven.fleks.Snapshot,components:[{class:com.gdx.test.Speed},{class:com.gdx.test.Position,x:3,y:3}]},1:{class:com.github.quillraven.fleks.Snapshot,components:[{class:com.gdx.test.Speed,value:2.5},{class:com.gdx.test.Position,x:1,y:2}]}}

    // clear the world
    world.removeAll(clearRecycled = true)
    println(world.numEntities) // prints: 0

    // deserialize world snapshot
    val jsonSnapshot = Json().fromJson(HashMap::class.java, Snapshot::class.java, json) as Map<String, Snapshot>
    world.loadSnapshot(jsonSnapshot.mapKeys { Entity(it.key.toInt(), 0u) })
    println(world.numEntities) // prints: 2
    snapshot = world.snapshot().mapKeys { it.key.id }
    println(Json().toJson(snapshot)) // prints: {0:{class:com.github.quillraven.fleks.Snapshot,components:[{class:com.gdx.test.Speed},{class:com.gdx.test.Position,x:3,y:3}]},1:{class:com.github.quillraven.fleks.Snapshot,components:[{class:com.gdx.test.Speed,value:2.5},{class:com.gdx.test.Position,x:1,y:2}]}}
}

The example above ignores the entity version information, which might be sufficient for your game, but in case you need it as well then here is the updated code snippet. For more details about entity version, check out the entity wiki.

fun main() {
    val world = configureWorld { }

    // create two entities with components
    world.entity {
        it += Position(3, 3)
        it += Speed()
    }
    world.entity {
        it += Position(1, 2)
        it += Speed(2.5f)
    }

    // serialize world snapshot
    var snapshot = world.snapshot()
    val json = Json().toJson(snapshot)
    println(json) // prints: {Entity(id=0, version=0):{class:com.github.quillraven.fleks.Snapshot,components:[{class:om.gdx.test.Speed},{class:om.gdx.test.Position,x:3,y:3}]},Entity(id=1, version=0):{class:com.github.quillraven.fleks.Snapshot,components:[{class:com.gdx.test.Speed,value:2.5},{class:com.gdx.test.Position,x:1,y:2}]}}

    // clear the world
    world.removeAll(clearRecycled = true)
    println(world.numEntities) // prints: 0

    // deserialize world snapshot
    val jsonSnapshot = Json().fromJson(HashMap::class.java, Snapshot::class.java, json) as Map<String, Snapshot>
    world.loadSnapshot(jsonSnapshot.mapKeys {
        Entity(
            it.key.substringAfter("id=").substringBefore(", ").toInt(),
            it.key.substringAfter("version=").substringBefore(")").toUInt()
        )
    })
    println(world.numEntities) // prints: 2
    snapshot = world.snapshot()
    println(Json().toJson(snapshot)) // prints: {Entity(id=0, version=0):{class:com.github.quillraven.fleks.Snapshot,components:[{class:om.gdx.test.Speed},{class:om.gdx.test.Position,x:3,y:3}]},Entity(id=1, version=0):{class:com.github.quillraven.fleks.Snapshot,components:[{class:com.gdx.test.Speed,value:2.5},{class:com.gdx.test.Position,x:1,y:2}]}}
}

Here is a link that describes how to serialize/deserialize the components of a single entity.

KorGE

Here is a link to an example on how to do it in KorGE.

Clone this wiki locally