examples/queries/query_change_tracking_1.rs
1#![allow(unused_imports)]
2#![allow(warnings)]
3use crate::z_ignore_test_common::*;
4
5use flecs_ecs::prelude::*;
6
7// Queries have a builtin mechanism for tracking changes per matched table. This
8// is a cheap way of eliminating redundant work, as many entities can be skipped
9// with a single check.
10//
11// This example shows how to use change tracking to skip tables that have not
12// changed. This is useful when you have a query that writes to a component, but
13// only want to write to tables that have changed.
14
15#[derive(Debug, Component)]
16pub struct Position {
17 pub x: f32,
18 pub y: f32,
19}
20
21#[derive(Debug, Component)]
22pub struct WorldPosition {
23 pub x: f32,
24 pub y: f32,
25}
26
27// this is to make sure independent entity is in a different archetype
28#[derive(Component)]
29pub struct DummyTag;
30
31// This system updates the WorldPosition component based on the Position and
32// the optional parent's Position.
33fn update_transforms(mut it: TableIter<true, ()>) {
34 while it.next() {
35 // With the it.changed() function we can check if the table we're
36 // currently iterating has changed since last iteration. If the table
37 // has not changed, we can skip it.
38 if !it.is_changed() {
39 println!("skip archetype: {:?}", it.archetype().unwrap());
40 it.skip();
41 continue;
42 }
43
44 println!("non-skip archetype: {:?}", it.archetype().unwrap());
45
46 let pos = it.field::<Position>(0);
47 let parent_pos = it.get_field::<Position>(1); // Optional
48 let mut world_pos = it.field_mut::<WorldPosition>(2);
49
50 match parent_pos {
51 None => {
52 for i in it.iter() {
53 world_pos[i].x = pos[i].x;
54 world_pos[i].y = pos[i].y;
55 }
56 }
57 Some(parent_pos) => {
58 for i in it.iter() {
59 world_pos[i].x = pos[i].x + parent_pos[i].x;
60 world_pos[i].y = pos[i].y + parent_pos[i].y;
61 }
62 }
63 }
64 }
65}
66
67fn print_world_positions(entity: EntityView, world_pos: &WorldPosition) {
68 println!("{}: {:?}", entity.name(), world_pos);
69}
70
71fn main() {
72 let world = World::new();
73
74 let transform_query = world
75 .query_named::<(&Position, Option<&Position>, &mut WorldPosition)>("update_transforms")
76 .term_at(1)
77 .parent()
78 .build();
79
80 let print_world_pos = world.query::<&WorldPosition>().build();
81
82 // create entities with and without parent
83 let parent = world
84 .entity_named("parent")
85 .set(Position { x: 100.0, y: 200.0 })
86 .set(WorldPosition { x: 0.0, y: 0.0 });
87
88 let child = world
89 .entity_named("child")
90 .set(Position { x: 10.0, y: 20.0 })
91 .set(WorldPosition { x: 0.0, y: 0.0 })
92 .child_of(parent);
93
94 let independent = world
95 .entity_named("independent")
96 .set(Position { x: 50.0, y: 30.0 })
97 .set(WorldPosition { x: 0.0, y: 0.0 })
98 // this is to make sure independent entity is in a different archetype
99 .add(DummyTag);
100
101 // Since this is the first time the query is iterated, all tables
102 // will show up as changed and not skipped
103 transform_query.run(update_transforms);
104
105 // Output:
106 // non-skip archetype: Position, WorldPosition
107 // non-skip archetype: Position, WorldPosition, (ChildOf,parent)
108 // non-skip archetype: Position, WorldPosition, DummyTag
109
110 // Set the child position to a new value. This will change the table that
111 // the child entity is in, which will cause the query to return true when
112 // we call changed().
113 child.set(Position { x: 110.0, y: 210.0 });
114
115 // When we iterate the query, we'll see that one table has changed and thus not skipped
116 transform_query.run(update_transforms);
117
118 println!();
119
120 // Output:
121 // skip archetype: Position, WorldPosition
122 // non-skip archetype: Position, WorldPosition, (ChildOf,parent)
123 // skip archetype: Position, WorldPosition, DummyTag
124
125 print_world_pos.each_entity(print_world_positions);
126
127 // Output:
128 // parent: WorldPosition { x: 110.0, y: 210.0 }
129 // independent: WorldPosition { x: 50.0, y: 30.0 }
130 // child: WorldPosition { x: 120.0, y: 230.0 }
131
132 // now the same, but for independent entity, which is in a different archetype.
133 independent.set(Position { x: 60.0, y: 40.0 });
134
135 transform_query.run(update_transforms);
136
137 println!();
138
139 // Output:
140 // skip archetype: Position, WorldPosition
141 // skip archetype: Position, WorldPosition, (ChildOf,parent)
142 // non-skip archetype: Position, WorldPosition, DummyTag
143
144 print_world_pos.each_entity(print_world_positions);
145
146 // Output:
147 // parent: WorldPosition { x: 110.0, y: 210.0 }
148 // independent: WorldPosition { x: 60.0, y: 40.0 }
149 // child: WorldPosition { x: 120.0, y: 230.0 }
150}
151
152#[cfg(feature = "flecs_nightly_tests")]
153#[test]
154fn test() {
155 let output_capture = OutputCapture::capture().unwrap();
156 main();
157 output_capture.test("query_change_tracking_1".to_string());
158}