Some Delayed Particle Emitters Don't Function with High Delta Times

During replay playback, if we time jump to a point in time where a particle system should already be emitting its particles, some particles do not spawn. When tracing the offending particle emitters, they all shared the following characteristics:

  • They all had a delay
  • They had an infinite lifetime
  • The max undilated frame time set in WorldSettings exceeded the particle emitter’s duration + delay

What essentially happens is that when time jumping, during a DemoNetDriver fast-forward, our game was taking near the max undilated frame time to recreate all the actors in the game, which would pass a relatively large delta time value on the first tick following that recreation. This was exasperated if we time dilated right after the time jump (for instance, a time dilation of 8 would 8x the max undilated frame time). However, the logic in the ParticleEmitterInstances class to spawn a particle is below:

if (!bHaltSpawning && !bHaltSpawningExternal && !bSuppressSpawning && (EmitterTime >= 0.0f)) { // If emitter is not done - spawn at current rate. // If EmitterLoops is 0, then we loop forever, so always spawn. if ((InCurrentLODLevel->RequiredModule->EmitterLoops == 0) || (LoopCount < InCurrentLODLevel->RequiredModule->EmitterLoops) || (SecondsSinceCreation < (EmitterDuration * InCurrentLODLevel->RequiredModule->EmitterLoops)) || bFirstTime) { bFirstTime = false; SpawnFraction = Spawn(DeltaTime); } }What happens is:

  1. The first tick of that emitter had a large DeltaTime (let’s say 3 seconds if we 8x time dilate after we come out of a time jump)
  2. The emitter calculates how long it has been since the emitter has been created and if it has surpassed its expected duration
  3. In instances where the emitter duration is less than the delta time, the emitter think it already looped the particle effects and spawned it, resetting the delay period
  4. When we then check to see if we can spawn the particle, we don’t pass the first condition (EmitterTime > 0) because the emitter thinks we still need to wait for the delay
  5. If we do pass the first condition (because some DeltaTimes loop and do not completely refresh the delay period), we still don’t spawn the particle because SecondsSinceCreation !< EmitterDuration (SecondsSinceCreation += DeltaTime in its tick calculation)
  6. Before we even get to do the second tick, the particle system thinks the emitter has completed because no particles have spawned and the SecondsSinceCreation > EmitterDuration (in the HasCompleted() function).
  7. The particle system retires that emitter, effectively never spawning it in the first place, which is what we see in the replay playback.

Please let me know if I’m interpreting this logic incorrectly, but right now, it doesn’t seem like the ParticleEmitterInstance class accounts for large delta times in its spawning logic and skips spawning the particle altogether if the delta time passed in is larger than the duration + delay.

Steps to Reproduce
You can repro this bug outside of replay playback and through normal gameplay.

  1. Create a particle system with the following characteristics
    1. Add an emitter instance (Sprite Template / graphic does not matter, can be a solid red line for example)
    2. Set the particle lifetime of that emitter instance to 0 (infinite lifetime)
    3. Set the emitter duration to an arbitrary small value like .05
    4. Set the emitter delay to an arbitrary small value like .05
  2. Tie spawning / de-spawning that particle system to some input so you can test spawning it at will
  3. Launch a PIE session
  4. Cap FPS of your session to some a small FPS value (“t.MaxFPS 5” for example)
  5. Try spawning the particle system and observe that particle does not emit

Hi Nizar,

Thanks for the report, I have entered a Jira with your repro steps and will pass on the public link when it’s available.

However I don’t expect us to action on it any time soon as Cascade is in maintenance mode and has no active development effort.

If you do make a fix locally and create a pull request we can look at integrating it.

Thanks,

Stu