Skip to content

Commit 1214dda

Browse files
Frizicart
andcommitted
drop overwritten component data on double insert (#2227)
Continuing the work on reducing the safety footguns in the code, I've removed one extra `UnsafeCell` in favour of safe `Cell` usage inisde `ComponentTicks`. That change led to discovery of misbehaving component insert logic, where data wasn't properly dropped when overwritten. Apart from that being fixed, some method names were changed to better convey the "initialize new allocation" and "replace existing allocation" semantic. Depends on #2221, I will rebase this PR after the dependency is merged. For now, review just the last commit. Co-authored-by: Carter Anderson <[email protected]>
1 parent 173bb48 commit 1214dda

File tree

12 files changed

+229
-151
lines changed

12 files changed

+229
-151
lines changed

crates/bevy_ecs/src/bundle.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -140,20 +140,20 @@ impl BundleInfo {
140140
// bundle_info.component_ids are also in "bundle order"
141141
let mut bundle_component = 0;
142142
bundle.get_components(|component_ptr| {
143-
// SAFE: component_id was initialized by get_dynamic_bundle_info
144143
let component_id = *self.component_ids.get_unchecked(bundle_component);
145-
let component_status = bundle_status.get_unchecked(bundle_component);
146144
match self.storage_types[bundle_component] {
147145
StorageType::Table => {
148146
let column = table.get_column_mut(component_id).unwrap();
149-
column.set_data_unchecked(table_row, component_ptr);
150-
let column_status = column.get_ticks_unchecked_mut(table_row);
151-
match component_status {
147+
match bundle_status.get_unchecked(bundle_component) {
152148
ComponentStatus::Added => {
153-
*column_status = ComponentTicks::new(change_tick);
149+
column.initialize(
150+
table_row,
151+
component_ptr,
152+
ComponentTicks::new(change_tick),
153+
);
154154
}
155155
ComponentStatus::Mutated => {
156-
column_status.set_changed(change_tick);
156+
column.replace(table_row, component_ptr, change_tick);
157157
}
158158
}
159159
}

crates/bevy_ecs/src/component/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ impl Components {
306306
}
307307
}
308308

309-
#[derive(Copy, Clone, Debug)]
309+
#[derive(Clone, Debug)]
310310
pub struct ComponentTicks {
311311
pub(crate) added: u32,
312312
pub(crate) changed: u32,

crates/bevy_ecs/src/lib.rs

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,34 @@ mod tests {
5050
};
5151
use bevy_tasks::TaskPool;
5252
use parking_lot::Mutex;
53-
use std::{any::TypeId, sync::Arc};
53+
use std::{
54+
any::TypeId,
55+
sync::{
56+
atomic::{AtomicUsize, Ordering},
57+
Arc,
58+
},
59+
};
5460

5561
#[derive(Debug, PartialEq, Eq)]
5662
struct A(usize);
5763
struct B(usize);
5864
struct C;
5965

66+
#[derive(Clone, Debug)]
67+
struct DropCk(Arc<AtomicUsize>);
68+
impl DropCk {
69+
fn new_pair() -> (Self, Arc<AtomicUsize>) {
70+
let atomic = Arc::new(AtomicUsize::new(0));
71+
(DropCk(atomic.clone()), atomic)
72+
}
73+
}
74+
75+
impl Drop for DropCk {
76+
fn drop(&mut self) {
77+
self.0.as_ref().fetch_add(1, Ordering::Relaxed);
78+
}
79+
}
80+
6081
#[test]
6182
fn random_access() {
6283
let mut world = World::new();
@@ -1176,4 +1197,33 @@ mod tests {
11761197
});
11771198
assert_eq!(*world.get_resource::<i32>().unwrap(), 1);
11781199
}
1200+
1201+
#[test]
1202+
fn insert_overwrite_drop() {
1203+
let (dropck1, dropped1) = DropCk::new_pair();
1204+
let (dropck2, dropped2) = DropCk::new_pair();
1205+
let mut world = World::default();
1206+
world.spawn().insert(dropck1).insert(dropck2);
1207+
assert_eq!(dropped1.load(Ordering::Relaxed), 1);
1208+
assert_eq!(dropped2.load(Ordering::Relaxed), 0);
1209+
drop(world);
1210+
assert_eq!(dropped1.load(Ordering::Relaxed), 1);
1211+
assert_eq!(dropped2.load(Ordering::Relaxed), 1);
1212+
}
1213+
1214+
#[test]
1215+
fn insert_overwrite_drop_sparse() {
1216+
let (dropck1, dropped1) = DropCk::new_pair();
1217+
let (dropck2, dropped2) = DropCk::new_pair();
1218+
let mut world = World::default();
1219+
world
1220+
.register_component(ComponentDescriptor::new::<DropCk>(StorageType::SparseSet))
1221+
.unwrap();
1222+
world.spawn().insert(dropck1).insert(dropck2);
1223+
assert_eq!(dropped1.load(Ordering::Relaxed), 1);
1224+
assert_eq!(dropped2.load(Ordering::Relaxed), 0);
1225+
drop(world);
1226+
assert_eq!(dropped1.load(Ordering::Relaxed), 1);
1227+
assert_eq!(dropped2.load(Ordering::Relaxed), 1);
1228+
}
11791229
}

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::{
99
};
1010
use bevy_ecs_macros::all_tuples;
1111
use std::{
12+
cell::UnsafeCell,
1213
marker::PhantomData,
1314
ptr::{self, NonNull},
1415
};
@@ -343,7 +344,7 @@ impl<'w, T: Component> Fetch<'w> for ReadFetch<T> {
343344
let column = tables[archetype.table_id()]
344345
.get_column(state.component_id)
345346
.unwrap();
346-
self.table_components = column.get_ptr().cast::<T>();
347+
self.table_components = column.get_data_ptr().cast::<T>();
347348
}
348349
StorageType::SparseSet => self.entities = archetype.entities().as_ptr(),
349350
}
@@ -354,7 +355,7 @@ impl<'w, T: Component> Fetch<'w> for ReadFetch<T> {
354355
self.table_components = table
355356
.get_column(state.component_id)
356357
.unwrap()
357-
.get_ptr()
358+
.get_data_ptr()
358359
.cast::<T>();
359360
}
360361

@@ -387,7 +388,7 @@ impl<T: Component> WorldQuery for &mut T {
387388
pub struct WriteFetch<T> {
388389
storage_type: StorageType,
389390
table_components: NonNull<T>,
390-
table_ticks: *mut ComponentTicks,
391+
table_ticks: *const UnsafeCell<ComponentTicks>,
391392
entities: *const Entity,
392393
entity_table_rows: *const usize,
393394
sparse_set: *const ComponentSparseSet,
@@ -482,7 +483,7 @@ impl<'w, T: Component> Fetch<'w> for WriteFetch<T> {
482483
entities: ptr::null::<Entity>(),
483484
entity_table_rows: ptr::null::<usize>(),
484485
sparse_set: ptr::null::<ComponentSparseSet>(),
485-
table_ticks: ptr::null_mut::<ComponentTicks>(),
486+
table_ticks: ptr::null::<UnsafeCell<ComponentTicks>>(),
486487
last_change_tick,
487488
change_tick,
488489
};
@@ -509,8 +510,8 @@ impl<'w, T: Component> Fetch<'w> for WriteFetch<T> {
509510
let column = tables[archetype.table_id()]
510511
.get_column(state.component_id)
511512
.unwrap();
512-
self.table_components = column.get_ptr().cast::<T>();
513-
self.table_ticks = column.get_ticks_mut_ptr();
513+
self.table_components = column.get_data_ptr().cast::<T>();
514+
self.table_ticks = column.get_ticks_ptr();
514515
}
515516
StorageType::SparseSet => self.entities = archetype.entities().as_ptr(),
516517
}
@@ -519,8 +520,8 @@ impl<'w, T: Component> Fetch<'w> for WriteFetch<T> {
519520
#[inline]
520521
unsafe fn set_table(&mut self, state: &Self::State, table: &Table) {
521522
let column = table.get_column(state.component_id).unwrap();
522-
self.table_components = column.get_ptr().cast::<T>();
523-
self.table_ticks = column.get_ticks_mut_ptr();
523+
self.table_components = column.get_data_ptr().cast::<T>();
524+
self.table_ticks = column.get_ticks_ptr();
524525
}
525526

526527
#[inline]
@@ -531,7 +532,7 @@ impl<'w, T: Component> Fetch<'w> for WriteFetch<T> {
531532
Mut {
532533
value: &mut *self.table_components.as_ptr().add(table_row),
533534
ticks: Ticks {
534-
component_ticks: &mut *self.table_ticks.add(table_row),
535+
component_ticks: &mut *(&*self.table_ticks.add(table_row)).get(),
535536
change_tick: self.change_tick,
536537
last_change_tick: self.last_change_tick,
537538
},
@@ -558,7 +559,7 @@ impl<'w, T: Component> Fetch<'w> for WriteFetch<T> {
558559
Mut {
559560
value: &mut *self.table_components.as_ptr().add(table_row),
560561
ticks: Ticks {
561-
component_ticks: &mut *self.table_ticks.add(table_row),
562+
component_ticks: &mut *(&*self.table_ticks.add(table_row)).get(),
562563
change_tick: self.change_tick,
563564
last_change_tick: self.last_change_tick,
564565
},
@@ -860,7 +861,7 @@ impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<T> {
860861
let column = tables[archetype.table_id()]
861862
.get_column(state.component_id)
862863
.unwrap();
863-
self.table_ticks = column.get_ticks_mut_ptr().cast::<ComponentTicks>();
864+
self.table_ticks = column.get_ticks_const_ptr();
864865
}
865866
StorageType::SparseSet => self.entities = archetype.entities().as_ptr(),
866867
}
@@ -871,8 +872,7 @@ impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<T> {
871872
self.table_ticks = table
872873
.get_column(state.component_id)
873874
.unwrap()
874-
.get_ticks_mut_ptr()
875-
.cast::<ComponentTicks>();
875+
.get_ticks_const_ptr();
876876
}
877877

878878
#[inline]
@@ -881,7 +881,7 @@ impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<T> {
881881
StorageType::Table => {
882882
let table_row = *self.entity_table_rows.add(archetype_index);
883883
ChangeTrackers {
884-
component_ticks: *self.table_ticks.add(table_row),
884+
component_ticks: (&*self.table_ticks.add(table_row)).clone(),
885885
marker: PhantomData,
886886
last_change_tick: self.last_change_tick,
887887
change_tick: self.change_tick,
@@ -890,7 +890,7 @@ impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<T> {
890890
StorageType::SparseSet => {
891891
let entity = *self.entities.add(archetype_index);
892892
ChangeTrackers {
893-
component_ticks: *(*self.sparse_set).get_ticks(entity).unwrap(),
893+
component_ticks: (&*self.sparse_set).get_ticks(entity).cloned().unwrap(),
894894
marker: PhantomData,
895895
last_change_tick: self.last_change_tick,
896896
change_tick: self.change_tick,
@@ -902,7 +902,7 @@ impl<'w, T: Component> Fetch<'w> for ChangeTrackersFetch<T> {
902902
#[inline]
903903
unsafe fn table_fetch(&mut self, table_row: usize) -> Self::Item {
904904
ChangeTrackers {
905-
component_ticks: *self.table_ticks.add(table_row),
905+
component_ticks: (&*self.table_ticks.add(table_row)).clone(),
906906
marker: PhantomData,
907907
last_change_tick: self.last_change_tick,
908908
change_tick: self.change_tick,

crates/bevy_ecs/src/query/filter.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use crate::{
88
world::World,
99
};
1010
use bevy_ecs_macros::all_tuples;
11-
use std::{marker::PhantomData, ptr};
11+
use std::{cell::UnsafeCell, marker::PhantomData, ptr};
1212

1313
// TODO: uncomment this and use as shorthand (remove where F::Fetch: FilterFetch everywhere) when
1414
// this bug is fixed in Rust 1.51: https://github.com/rust-lang/rust/pull/81671
@@ -561,7 +561,7 @@ macro_rules! impl_tick_filter {
561561
$(#[$fetch_meta])*
562562
pub struct $fetch_name<T> {
563563
storage_type: StorageType,
564-
table_ticks: *mut ComponentTicks,
564+
table_ticks: *const UnsafeCell<ComponentTicks>,
565565
entity_table_rows: *const usize,
566566
marker: PhantomData<T>,
567567
entities: *const Entity,
@@ -630,7 +630,7 @@ macro_rules! impl_tick_filter {
630630
unsafe fn init(world: &World, state: &Self::State, last_change_tick: u32, change_tick: u32) -> Self {
631631
let mut value = Self {
632632
storage_type: state.storage_type,
633-
table_ticks: ptr::null_mut::<ComponentTicks>(),
633+
table_ticks: ptr::null::<UnsafeCell<ComponentTicks>>(),
634634
entities: ptr::null::<Entity>(),
635635
entity_table_rows: ptr::null::<usize>(),
636636
sparse_set: ptr::null::<ComponentSparseSet>(),
@@ -655,7 +655,7 @@ macro_rules! impl_tick_filter {
655655
unsafe fn set_table(&mut self, state: &Self::State, table: &Table) {
656656
self.table_ticks = table
657657
.get_column(state.component_id).unwrap()
658-
.get_ticks_mut_ptr();
658+
.get_ticks_ptr();
659659
}
660660

661661
unsafe fn set_archetype(&mut self, state: &Self::State, archetype: &Archetype, tables: &Tables) {
@@ -665,25 +665,25 @@ macro_rules! impl_tick_filter {
665665
let table = &tables[archetype.table_id()];
666666
self.table_ticks = table
667667
.get_column(state.component_id).unwrap()
668-
.get_ticks_mut_ptr();
668+
.get_ticks_ptr();
669669
}
670670
StorageType::SparseSet => self.entities = archetype.entities().as_ptr(),
671671
}
672672
}
673673

674674
unsafe fn table_fetch(&mut self, table_row: usize) -> bool {
675-
$is_detected(&*self.table_ticks.add(table_row), self.last_change_tick, self.change_tick)
675+
$is_detected(&*(&*self.table_ticks.add(table_row)).get(), self.last_change_tick, self.change_tick)
676676
}
677677

678678
unsafe fn archetype_fetch(&mut self, archetype_index: usize) -> bool {
679679
match self.storage_type {
680680
StorageType::Table => {
681681
let table_row = *self.entity_table_rows.add(archetype_index);
682-
$is_detected(&*self.table_ticks.add(table_row), self.last_change_tick, self.change_tick)
682+
$is_detected(&*(&*self.table_ticks.add(table_row)).get(), self.last_change_tick, self.change_tick)
683683
}
684684
StorageType::SparseSet => {
685685
let entity = *self.entities.add(archetype_index);
686-
let ticks = (*(*self.sparse_set).get_ticks(entity).unwrap());
686+
let ticks = (&*self.sparse_set).get_ticks(entity).cloned().unwrap();
687687
$is_detected(&ticks, self.last_change_tick, self.change_tick)
688688
}
689689
}

crates/bevy_ecs/src/storage/blob_vec.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -86,15 +86,25 @@ impl BlobVec {
8686
}
8787

8888
/// # Safety
89-
/// `index` must be in bounds
90-
/// Allows aliased mutable access to `index`'s data. Caller must ensure this does not happen
89+
/// - index must be in bounds
90+
/// - memory must be reserved and uninitialized
9191
#[inline]
92-
pub unsafe fn set_unchecked(&self, index: usize, value: *mut u8) {
92+
pub unsafe fn initialize_unchecked(&mut self, index: usize, value: *mut u8) {
9393
debug_assert!(index < self.len());
9494
let ptr = self.get_unchecked(index);
9595
std::ptr::copy_nonoverlapping(value, ptr, self.item_layout.size());
9696
}
9797

98+
/// # Safety
99+
/// - index must be in-bounds
100+
// - memory must be previously initialized
101+
pub unsafe fn replace_unchecked(&mut self, index: usize, value: *mut u8) {
102+
debug_assert!(index < self.len());
103+
let ptr = self.get_unchecked(index);
104+
(self.drop)(ptr);
105+
std::ptr::copy_nonoverlapping(value, ptr, self.item_layout.size());
106+
}
107+
98108
/// increases the length by one (and grows the vec if needed) with uninitialized memory and
99109
/// returns the index
100110
///
@@ -267,7 +277,7 @@ mod tests {
267277
/// `blob_vec` must have a layout that matches Layout::new::<T>()
268278
unsafe fn push<T>(blob_vec: &mut BlobVec, mut value: T) {
269279
let index = blob_vec.push_uninit();
270-
blob_vec.set_unchecked(index, (&mut value as *mut T).cast::<u8>());
280+
blob_vec.initialize_unchecked(index, (&mut value as *mut T).cast::<u8>());
271281
std::mem::forget(value);
272282
}
273283

0 commit comments

Comments
 (0)