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}