Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/dynamic.rs
9328 views
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
use std::{alloc::Layout, collections::HashMap, io::Write, ptr::NonNull};
10
11
use 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
23
const PROMPT: &str = "
24
Commands:
25
comp, c Create new components
26
spawn, s Spawn entities
27
query, q Query for entities
28
Enter a command with no parameters for usage.";
29
30
const COMPONENT_PROMPT: &str = "
31
comp, 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
35
const ENTITY_PROMPT: &str = "
36
spawn, 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
40
const QUERY_PROMPT: &str = "
41
query, 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
51
fn 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
// Register our new component to the world with a layout specified by it's size
91
// SAFETY: [u64] is Send + Sync
92
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
None,
101
)
102
});
103
let Some(info) = world.components().get_info(id) else {
104
return;
105
};
106
component_names.insert(name.to_string(), id);
107
component_info.insert(id, info.clone());
108
println!("Component {} created with id: {}", name, id.index());
109
});
110
}
111
"s" => {
112
let mut to_insert_ids = Vec::new();
113
let mut to_insert_data = Vec::new();
114
rest.split(',').for_each(|component| {
115
let mut component = component.split_whitespace();
116
let Some(name) = component.next() else {
117
return;
118
};
119
120
// Get the id for the component with the given name
121
let Some(&id) = component_names.get(name) else {
122
println!("Component {name} does not exist");
123
return;
124
};
125
126
// Calculate the length for the array based on the layout created for this component id
127
let info = world.components().get_info(id).unwrap();
128
let len = info.layout().size() / size_of::<u64>();
129
let mut values: Vec<u64> = component
130
.take(len)
131
.filter_map(|value| value.parse::<u64>().ok())
132
.collect();
133
values.resize(len, 0);
134
135
// Collect the id and array to be inserted onto our entity
136
to_insert_ids.push(id);
137
to_insert_data.push(values);
138
});
139
140
let mut entity = world.spawn_empty();
141
142
// Construct an `OwningPtr` for each component in `to_insert_data`
143
let to_insert_ptr = to_owning_ptrs(&mut to_insert_data);
144
145
// SAFETY:
146
// - Component ids have been taken from the same world
147
// - Each array is created to the layout specified in the world
148
unsafe {
149
entity.insert_by_ids(&to_insert_ids, to_insert_ptr.into_iter());
150
}
151
152
println!("Entity spawned with id: {}", entity.id());
153
}
154
"q" => {
155
let mut builder = QueryBuilder::<FilteredEntityMut>::new(&mut world);
156
parse_query(rest, &mut builder, &component_names);
157
let mut query = builder.build();
158
query.iter_mut(&mut world).for_each(|filtered_entity| {
159
let terms = filtered_entity
160
.access()
161
.try_iter_component_access()
162
.unwrap()
163
.map(|component_access| {
164
let id = *component_access.index();
165
let ptr = filtered_entity.get_by_id(id).unwrap();
166
let info = component_info.get(&id).unwrap();
167
let len = info.layout().size() / size_of::<u64>();
168
169
// SAFETY:
170
// - All components are created with layout [u64]
171
// - len is calculated from the component descriptor
172
let data = unsafe {
173
std::slice::from_raw_parts_mut(
174
ptr.assert_unique().as_ptr().cast::<u64>(),
175
len,
176
)
177
};
178
179
// If we have write access, increment each value once
180
if matches!(component_access, ComponentAccessKind::Exclusive(_)) {
181
data.iter_mut().for_each(|data| {
182
*data += 1;
183
});
184
}
185
186
format!("{}: {:?}", info.name(), data[0..len].to_vec())
187
})
188
.collect::<Vec<_>>()
189
.join(", ");
190
191
println!("{}: {}", filtered_entity.id(), terms);
192
});
193
}
194
_ => continue,
195
}
196
}
197
}
198
199
// Constructs `OwningPtr` for each item in `components`
200
// By sharing the lifetime of `components` with the resulting ptrs we ensure we don't drop the data before use
201
fn to_owning_ptrs(components: &mut [Vec<u64>]) -> Vec<OwningPtr<'_, Aligned>> {
202
components
203
.iter_mut()
204
.map(|data| {
205
let ptr = data.as_mut_ptr();
206
// SAFETY:
207
// - Pointers are guaranteed to be non-null
208
// - Memory pointed to won't be dropped until `components` is dropped
209
unsafe {
210
let non_null = NonNull::new_unchecked(ptr.cast());
211
OwningPtr::new(non_null)
212
}
213
})
214
.collect()
215
}
216
217
fn parse_term<Q: QueryData>(
218
str: &str,
219
builder: &mut QueryBuilder<Q>,
220
components: &HashMap<String, ComponentId>,
221
) {
222
let mut matched = false;
223
let str = str.trim();
224
match str.chars().next() {
225
// Optional term
226
Some('?') => {
227
builder.optional(|b| parse_term(&str[1..], b, components));
228
matched = true;
229
}
230
// Reference term
231
Some('&') => {
232
let mut parts = str.split_whitespace();
233
let first = parts.next().unwrap();
234
if first == "&mut" {
235
if let Some(str) = parts.next()
236
&& let Some(&id) = components.get(str)
237
{
238
builder.mut_id(id);
239
matched = true;
240
};
241
} else if let Some(&id) = components.get(&first[1..]) {
242
builder.ref_id(id);
243
matched = true;
244
}
245
}
246
// With term
247
Some(_) => {
248
if let Some(&id) = components.get(str) {
249
builder.with_id(id);
250
matched = true;
251
}
252
}
253
None => {}
254
};
255
256
if !matched {
257
println!("Unable to find component: {str}");
258
}
259
}
260
261
fn parse_query<Q: QueryData>(
262
str: &str,
263
builder: &mut QueryBuilder<Q>,
264
components: &HashMap<String, ComponentId>,
265
) {
266
let str = str.split(',');
267
str.for_each(|term| {
268
let sub_terms: Vec<_> = term.split("||").collect();
269
if sub_terms.len() == 1 {
270
parse_term(sub_terms[0], builder, components);
271
} else {
272
builder.or(|b| {
273
sub_terms
274
.iter()
275
.for_each(|term| parse_term(term, b, components));
276
});
277
}
278
});
279
}
280
281