examples/queries/query_transitive_queries.rs
1use crate::z_ignore_test_common::*;
2
3use flecs_ecs::prelude::*;
4// Transitive relationships make it possible to tell the ECS that if an entity
5// has a relationship (R, X) and X has relationship (R, Y), the entity should be
6// treated as if it also has (R, Y). In practice this is useful for expressing
7// things like:
8//
9// Bob lives in SanFrancisco
10// San Francisco is located in the United States
11// Therefore Bob also lives in the United States.
12//
13// An example of transitivity can be seen in the component_inheritance example.
14// This example uses the builtin IsA relationship, which is transitive. This
15// example shows how to achieve similar behavior with a user-defined relationship.
16
17#[derive(Component)]
18struct LocatedIn;
19
20#[derive(Component)]
21struct Planet;
22
23#[derive(Component)]
24struct Continent;
25
26#[derive(Component)]
27struct Country;
28
29#[derive(Component)]
30struct State;
31
32#[derive(Component)]
33struct City;
34
35#[derive(Component)]
36struct Person;
37
38fn main() {
39 let world = World::new();
40
41 // Register the LocatedIn relationship as transitive
42 world.component::<LocatedIn>().add::<flecs::Transitive>();
43
44 // Populate the store with locations
45 let earth = world.entity_named("Earth").add::<Planet>();
46
47 // Continents
48 let north_america = world
49 .entity_named("NorthAmerica")
50 .add::<Continent>()
51 .add_first::<LocatedIn>(earth);
52
53 let europe = world
54 .entity_named("Europe")
55 .add::<Continent>()
56 .add_first::<LocatedIn>(earth);
57
58 // Countries
59 let united_states = world
60 .entity_named("UnitedStates")
61 .add::<Country>()
62 .add_first::<LocatedIn>(north_america);
63
64 let netherlands = world
65 .entity_named("Netherlands")
66 .add::<Country>()
67 .add_first::<LocatedIn>(europe);
68
69 // States
70 let california = world
71 .entity_named("California")
72 .add::<State>()
73 .add_first::<LocatedIn>(united_states);
74
75 let washington = world
76 .entity_named("Washington")
77 .add::<State>()
78 .add_first::<LocatedIn>(united_states);
79
80 let noord_holland = world
81 .entity_named("NoordHolland")
82 .add::<State>()
83 .add_first::<LocatedIn>(netherlands);
84
85 // Cities
86 let san_francisco = world
87 .entity_named("SanFrancisco")
88 .add::<City>()
89 .add_first::<LocatedIn>(california);
90
91 let seattle = world
92 .entity_named("Seattle")
93 .add::<City>()
94 .add_first::<LocatedIn>(washington);
95
96 let amsterdam = world
97 .entity_named("Amsterdam")
98 .add::<City>()
99 .add_first::<LocatedIn>(noord_holland);
100
101 // Inhabitants
102 world
103 .entity_named("Bob")
104 .add::<Person>()
105 .add_first::<LocatedIn>(san_francisco);
106
107 world
108 .entity_named("Alice")
109 .add::<Person>()
110 .add_first::<LocatedIn>(seattle);
111
112 world
113 .entity_named("Job")
114 .add::<Person>()
115 .add_first::<LocatedIn>(amsterdam);
116
117 // Create a query that finds the countries persons live in. Note that these
118 // have not been explicitly added to the Person entities, but because the
119 // LocatedIn is transitive, the query engine will traverse the relationship
120 // until it found something that is a country.
121 //
122 // The equivalent of this query in the DSL is:
123 // Person, (LocatedIn, $Location), Country($Location)
124
125 let query = world
126 .query::<()>()
127 .with::<&Person>()
128 .with_first_name::<&LocatedIn>("$Location")
129 .with::<&Country>()
130 .set_src_name("$Location")
131 .build();
132
133 // Lookup the index of the variable. This will let us quickly lookup its
134 // value while we're iterating.
135 let location_var = query.find_var("Location").unwrap();
136
137 // Iterate the query
138 query.each_iter(|it, index, _| {
139 println!("{} lives in {}", it.entity(index), it.get_var(location_var));
140 });
141
142 // Output:
143 // Bob lives in UnitedStates
144 // Alice lives in UnitedStates
145 // Job lives in Netherlands
146}
147
148#[cfg(feature = "flecs_nightly_tests")]
149#[test]
150fn test() {
151 let output_capture = OutputCapture::capture().unwrap();
152 main();
153 output_capture.test("query_transitive_queries".to_string());
154}