| Crates.io | bevy_query_observer |
| lib.rs | bevy_query_observer |
| version | 0.2.1 |
| created_at | 2025-12-14 01:36:38.813846+00 |
| updated_at | 2026-01-03 10:25:32.879959+00 |
| description | More sophisticated observers for Bevy |
| homepage | |
| repository | https://github.com/corvusprudens/bevy_query_observer |
| max_upload_size | |
| id | 1983626 |
| size | 141,996 |
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.
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);
}
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.
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 before MyFilter is added
to fulfill the semantics of Stop. However, Bevy's lifecycle events can't express
this, so the query observer ignores Without<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 QueryData and QueryFilter types are supported.
While bevy_query_observer could spawn observers in terms of a Query's access,
Bevy's access types are private. As a result, Start and Stop depend
on QueryObserverAccess, 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 greater than a normal observer. Spawning query observers is nearly an order of magnitude slower.
Query observers frequently early return
Since each unique component in Start and Stop needs 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.
bevy |
bevy_query_observer |
|---|---|
| 0.17 | 0.1, 0.2 |