Skip to content

Commit 93cc721

Browse files
committed
small ecs cleanup and remove_bundle drop bugfix (#2172)
- simplified code around archetype generations a little bit, as the special case value is not actually needed - removed unnecessary UnsafeCell around pointer value that is never updated through shared references - fixed and added a test for correct drop behaviour when removing sparse components through remove_bundle command
1 parent 4563e69 commit 93cc721

File tree

8 files changed

+85
-76
lines changed

8 files changed

+85
-76
lines changed

crates/bevy_ecs/src/archetype.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,8 @@ pub struct ArchetypeGeneration(usize);
317317

318318
impl ArchetypeGeneration {
319319
#[inline]
320-
pub fn new(generation: usize) -> Self {
321-
ArchetypeGeneration(generation)
320+
pub const fn initial() -> Self {
321+
ArchetypeGeneration(0)
322322
}
323323

324324
#[inline]

crates/bevy_ecs/src/query/state.rs

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ where
5454

5555
let mut state = Self {
5656
world_id: world.id(),
57-
archetype_generation: ArchetypeGeneration::new(usize::MAX),
57+
archetype_generation: ArchetypeGeneration::initial(),
5858
matched_table_ids: Vec::new(),
5959
matched_archetype_ids: Vec::new(),
6060
fetch_state,
@@ -74,17 +74,10 @@ where
7474
std::any::type_name::<Self>());
7575
}
7676
let archetypes = world.archetypes();
77-
let old_generation = self.archetype_generation;
78-
let archetype_index_range = if old_generation == archetypes.generation() {
79-
0..0
80-
} else {
81-
self.archetype_generation = archetypes.generation();
82-
if old_generation.value() == usize::MAX {
83-
0..archetypes.len()
84-
} else {
85-
old_generation.value()..archetypes.len()
86-
}
87-
};
77+
let new_generation = archetypes.generation();
78+
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
79+
let archetype_index_range = old_generation.value()..new_generation.value();
80+
8881
for archetype_index in archetype_index_range {
8982
self.new_archetype(&archetypes[ArchetypeId::new(archetype_index)]);
9083
}

crates/bevy_ecs/src/schedule/executor.rs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,7 @@ pub struct SingleThreadedExecutor {
1818
impl Default for SingleThreadedExecutor {
1919
fn default() -> Self {
2020
Self {
21-
// MAX ensures access information will be initialized on first run.
22-
archetype_generation: ArchetypeGeneration::new(usize::MAX),
21+
archetype_generation: ArchetypeGeneration::initial(),
2322
}
2423
}
2524
}
@@ -46,24 +45,15 @@ impl SingleThreadedExecutor {
4645
/// [update_archetypes] and updates cached archetype_component_access.
4746
fn update_archetypes(&mut self, systems: &mut [ParallelSystemContainer], world: &World) {
4847
let archetypes = world.archetypes();
49-
let old_generation = self.archetype_generation;
5048
let new_generation = archetypes.generation();
51-
if old_generation == new_generation {
52-
return;
53-
}
49+
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
50+
let archetype_index_range = old_generation.value()..new_generation.value();
5451

55-
let archetype_index_range = if old_generation.value() == usize::MAX {
56-
0..archetypes.len()
57-
} else {
58-
old_generation.value()..archetypes.len()
59-
};
6052
for archetype in archetypes.archetypes[archetype_index_range].iter() {
6153
for container in systems.iter_mut() {
6254
let system = container.system_mut();
6355
system.new_archetype(archetype);
6456
}
6557
}
66-
67-
self.archetype_generation = new_generation;
6858
}
6959
}

crates/bevy_ecs/src/schedule/executor_parallel.rs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,7 @@ impl Default for ParallelExecutor {
5858
fn default() -> Self {
5959
let (finish_sender, finish_receiver) = async_channel::unbounded();
6060
Self {
61-
// MAX ensures access information will be initialized on first run.
62-
archetype_generation: ArchetypeGeneration::new(usize::MAX),
61+
archetype_generation: ArchetypeGeneration::initial(),
6362
system_metadata: Default::default(),
6463
finish_sender,
6564
finish_receiver,
@@ -152,17 +151,10 @@ impl ParallelExecutor {
152151
/// [update_archetypes] and updates cached archetype_component_access.
153152
fn update_archetypes(&mut self, systems: &mut [ParallelSystemContainer], world: &World) {
154153
let archetypes = world.archetypes();
155-
let old_generation = self.archetype_generation;
156154
let new_generation = archetypes.generation();
157-
if old_generation == new_generation {
158-
return;
159-
}
155+
let old_generation = std::mem::replace(&mut self.archetype_generation, new_generation);
156+
let archetype_index_range = old_generation.value()..new_generation.value();
160157

161-
let archetype_index_range = if old_generation.value() == usize::MAX {
162-
0..archetypes.len()
163-
} else {
164-
old_generation.value()..archetypes.len()
165-
};
166158
for archetype in archetypes.archetypes[archetype_index_range].iter() {
167159
for (index, container) in systems.iter_mut().enumerate() {
168160
let meta = &mut self.system_metadata[index];
@@ -172,8 +164,6 @@ impl ParallelExecutor {
172164
.extend(system.archetype_component_access());
173165
}
174166
}
175-
176-
self.archetype_generation = new_generation;
177167
}
178168

179169
/// Populates `should_run` bitset, spawns tasks for systems that should run this iteration,

crates/bevy_ecs/src/storage/blob_vec.rs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
use std::{
22
alloc::{handle_alloc_error, Layout},
3-
cell::UnsafeCell,
43
ptr::NonNull,
54
};
65

@@ -9,17 +8,17 @@ pub struct BlobVec {
98
item_layout: Layout,
109
capacity: usize,
1110
len: usize,
12-
data: UnsafeCell<NonNull<u8>>,
13-
swap_scratch: UnsafeCell<NonNull<u8>>,
11+
data: NonNull<u8>,
12+
swap_scratch: NonNull<u8>,
1413
drop: unsafe fn(*mut u8),
1514
}
1615

1716
impl BlobVec {
1817
pub fn new(item_layout: Layout, drop: unsafe fn(*mut u8), capacity: usize) -> BlobVec {
1918
if item_layout.size() == 0 {
2019
BlobVec {
21-
swap_scratch: UnsafeCell::new(NonNull::dangling()),
22-
data: UnsafeCell::new(NonNull::dangling()),
20+
swap_scratch: NonNull::dangling(),
21+
data: NonNull::dangling(),
2322
capacity: usize::MAX,
2423
len: 0,
2524
item_layout,
@@ -29,8 +28,8 @@ impl BlobVec {
2928
let swap_scratch = NonNull::new(unsafe { std::alloc::alloc(item_layout) })
3029
.unwrap_or_else(|| std::alloc::handle_alloc_error(item_layout));
3130
let mut blob_vec = BlobVec {
32-
swap_scratch: UnsafeCell::new(swap_scratch),
33-
data: UnsafeCell::new(NonNull::dangling()),
31+
swap_scratch,
32+
data: NonNull::dangling(),
3433
capacity: 0,
3534
len: 0,
3635
item_layout,
@@ -81,9 +80,7 @@ impl BlobVec {
8180
)
8281
};
8382

84-
self.data = UnsafeCell::new(
85-
NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout)),
86-
);
83+
self.data = NonNull::new(new_data).unwrap_or_else(|| handle_alloc_error(new_layout));
8784
}
8885
self.capacity = new_capacity;
8986
}
@@ -132,7 +129,7 @@ impl BlobVec {
132129
pub unsafe fn swap_remove_and_forget_unchecked(&mut self, index: usize) -> *mut u8 {
133130
debug_assert!(index < self.len());
134131
let last = self.len - 1;
135-
let swap_scratch = (*self.swap_scratch.get()).as_ptr();
132+
let swap_scratch = self.swap_scratch.as_ptr();
136133
std::ptr::copy_nonoverlapping(
137134
self.get_unchecked(index),
138135
swap_scratch,
@@ -170,7 +167,7 @@ impl BlobVec {
170167
/// must ensure rust mutability rules are not violated
171168
#[inline]
172169
pub unsafe fn get_ptr(&self) -> NonNull<u8> {
173-
*self.data.get()
170+
self.data
174171
}
175172

176173
pub fn clear(&mut self) {
@@ -199,7 +196,7 @@ impl Drop for BlobVec {
199196
array_layout(&self.item_layout, self.capacity)
200197
.expect("array layout should be valid"),
201198
);
202-
std::alloc::dealloc((*self.swap_scratch.get()).as_ptr(), self.item_layout);
199+
std::alloc::dealloc(self.swap_scratch.as_ptr(), self.item_layout);
203200
}
204201
}
205202
}

crates/bevy_ecs/src/storage/sparse_set.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -180,10 +180,7 @@ impl ComponentSparseSet {
180180
/// returned).
181181
pub fn remove_and_forget(&mut self, entity: Entity) -> Option<*mut u8> {
182182
self.sparse.remove(entity).map(|dense_index| {
183-
// SAFE: unique access to ticks
184-
unsafe {
185-
(*self.ticks.get()).swap_remove(dense_index);
186-
}
183+
self.ticks.get_mut().swap_remove(dense_index);
187184
self.entities.swap_remove(dense_index);
188185
let is_last = dense_index == self.dense.len() - 1;
189186
// SAFE: dense_index was just removed from `sparse`, which ensures that it is valid

crates/bevy_ecs/src/system/commands.rs

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -417,9 +417,29 @@ impl<T: Component> Command for RemoveResource<T> {
417417
#[allow(clippy::float_cmp, clippy::approx_constant)]
418418
mod tests {
419419
use crate::{
420+
component::{ComponentDescriptor, StorageType},
420421
system::{CommandQueue, Commands},
421422
world::World,
422423
};
424+
use std::sync::{
425+
atomic::{AtomicUsize, Ordering},
426+
Arc,
427+
};
428+
429+
#[derive(Clone, Debug)]
430+
struct DropCk(Arc<AtomicUsize>);
431+
impl DropCk {
432+
fn new_pair() -> (Self, Arc<AtomicUsize>) {
433+
let atomic = Arc::new(AtomicUsize::new(0));
434+
(DropCk(atomic.clone()), atomic)
435+
}
436+
}
437+
438+
impl Drop for DropCk {
439+
fn drop(&mut self) {
440+
self.0.as_ref().fetch_add(1, Ordering::Relaxed);
441+
}
442+
}
423443

424444
#[test]
425445
fn commands() {
@@ -454,10 +474,20 @@ mod tests {
454474
#[test]
455475
fn remove_components() {
456476
let mut world = World::default();
477+
478+
struct DenseDropCk(DropCk);
479+
world
480+
.register_component(ComponentDescriptor::new::<DropCk>(StorageType::SparseSet))
481+
.unwrap();
482+
457483
let mut command_queue = CommandQueue::default();
484+
let (dense_dropck, dense_is_dropped) = DropCk::new_pair();
485+
let dense_dropck = DenseDropCk(dense_dropck);
486+
let (sparse_dropck, sparse_is_dropped) = DropCk::new_pair();
487+
458488
let entity = Commands::new(&mut command_queue, &world)
459489
.spawn()
460-
.insert_bundle((1u32, 2u64))
490+
.insert_bundle((1u32, 2u64, dense_dropck, sparse_dropck))
461491
.id();
462492
command_queue.apply(&mut world);
463493
let results_before = world
@@ -471,8 +501,14 @@ mod tests {
471501
Commands::new(&mut command_queue, &world)
472502
.entity(entity)
473503
.remove::<u32>()
474-
.remove_bundle::<(u32, u64)>();
504+
.remove_bundle::<(u32, u64, DenseDropCk, DropCk)>();
505+
506+
assert_eq!(dense_is_dropped.load(Ordering::Relaxed), 0);
507+
assert_eq!(sparse_is_dropped.load(Ordering::Relaxed), 0);
475508
command_queue.apply(&mut world);
509+
assert_eq!(dense_is_dropped.load(Ordering::Relaxed), 1);
510+
assert_eq!(sparse_is_dropped.load(Ordering::Relaxed), 1);
511+
476512
let results_after = world
477513
.query::<(&u32, &u64)>()
478514
.iter(&world)

crates/bevy_ecs/src/world/entity_ref.rs

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ impl<'w> EntityMut<'w> {
325325
T::from_components(|| {
326326
let component_id = bundle_components.next().unwrap();
327327
// SAFE: entity location is valid and table row is removed below
328-
remove_component(
328+
take_component(
329329
components,
330330
storages,
331331
old_archetype,
@@ -406,17 +406,18 @@ impl<'w> EntityMut<'w> {
406406
let entity = self.entity;
407407
for component_id in bundle_info.component_ids.iter().cloned() {
408408
if old_archetype.contains(component_id) {
409-
// SAFE: entity location is valid and table row is removed below
410-
unsafe {
411-
remove_component(
412-
components,
413-
storages,
414-
old_archetype,
415-
removed_components,
416-
component_id,
417-
entity,
418-
old_location,
419-
);
409+
removed_components
410+
.get_or_insert_with(component_id, Vec::new)
411+
.push(entity);
412+
413+
// Make sure to drop components stored in sparse sets.
414+
// Dense components are dropped later in `move_to_and_drop_missing_unchecked`.
415+
if let Some(StorageType::SparseSet) = old_archetype.get_storage_type(component_id) {
416+
storages
417+
.sparse_sets
418+
.get_mut(component_id)
419+
.unwrap()
420+
.remove(entity);
420421
}
421422
}
422423
}
@@ -586,13 +587,18 @@ unsafe fn get_component_and_ticks(
586587
}
587588
}
588589

590+
/// Moves component data out of storage.
591+
///
592+
/// This function leaves the underlying memory unchanged, but the component behind
593+
/// returned pointer is semantically owned by the caller and will not be dropped in its original location.
594+
/// Caller is responsible to drop component data behind returned pointer.
595+
///
589596
/// # Safety
590-
// `entity_location` must be within bounds of the given archetype and `entity` must exist inside the
591-
// archetype
592-
/// The relevant table row must be removed separately
593-
/// `component_id` must be valid
597+
/// - `entity_location` must be within bounds of the given archetype and `entity` must exist inside the archetype
598+
/// - `component_id` must be valid
599+
/// - The relevant table row **must be removed** by the caller once all components are taken
594600
#[inline]
595-
unsafe fn remove_component(
601+
unsafe fn take_component(
596602
components: &Components,
597603
storages: &mut Storages,
598604
archetype: &Archetype,

0 commit comments

Comments
 (0)