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 it's 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                            ComponentCloneBehavior::Default,
121                            None,
122                        )
123                    });
124                    let Some(info) = world.components().get_info(id) else {
125                        return;
126                    };
127                    component_names.insert(name.to_string(), id);
128                    component_info.insert(id, info.clone());
129                    println!("Component {} created with id: {}", name, id.index());
130                });
131            }
132            "s" => {
133                let mut to_insert_ids = Vec::new();
134                let mut to_insert_data = Vec::new();
135                rest.split(',').for_each(|component| {
136                    let mut component = component.split_whitespace();
137                    let Some(name) = component.next() else {
138                        return;
139                    };
140
141                    // Get the id for the component with the given name
142                    let Some(&id) = component_names.get(name) else {
143                        println!("Component {name} does not exist");
144                        return;
145                    };
146
147                    // Calculate the length for the array based on the layout created for this component id
148                    let info = world.components().get_info(id).unwrap();
149                    let len = info.layout().size() / size_of::<u64>();
150                    let mut values: Vec<u64> = component
151                        .take(len)
152                        .filter_map(|value| value.parse::<u64>().ok())
153                        .collect();
154                    values.resize(len, 0);
155
156                    // Collect the id and array to be inserted onto our entity
157                    to_insert_ids.push(id);
158                    to_insert_data.push(values);
159                });
160
161                let mut entity = world.spawn_empty();
162
163                // Construct an `OwningPtr` for each component in `to_insert_data`
164                let to_insert_ptr = to_owning_ptrs(&mut to_insert_data);
165
166                // SAFETY:
167                // - Component ids have been taken from the same world
168                // - Each array is created to the layout specified in the world
169                unsafe {
170                    entity.insert_by_ids(&to_insert_ids, to_insert_ptr.into_iter());
171                }
172
173                println!("Entity spawned with id: {}", entity.id());
174            }
175            "q" => {
176                let mut builder = QueryBuilder::<FilteredEntityMut>::new(&mut world);
177                parse_query(rest, &mut builder, &component_names);
178                let mut query = builder.build();
179                query.iter_mut(&mut world).for_each(|filtered_entity| {
180                    let terms = filtered_entity
181                        .access()
182                        .try_iter_access()
183                        .unwrap()
184                        .map(|component_access| {
185                            let id = *component_access.index();
186                            let ptr = filtered_entity.get_by_id(id).unwrap();
187                            let info = component_info.get(&id).unwrap();
188                            let len = info.layout().size() / size_of::<u64>();
189
190                            // SAFETY:
191                            // - All components are created with layout [u64]
192                            // - len is calculated from the component descriptor
193                            let data = unsafe {
194                                std::slice::from_raw_parts_mut(
195                                    ptr.assert_unique().as_ptr().cast::<u64>(),
196                                    len,
197                                )
198                            };
199
200                            // If we have write access, increment each value once
201                            if matches!(component_access, ComponentAccessKind::Exclusive(_)) {
202                                data.iter_mut().for_each(|data| {
203                                    *data += 1;
204                                });
205                            }
206
207                            format!("{}: {:?}", info.name(), data[0..len].to_vec())
208                        })
209                        .collect::<Vec<_>>()
210                        .join(", ");
211
212                    println!("{}: {}", filtered_entity.id(), terms);
213                });
214            }
215            "e" => {
216                rest.split(',').for_each(|event| {
217                    let name = event.trim();
218                    if name.is_empty() {
219                        return;
220                    }
221
222                    // Register a ComponentId for this event, no Rust type needed.
223                    // SAFETY: ZST with no drop
224                    let event_component_id = world.register_component_with_descriptor(unsafe {
225                        ComponentDescriptor::new_with_layout(
226                            format!("event:{name}"),
227                            StorageType::Table,
228                            Layout::new::<()>(),
229                            None,
230                            false,
231                            ComponentCloneBehavior::Ignore,
232                            None,
233                        )
234                    });
235                    // SAFETY: event_component_id was just registered for this event
236                    let event_key = unsafe { EventKey::new(event_component_id) };
237                    event_names.insert(name.to_string(), event_key);
238
239                    // Build a dynamic observer that prints when the event fires.
240                    let runner: ObserverRunner = |mut world, _observer, ctx, _event, _trigger| {
241                        println!("  Observer fired!");
242                        if let Some(mut counts) = world.get_resource_mut::<EventFireCount>() {
243                            *counts.0.entry(ctx.event_key).or_insert(0) += 1;
244                        }
245                    };
246
247                    // SAFETY: event_key was just registered, runner ignores pointers
248                    let observer =
249                        unsafe { Observer::with_dynamic_runner(runner).with_event_key(event_key) };
250                    world.spawn(observer);
251
252                    println!(
253                        "Event '{name}' registered (key: {}) with a dynamic observer",
254                        event_component_id.index()
255                    );
256                });
257
258                // Ensure the counter resource exists.
259                world.init_resource::<EventFireCount>();
260            }
261            "t" => {
262                let name = rest.trim();
263                let Some(&event_key) = event_names.get(name) else {
264                    println!(
265                        "Event '{name}' does not exist. Register it first with 'event {name}'"
266                    );
267                    continue;
268                };
269
270                let mut event_data = ();
271                let mut trigger_data = ();
272                // SAFETY: event_key was registered in this world, both pointers are valid ZSTs
273                unsafe {
274                    world.trigger_dynamic(
275                        event_key,
276                        PtrMut::from(&mut event_data),
277                        PtrMut::from(&mut trigger_data),
278                    );
279                }
280
281                let count = world
282                    .get_resource::<EventFireCount>()
283                    .map_or(0, |c| c.0.get(&event_key).copied().unwrap_or(0));
284                println!("Event '{name}' triggered ({count} fires)");
285            }
286            _ => continue,
287        }
288    }
289}
290
291/// Tracks how many times each dynamic event's observer has fired.
292#[derive(Resource, Default)]
293struct EventFireCount(HashMap<EventKey, u32>);
294
295// Constructs `OwningPtr` for each item in `components`
296// By sharing the lifetime of `components` with the resulting ptrs we ensure we don't drop the data before use
297fn to_owning_ptrs(components: &mut [Vec<u64>]) -> Vec<OwningPtr<'_, Aligned>> {
298    components
299        .iter_mut()
300        .map(|data| {
301            let ptr = data.as_mut_ptr();
302            // SAFETY:
303            // - Pointers are guaranteed to be non-null
304            // - Memory pointed to won't be dropped until `components` is dropped
305            unsafe {
306                let non_null = NonNull::new_unchecked(ptr.cast());
307                OwningPtr::new(non_null)
308            }
309        })
310        .collect()
311}
312
313fn parse_term<Q: QueryData>(
314    str: &str,
315    builder: &mut QueryBuilder<Q>,
316    components: &HashMap<String, ComponentId>,
317) {
318    let mut matched = false;
319    let str = str.trim();
320    match str.chars().next() {
321        // Optional term
322        Some('?') => {
323            builder.optional(|b| parse_term(&str[1..], b, components));
324            matched = true;
325        }
326        // Reference term
327        Some('&') => {
328            let mut parts = str.split_whitespace();
329            let first = parts.next().unwrap();
330            if first == "&mut" {
331                if let Some(str) = parts.next()
332                    && let Some(&id) = components.get(str)
333                {
334                    builder.mut_id(id);
335                    matched = true;
336                };
337            } else if let Some(&id) = components.get(&first[1..]) {
338                builder.ref_id(id);
339                matched = true;
340            }
341        }
342        // With term
343        Some(_) => {
344            if let Some(&id) = components.get(str) {
345                builder.with_id(id);
346                matched = true;
347            }
348        }
349        None => {}
350    };
351
352    if !matched {
353        println!("Unable to find component: {str}");
354    }
355}
356
357fn parse_query<Q: QueryData>(
358    str: &str,
359    builder: &mut QueryBuilder<Q>,
360    components: &HashMap<String, ComponentId>,
361) {
362    let str = str.split(',');
363    str.for_each(|term| {
364        let sub_terms: Vec<_> = term.split("||").collect();
365        if sub_terms.len() == 1 {
366            parse_term(sub_terms[0], builder, components);
367        } else {
368            builder.or(|b| {
369                sub_terms
370                    .iter()
371                    .for_each(|term| parse_term(term, b, components));
372            });
373        }
374    });
375}