🏊Actor Pooling
Last updated
Last updated
Actor pooling is a concept which allows us to swap in and out actors at runtime without creating and destroying them, by caching actors in a list called a pool. When a new actor of a particular type is required, if one exists in the pool we can grab that actor and use it instead of creating a new one, eliminating much of the overhead involved in creating an actor at the expense of using up some memory.
MSquared's actor pooling system is almost entirely standard, and this article will concentrate less on technical details and more on common usages together with the kinds of situations which can catch people out!
The main use of the actor pooling system is in our LOD system for characters. With up to 20k players in a level where only the nearest ~35 are displayed in full fidelity (the rest being represented with Crowd Rendering), we need a way of efficiently assigning and removing render target actors from player MAs as they run around.
Actor pooling fulfills that brief by returning a render target to the pool when a player moves into the crowd, and grabbing a render target from the pool when they move out of the crowd.
NOTE: This only applies to other clients' characters. The authoritative client's render target actor is not pooled, and cannot go into the crowd.
The main downside of actor pooling is that if e.g. a render target actor is moved to the pool, it retains any state on it that occurred during natural gameplay involving its previous owner. This can lead to visual and logical inconsistencies, where the newly-obtained render target is still doing things based on its state before it was returned to the pool.
Therefore, most of the work in actor pooling is in making sure that any such state is reset before the actor is returned to the pool (or after it’s taken from the pool, depending on what’s appropriate).
Typically if a visual or logical bug occurs only when swapping roles or when large amounts of people are running around, it’s likely to do with state not being reset during actor pooling.
The main implication of the above is that any component on a player render target should be checked for state which may cause issues during pooling when work is done on it.
We also have to be careful to gracefully handle any components which are dynamically added or removed as when pooling the actor they may need to be removed or added respectively.
The lifecycle of pooled actors, and the relevant transition events is as follows:
To enable actor pooling on an actor or component, implement the IMorpheusPooledActor
interface on it.
For the interface on the component to work, it will need to be on an actor that also implements the interface.
To enable actor pooling on a render target, implement the IMorpheusPooledRenderTarget
interface on it.
This is a more specialised version of IMorpheusPooledActor
, that adds the OnEndPlayToPoolWithOwner
event.
And that’s it! Everything else is handled for you. The interfaces provide three functions, and a fourth for the render target one specifically:
OnBeginPlayFromPool()
can be implemented to perform logic when an actor is taken from the pool. Logic such as making the actor visible again, or setting up the state based on the latest MorpheusActor associated with the actor is done here.
OnEndPlayToPool()
can be implemented to perform logic when an actor is returned to the pool. Logic such as hiding the actor, or cleaning up any added visuals is done here.
IsPoolingEnabled()
can be implemented to provide an additional conditional check for pooling. If false, the actor won't be pooled.
NOTE: This interface function has no effect when implemented on a component; it will perform its begin/end logic regardless, dependent entirely on whether IsPoolingEnabled()
returns true
for its owning actor.
OnEndPlayToPoolWithOwner()
on the render target version only is a version of OnEndPlayToPool()
which provides a MorpheusActor
owner as an argument. (This is because the association between the render target actor and its old MorpheusActor is cleaned up before it is returned to the pool)
Naturally, each usage of actor pooling will require a different testing procedure due to it being specific to particular actors. However, a general solution for testing render targets or components on those render targets is to simulate targets moving into and out of LoD0.
We can do this by overriding the PlayerClient.Rendering.NumInLoD0
live config value in the editor (or in live config, if testing a deployment) to e.g. 1 and then having 3 clients present. This means that one client can observe as the other two run in and out of LoD0.
The following sections refer to functionality added in release v29
If a render target actor has been returned to the pool, but hasn't unbound any delegates that it bound to on its previous Morpheus Actor (or any components on that Morpheus Actor), you could see strange bugs present themselves, since the pooled actor would still be listening to that old Morpheus Actor's events. If it then re-enters play out of the pool, being assigned a new Morpheus Actor, it could appear to be mostly working, but be listening to events on the old owner, instead of (or as well as) the new one.
We have added some additional warnings that will be printed if this is the case, to help track issues like this down.
We also have automatic logic, gated behind the Pooling.RemoveDelegatesBoundToOldMorpheusActor
live config flag, to unbind such delegates. This should at least minimise the damage of not fully implementing actor pooling (the delegates will still need to be bound when the actor is returned to the pool though!)
If you have verbose logging enabled for LogMorpheusActorPooling
category, it will print additional details on the actor returning to the pool, comparing it to the base class's default values.
These logs will print any differences from a reference actor, which hasn't yet begun play. Some of these may not be issues, but worth considering any differences that you don't expect, relevant to components/properties you added!
This can be set by doing e.g.:
Using the Log LogMorpheusActorPooling Verbose
command
Adding the LogMorpheusActorPooling=Verbose
line to the [Core.Log]
section of your Engine .ini
file.
Characters changing LOD levels can be easily triggered via the Morpheus Inspector - see