mirror of
https://github.com/bevyengine/bevy
synced 2024-12-24 20:13:07 +00:00
711246aa34
Updating dependencies; adopted version of #15696. (Supercedes #15696.) Long answer: hashbrown is no longer using ahash by default, meaning that we can't use the default-hasher methods with ahasher. So, we have to use the longer-winded versions instead. This takes the opportunity to also switch our default hasher as well, but without actually enabling the default-hasher feature for hashbrown, meaning that we'll be able to change our hasher more easily at the cost of all of these method calls being obnoxious forever. One large change from 0.15 is that `insert_unique_unchecked` is now `unsafe`, and for cases where unsafe code was denied at the crate level, I replaced it with `insert`. ## Migration Guide `bevy_utils` has updated its version of `hashbrown` to 0.15 and now defaults to `foldhash` instead of `ahash`. This means that if you've hard-coded your hasher to `bevy_utils::AHasher` or separately used the `ahash` crate in your code, you may need to switch to `foldhash` to ensure that everything works like it does in Bevy.
273 lines
9.8 KiB
Rust
273 lines
9.8 KiB
Rust
#![allow(unsafe_code)]
|
|
|
|
//! This example show how you can create components dynamically, spawn entities with those components
|
|
//! as well as query for entities with those components.
|
|
|
|
use std::{alloc::Layout, collections::HashMap, io::Write, ptr::NonNull};
|
|
|
|
use bevy::{
|
|
ecs::{
|
|
component::{ComponentDescriptor, ComponentId, ComponentInfo, StorageType},
|
|
query::QueryData,
|
|
world::FilteredEntityMut,
|
|
},
|
|
prelude::*,
|
|
ptr::{Aligned, OwningPtr},
|
|
};
|
|
|
|
const PROMPT: &str = "
|
|
Commands:
|
|
comp, c Create new components
|
|
spawn, s Spawn entities
|
|
query, q Query for entities
|
|
Enter a command with no parameters for usage.";
|
|
|
|
const COMPONENT_PROMPT: &str = "
|
|
comp, c Create new components
|
|
Enter a comma separated list of type names optionally followed by a size in u64s.
|
|
e.g. CompA 3, CompB, CompC 2";
|
|
|
|
const ENTITY_PROMPT: &str = "
|
|
spawn, s Spawn entities
|
|
Enter a comma separated list of components optionally followed by values.
|
|
e.g. CompA 0 1 0, CompB, CompC 1";
|
|
|
|
const QUERY_PROMPT: &str = "
|
|
query, q Query for entities
|
|
Enter a query to fetch and update entities
|
|
Components with read or write access will be displayed with their values
|
|
Components with write access will have their fields incremented by one
|
|
|
|
Accesses: 'A' with, '&A' read, '&mut A' write
|
|
Operators: '||' or, ',' and, '?' optional
|
|
|
|
e.g. &A || &B, &mut C, D, ?E";
|
|
|
|
fn main() {
|
|
let mut world = World::new();
|
|
let mut lines = std::io::stdin().lines();
|
|
let mut component_names = HashMap::<String, ComponentId>::new();
|
|
let mut component_info = HashMap::<ComponentId, ComponentInfo>::new();
|
|
|
|
println!("{PROMPT}");
|
|
loop {
|
|
print!("\n> ");
|
|
let _ = std::io::stdout().flush();
|
|
let Some(Ok(line)) = lines.next() else {
|
|
return;
|
|
};
|
|
|
|
if line.is_empty() {
|
|
return;
|
|
};
|
|
|
|
let Some((first, rest)) = line.trim().split_once(|c: char| c.is_whitespace()) else {
|
|
match &line.chars().next() {
|
|
Some('c') => println!("{COMPONENT_PROMPT}"),
|
|
Some('s') => println!("{ENTITY_PROMPT}"),
|
|
Some('q') => println!("{QUERY_PROMPT}"),
|
|
_ => println!("{PROMPT}"),
|
|
}
|
|
continue;
|
|
};
|
|
|
|
match &first[0..1] {
|
|
"c" => {
|
|
rest.split(',').for_each(|component| {
|
|
let mut component = component.split_whitespace();
|
|
let Some(name) = component.next() else {
|
|
return;
|
|
};
|
|
let size = match component.next().map(str::parse) {
|
|
Some(Ok(size)) => size,
|
|
_ => 0,
|
|
};
|
|
// Register our new component to the world with a layout specified by it's size
|
|
// SAFETY: [u64] is Send + Sync
|
|
let id = world.register_component_with_descriptor(unsafe {
|
|
ComponentDescriptor::new_with_layout(
|
|
name.to_string(),
|
|
StorageType::Table,
|
|
Layout::array::<u64>(size).unwrap(),
|
|
None,
|
|
true,
|
|
)
|
|
});
|
|
let Some(info) = world.components().get_info(id) else {
|
|
return;
|
|
};
|
|
component_names.insert(name.to_string(), id);
|
|
component_info.insert(id, info.clone());
|
|
println!("Component {} created with id: {:?}", name, id.index());
|
|
});
|
|
}
|
|
"s" => {
|
|
let mut to_insert_ids = Vec::new();
|
|
let mut to_insert_data = Vec::new();
|
|
rest.split(',').for_each(|component| {
|
|
let mut component = component.split_whitespace();
|
|
let Some(name) = component.next() else {
|
|
return;
|
|
};
|
|
|
|
// Get the id for the component with the given name
|
|
let Some(&id) = component_names.get(name) else {
|
|
println!("Component {name} does not exist");
|
|
return;
|
|
};
|
|
|
|
// Calculate the length for the array based on the layout created for this component id
|
|
let info = world.components().get_info(id).unwrap();
|
|
let len = info.layout().size() / size_of::<u64>();
|
|
let mut values: Vec<u64> = component
|
|
.take(len)
|
|
.filter_map(|value| value.parse::<u64>().ok())
|
|
.collect();
|
|
values.resize(len, 0);
|
|
|
|
// Collect the id and array to be inserted onto our entity
|
|
to_insert_ids.push(id);
|
|
to_insert_data.push(values);
|
|
});
|
|
|
|
let mut entity = world.spawn_empty();
|
|
|
|
// Construct an `OwningPtr` for each component in `to_insert_data`
|
|
let to_insert_ptr = to_owning_ptrs(&mut to_insert_data);
|
|
|
|
// SAFETY:
|
|
// - Component ids have been taken from the same world
|
|
// - Each array is created to the layout specified in the world
|
|
unsafe {
|
|
entity.insert_by_ids(&to_insert_ids, to_insert_ptr.into_iter());
|
|
}
|
|
|
|
println!("Entity spawned with id: {:?}", entity.id());
|
|
}
|
|
"q" => {
|
|
let mut builder = QueryBuilder::<FilteredEntityMut>::new(&mut world);
|
|
parse_query(rest, &mut builder, &component_names);
|
|
let mut query = builder.build();
|
|
|
|
query.iter_mut(&mut world).for_each(|filtered_entity| {
|
|
#[allow(deprecated)]
|
|
let terms = filtered_entity
|
|
.access()
|
|
.component_reads_and_writes()
|
|
.0
|
|
.map(|id| {
|
|
let ptr = filtered_entity.get_by_id(id).unwrap();
|
|
let info = component_info.get(&id).unwrap();
|
|
let len = info.layout().size() / size_of::<u64>();
|
|
|
|
// SAFETY:
|
|
// - All components are created with layout [u64]
|
|
// - len is calculated from the component descriptor
|
|
let data = unsafe {
|
|
std::slice::from_raw_parts_mut(
|
|
ptr.assert_unique().as_ptr().cast::<u64>(),
|
|
len,
|
|
)
|
|
};
|
|
|
|
// If we have write access, increment each value once
|
|
if filtered_entity.access().has_component_write(id) {
|
|
data.iter_mut().for_each(|data| {
|
|
*data += 1;
|
|
});
|
|
}
|
|
|
|
format!("{}: {:?}", info.name(), data[0..len].to_vec())
|
|
})
|
|
.collect::<Vec<_>>()
|
|
.join(", ");
|
|
|
|
println!("{:?}: {}", filtered_entity.id(), terms);
|
|
});
|
|
}
|
|
_ => continue,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Constructs `OwningPtr` for each item in `components`
|
|
// By sharing the lifetime of `components` with the resulting ptrs we ensure we don't drop the data before use
|
|
fn to_owning_ptrs(components: &mut [Vec<u64>]) -> Vec<OwningPtr<Aligned>> {
|
|
components
|
|
.iter_mut()
|
|
.map(|data| {
|
|
let ptr = data.as_mut_ptr();
|
|
// SAFETY:
|
|
// - Pointers are guaranteed to be non-null
|
|
// - Memory pointed to won't be dropped until `components` is dropped
|
|
unsafe {
|
|
let non_null = NonNull::new_unchecked(ptr.cast());
|
|
OwningPtr::new(non_null)
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn parse_term<Q: QueryData>(
|
|
str: &str,
|
|
builder: &mut QueryBuilder<Q>,
|
|
components: &HashMap<String, ComponentId>,
|
|
) {
|
|
let mut matched = false;
|
|
let str = str.trim();
|
|
match str.chars().next() {
|
|
// Optional term
|
|
Some('?') => {
|
|
builder.optional(|b| parse_term(&str[1..], b, components));
|
|
matched = true;
|
|
}
|
|
// Reference term
|
|
Some('&') => {
|
|
let mut parts = str.split_whitespace();
|
|
let first = parts.next().unwrap();
|
|
if first == "&mut" {
|
|
if let Some(str) = parts.next() {
|
|
if let Some(&id) = components.get(str) {
|
|
builder.mut_id(id);
|
|
matched = true;
|
|
}
|
|
};
|
|
} else if let Some(&id) = components.get(&first[1..]) {
|
|
builder.ref_id(id);
|
|
matched = true;
|
|
}
|
|
}
|
|
// With term
|
|
Some(_) => {
|
|
if let Some(&id) = components.get(str) {
|
|
builder.with_id(id);
|
|
matched = true;
|
|
}
|
|
}
|
|
None => {}
|
|
};
|
|
|
|
if !matched {
|
|
println!("Unable to find component: {str}");
|
|
}
|
|
}
|
|
|
|
fn parse_query<Q: QueryData>(
|
|
str: &str,
|
|
builder: &mut QueryBuilder<Q>,
|
|
components: &HashMap<String, ComponentId>,
|
|
) {
|
|
let str = str.split(',');
|
|
str.for_each(|term| {
|
|
let sub_terms: Vec<_> = term.split("||").collect();
|
|
if sub_terms.len() == 1 {
|
|
parse_term(sub_terms[0], builder, components);
|
|
} else {
|
|
builder.or(|b| {
|
|
sub_terms
|
|
.iter()
|
|
.for_each(|term| parse_term(term, b, components));
|
|
});
|
|
}
|
|
});
|
|
}
|