use crate::{1component::{Component, ComponentId, Components, ComponentsRegistrator},2relationship::RelationshipHookMode,3world::EntityWorldMut,4};5use alloc::vec::Vec;6use bevy_ptr::OwningPtr;7use bumpalo::Bump;8use core::{alloc::Layout, ptr::NonNull};910/// Enables pushing components to internal scratch space (uses a bump allocator), which can then be11/// written as a dynamic bundle. The contents are cleared after each write and the allocated scratch12/// space is reused across writes.13///14/// Also see [`BundleWriter`].15#[derive(Default)]16pub struct BundleScratch {17// Correctness: this should never be made public or mismatched component ids could be inserted18component_ids: Vec<ComponentId>,19// Correctness: this should never be made public or arbitrary non-components could be inserted20component_ptrs: Vec<NonNull<u8>>,21// Safety: this cannot be exposed, otherwise `alloc.reset()` could be called in arbitrary places,22// which could invalidate the data stored in `component_ptrs`.23alloc: Bump,24}2526impl BundleScratch {27/// Creates a new [`BundleWriter`] using this scratch space. For safety / correctness, this will28/// clear any existing components.29///30/// Note that for performance reasons this will _not_ clear the internal allocator. To avoid leaking,31/// make sure every component pushed to the [`BundleWriter`] is followed by either a32/// [`BundleWriter::write`] or a [`BundleScratch::manual_drop`].33#[inline]34pub fn writer<'a>(&'a mut self) -> BundleWriter<'a> {35// This is necessary to ensure safety / correctness is maintained in the context of catch_unwind36// or a skipped `write`37self.component_ids.clear();38self.component_ptrs.clear();39BundleWriter(self)40}4142/// Returns true if there are currently no components stored in the scratch space.43#[inline]44pub fn is_empty(&self) -> bool {45self.component_ids.is_empty()46}4748/// This will drop all components currently stored in the scratch space. This is generally used to49/// ensure drops occur in error scenarios.50///51/// # Safety52/// `components` must be from the same world as the components that were pushed to this writer.53pub unsafe fn manual_drop(&mut self, components: &Components) {54for (id, ptr) in self55.component_ids56.drain(..)57.zip(self.component_ptrs.drain(..))58{59if let Some(info) = components.get_info(id)60&& let Some(drop) = info.drop()61{62// SAFETY: ptr is a valid component that matches the given component id63unsafe {64let ptr = OwningPtr::new(ptr);65(drop)(ptr);66}67}68}69self.alloc.reset();70}71}7273/// Enables pushing components to the internal [`BundleScratch`], which can then be74/// written as a dynamic bundle.75///76/// Components pushed to this writer should either be followed by a [`BundleWriter::write`] or a77/// [`BundleScratch::manual_drop`] to avoid leaking.78pub struct BundleWriter<'a>(&'a mut BundleScratch);7980// SAFETY: The `NonNull`s in component_ptrs are always a `Component`, which is Send81unsafe impl Send for BundleScratch where Bump: Send {}8283impl<'a> BundleWriter<'a> {84/// Pushes the given component to the back of the current bundle scratch space. It will register85/// the component in `components` if it does not already exist.86///87/// # Safety88///89/// `components` must be from the same world that all previous [`Self::push_component`] calls were called with,90/// and the _next_ [`Self::write`] call.91pub unsafe fn push_component<C: Component>(92&mut self,93components: &mut ComponentsRegistrator,94component: C,95) {96let id = components.register_component::<C>();97OwningPtr::make(component, |ptr| {98// SAFETY: ptr points to a C component value which matches the `id` looked up above.99// Layout matches C.100self.push_component_by_id(id, ptr, Layout::new::<C>());101});102}103104/// Pushes the given component ptr to the back of the current bundle scratch space.105///106/// # Safety107///108/// `components` must be from the same world that all previous [`Self::push_component`] calls were called with,109/// and the _next_ [`Self::write`] call. `component` must point to a [`Component`] value that matches `id`.110/// `layout` must correspond to the layout of the [`Component`] type.111pub unsafe fn push_component_by_id(112&mut self,113id: ComponentId,114component: OwningPtr<'_>,115layout: Layout,116) {117let ptr = self.0.alloc.alloc_layout(layout);118core::ptr::copy(component.as_ptr(), ptr.as_ptr(), layout.size());119self.0.component_ids.push(id);120self.0.component_ptrs.push(ptr);121}122123/// Writes the current contents of the bundle to the given `entity` and clears the scratch space.124///125/// # Safety126///127/// `entity` must be from the same world that all [`Self::push_component`] calls since the last128/// [`Self::write`] were called with.129pub unsafe fn write(self, entity: &mut EntityWorldMut) {130// SAFETY:131// - All `component_ids` are from the same world as `entity`132// - All `component_data_ptrs` are valid types represented by `component_ids`133unsafe {134entity.insert_by_ids_internal(135&self.0.component_ids,136self.0137.component_ptrs138.drain(..)139.map(|ptr| OwningPtr::new(ptr)),140RelationshipHookMode::Run,141);142}143self.0.component_ids.clear();144self.0.alloc.reset();145}146147/// Returns true if there are currently no components.148#[inline]149pub fn is_empty(&self) -> bool {150self.0.component_ids.is_empty()151}152}153154#[cfg(test)]155mod tests {156use crate::{bundle::BundleScratch, component::Component, name::Name, world::World};157158#[test]159fn write_component() {160#[derive(Component)]161struct X;162163let mut world = World::new();164let mut bundle_scratch = BundleScratch::default();165let mut bundle_writer = bundle_scratch.writer();166// SAFETY: the same world is used for every bundle_writer operation167unsafe {168let mut components = world.components_registrator();169bundle_writer.push_component(&mut components, X);170bundle_writer.push_component(&mut components, Name::new("Hi"));171let mut entity = world.spawn_empty();172bundle_writer.write(&mut entity);173174assert_eq!(entity.get::<Name>().unwrap().as_str(), "Hi");175assert!(entity.contains::<X>());176}177}178}179180181