Expand description
bevy_query_observer provides observers that trigger
when an entity starts or stops matching a query. Query
observers also trigger when a matching entity’s data
changes according to lifecycle events.
#[derive(Component)]
struct Red;
#[derive(Component)]
struct Blue;
fn red_and_blue(
data: Start<Entity, (With<Red>, With<Blue>)>,
mut commands: Commands,
) {
warn!("{:?} is both red and blue", *data);
}The design is based on Lifecycle event observers for queries.
§Getting started
Since bevy_query_observer is implemented entirely in terms of lifecycle events,
all you need to get started is to add the dependency.
[dependencies]
bevy = "0.17.3"
bevy_query_observer = "0.2.0"Query observers are added like normal observers.
fn plugin(app: &mut App) {
app.add_start_observer(red_and_blue);
}Like Observer, you can spawn QueryObserver directly for greater control
over what it watches and where it’s spawned.
fn target_entity(mut commands: Commands) {
let target = commands.spawn_empty().id();
let query_observer = QueryObserver::start(red_and_blue).with_entity(target);
commands.spawn_query_observer(query_observer);
}§Maintaining non-trivial invariants
Query observers make it easier to maintain non-trivial invariants. This example, taken from the initial design discussion, illustrates how you can maintain an entity index with excluding marker components.
#[derive(Component, Eq, PartialEq, Hash, Copy, Clone)]
#[component(immutable)]
struct Value(usize);
#[derive(Component)]
struct ExcludeFromIndex;
#[derive(Resource)]
struct Index(HashMap<Value, EntityHashSet>);
fn add_to_index(
event: Start<(Entity, &Value), Without<ExcludeFromIndex>>,
mut index: ResMut<Index>,
) {
let (entity, value) = event.into_inner();
index.0.entry(*value).or_default().insert(entity);
}
fn remove_from_index(
event: Stop<(Entity, &Value), Without<ExcludeFromIndex>>,
mut index: ResMut<Index>,
) {
let (entity, value) = event.into_inner();
index.0.entry(*value).or_default().remove(&entity);
}Start and Stop handle default query filters automatically,
meaning the above implicitly accounts for Disabled and any
other default filter.
Expressing the above in terms of normal observers requires at least four hand-written observers. Each disabling component requires two additional observers.
§Limitations
Because bevy_query_observer is implemented in user space and bevy_ecs isn’t
yet expressive enough, the crate comes with a few limitations.
-
Certain observers that require evaluation before an archetype change are faked.
For example,
Stop<(), Without<MyFilter>>must run just beforeMyFilteris added to fulfill the semantics ofStop. However, Bevy’s lifecycle events can’t express this, so the query observer ignoresWithout<MyFilter>in this scenario when fetching the data. In practice this is usually fine, but it may deny simultaneous mutable access or break subtle invariants. -
Only a subset of Bevy’s built-in
QueryDataandQueryFiltertypes are supported.While
bevy_query_observercould spawn observers in terms of aQuery’s access, Bevy’s access types are private. As a result,StartandStopdepend onQueryObserverAccess, meaning any custom query data or filters require an implementation. -
Performance isn’t great
The overhead of evaluating a query observer is around three times slower than a normal observer. Spawning query observers is nearly an order of magnitude slower.
-
Query observers frequently early return
Since each unique component in
StartandStopneeds a dedicated observer, lifecycle events will frequently trigger query observer evaluations that do nothing. Default filters are included, so inserting or removing a disabling component may trigger many short-circuiting observers.In principle, first party query observers could reduce unnecessary evaluations using archetype information.
Re-exports§
pub use observer::InsertQueryObserver;pub use observer::QueryObserver;pub use observer::SpawnQueryObserver;pub use observer::start::AddStartObserver;pub use observer::start::Start;pub use observer::stop::AddStopObserver;pub use observer::stop::Stop;
Modules§
- observer
- Query observers that match when an entity starts and stops matching a query.