Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unify the clone modifier and spawners, and fix races. #387

Merged
merged 3 commits into from
Oct 29, 2024

Conversation

pcwalton
Copy link
Contributor

This large patch essentially makes particle trails and ribbons part of the spawner, which is processed during the init phase, rather than modifiers that execute during the update phase. Along the way, this made it easier to fix race conditions in spawning of trails, because spawning only happens in the init phase while despawning only happens in the update phase. This addresses #376, as well as underflow bugs that could occur in certain circumstances.

In detail, this commit makes the following changes:

  • Every group now has an initializer. An initializer can either be a spawner or a cloner. This allows spawners to spawn into any group, not just the first one.

  • The EffectSpawner component is now EffectInitializers, a component which contains the initializers for every group. Existing code that uses EffectSpawner can migrate by picking the first EffectInitializer from that component.

  • The CloneModifier has been removed. Instead, use a Cloner, which manages the age and lifetime attributes automatically to avoid artifacts. The easiest way to create a cloner is to call with_trails or with_ribbons on your EffectAsset.

  • The RibbonModifier has been removed. Instead, at most one of the groups may be delegated the ribbon group. The easiest way to delegate a ribbon group is to call EffectAsset::with_ribbons. (It may seem like a loss of functionality to only support one ribbon group, but it actually isn't, because there was only one prev and next attribute pair and so multiple ribbons never actually worked before.)

  • The capacity parameter in EffectAsset::new is no longer a vector of capacities. Instead, you supply the capacity of each group as you create it. I figured this was cleaner.

  • Init modifiers can now be specific to a particle group, and they execute for cloned particles as well. There's no need to use update modifiers to set values on cloned particles.

  • Age and lifetime can no longer be set manually for cloned particles. (They can still be set as usual for spawned particles.) This enforces LIFO ordering for ribbons, which is necessary to avoid races.

  • Underflow of particle counts that could occur in certain scenarios involving multiple groups is fixed. The racy hack that "put back" particles that would otherwise underflow the alive count is no longer needed.

  • Particle linked lists no longer race with one another (Artifacts in firework example #376).

Closes #376.

Copy link
Owner

@djeedai djeedai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this! I really like the way things are moving, this is going in the same direction as my upcoming hierarchy change so although this will heavily conflict due to touching the same code (I'll handle it) conceptually the changes align, moving the cloning as an init of sort.

On the code itself, mostly OK overall, but there's a number of small points I either don't understand or could be clarified. I don't think there's any major error though. If you can do one more pass on it that'd be great.

Thanks a lot!

src/lib.rs Outdated Show resolved Hide resolved
src/spawn.rs Outdated Show resolved Hide resolved
src/spawn.rs Outdated
}
}

/// Holds the runtime state for the initializer of each particle group on a
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not "each", isn't it? It's "a single" particle group, no?

src/spawn.rs Outdated Show resolved Hide resolved
src/spawn.rs Show resolved Hide resolved
src/render/mod.rs Outdated Show resolved Hide resolved
src/render/mod.rs Outdated Show resolved Hide resolved
src/render/mod.rs Outdated Show resolved Hide resolved
src/render/mod.rs Show resolved Hide resolved
src/render/mod.rs Show resolved Hide resolved
Copy link
Contributor

@Seldom-SE Seldom-SE left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the rework, I like the API changes! I tried out this PR, and I've noticed some differences that I think are notable:

  • Since trail lifetimes are CpuValues, their lifetimes can't be configured via properties. Not a big issue, to be clear, I'll just make separate effect assets when I need a different lifetime.
  • My effects spawn a single particle in group 0, but now I need at least a group 0 capacity of 2 for the particles to show up at all
  • Now many attributes (including POSITION and HDR_COLOR, but excluding AGE) must be initialized in init instead of just being set in update

I've also still noticed lag and artifacts like in #376, see the end of this video:

2024-10-23.13-47-01.mp4

I didn't read through these changes, just left comments on some things I noticed

src/asset.rs Show resolved Hide resolved
src/asset.rs Show resolved Hide resolved
@pcwalton pcwalton force-pushed the trail-reform branch 2 times, most recently from 899f1ab to 656ee1b Compare October 23, 2024 22:04
@pcwalton
Copy link
Contributor Author

Comments addressed.

@chronicl
Copy link

Great PR!
However, the "worms" example is broken for me. Could you check that out?

This large patch essentially makes particle trails and ribbons part of
the spawner, which is processed during the init phase, rather than
modifiers that execute during the update phase. Along the way, this made
it easier to fix race conditions in spawning of trails, because spawning
only happens in the init phase while despawning only happens in the
update phase. This addresses djeedai#376, as well as underflow bugs that could
occur in certain circumstances.

In detail, this commit makes the following changes:

* Every group now has an *initializer*. An initializer can either be a
  *spawner* or a *cloner*. This allows spawners to spawn into any group,
  not just the first one.

* The `EffectSpawner` component is now `EffectInitializers`, a component
  which contains the initializers for every group. Existing code that
  uses `EffectSpawner` can migrate by picking the first
  `EffectInitializer` from that component.

* The `CloneModifier` has been removed. Instead, use a `Cloner`, which
  manages the `age` and `lifetime` attributes automatically to avoid
  artifacts. The easiest way to create a cloner is to call `with_trails`
  or `with_ribbons` on your `EffectAsset`.

* The `RibbonModifier` has been removed. Instead, at most one of the
  groups may be delegated the ribbon group. The easiest way to delegate
  a ribbon group is to call `EffectAsset::with_ribbons`. (It may seem
  like a loss of functionality to only support one ribbon group, but it
  actually isn't, because there was only one `prev` and `next` attribute
  pair and so multiple ribbons never actually worked before.)

* The `capacity` parameter in `EffectAsset::new` is no longer a vector
  of capacities. Instead, you supply the capacity of each group as you
  create it. I figured this was cleaner.

* Init modifiers can now be specific to a particle group, and they
  execute for cloned particles as well. There's no need to use update
  modifiers to set values on cloned particles.

* Age and lifetime can no longer be set manually for cloned particles.
  (They can still be set as usual for spawned particles.) This enforces
  LIFO ordering for ribbons, which is necessary to avoid races.

* Underflow of particle counts that could occur in certain scenarios
  involving multiple groups is fixed. The racy hack that "put back"
  particles that would otherwise underflow the alive count is no longer
  needed.

* Particle linked lists no longer race with one another (djeedai#376).

Closes djeedai#376.
@pcwalton
Copy link
Contributor Author

Fixed.

Copy link
Owner

@djeedai djeedai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks all good to me, but I'd like for @Seldom-SE to confirm that the lags are gone before merging, because that was the entire point of this change (or part of it at least).

Also @pcwalton can you please consider avoiding force-push in the future, especially on large PRs like this? In general with git this doesn't work well with PRs, but here the GitHub UI is rather bad, and I can only see the difference between your previous and current version at the file level (whether the file was modified or not, as a boolean state), not at the line level (what actually changed). So each time I have to review 3000 lines again to figure out what changed in render/mod.rs for example 😢 At least with incremental commits I can look at just the newer commits. I'll squash on merge anyway (unless there's a strong reason to keep history, but that's very rare). Thanks!

Copy link
Contributor

@Seldom-SE Seldom-SE left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like for @Seldom-SE to confirm that the lags are gone before merging

This PR fixes some long random freezes we were running into, but doesn't fix the constant low FPS at about 750 particles. I'm getting FPS lows in the same ballpark (1-4.5) in a given situation in our game. Whatever's causing that lag is likely out of scope for this PR, and I'm not seeing the ribbon artifacts anymore, so I think this is good to merge.

Thanks!

@djeedai
Copy link
Owner

djeedai commented Oct 26, 2024

The following examples are broken:

  • spawn_on_command
  • activate
  • force_field

Also the ribbon example at start looks like it has 2 particles attached to the same root:

ribbon

* `ribbon` was a bit buggy because we could spawn and clone the
  newly-spawned particle on the first frame. I changed the shader to use
  `max_update` instead.

* `spawn_on_command`, `activate`, and `force_field` were broken because
  we were copying `Spawner`s. I fixed that and removed `#[derive(Copy)]`
  so it doesn't happen again.
@pcwalton
Copy link
Contributor Author

Fixed. See the commit message for more details as to what was going wrong.

src/spawn.rs Outdated Show resolved Hide resolved
src/spawn.rs Outdated Show resolved Hide resolved
src/render/vfx_init.wgsl Outdated Show resolved Hide resolved
@pcwalton
Copy link
Contributor Author

Ok, I fixed the problem by reverting to alive_count and instead sorting the groups so that we evaluate cloners before spawners.

@pcwalton pcwalton requested a review from djeedai October 27, 2024 21:17
@djeedai djeedai merged commit 3e3c814 into djeedai:main Oct 29, 2024
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Artifacts in firework example
4 participants