1#![expect(
2 unsafe_code,
3 reason = "Unsafe code is needed to work with dynamic components"
4)]
5
6use 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 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 let Some(&id) = component_names.get(name) else {
144 println!("Component {name} does not exist");
145 return;
146 };
147
148 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 to_insert_ids.push(id);
159 to_insert_data.push(values);
160 });
161
162 let mut entity = world.spawn_empty();
163
164 let to_insert_ptr = to_owning_ptrs(&mut to_insert_data);
166
167 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 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 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 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 let event_key = unsafe { EventKey::new(event_component_id) };
239 event_names.insert(name.to_string(), event_key);
240
241 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 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 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 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#[derive(Resource, Default)]
295struct EventFireCount(HashMap<EventKey, u32>);
296
297fn 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 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 Some('?') => {
325 builder.optional(|b| parse_term(&str[1..], b, components));
326 matched = true;
327 }
328 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 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}