Skip to content

Commit 52b5b27

Browse files
committed
component_scope
1 parent 06a9b62 commit 52b5b27

File tree

6 files changed

+172
-46
lines changed

6 files changed

+172
-46
lines changed

crates/bevy_ecs/src/archetype.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ pub struct Archetype {
395395
table_id: TableId,
396396
edges: Edges,
397397
entities: Vec<ArchetypeEntity>,
398-
disabled_entities: u32,
398+
pub(crate) disabled_entities: u32,
399399
components: ImmutableSparseSet<ComponentId, ArchetypeComponentInfo>,
400400
pub(crate) flags: ArchetypeFlags,
401401
}
@@ -488,6 +488,12 @@ impl Archetype {
488488
&self.entities
489489
}
490490

491+
/// Fetches the disabled entities contained in this archetype.
492+
#[inline]
493+
pub fn disabled_entities(&self) -> &[ArchetypeEntity] {
494+
&self.entities[..self.disabled_entities as usize]
495+
}
496+
491497
/// Get the valid table rows (i.e. non-disabled entities).
492498
#[inline]
493499
pub fn archetype_rows(&self) -> Range<u32> {
@@ -499,7 +505,10 @@ impl Archetype {
499505

500506
/// Fetches the entities contained in this archetype.
501507
#[inline]
502-
pub fn entities_with_location(&self) -> impl Iterator<Item = (Entity, EntityLocation)> {
508+
pub fn entities_with_location(
509+
&self,
510+
) -> impl Iterator<Item = (Entity, EntityLocation)> + DoubleEndedIterator + ExactSizeIterator
511+
{
503512
self.entities.iter().enumerate().map(
504513
|(archetype_row, &ArchetypeEntity { entity, table_row })| {
505514
(
@@ -635,9 +644,11 @@ impl Archetype {
635644
self.entities.reserve(additional);
636645
}
637646

638-
/// Disables the entity at `row` by swapping it with the first enabled
639-
/// entity. Returns the swapped entities with their respective table and
640-
/// archetype rows, or `None` if no swap occurred.
647+
/// Disables or enables the entity at `row` by swapping it with the first
648+
/// enabled or disabled entity. Returns the swapped entities with their
649+
/// respective table and archetype rows, or `None` if no swap occurred. If a
650+
/// swap occurred, the caller is responsible for updating the entity's
651+
/// location.
641652
///
642653
/// # Panics
643654
/// This function will panic if `row >= self.entities.len()`
@@ -650,12 +661,29 @@ impl Archetype {
650661
Option<(ArchetypeEntity, ArchetypeRow)>,
651662
) {
652663
debug_assert!(row.index_u32() < self.len());
653-
debug_assert!(row.index_u32() >= self.disabled_entities);
654664

655-
if row.index_u32() == self.disabled_entities {
656-
// no need to swap, just increment the disabled count
665+
let disabled_row = if row.index_u32() >= self.disabled_entities {
666+
// the entity is currently enabled, swap it with the first enabled entity:
667+
668+
// SAFETY: `self.disabled_entities` is always less than `u32::MAX`,
669+
// as guaranteed by `allocate`.
670+
let disabled_row =
671+
ArchetypeRow::new(unsafe { NonMaxU32::new_unchecked(self.disabled_entities) });
657672
self.disabled_entities += 1;
658673

674+
disabled_row
675+
} else {
676+
self.disabled_entities -= 1;
677+
// the entity is currently disabled, swap it with the last disabled entity:
678+
679+
// SAFETY: `self.disabled_entities` is always less than `u32::MAX`,
680+
// as guaranteed by `allocate`.
681+
ArchetypeRow::new(unsafe { NonMaxU32::new_unchecked(self.disabled_entities) })
682+
};
683+
684+
if row == disabled_row {
685+
// the entity is already in the correct position, no swap needed
686+
659687
// SAFETY: `row` is guaranteed to be in-bounds.
660688
(
661689
(unsafe { *self.entities.get_unchecked(row.index()) }, row),

crates/bevy_ecs/src/entity/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1318,6 +1318,13 @@ pub struct EntityLocation {
13181318
/// It is also useful for reserving an id; commands will often allocate an `Entity` but not provide it a location until the command is applied.
13191319
pub type EntityIdLocation = Option<EntityLocation>;
13201320

1321+
/// An [`Entity`] that has been disabled. This entity will not be found by queries or accessible via other ECS operations, but it's components are still stored in the ECS.
1322+
/// This is useful for temporarily disabling an entity without fully despawning it or invoking archetype moves. This entity keeps track of its location, so it can be re-enabled later.
1323+
pub struct DisabledEntity {
1324+
pub entity: Entity,
1325+
pub location: EntityLocation,
1326+
}
1327+
13211328
#[cfg(test)]
13221329
mod tests {
13231330
use super::*;

crates/bevy_ecs/src/query/iter.rs

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2746,19 +2746,17 @@ mod tests {
27462746
let mut query = world.query::<&A>();
27472747
let mut iter = query.iter(&world);
27482748
println!(
2749-
"archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
2749+
"archetype_entities: {} table_entities: {} current_range: {:?}",
27502750
iter.cursor.archetype_entities.len(),
27512751
iter.cursor.table_entities.len(),
2752-
iter.cursor.current_len,
2753-
iter.cursor.current_row
2752+
iter.cursor.current_range,
27542753
);
27552754
_ = iter.next();
27562755
println!(
2757-
"archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
2756+
"archetype_entities: {} table_entities: {} current_range: {:?}",
27582757
iter.cursor.archetype_entities.len(),
27592758
iter.cursor.table_entities.len(),
2760-
iter.cursor.current_len,
2761-
iter.cursor.current_row
2759+
iter.cursor.current_range,
27622760
);
27632761
println!("{}", iter.sort::<Entity>().len());
27642762
}
@@ -2776,19 +2774,17 @@ mod tests {
27762774
let mut query = world.query::<&Sparse>();
27772775
let mut iter = query.iter(&world);
27782776
println!(
2779-
"before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
2777+
"before_next_call: archetype_entities: {} table_entities: {} current_range: {:?}",
27802778
iter.cursor.archetype_entities.len(),
27812779
iter.cursor.table_entities.len(),
2782-
iter.cursor.current_len,
2783-
iter.cursor.current_row
2780+
iter.cursor.current_range,
27842781
);
27852782
_ = iter.next();
27862783
println!(
2787-
"after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
2784+
"after_next_call: archetype_entities: {} table_entities: {} current_range: {:?}",
27882785
iter.cursor.archetype_entities.len(),
27892786
iter.cursor.table_entities.len(),
2790-
iter.cursor.current_len,
2791-
iter.cursor.current_row
2787+
iter.cursor.current_range,
27922788
);
27932789
println!("{}", iter.sort::<Entity>().len());
27942790
}
@@ -2801,19 +2797,17 @@ mod tests {
28012797
let mut query = world.query::<(&A, &Sparse)>();
28022798
let mut iter = query.iter(&world);
28032799
println!(
2804-
"before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
2800+
"before_next_call: archetype_entities: {} table_entities: {} current_range: {:?}",
28052801
iter.cursor.archetype_entities.len(),
28062802
iter.cursor.table_entities.len(),
2807-
iter.cursor.current_len,
2808-
iter.cursor.current_row
2803+
iter.cursor.current_range,
28092804
);
28102805
_ = iter.next();
28112806
println!(
2812-
"after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
2807+
"before_next_call: archetype_entities: {} table_entities: {} current_range: {:?}",
28132808
iter.cursor.archetype_entities.len(),
28142809
iter.cursor.table_entities.len(),
2815-
iter.cursor.current_len,
2816-
iter.cursor.current_row
2810+
iter.cursor.current_range,
28172811
);
28182812
println!("{}", iter.sort::<Entity>().len());
28192813
}
@@ -2829,20 +2823,18 @@ mod tests {
28292823
let mut query = world.query::<(&A, &Sparse)>();
28302824
let mut iter = query.iter(&world);
28312825
println!(
2832-
"before_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
2826+
"before_next_call: archetype_entities: {} table_entities: {} current_range: {:?}",
28332827
iter.cursor.archetype_entities.len(),
28342828
iter.cursor.table_entities.len(),
2835-
iter.cursor.current_len,
2836-
iter.cursor.current_row
2829+
iter.cursor.current_range,
28372830
);
28382831
assert!(iter.cursor.table_entities.len() | iter.cursor.archetype_entities.len() == 0);
28392832
_ = iter.next();
28402833
println!(
2841-
"after_next_call: archetype_entities: {} table_entities: {} current_len: {} current_row: {}",
2834+
"before_next_call: archetype_entities: {} table_entities: {} current_range: {:?}",
28422835
iter.cursor.archetype_entities.len(),
28432836
iter.cursor.table_entities.len(),
2844-
iter.cursor.current_len,
2845-
iter.cursor.current_row
2837+
iter.cursor.current_range,
28462838
);
28472839
assert!(
28482840
(

crates/bevy_ecs/src/storage/table/mod.rs

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ impl Table {
198198
/// Fetches a read-only slice of the entities stored within the [`Table`].
199199
#[inline]
200200
pub fn entities(&self) -> &[Entity] {
201-
&self.entities[self.disabled_entities as usize..]
201+
&self.entities
202202
}
203203

204204
/// Get the valid table rows (i.e. non-disabled entities).
@@ -217,7 +217,11 @@ impl Table {
217217
self.entities.capacity()
218218
}
219219

220-
/// Disables the entity at the given row, moving it to the back of the table and returns the entities that were swapped (if any) together with their new `TableRow`.
220+
/// Disables or enables the entity at `row` by swapping it with the first
221+
/// enabled or disabled entity. Returns the swapped entities with their
222+
/// respective table rows, or `None` if no swap occurred. If a
223+
/// swap occurred, the caller is responsible for updating the entity's
224+
/// location.
221225
///
222226
/// # Safety
223227
/// `row` must be in-bounds (`row.as_usize()` < `self.len()`) and not disabled.
@@ -226,20 +230,37 @@ impl Table {
226230
row: TableRow,
227231
) -> ((Entity, TableRow), Option<(Entity, TableRow)>) {
228232
debug_assert!(row.index_u32() < self.len());
229-
debug_assert!(row.index_u32() >= self.disabled_entities);
230233

231-
if row.index_u32() != self.disabled_entities {
234+
let disabled_row = if row.index_u32() >= self.disabled_entities {
235+
// the entity is currently enabled, swap it with the first enabled entity:
236+
232237
// SAFETY: `self.disabled_entities` is always less than `u32::MAX`,
233238
// as guaranteed by `allocate`.
234239
let disabled_row =
235240
TableRow::new(unsafe { NonMaxU32::new_unchecked(self.disabled_entities) });
241+
self.disabled_entities += 1;
242+
243+
disabled_row
244+
} else {
245+
self.disabled_entities -= 1;
246+
// the entity is currently disabled, swap it with the last disabled entity:
247+
248+
// SAFETY: `self.disabled_entities` is always less than `u32::MAX`,
249+
// as guaranteed by `allocate`.
250+
TableRow::new(unsafe { NonMaxU32::new_unchecked(self.disabled_entities) })
251+
};
236252

253+
if row == disabled_row {
254+
// the entity is already in the correct position, no swap needed
255+
256+
// SAFETY: TODO
257+
((*self.entities.get_unchecked(row.index()), row), None)
258+
} else {
237259
for col in self.columns.values_mut() {
238260
col.swap_unchecked(row, disabled_row);
239261
}
240262

241263
self.entities.swap(row.index(), disabled_row.index());
242-
self.disabled_entities += 1;
243264

244265
(
245266
(
@@ -248,9 +269,6 @@ impl Table {
248269
),
249270
Some((*self.entities.get_unchecked(row.index()), row)),
250271
)
251-
} else {
252-
self.disabled_entities += 1;
253-
((*self.entities.get_unchecked(row.index()), row), None)
254272
}
255273
}
256274

crates/bevy_ecs/src/world/entity_ref.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use crate::{
66
change_detection::{MaybeLocation, MutUntyped},
77
component::{Component, ComponentId, ComponentTicks, Components, Mutable, StorageType, Tick},
88
entity::{
9-
ContainsEntity, Entity, EntityCloner, EntityClonerBuilder, EntityEquivalent,
10-
EntityIdLocation, EntityLocation, OptIn, OptOut,
9+
ContainsEntity, DisabledEntity, Entity, EntityCloner, EntityClonerBuilder,
10+
EntityEquivalent, EntityIdLocation, EntityLocation, OptIn, OptOut,
1111
},
1212
event::{EntityComponentsTrigger, EntityEvent},
1313
lifecycle::{Despawn, Remove, Replace, DESPAWN, REMOVE, REPLACE},
@@ -2564,7 +2564,7 @@ impl<'w> EntityWorldMut<'w> {
25642564

25652565
/// Disable the current entity.
25662566
///
2567-
pub fn disable(self) -> EntityLocation {
2567+
pub fn disable(self) -> DisabledEntity {
25682568
let world = self.world;
25692569

25702570
let location = world
@@ -2629,7 +2629,10 @@ impl<'w> EntityWorldMut<'w> {
26292629
world.entities.set(self.entity.index(), None);
26302630
}
26312631

2632-
location
2632+
DisabledEntity {
2633+
entity: self.entity,
2634+
location,
2635+
}
26332636
}
26342637

26352638
pub(crate) fn despawn_with_caller(self, caller: MaybeLocation) {

crates/bevy_ecs/src/world/mod.rs

Lines changed: 80 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use crate::{
4141
ComponentTicks, Components, ComponentsQueuedRegistrator, ComponentsRegistrator, Mutable,
4242
RequiredComponents, RequiredComponentsError, Tick,
4343
},
44-
entity::{Entities, Entity, EntityDoesNotExistError, EntityLocation},
44+
entity::{DisabledEntity, Entities, Entity, EntityDoesNotExistError},
4545
entity_disabling::DefaultQueryFilters,
4646
error::{DefaultErrorHandler, ErrorHandler},
4747
lifecycle::{ComponentHooks, RemovedComponentMessages, ADD, DESPAWN, INSERT, REMOVE, REPLACE},
@@ -1439,13 +1439,91 @@ impl World {
14391439
Ok(())
14401440
}
14411441

1442-
pub fn disable(&mut self, entity: Entity) -> Result<EntityLocation, EntityMutableFetchError> {
1442+
/// Disables the given `entity`, returning a [`DisabledEntity`] if the
1443+
/// entity exists and was enabled.
1444+
pub fn disable(&mut self, entity: Entity) -> Result<DisabledEntity, EntityMutableFetchError> {
14431445
self.flush();
14441446

14451447
let entity = self.get_entity_mut(entity)?;
14461448
Ok(entity.disable())
14471449
}
14481450

1451+
/// Re-enables a previously disabled entity, returning an [`EntityWorldMut`]
1452+
/// to it.
1453+
pub fn enable(&mut self, disabled: DisabledEntity) -> EntityWorldMut<'_> {
1454+
self.flush();
1455+
1456+
let archetype = &self.archetypes[disabled.location.archetype_id];
1457+
// Find the location of the disabled entity in the archetype by
1458+
// searching backwards through the disabled entities.
1459+
// The disabled entity is most likely the last disabled entity, as
1460+
// is the case when disabled for `entity_scope`.
1461+
let location = archetype
1462+
.entities_with_location()
1463+
.take(archetype.disabled_entities as usize)
1464+
.rev()
1465+
.find(|(e, _)| *e == disabled.entity)
1466+
.map(|(_, location)| location);
1467+
1468+
// SAFETY: disabled entity existed when it was disabled, and wasn't
1469+
// despawned in the meantime.
1470+
unsafe {
1471+
self.entities.set(disabled.entity.index(), location);
1472+
}
1473+
1474+
self.entity_mut(disabled.entity)
1475+
}
1476+
1477+
pub fn component_scope<
1478+
C: Component<Mutability = Mutable>,
1479+
R,
1480+
F: FnOnce(Entity, &mut C, &mut World) -> R,
1481+
>(
1482+
&mut self,
1483+
entity: Entity,
1484+
f: F,
1485+
) {
1486+
self.try_component_scope(entity, f).unwrap_or_else(|| {
1487+
panic!(
1488+
"component {} does not exist for entity {entity}",
1489+
DebugName::type_name::<C>()
1490+
)
1491+
});
1492+
}
1493+
1494+
pub fn try_component_scope<
1495+
C: Component<Mutability = Mutable>,
1496+
R,
1497+
F: FnOnce(Entity, &mut C, &mut World) -> R,
1498+
>(
1499+
&mut self,
1500+
entity: Entity,
1501+
f: F,
1502+
) -> Option<R> {
1503+
self.flush();
1504+
1505+
let mut entity_mut = self.get_entity_mut(entity).ok()?;
1506+
let mut component = {
1507+
let component = entity_mut.get_mut::<C>()?;
1508+
// SAFETY: TODO
1509+
unsafe { core::ptr::read::<C>(&raw const *component) }
1510+
};
1511+
1512+
let disabled = entity_mut.disable();
1513+
1514+
let out = f(entity, &mut component, self);
1515+
1516+
let mut entity_mut = self.enable(disabled);
1517+
1518+
let mut component_mut = entity_mut.get_mut::<C>().unwrap();
1519+
// SAFETY: TODO
1520+
unsafe {
1521+
core::ptr::write::<C>(&raw mut *component_mut, component);
1522+
}
1523+
1524+
Some(out)
1525+
}
1526+
14491527
/// Clears the internal component tracker state.
14501528
///
14511529
/// The world maintains some internal state about changed and removed components. This state

0 commit comments

Comments
 (0)