Skip to content

Commit 91c48d1

Browse files
Performance tweaks
1 parent b3009a5 commit 91c48d1

File tree

12 files changed

+410
-95
lines changed

12 files changed

+410
-95
lines changed

crates/bevy_ecs/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ rustc-hash = "1.1"
2929
downcast-rs = "1.2"
3030
serde = "1"
3131
thiserror = "1.0"
32+
smallvec = "1.6"
3233

3334
[dev-dependencies]
3435
rand = "0.8"

crates/bevy_ecs/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub mod prelude {
5353
Commands, Deferred, In, IntoSystem, Local, NonSend, NonSendMut, ParallelCommands,
5454
ParamSet, Query, ReadOnlySystem, Res, ResMut, Resource, System, SystemParamFunction,
5555
},
56+
term_query::{TermQuery, TermQueryState},
5657
world::{EntityMut, EntityRef, EntityWorldMut, FromWorld, World},
5758
};
5859
}

crates/bevy_ecs/src/term_query/builder.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,18 @@ use crate::{
55
prelude::{Component, With, Without, World},
66
};
77

8-
use super::{ComponentTerm, QueryTermGroup, Term, TermQueryState};
8+
use super::{ComponentTerm, QueryTermGroup, Term, TermQueryState, TermVec};
99

1010
pub struct QueryBuilder<'w, Q: QueryTermGroup = ()> {
11-
terms: Vec<Term>,
11+
terms: TermVec<Term>,
1212
current_term: usize,
1313
world: &'w mut World,
1414
_marker: PhantomData<Q>,
1515
}
1616

