Refactor ECS to reduce the dependency on a 1-to-1 mapping between components and real rust types (#2490)

# Objective

There is currently a 1-to-1 mapping between components and real rust types. This means that it is impossible for multiple components to be represented by the same rust type or for a component to not have a rust type at all. This means that component types can't be defined in languages other than rust like necessary for scripting or sandboxed (wasm?) plugins.

## Solution

Refactor `ComponentDescriptor` and `Bundle` to remove `TypeInfo`. `Bundle` now uses `ComponentId` instead. `ComponentDescriptor` is now always created from a rust type instead of through the `TypeInfo` indirection. A future PR may make it possible to construct a `ComponentDescriptor` from it's fields without a rust type being involved.
This commit is contained in:
bjorn3 2021-07-28 19:29:12 +00:00
parent 4b6238d35a
commit 86cc70b902
7 changed files with 119 additions and 167 deletions

View file

@ -110,15 +110,15 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
.map(|field| &field.ty) .map(|field| &field.ty)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let mut field_type_infos = Vec::new(); let mut field_component_ids = Vec::new();
let mut field_get_components = Vec::new(); let mut field_get_components = Vec::new();
let mut field_from_components = Vec::new(); let mut field_from_components = Vec::new();
for ((field_type, is_bundle), field) in for ((field_type, is_bundle), field) in
field_type.iter().zip(is_bundle.iter()).zip(field.iter()) field_type.iter().zip(is_bundle.iter()).zip(field.iter())
{ {
if *is_bundle { if *is_bundle {
field_type_infos.push(quote! { field_component_ids.push(quote! {
type_info.extend(<#field_type as #ecs_path::bundle::Bundle>::type_info()); component_ids.extend(<#field_type as #ecs_path::bundle::Bundle>::component_ids(components));
}); });
field_get_components.push(quote! { field_get_components.push(quote! {
self.#field.get_components(&mut func); self.#field.get_components(&mut func);
@ -127,8 +127,8 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
#field: <#field_type as #ecs_path::bundle::Bundle>::from_components(&mut func), #field: <#field_type as #ecs_path::bundle::Bundle>::from_components(&mut func),
}); });
} else { } else {
field_type_infos.push(quote! { field_component_ids.push(quote! {
type_info.push(#ecs_path::component::TypeInfo::of::<#field_type>()); component_ids.push(components.get_or_insert_id::<#field_type>());
}); });
field_get_components.push(quote! { field_get_components.push(quote! {
func((&mut self.#field as *mut #field_type).cast::<u8>()); func((&mut self.#field as *mut #field_type).cast::<u8>());
@ -147,10 +147,12 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
TokenStream::from(quote! { TokenStream::from(quote! {
/// SAFE: TypeInfo is returned in field-definition-order. [from_components] and [get_components] use field-definition-order /// SAFE: TypeInfo is returned in field-definition-order. [from_components] and [get_components] use field-definition-order
unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name#ty_generics #where_clause { unsafe impl #impl_generics #ecs_path::bundle::Bundle for #struct_name#ty_generics #where_clause {
fn type_info() -> Vec<#ecs_path::component::TypeInfo> { fn component_ids(
let mut type_info = Vec::with_capacity(#field_len); components: &mut #ecs_path::component::Components,
#(#field_type_infos)* ) -> Vec<#ecs_path::component::ComponentId> {
type_info let mut component_ids = Vec::with_capacity(#field_len);
#(#field_component_ids)*
component_ids
} }
#[allow(unused_variables, unused_mut, non_snake_case)] #[allow(unused_variables, unused_mut, non_snake_case)]

View file

@ -2,7 +2,7 @@ pub use bevy_ecs_macros::Bundle;
use crate::{ use crate::{
archetype::ComponentStatus, archetype::ComponentStatus,
component::{Component, ComponentId, ComponentTicks, Components, StorageType, TypeInfo}, component::{Component, ComponentId, ComponentTicks, Components, StorageType},
entity::Entity, entity::Entity,
storage::{SparseSetIndex, SparseSets, Table}, storage::{SparseSetIndex, SparseSets, Table},
}; };
@ -38,13 +38,13 @@ use std::{any::TypeId, collections::HashMap};
/// ``` /// ```
/// ///
/// # Safety /// # Safety
/// [Bundle::type_info] must return the TypeInfo for each component type in the bundle, in the /// [Bundle::component_id] must return the ComponentId for each component type in the bundle, in the
/// _exact_ order that [Bundle::get_components] is called. /// _exact_ order that [Bundle::get_components] is called.
/// [Bundle::from_components] must call `func` exactly once for each [TypeInfo] returned by /// [Bundle::from_components] must call `func` exactly once for each [ComponentId] returned by
/// [Bundle::type_info] /// [Bundle::component_id]
pub unsafe trait Bundle: Send + Sync + 'static { pub unsafe trait Bundle: Send + Sync + 'static {
/// Gets this [Bundle]'s components type info, in the order of this bundle's Components /// Gets this [Bundle]'s component ids, in the order of this bundle's Components
fn type_info() -> Vec<TypeInfo>; fn component_ids(components: &mut Components) -> Vec<ComponentId>;
/// Calls `func`, which should return data for each component in the bundle, in the order of /// Calls `func`, which should return data for each component in the bundle, in the order of
/// this bundle's Components /// this bundle's Components
@ -66,8 +66,9 @@ macro_rules! tuple_impl {
($($name: ident),*) => { ($($name: ident),*) => {
/// SAFE: TypeInfo is returned in tuple-order. [Bundle::from_components] and [Bundle::get_components] use tuple-order /// SAFE: TypeInfo is returned in tuple-order. [Bundle::from_components] and [Bundle::get_components] use tuple-order
unsafe impl<$($name: Component),*> Bundle for ($($name,)*) { unsafe impl<$($name: Component),*> Bundle for ($($name,)*) {
fn type_info() -> Vec<TypeInfo> { #[allow(unused_variables)]
vec![$(TypeInfo::of::<$name>()),*] fn component_ids(components: &mut Components) -> Vec<ComponentId> {
vec![$(components.get_or_insert_id::<$name>()),*]
} }
#[allow(unused_variables, unused_mut)] #[allow(unused_variables, unused_mut)]
@ -205,10 +206,12 @@ impl Bundles {
) -> &'a BundleInfo { ) -> &'a BundleInfo {
let bundle_infos = &mut self.bundle_infos; let bundle_infos = &mut self.bundle_infos;
let id = self.bundle_ids.entry(TypeId::of::<T>()).or_insert_with(|| { let id = self.bundle_ids.entry(TypeId::of::<T>()).or_insert_with(|| {
let type_info = T::type_info(); let component_ids = T::component_ids(components);
let id = BundleId(bundle_infos.len()); let id = BundleId(bundle_infos.len());
let bundle_info = // SAFE: T::component_id ensures info was created
initialize_bundle(std::any::type_name::<T>(), &type_info, id, components); let bundle_info = unsafe {
initialize_bundle(std::any::type_name::<T>(), component_ids, id, components)
};
bundle_infos.push(bundle_info); bundle_infos.push(bundle_info);
id id
}); });
@ -217,21 +220,21 @@ impl Bundles {
} }
} }
fn initialize_bundle( /// # Safety
///
/// `component_id` must be valid [ComponentId]'s
unsafe fn initialize_bundle(
bundle_type_name: &'static str, bundle_type_name: &'static str,
type_info: &[TypeInfo], component_ids: Vec<ComponentId>,
id: BundleId, id: BundleId,
components: &mut Components, components: &mut Components,
) -> BundleInfo { ) -> BundleInfo {
let mut component_ids = Vec::new();
let mut storage_types = Vec::new(); let mut storage_types = Vec::new();
for type_info in type_info { for &component_id in &component_ids {
let component_id = components.get_or_insert_with(type_info.type_id(), || type_info.clone()); // SAFE: component_id exists and is therefore valid
// SAFE: get_with_type_info ensures info was created let component_info = components.get_info_unchecked(component_id);
let info = unsafe { components.get_info_unchecked(component_id) }; storage_types.push(component_info.storage_type());
component_ids.push(component_id);
storage_types.push(info.storage_type());
} }
let mut deduped = component_ids.clone(); let mut deduped = component_ids.clone();

View file

@ -1,7 +1,3 @@
mod type_info;
pub use type_info::*;
use crate::storage::SparseSetIndex; use crate::storage::SparseSetIndex;
use std::{ use std::{
alloc::Layout, alloc::Layout,
@ -56,15 +52,8 @@ impl Default for StorageType {
#[derive(Debug)] #[derive(Debug)]
pub struct ComponentInfo { pub struct ComponentInfo {
name: String,
id: ComponentId, id: ComponentId,
type_id: Option<TypeId>, descriptor: ComponentDescriptor,
// SAFETY: This must remain private. It must only be set to "true" if this component is
// actually Send + Sync
is_send_and_sync: bool,
layout: Layout,
drop: unsafe fn(*mut u8),
storage_type: StorageType,
} }
impl ComponentInfo { impl ComponentInfo {
@ -75,44 +64,36 @@ impl ComponentInfo {
#[inline] #[inline]
pub fn name(&self) -> &str { pub fn name(&self) -> &str {
&self.name &self.descriptor.name
} }
#[inline] #[inline]
pub fn type_id(&self) -> Option<TypeId> { pub fn type_id(&self) -> Option<TypeId> {
self.type_id self.descriptor.type_id
} }
#[inline] #[inline]
pub fn layout(&self) -> Layout { pub fn layout(&self) -> Layout {
self.layout self.descriptor.layout
} }
#[inline] #[inline]
pub fn drop(&self) -> unsafe fn(*mut u8) { pub fn drop(&self) -> unsafe fn(*mut u8) {
self.drop self.descriptor.drop
} }
#[inline] #[inline]
pub fn storage_type(&self) -> StorageType { pub fn storage_type(&self) -> StorageType {
self.storage_type self.descriptor.storage_type
} }
#[inline] #[inline]
pub fn is_send_and_sync(&self) -> bool { pub fn is_send_and_sync(&self) -> bool {
self.is_send_and_sync self.descriptor.is_send_and_sync
} }
fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self { fn new(id: ComponentId, descriptor: ComponentDescriptor) -> Self {
ComponentInfo { ComponentInfo { id, descriptor }
id,
name: descriptor.name,
storage_type: descriptor.storage_type,
type_id: descriptor.type_id,
is_send_and_sync: descriptor.is_send_and_sync,
drop: descriptor.drop,
layout: descriptor.layout,
}
} }
} }
@ -142,6 +123,7 @@ impl SparseSetIndex for ComponentId {
} }
} }
#[derive(Debug)]
pub struct ComponentDescriptor { pub struct ComponentDescriptor {
name: String, name: String,
storage_type: StorageType, storage_type: StorageType,
@ -154,6 +136,11 @@ pub struct ComponentDescriptor {
} }
impl ComponentDescriptor { impl ComponentDescriptor {
// SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
unsafe fn drop_ptr<T>(x: *mut u8) {
x.cast::<T>().drop_in_place()
}
pub fn new<T: Component>(storage_type: StorageType) -> Self { pub fn new<T: Component>(storage_type: StorageType) -> Self {
Self { Self {
name: std::any::type_name::<T>().to_string(), name: std::any::type_name::<T>().to_string(),
@ -161,7 +148,18 @@ impl ComponentDescriptor {
is_send_and_sync: true, is_send_and_sync: true,
type_id: Some(TypeId::of::<T>()), type_id: Some(TypeId::of::<T>()),
layout: Layout::new::<T>(), layout: Layout::new::<T>(),
drop: TypeInfo::drop_ptr::<T>, drop: Self::drop_ptr::<T>,
}
}
fn new_non_send<T: Any>(storage_type: StorageType) -> Self {
Self {
name: std::any::type_name::<T>().to_string(),
storage_type,
is_send_and_sync: false,
type_id: Some(TypeId::of::<T>()),
layout: Layout::new::<T>(),
drop: Self::drop_ptr::<T>,
} }
} }
@ -181,19 +179,6 @@ impl ComponentDescriptor {
} }
} }
impl From<TypeInfo> for ComponentDescriptor {
fn from(type_info: TypeInfo) -> Self {
Self {
name: type_info.type_name().to_string(),
storage_type: StorageType::default(),
is_send_and_sync: type_info.is_send_and_sync(),
type_id: Some(type_info.type_id()),
drop: type_info.drop(),
layout: type_info.layout(),
}
}
}
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Components { pub struct Components {
components: Vec<ComponentInfo>, components: Vec<ComponentInfo>,
@ -231,7 +216,12 @@ impl Components {
#[inline] #[inline]
pub fn get_or_insert_id<T: Component>(&mut self) -> ComponentId { pub fn get_or_insert_id<T: Component>(&mut self) -> ComponentId {
self.get_or_insert_with(TypeId::of::<T>(), TypeInfo::of::<T>) // SAFE: The [`ComponentDescriptor`] matches the [`TypeId`]
unsafe {
self.get_or_insert_with(TypeId::of::<T>(), || {
ComponentDescriptor::new::<T>(StorageType::default())
})
}
} }
#[inline] #[inline]
@ -279,42 +269,58 @@ impl Components {
#[inline] #[inline]
pub fn get_or_insert_resource_id<T: Component>(&mut self) -> ComponentId { pub fn get_or_insert_resource_id<T: Component>(&mut self) -> ComponentId {
self.get_or_insert_resource_with(TypeId::of::<T>(), TypeInfo::of::<T>) // SAFE: The [`ComponentDescriptor`] matches the [`TypeId`]
unsafe {
self.get_or_insert_resource_with(TypeId::of::<T>(), || {
ComponentDescriptor::new::<T>(StorageType::default())
})
}
} }
#[inline] #[inline]
pub fn get_or_insert_non_send_resource_id<T: Any>(&mut self) -> ComponentId { pub fn get_or_insert_non_send_resource_id<T: Any>(&mut self) -> ComponentId {
self.get_or_insert_resource_with(TypeId::of::<T>(), TypeInfo::of_non_send_and_sync::<T>) // SAFE: The [`ComponentDescriptor`] matches the [`TypeId`]
unsafe {
self.get_or_insert_resource_with(TypeId::of::<T>(), || {
ComponentDescriptor::new_non_send::<T>(StorageType::default())
})
}
} }
/// # Safety
///
/// The [`ComponentDescriptor`] must match the [`TypeId`]
#[inline] #[inline]
fn get_or_insert_resource_with( unsafe fn get_or_insert_resource_with(
&mut self, &mut self,
type_id: TypeId, type_id: TypeId,
func: impl FnOnce() -> TypeInfo, func: impl FnOnce() -> ComponentDescriptor,
) -> ComponentId { ) -> ComponentId {
let components = &mut self.components; let components = &mut self.components;
let index = self.resource_indices.entry(type_id).or_insert_with(|| { let index = self.resource_indices.entry(type_id).or_insert_with(|| {
let type_info = func(); let descriptor = func();
let index = components.len(); let index = components.len();
components.push(ComponentInfo::new(ComponentId(index), type_info.into())); components.push(ComponentInfo::new(ComponentId(index), descriptor));
index index
}); });
ComponentId(*index) ComponentId(*index)
} }
/// # Safety
///
/// The [`ComponentDescriptor`] must match the [`TypeId`]
#[inline] #[inline]
pub(crate) fn get_or_insert_with( pub(crate) unsafe fn get_or_insert_with(
&mut self, &mut self,
type_id: TypeId, type_id: TypeId,
func: impl FnOnce() -> TypeInfo, func: impl FnOnce() -> ComponentDescriptor,
) -> ComponentId { ) -> ComponentId {
let components = &mut self.components; let components = &mut self.components;
let index = self.indices.entry(type_id).or_insert_with(|| { let index = self.indices.entry(type_id).or_insert_with(|| {
let type_info = func(); let descriptor = func();
let index = components.len(); let index = components.len();
components.push(ComponentInfo::new(ComponentId(index), type_info.into())); components.push(ComponentInfo::new(ComponentId(index), descriptor));
index index
}); });

View file

@ -1,63 +0,0 @@
use std::{alloc::Layout, any::TypeId};
/// Metadata required to store a component.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeInfo {
type_id: TypeId,
layout: Layout,
drop: unsafe fn(*mut u8),
type_name: &'static str,
is_send_and_sync: bool,
}
impl TypeInfo {
/// Metadata for `T`.
pub fn of<T: Send + Sync + 'static>() -> Self {
Self {
type_id: TypeId::of::<T>(),
layout: Layout::new::<T>(),
is_send_and_sync: true,
drop: Self::drop_ptr::<T>,
type_name: core::any::type_name::<T>(),
}
}
pub fn of_non_send_and_sync<T: 'static>() -> Self {
Self {
type_id: TypeId::of::<T>(),
layout: Layout::new::<T>(),
is_send_and_sync: false,
drop: Self::drop_ptr::<T>,
type_name: core::any::type_name::<T>(),
}
}
#[inline]
pub fn type_id(&self) -> TypeId {
self.type_id
}
#[inline]
pub fn layout(&self) -> Layout {
self.layout
}
#[inline]
pub fn drop(&self) -> unsafe fn(*mut u8) {
self.drop
}
#[inline]
pub fn is_send_and_sync(&self) -> bool {
self.is_send_and_sync
}
#[inline]
pub fn type_name(&self) -> &'static str {
self.type_name
}
pub(crate) unsafe fn drop_ptr<T>(x: *mut u8) {
x.cast::<T>().drop_in_place()
}
}

View file

@ -41,7 +41,7 @@ mod tests {
use crate as bevy_ecs; use crate as bevy_ecs;
use crate::{ use crate::{
bundle::Bundle, bundle::Bundle,
component::{Component, ComponentDescriptor, ComponentId, StorageType, TypeInfo}, component::{Component, ComponentDescriptor, ComponentId, StorageType},
entity::Entity, entity::Entity,
query::{ query::{
Added, ChangeTrackers, Changed, FilterFetch, FilteredAccess, With, Without, WorldQuery, Added, ChangeTrackers, Changed, FilterFetch, FilteredAccess, With, Without, WorldQuery,
@ -102,21 +102,26 @@ mod tests {
#[test] #[test]
fn bundle_derive() { fn bundle_derive() {
let mut world = World::new();
#[derive(Bundle, PartialEq, Debug)] #[derive(Bundle, PartialEq, Debug)]
struct Foo { struct Foo {
x: &'static str, x: &'static str,
y: i32, y: i32,
} }
assert_eq!(
<Foo as Bundle>::type_info(),
vec![TypeInfo::of::<&'static str>(), TypeInfo::of::<i32>(),]
);
let mut world = World::new();
world world
.register_component(ComponentDescriptor::new::<i32>(StorageType::SparseSet)) .register_component(ComponentDescriptor::new::<i32>(StorageType::SparseSet))
.unwrap(); .unwrap();
assert_eq!(
<Foo as Bundle>::component_ids(world.components_mut()),
vec![
world.components_mut().get_or_insert_id::<&'static str>(),
world.components_mut().get_or_insert_id::<i32>(),
]
);
let e1 = world.spawn().insert_bundle(Foo { x: "abc", y: 123 }).id(); let e1 = world.spawn().insert_bundle(Foo { x: "abc", y: 123 }).id();
let e2 = world.spawn().insert_bundle(("def", 456, true)).id(); let e2 = world.spawn().insert_bundle(("def", 456, true)).id();
assert_eq!(*world.get::<&str>(e1).unwrap(), "abc"); assert_eq!(*world.get::<&str>(e1).unwrap(), "abc");
@ -146,12 +151,12 @@ mod tests {
} }
assert_eq!( assert_eq!(
<Nested as Bundle>::type_info(), <Nested as Bundle>::component_ids(world.components_mut()),
vec![ vec![
TypeInfo::of::<usize>(), world.components_mut().get_or_insert_id::<usize>(),
TypeInfo::of::<&'static str>(), world.components_mut().get_or_insert_id::<&'static str>(),
TypeInfo::of::<i32>(), world.components_mut().get_or_insert_id::<i32>(),
TypeInfo::of::<u8>(), world.components_mut().get_or_insert_id::<u8>(),
] ]
); );

View file

@ -267,9 +267,13 @@ const fn padding_needed_for(layout: &Layout, align: usize) -> usize {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::BlobVec; use super::BlobVec;
use crate::component::TypeInfo;
use std::{alloc::Layout, cell::RefCell, rc::Rc}; use std::{alloc::Layout, cell::RefCell, rc::Rc};
// SAFETY: The pointer points to a valid value of type `T` and it is safe to drop this value.
unsafe fn drop_ptr<T>(x: *mut u8) {
x.cast::<T>().drop_in_place()
}
/// # Safety /// # Safety
/// ///
/// `blob_vec` must have a layout that matches Layout::new::<T>() /// `blob_vec` must have a layout that matches Layout::new::<T>()
@ -300,7 +304,7 @@ mod tests {
#[test] #[test]
fn resize_test() { fn resize_test() {
let item_layout = Layout::new::<usize>(); let item_layout = Layout::new::<usize>();
let drop = TypeInfo::drop_ptr::<usize>; let drop = drop_ptr::<usize>;
let mut blob_vec = BlobVec::new(item_layout, drop, 64); let mut blob_vec = BlobVec::new(item_layout, drop, 64);
unsafe { unsafe {
for i in 0..1_000 { for i in 0..1_000 {
@ -330,7 +334,7 @@ mod tests {
let drop_counter = Rc::new(RefCell::new(0)); let drop_counter = Rc::new(RefCell::new(0));
{ {
let item_layout = Layout::new::<Foo>(); let item_layout = Layout::new::<Foo>();
let drop = TypeInfo::drop_ptr::<Foo>; let drop = drop_ptr::<Foo>;
let mut blob_vec = BlobVec::new(item_layout, drop, 2); let mut blob_vec = BlobVec::new(item_layout, drop, 2);
assert_eq!(blob_vec.capacity(), 2); assert_eq!(blob_vec.capacity(), 2);
unsafe { unsafe {
@ -390,7 +394,7 @@ mod tests {
#[test] #[test]
fn blob_vec_drop_empty_capacity() { fn blob_vec_drop_empty_capacity() {
let item_layout = Layout::new::<Foo>(); let item_layout = Layout::new::<Foo>();
let drop = TypeInfo::drop_ptr::<Foo>; let drop = drop_ptr::<Foo>;
let _ = BlobVec::new(item_layout, drop, 0); let _ = BlobVec::new(item_layout, drop, 0);
} }
} }

View file

@ -500,17 +500,12 @@ impl IndexMut<TableId> for Tables {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{ use crate::{component::Components, entity::Entity, storage::Table};
component::{Components, TypeInfo},
entity::Entity,
storage::Table,
};
#[test] #[test]
fn table() { fn table() {
let mut components = Components::default(); let mut components = Components::default();
let type_info = TypeInfo::of::<usize>(); let component_id = components.get_or_insert_id::<usize>();
let component_id = components.get_or_insert_with(type_info.type_id(), || type_info);
let columns = &[component_id]; let columns = &[component_id];
let mut table = Table::with_capacity(0, columns.len()); let mut table = Table::with_capacity(0, columns.len());
table.add_column(components.get_info(component_id).unwrap()); table.add_column(components.get_info(component_id).unwrap());