GKComponent References in GameplayKit
Swift strategies to hold references between GKComponents in GameplayKit with convenience properties.
When setting up an entity component system it is convenient to be able to communicate between the components owned by an entity.
In GameplayKit that can be achieved by asking the owning GKEntity
of a component if it has an instance of some other GKComponent
.
if let component = entity?.component(ofType: SomeComponent.self) {
// Do stuff with the component here...
}
GKComponent Reference in a Class Property
In many cases it can be beneficial and convenient to hold the reference in a class property.
Convenient when a component is referenced in multiple places as it will reduce repetitive code, and beneficial if the component is referenced frequently. That would be the case if it is referenced in the game update loop, as it will then add a bit of optimization not having to resolve the relationship on each frame.
When a GKComponent
initializes it is not yet added to an entity so we can't set the relationship in the convenience property during initialization, which leaves us the options to use either a computed property or a lazy stored property.
Another strategy would be to keep the convenience property optional, just like the entity?
property is in GKComponent
, and then set the reference in didAddToEntity()
. That would leave us having to unwrap the property when using it, which would reduce some of the convenience we're looking for, so we won't go down that route in this case.
Computed Property
The computed property is evaluated when it is referenced, so as long as it is not referenced until the entity is fully setup and contains all needed components, this would be the most simple way to define it.
var someOtherComponent: SomeOtherComponent {
return entity!.component(ofType: SomeOtherComponent.self)!
}
Lots of force unwrapping going on there, so to play it a bit safer and get a relevant error message if the component system is incorrectly initialized, something like this would make more sense.
var someOtherComponent: SomeOtherComponent {
guard let someOtherComponent = entity?.component(ofType: SomeOtherComponent.self) else {
fatalError("The entity needs SomeOtherComponent.")
}
return someOtherComponent
}
That looks better already. If the component uses the referenced component and we have forgot to add it to the owning entity we would now get a meaningful error message so we can fix it.
One drawback to use a computed property in this case is that the computed property is evaluated each time it is referenced. Depending on where and how often the component is requested it might not be optimal performance wise. If it is not used in time critical places we are good to go as-is.
But let's look at the case where we use it in the game update loop. Then it might be re-evaluated multiple times on each update which in turn runs 60 or even 120 times per second.
In Apple's DemoBots
example for GameplayKit they handled that by assigning the computed property to a constant within the update(deltaTime:)
scope. Which is better, but not optimal.
override func update(deltaTime seconds: TimeInterval) {
// Assign the computed property to a constant in the scope so it won't have to be re-evaluated.
let node = renderComponent.node
}
If we put everything together so far, we will end up with this.
class SomeComponent: GKComponent {
var someOtherComponent: SomeOtherComponent {
guard let someOtherComponent = entity?.component(ofType: SomeOtherComponent.self) else {
fatalError("The entity needs SomeOtherComponent.")
}
return someOtherComponent
}
override func update(deltaTime seconds: TimeInterval) {
let someOtherComponent = self.someOtherComponent
// Do stuff with `someOtherComponent` here...
}
}
While we can partly ease the load by assigning the computed property to a constant within the scope, to minimize repeated evaluation, it still has to take place once on each update when the method is called, which is 60 or 120 times per second and then times the number of component instances in the system.
All in all, going this route provides us with all the convenience and some optimization by using local scope constants. Let's compare it with the second approach, using a lazy stored property.
Lazy Stored Property
As a lazy property is not evaluated until it is first accessed, it allows us to set up the relationship to the other component without having the owning entity and other components available during initialization.
In that regard it is somewhat similar to the computed property. But in contrast to the computed property a lazy property's value is evaluated once and only once. After it has been accessed the first time, the value will remain stored for future access.
As in the case with the computed property, we can type a short version by force unwrapping the values, but let's make it proper right away and use a closure with a guard and a meaningful error message, in the case we make some mistake when setting up the owning entity. We would then end up with something like this.
class SomeComponent: GKComponent {
lazy var someOtherComponent: SomeOtherComponent = {
guard let someOtherComponent = entity?.component(ofType: SomeOtherComponent.self) else {
fatalError("The entity needs a render SomeOtherComponent.")
}
return someOtherComponent
}()
}
Now we can reference someOtherComponent
during the game update loop and other places in the someComponent
instance, without ever triggering additional evaluations of the reference value.
In most cases the referenced component will never change. If an entity has a movement component referencing a render component, the render component will stay the same during the lifecycle of the movement component, so in such a case we can safely assume that the lazy stored property will hold the correct value.
Conclusion
Personally I decided to go with the lazy stored property using a closure in most cases. The only drawback I see with this solution is that I can't define it as a constant property, which would have required me to go down a completely different path passing component references via init()
methods.
Many of my components are assigned to GKComponentSystem
instances which gets called on each frame in my game loop. Evaluating a computed property on each frame for each component adds unnecessary overhead compared to going with the lazy stored property that is just evaluated once.
To sum it up, use lazy stored properties to add convenience to hold references between GKComponent
instances.