1717
impl<'w, Q: QueryTermGroup> QueryBuilder<'w, Q> {
1818
pub fn new(world: &'w mut World) -> Self {
19-
let mut terms = Vec::new();
19+
let mut terms = TermVec::new();
2020
Q::init_terms(world, &mut terms);
2121
Self {
2222
current_term: terms.len(),

crates/bevy_ecs/src/term_query/iter.rs

Lines changed: 102 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ use std::{marker::PhantomData, slice};
33
use crate::{
44
archetype::{ArchetypeEntity, ArchetypeId, Archetypes},
55
component::Tick,
6+
entity::Entity,
67
query::DebugCheckedUnwrap,
7-
storage::Tables,
8+
storage::{TableId, TableRow, Tables},
89
world::unsafe_world_cell::UnsafeWorldCell,
910
};
1011

11-
use super::{Fetchable, FetchedTerm, QueryTermGroup, TermQueryState, TermState};
12+
use super::{Fetchable, FetchedTerm, QueryTermGroup, TermQueryState, TermState, TermVec};
1213

1314
pub struct TermQueryCursor<'w, 's> {
15+
table_id_iter: slice::Iter<'s, TableId>,
1416
archetype_id_iter: slice::Iter<'s, ArchetypeId>,
17+
table_entities: &'w [Entity],
1518
archetype_entities: &'w [ArchetypeEntity],
16-
term_state: Vec<TermState<'w>>,
19+
term_state: TermVec<TermState<'w>>,
1720
current_len: usize,
1821
current_row: usize,
22+
dense: bool,
1923
}
2024

2125
impl<'w, 's> TermQueryCursor<'w, 's> {
26+
#[inline]
2227
unsafe fn new<Q: QueryTermGroup>(
2328
world: UnsafeWorldCell<'w>,
2429
query_state: &'s TermQueryState<Q>,
@@ -27,63 +32,116 @@ impl<'w, 's> TermQueryCursor<'w, 's> {
2732
) -> Self {
2833
let term_state = query_state.init_term_state(world, last_run, this_run);
2934
Self {
35+
table_id_iter: query_state.matched_table_ids.iter(),
3036
archetype_id_iter: query_state.matched_archetype_ids.iter(),
37+
table_entities: &[],
3138
archetype_entities: &[],
39+
dense: term_state.iter().all(|t| t.dense()),
3240
term_state,
3341
current_len: 0,
3442
current_row: 0,
3543
}
3644
}
3745

46+
#[inline(always)]
3847
unsafe fn next<Q: QueryTermGroup>(
3948
&mut self,
4049
tables: &'w Tables,
4150
archetypes: &'w Archetypes,
4251
query_state: &'s TermQueryState<Q>,
43-
) -> Option<Vec<FetchedTerm<'w>>> {
44-
loop {
45-
if self.current_row == self.current_len {
46-
let archetype_id = self.archetype_id_iter.next()?;
47-
let archetype = archetypes.get(*archetype_id).debug_checked_unwrap();
48-
// SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for,
49-
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
50-
let table = tables.get(archetype.table_id()).debug_checked_unwrap();
51-
query_state
52+
) -> Option<TermVec<FetchedTerm<'w>>> {
53+
if self.dense {
54+
loop {
55+
// we are on the beginning of the query, or finished processing a table, so skip to the next
56+
if self.current_row == self.current_len {
57+
let table_id = self.table_id_iter.next()?;
58+
let table = tables.get(*table_id).debug_checked_unwrap();
59+
// SAFETY: `table` is from the world that `fetch/filter` were created for,
60+
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
61+
query_state
62+
.terms
63+
.iter()
64+
.zip(self.term_state.iter_mut())
65+
.for_each(|(term, state)| term.set_table(state, table));
66+
self.table_entities = table.entities();
67+
self.current_len = table.entity_count();
68+
self.current_row = 0;
69+
continue;
70+
}
71+
72+
// SAFETY: set_table was called prior.
73+
// `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed.
74+
let entity = *self.table_entities.get_unchecked(self.current_row);
75+
let row = TableRow::new(self.current_row);
76+
77+
// SAFETY:
78+
// - set_table was called prior.
79+
// - `current_row` must be a table row in range of the current table,
80+
// because if it was not, then the if above would have been executed.
81+
// - fetch is only called once for each `entity`.
82+
self.current_row += 1;
83+
84+
if query_state
5285
.terms
5386
.iter()
5487
.zip(self.term_state.iter_mut())
55-
.for_each(|(term, state)| term.set_table(state, table));
56-
self.archetype_entities = archetype.entities();
57-
self.current_len = archetype.len();
58-
self.current_row = 0;
59-
continue;
88+
.all(|(term, state)| term.filter_fetch(state, entity, row))
89+
{
90+
return Some(
91+
query_state
92+
.terms
93+
.iter()
94+
.zip(self.term_state.iter_mut())
95+
.map(|(term, state)| term.fetch(state, entity, row))
96+
.collect(),
97+
);
98+
}
6099
}
61-
62-
// SAFETY:
63-
// - set_archetype was called prior.
64-
// - `current_row` must be an archetype index row in range of the current archetype,
65-
// because if it was not, then the if above would have been executed.
66-
// - fetch is only called once for each `archetype_entity`.
67-
let archetype_entity = self.archetype_entities.get_unchecked(self.current_row);
68-
self.current_row += 1;
69-
70-
let entity = archetype_entity.entity();
71-
let row = archetype_entity.table_row();
72-
// Apply filters
73-
if query_state
74-
.terms
75-
.iter()
76-
.zip(self.term_state.iter_mut())
77-
.all(|(term, state)| term.filter_fetch(state, entity, row))
78-
{
79-
return Some(
100+
} else {
101+
loop {
102+
if self.current_row == self.current_len {
103+
let archetype_id = self.archetype_id_iter.next()?;
104+
let archetype = archetypes.get(*archetype_id).debug_checked_unwrap();
105+
// SAFETY: `archetype` and `tables` are from the world that `fetch/filter` were created for,
106+
// `fetch_state`/`filter_state` are the states that `fetch/filter` were initialized with
107+
let table = tables.get(archetype.table_id()).debug_checked_unwrap();
80108
query_state
81109
.terms
82110
.iter()
83111
.zip(self.term_state.iter_mut())
84-
.map(|(term, state)| term.fetch(state, entity, row))
85-
.collect(),
86-
);
112+
.for_each(|(term, state)| term.set_table(state, table));
113+
self.archetype_entities = archetype.entities();
114+
self.current_len = archetype.len();
115+
self.current_row = 0;
116+
continue;
117+
}
118+
119+
// SAFETY:
120+
// - set_archetype was called prior.
121+
// - `current_row` must be an archetype index row in range of the current archetype,
122+
// because if it was not, then the if above would have been executed.
123+
// - fetch is only called once for each `archetype_entity`.
124+
let archetype_entity = self.archetype_entities.get_unchecked(self.current_row);
125+
self.current_row += 1;
126+
127+
let entity = archetype_entity.entity();
128+
let row = archetype_entity.table_row();
129+
// Apply filters
130+
if query_state
131+
.terms
132+
.iter()
133+
.zip(self.term_state.iter_mut())
134+
.all(|(term, state)| term.filter_fetch(state, entity, row))
135+
{
136+
return Some(
137+
query_state
138+
.terms
139+
.iter()
140+
.zip(self.term_state.iter_mut())
141+
.map(|(term, state)| term.fetch(state, entity, row))
142+
.collect(),
143+
);
144+
}
87145
}
88146
}
89147
}
@@ -97,6 +155,7 @@ pub struct TermQueryIterUntyped<'w, 's> {
97155
}
98156

99157
impl<'w, 's> TermQueryIterUntyped<'w, 's> {
158+
#[inline]
100159
pub unsafe fn new<Q: QueryTermGroup>(
101160
world: UnsafeWorldCell<'w>,
102161
query_state: &'s TermQueryState<Q>,
@@ -113,8 +172,9 @@ impl<'w, 's> TermQueryIterUntyped<'w, 's> {
113172
}
114173

115174
impl<'w, 's> Iterator for TermQueryIterUntyped<'w, 's> {
116-
type Item = Vec<FetchedTerm<'w>>;
175+
type Item = TermVec<FetchedTerm<'w>>;
117176

177+
#[inline(always)]
118178
fn next(&mut self) -> Option<Self::Item> {
119179
unsafe {
120180
self.cursor
@@ -129,6 +189,7 @@ pub struct TermQueryIter<'w, 's, Q: QueryTermGroup> {
129189
}
130190

131191
impl<'w, 's, Q: QueryTermGroup> TermQueryIter<'w, 's, Q> {
192+
#[inline(always)]
132193
pub unsafe fn new(
133194
world: UnsafeWorldCell<'w>,
134195
query_state: &'s TermQueryState<Q>,
@@ -145,6 +206,7 @@ impl<'w, 's, Q: QueryTermGroup> TermQueryIter<'w, 's, Q> {
145206
impl<'w, 's, Q: QueryTermGroup> Iterator for TermQueryIter<'w, 's, Q> {
146207
type Item = Q::Item<'w>;
147208

209+
#[inline(always)]
148210
fn next(&mut self) -> Option<Self::Item> {
149211
unsafe {
150212
self.inner

crates/bevy_ecs/src/term_query/mod.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,4 +349,40 @@ mod tests {
349349
assert_eq!(Some(&B(0)), entity.get::<B>());
350350
assert_eq!(None, entity.get::<C>());
351351
}
352+
353+
#[derive(Component)]
354+
#[component(storage = "SparseSet")]
355+
struct S(usize);
356+
357+
#[test]
358+
fn term_query_sparse_set() {
359+
let mut world = World::new();
360+
let entity_a = world.spawn((A(0), S(1))).id();
361+
362+
let mut query = world.term_query::<(Entity, &A, &S)>();
363+
364+
let (e, a, s) = query.single(&world);
365+
assert_eq!(entity_a, e);
366+
assert_eq!(0, a.0);
367+
assert_eq!(1, s.0);
368+
}
369+
370+
#[test]
371+
fn term_query_iteration() {
372+
let mut world = World::new();
373+
let entity = world.spawn((A(1), B(0), C(0))).id();
374+
world.spawn_batch((1..1000).map(|i| (A(i), B(0))));
375+
376+
let mut query = world.term_query::<(&A, &mut B)>();
377+
378+
query
379+
.iter_mut(&mut world)
380+
.for_each(|(a, mut b)| b.0 = a.0 * 2);
381+
382+
let mut query = world.term_query_filtered::<(Entity, &B), With<C>>();
383+
let (e, b) = query.single(&world);
384+
385+
assert_eq!(e, entity);
386+
assert_eq!(2, b.0);
387+
}
352388
}

crates/bevy_ecs/src/term_query/query.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ impl<'w, 's, Q: QueryTermGroup, F: QueryTermGroup> TermQuery<'w, 's, Q, F> {
102102
self.get_single_mut().unwrap()
103103
}
104104

105+
#[inline]
105106
pub fn get_single_mut(&mut self) -> Result<Q::Item<'_>, QuerySingleError> {
106107
// SAFETY:
107108
// the query ensures mutable access to the components it accesses, and the query

0 commit comments

Comments
 (0)