Skip to content

Commit d661430

Browse files
committed
add swap functions to table
1 parent 11059de commit d661430

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
@@ -388,7 +388,7 @@ impl BlobArray {
388388
self.get_unchecked_mut(index_to_keep).promote()
389389
}
390390

391-
/// The same as [`Self::swap_remove_unchecked`] but the two elements must non-overlapping.
391+
/// This method will swap two elements in the array.
392392
///
393393
/// # Safety
394394
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
@@ -398,11 +398,11 @@ impl BlobArray {
398398
/// 1) initialize a different value in `index_to_keep`
399399
/// 2) update the saved length of the array if `index_to_keep` was the last element.
400400
#[inline]
401-
pub unsafe fn swap_remove_unchecked_nonoverlapping(
401+
pub unsafe fn swap_unchecked_nonoverlapping(
402402
&mut self,
403403
index_to_remove: usize,
404404
index_to_keep: usize,
405-
) -> OwningPtr<'_> {
405+
) {
406406
#[cfg(debug_assertions)]
407407
{
408408
debug_assert!(self.capacity > index_to_keep);
@@ -415,6 +415,24 @@ impl BlobArray {
415415
self.get_unchecked_mut(index_to_remove).as_ptr(),
416416
self.item_layout.size(),
417417
);
418+
}
419+
420+
/// The same as [`Self::swap_remove_unchecked`] but the two elements must non-overlapping.
421+
///
422+
/// # Safety
423+
/// - `index_to_keep` must be safe to access (within the bounds of the length of the array).
424+
/// - `index_to_remove` must be safe to access (within the bounds of the length of the array).
425+
/// - `index_to_remove` != `index_to_keep`
426+
/// - The caller should address the inconsistent state of the array that has occurred after the swap, either:
427+
/// 1) initialize a different value in `index_to_keep`
428+
/// 2) update the saved length of the array if `index_to_keep` was the last element.
429+
#[inline]
430+
pub unsafe fn swap_remove_unchecked_nonoverlapping(
431+
&mut self,
432+
index_to_remove: usize,
433+
index_to_keep: usize,
434+
) -> OwningPtr<'_> {
435+
self.swap_unchecked_nonoverlapping(index_to_remove, index_to_keep);
418436
// Now the element that used to be in index `index_to_remove` is now in index `index_to_keep` (after swap)
419437
// If we are storing ZSTs than the index doesn't actually matter because the size is 0.
420438
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
@@ -36,6 +36,23 @@ impl ThinColumn {
3636
}
3737
}
3838

39+
/// Swap the two elements.
40+
///
41+
/// # Safety
42+
/// - `a.as_usize()` < `len`
43+
/// - `b.as_usize()` < `len`
44+
/// - `a` != `b`
45+
pub(crate) unsafe fn swap_unchecked(&mut self, a: TableRow, b: TableRow) {
46+
let a = a.index();
47+
let b = b.index();
48+
self.data.swap_unchecked_nonoverlapping(a, b);
49+
self.added_ticks.swap_unchecked_nonoverlapping(a, b);
50+
self.changed_ticks.swap_unchecked_nonoverlapping(a, b);
51+
self.changed_by.as_mut().map(|changed_by| {
52+
changed_by.swap_unchecked_nonoverlapping(a, b);
53+
});
54+
}
55+
3956
/// Swap-remove and drop the removed element, but the component at `row` must not be the last element.
4057
///
4158
/// # Safety

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

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use core::{
1313
alloc::Layout,
1414
cell::UnsafeCell,
1515
num::NonZeroUsize,
16-
ops::{Index, IndexMut},
16+
ops::{Index, IndexMut, Range},
1717
panic::Location,
1818
};
1919
use nonmax::NonMaxU32;
@@ -167,6 +167,7 @@ impl TableBuilder {
167167
Table {
168168
columns: self.columns.into_immutable(),
169169
entities: self.entities,
170+
disabled_entities: 0,
170171
}
171172
}
172173
}
@@ -191,6 +192,7 @@ impl TableBuilder {
191192
pub struct Table {
192193
columns: ImmutableSparseSet<ComponentId, ThinColumn>,
193194
entities: Vec<Entity>,
195+
disabled_entities: u32,
194196
}
195197

196198
struct AbortOnPanic;
@@ -206,7 +208,20 @@ impl Table {
206208
/// Fetches a read-only slice of the entities stored within the [`Table`].
207209
#[inline]
208210
pub fn entities(&self) -> &[Entity] {
209-
&self.entities
211+
&self.entities[self.disabled_entities as usize..]
212+
}
213+
214+
/// Get the valid table rows (i.e. non-disabled entities).
215+
#[inline]
216+
pub fn table_rows(&self) -> Range<TableRow> {
217+
// SAFETY:
218+
// - No entity row may be in more than one table row at once, so there are no duplicates,
219+
// and there can not be an entity row of u32::MAX. Therefore, this can not be max either.
220+
// - self.disabled_entities <= self.entity_count()
221+
unsafe {
222+
TableRow::new(NonMaxU32::new_unchecked(self.disabled_entities))
223+
..TableRow::new(NonMaxU32::new_unchecked(self.entity_count()))
224+
}
210225
}
211226

212227
/// Get the capacity of this table, in entities.
@@ -216,6 +231,39 @@ impl Table {
216231
self.entities.capacity()
217232
}
218233

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

crates/bevy_ecs/src/storage/thin_array_ptr.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,24 @@ impl<T> ThinArrayPtr<T> {
181181
}
182182
}
183183

184+
/// Perform a [`swap`](https://doc.rust-lang.org/std/primitive.slice.html#method.swap).
185+
///
186+
/// # Safety
187+
/// - `a` must be safe to access (within the bounds of the length of the array).
188+
/// - `b` must be safe to access (within the bounds of the length of the array).
189+
/// - `a` != `b`
190+
#[inline]
191+
pub unsafe fn swap_unchecked_nonoverlapping(&mut self, a: usize, b: usize) {
192+
#[cfg(debug_assertions)]
193+
{
194+
debug_assert!(self.capacity > a);
195+
debug_assert!(self.capacity > b);
196+
debug_assert_ne!(a, b);
197+
}
198+
let base_ptr = self.data.as_ptr();
199+
ptr::copy_nonoverlapping(base_ptr.add(a), base_ptr.add(b), 1);
200+
}
201+
184202
/// Perform a [`swap-remove`](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.swap_remove) and return the removed value.
185203
///
186204
/// # Safety

0 commit comments

Comments
 (0)