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}