use std::{alloc::Layout, any::TypeId, ptr::NonNull};
use bevy_app::App;
use bevy_ecs::{
component::{Component, ComponentDescriptor, ComponentId},
entity::Entity,
system::{EntityCommands, Resource},
world::{Command, EntityWorldMut, World},
};
use bevy_ptr::OwningPtr;
use bevy_utils::HashMap;
pub mod prelude {
pub use super::{
dynamic_components_plugin, DynamicComponentsEntityExt, DynamicComponentsWorldExt,
};
}
pub fn dynamic_components_plugin(app: &mut App) {
app.init_resource::<DynamicComponentRegistry>();
}
#[derive(Resource, Default, Debug)]
pub struct DynamicComponentRegistry {
component_types: HashMap<ComponentId, TypeId>,
}
pub trait DynamicComponentsWorldExt {
fn dynamic_component<T: Component>(&mut self) -> ComponentId;
}
impl DynamicComponentsWorldExt for World {
fn dynamic_component<T: Component>(&mut self) -> ComponentId {
assert!(self.get_resource::<DynamicComponentRegistry>().is_some(), "`DynamicComponentRegistry` was not in the world! Make sure to add `dynamic_components_plugin` before using this API.");
unsafe fn drop<T>(this: OwningPtr) {
unsafe {
this.drop_as::<T>();
}
}
let descriptor = unsafe {
ComponentDescriptor::new_with_layout(
std::any::type_name::<T>(),
T::STORAGE_TYPE,
Layout::new::<T>(),
Some(drop::<T>),
)
};
let id = self.register_component_with_descriptor(descriptor);
self.resource_mut::<DynamicComponentRegistry>()
.component_types
.insert_unique_unchecked(id, TypeId::of::<T>());
id
}
}
pub trait DynamicComponentsEntityExt {
fn insert_dynamic<T: Component>(&mut self, component_id: ComponentId, data: T) -> &mut Self;
}
impl DynamicComponentsEntityExt for EntityWorldMut<'_> {
fn insert_dynamic<T: Component>(
&mut self,
component_id: ComponentId,
mut data: T,
) -> &mut Self {
assert_eq!(
*self
.world()
.get_resource::<DynamicComponentRegistry>()
.expect("`DynamicComponentRegistry` was not in the world! Make sure to add `dynamic_components_plugin` before using this API.")
.component_types
.get(&component_id)
.expect("Can't insert component with invalid id! The id must have been returned from `world.dynamic_component()` for this `World`."),
TypeId::of::<T>(),
"Incorrect type arguments supplied to `insert_dynamic`! `T` must be the same type `component_id` was created with."
);
let data_ptr = (&mut data as *mut T).cast::<u8>();
let data_ptr = unsafe { NonNull::new_unchecked(data_ptr) };
let data_ptr = unsafe { OwningPtr::new(data_ptr) };
std::mem::forget(data);
unsafe { self.insert_by_id(component_id, data_ptr) }
}
}
impl DynamicComponentsEntityExt for EntityCommands<'_> {
fn insert_dynamic<T: Component>(&mut self, component_id: ComponentId, data: T) -> &mut Self {
let entity = self.id();
self.commands().queue(InsertDynamic {
entity,
component_id,
data,
});
self
}
}
pub struct InsertDynamic<T: Component> {
pub entity: Entity,
pub component_id: ComponentId,
pub data: T,
}
impl<T: Component> Command for InsertDynamic<T> {
fn apply(self, world: &mut World) {
world
.entity_mut(self.entity)
.insert_dynamic(self.component_id, self.data);
}
}