Skip to content

Commit cfe55be

Browse files
committed
add swap functions to table
1 parent 1610aa9 commit cfe55be

File tree

4 files changed

+106
-5
lines changed

4 files changed

+106
-5
lines changed

crates/bevy_ecs/src/storage/blob_array.rs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ impl BlobArray {
403403
self.get_unchecked_mut(index_to_keep).promote()
404404
}
405405

406-
/// The same as [`Self::swap_remove_unchecked`] but the two elements must non-overlapping.
406+
/// This method will swap two elements in the array.
407407
///
408408
/// # Safety
409409
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
@@ -413,11 +413,11 @@ impl BlobArray {
413413
/// 1) initialize a different value in `index_to_keep`
414414
/// 2) update the saved length of the array if `index_to_keep` was the last element.
415415
#[inline]
416-
pub unsafe fn swap_remove_unchecked_nonoverlapping(
416+
pub unsafe fn swap_unchecked_nonoverlapping(
417417
&mut self,
418418
index_to_remove: usize,
419419
index_to_keep: usize,
420-
) -> OwningPtr<'_> {
420+
) {
421421
#[cfg(debug_assertions)]
422422
{
423423
debug_assert!(self.capacity > index_to_keep);
@@ -430,6 +430,24 @@ impl BlobArray {
430430
self.get_unchecked_mut(index_to_remove).as_ptr(),
431431
self.item_layout.size(),
432432
);
433+
}
434+
435+
/// The same as [`Self::swap_remove_unchecked`] but the two elements must non-overlapping.
436+
///
437+
/// # Safety
438+
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
439+
/// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
440+
/// - `index_to_remove` != `index_to_keep`
441+
/// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
442+
/// 1) initialize a different value in `index_to_keep`
443+
/// 2) update the saved length of the array if `index_to_keep` was the last element.
444+
#[inline]
445+
pub unsafe fn swap_remove_unchecked_nonoverlapping(
446+
&mut self,
447+
index_to_remove: usize,
448+
index_to_keep: usize,
449+
) -> OwningPtr<'_> {
450+
self.swap_unchecked_nonoverlapping(index_to_remove, index_to_keep);
433451
// Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap)
434452
// If we are storing ZSTs than the index doesn't actually matter because the size is 0.
435453
self.get_unchecked_mut(index_to_keep).promote()

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,23 @@ impl Column {
4343
}
4444
}
4545

46+
/// Swap the two elements.
47+
///
48+
/// # Safety
49+
/// - `a.as_usize()` < `len`
50+
/// - `b.as_usize()` < `len`
51+
/// - `a` != `b`
52+
pub(crate) unsafe fn swap_unchecked(&mut self, a: TableRow, b: TableRow) {
53+
let a = a.index();
54+
let b = b.index();
55+
self.data.swap_unchecked_nonoverlapping(a, b);
56+
self.added_ticks.swap_unchecked_nonoverlapping(a, b);
57+
self.changed_ticks.swap_unchecked_nonoverlapping(a, b);
58+
self.changed_by.as_mut().map(|changed_by| {
59+
changed_by.swap_unchecked_nonoverlapping(a, b);
60+
});
61+
}
62+
4663
/// Swap-remove and drop the removed element, but the component at `row` must not be the last element.
4764
///
4865
/// # Safety

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

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pub use column::*;
1212
use core::{
1313
cell::UnsafeCell,
1414
num::NonZeroUsize,
15-
ops::{Index, IndexMut},
15+
ops::{Index, IndexMut, Range},
1616
panic::Location,
1717
};
1818
use nonmax::NonMaxU32;
@@ -166,6 +166,7 @@ impl TableBuilder {
166166
Table {
167167
columns: self.columns.into_immutable(),
168168
entities: self.entities,
169+
disabled_entities: 0,
169170
}
170171
}
171172
}
@@ -190,13 +191,27 @@ impl TableBuilder {
190191
pub struct Table {
191192
columns: ImmutableSparseSet<ComponentId, Column>,
192193
entities: Vec<Entity>,
194+
disabled_entities: u32,
193195
}
194196

195197
impl Table {
196198
/// Fetches a read-only slice of the entities stored within the [`Table`].
197199
#[inline]
198200
pub fn entities(&self) -> &[Entity] {
199-
&self.entities
201+
&self.entities[self.disabled_entities as usize..]
202+
}
203+
204+
/// Get the valid table rows (i.e. non-disabled entities).
205+
#[inline]
206+
pub fn table_rows(&self) -> Range<TableRow> {
207+
// SAFETY:
208+
// - No entity row may be in more than one table row at once, so there are no duplicates,
209+
// and there can not be an entity row of u32::MAX. Therefore, this can not be max either.
210+
// - self.disabled_entities <= self.entity_count()
211+
unsafe {
212+
TableRow::new(NonMaxU32::new_unchecked(self.disabled_entities))
213+
..TableRow::new(NonMaxU32::new_unchecked(self.entity_count()))
214+
}
200215
}
201216

202217
/// Get the capacity of this table, in entities.
@@ -206,6 +221,39 @@ impl Table {
206221
self.entities.capacity()
207222
}
208223

224+
/// 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`.
225+
///
226+
/// # Safety
227+
/// `row` must be in-bounds (`row.as_usize()` < `self.len()`) and not disabled.
228+
pub(crate) unsafe fn swap_disable_unchecked(
229+
&mut self,
230+
row: TableRow,
231+
) -> Option<((Entity, TableRow), (Entity, TableRow))> {
232+
debug_assert!(row.index_u32() < self.entity_count());
233+
debug_assert!(row.index_u32() >= self.disabled_entities);
234+
235+
if row.index_u32() != 0 {
236+
// SAFETY: `self.disabled_entities` is always less than `u32::MAX`,
237+
// as guaranteed by `allocate`.
238+
let other = TableRow::new(unsafe { NonMaxU32::new_unchecked(self.disabled_entities) });
239+
240+
for col in self.columns.values_mut() {
241+
col.swap_unchecked(row, other);
242+
}
243+
244+
self.entities.swap(row.index(), other.index());
245+
self.disabled_entities += 1;
246+
247+
Some((
248+
(*self.entities.get_unchecked(other.index()), other),
249+
(*self.entities.get_unchecked(row.index()), row),
250+
))
251+
} else {
252+
self.disabled_entities += 1;
253+
None
254+
}
255+
}
256+
209257
/// Removes the entity at the given row and returns the entity swapped in to replace it (if an
210258
/// entity was swapped in)
211259
///

crates/bevy_ecs/src/storage/thin_array_ptr.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,24 @@ impl<T> ThinArrayPtr<T> {
170170
}
171171
}
172172

173+
/// Perform a [`swap`](https://doc.rust-lang.org/std/primitive.slice.html#method.swap).
174+
///
175+
/// # Safety
176+
/// - `a` must be safe to access (within the bounds of the length of the array).
177+
/// - `b` must be safe to access (within the bounds of the length of the array).
178+
/// - `a` != `b`
179+
#[inline]
180+
pub unsafe fn swap_unchecked_nonoverlapping(&mut self, a: usize, b: usize) {
181+
#[cfg(debug_assertions)]
182+
{
183+
debug_assert!(self.capacity > a);
184+
debug_assert!(self.capacity > b);
185+
debug_assert_ne!(a, b);
186+
}
187+
let base_ptr = self.data.as_ptr();
188+
ptr::copy_nonoverlapping(base_ptr.add(a), base_ptr.add(b), 1);
189+
}
190+
173191
/// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and return the removed value.
174192
///
175193
/// # Safety

0 commit comments

Comments
 (0)