gecs/archetype/
slot.rs

1use std::mem::MaybeUninit;
2
3use crate::index::{DataIndex, MAX_DATA_CAPACITY, MAX_DATA_INDEX};
4use crate::util::{num_assert_leq, num_assert_lt};
5use crate::version::VersionSlot;
6
7// We use the highest order bit to mark which indices are free list.
8// This is necessary because we need to catch if a bad entity handle
9// (say, from a different ECS world) tries to access a freed slot and
10// treat it as a live data slot, which could cause OOB memory access.
11const FREE_BIT: u32 = 1 << 31;
12
13// This is a reserved index marking the end of the free list. It should
14// always be bigger than the maximum index we can store in an entity/slot.
15// This index has the FREE_BIT baked into it.
16const FREE_LIST_END: u32 = (FREE_BIT - 1) | FREE_BIT;
17
18#[inline(always)]
19pub(crate) fn populate_free_list(
20    free_list_start: DataIndex, // Index in the full slot array of where to begin
21    slots: &mut [MaybeUninit<Slot>], // Complete slot array, including old slots
22) -> SlotIndex {
23    // We need to make sure that MAX_DATA_CAPACITY wouldn't overflow a u32.
24    num_assert_leq!(MAX_DATA_CAPACITY as usize, u32::MAX as usize);
25    // Make sure we aren't trying to populate more slots than we could store.
26    if slots.len() > MAX_DATA_CAPACITY as usize {
27        panic!("capacity may not exceed {}", MAX_DATA_CAPACITY);
28    }
29
30    if slots.len() > 0 {
31        let start_index = free_list_start.get() as usize;
32        let last_index = slots.len() - 1;
33
34        debug_assert!(start_index <= slots.len());
35
36        for i in start_index..(slots.len() - 1) {
37            unsafe {
38                // SAFETY: We know i is less than MAX_DATA_CAPACITY and won't
39                // overflow because we only go up to slots.len() - 1, and here
40                // we also know that slots.len() <= MAX_DATA_CAPACITY <= u32::MAX.
41                let index = DataIndex::new_unchecked(i.wrapping_add(1) as u32);
42                let slot = Slot::new_free(SlotIndex::new_free(index));
43
44                // SAFETY: We know that i < slots.len() and is safe to write to.
45                slots.get_unchecked_mut(i).write(slot);
46            }
47        }
48
49        // Set the last slot to point off the end of the free list.
50        let last_slot = Slot::new_free(SlotIndex::new_free_end());
51        // SAFETY: We know that last_index is valid and less than slots.len().
52        unsafe { slots.get_unchecked_mut(last_index).write(last_slot) };
53
54        // Point the free list head to the front of the list.
55        SlotIndex::new_free(free_list_start)
56    } else {
57        // Otherwise, we have nothing, so point the free list head to the end.
58        SlotIndex::new_free_end()
59    }
60}
61
62/// The data index stored in a slot.
63///
64/// This can point to entity data, or be a member of the slot free list.
65#[derive(Clone, Copy)]
66pub(crate) struct SlotIndex(u32);
67
68impl SlotIndex {
69    /// Creates a new SlotIndex at the end of the free list.
70    #[inline(always)]
71    pub(crate) fn new_free_end() -> Self {
72        Self(FREE_LIST_END)
73    }
74
75    /// Creates a new SlotIndex pointing to another slot in the free list.
76    pub(crate) fn new_free(next_free: DataIndex) -> Self {
77        Self(FREE_BIT | next_free.get())
78    }
79
80    /// Returns true if this slot is freed.
81    #[inline(always)]
82    pub(crate) fn is_free(&self) -> bool {
83        (FREE_BIT & self.0) != 0
84    }
85
86    /// Returns true if this is points to the end of the free list.
87    #[inline(always)]
88    pub(crate) fn is_free_list_end(&self) -> bool {
89        self.0 == FREE_LIST_END
90    }
91
92    /// Returns the data index this slot points to.
93    /// Will be `None` if the index is a free list index.
94    #[inline(always)]
95    pub(crate) fn get_data(&self) -> Option<DataIndex> {
96        debug_assert!(self.is_free() == false);
97        DataIndex::new_u32(self.0)
98    }
99
100    /// Returns the free list index this slot points to.
101    /// Will be `None` if the index is the free list end.
102    #[inline(always)]
103    pub(crate) fn get_next_free(&self) -> Option<DataIndex> {
104        // This only works if (!FREE_BIT & FREE_LIST_END) is too big to fit in a
105        // DataIndex. We test this in verify_free_list_end_is_invalid_data_index.
106
107        debug_assert!(self.is_free());
108        DataIndex::new_u32(!FREE_BIT & self.0)
109    }
110
111    /// Assigns a slot to some non-free data index.
112    /// This may be a reassignment of an already live slot.
113    #[inline(always)]
114    pub(crate) fn assign_data(&mut self, index_data: DataIndex) {
115        self.0 = index_data.get();
116    }
117}
118
119#[derive(Clone, Copy)]
120pub(crate) struct Slot {
121    index: SlotIndex,
122    version: VersionSlot,
123}
124
125impl Slot {
126    #[inline(always)]
127    pub(crate) fn new_free(next_free: SlotIndex) -> Self {
128        // Make sure that there is room in the index for the free bit.
129        num_assert_lt!(MAX_DATA_INDEX as usize, FREE_BIT as usize);
130
131        Self {
132            index: next_free,
133            version: VersionSlot::start(),
134        }
135    }
136
137    /// Returns this slot's index. May point to data or a free list entry.
138    #[inline(always)]
139    pub(crate) fn index(&self) -> SlotIndex {
140        self.index
141    }
142
143    /// Returns true if this slot is freed.
144    #[inline(always)]
145    pub(crate) fn is_free(&self) -> bool {
146        self.index.is_free()
147    }
148
149    /// Get the slot's generational version.
150    #[inline(always)]
151    pub(crate) fn version(&self) -> VersionSlot {
152        self.version
153    }
154
155    /// Assigns a slot to some data. This does not increment the version.
156    #[inline(always)]
157    pub(crate) fn assign(&mut self, index_data: DataIndex) {
158        self.index.assign_data(index_data);
159
160        // NOTE: We increment the version on release, not assignment.
161    }
162
163    /// Releases a slot and increments its version, invalidating all handles.
164    /// Returns an `EcsError::VersionOverflow` if the version increment overflows.
165    #[inline(always)]
166    pub(crate) fn release(&mut self, index_next_free: SlotIndex) {
167        debug_assert!(self.is_free() == false);
168        self.index = index_next_free;
169        self.version = self.version.next();
170    }
171}
172
173// Need to enforce this invariant here just in case.
174// If this isn't true, then we can't trust the FREE_LIST_END value.
175#[test]
176fn verify_free_list_end_is_invalid_data_index() {
177    assert!(DataIndex::new_u32(!FREE_BIT & FREE_LIST_END).is_none());
178}