Skip to content

EntityRef

Simon edited this page Aug 29, 2026 · 1 revision

EntityRef

An Entity is a lightweight value class that only wraps an id. It can be stored in a Component, but it does not tell you anything about the entity's lifecycle. If that entity gets removed from the world later on, your stored Entity value still points to the (now reused) id and you have no way of knowing that it became stale.

EntityRef solves that problem. It is a stable reference to an Entity that you can safely store inside a component. As long as the referenced entity is part of the world, the ref is valid. As soon as the entity gets removed, Fleks marks the ref as invalid, so you can reliably detect stale references instead of accidentally operating on an unrelated entity that reused the same id.

An EntityRef is created via entity.getRef(). The same ref instance is returned every time you call getRef() for the same entity. You can check its state through the valid property or the isValid() / isNotValid() companion extensions (which are also null-safe).

Usage

Imagine a Follow component that holds a reference to the entity another entity should follow:

data class Follow(val target: EntityRef) : Component<Follow> {
    override fun type() = Follow
}

When an entity should start following something, you create the ref and store it in the component. In the example below we assume that within onTick of an IteratingSystem we assign the entity to the component:

class FollowSystem : IteratingSystem(family = Family.all(Follow)) {

    override fun onTickEntity(entity: Entity) {
        // assume we want every entity to follow "someEntity"
        val targetEntity = world.entity { } // just a placeholder for the target instance in this example
        entity[Follow].target = targetEntity.getRef()
    }
}

Whenever you actually want to use the reference, you should first check whether it is still valid:

class FollowSystem : IteratingSystem(family = Family.all(Follow)) {

    override fun onTickEntity(entity: Entity) {
        val follow = entity[Follow]

        // only operate on the target if it is still part of the world
        if (follow.target.isValid()) {
            moveTowards(entity, follow.target)
        } else {
            // target is gone -> stop following
        }
    }
}

isValid() and isNotValid() are null-safe companion extensions, so you can also use them on a nullable reference or one that was initialized with EntityRef.NONE:

Nullable EntityRef? variant

If an entity should not necessarily have a target, you can use a nullable EntityRef?. Here we initially start with null and assign the target during onTick as before:

data class Follow(val target: EntityRef?) : Component<Follow> {
    override fun type() = Follow
}

class FollowSystem : IteratingSystem(family = Family.all(Follow)) {

    override fun onTickEntity(entity: Entity) {
        val follow = entity[Follow]

        // assign a target the first time we see this entity
        if (follow.target == null) {
            follow.target = world.entity().getRef()
        }
    }
}

Non-null EntityRef.NONE variant

If you prefer a non-null field, initialize it with EntityRef.NONE. NONE is never valid and behaves the same way a null would, but without the need for null-checks:

data class Follow(val target: EntityRef = EntityRef.NONE) : Component<Follow> {
    override fun type() = Follow
}

class FollowSystem : IteratingSystem(family = Family.all(Follow)) {

    override fun onTickEntity(entity: Entity) {
        val follow = entity[Follow]

        // assign a target the first time we see this entity
        if (follow.target.isNotValid()) {
            follow.target = world.entity().getRef()
        }

        // and always operate safely on it afterwards
        if (follow.target.isValid()) {
            moveTowards(entity, follow.target)
        }
    }
}

Note: EntityRef.NONE wraps the Entity.NONE sentinel and is always marked as valid = false. This makes it a clean "no target" default that you can check with isNotValid() without any null handling.

Everything an Entity can do, an EntityRef can do too

EntityRef mirrors the Entity extension functions, so you can use it almost like a plain Entity:

  • EntityRef[componentType] → get a component (throws if not present)
  • EntityRef.getOrNull(componentType) → get a component or null
  • EntityRef has componentType / EntityRef hasNo componentType / componentType in EntityRef → component checks
  • EntityRef.configure { ... } → add/remove components
  • EntityRef.remove() → remove the entity from the world
  • ...

Migration from Entity to EntityRef

The general idea: instead of storing an Entity, store an EntityRef.

Step-by-step migration guide

  1. Change the component field type from Entity to EntityRef. If the field is optional, use EntityRef? with a null initial value, otherwise use EntityRef = EntityRef.NONE as a non-null default.

  2. Replace assignments. Wherever you previously assigned an Entity, assign a ref created via getRef():

    // before
    component.target = someEntity
    
    // after
    component.target = someEntity.getRef()
  3. Replace staleness checks. Wherever you previously checked entity.wasRemoved() (or similar), use the ref's isValid() / isNotValid():

    // before
    if (!component.target.wasRemoved()) { ... }
    
    // after
    if (component.target.isValid()) { ... }
  4. Use the ref where you used the entity. EntityRef supports the same extension functions as Entity (get, getOrNull, has, hasNo, configure, remove), so most usages work without further changes. If you ever need the underlying Entity back, access the ref.entity property.

Clone this wiki locally