Skip to main content

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}