ProjectilePerformanceDrain

Tracking Issue: #720

The game uses a global pool WorldInfo.MyEmitterPool to store Particle System Components (PSCs), which helps optimize performance. When a PSC is no longer needed, it is returned to the pool so that it can be reused again later.

To return a PSC back to the pool, the EmitterPool::OnParticleSystemFinished() function must be called, which usually happens seamlessly - the pool sets the ParticleSystemComponent::OnSystemFinished delegate to the aforementioned function when the PSC is initially borrowed from the pool (via EmitterPool::SpawnEmitter).

However, X2UnifiedProjectile overwrites the OnSystemFinished delegate with its own one after borrowing the PSC from the pool, which means the EmitterPool::OnParticleSystemFinished() is never called for these PSCs.

As such, the pool is never made aware that those PSCs are ready for reuse and when a new PCS is requested, the pool has no choice but to simply create a new one, resulting in the pool getting bloated with all the PSCs ever spawned by X2UnifiedProjectiles.

This bloat leads to two observable effects: endlessly increasing RAM usage and a significant performance degradation. The more projectiles are fired over the course of a mission, the worse these effects become. Frequent use of the Suppression ability or Idle Suppression mod kick the problem into overdrive.

To address this bug, we manually call WorldInfo.MyEmitterPool.OnParticleSystemFinished() on PSCs in X2UnifiedProjectile when the projectile is done with them.

Compability note: some particle systems cannot be returned directly after the projectile is done with the particle system - doing so destoys trails/smoke/effects that dissipate over time (e.g. rocket trails). To address this issue, we delay the return to pool (and the forced destruction of any remaining particles) by 1 minute. If this is not enough for your particle system, you can override the behaviour in XComGame.ini. Here's an example/template:

[XComGame.CHHelpers]
+ProjectileParticleSystemExpirationOverrides=(ParticleSystemPathName="SomePackage.P_SomeParticleSystem", ExpiryTime=120)
Property Value
ParticleSystemPathName The full path to your ParticleSystem (what you configure with emitters in the editor)
ExpiryTime Time in seconds to pass between the projectile being done with the system and its return to the pool

Keep in mind that the above time (either the 1 min default or the override) is the max allowed delay - if a particle system finishes (and reports so by calling PSC.OnSystemFinished), the delay will be aborted and the PSC will be instantly returned to the pool. As such, there is no need/reason to manually set lower expiration times than the default - just make sure that your particle system is properly configured in the editor.

Mods that create Particle System Components using the Emitter Pool must carefully handle them the same way: if the PSC's OnSystemFinished delegate is replaced, then EmitterPool::OnParticleSystemFinished() must be called for this PSC manually when the PSC is no longer needed, otherwise the same "memory leak" will occur. This applies to all PSCs using the Emitter Pool, not just those in X2UnifiedProjectile.

Source code references