diff --git a/crates/effects/src/sim3.rs b/crates/effects/src/sim3.rs index 2cdc3fed2..40b69a4c3 100644 --- a/crates/effects/src/sim3.rs +++ b/crates/effects/src/sim3.rs @@ -499,18 +499,22 @@ impl Pg { // Particle Exploder: affected particles larger than the new radius burst into smaller // ones that fly apart (each new particle bursts no further). if self.pex_radius > 0.0 { - let mut out = Vec::with_capacity(st.parts.len()); - for q in std::mem::take(&mut st.parts) { + let total = st.parts.len(); + let mut out = Vec::with_capacity(total); + for (i, q) in std::mem::take(&mut st.parts).into_iter().enumerate() { + let n = ((q.r / self.pex_radius).powi(2).round() as u32).clamp(2, 16); + // The particles after this one are all kept, so a burst fits only if they and its + // children still fit the budget. + let unprocessed = total - i - 1; if q.src == SRC_PARTICLE_EXPLODER || q.ch != 0 || q.r <= self.pex_radius * 1.01 || self.pex_affects.weight(&q) <= 0.5 - || out.len() > MAX_PG_PARTICLES + || out.len() + unprocessed + n as usize > MAX_PG_PARTICLES { out.push(q); continue; } - let n = ((q.r / self.pex_radius).powi(2).round() as u32).clamp(2, 16); for k in 0..n { let id = st.next_id; st.next_id = st.next_id.wrapping_add(1); diff --git a/crates/effects/src/sim3/tests.rs b/crates/effects/src/sim3/tests.rs index fb7cb35be..331e94e9d 100644 --- a/crates/effects/src/sim3/tests.rs +++ b/crates/effects/src/sim3/tests.rs @@ -281,6 +281,21 @@ fn particle_exploder_bursts_big_particles() { assert!(s.parts.is_empty()); } +/// Bursts count the particles still waiting to be processed, so a grid near the particle budget +/// cannot end the frame over it. +#[test] +fn particle_exploder_stays_within_the_particle_budget() { + let st = Pg { pex_radius: 2.0, pex_dispersion: 30.0, ..bare(1) }; + let mut s = PgState::default(); + for _ in 0..MAX_PG_PARTICLES - 20 { + s.parts.push(PgParticle::new([50.0, 50.0], [0.0; 2], [1.0; 4], 6.0, 0, SRC_GRID)); + } + st.step(&mut s); + assert!(s.parts.len() <= MAX_PG_PARTICLES, "{}", s.parts.len()); + // Room was left for the first bursts: 20 spare slots hold two bursts of 9 - 1 new each. + assert!(s.parts.len() > MAX_PG_PARTICLES - 20); +} + #[test] fn text_particles_draw_characters() { let grid = |text: &str| {