Skip to main content

examples/queries/
query_change_tracking_2.rs

1#![allow(unused_imports)]
2#![allow(warnings)]
3use crate::z_ignore_test_common::*;
4
5use flecs_ecs::prelude::*;
6// Queries have a builtin mechanism for tracking changes per matched table. This
7// is a cheap way of eliminating redundant work, as many entities can be skipped
8// with a single check.
9//
10// This example shows how to use change tracking in combination with using
11// prefabs to store a single dirty state for multiple entities.
12
13#[derive(Debug, Component)]
14pub struct Position {
15    pub x: f32,
16    pub y: f32,
17}
18
19#[derive(Debug, Component)]
20struct Dirty {
21    value: bool,
22}
23
24fn main() {
25    let world = World::new();
26
27    // Make Dirty inheritable so that queries can match it on prefabs
28    world
29        .component::<Dirty>()
30        .add_trait::<(flecs::OnInstantiate, flecs::Inherit)>();
31
32    // Create a query that just reads a component. We'll use this query for
33    // change tracking. Change tracking for a query is automatically enabled
34    // when query::changed() is called.
35    // Each query has its own private dirty state which is reset only when the
36    // query is iterated.
37
38    let query_read = world
39        .query::<&Position>()
40        .detect_changes()
41        .set_cached()
42        .build();
43
44    // Create a query that writes the component based on a Dirty state.
45    let query_write = world
46        .query::<(&Dirty, &mut Position)>()
47        .term_at(0)
48        .up_id(id::<flecs::IsA>()) // Only match Dirty from prefab
49        .build();
50
51    // Create two prefabs with a Dirty component. We can use this to share a
52    // single Dirty value for all entities in a table.
53    let prefab_dirty_false = world
54        .prefab_named("prefab_dirty_false")
55        .set(Dirty { value: false });
56
57    let prefab_dirty_true = world
58        .prefab_named("prefab_dirty_true")
59        .set(Dirty { value: true });
60
61    // Create instances of p1 and p2. Because the entities have different
62    // prefabs, they end up in different tables.
63    world
64        .entity_named("e1_dirty_false")
65        .is_a(prefab_dirty_false)
66        .set(Position { x: 10.0, y: 20.0 });
67
68    world
69        .entity_named("e2_dirty_false")
70        .is_a(prefab_dirty_false)
71        .set(Position { x: 30.0, y: 40.0 });
72
73    world
74        .entity_named("e3_dirty_true")
75        .is_a(prefab_dirty_true)
76        .set(Position { x: 40.0, y: 50.0 });
77
78    world
79        .entity_named("e4_dirty_true")
80        .is_a(prefab_dirty_true)
81        .set(Position { x: 50.0, y: 60.0 });
82
83    // We can use the changed() function on the query to check if any of the
84    // tables it is matched with has changed. Since this is the first time that
85    // we check this and the query is matched with the tables we just created,
86    // the function will return true.
87    println!();
88    println!("query_read.is_changed(): {}", query_read.is_changed());
89    println!();
90
91    // The changed state will remain true until we have iterated each table.
92    query_read.run(|mut iter| {
93        while iter.next() {
94            // With the it.changed() function we can check if the table we're
95            // currently iterating has changed since last iteration.
96            // Because this is the first time the query is iterated, all tables
97            // will show up as changed.
98            println!(
99                "iiter.is_changed() for table [{}]: {}",
100                iter.archetype().unwrap(),
101                iter.is_changed()
102            );
103        }
104    });
105
106    // Now that we have iterated all tables, the dirty state is reset.
107    println!();
108    println!("query_read.is_changed(): {:?}", query_read.is_changed());
109    println!();
110
111    // Iterate the write query. Because the Position term is InOut (default)
112    // iterating the query will write to the dirty state of iterated tables.
113    query_write.run(|mut it| {
114        while it.next() {
115            let dirty = it.field::<Dirty>(0);
116            let mut pos = it.field_mut::<Position>(1);
117
118            println!("iterate table [{}]", it.archetype().unwrap());
119
120            // Because we enforced that Dirty is a shared component, we can check
121            // a single value for the entire table.
122            if !dirty[0].value {
123                println!("iter.skip() for table [{}]", it.archetype().unwrap());
124
125                // If the dirty flag is false, skip the table. This way the table's
126                // dirty state is not updated by the query.
127                dirty.drop();
128                pos.drop();
129                it.skip();
130
131                continue;
132            }
133
134            // For all other tables the dirty state will be set.
135            for i in it.iter() {
136                pos[i].x += 1.0;
137                pos[i].y += 1.0;
138            }
139        }
140    });
141
142    // One of the tables has changed, so q_read.changed() will return true
143    println!();
144    println!("query_read.is_changed(): {}", query_read.is_changed());
145    println!();
146
147    // When we iterate the read query, we'll see that one table has changed.
148    query_read.run(|mut iter| {
149        while iter.next() {
150            println!(
151                "iter.is_changed() for table [{}]: {}",
152                iter.archetype().unwrap(),
153                iter.is_changed()
154            );
155        }
156    });
157    println!();
158
159    // Output:
160    //  query_read.is_changed(): true
161    //
162    //  iiter.is_changed() for table [Position, (Identifier,Name), (IsA,prefab_dirty_false)]: true
163    //  iiter.is_changed() for table [Position, (Identifier,Name), (IsA,prefab_dirty_true)]: true
164    //
165    //  query_read.is_changed(): false
166    //
167    //  iterate table [Position, (Identifier,Name), (IsA,prefab_dirty_false)]
168    //  iter.skip() for table [Position, (Identifier,Name), (IsA,prefab_dirty_false)]
169    //  iterate table [Position, (Identifier,Name), (IsA,prefab_dirty_true)]
170    //
171    //  query_read.is_changed(): true
172    //
173    //  iter.is_changed() for table [Position, (Identifier,Name), (IsA,prefab_dirty_false)]: false
174    //  iter.is_changed() for table [Position, (Identifier,Name), (IsA,prefab_dirty_true)]: true
175}
176
177#[cfg(feature = "flecs_nightly_tests")]
178#[test]
179fn test() {
180    let output_capture = OutputCapture::capture().unwrap();
181    main();
182    output_capture.test("query_change_tracking_2".to_string());
183}