๐ŸŠActor Pooling

Intro

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!

Pooled Render Target Actors

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 The Animated Crowd), 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.

When Actor Pooling Goes Wrong

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.

Technical Usage

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)

How to Test

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.

Last updated