edict/query/fetch.rs
1use crate::archetype::chunk_idx;
2
3/// This type can be used in [`Fetch`] implementation
4/// to assert that [`Fetch`] API is used correctly.
5/// Should be used only in debug mode.
6#[derive(Clone, Copy)]
7pub struct VerifyFetch {
8 /// panics on any call if true
9 dangling: bool,
10
11 /// contains index of the last `visit_chunk` call and whether it returns true.
12 visit_chunk: Option<(u32, bool)>,
13
14 /// contains `Some` after `touch_chunk` is called.
15 /// cleared in `visit_chunk` call.
16 touch_chunk: Option<u32>,
17
18 /// Contains index of the `skip_ite,` call and whether it returns true.
19 visit_item: Option<(u32, bool)>,
20}
21
22impl VerifyFetch {
23 /// Returns new `VerifyFetch` instance flagged as dangling.
24 /// Any method call will panic. Dangling fetches must not be used.
25 #[inline(always)]
26 pub fn dangling() -> Self {
27 VerifyFetch {
28 dangling: true,
29 visit_chunk: None,
30 touch_chunk: None,
31 visit_item: None,
32 }
33 }
34
35 /// Returns new `VerifyFetch` instance.
36 #[inline(always)]
37 pub fn new() -> Self {
38 VerifyFetch {
39 dangling: false,
40 visit_chunk: None,
41 touch_chunk: None,
42 visit_item: None,
43 }
44 }
45
46 /// This method must be called in [`Fetch::visit_chunk`] implementation.
47 /// `visiting` must be equal to the value returned by the [`Fetch::visit_chunk`] method.
48 ///
49 /// # Panics
50 ///
51 /// If dangling.
52 #[inline(always)]
53 pub fn visit_chunk(&mut self, chunk_idx: u32, visiting: bool) {
54 if self.dangling {
55 panic!("FetchVerify: skip_chunk called for dangling fetch");
56 }
57 self.visit_item = None;
58 self.touch_chunk = None;
59 self.visit_chunk = Some((chunk_idx, visiting));
60 }
61
62 /// This method must be called in [`Fetch::touch_chunk`] implementation.
63 ///
64 /// # Panics
65 ///
66 /// If dangling.
67 /// If `visit_chunk` was not called with `visiting = true` for the same chunk just before this call.
68 #[inline(always)]
69 pub fn touch_chunk(&mut self, chunk_idx: u32) {
70 if self.dangling {
71 panic!("FetchVerify: visit_chunk called for dangling fetch");
72 }
73 match self.visit_chunk {
74 None => {
75 panic!("FetchVerify: visit_chunk called without visit_chunk");
76 }
77 Some((visit_chunk_idx, visiting)) => {
78 if chunk_idx != visit_chunk_idx {
79 panic!("FetchVerify: visit_chunk called with chunk_idx {}, but last call to `visit_chunk` was with chunk_idx {}", chunk_idx, visit_chunk_idx);
80 }
81 if !visiting {
82 panic!("FetchVerify: visit_chunk called with chunk_idx {}, but `visit_chunk` returned true for this chunk index", chunk_idx);
83 }
84 self.touch_chunk = Some(visit_chunk_idx);
85 }
86 }
87 }
88
89 /// This method must be called in [`Fetch::visit_item`] implementation.
90 ///
91 /// # Panics
92 ///
93 /// If dangling.
94 /// if `touch_chunk` was not called for the corresponding chunk before this call.
95 #[inline(always)]
96 pub fn visit_item(&mut self, idx: u32, visiting: bool) {
97 if self.dangling {
98 panic!("FetchVerify: visit_item called for dangling fetch");
99 }
100 match self.visit_chunk {
101 None => {
102 panic!("FetchVerify: visit_item called without visit_chunk");
103 }
104 Some((visit_chunk_idx, visiting_chunk)) => {
105 if chunk_idx(idx) != visit_chunk_idx {
106 panic!("FetchVerify: visit_item called with idx {} that correspond to chunk {}, but last call to `touch_chunk` was with chunk_idx {}", idx, chunk_idx(idx), visit_chunk_idx);
107 }
108 if !visiting_chunk {
109 panic!("FetchVerify: visit_item called with idx {}, but `visit_chunk` returned false for this idx", idx);
110 }
111 self.visit_item = Some((idx, visiting));
112 }
113 }
114 }
115
116 /// This method must be called in [`Fetch::get_item`] implementation.
117 ///
118 /// # Panics
119 ///
120 /// If dangling.
121 /// If `visit_item` was not called with `visiting = true` for the same item just before this call.
122 #[inline(always)]
123 pub fn get_item(&mut self, idx: u32) {
124 if self.dangling {
125 panic!("FetchVerify: get_item called for dangling fetch");
126 }
127 match self.visit_item {
128 None => {
129 panic!("FetchVerify: get_item called without visit_item");
130 }
131 Some((valid_idx, visiting)) => {
132 if idx != valid_idx {
133 panic!("FetchVerify: get_item called with idx {}, but last call to `visit_item` was with idx {}", idx, valid_idx);
134 }
135 if !visiting {
136 panic!("FetchVerify: get_item called with idx {}, but `visit_item` returned true for this idx", idx);
137 }
138 self.visit_item = None;
139 }
140 }
141 }
142}
143
144/// Trait implemented for `Query::Fetch` associated types.
145///
146/// # Safety
147///
148/// Implementation of unsafe methods must follow safety rules.
149///
150/// The order of method calls must be one of the following in call cases
151///
152/// 1:
153/// ```ignore
154/// Fetch::dangling();
155/// ```
156///
157/// 2:
158/// ```ignore
159/// let fetch = Query::fetch(...)?;
160///
161/// for chunk_idx in 0..archetype.chunks_count() {
162/// if fetch.skip_chunk(chunk_idx) && another_chunk_condition {
163/// fetch.visit_chunk(chunk_idx);
164/// for idx in 0..CHUNK_LEN_USIZE {
165/// if fetch.skip_item(idx) && another_item_condition {
166/// fetch.get_item(CHUNK_LEN_USIZE * chunk_idx + idx);
167/// }
168/// }
169/// }
170/// }
171/// ```
172pub unsafe trait Fetch<'a> {
173 /// Item type this fetch type yields.
174 type Item: 'a;
175
176 /// Returns dummy `Fetch` value that must never be used.
177 ///
178 /// # Safety
179 ///
180 /// Implementation may return any initialized value.
181 /// Even if calling any other method with it triggers an UB.
182 ///
183 /// Implementations are encouraged to do checks in debug mode
184 /// if possible with minor performance penalty.
185 #[must_use]
186 fn dangling() -> Self;
187
188 /// Checks if chunk with specified index must be visited or skipped.
189 ///
190 /// # Safety
191 ///
192 /// Chunk index must in range `0..=chunk_count`,
193 /// where `chunk_count` is the number of chunks in the archetype
194 /// from which query produced this instance.
195 #[inline(always)]
196 #[must_use]
197 unsafe fn visit_chunk(&mut self, chunk_idx: u32) -> bool {
198 let _ = chunk_idx;
199 true
200 }
201
202 /// Checks if item with specified index must be visited or skipped.
203 ///
204 /// # Safety
205 ///
206 /// Entity index must in range `0..=entity_count`,
207 /// where `entity_count` is the number of entities in the archetype
208 /// from which query produced this instance.
209 #[inline(always)]
210 #[must_use]
211 unsafe fn visit_item(&mut self, idx: u32) -> bool {
212 let _ = idx;
213 true
214 }
215
216 /// Notifies this fetch that at least one item in the chunk will be accessed.
217 /// This method is called for each chunk in the archetype that is not skipped.
218 ///
219 /// # Safety
220 ///
221 /// Chunk index must in range `0..=chunk_count`,
222 /// where `chunk_count` is the number of chunks in the archetype
223 /// from which query produced this instance.
224 ///
225 /// `visit_chunk` must have been called just before this method.
226 /// If `visit_chunk` returned `false`, this method must not be called.
227 #[inline(always)]
228 unsafe fn touch_chunk(&mut self, chunk_idx: u32) {
229 let _ = chunk_idx;
230 }
231
232 /// Returns fetched item at specified index.
233 ///
234 /// # Safety
235 ///
236 /// Entity index must in range `0..=entity_count`,
237 /// where `entity_count` is the number of entities in the archetype
238 /// from which query produced this instance.
239 ///
240 /// `skip_item` must have been called just before this method.
241 /// If `skip_item` returned `false`, this method must not be called.
242 ///
243 /// `visit_chunk` must have been called before this method
244 /// with chunk index that corresponds to the entity index.
245 #[must_use]
246 unsafe fn get_item(&mut self, idx: u32) -> Self::Item;
247}
248
249/// Fetch type for `Query` implementations
250/// where nothing needs to be fetched.
251#[repr(transparent)]
252#[derive(Clone, Copy)]
253pub struct UnitFetch {
254 #[cfg(debug_assertions)]
255 verify: VerifyFetch,
256}
257
258impl UnitFetch {
259 /// Returns new [`UnitFetch`] instance.
260 pub fn new() -> Self {
261 UnitFetch {
262 #[cfg(debug_assertions)]
263 verify: VerifyFetch::new(),
264 }
265 }
266}
267
268unsafe impl<'a> Fetch<'a> for UnitFetch {
269 type Item = ();
270
271 #[inline(always)]
272 fn dangling() -> Self {
273 UnitFetch {
274 #[cfg(debug_assertions)]
275 verify: VerifyFetch::dangling(),
276 }
277 }
278
279 #[inline(always)]
280 unsafe fn visit_chunk(&mut self, chunk_idx: u32) -> bool {
281 let _ = chunk_idx;
282 #[cfg(debug_assertions)]
283 self.verify.visit_chunk(chunk_idx, true);
284 true
285 }
286
287 #[inline(always)]
288 unsafe fn touch_chunk(&mut self, chunk_idx: u32) {
289 let _ = chunk_idx;
290 #[cfg(debug_assertions)]
291 self.verify.touch_chunk(chunk_idx)
292 }
293
294 #[inline(always)]
295 unsafe fn visit_item(&mut self, idx: u32) -> bool {
296 let _ = idx;
297 #[cfg(debug_assertions)]
298 self.verify.visit_item(idx, true);
299 true
300 }
301
302 #[inline(always)]
303 unsafe fn get_item(&mut self, idx: u32) -> () {
304 let _ = idx;
305 #[cfg(debug_assertions)]
306 self.verify.get_item(idx)
307 }
308}