mixed_usage/
mixed_usage.rs1use bevy_ecs::prelude::*;
13use bevy_entity_ptr::{BoundEntity, EntityHandle, EntityPtr, WorldExt};
14
15#[derive(Component)]
18struct Name(&'static str);
19
20#[derive(Component)]
21struct Health(i32);
22
23#[derive(Component)]
24struct LinkedList {
25 next: Option<EntityHandle>,
26}
27
28#[derive(Component)]
29struct Team(Vec<EntityHandle>);
30
31fn count_chain_length_bound(start: BoundEntity) -> usize {
33 let mut count = 1;
34 let mut current = start;
35
36 while let Some(next) = current.follow_opt::<LinkedList, _>(|l| l.next) {
37 count += 1;
38 current = next;
39 }
40
41 count
42}
43
44fn count_chain_length_ptr(start: EntityPtr) -> usize {
46 let mut count = 1;
47 let mut current = start;
48
49 while let Some(next) = current.follow_opt::<LinkedList, _>(|l| l.next) {
50 count += 1;
51 current = next;
52 }
53
54 count
55}
56
57fn team_health(leader: EntityPtr) -> i32 {
59 leader
60 .get::<Team>()
61 .map(|team| {
62 team.0
63 .iter()
64 .filter_map(|h| leader.follow_handle(*h).get::<Health>())
65 .map(|h| h.0)
66 .sum::<i32>()
67 })
68 .unwrap_or(0)
69 + leader.get::<Health>().map(|h| h.0).unwrap_or(0)
70}
71
72fn find_team_member<'w>(leader: BoundEntity<'w>, target_name: &str) -> Option<BoundEntity<'w>> {
74 leader.get::<Team>().and_then(|team| {
75 team.0.iter().find_map(|h| {
76 let member = h.bind(leader.world());
77 if member.get::<Name>().map(|n| n.0) == Some(target_name) {
78 Some(member)
79 } else {
80 None
81 }
82 })
83 })
84}
85
86fn main() {
87 let mut world = World::new();
88
89 println!("=== Mixed Usage Example ===\n");
90
91 println!("Building linked list: a -> b -> c -> d\n");
93
94 let d = world.spawn((Name("d"), LinkedList { next: None })).id();
95 let c = world
96 .spawn((
97 Name("c"),
98 LinkedList {
99 next: Some(EntityHandle::new(d)),
100 },
101 ))
102 .id();
103 let b = world
104 .spawn((
105 Name("b"),
106 LinkedList {
107 next: Some(EntityHandle::new(c)),
108 },
109 ))
110 .id();
111 let a = world
112 .spawn((
113 Name("a"),
114 LinkedList {
115 next: Some(EntityHandle::new(b)),
116 },
117 ))
118 .id();
119
120 let a_handle = EntityHandle::new(a);
122
123 println!("--- BoundEntity Approach (explicit &World) ---");
125
126 let bound = a_handle.bind(&world);
127 let length = count_chain_length_bound(bound);
128 println!("Chain length from 'a': {}", length);
129 assert_eq!(length, 4);
130
131 let name = a_handle.get::<Name>(&world).unwrap().0;
133 println!("Starting node name: {}", name);
134
135 println!("\n--- EntityPtr Approach (ergonomic) ---");
137
138 let ptr = world.entity_ptr(a_handle.entity()); let length = count_chain_length_ptr(ptr);
142 println!("Chain length from 'a': {}", length);
143 assert_eq!(length, 4);
144
145 let back_to_handle = ptr.handle();
147 assert_eq!(back_to_handle.entity(), a_handle.entity());
148 println!("Round-trip handle conversion: OK");
149
150 println!("\n--- Team Example ---");
152
153 let alice = world.spawn((Name("Alice"), Health(100))).id();
155 let bob = world.spawn((Name("Bob"), Health(80))).id();
156 let charlie = world.spawn((Name("Charlie"), Health(120))).id();
157
158 let leader = world
160 .spawn((
161 Name("Leader"),
162 Health(150),
163 Team(vec![
164 EntityHandle::new(alice),
165 EntityHandle::new(bob),
166 EntityHandle::new(charlie),
167 ]),
168 ))
169 .id();
170
171 let leader_ptr = world.entity_ptr(leader);
173 let total = team_health(leader_ptr);
174 println!("Total team health: {}", total);
175 println!(" Expected: 150 + 100 + 80 + 120 = 450");
176 assert_eq!(total, 450);
177
178 let leader_bound = EntityHandle::new(leader).bind(&world);
180 let bob_bound = find_team_member(leader_bound, "Bob").unwrap();
181 let bob_health = bob_bound.get::<Health>().unwrap().0;
182 println!("Bob's health: {}", bob_health);
183 assert_eq!(bob_health, 80);
184
185 println!("\n--- Stale Reference Handling ---");
187
188 let temp = world.spawn((Name("Temporary"), Health(50))).id();
189 let temp_handle = EntityHandle::new(temp);
190
191 println!("Before despawn:");
193 println!(" is_alive: {}", temp_handle.is_alive(&world));
194 println!(" name: {:?}", temp_handle.get::<Name>(&world).map(|n| n.0));
195 assert!(temp_handle.is_alive(&world));
196
197 world.despawn(temp);
199
200 println!("After despawn:");
202 println!(" is_alive: {}", temp_handle.is_alive(&world));
203 println!(" name: {:?}", temp_handle.get::<Name>(&world).map(|n| n.0));
204 assert!(!temp_handle.is_alive(&world));
205 assert!(temp_handle.get::<Name>(&world).is_none());
206
207 println!("\n--- Mixing Approaches ---");
209
210 let leader_handle = EntityHandle::new(leader);
212
213 let bound = leader_handle.bind(&world);
215 let leader_name = bound.get::<Name>().unwrap().0;
216
217 let ptr = world.entity_ptr(leader_handle.entity());
219 let total_health = team_health(ptr);
220
221 println!(
222 "{}'s team has total health of {}",
223 leader_name, total_health
224 );
225
226 let ptr_handle = ptr.handle();
228 let rebound = ptr_handle.bind(&world);
229 assert_eq!(rebound.get::<Name>().unwrap().0, leader_name);
230 println!("Seamless conversion between approaches: OK");
231
232 println!("\nAll assertions passed!");
233}