Skip to main content

mixed_usage/
mixed_usage.rs

1//! Mixed usage example showing EntityHandle storage with EntityPtr traversal.
2//!
3//! This example demonstrates:
4//! - Storing EntityHandle in components (Send + Sync)
5//! - Using BoundEntity for explicit world parameter style
6//! - Using EntityPtr for ergonomic traversal (via `WorldExt`)
7//! - Converting between the two approaches
8//! - Handling stale references gracefully
9//!
10//! Run with: `cargo run --example mixed_usage`
11
12use bevy_ecs::prelude::*;
13use bevy_entity_ptr::{BoundEntity, EntityHandle, EntityPtr, WorldExt};
14
15// Components
16
17#[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
31// Using BoundEntity (explicit world parameter style)
32fn 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
44// Using EntityPtr (ergonomic style)
45fn 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
57// Calculate total team health using EntityPtr
58fn 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
72// Find team member by name
73fn 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    // Build a linked list: a -> b -> c -> d
92    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    // Store handle for later use (EntityHandle is Send + Sync)
121    let a_handle = EntityHandle::new(a);
122
123    // === BoundEntity approach ===
124    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    // Get component with explicit world
132    let name = a_handle.get::<Name>(&world).unwrap().0;
133    println!("Starting node name: {}", name);
134
135    // === EntityPtr approach ===
136    println!("\n--- EntityPtr Approach (ergonomic) ---");
137
138    // No unsafe needed! WorldExt provides ergonomic access
139    let ptr = world.entity_ptr(a_handle.entity()); // Convert handle to ptr
140
141    let length = count_chain_length_ptr(ptr);
142    println!("Chain length from 'a': {}", length);
143    assert_eq!(length, 4);
144
145    // Convert back to handle
146    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    // === Team example ===
151    println!("\n--- Team Example ---");
152
153    // Create team members
154    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    // Create team leader with team
159    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    // Calculate total team health using EntityPtr
172    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    // Find team member using BoundEntity
179    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    // === Stale Reference Handling ===
186    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    // Check it's alive
192    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    // Despawn the entity
198    world.despawn(temp);
199
200    // Handle gracefully returns None
201    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    // === Mixing approaches in computation ===
208    println!("\n--- Mixing Approaches ---");
209
210    // Start with a stored handle
211    let leader_handle = EntityHandle::new(leader);
212
213    // Use BoundEntity for one part of the computation
214    let bound = leader_handle.bind(&world);
215    let leader_name = bound.get::<Name>().unwrap().0;
216
217    // Switch to EntityPtr for another part
218    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    // Can also convert EntityPtr back to BoundEntity's handle
227    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}