flax/fetch/
transform.rs

1use crate::{
2    archetype::ChangeKind,
3    component::ComponentValue,
4    filter::{ChangeFilter, Filtered, NoEntities, Union},
5    Component, EntityIds, FetchExt, Mutable,
6};
7
8/// Allows transforming a fetch into another.
9///
10/// For example transforming a tuple or struct fetch into a modified filtering fetch.
11/// The generic signifies a marker to use for transforming
12pub trait TransformFetch<Method> {
13    /// The transformed type.
14    ///
15    /// May of may not have the same `Item`
16    type Output;
17    /// Transform the fetch using the provided method
18    fn transform_fetch(self, method: Method) -> Self::Output;
19}
20
21impl<T: ComponentValue> TransformFetch<Modified> for Component<T> {
22    type Output = ChangeFilter<T>;
23    fn transform_fetch(self, _: Modified) -> Self::Output {
24        self.into_change_filter(ChangeKind::Modified)
25    }
26}
27
28impl<T: ComponentValue> TransformFetch<Added> for Component<T> {
29    type Output = ChangeFilter<T>;
30    fn transform_fetch(self, _: Added) -> Self::Output {
31        self.into_change_filter(ChangeKind::Added)
32    }
33}
34
35impl<T: ComponentValue> TransformFetch<Modified> for Mutable<T> {
36    type Output = Filtered<Self, NoEntities>;
37    fn transform_fetch(self, _: Modified) -> Self::Output {
38        self.filtered(NoEntities)
39    }
40}
41
42impl<T: ComponentValue> TransformFetch<Added> for Mutable<T> {
43    type Output = Filtered<Self, NoEntities>;
44    fn transform_fetch(self, _: Added) -> Self::Output {
45        self.filtered(NoEntities)
46    }
47}
48
49impl TransformFetch<Modified> for EntityIds {
50    type Output = Filtered<Self, NoEntities>;
51    fn transform_fetch(self, _: Modified) -> Self::Output {
52        self.filtered(NoEntities)
53    }
54}
55
56impl TransformFetch<Added> for EntityIds {
57    type Output = Filtered<Self, NoEntities>;
58    fn transform_fetch(self, _: Added) -> Self::Output {
59        self.filtered(NoEntities)
60    }
61}
62
63/// Marker for a fetch which has been transformed to filter modified items.
64#[derive(Debug, Clone, Copy)]
65pub struct Modified;
66
67/// Marker for a fetch which has been transformed to filter inserted items.
68#[derive(Debug, Clone, Copy)]
69pub struct Added;
70
71macro_rules! tuple_impl {
72    ($($idx: tt => $ty: ident),*) => {
73        impl<$($ty: TransformFetch<Modified>,)*> TransformFetch<Modified> for ($($ty,)*) {
74            type Output = Union<($($ty::Output,)*)>;
75            fn transform_fetch(self, method: Modified) -> Self::Output {
76                Union(($(self.$idx.transform_fetch(method),)*))
77            }
78        }
79
80        impl<$($ty: TransformFetch<Added>,)*> TransformFetch<Added> for ($($ty,)*) {
81            type Output = Union<($($ty::Output,)*)>;
82            fn transform_fetch(self, method: Added) -> Self::Output {
83                Union(($(self.$idx.transform_fetch(method),)*))
84            }
85        }
86    };
87}
88
89tuple_impl! { 0 => A }
90tuple_impl! { 0 => A, 1 => B }
91tuple_impl! { 0 => A, 1 => B, 2 => C }
92tuple_impl! { 0 => A, 1 => B, 2 => C, 3 => D }
93tuple_impl! { 0 => A, 1 => B, 2 => C, 3 => D, 4 => E }
94tuple_impl! { 0 => A, 1 => B, 2 => C, 3 => D, 4 => E, 5 => F }
95tuple_impl! { 0 => A, 1 => B, 2 => C, 3 => D, 4 => E, 5 => F, 6 => H }
96
97#[cfg(test)]
98mod tests {
99
100    use alloc::string::{String, ToString};
101    use itertools::Itertools;
102
103    use crate::{component, entity_ids, CommandBuffer, Entity, FetchExt, Query, World};
104
105    #[test]
106    fn query_modified() {
107        component! {
108            a: i32,
109            b: String,
110            other: (),
111        }
112
113        let mut world = World::new();
114
115        let id1 = Entity::builder()
116            .set(a(), 0)
117            .set(b(), "Hello".into())
118            .spawn(&mut world);
119
120        let id2 = Entity::builder()
121            .set(a(), 1)
122            .set(b(), "World".into())
123            .spawn(&mut world);
124
125        let id3 = Entity::builder()
126            // .set(a(), 0)
127            .set(b(), "There".into())
128            .spawn(&mut world);
129
130        // Force to a different archetype
131        let id4 = Entity::builder()
132            .set(a(), 2)
133            .set(b(), "!".into())
134            .tag(other())
135            .spawn(&mut world);
136
137        let mut query = Query::new((entity_ids(), (a(), b(), other().as_mut().opt()).modified()));
138
139        assert_eq!(
140            query.borrow(&world).iter().collect_vec(),
141            [
142                (id1, (&0, &"Hello".to_string(), None)),
143                (id2, (&1, &"World".to_string(), None)),
144                (id4, (&2, &"!".to_string(), Some(&mut ())))
145            ]
146        );
147
148        assert_eq!(query.borrow(&world).iter().collect_vec(), []);
149
150        // Get mut *without* a mut deref is not a change
151        assert_eq!(*world.get_mut(id2, a()).unwrap(), 1);
152
153        assert_eq!(query.borrow(&world).iter().collect_vec(), []);
154
155        *world.get_mut(id2, a()).unwrap() = 5;
156
157        assert_eq!(
158            query.borrow(&world).iter().collect_vec(),
159            [(id2, (&5, &"World".to_string(), None))]
160        );
161
162        // Adding the required component to id3 will cause it to be picked up by the query
163        let mut cmd = CommandBuffer::new();
164        cmd.set(id3, a(), -1).apply(&mut world).unwrap();
165
166        assert_eq!(
167            query.borrow(&world).iter().collect_vec(),
168            [(id3, (&-1, &"There".to_string(), None))]
169        );
170
171        cmd.set(id3, b(), ":P".into()).apply(&mut world).unwrap();
172
173        assert_eq!(
174            query.borrow(&world).iter().collect_vec(),
175            [(id3, (&-1, &":P".to_string(), None))]
176        );
177    }
178
179    #[test]
180    #[cfg(feature = "derive")]
181    fn query_modified_struct() {
182        use crate::{fetch::Cloned, Component, Fetch, Mutable, Opt};
183
184        component! {
185            a: i32,
186            b: String,
187            other: (),
188            c: f32,
189        }
190
191        #[derive(Fetch)]
192        #[fetch(item_derives = [Debug], transforms = [Modified])]
193        struct MyFetch {
194            a: Component<i32>,
195            b: Cloned<Component<String>>,
196            c: Mutable<f32>,
197            other: Opt<Mutable<()>>,
198        }
199
200        let mut world = World::new();
201
202        let id1 = Entity::builder()
203            .set(a(), 0)
204            .set(b(), "Hello".into())
205            .set_default(c())
206            .spawn(&mut world);
207
208        let id2 = Entity::builder()
209            .set(a(), 1)
210            .set(b(), "World".into())
211            .set_default(c())
212            .spawn(&mut world);
213
214        let id3 = Entity::builder()
215            // .set(a(), 0)
216            .set(b(), "There".into())
217            .set_default(c())
218            .spawn(&mut world);
219
220        // Force to a different archetype
221        let id4 = Entity::builder()
222            .set(a(), 2)
223            .set(b(), "!".into())
224            .set_default(c())
225            .tag(other())
226            .spawn(&mut world);
227
228        let query = MyFetch {
229            a: a(),
230            b: b().cloned(),
231            c: c().as_mut(),
232            other: other().as_mut().opt(),
233        }
234        .modified()
235        .map(|v| (*v.a, v.b));
236
237        let mut query = Query::new((entity_ids(), query));
238
239        assert_eq!(
240            query.collect_vec(&world),
241            [
242                (id1, (0, "Hello".to_string())),
243                (id2, (1, "World".to_string())),
244                (id4, (2, "!".to_string()))
245            ]
246        );
247
248        assert_eq!(query.collect_vec(&world), []);
249
250        // Get mut *without* a mut deref is not a change
251        assert_eq!(*world.get_mut(id2, a()).unwrap(), 1);
252
253        assert_eq!(query.collect_vec(&world), []);
254
255        *world.get_mut(id2, a()).unwrap() = 5;
256
257        assert_eq!(query.collect_vec(&world), [(id2, (5, "World".to_string()))]);
258
259        // Adding the required component to id3 will cause it to be picked up by the query
260        let mut cmd = CommandBuffer::new();
261        cmd.set(id3, a(), -1).apply(&mut world).unwrap();
262
263        assert_eq!(
264            query.collect_vec(&world),
265            [(id3, (-1, "There".to_string()))]
266        );
267
268        cmd.set(id3, b(), ":P".into()).apply(&mut world).unwrap();
269
270        assert_eq!(query.collect_vec(&world), [(id3, (-1, ":P".to_string()))]);
271    }
272
273    #[test]
274    #[cfg(feature = "derive")]
275    fn query_inserted_struct() {
276        use crate::{fetch::Cloned, Component, EntityIds, Fetch, Mutable};
277
278        #[derive(Debug)]
279        struct Custom;
280
281        component! {
282            a: i32,
283            b: String,
284            c: Custom,
285            other: (),
286        }
287
288        #[derive(Fetch)]
289        #[fetch(item_derives = [Debug], transforms = [Modified, Added])]
290        struct MyFetch {
291            #[fetch(ignore)]
292            id: EntityIds,
293
294            a: Component<i32>,
295            b: Cloned<Component<String>>,
296            #[fetch(ignore)]
297            c: Mutable<Custom>,
298        }
299
300        let mut world = World::new();
301
302        let id1 = Entity::builder()
303            .set(a(), 0)
304            .set(b(), "Hello".into())
305            .set(c(), Custom)
306            .spawn(&mut world);
307
308        let id2 = Entity::builder()
309            .set(a(), 1)
310            .set(b(), "World".into())
311            .set(c(), Custom)
312            .spawn(&mut world);
313
314        let id3 = Entity::builder()
315            // .set(a(), 0)
316            .set(b(), "There".into())
317            .set(c(), Custom)
318            .spawn(&mut world);
319
320        // Force to a different archetype
321        let id4 = Entity::builder()
322            .set(a(), 2)
323            .set(b(), "!".into())
324            .set(c(), Custom)
325            .tag(other())
326            .spawn(&mut world);
327
328        let query = MyFetch {
329            id: entity_ids(),
330            a: a(),
331            b: b().cloned(),
332            c: c().as_mut(),
333        }
334        .added()
335        .map(|v| (v.id, *v.a, v.b));
336
337        let mut query = Query::new(query);
338
339        assert_eq!(
340            query.collect_vec(&world),
341            [
342                (id1, 0, "Hello".to_string()),
343                (id2, 1, "World".to_string()),
344                (id4, 2, "!".to_string())
345            ]
346        );
347
348        assert_eq!(query.collect_vec(&world), []);
349
350        assert_eq!(query.collect_vec(&world), []);
351
352        world.remove(id2, a()).unwrap();
353
354        assert_eq!(query.collect_vec(&world), []);
355
356        world.set(id2, a(), 5).unwrap();
357
358        assert_eq!(query.collect_vec(&world), [(id2, 5, "World".to_string())]);
359
360        // Adding the required component to id3 will cause it to be picked up by the query
361        let mut cmd = CommandBuffer::new();
362        cmd.set(id3, a(), -1).apply(&mut world).unwrap();
363
364        assert_eq!(query.collect_vec(&world), [(id3, -1, "There".to_string())]);
365    }
366
367    #[test]
368    #[cfg(feature = "derive")]
369    fn test_derive_parse() {
370        use crate::{fetch::Cloned, Component, Fetch};
371
372        #[derive(Fetch)]
373        struct MyFetch {
374            a: Component<i32>,
375            b: Cloned<Component<String>>,
376        }
377    }
378}