The game can crash on pooled mass agents when any command that uses a FGlobalComponentReregisterContext comes along to re-register components like ToggleShadowIndexBuffers. This is because UMassAgentComponent::OnRegister always calls a RegisterWithAgentSubsystem. However the OnUnregister call to UnregisterWithAgentSubsystem will free it to the pool and when it gets selected from the pool by another spawn request, it will call RegisterWithAgentSubsystem again. This leads to a assert crash in the spawn request because the state is already EntityCreated when it expects it not to be. This is because it processes the earlier RegisterWithAgentSubsystem from OnRegister for this instance before being pulled from the pool. To support pooled components, you likely only need to call RegisterWithAgentSubsystem once ever for a UMassAgentComponent.
This is still the case at head of engine it looks like.
Steps to Reproduce
Spawn pool-supported mass entities. Run ToggleShadowIndexBuffers or any other command that uses a FGlobalComponentReregisterContext to unregister-reregister all components. Note potential to crash.
Thank you for letting us know about this! We added the poolable interface, but never got around to using it in the City Sample project. As such, there are areas that never got addressed.
Do you see this crash when re-using an Actor from the pool? Or is this happening only with commands causing components to re-register? Can you share the callstack you are encountering as well? Have you attempted any solution such as logging an error for attempting to re-register an already registered component with the subsystem? Does that have any secondary effects if so?
-James
This will only happen when the component re-registers. Re-registering itself is incompatible with the pool due to the double firing of re-registering as described above. We fixed this by only calling RegisterWithAgentSubsystem once statically for the lifetime of the component and letting the unregister clean it up and free it back to the pool to be re-selected/registered by the system.
Apologies for the delay. Do you happen to have the callstack from when this occurs? Are you using the IMassPoolableActorInterface with this as well or do you have your own pooling system? Do you have these agents originally placed in the level or spawned by Mass at runtime/startup?
Sorry, missed this one. I’ll have to dig back for the callstack. It is using the IMassPoolableActorInterface yeah. Our renderers hit it when using ToggleShadowIndexBuffers, which re-registers all components in the scene. The agents are spawned via mass spawner. They are effectively the vehicles from the matrix demo I believe.
Stepping through the code, the unregister would free it to the pool, then the re-register would put the entity in a certain instantiated state even though it wasn’t yet claimed from the pool. When the system then tried to pull it from the pool, it would assert/fatal that the entity was already in some mass state that it didn’t expect (it assumed it would pull from the pool before the registration on this component happened). I’ll have to go back for the details though, it was a while ago and now have a full plate of other stuff at the moment.
I am diving back into this after a wild month of conferences and vacation. It seems like the issue arises from the unregistering of the agent component since the state of the puppet is mentioned. We do not expect to have the agent unregistered without changing the state of the component, and I do not believe we have encountered this issue internally.
In your change for only registering once during the lifetime, how are you handling updating the entity handle when returning an agent to the pool? Are you clearing the current entity handle for the component when returning it to the pool? Have you encountered any issue with agent components being associated with incorrect entities during simulation?
Correct, this happens when an agent component is unregistered and then specifically force re-registered. This can happen for anything using a FGlobalComponentReregisterContext which forces all components in the world to unregister then re-register. The render debug command above and a couple other commands do this, but I’m not sure if this can happen naturally in a shipping config. I can’t imagine so. So far, just impacts render debug tools.
I believe RegisterWithAgentSubsystem happens when the system pulls the actor from the pool in UMassActorSpawnerSubsystem::SpawnOrRetrieveFromPool. So we just avoid doing the RegisterWithAgentSubsystem from the component OnRegister in that case and let the UMassActorSpawnerSubsystem handle it. Functionally, all the same code runs I believe. Forcing the unregister and reregister just caused a double firing of RegisterWithAgentSubsystem which caused the issue. (The unregister freed it back to the pool, then it was naturally selected to spawn on the next pump being pulled from the pool can recall RegisterWithAgentSubsystem, which the FGlobalComponentReregisterContext also called when forcing a re-register on it, so it double pumped.)
We haven’t had any issues with the agent components being mismatched with this change.
I am not getting this crash in our latest on UE5 Main. I believe a change was made a couple months back about registering agent component’s in inactive worlds. The CL # is 40244604 in UE5 Main. It is the only change I see recently to the component or subsystem that would appear to be affecting this. I had been able to repro this in our CitySample in 5.5, so I know it did exist.
I believe this is the fix. Would you be able to cherrypick it to your version of the engine? If you are still seeing it with the change, I will check if others on the team know of any other CLs that may have addressed this issue.