Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ecs/dynamic.rs
6592 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
)
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
// Get the id for the component with the given name
120
let Some(&id) = component_names.get(name) else {
121
println!("Component {name} does not exist");
122
return;
123
};
124
125
// Calculate the length for the array based on the layout created for this component id
126
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
// Collect the id and array to be inserted onto our entity
135
to_insert_ids.push(id);
136
to_insert_data.push(values);
137
});
138
139
let mut entity = world.spawn_empty();
140
141
// Construct an `OwningPtr` for each component in `to_insert_data`
142
let to_insert_ptr = to_owning_ptrs(&mut to_insert_data);
143
144
// SAFETY:
145
// - Component ids have been taken from the same world
146
// - Each array is created to the layout specified in the world
147
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
// SAFETY:
169
// - All components are created with layout [u64]
170
// - len is calculated from the component descriptor
171
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 we have write access, increment each value once
179
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
198
// Constructs `OwningPtr` for each item in `components`
199
// By sharing the lifetime of `components` with the resulting ptrs we ensure we don't drop the data before use
200
fn 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
// SAFETY:
206
// - Pointers are guaranteed to be non-null
207
// - Memory pointed to won't be dropped until `components` is dropped
208
unsafe {
209
let non_null = NonNull::new_unchecked(ptr.cast());
210
OwningPtr::new(non_null)
211
}
212
})
213
.collect()
214
}
215
216
fn 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
// Optional term
225
Some('?') => {
226
builder.optional(|b| parse_term(&str[1..], b, components));
227
matched = true;
228
}
229
// Reference term
230
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
// With term
246
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
260
fn 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
}
279
280