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}