bevy_ecs: add untyped methods for inserting components and bundles (#7204)
This MR is a rebased and alternative proposal to https://github.com/bevyengine/bevy/pull/5602 # Objective - https://github.com/bevyengine/bevy/pull/4447 implemented untyped (using component ids instead of generics and TypeId) APIs for inserting/accessing resources and accessing components, but left inserting components for another PR (this one) ## Solution - add `EntityMut::insert_by_id` - split `Bundle` into `DynamicBundle` with `get_components` and `Bundle: DynamicBundle`. This allows the `BundleInserter` machinery to be reused for bundles that can only be written, not read, and have no statically available `ComponentIds` - Compared to the original MR this approach exposes unsafe endpoints and requires the user to manage instantiated `BundleIds`. This is quite easy for the end user to do and does not incur the performance penalty of checking whether component input is correctly provided for the `BundleId`. - This MR does ensure that constructing `BundleId` itself is safe --- ## Changelog - add methods for inserting bundles and components to: `world.entity_mut(entity).insert_by_id`
This commit is contained in:
		
							parent
							
								
									3b51e1c8d9
								
							
						
					
					
						commit
						caa662272c
					
				@ -126,7 +126,9 @@ pub fn derive_bundle(input: TokenStream) -> TokenStream {
 | 
			
		||||
                    #(#field_from_components)*
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl #impl_generics #ecs_path::bundle::DynamicBundle for #struct_name #ty_generics #where_clause {
 | 
			
		||||
            #[allow(unused_variables)]
 | 
			
		||||
            #[inline]
 | 
			
		||||
            fn get_components(
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
//! This module contains the [`Bundle`] trait and some other helper types.
 | 
			
		||||
 | 
			
		||||
pub use bevy_ecs_macros::Bundle;
 | 
			
		||||
use bevy_utils::HashSet;
 | 
			
		||||
use bevy_utils::{HashMap, HashSet};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    archetype::{
 | 
			
		||||
@ -135,10 +135,10 @@ use std::any::TypeId;
 | 
			
		||||
/// [`Query`]: crate::system::Query
 | 
			
		||||
// Some safety points:
 | 
			
		||||
// - [`Bundle::component_ids`] must return the [`ComponentId`] for each component type in the
 | 
			
		||||
// bundle, in the _exact_ order that [`Bundle::get_components`] is called.
 | 
			
		||||
// bundle, in the _exact_ order that [`DynamicBundle::get_components`] is called.
 | 
			
		||||
// - [`Bundle::from_components`] must call `func` exactly once for each [`ComponentId`] returned by
 | 
			
		||||
//   [`Bundle::component_ids`].
 | 
			
		||||
pub unsafe trait Bundle: Send + Sync + 'static {
 | 
			
		||||
pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static {
 | 
			
		||||
    /// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s
 | 
			
		||||
    #[doc(hidden)]
 | 
			
		||||
    fn component_ids(
 | 
			
		||||
@ -159,7 +159,10 @@ pub unsafe trait Bundle: Send + Sync + 'static {
 | 
			
		||||
        // Ensure that the `OwningPtr` is used correctly
 | 
			
		||||
        F: for<'a> FnMut(&'a mut T) -> OwningPtr<'a>,
 | 
			
		||||
        Self: Sized;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The parts from [`Bundle`] that don't require statically knowing the components of the bundle.
 | 
			
		||||
pub trait DynamicBundle {
 | 
			
		||||
    // SAFETY:
 | 
			
		||||
    // The `StorageType` argument passed into [`Bundle::get_components`] must be correct for the
 | 
			
		||||
    // component being fetched.
 | 
			
		||||
@ -192,7 +195,9 @@ unsafe impl<C: Component> Bundle for C {
 | 
			
		||||
        // Safety: The id given in `component_ids` is for `Self`
 | 
			
		||||
        func(ctx).read()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<C: Component> DynamicBundle for C {
 | 
			
		||||
    #[inline]
 | 
			
		||||
    fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
 | 
			
		||||
        OwningPtr::make(self, |ptr| func(C::Storage::STORAGE_TYPE, ptr));
 | 
			
		||||
@ -203,7 +208,7 @@ macro_rules! tuple_impl {
 | 
			
		||||
    ($($name: ident),*) => {
 | 
			
		||||
        // SAFETY:
 | 
			
		||||
        // - `Bundle::component_ids` calls `ids` for each component type in the
 | 
			
		||||
        // bundle, in the exact order that `Bundle::get_components` is called.
 | 
			
		||||
        // bundle, in the exact order that `DynamicBundle::get_components` is called.
 | 
			
		||||
        // - `Bundle::from_components` calls `func` exactly once for each `ComponentId` returned by `Bundle::component_ids`.
 | 
			
		||||
        // - `Bundle::get_components` is called exactly once for each member. Relies on the above implementation to pass the correct
 | 
			
		||||
        //   `StorageType` into the callback.
 | 
			
		||||
@ -223,7 +228,9 @@ macro_rules! tuple_impl {
 | 
			
		||||
                // https://doc.rust-lang.org/reference/expressions.html#evaluation-order-of-operands
 | 
			
		||||
                ($(<$name as Bundle>::from_components(ctx, func),)*)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        impl<$($name: Bundle),*> DynamicBundle for ($($name,)*) {
 | 
			
		||||
            #[allow(unused_variables, unused_mut)]
 | 
			
		||||
            #[inline(always)]
 | 
			
		||||
            fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
 | 
			
		||||
@ -376,7 +383,7 @@ impl BundleInfo {
 | 
			
		||||
    /// `entity`, `bundle` must match this [`BundleInfo`]'s type
 | 
			
		||||
    #[inline]
 | 
			
		||||
    #[allow(clippy::too_many_arguments)]
 | 
			
		||||
    unsafe fn write_components<T: Bundle, S: BundleComponentStatus>(
 | 
			
		||||
    unsafe fn write_components<T: DynamicBundle, S: BundleComponentStatus>(
 | 
			
		||||
        &self,
 | 
			
		||||
        table: &mut Table,
 | 
			
		||||
        sparse_sets: &mut SparseSets,
 | 
			
		||||
@ -527,7 +534,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
 | 
			
		||||
    /// `entity` must currently exist in the source archetype for this inserter. `archetype_row`
 | 
			
		||||
    /// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub unsafe fn insert<T: Bundle>(
 | 
			
		||||
    pub unsafe fn insert<T: DynamicBundle>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        entity: Entity,
 | 
			
		||||
        location: EntityLocation,
 | 
			
		||||
@ -677,7 +684,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
 | 
			
		||||
    /// # Safety
 | 
			
		||||
    /// `entity` must be allocated (but non-existent), `T` must match this [`BundleInfo`]'s type
 | 
			
		||||
    #[inline]
 | 
			
		||||
    pub unsafe fn spawn_non_existent<T: Bundle>(
 | 
			
		||||
    pub unsafe fn spawn_non_existent<T: DynamicBundle>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        entity: Entity,
 | 
			
		||||
        bundle: T,
 | 
			
		||||
@ -712,7 +719,12 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
 | 
			
		||||
#[derive(Default)]
 | 
			
		||||
pub struct Bundles {
 | 
			
		||||
    bundle_infos: Vec<BundleInfo>,
 | 
			
		||||
    /// Cache static [`BundleId`]
 | 
			
		||||
    bundle_ids: TypeIdMap<BundleId>,
 | 
			
		||||
    /// Cache dynamic [`BundleId`] with multiple components
 | 
			
		||||
    dynamic_bundle_ids: HashMap<Vec<ComponentId>, (BundleId, Vec<StorageType>)>,
 | 
			
		||||
    /// Cache optimized dynamic [`BundleId`] with single component
 | 
			
		||||
    dynamic_component_bundle_ids: HashMap<ComponentId, (BundleId, StorageType)>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Bundles {
 | 
			
		||||
@ -726,6 +738,7 @@ impl Bundles {
 | 
			
		||||
        self.bundle_ids.get(&type_id).cloned()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Initializes a new [`BundleInfo`] for a statically known type.
 | 
			
		||||
    pub(crate) fn init_info<'a, T: Bundle>(
 | 
			
		||||
        &'a mut self,
 | 
			
		||||
        components: &mut Components,
 | 
			
		||||
@ -745,6 +758,64 @@ impl Bundles {
 | 
			
		||||
        // SAFETY: index either exists, or was initialized
 | 
			
		||||
        unsafe { self.bundle_infos.get_unchecked(id.0) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`].
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// Panics if any of the provided [`ComponentId`]s do not exist in the
 | 
			
		||||
    /// provided [`Components`].
 | 
			
		||||
    pub(crate) fn init_dynamic_info(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        components: &mut Components,
 | 
			
		||||
        component_ids: &[ComponentId],
 | 
			
		||||
    ) -> (&BundleInfo, &Vec<StorageType>) {
 | 
			
		||||
        let bundle_infos = &mut self.bundle_infos;
 | 
			
		||||
 | 
			
		||||
        // Use `raw_entry_mut` to avoid cloning `component_ids` to access `Entry`
 | 
			
		||||
        let (_, (bundle_id, storage_types)) = self
 | 
			
		||||
            .dynamic_bundle_ids
 | 
			
		||||
            .raw_entry_mut()
 | 
			
		||||
            .from_key(component_ids)
 | 
			
		||||
            .or_insert_with(|| {
 | 
			
		||||
                (
 | 
			
		||||
                    Vec::from(component_ids),
 | 
			
		||||
                    initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)),
 | 
			
		||||
                )
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        // SAFETY: index either exists, or was initialized
 | 
			
		||||
        let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) };
 | 
			
		||||
 | 
			
		||||
        (bundle_info, storage_types)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`] with single component.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Panics
 | 
			
		||||
    ///
 | 
			
		||||
    /// Panics if the provided [`ComponentId`] does not exist in the provided [`Components`].
 | 
			
		||||
    pub(crate) fn init_component_info(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        components: &mut Components,
 | 
			
		||||
        component_id: ComponentId,
 | 
			
		||||
    ) -> (&BundleInfo, StorageType) {
 | 
			
		||||
        let bundle_infos = &mut self.bundle_infos;
 | 
			
		||||
        let (bundle_id, storage_types) = self
 | 
			
		||||
            .dynamic_component_bundle_ids
 | 
			
		||||
            .entry(component_id)
 | 
			
		||||
            .or_insert_with(|| {
 | 
			
		||||
                let (id, storage_type) =
 | 
			
		||||
                    initialize_dynamic_bundle(bundle_infos, components, vec![component_id]);
 | 
			
		||||
                // SAFETY: `storage_type` guaranteed to have length 1
 | 
			
		||||
                (id, storage_type[0])
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
        // SAFETY: index either exists, or was initialized
 | 
			
		||||
        let bundle_info = unsafe { bundle_infos.get_unchecked(bundle_id.0) };
 | 
			
		||||
 | 
			
		||||
        (bundle_info, *storage_types)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// # Safety
 | 
			
		||||
@ -784,3 +855,28 @@ unsafe fn initialize_bundle(
 | 
			
		||||
 | 
			
		||||
    BundleInfo { id, component_ids }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Asserts that all components are part of [`Components`]
 | 
			
		||||
/// and initializes a [`BundleInfo`].
 | 
			
		||||
fn initialize_dynamic_bundle(
 | 
			
		||||
    bundle_infos: &mut Vec<BundleInfo>,
 | 
			
		||||
    components: &Components,
 | 
			
		||||
    component_ids: Vec<ComponentId>,
 | 
			
		||||
) -> (BundleId, Vec<StorageType>) {
 | 
			
		||||
    // Assert component existence
 | 
			
		||||
    let storage_types = component_ids.iter().map(|&id| {
 | 
			
		||||
        components.get_info(id).unwrap_or_else(|| {
 | 
			
		||||
            panic!(
 | 
			
		||||
                "init_dynamic_info called with component id {id:?} which doesn't exist in this world"
 | 
			
		||||
            )
 | 
			
		||||
        }).storage_type()
 | 
			
		||||
    }).collect();
 | 
			
		||||
 | 
			
		||||
    let id = BundleId(bundle_infos.len());
 | 
			
		||||
    let bundle_info =
 | 
			
		||||
        // SAFETY: `component_ids` are valid as they were just checked
 | 
			
		||||
        unsafe { initialize_bundle("<dynamic bundle>", components, component_ids, id) };
 | 
			
		||||
    bundle_infos.push(bundle_info);
 | 
			
		||||
 | 
			
		||||
    (id, storage_types)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
use crate::{
 | 
			
		||||
    archetype::{Archetype, ArchetypeId, Archetypes},
 | 
			
		||||
    bundle::{Bundle, BundleInfo},
 | 
			
		||||
    bundle::{Bundle, BundleInfo, BundleInserter, DynamicBundle},
 | 
			
		||||
    change_detection::MutUntyped,
 | 
			
		||||
    component::{Component, ComponentId, ComponentTicks, Components, StorageType},
 | 
			
		||||
    entity::{Entities, Entity, EntityLocation},
 | 
			
		||||
@ -279,6 +279,90 @@ impl<'w> EntityMut<'w> {
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Inserts a dynamic [`Component`] into the entity.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This will overwrite any previous value(s) of the same component type.
 | 
			
		||||
    ///
 | 
			
		||||
    /// You should prefer to use the typed API [`EntityMut::insert`] where possible.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Safety
 | 
			
		||||
    ///
 | 
			
		||||
    /// - [`ComponentId`] must be from the same world as [`EntityMut`]
 | 
			
		||||
    /// - [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`]
 | 
			
		||||
    pub unsafe fn insert_by_id(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        component_id: ComponentId,
 | 
			
		||||
        component: OwningPtr<'_>,
 | 
			
		||||
    ) -> &mut Self {
 | 
			
		||||
        let change_tick = self.world.change_tick();
 | 
			
		||||
 | 
			
		||||
        let bundles = &mut self.world.bundles;
 | 
			
		||||
        let components = &mut self.world.components;
 | 
			
		||||
 | 
			
		||||
        let (bundle_info, storage_type) = bundles.init_component_info(components, component_id);
 | 
			
		||||
        let bundle_inserter = bundle_info.get_bundle_inserter(
 | 
			
		||||
            &mut self.world.entities,
 | 
			
		||||
            &mut self.world.archetypes,
 | 
			
		||||
            &mut self.world.components,
 | 
			
		||||
            &mut self.world.storages,
 | 
			
		||||
            self.location.archetype_id,
 | 
			
		||||
            change_tick,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        self.location = insert_dynamic_bundle(
 | 
			
		||||
            bundle_inserter,
 | 
			
		||||
            self.entity,
 | 
			
		||||
            self.location,
 | 
			
		||||
            Some(component).into_iter(),
 | 
			
		||||
            Some(storage_type).into_iter(),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Inserts a dynamic [`Bundle`] into the entity.
 | 
			
		||||
    ///
 | 
			
		||||
    /// This will overwrite any previous value(s) of the same component type.
 | 
			
		||||
    ///
 | 
			
		||||
    /// You should prefer to use the typed API [`EntityMut::insert`] where possible.
 | 
			
		||||
    /// If your [`Bundle`] only has one component, use the cached API [`EntityMut::insert_by_id`].
 | 
			
		||||
    ///
 | 
			
		||||
    /// If possible, pass a sorted slice of `ComponentId` to maximize caching potential.
 | 
			
		||||
    ///
 | 
			
		||||
    /// # Safety
 | 
			
		||||
    /// - Each [`ComponentId`] must be from the same world as [`EntityMut`]
 | 
			
		||||
    /// - Each [`OwningPtr`] must be a valid reference to the type represented by [`ComponentId`]
 | 
			
		||||
    pub unsafe fn insert_by_ids<'a, I: Iterator<Item = OwningPtr<'a>>>(
 | 
			
		||||
        &mut self,
 | 
			
		||||
        component_ids: &[ComponentId],
 | 
			
		||||
        iter_components: I,
 | 
			
		||||
    ) -> &mut Self {
 | 
			
		||||
        let change_tick = self.world.change_tick();
 | 
			
		||||
 | 
			
		||||
        let bundles = &mut self.world.bundles;
 | 
			
		||||
        let components = &mut self.world.components;
 | 
			
		||||
 | 
			
		||||
        let (bundle_info, storage_types) = bundles.init_dynamic_info(components, component_ids);
 | 
			
		||||
        let bundle_inserter = bundle_info.get_bundle_inserter(
 | 
			
		||||
            &mut self.world.entities,
 | 
			
		||||
            &mut self.world.archetypes,
 | 
			
		||||
            &mut self.world.components,
 | 
			
		||||
            &mut self.world.storages,
 | 
			
		||||
            self.location.archetype_id,
 | 
			
		||||
            change_tick,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        self.location = insert_dynamic_bundle(
 | 
			
		||||
            bundle_inserter,
 | 
			
		||||
            self.entity,
 | 
			
		||||
            self.location,
 | 
			
		||||
            iter_components,
 | 
			
		||||
            storage_types.iter().cloned(),
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        self
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /// Removes all components in the [`Bundle`] from the entity and returns their previous values.
 | 
			
		||||
    ///
 | 
			
		||||
    /// **Note:** If the entity does not have every component in the bundle, this method will not
 | 
			
		||||
@ -672,6 +756,44 @@ impl<'w> EntityMut<'w> {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Inserts a dynamic [`Bundle`] into the entity.
 | 
			
		||||
///
 | 
			
		||||
/// # Safety
 | 
			
		||||
///
 | 
			
		||||
/// - [`OwningPtr`] and [`StorageType`] iterators must correspond to the
 | 
			
		||||
/// [`BundleInfo`] used to construct [`BundleInserter`]
 | 
			
		||||
/// - [`Entity`] must correspond to [`EntityLocation`]
 | 
			
		||||
unsafe fn insert_dynamic_bundle<
 | 
			
		||||
    'a,
 | 
			
		||||
    I: Iterator<Item = OwningPtr<'a>>,
 | 
			
		||||
    S: Iterator<Item = StorageType>,
 | 
			
		||||
>(
 | 
			
		||||
    mut bundle_inserter: BundleInserter<'_, '_>,
 | 
			
		||||
    entity: Entity,
 | 
			
		||||
    location: EntityLocation,
 | 
			
		||||
    components: I,
 | 
			
		||||
    storage_types: S,
 | 
			
		||||
) -> EntityLocation {
 | 
			
		||||
    struct DynamicInsertBundle<'a, I: Iterator<Item = (StorageType, OwningPtr<'a>)>> {
 | 
			
		||||
        components: I,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    impl<'a, I: Iterator<Item = (StorageType, OwningPtr<'a>)>> DynamicBundle
 | 
			
		||||
        for DynamicInsertBundle<'a, I>
 | 
			
		||||
    {
 | 
			
		||||
        fn get_components(self, func: &mut impl FnMut(StorageType, OwningPtr<'_>)) {
 | 
			
		||||
            self.components.for_each(|(t, ptr)| func(t, ptr));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let bundle = DynamicInsertBundle {
 | 
			
		||||
        components: storage_types.zip(components),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // SAFETY: location matches current entity.
 | 
			
		||||
    unsafe { bundle_inserter.insert(entity, location, bundle) }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Removes a bundle from the given archetype and returns the resulting archetype (or None if the
 | 
			
		||||
/// removal was invalid). in the event that adding the given bundle does not result in an Archetype
 | 
			
		||||
/// change. Results are cached in the Archetype Graph to avoid redundant work.
 | 
			
		||||
@ -835,6 +957,7 @@ pub(crate) unsafe fn take_component<'a>(
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use bevy_ptr::OwningPtr;
 | 
			
		||||
    use std::panic::AssertUnwindSafe;
 | 
			
		||||
 | 
			
		||||
    use crate as bevy_ecs;
 | 
			
		||||
@ -862,9 +985,13 @@ mod tests {
 | 
			
		||||
        assert_eq!(a, vec![1]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[derive(Component)]
 | 
			
		||||
    #[derive(Component, Clone, Copy, Debug, PartialEq)]
 | 
			
		||||
    struct TestComponent(u32);
 | 
			
		||||
 | 
			
		||||
    #[derive(Component, Clone, Copy, Debug, PartialEq)]
 | 
			
		||||
    #[component(storage = "SparseSet")]
 | 
			
		||||
    struct TestComponent2(u32);
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn entity_ref_get_by_id() {
 | 
			
		||||
        let mut world = World::new();
 | 
			
		||||
@ -1107,4 +1234,72 @@ mod tests {
 | 
			
		||||
 | 
			
		||||
        assert_eq!(world.entity(e2).get::<Dense>().unwrap(), &Dense(1));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn entity_mut_insert_by_id() {
 | 
			
		||||
        let mut world = World::new();
 | 
			
		||||
        let test_component_id = world.init_component::<TestComponent>();
 | 
			
		||||
 | 
			
		||||
        let mut entity = world.spawn_empty();
 | 
			
		||||
        OwningPtr::make(TestComponent(42), |ptr| {
 | 
			
		||||
            // SAFETY: `ptr` matches the component id
 | 
			
		||||
            unsafe { entity.insert_by_id(test_component_id, ptr) };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let components: Vec<_> = world.query::<&TestComponent>().iter(&world).collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(components, vec![&TestComponent(42)]);
 | 
			
		||||
 | 
			
		||||
        // Compare with `insert_bundle_by_id`
 | 
			
		||||
 | 
			
		||||
        let mut entity = world.spawn_empty();
 | 
			
		||||
        OwningPtr::make(TestComponent(84), |ptr| {
 | 
			
		||||
            // SAFETY: `ptr` matches the component id
 | 
			
		||||
            unsafe { entity.insert_by_ids(&[test_component_id], vec![ptr].into_iter()) };
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let components: Vec<_> = world.query::<&TestComponent>().iter(&world).collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(components, vec![&TestComponent(42), &TestComponent(84)]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn entity_mut_insert_bundle_by_id() {
 | 
			
		||||
        let mut world = World::new();
 | 
			
		||||
        let test_component_id = world.init_component::<TestComponent>();
 | 
			
		||||
        let test_component_2_id = world.init_component::<TestComponent2>();
 | 
			
		||||
 | 
			
		||||
        let component_ids = [test_component_id, test_component_2_id];
 | 
			
		||||
        let test_component_value = TestComponent(42);
 | 
			
		||||
        let test_component_2_value = TestComponent2(84);
 | 
			
		||||
 | 
			
		||||
        let mut entity = world.spawn_empty();
 | 
			
		||||
        OwningPtr::make(test_component_value, |ptr1| {
 | 
			
		||||
            OwningPtr::make(test_component_2_value, |ptr2| {
 | 
			
		||||
                // SAFETY: `ptr1` and `ptr2` match the component ids
 | 
			
		||||
                unsafe { entity.insert_by_ids(&component_ids, vec![ptr1, ptr2].into_iter()) };
 | 
			
		||||
            });
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let dynamic_components: Vec<_> = world
 | 
			
		||||
            .query::<(&TestComponent, &TestComponent2)>()
 | 
			
		||||
            .iter(&world)
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(
 | 
			
		||||
            dynamic_components,
 | 
			
		||||
            vec![(&TestComponent(42), &TestComponent2(84))]
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        // Compare with `World` generated using static type equivalents
 | 
			
		||||
        let mut static_world = World::new();
 | 
			
		||||
 | 
			
		||||
        static_world.spawn((test_component_value, test_component_2_value));
 | 
			
		||||
        let static_components: Vec<_> = static_world
 | 
			
		||||
            .query::<(&TestComponent, &TestComponent2)>()
 | 
			
		||||
            .iter(&static_world)
 | 
			
		||||
            .collect();
 | 
			
		||||
 | 
			
		||||
        assert_eq!(dynamic_components, static_components);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user