Skip to main content

dynamic/
dynamic.rs

1#![expect(
2    unsafe_code,
3    reason = "Unsafe code is needed to work with dynamic components"
4)]
5
6//! This example show how you can create components dynamically, spawn entities with those components
7//! as well as query for entities with those components.
8//!
9//! It also demonstrates dynamic observers: registering events and observers at
10//! runtime without compile-time event types, and triggering them with raw data.
11
12use std::{alloc::Layout, collections::HashMap, io::Write, ptr::NonNull};
13
14use bevy::{
15    ecs::{
16        component::{
17            ComponentCloneBehavior, ComponentDescriptor, ComponentId, ComponentInfo, StorageType,
18        },
19        event::EventKey,
20        observer::{Observer, ObserverRunner},
21        query::{ComponentAccessKind, QueryData},
22        world::FilteredEntityMut,
23    },
24    prelude::*,
25    ptr::{Aligned, OwningPtr, PtrMut},
26};
27
28const PROMPT: &str = "
29Commands:
30    comp, c    Create new components
31    spawn, s   Spawn entities
32    query, q   Query for entities
33    event, e   Register dynamic events and observers
34    emit, t    Trigger a dynamic event
35Enter a command with no parameters for usage.";
36
37const COMPONENT_PROMPT: &str = "
38comp, c   Create new components
39    Enter a comma separated list of type names optionally followed by a size in u64s.
40    e.g. CompA 3, CompB, CompC 2";
41
42const ENTITY_PROMPT: &str = "
43spawn, s  Spawn entities
44    Enter a comma separated list of components optionally followed by values.
45    e.g. CompA 0 1 0, CompB, CompC 1";
46
47const QUERY_PROMPT: &str = "
48query, q  Query for entities
49    Enter a query to fetch and update entities
50    Components with read or write access will be displayed with their values
51    Components with write access will have their fields incremented by one
52
53    Accesses: 'A' with, '&A' read, '&mut A' write
54    Operators: '||' or, ',' and, '?' optional
55
56    e.g. &A || &B, &mut C, D, ?E";
57
58const EVENT_PROMPT: &str = "
59event, e  Register dynamic events and observers
60    Enter a comma separated list of event names.
61    Each event gets a dynamic observer that prints when fired.
62    e.g. OnDamage, OnHeal, OnDeath";
63
64const EMIT_PROMPT: &str = "
65emit, t   Trigger a dynamic event
66    Enter the name of a previously registered event.
67    e.g. OnDamage";
68
69fn main() {
70    let mut world = World::new();
71    let mut lines = std::io::stdin().lines();
72    let mut component_names = HashMap::<String, ComponentId>::new();
73    let mut component_info = HashMap::<ComponentId, ComponentInfo>::new();
74    let mut event_names = HashMap::<String, EventKey>::new();
75
76    println!("{PROMPT}");
77    loop {
78        print!("\n> ");
79        let _ = std::io::stdout().flush();
80        let Some(Ok(line)) = lines.next() else {
81            return;
82        };
83
84        if line.is_empty() {
85            return;
86        };
87
88        let Some((first, rest)) = line.trim().split_once(|c: char| c.is_whitespace()) else {
89            match &line.chars().next() {
90                Some('c') => println!("{COMPONENT_PROMPT}"),
91                Some('s') => println!("{ENTITY_PROMPT}"),
92                Some('q') => println!("{QUERY_PROMPT}"),
93                Some('e') => println!("{EVENT_PROMPT}"),
94                Some('t') => println!("{EMIT_PROMPT}"),
95                _ => println!("{PROMPT}"),
96            }
97            continue;
98        };
99
100        match &first[0..1] {
101            "c" => {
102                rest.split(',').for_each(|component| {
103                    let mut component = component.split_whitespace();
104                    let Some(name) = component.next() else {
105                        return;
106                    };
107                    let size = match component.next().map(str::parse) {
108                        Some(Ok(size)) => size,
109                        _ => 0,
110                    };
111                    // Register our new component to the world with a layout specified by its size
112                    // SAFETY: [u64] is Send + Sync
113                    let id = world.register_component_with_descriptor(unsafe {
114                        ComponentDescriptor::new_with_layout(
115                            name.to_string(),
116                            StorageType::Table,
117                            Layout::array::<u64>(size).unwrap(),
118                            None,
119                            true,
120                            false,
121                            ComponentCloneBehavior::Default,
122                            None,
123                        )
124                    });
125                    let Some(info) = world.components().get_info(id) else {
126                        return;
127                    };
128                    component_names.insert(name.to_string(), id);
129                    component_info.insert(id, info.clone());
130                    println!("Component {} created with id: {}", name, id.index());
131                });
132            }
133            "s" => {
134                let mut to_insert_ids = Vec::new();
135                let mut to_insert_data = Vec::new();
136                rest.split(',').for_each(|component| {
137                    let mut component = component.split_whitespace();
138                    let Some(name) = component.next() else {
139                        return;
140                    };
141
142                    // Get the id for the component with the given name
143                    let Some(&id) = component_names.get(name) else {
144                        println!("Component {name} does not exist");
145                        return;
146                    };
147
148                    // Calculate the length for the array based on the layout created for this component id
149                    let info = world.components().get_info(id).unwrap();
150                    let len = info.layout().size() / size_of::<u64>();
151                    let mut values: Vec<u64> = component
152                        .take(len)
153                        .filter_map(|value| value.parse::<u64>().ok())
154                        .collect();
155                    values.resize(len, 0);
156
157                    // Collect the id and array to be inserted onto our entity
158                    to_insert_ids.push(id);
159                    to_insert_data.push(values);
160                });
161
162                let mut entity = world.spawn_empty();
163
164                // Construct an `OwningPtr` for each component in `to_insert_data`
165                let to_insert_ptr = to_owning_ptrs(&mut to_insert_data);
166
167                // SAFETY:
168                // - Component ids have been taken from the same world
169                // - Each array is created to the layout specified in the world
170                unsafe {
171                    entity.insert_by_ids(&to_insert_ids, to_insert_ptr.into_iter());
172                }
173
174                println!("Entity spawned with id: {}", entity.id());
175            }
176            "q" => {
177                let mut builder = QueryBuilder::<FilteredEntityMut>::new(&mut world);
178                parse_query(rest, &mut builder, &component_names);
179                let mut query = builder.build();
180                query.iter_mut(&mut world).for_each(|filtered_entity| {
181                    let terms = filtered_entity
182                        .access()
183                        .try_iter_access()
184                        .unwrap()
185                        .map(|component_access| {
186                            let id = *component_access.index();
187                            let ptr = filtered_entity.get_by_id(id).unwrap();
188                            let info = component_info.get(&id).unwrap();
189                            let len = info.layout().size() / size_of::<u64>();
190
191                            // SAFETY:
192                            // - All components are created with layout [u64]
193                            // - len is calculated from the component descriptor
194                            let data = unsafe {
195                                std::slice::from_raw_parts_mut(
196                                    ptr.assert_unique().as_ptr().cast::<u64>(),
197                                    len,
198                                )
199                            };
200
201                            // If we have write access, increment each value once
202                            if matches!(component_access, ComponentAccessKind::Exclusive(_)) {
203                                data.iter_mut().for_each(|data| {
204                                    *data += 1;
205                                });
206                            }
207
208                            format!("{}: {:?}", info.name(), data[0..len].to_vec())
209                        })
210                        .collect::<Vec<_>>()
211                        .join(", ");
212
213                    println!("{}: {}", filtered_entity.id(), terms);
214                });
215            }
216            "e" => {
217                rest.split(',').for_each(|event| {
218                    let name = event.trim();
219                    if name.is_empty() {
220                        return;
221                    }
222
223                    // Register a ComponentId for this event, no Rust type needed.
224                    // SAFETY: ZST with no drop
225                    let event_component_id = world.register_component_with_descriptor(unsafe {
226                        ComponentDescriptor::new_with_layout(
227                            format!("event:{name}"),
228                            StorageType::Table,
229                            Layout::new::<()>(),
230                            None,
231                            false,
232                            false,
233                            ComponentCloneBehavior::Ignore,
234                            None,
235                        )
236                    });
237                    // SAFETY: event_component_id was just registered for this event
238                    let event_key = unsafe { EventKey::new(event_component_id) };
239                    event_names.insert(name.to_string(), event_key);
240
241                    // Build a dynamic observer that prints when the event fires.
242                    let runner: ObserverRunner = |mut world, _observer, ctx, _event, _trigger| {
243                        println!("  Observer fired!");
244                        if let Some(mut counts) = world.get_resource_mut::<EventFireCount>() {
245                            *counts.0.entry(ctx.event_key).or_insert(0) += 1;
246                        }
247                    };
248
249                    // SAFETY: event_key was just registered, runner ignores pointers
250                    let observer =
251                        unsafe { Observer::with_dynamic_runner(runner).with_event_key(event_key) };
252                    world.spawn(observer);
253
254                    println!(
255                        "Event '{name}' registered (key: {}) with a dynamic observer",
256                        event_component_id.index()
257                    );
258                });
259
260                // Ensure the counter resource exists.
261                world.init_resource::<EventFireCount>();
262            }
263            "t" => {
264                let name = rest.trim();
265                let Some(&event_key) = event_names.get(name) else {
266                    println!(
267                        "Event '{name}' does not exist. Register it first with 'event {name}'"
268                    );
269                    continue;
270                };
271
272                let mut event_data = ();
273                let mut trigger_data = ();
274                // SAFETY: event_key was registered in this world, both pointers are valid ZSTs
275                unsafe {
276                    world.trigger_dynamic(
277                        event_key,
278                        PtrMut::from(&mut event_data),
279                        PtrMut::from(&mut trigger_data),
280                    );
281                }
282
283                let count = world
284                    .get_resource::<EventFireCount>()
285                    .map_or(0, |c| c.0.get(&event_key).copied().unwrap_or(0));
286                println!("Event '{name}' triggered ({count} fires)");
287            }
288            _ => continue,
289        }
290    }
291}
292
293/// Tracks how many times each dynamic event's observer has fired.
294#[derive(Resource, Default)]
295struct EventFireCount(HashMap<EventKey, u32>);
296
297// Constructs `OwningPtr` for each item in `components`
298// By sharing the lifetime of `components` with the resulting ptrs we ensure we don't drop the data before use
299fn to_owning_ptrs(components: &mut [Vec<u64>]) -> Vec<OwningPtr<'_, Aligned>> {
300    components
301        .iter_mut()
302        .map(|data| {
303            let ptr = data.as_mut_ptr();
304            // SAFETY:
305            // - Pointers are guaranteed to be non-null
306            // - Memory pointed to won't be dropped until `components` is dropped
307            unsafe {
308                let non_null = NonNull::new_unchecked(ptr.cast());
309                OwningPtr::new(non_null)
310            }
311        })
312        .collect()
313}
314
315fn parse_term<Q: QueryData>(
316    str: &str,
317    builder: &mut QueryBuilder<Q>,
318    components: &HashMap<String, ComponentId>,
319) {
320    let mut matched = false;
321    let str = str.trim();
322    match str.chars().next() {
323        // Optional term
324        Some('?') => {
325            builder.optional(|b| parse_term(&str[1..], b, components));
326            matched = true;
327        }
328        // Reference term
329        Some('&') => {
330            let mut parts = str.split_whitespace();
331            let first = parts.next().unwrap();
332            if first == "&mut" {
333                if let Some(str) = parts.next()
334                    && let Some(&id) = components.get(str)
335                {
336                    builder.mut_id(id);
337                    matched = true;
338                };
339            } else if let Some(&id) = components.get(&first[1..]) {
340                builder.ref_id(id);
341                matched = true;
342            }
343        }
344        // With term
345        Some(_) => {
346            if let Some(&id) = components.get(str) {
347                builder.with_id(id);
348                matched = true;
349            }
350        }
351        None => {}
352    };
353
354    if !matched {
355        println!("Unable to find component: {str}");
356    }
357}
358
359fn parse_query<Q: QueryData>(
360    str: &str,
361    builder: &mut QueryBuilder<Q>,
362    components: &HashMap<String, ComponentId>,
363) {
364    let str = str.split(',');
365    str.for_each(|term| {
366        let sub_terms: Vec<_> = term.split("||").collect();
367        if sub_terms.len() == 1 {
368            parse_term(sub_terms[0], builder, components);
369        } else {
370            builder.or(|b| {
371                sub_terms
372                    .iter()
373                    .for_each(|term| parse_term(term, b, components));
374            });
375        }
376    });
377}