bevyengine / bevy

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

Add an inverse to `Changed` and `Added` filters #7265

Open nicopap opened 1 year ago

nicopap commented 1 year ago

Allows exclusive access based on Changed or Added component, similar to With and Without.

See https://github.com/bevyengine/bevy/pull/7264#discussion_r1073382489

This is very niche, but might help squeeze out some perfs.

What solution would you like?

Add a NotChanged<T> and NotAdded<T> component that returns all entities that their Changed and Added counterparts do not return.

Am I missing something? It seems possible to discriminate based on those filters. Though it would likely require modifying the AccessSet content.

What alternative(s) have you considered?

I added a changed: Query<(), Changed<Transform>> parameter, and call !changed.contains(&entity).

Additional context

djeedai commented 1 year ago

Should we consider a generic Not<>? I don't know how feasible this is though.

james7132 commented 1 year ago

Should we consider a generic Not<>? I don't know how feasible this is though.

Probably would just be a simple Not<T: ReadOnlyWorldQuery> that just inverts the output on filter_fetch. It probably should be noted that it wouldn't be an archetypal filter and would run against the split between With/Without.

CatThingy commented 1 year ago

I took a stab at Not<T> in #7271, but ran into some issues I could not resolve. matches_component_set has different "negation behaviours" for With(out) and Added/Changed: for With(out), it must be negated; for Added/Changed, it must not be. If individual implementations were implemented for the four "inner" filters, Or then becomes impossible to generically negate as it's possible to mix With(out) and Added/Changed within an Or. One possible solution moving forward is to have an additional trait that defines the negation behaviour.

My implementation also had Not<Added<T>> imply With<T>, which is not a logical negation of Added<T>.

ickk commented 1 year ago

As a user-story, today I tried to do something like the following in order to filter out all the entities that had a component added in the same frame.

Query<&T, Without<Added<T>>>

I was somewhat surprised that this was not supported.


Having set-like operations defined on Query could also be useful to solve similar problems instead with run-time compositions, i.e.

fn system(
  a: Query<Entity>
  b: Query<Added<&T>
) {
  // set difference; C is the set of all entities that satisfy A but 
  // with the entities that satisfy B removed
  let c: Query<Entity> = a - b;
}
maniwani commented 1 year ago

NotChanged<T> and NotAdded<T> return all entities that Changed<T> and Added<T> do not.

Changed<T> and Added<T> have an implicit With<T>. Inverting their output won't change that. They'll return the entities that have a T that wasn't recently changed or added. Their inverses should not include entities matched by Without<T>.

My implementation also had Not<Added<T>> imply With<T>, which is not a logical negation of Added<T>.

This would be much clearer if Added<T> and is_added were named New<T> and is_new (which is what it means). IMO if you ask for "entities whose T is not new", it doesn't make any sense to include entities that don't even have T.

It's not the complementary set of entities, but the implied With<T> is intuitive.