Skip to main content

concurrent_systems/
concurrent_systems.rs

1//! Concurrent systems example demonstrating multi-threaded read-only access.
2//!
3//! This example shows how to:
4//! - Create multiple read-only systems that use EntityPtr
5//! - Understand why concurrent reads are safe with this crate
6//! - Use the `WorldExt` trait for ergonomic EntityPtr creation
7//!
8//! Run with: `cargo run --example concurrent_systems`
9//!
10//! # Thread Safety Explanation
11//!
12//! Each system creates its own `EntityPtr` instances using `world.entity_ptr()`.
13//! These types are intentionally NOT Send/Sync, meaning they cannot be shared
14//! across threads. However, Bevy's scheduler can run multiple read-only systems
15//! in parallel because:
16//!
17//! 1. Each system has its own `&World` reference (immutable borrow)
18//! 2. Each system creates independent `EntityPtr` instances from that reference
19//! 3. All operations through `EntityPtr` are read-only
20//!
21//! This is the intended usage pattern: use `world.entity_ptr()` to get pointers,
22//! traverse entities within the system, and let everything drop before returning.
23
24use bevy_ecs::prelude::*;
25use bevy_entity_ptr::{EntityHandle, EntityPtr, WorldExt};
26
27// Components for our hierarchy
28
29#[derive(Component)]
30struct Name(&'static str);
31
32#[derive(Component)]
33struct Health(i32);
34
35#[derive(Component)]
36struct Armor(i32);
37
38#[derive(Component)]
39struct Children(Vec<EntityHandle>);
40
41#[derive(Component)]
42struct RootMarker;
43
44// Recursive helper: sum health across a hierarchy
45fn sum_health(node: EntityPtr) -> i32 {
46    let my_health = node.get::<Health>().map(|h| h.0).unwrap_or(0);
47
48    let children_health: i32 = node
49        .get::<Children>()
50        .map(|c| c.0.iter().map(|h| sum_health(node.follow_handle(*h))).sum())
51        .unwrap_or(0);
52
53    my_health + children_health
54}
55
56// Recursive helper: sum armor across a hierarchy
57fn sum_armor(node: EntityPtr) -> i32 {
58    let my_armor = node.get::<Armor>().map(|a| a.0).unwrap_or(0);
59
60    let children_armor: i32 = node
61        .get::<Children>()
62        .map(|c| c.0.iter().map(|h| sum_armor(node.follow_handle(*h))).sum())
63        .unwrap_or(0);
64
65    my_armor + children_armor
66}
67
68/// System A: Computes total health across hierarchies.
69///
70/// This system can run concurrently with other read-only systems because:
71/// - It only reads from the World (no mutations)
72/// - Its EntityPtr instances are local to this system
73/// - Bevy's scheduler detects the `&World` parameter and allows parallel execution
74fn compute_health_system(world: &World, query: Query<(Entity, &Name), With<RootMarker>>) {
75    for (entity, name) in &query {
76        let root_ptr = world.entity_ptr(entity);
77        let total_health = sum_health(root_ptr);
78        println!("[Health System] {} total health: {}", name.0, total_health);
79    }
80}
81
82/// System B: Computes total armor across hierarchies.
83///
84/// This system runs concurrently with compute_health_system because both
85/// only perform read operations. In a real Bevy app, the scheduler would
86/// automatically parallelize these systems.
87fn compute_armor_system(world: &World, query: Query<(Entity, &Name), With<RootMarker>>) {
88    for (entity, name) in &query {
89        let root_ptr = world.entity_ptr(entity);
90        let total_armor = sum_armor(root_ptr);
91        println!("[Armor System] {} total armor: {}", name.0, total_armor);
92    }
93}
94
95fn main() {
96    let mut world = World::new();
97
98    // Build two separate hierarchies:
99    //
100    // Squad Alpha:           Squad Beta:
101    //   alpha (H:100, A:50)    beta (H:80, A:30)
102    //    ├─ a1 (H:50, A:20)     ├─ b1 (H:40, A:15)
103    //    └─ a2 (H:50, A:20)     └─ b2 (H:40, A:15)
104    //
105    println!("Building entity hierarchies...\n");
106
107    // Build Squad Alpha
108    let a1 = world.spawn((Name("a1"), Health(50), Armor(20))).id();
109    let a2 = world.spawn((Name("a2"), Health(50), Armor(20))).id();
110    let alpha = world
111        .spawn((
112            Name("Squad Alpha"),
113            Health(100),
114            Armor(50),
115            RootMarker,
116            Children(vec![EntityHandle::new(a1), EntityHandle::new(a2)]),
117        ))
118        .id();
119
120    // Build Squad Beta
121    let b1 = world.spawn((Name("b1"), Health(40), Armor(15))).id();
122    let b2 = world.spawn((Name("b2"), Health(40), Armor(15))).id();
123    let _beta = world
124        .spawn((
125            Name("Squad Beta"),
126            Health(80),
127            Armor(30),
128            RootMarker,
129            Children(vec![EntityHandle::new(b1), EntityHandle::new(b2)]),
130        ))
131        .id();
132
133    // In a real Bevy application, you would add systems like this:
134    //
135    //   app.add_systems(Update, (compute_health_system, compute_armor_system));
136    //
137    // Bevy's scheduler would automatically run them in parallel because
138    // both systems only have `&World` access (no exclusive/mutable access).
139
140    println!("Simulating concurrent system execution:\n");
141    println!("(In a real Bevy app, these would run in parallel)\n");
142
143    // Create a schedule and add both systems
144    let mut schedule = Schedule::default();
145    schedule.add_systems((compute_health_system, compute_armor_system));
146
147    // Run the schedule - systems execute (potentially in parallel with multi-threading)
148    schedule.run(&mut world);
149
150    // Verify the computed values
151    println!("\n--- Verification ---");
152
153    let alpha_ptr = world.entity_ptr(alpha);
154
155    let alpha_health = sum_health(alpha_ptr);
156    let alpha_armor = sum_armor(alpha_ptr);
157
158    println!(
159        "Squad Alpha: health={}, armor={}",
160        alpha_health, alpha_armor
161    );
162    assert_eq!(alpha_health, 200); // 100 + 50 + 50
163    assert_eq!(alpha_armor, 90); // 50 + 20 + 20
164
165    println!("\nAll assertions passed!");
166    println!("\nKey takeaway: Use world.entity_ptr() in each system.");
167    println!("EntityPtr is NOT shared between systems - each system has");
168    println!("independent instances that are safe for concurrent reads.");
169}