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};
10
11use bevy::{
12 ecs::{
13 component::{
14 ComponentCloneBehavior, ComponentDescriptor, ComponentId, ComponentInfo, StorageType,
15 },
16 query::{ComponentAccessKind, QueryData},
17 world::FilteredEntityMut,
18 },
19 prelude::*,
20 ptr::{Aligned, OwningPtr},
21};
22
23const PROMPT: &str = "
24Commands:
25 comp, c Create new components
26 spawn, s Spawn entities
27 query, q Query for entities
28Enter a command with no parameters for usage.";
29
30const COMPONENT_PROMPT: &str = "
31comp, c Create new components
32 Enter a comma separated list of type names optionally followed by a size in u64s.
33 e.g. CompA 3, CompB, CompC 2";
34
35const ENTITY_PROMPT: &str = "
36spawn, s Spawn entities
37 Enter a comma separated list of components optionally followed by values.
38 e.g. CompA 0 1 0, CompB, CompC 1";
39
40const QUERY_PROMPT: &str = "
41query, q Query for entities
42 Enter a query to fetch and update entities
43 Components with read or write access will be displayed with their values
44 Components with write access will have their fields incremented by one
45
46 Accesses: 'A' with, '&A' read, '&mut A' write
47 Operators: '||' or, ',' and, '?' optional
48
49 e.g. &A || &B, &mut C, D, ?E";
50
51fn main() {
52 let mut world = World::new();
53 let mut lines = std::io::stdin().lines();
54 let mut component_names = HashMap::<String, ComponentId>::new();
55 let mut component_info = HashMap::<ComponentId, ComponentInfo>::new();
56
57 println!("{PROMPT}");
58 loop {
59 print!("\n> ");
60 let _ = std::io::stdout().flush();
61 let Some(Ok(line)) = lines.next() else {
62 return;
63 };
64
65 if line.is_empty() {
66 return;
67 };
68
69 let Some((first, rest)) = line.trim().split_once(|c: char| c.is_whitespace()) else {
70 match &line.chars().next() {
71 Some('c') => println!("{COMPONENT_PROMPT}"),
72 Some('s') => println!("{ENTITY_PROMPT}"),
73 Some('q') => println!("{QUERY_PROMPT}"),
74 _ => println!("{PROMPT}"),
75 }
76 continue;
77 };
78
79 match &first[0..1] {
80 "c" => {
81 rest.split(',').for_each(|component| {
82 let mut component = component.split_whitespace();
83 let Some(name) = component.next() else {
84 return;
85 };
86 let size = match component.next().map(str::parse) {
87 Some(Ok(size)) => size,
88 _ => 0,
89 };
90 let id = world.register_component_with_descriptor(unsafe {
93 ComponentDescriptor::new_with_layout(
94 name.to_string(),
95 StorageType::Table,
96 Layout::array::<u64>(size).unwrap(),
97 None,
98 true,
99 ComponentCloneBehavior::Default,
100 )
101 });
102 let Some(info) = world.components().get_info(id) else {
103 return;
104 };
105 component_names.insert(name.to_string(), id);
106 component_info.insert(id, info.clone());
107 println!("Component {} created with id: {}", name, id.index());
108 });
109 }
110 "s" => {
111 let mut to_insert_ids = Vec::new();
112 let mut to_insert_data = Vec::new();
113 rest.split(',').for_each(|component| {
114 let mut component = component.split_whitespace();
115 let Some(name) = component.next() else {
116 return;
117 };
118
119 let Some(&id) = component_names.get(name) else {
121 println!("Component {name} does not exist");
122 return;
123 };
124
125 let info = world.components().get_info(id).unwrap();
127 let len = info.layout().size() / size_of::<u64>();
128 let mut values: Vec<u64> = component
129 .take(len)
130 .filter_map(|value| value.parse::<u64>().ok())
131 .collect();
132 values.resize(len, 0);
133
134 to_insert_ids.push(id);
136 to_insert_data.push(values);
137 });
138
139 let mut entity = world.spawn_empty();
140
141 let to_insert_ptr = to_owning_ptrs(&mut to_insert_data);
143
144 unsafe {
148 entity.insert_by_ids(&to_insert_ids, to_insert_ptr.into_iter());
149 }
150
151 println!("Entity spawned with id: {}", entity.id());
152 }
153 "q" => {
154 let mut builder = QueryBuilder::<FilteredEntityMut>::new(&mut world);
155 parse_query(rest, &mut builder, &component_names);
156 let mut query = builder.build();
157 query.iter_mut(&mut world).for_each(|filtered_entity| {
158 let terms = filtered_entity
159 .access()
160 .try_iter_component_access()
161 .unwrap()
162 .map(|component_access| {
163 let id = *component_access.index();
164 let ptr = filtered_entity.get_by_id(id).unwrap();
165 let info = component_info.get(&id).unwrap();
166 let len = info.layout().size() / size_of::<u64>();
167
168 let data = unsafe {
172 std::slice::from_raw_parts_mut(
173 ptr.assert_unique().as_ptr().cast::<u64>(),
174 len,
175 )
176 };
177
178 if matches!(component_access, ComponentAccessKind::Exclusive(_)) {
180 data.iter_mut().for_each(|data| {
181 *data += 1;
182 });
183 }
184
185 format!("{}: {:?}", info.name(), data[0..len].to_vec())
186 })
187 .collect::<Vec<_>>()
188 .join(", ");
189
190 println!("{}: {}", filtered_entity.id(), terms);
191 });
192 }
193 _ => continue,
194 }
195 }
196}
197
198fn to_owning_ptrs(components: &mut [Vec<u64>]) -> Vec<OwningPtr<'_, Aligned>> {
201 components
202 .iter_mut()
203 .map(|data| {
204 let ptr = data.as_mut_ptr();
205 unsafe {
209 let non_null = NonNull::new_unchecked(ptr.cast());
210 OwningPtr::new(non_null)
211 }
212 })
213 .collect()
214}
215
216fn parse_term<Q: QueryData>(
217 str: &str,
218 builder: &mut QueryBuilder<Q>,
219 components: &HashMap<String, ComponentId>,
220) {
221 let mut matched = false;
222 let str = str.trim();
223 match str.chars().next() {
224 Some('?') => {
226 builder.optional(|b| parse_term(&str[1..], b, components));
227 matched = true;
228 }
229 Some('&') => {
231 let mut parts = str.split_whitespace();
232 let first = parts.next().unwrap();
233 if first == "&mut" {
234 if let Some(str) = parts.next()
235 && let Some(&id) = components.get(str)
236 {
237 builder.mut_id(id);
238 matched = true;
239 };
240 } else if let Some(&id) = components.get(&first[1..]) {
241 builder.ref_id(id);
242 matched = true;
243 }
244 }
245 Some(_) => {
247 if let Some(&id) = components.get(str) {
248 builder.with_id(id);
249 matched = true;
250 }
251 }
252 None => {}
253 };
254
255 if !matched {
256 println!("Unable to find component: {str}");
257 }
258}
259
260fn parse_query<Q: QueryData>(
261 str: &str,
262 builder: &mut QueryBuilder<Q>,
263 components: &HashMap<String, ComponentId>,
264) {
265 let str = str.split(',');
266 str.for_each(|term| {
267 let sub_terms: Vec<_> = term.split("||").collect();
268 if sub_terms.len() == 1 {
269 parse_term(sub_terms[0], builder, components);
270 } else {
271 builder.or(|b| {
272 sub_terms
273 .iter()
274 .for_each(|term| parse_term(term, b, components));
275 });
276 }
277 });
278}