-
-
Notifications
You must be signed in to change notification settings - Fork 23
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).
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:
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()
}
}
}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.
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 ornull -
EntityRef has componentType/EntityRef hasNo componentType/componentType in EntityRef→ component checks -
EntityRef.configure { ... }→ add/remove components -
EntityRef.remove()→ remove the entity from the world - ...
The general idea: instead of storing an Entity, store an EntityRef.
-
Change the component field type from
EntitytoEntityRef. If the field is optional, useEntityRef?with anullinitial value, otherwise useEntityRef = EntityRef.NONEas a non-null default. -
Replace assignments. Wherever you previously assigned an
Entity, assign a ref created viagetRef():// before component.target = someEntity // after component.target = someEntity.getRef()
-
Replace staleness checks. Wherever you previously checked
entity.wasRemoved()(or similar), use the ref'sisValid()/isNotValid():// before if (!component.target.wasRemoved()) { ... } // after if (component.target.isValid()) { ... }
-
Use the ref where you used the entity.
EntityRefsupports the same extension functions asEntity(get,getOrNull,has,hasNo,configure,remove), so most usages work without further changes. If you ever need the underlyingEntityback, access theref.entityproperty.