Skip to content

Commit d0fda0c

Browse files
committed
convert other get_components to return a result
1 parent 3d44c26 commit d0fda0c

File tree

9 files changed

+186
-58
lines changed

9 files changed

+186
-58
lines changed

benches/benches/bevy_ecs/world/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,6 @@ criterion_group!(
3535
query_get_many::<2>,
3636
query_get_many::<5>,
3737
query_get_many::<10>,
38+
query_get_components_mut,
3839
entity_set_build_and_lookup,
3940
);

benches/benches/bevy_ecs/world/world_get.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
use benches::bench;
12
use core::hint::black_box;
23

34
use bevy_ecs::{
45
bundle::{Bundle, NoBundleEffect},
56
component::Component,
67
entity::Entity,
78
system::{Query, SystemState},
8-
world::World,
9+
world::{EntityMut, World},
910
};
1011
use criterion::Criterion;
1112
use rand::{prelude::SliceRandom, SeedableRng};
@@ -357,3 +358,74 @@ pub fn query_get_many<const N: usize>(criterion: &mut Criterion) {
357358
});
358359
}
359360
}
361+
362+
pub fn query_get_components_mut(criterion: &mut Criterion) {
363+
let mut group = criterion.benchmark_group(bench!("world_query_get_components_mut"));
364+
group.warm_up_time(core::time::Duration::from_millis(500));
365+
group.measurement_time(core::time::Duration::from_secs(4));
366+
367+
for entity_count in RANGE.map(|i| i * 10_000) {
368+
let (mut world, entities) = setup_wide::<(
369+
WideTable<0>,
370+
WideTable<1>,
371+
WideTable<2>,
372+
WideTable<3>,
373+
WideTable<4>,
374+
WideTable<5>,
375+
WideTable<6>,
376+
WideTable<7>,
377+
WideTable<8>,
378+
WideTable<9>,
379+
)>(entity_count);
380+
let mut query = world.query::<EntityMut>();
381+
group.bench_function(format!("{entity_count}_entities"), |bencher| {
382+
bencher.iter(|| {
383+
for entity in &entities {
384+
assert!(query
385+
.get_mut(&mut world, *entity)
386+
.unwrap()
387+
.get_components_mut::<(
388+
&mut WideTable<0>,
389+
&mut WideTable<1>,
390+
&mut WideTable<2>,
391+
&mut WideTable<3>,
392+
&mut WideTable<4>,
393+
&mut WideTable<5>,
394+
&mut WideTable<6>,
395+
&mut WideTable<7>,
396+
&mut WideTable<8>,
397+
&mut WideTable<9>,
398+
)>()
399+
.is_ok());
400+
}
401+
});
402+
});
403+
group.bench_function(format!("unchecked_{entity_count}_entities"), |bencher| {
404+
bencher.iter(|| {
405+
for entity in &entities {
406+
// SAFETY: no duplicate components are listed
407+
unsafe {
408+
assert!(query
409+
.get_mut(&mut world, *entity)
410+
.unwrap()
411+
.get_components_mut_unchecked::<(
412+
&mut WideTable<0>,
413+
&mut WideTable<1>,
414+
&mut WideTable<2>,
415+
&mut WideTable<3>,
416+
&mut WideTable<4>,
417+
&mut WideTable<5>,
418+
&mut WideTable<6>,
419+
&mut WideTable<7>,
420+
&mut WideTable<8>,
421+
&mut WideTable<9>,
422+
)>()
423+
.is_ok());
424+
}
425+
}
426+
});
427+
});
428+
}
429+
430+
group.finish();
431+
}

crates/bevy_ecs/src/event/trigger.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,7 @@ unsafe impl<
301301
return;
302302
}
303303
if let Ok(entity) = world.get_entity(current_entity)
304-
&& let Some(item) = entity.get_components::<T>()
304+
&& let Ok(item) = entity.get_components::<T>()
305305
&& let Some(traverse_to) = T::traverse(item, event)
306306
{
307307
current_entity = traverse_to;

crates/bevy_ecs/src/query/access_iter.rs

Lines changed: 35 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
};
55

