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 X2UnifiedProjectile
s.
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
.