This commit is contained in:
urben1680 2025-07-17 17:08:43 -05:00 committed by GitHub
commit 43ffe756fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 322 additions and 35 deletions

View File

@ -10,7 +10,7 @@ use crate::{
change_detection::MaybeLocation,
component::{
ComponentId, Components, ComponentsRegistrator, RequiredComponentConstructor,
RequiredComponents, StorageType, Tick,
RequiredComponents, RequiredComponentsError, StorageType, Tick,
},
entity::Entity,
query::DebugCheckedUnwrap as _,
@ -75,6 +75,9 @@ pub struct BundleInfo {
impl BundleInfo {
/// Create a new [`BundleInfo`].
///
/// The `component_to_containing_bundles` parameter comes from [`Bundles::component_to_containing_bundles`],
/// see its documentation for more information.
///
/// # Safety
///
/// Every ID in `component_ids` must be valid within the World that owns the `BundleInfo`
@ -83,7 +86,8 @@ impl BundleInfo {
bundle_type_name: &'static str,
storages: &mut Storages,
components: &Components,
mut component_ids: Vec<ComponentId>,
component_to_containing_bundles: &mut Vec<Vec<BundleId>>,
component_ids: Vec<ComponentId>,
id: BundleId,
) -> BundleInfo {
// check for duplicates
@ -111,41 +115,115 @@ impl BundleInfo {
panic!("Bundle {bundle_type_name} has duplicate components: {names:?}");
}
// handle explicit components
let explicit_components_len = component_ids.len();
let mut required_components = RequiredComponents::default();
for component_id in component_ids.iter().copied() {
// SAFETY: caller has verified that all ids are valid
let info = unsafe { components.get_info_unchecked(component_id) };
required_components.merge(info.required_components());
storages.prepare_component(info);
}
required_components.remove_explicit_components(&component_ids);
// handle required components
let required_components = required_components
.0
.into_iter()
.map(|(component_id, v)| {
// Safety: These ids came out of the passed `components`, so they must be valid.
let info = unsafe { components.get_info_unchecked(component_id) };
storages.prepare_component(info);
// This adds required components to the component_ids list _after_ using that list to remove explicitly provided
// components. This ordering is important!
component_ids.push(component_id);
v.constructor
})
.collect();
// SAFETY: The caller ensures that component_ids:
// - is valid for the associated world
// - has had its storage initialized
// - is in the same order as the source bundle type
BundleInfo {
let mut info = BundleInfo {
id,
component_ids,
required_components,
required_components: Vec::new(),
explicit_components_len,
};
// SAFETY: first call with the same `storages` and `components`.
unsafe {
info.set_required_components(
storages,
components,
component_to_containing_bundles,
|_| true,
);
}
info
}
/// Update [`Self::component_ids`] from containing only the explicit components to additionally contain all required
/// components as well and update [`Self::required_components`] alongside.
///
/// The `component_to_containing_bundles` parameter comes from [`Bundles::component_to_containing_bundles`],
/// see its documentation for more information.
///
/// The filter `component_to_containing_bundles_filter` determines for which components [`Self::id`] is added to `component_to_containing_bundles`.
///
/// # Safety
///
/// `Self` must be constructed from [`Self::new`] with the same `storages` and `components`.
unsafe fn set_required_components(
&mut self,
storages: &mut Storages,
components: &Components,
component_to_containing_bundles: &mut Vec<Vec<BundleId>>,
component_to_containing_bundles_filter: impl Fn(ComponentId) -> bool,
) {
let mut component_to_containing_bundles_push = |component: ComponentId| {
if !component_to_containing_bundles_filter(component) {
return;
}
match component_to_containing_bundles.get_mut(component.index()) {
Some(bundles) => bundles.push(self.id),
None => {
component_to_containing_bundles.resize_with(component.index() + 1, Vec::new);
*component_to_containing_bundles.last_mut().unwrap() = vec![self.id];
}
}
};
// handle explicit components
let mut required_components = RequiredComponents::default();
for component_id in self.component_ids.iter().copied() {
// SAFETY: caller of initial constructor has verified that all ids are valid
let info = unsafe { components.get_info_unchecked(component_id) };
required_components.merge(info.required_components());
storages.prepare_component(info);
component_to_containing_bundles_push(component_id);
}
required_components.remove_explicit_components(&self.component_ids);
// handle required components, clear first in case this gets called again for this bundle
self.required_components.clear();
self.required_components
.extend(required_components.0.into_iter().map(|(component_id, v)| {
// Safety: These ids came out of the passed `components`, so they must be valid.
let info = unsafe { components.get_info_unchecked(component_id) };
storages.prepare_component(info);
// This adds required components to the component_ids list _after_ using that list to remove explicitly provided
// components. This ordering is important!
self.component_ids.push(component_id);
component_to_containing_bundles_push(component_id);
v.constructor
}));
}
/// Updates required components for new runtime-added components.
///
/// The `component_to_containing_bundles` parameter comes from [`Bundles::component_to_containing_bundles`],
/// see its documentation for more information.
///
/// # Safety
///
/// `Self` must be constructed from [`Self::new`] with the same `storages` and `components`.
unsafe fn reset_required_components(
&mut self,
storages: &mut Storages,
components: &Components,
component_to_containing_bundles: &mut Vec<Vec<BundleId>>,
) {
let registered_component_to_containing_bundles: HashSet<_> =
self.component_ids.iter().copied().collect();
self.component_ids.truncate(self.explicit_components_len);
// SAFETY: caller ensured the same contract is fulfilled
unsafe {
self.set_required_components(
storages,
components,
component_to_containing_bundles,
|component| !registered_component_to_containing_bundles.contains(&component),
);
}
}
@ -368,6 +446,9 @@ pub struct Bundles {
/// Cache optimized dynamic [`BundleId`] with single component
dynamic_component_bundle_ids: HashMap<ComponentId, BundleId>,
dynamic_component_storages: HashMap<BundleId, StorageType>,
/// Cache [`BundleId`]s that contain a certain [`ComponentId`], which uses
/// [`ComponentId::index`] as the index of the outer vector into the inner
component_to_containing_bundles: Vec<Vec<BundleId>>,
}
impl Bundles {
@ -419,7 +500,7 @@ impl Bundles {
// - its info was created
// - appropriate storage for it has been initialized.
// - it was created in the same order as the components in T
unsafe { BundleInfo::new(core::any::type_name::<T>(), storages, components, component_ids, id) };
unsafe { BundleInfo::new(core::any::type_name::<T>(), storages, components, &mut self.component_to_containing_bundles, component_ids, id) };
bundle_infos.push(bundle_info);
id
})
@ -428,6 +509,8 @@ impl Bundles {
/// Registers a new [`BundleInfo`], which contains both explicit and required components for a statically known type.
///
/// Also registers all the components in the bundle.
///
/// Using this forbids components in this bundle to add more required components via [`Self::refresh_required_components`].
pub(crate) fn register_contributed_bundle_info<T: Bundle>(
&mut self,
components: &mut ComponentsRegistrator,
@ -436,7 +519,7 @@ impl Bundles {
if let Some(id) = self.contributed_bundle_ids.get(&TypeId::of::<T>()).cloned() {
id
} else {
let explicit_bundle_id = self.register_info::<T>(components, storages);
let explicit_bundle_id: BundleId = self.register_info::<T>(components, storages);
// SAFETY: reading from `explicit_bundle_id` and creating new bundle in same time. Its valid because bundle hashmap allow this
let id = unsafe {
let (ptr, len) = {
@ -502,6 +585,7 @@ impl Bundles {
.or_insert_with(|| {
let (id, storages) = initialize_dynamic_bundle(
bundle_infos,
&mut self.component_to_containing_bundles,
storages,
components,
Vec::from(component_ids),
@ -534,6 +618,7 @@ impl Bundles {
.or_insert_with(|| {
let (id, storage_type) = initialize_dynamic_bundle(
bundle_infos,
&mut self.component_to_containing_bundles,
storages,
components,
vec![component_id],
@ -543,12 +628,111 @@ impl Bundles {
});
*bundle_id
}
/// Checks if the bundles containing `requiree` can have their required components be updated,
/// in which case this returns `Ok(true)`.
///
/// Returns `Ok(false)` if there are no known bundles with this component.
///
/// Returns the [`RequiredComponentsError::RemovedFromArchetype`] error if bundles cannot be updated
/// in which case the registration of any new required component must be refused.
/// This does not check for [`RequiredComponentsError::ArchetypeExists`] as that is done by
/// [`Components::register_required_components`] that needs to be called before
/// [`Self::refresh_required_components`] anyway.
pub(crate) fn verify_to_refresh_required_components(
&self,
requiree: ComponentId,
) -> Result<bool, RequiredComponentsError> {
let Some(bundles_with_requiree) = self
.component_to_containing_bundles
.get(requiree.index())
.filter(|bundles| !bundles.is_empty())
else {
// no bundle with `requiree` exists
return Ok(false);
};
// `EntityWorldMut::remove_with_requires` generate edges between archetypes where the required
// components matter for the the target archetype. These bundles cannot receive new required
// components as that would invalidate these edges.
// The mechanism is using `Self::contributed_bundle_ids` to track the bundles so we check here if it
// has any overlapping `BundleId`s with `bundles_with_requiree`.
// It would also be an error if the bundle is part of any archetype (edge) involving inserts,
// but that error is checked on by `Components::register_required_components` so doing that here
// would be redundant.
// TODO: Remove this error and update archetype edges accordingly when required components are added
if bundles_with_requiree.iter().any(|bundles_with_requiree| {
self.contributed_bundle_ids
.values()
.any(|bundle: &BundleId| bundles_with_requiree == bundle)
}) {
return Err(RequiredComponentsError::RemovedFromArchetype(requiree));
}
Ok(true)
}
/// Updates the required components of bundles that contain `requiree`.
///
/// This assumes that `requiree` cannot have been added itself as a new required component.
///
/// # Safety
///
/// The caller assures that immediately prior calling this, ...
/// - [`Self::verify_to_refresh_required_components`] returned `Ok(true)` for `requiree`
/// - [`Components::register_required_components`] did not return `Err` for `requiree`
/// - and `storages` and `components` must be the same that were used to create the stored bundles
pub(crate) unsafe fn refresh_required_components(
&mut self,
storages: &mut Storages,
components: &Components,
requiree: ComponentId,
) {
// take list of bundles to update `Self::bundles_with_requiree` while iterating this
let taken_bundles_with_requiree = self
.component_to_containing_bundles
.get_mut(requiree.index())
.filter(|bundles| !bundles.is_empty())
.map(core::mem::take)
.expect("verify_to_refresh_required_components confirmed existing bundles to refresh");
for bundle_id in taken_bundles_with_requiree.iter() {
let bundle_info = self.bundle_infos.get_mut(bundle_id.index());
// SAFETY: BundleIds in Self::component_to_containing_bundles are valid ids of Self::bundle_infos
let bundle_info = unsafe { bundle_info.debug_checked_unwrap() };
// SAFETY: Caller ensured `storages` and `components` match those used to create this bundle
unsafe {
bundle_info.reset_required_components(
storages,
components,
&mut self.component_to_containing_bundles,
);
}
}
// put the list of bundles with `requiree` back
let replaced = self
.component_to_containing_bundles
.get_mut(requiree.index());
// SAFETY: this list has to exist at this index to be taken in the first place
let replaced = unsafe { replaced.debug_checked_unwrap() };
// put the bundles back, the placeholder that was put in its place earlier cannot have been updated
// because only the lists of the newly required components get new bundles added.
// if that happened for this list, that would mean `requiree` would be its own new required component,
// which is invalid.
*replaced = taken_bundles_with_requiree;
}
}
/// Asserts that all components are part of [`Components`]
/// and initializes a [`BundleInfo`].
fn initialize_dynamic_bundle(
bundle_infos: &mut Vec<BundleInfo>,
component_to_containing_bundles: &mut Vec<Vec<BundleId>>,
storages: &mut Storages,
components: &Components,
component_ids: Vec<ComponentId>,
@ -565,7 +749,7 @@ fn initialize_dynamic_bundle(
let id = BundleId(bundle_infos.len());
let bundle_info =
// SAFETY: `component_ids` are valid as they were just checked
unsafe { BundleInfo::new("<dynamic bundle>", storages, components, component_ids, id) };
unsafe { BundleInfo::new("<dynamic bundle>", storages, components, component_to_containing_bundles, component_ids, id) };
bundle_infos.push(bundle_info);
(id, storage_types)

View File

@ -24,7 +24,8 @@ impl Components {
///
/// # Safety
///
/// The given component IDs `required` and `requiree` must be valid.
/// - The given component IDs `required` and `requiree` must be valid
/// - [`crate::bundle::Bundles::verify_to_refresh_required_components`] returned `Ok` for `requiree`
///
/// # Errors
///
@ -271,9 +272,12 @@ pub enum RequiredComponentsError {
/// The component is already a directly required component for the requiree.
#[error("Component {0:?} already directly requires component {1:?}")]
DuplicateRegistration(ComponentId, ComponentId),
/// An archetype with the component that requires other components already exists
/// An archetype with the component that requires other components already exists.
#[error("An archetype with the component {0:?} that requires other components already exists")]
ArchetypeExists(ComponentId),
/// A bundle with the component that requires other components was removed via [`crate::world::EntityWorldMut::remove_with_requires`].
#[error("A bundle with the component {0:?} that requires other components was removed including required components previously")]
RemovedFromArchetype(ComponentId),
}
/// A Required Component constructor. See [`Component`] for details.

View File

@ -2473,6 +2473,26 @@ mod tests {
));
}
#[test]
fn runtime_required_components_of_remove_with_requires_bundle() {
#[derive(Component)]
struct A;
#[derive(Component, Default)]
struct B;
let mut world = World::new();
// This should fail, removing A from an archetype with only B was previously noop
// and register_required_components cannot trivially update the edge to now cause
// a move to an archetype without A
world.spawn(B).remove_with_requires::<A>();
assert!(matches!(
world.try_register_required_components::<A, B>(),
Err(RequiredComponentsError::RemovedFromArchetype(_))
));
}
#[test]
fn required_components_inheritance_depth() {
// Test that inheritance depths are computed correctly for requirements.
@ -2556,6 +2576,66 @@ mod tests {
assert_eq!(to_vec(required_z), vec![(b, 0), (c, 1)]);
}
#[test]
fn update_required_components_in_bundle() {
#[derive(Component)]
#[require(B)]
struct A;
#[derive(Component, Default)]
struct B;
#[derive(Component, Default)]
#[require(D)]
struct C;
#[derive(Component, Default)]
struct D;
#[derive(Component, Default)]
struct E;
let mut world = World::new();
let a_id = world.register_component::<A>();
let b_id = world.register_component::<B>();
let c_id = world.register_component::<C>();
let d_id = world.register_component::<D>();
let e_id = world.register_component::<E>();
let bundle = world.register_bundle::<A>();
let bundle_id = bundle.id();
let contributed: HashSet<_> = bundle.contributed_components().iter().copied().collect();
assert!(contributed.contains(&a_id));
assert!(contributed.contains(&b_id));
assert!(!contributed.contains(&c_id));
assert!(!contributed.contains(&d_id));
assert!(!contributed.contains(&e_id));
// check if registration succeeds
world.register_required_components::<B, C>();
let bundle = world.bundles().get(bundle_id).unwrap();
let contributed: HashSet<_> = bundle.contributed_components().iter().copied().collect();
assert!(contributed.contains(&a_id));
assert!(contributed.contains(&b_id));
assert!(contributed.contains(&c_id));
assert!(contributed.contains(&d_id));
assert!(!contributed.contains(&e_id));
// check if another registration can be associated to the bundle using the previously registered component
world.register_required_components::<D, E>();
let bundle = world.bundles().get(bundle_id).unwrap();
let contributed: HashSet<_> = bundle.contributed_components().iter().copied().collect();
assert!(contributed.contains(&a_id));
assert!(contributed.contains(&b_id));
assert!(contributed.contains(&c_id));
assert!(contributed.contains(&d_id));
assert!(contributed.contains(&e_id));
}
#[test]
fn required_components_inheritance_depth_bias() {
#[derive(Component, PartialEq, Eq, Clone, Copy, Debug)]

View File

@ -2144,6 +2144,8 @@ impl<'w> EntityWorldMut<'w> {
/// # Panics
///
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
// Implementation note: Using this forbids components in this bundle to add more required components
// See `Bundles::verify_to_refresh_required_components`
#[track_caller]
pub fn remove_with_requires<T: Bundle>(&mut self) -> &mut Self {
self.remove_with_requires_with_caller::<T>(MaybeLocation::caller())

View File

@ -535,18 +535,35 @@ impl World {
) -> Result<(), RequiredComponentsError> {
let requiree = self.register_component::<T>();
// TODO: Remove this panic and update archetype edges accordingly when required components are added
// TODO: Remove this error and update archetype edges accordingly when required components are added
if self.archetypes().component_index().contains_key(&requiree) {
return Err(RequiredComponentsError::ArchetypeExists(requiree));
}
let required = self.register_component::<R>();
let update_bundles = self
.bundles
.verify_to_refresh_required_components(requiree)?;
// SAFETY: We just created the `required` and `requiree` components.
unsafe {
self.components
.register_required_components::<R>(requiree, required, constructor)
.register_required_components::<R>(requiree, required, constructor)?;
}
if update_bundles {
// SAFETY: all bundles are created with Self::storages and Self::components
unsafe {
self.bundles.refresh_required_components(
&mut self.storages,
&self.components,
requiree,
);
}
}
Ok(())
}
/// Retrieves the [required components](RequiredComponents) for the given component type, if it exists.