bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
36.29k stars 3.58k forks source link

Observer ordering/scheduling #14890

Open Azorlogh opened 2 months ago

Azorlogh commented 2 months ago

What problem does this solve or what need does it fill?

Sometimes an observer can rely on things added by other observers. But it is currently not possible to explicitly order the observers.

What solution would you like?

I would like the ability to mark an observer as having to run after or before another one.

Azorlogh commented 2 months ago

I have two ideas on what the API side can look like:

alice-i-cecile commented 2 months ago

This is needed in #12365 as well.

alice-i-cecile commented 2 months ago

We definitely want this, just figuring out the architecture and API here is tricky. I'd really like to not be blocked on systems-as-entities and a relationship-powered schedule graph, so a decent temporary solution would be nice here.

bushrat011899 commented 4 days ago

I experimented with this idea a little today and there's definitely some non-trivial design constraints we need to consider.

Initially I thought the most obvious answer would be to store observers in a Schedule (rather than the current CachedObservers struct) per event-type E. My rationale was "We want system-like ordering, so why not use the existing solution to that problem?". Since observers are just systems anyway, this makes sense. However, there are some major issues with this approach:

  1. Events propagate, so observers must run one-by-one for a particular event. This means the ability for the schedule to run observers in parallel doesn't matter.
  2. Some observers only watch particular entities or particular components. This isn't unworkable, since we could encode this into run-conditions in a Schedule, but it may be less performant than the current solution (EntityHashMap for observer systems).
  3. The Schedule is a large struct (1kB). Having 1 schedule per event type is just too much overhead.

Unfortunately, I don't have any affirmative ideas on how we should pursue this feature, just the above findings on a way we probably shouldn't do it.

It's a shame events need to propagate (it's non negotiable, too important for UI), because the other two concerns might be counteracted by the new-found ability to run observers in parallel, rather than one-by-one in a single-threaded for-loop (as they currently are). Might be worth changing how Trigger works to make stopping propagation something that's requested separately, and thus observable in the type system. I imagine no matter what we do here, there's a performance win that could come from knowing which observers could be run in parallel, and doing so.

alice-i-cecile commented 4 days ago

I imagine no matter what we do here, there's a performance win that could come from knowing which observers could be run in parallel, and doing so.

Amdahl's Law is gonna bite you there :) Observers almost always do tiny bits of work: it's unlikely that parallelizing them is worth the overhead.