66
/// The data storage type that is being accessed.
7-
#[derive(Clone, Copy, Debug)]
7+
#[derive(Clone, Copy, Debug, PartialEq)]
88
pub enum EcsAccessType {
99
/// Accesses [`Component`](crate::prelude::Component) data
1010
Component(EcsAccessLevel),
@@ -14,7 +14,7 @@ pub enum EcsAccessType {
1414

1515
/// The way the data will be accessed and whether we take access on all the components on
1616
/// an entity or just one component.
17-
#[derive(Clone, Copy, Debug)]
17+
#[derive(Clone, Copy, Debug, PartialEq)]
1818
pub enum EcsAccessLevel {
1919
/// Reads [`Component`](crate::prelude::Component) with [`ComponentId`]
2020
Read(ComponentId),
@@ -47,7 +47,7 @@ pub enum EcsAccessLevel {
4747
}
4848

4949
/// Access level needed by [`QueryData`] fetch to the resource.
50-
#[derive(Copy, Clone, Debug)]
50+
#[derive(Copy, Clone, Debug, PartialEq)]
5151
pub enum ResourceAccessLevel {
5252
/// Reads the resource with [`ComponentId`]
5353
Read(ComponentId),
@@ -207,37 +207,40 @@ impl EcsAccessType {
207207
}
208208

209209
/// Error returned from [`has_conflicts`].
210-
#[derive(Clone, Copy, Debug)]
211-
pub enum AccessError {
210+
#[derive(Clone, Copy, Debug, PartialEq)]
211+
pub enum QueryAccessError {
212+
/// Component was not registered on world
212213
ComponentNotRegistered,
214+
/// The [`EcsAccessType`]'s conflict with each other
213215
Conflict(EcsAccessType, EcsAccessType),
216+
/// Entity did not have the requested components
214217
EntityDoesNotMatch,
215218
}
216219

217-
impl core::error::Error for AccessError {}
220+
impl core::error::Error for QueryAccessError {}
218221

219-
impl core::fmt::Display for AccessError {
222+
impl core::fmt::Display for QueryAccessError {
220223
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
221224
match *self {
222-
AccessError::ComponentNotRegistered => {
225+
QueryAccessError::ComponentNotRegistered => {
223226
write!(
224227
f,
225228
"At least one component in Q was not registered in world.
226229
Consider calling `World::register_component`"
227230
)
228231
}
229-
AccessError::Conflict(ecs_access, ecs_access1) => {
232+
QueryAccessError::Conflict(ecs_access, ecs_access1) => {
230233
write!(f, "Conflict between {ecs_access:?} and {ecs_access1:?}")
231234
}
232-
AccessError::EntityDoesNotMatch => {
235+
QueryAccessError::EntityDoesNotMatch => {
233236
write!(f, "Entity does not match Q")
234237
}
235238
}
236239
}
237240
}
238241

239242
/// Check if `Q` has any internal conflicts.
240-
pub fn has_conflicts<Q: QueryData>(components: &Components) -> Result<(), AccessError> {
243+
pub fn has_conflicts<Q: QueryData>(components: &Components) -> Result<(), QueryAccessError> {
241244
let mut index_outer = 0;
242245
for (i, access) in Q::iter_access(components, &mut index_outer).enumerate() {
243246
let mut index_inner = 0;
@@ -251,7 +254,7 @@ pub fn has_conflicts<Q: QueryData>(components: &Components) -> Result<(), Access
251254
continue;
252255
}
253256
let (Some(access), Some(access_other)) = (access, access_other) else {
254-
return Err(AccessError::ComponentNotRegistered);
257+
return Err(QueryAccessError::ComponentNotRegistered);
255258
};
256259

257260
// if we're in an except sequence, check if the sequence has ended
@@ -264,7 +267,7 @@ pub fn has_conflicts<Q: QueryData>(components: &Components) -> Result<(), Access
264267

265268
if sequence_ended {
266269
if !except_compatible {
267-
return Err(AccessError::Conflict(access, access_other));
270+
return Err(QueryAccessError::Conflict(access, access_other));
268271
}
269272
except_compatible = false;
270273
except_index = None;
@@ -279,7 +282,7 @@ pub fn has_conflicts<Q: QueryData>(components: &Components) -> Result<(), Access
279282
except_index = Some(index);
280283
except_compatible = true;
281284
},
282-
AccessCompatible::Conflicts => return Err(AccessError::Conflict(access, access_other)),
285+
AccessCompatible::Conflicts => return Err(QueryAccessError::Conflict(access, access_other)),
283286
AccessCompatible::ConflictsExceptSecond(index) => {
284287
except_index = Some(index);
285288
}
@@ -288,7 +291,7 @@ pub fn has_conflicts<Q: QueryData>(components: &Components) -> Result<(), Access
288291

289292
if except_index.is_some() && !except_compatible {
290293
if let (Some(access), Some(access_other)) = (access, last_access) {
291-
return Err(AccessError::Conflict(access, access_other));
294+
return Err(QueryAccessError::Conflict(access, access_other));
292295
}
293296
}
294297
}
@@ -324,15 +327,15 @@ mod tests {
324327
// Conflicts
325328
assert!(matches!(
326329
has_conflicts::<(&C1, &mut C1)>(c),
327-
Err(AccessError::Conflict(_, _))
330+
Err(QueryAccessError::Conflict(_, _))
328331
));
329332
assert!(matches!(
330333
has_conflicts::<(&mut C1, &C1)>(c),
331-
Err(AccessError::Conflict(_, _))
334+
Err(QueryAccessError::Conflict(_, _))
332335
));
333336
assert!(matches!(
334337
has_conflicts::<(&mut C1, &mut C1)>(c),
335-
Err(AccessError::Conflict(_, _))
338+
Err(QueryAccessError::Conflict(_, _))
336339
));
337340
}
338341

@@ -351,35 +354,35 @@ mod tests {
351354
// Conflicts
352355
assert!(matches!(
353356
has_conflicts::<(EntityRef, &mut C1)>(c),
354-
Err(AccessError::Conflict(_, _))
357+
Err(QueryAccessError::Conflict(_, _))
355358
));
356359
assert!(matches!(
357360
has_conflicts::<(&mut C1, EntityRef)>(c),
358-
Err(AccessError::Conflict(_, _))
361+
Err(QueryAccessError::Conflict(_, _))
359362
));
360363
assert!(matches!(
361364
has_conflicts::<(EntityMut, &C1)>(c),
362-
Err(AccessError::Conflict(_, _))
365+
Err(QueryAccessError::Conflict(_, _))
363366
));
364367
assert!(matches!(
365368
has_conflicts::<(&C1, EntityMut)>(c),
366-
Err(AccessError::Conflict(_, _))
369+
Err(QueryAccessError::Conflict(_, _))
367370
));
368371
assert!(matches!(
369372
has_conflicts::<(EntityMut, &mut C1)>(c),
370-
Err(AccessError::Conflict(_, _))
373+
Err(QueryAccessError::Conflict(_, _))
371374
));
372375
assert!(matches!(
373376
has_conflicts::<(&mut C1, EntityMut)>(c),
374-
Err(AccessError::Conflict(_, _))
377+
Err(QueryAccessError::Conflict(_, _))
375378
));
376379
assert!(matches!(
377380
has_conflicts::<(EntityMut, EntityRef)>(c),
378-
Err(AccessError::Conflict(_, _))
381+
Err(QueryAccessError::Conflict(_, _))
379382
));
380383
assert!(matches!(
381384
has_conflicts::<(EntityRef, EntityMut)>(c),
382-
Err(AccessError::Conflict(_, _))
385+
Err(QueryAccessError::Conflict(_, _))
383386
));
384387
}
385388

@@ -403,11 +406,11 @@ mod tests {
403406
// Conflicts
404407
assert!(matches!(
405408
has_conflicts::<(EntityRefExcept<C1>, &mut C2)>(c),
406-
Err(AccessError::Conflict(_, _))
409+
Err(QueryAccessError::Conflict(_, _))
407410
));
408411
assert!(matches!(
409412
has_conflicts::<(&mut C2, EntityRefExcept<C1>)>(c),
410-
Err(AccessError::Conflict(_, _))
413+
Err(QueryAccessError::Conflict(_, _))
411414
));
412415
}
413416

@@ -430,19 +433,19 @@ mod tests {
430433
// Conflicts
431434
assert!(matches!(
432435
has_conflicts::<(&C2, EntityMutExcept<C1>)>(c),
433-
Err(AccessError::Conflict(_, _))
436+
Err(QueryAccessError::Conflict(_, _))
434437
));
435438
assert!(matches!(
436439
has_conflicts::<(EntityMutExcept<C1>, &C2)>(c),
437-
Err(AccessError::Conflict(_, _))
440+
Err(QueryAccessError::Conflict(_, _))
438441
));
439442
assert!(matches!(
440443
has_conflicts::<(EntityMutExcept<C1>, &mut C2)>(c),
441-
Err(AccessError::Conflict(_, _))
444+
Err(QueryAccessError::Conflict(_, _))
442445
));
443446
assert!(matches!(
444447
has_conflicts::<(&mut C2, EntityMutExcept<C1>)>(c),
445-
Err(AccessError::Conflict(_, _))
448+
Err(QueryAccessError::Conflict(_, _))
446449
));
447450
}
448451
}

crates/bevy_ecs/src/world/entity_access/entity_mut.rs

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
change_detection::{ComponentTicks, MaybeLocation, Tick},
44
component::{Component, ComponentId, Mutable},
55
entity::{ContainsEntity, Entity, EntityEquivalent, EntityLocation},
6-
query::{has_conflicts, Access, AccessError, ReadOnlyQueryData, ReleaseStateQueryData},
6+
query::{has_conflicts, Access, QueryAccessError, ReadOnlyQueryData, ReleaseStateQueryData},
77
world::{
88
error::EntityComponentError, unsafe_world_cell::UnsafeEntityCell, DynamicComponentFetch,
99
EntityRef, FilteredEntityMut, FilteredEntityRef, Mut, Ref,
@@ -152,7 +152,7 @@ impl<'w> EntityMut<'w> {
152152
/// or `None` if the entity does not have the components required by the query `Q`.
153153
pub fn get_components<Q: ReadOnlyQueryData + ReleaseStateQueryData>(
154154
&self,
155-
) -> Option<Q::Item<'_, 'static>> {
155+
) -> Result<Q::Item<'_, 'static>, QueryAccessError> {
156156
self.as_readonly().get_components::<Q>()
157157
}
158158

@@ -186,18 +186,40 @@ impl<'w> EntityMut<'w> {
186186
/// the `QueryData` does not provide aliasing mutable references to the same component.
187187
pub unsafe fn get_components_mut_unchecked<Q: ReleaseStateQueryData>(
188188
&mut self,
189-
) -> Option<Q::Item<'_, 'static>> {
189+
) -> Result<Q::Item<'_, 'static>, QueryAccessError> {
190190
// SAFETY: Caller ensures the `QueryData` does not provide aliasing mutable references to the same component
191191
unsafe { self.reborrow().into_components_mut_unchecked::<Q>() }
192192
}
193193

194-
/// returns None if component wasn't registered, or if the access is not compatible bewteen terms
194+
/// Returns components for the current entity that match the query `Q`.
195+
/// In the case of conflicting [`QueryData`](crate::query::QueryData), unregistered components, or missing components,
196+
/// this will return a [`QueryAccessError`]
197+
///
198+
/// # Example
199+
///
200+
/// ```
201+
/// # use bevy_ecs::prelude::*;
202+
/// #
203+
/// #[derive(Component)]
204+
/// struct X(usize);
205+
/// #[derive(Component)]
206+
/// struct Y(usize);
207+
///
208+
/// # let mut world = World::default();
209+
/// let mut entity = world.spawn((X(0), Y(0))).into_mutable();
210+
/// // Get mutable access to two components at once
211+
/// // SAFETY: X and Y are different components
212+
/// let (mut x, mut y) = entity.get_components_mut::<(&mut X, &mut Y)>().unwrap();
213+
/// ```
214+
///
215+
/// Note that this does a O(n^2) check that the [`QueryData`](crate::query::QueryData) does not conflict. If performance is a
216+
/// consideration you should use [`Self::get_components_mut_unchecked`] instead.
195217
pub fn get_components_mut<Q: ReleaseStateQueryData>(
196218
&mut self,
197-
) -> Result<Q::Item<'_, 'static>, AccessError> {
219+
) -> Result<Q::Item<'_, 'static>, QueryAccessError> {
198220
has_conflicts::<Q>(self.cell.world().components())?;
199221
// SAFETY: we checked that there were not conflicting components above
200-
unsafe { self.get_components_mut_unchecked::<Q>() }.ok_or(AccessError::EntityDoesNotMatch)
222+
unsafe { self.get_components_mut_unchecked::<Q>() }
201223
}
202224

203225
/// Consumes self and returns components for the current entity that match the query `Q` for the world lifetime `'w`,
@@ -230,7 +252,7 @@ impl<'w> EntityMut<'w> {
230252
/// the `QueryData` does not provide aliasing mutable references to the same component.
231253
pub unsafe fn into_components_mut_unchecked<Q: ReleaseStateQueryData>(
232254
self,
233-
) -> Option<Q::Item<'w, 'static>> {
255+
) -> Result<Q::Item<'w, 'static>, QueryAccessError> {
234256
// SAFETY:
235257
// - We have mutable access to all components of this entity.
236258
// - Caller asserts the `QueryData` does not provide aliasing mutable references to the same component

0 commit comments

Comments
 (0)