docs/bevy/examples/ecs/dynamic.rs 14.3 K · 377 lines · raw · history

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
12 use std::{alloc::Layout, collections::HashMap, io::Write, ptr::NonNull};
13
14 use 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
28 const PROMPT: &str = "
29 Commands:
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
35 Enter a command with no parameters for usage.";
36
37 const COMPONENT_PROMPT: &str = "
38 comp, 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
42 const ENTITY_PROMPT: &str = "
43 spawn, 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
47 const QUERY_PROMPT: &str = "
48 query, 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
58 const EVENT_PROMPT: &str = "
59 event, 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
64 const EMIT_PROMPT: &str = "
65 emit, t Trigger a dynamic event
66 Enter the name of a previously registered event.
67 e.g. OnDamage";
68
69 fn 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)]
295 struct 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
299 fn 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
315 fn 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
359 fn 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 }