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 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 let Some(&id) = component_names.get(name) else {
143 println!("Component {name} does not exist");
144 return;
145 };
146
147 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 to_insert_ids.push(id);
158 to_insert_data.push(values);
159 });
160
161 let mut entity = world.spawn_empty();
162
163 let to_insert_ptr = to_owning_ptrs(&mut to_insert_data);
165
166 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 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 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 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 let event_key = unsafe { EventKey::new(event_component_id) };
237 event_names.insert(name.to_string(), event_key);
238
239 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 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 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 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#[derive(Resource, Default)]
293struct EventFireCount(HashMap<EventKey, u32>);
294
295fn 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 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 Some('?') => {
323 builder.optional(|b| parse_term(&str[1..], b, components));
324 matched = true;
325 }
326 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 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}