Get names of queued components (#18451)

# Objective

#18173 allows components to be queued without being fully registered.
But much of bevy's debug logging contained
`components.get_name(id).unwrap()`. However, this panics when the id is
queued. This PR fixes this, allowing names to be retrieved for debugging
purposes, etc, even while they're still queued.

## Solution

We change `ComponentInfo::descriptor` to be `Arc<ComponentDescriptor>`
instead of not arc'd. This lets us pass the descriptor around (as a name
or otherwise) as needed. The alternative would require some form of
`MappedRwLockReadGuard`, which is unstable, and would be terribly
blocking. Putting it in an arc also signifies that it doesn't change,
which is a nice signal to users. This does mean there's an extra pointer
dereference, but I don't think that's an issue here, as almost all paths
that use this are for debugging purposes or one-time set ups.

## Testing

Existing tests.

## Migration Guide

`Components::get_name` now returns `Option<Cow<'_, str>` instead of
`Option<&str>`. This is because it now returns results for queued
components. If that behavior is not desired, or you know the component
is not queued, you can use
`components.get_info().map(ComponentInfo::name)` instead.

Similarly, `ScheduleGraph::conflicts_to_string` now returns `impl
Iterator<Item = (String, String, Vec<Cow<str>>)>` instead of `impl
Iterator<Item = (String, String, Vec<&str>)>`. Because `Cow<str>` derefs
to `&str`, most use cases can remain unchanged.

---------

Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
This commit is contained in:
Eagster 2025-03-31 19:22:33 -04:00 committed by GitHub
parent 99289ad988
commit 5db67f35e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 145 additions and 62 deletions

View File

@ -34,7 +34,7 @@ bevy_reflect = ["dep:bevy_reflect"]
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"] reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
## Use the configurable global error handler as the default error handler. ## Use the configurable global error handler as the default error handler.
## ##
## This is typically used to turn panics from the ECS into loggable errors. ## This is typically used to turn panics from the ECS into loggable errors.
## This may be useful for production builds, ## This may be useful for production builds,
## but can result in a measurable performance impact, especially for commands. ## but can result in a measurable performance impact, especially for commands.
@ -110,7 +110,6 @@ bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-d
] } ] }
bitflags = { version = "2.3", default-features = false } bitflags = { version = "2.3", default-features = false }
concurrent-queue = { version = "2.5.0", default-features = false }
disqualified = { version = "1.0", default-features = false } disqualified = { version = "1.0", default-features = false }
fixedbitset = { version = "0.5", default-features = false } fixedbitset = { version = "0.5", default-features = false }
serde = { version = "1", default-features = false, features = [ serde = { version = "1", default-features = false, features = [
@ -133,6 +132,7 @@ tracing = { version = "0.1", default-features = false, optional = true }
log = { version = "0.4", default-features = false } log = { version = "0.4", default-features = false }
bumpalo = "3" bumpalo = "3"
concurrent-queue = { version = "2.5.0", default-features = false }
[target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies] [target.'cfg(not(all(target_has_atomic = "8", target_has_atomic = "16", target_has_atomic = "32", target_has_atomic = "64", target_has_atomic = "ptr")))'.dependencies]
concurrent-queue = { version = "2.5.0", default-features = false, features = [ concurrent-queue = { version = "2.5.0", default-features = false, features = [
"portable-atomic", "portable-atomic",

View File

@ -1243,8 +1243,9 @@ impl ComponentCloneBehavior {
/// A queued component registration. /// A queued component registration.
struct QueuedRegistration { struct QueuedRegistration {
registrator: Box<dyn FnOnce(&mut ComponentsRegistrator, ComponentId)>, registrator: Box<dyn FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor)>,
id: ComponentId, id: ComponentId,
descriptor: ComponentDescriptor,
} }
impl QueuedRegistration { impl QueuedRegistration {
@ -1255,17 +1256,19 @@ impl QueuedRegistration {
/// [`ComponentId`] must be unique. /// [`ComponentId`] must be unique.
unsafe fn new( unsafe fn new(
id: ComponentId, id: ComponentId,
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static, descriptor: ComponentDescriptor,
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
) -> Self { ) -> Self {
Self { Self {
registrator: Box::new(func), registrator: Box::new(func),
id, id,
descriptor,
} }
} }
/// Performs the registration, returning the now valid [`ComponentId`]. /// Performs the registration, returning the now valid [`ComponentId`].
fn register(self, registrator: &mut ComponentsRegistrator) -> ComponentId { fn register(self, registrator: &mut ComponentsRegistrator) -> ComponentId {
(self.registrator)(registrator, self.id); (self.registrator)(registrator, self.id, self.descriptor);
self.id self.id
} }
} }
@ -1362,6 +1365,7 @@ impl ComponentIds {
/// ///
/// As a rule of thumb, if you have mutable access to [`ComponentsRegistrator`], prefer to use that instead. /// As a rule of thumb, if you have mutable access to [`ComponentsRegistrator`], prefer to use that instead.
/// Use this only if you need to know the id of a component but do not need to modify the contents of the world based on that id. /// Use this only if you need to know the id of a component but do not need to modify the contents of the world based on that id.
#[derive(Clone, Copy)]
pub struct ComponentsQueuedRegistrator<'w> { pub struct ComponentsQueuedRegistrator<'w> {
components: &'w Components, components: &'w Components,
ids: &'w ComponentIds, ids: &'w ComponentIds,
@ -1394,7 +1398,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
unsafe fn force_register_arbitrary_component( unsafe fn force_register_arbitrary_component(
&self, &self,
type_id: TypeId, type_id: TypeId,
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static, descriptor: ComponentDescriptor,
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
) -> ComponentId { ) -> ComponentId {
let id = self.ids.next(); let id = self.ids.next();
self.components self.components
@ -1405,7 +1410,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
.insert( .insert(
type_id, type_id,
// SAFETY: The id was just generated. // SAFETY: The id was just generated.
unsafe { QueuedRegistration::new(id, func) }, unsafe { QueuedRegistration::new(id, descriptor, func) },
); );
id id
} }
@ -1418,7 +1423,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
unsafe fn force_register_arbitrary_resource( unsafe fn force_register_arbitrary_resource(
&self, &self,
type_id: TypeId, type_id: TypeId,
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static, descriptor: ComponentDescriptor,
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
) -> ComponentId { ) -> ComponentId {
let id = self.ids.next(); let id = self.ids.next();
self.components self.components
@ -1429,7 +1435,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
.insert( .insert(
type_id, type_id,
// SAFETY: The id was just generated. // SAFETY: The id was just generated.
unsafe { QueuedRegistration::new(id, func) }, unsafe { QueuedRegistration::new(id, descriptor, func) },
); );
id id
} }
@ -1437,7 +1443,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
/// Queues this function to run as a dynamic registrator. /// Queues this function to run as a dynamic registrator.
fn force_register_arbitrary_dynamic( fn force_register_arbitrary_dynamic(
&self, &self,
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static, descriptor: ComponentDescriptor,
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
) -> ComponentId { ) -> ComponentId {
let id = self.ids.next(); let id = self.ids.next();
self.components self.components
@ -1447,7 +1454,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
.dynamic_registrations .dynamic_registrations
.push( .push(
// SAFETY: The id was just generated. // SAFETY: The id was just generated.
unsafe { QueuedRegistration::new(id, func) }, unsafe { QueuedRegistration::new(id, descriptor, func) },
); );
id id
} }
@ -1456,6 +1463,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
/// This will reserve an id and queue the registration. /// This will reserve an id and queue the registration.
/// These registrations will be carried out at the next opportunity. /// These registrations will be carried out at the next opportunity.
/// ///
/// If this has already been registered or queued, this returns the previous [`ComponentId`].
///
/// # Note /// # Note
/// ///
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later. /// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
@ -1465,13 +1474,17 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
self.component_id::<T>().unwrap_or_else(|| { self.component_id::<T>().unwrap_or_else(|| {
// SAFETY: We just checked that this type was not in the queue. // SAFETY: We just checked that this type was not in the queue.
unsafe { unsafe {
self.force_register_arbitrary_component(TypeId::of::<T>(), |registrator, id| { self.force_register_arbitrary_component(
// SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. TypeId::of::<T>(),
#[expect(unused_unsafe, reason = "More precise to specify.")] ComponentDescriptor::new::<T>(),
unsafe { |registrator, id, _descriptor| {
registrator.register_component_unchecked::<T>(&mut Vec::new(), id); // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue.
} #[expect(unused_unsafe, reason = "More precise to specify.")]
}) unsafe {
registrator.register_component_unchecked::<T>(&mut Vec::new(), id);
}
},
)
} }
}) })
} }
@ -1489,7 +1502,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
&self, &self,
descriptor: ComponentDescriptor, descriptor: ComponentDescriptor,
) -> ComponentId { ) -> ComponentId {
self.force_register_arbitrary_dynamic(|registrator, id| { self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
// SAFETY: Id uniqueness handled by caller. // SAFETY: Id uniqueness handled by caller.
unsafe { unsafe {
registrator.register_component_inner(id, descriptor); registrator.register_component_inner(id, descriptor);
@ -1501,6 +1514,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
/// This will reserve an id and queue the registration. /// This will reserve an id and queue the registration.
/// These registrations will be carried out at the next opportunity. /// These registrations will be carried out at the next opportunity.
/// ///
/// If this has already been registered or queued, this returns the previous [`ComponentId`].
///
/// # Note /// # Note
/// ///
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later. /// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
@ -1511,16 +1526,18 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
self.get_resource_id(type_id).unwrap_or_else(|| { self.get_resource_id(type_id).unwrap_or_else(|| {
// SAFETY: We just checked that this type was not in the queue. // SAFETY: We just checked that this type was not in the queue.
unsafe { unsafe {
self.force_register_arbitrary_resource(type_id, move |registrator, id| { self.force_register_arbitrary_resource(
// SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. type_id,
// SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor. ComponentDescriptor::new_resource::<T>(),
#[expect(unused_unsafe, reason = "More precise to specify.")] move |registrator, id, descriptor| {
unsafe { // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue.
registrator.register_resource_unchecked_with(type_id, id, || { // SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor.
ComponentDescriptor::new_resource::<T>() #[expect(unused_unsafe, reason = "More precise to specify.")]
}); unsafe {
} registrator.register_resource_unchecked(type_id, id, descriptor);
}) }
},
)
} }
}) })
} }
@ -1529,6 +1546,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
/// This will reserve an id and queue the registration. /// This will reserve an id and queue the registration.
/// These registrations will be carried out at the next opportunity. /// These registrations will be carried out at the next opportunity.
/// ///
/// If this has already been registered or queued, this returns the previous [`ComponentId`].
///
/// # Note /// # Note
/// ///
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later. /// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
@ -1539,16 +1558,18 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
self.get_resource_id(type_id).unwrap_or_else(|| { self.get_resource_id(type_id).unwrap_or_else(|| {
// SAFETY: We just checked that this type was not in the queue. // SAFETY: We just checked that this type was not in the queue.
unsafe { unsafe {
self.force_register_arbitrary_resource(type_id, move |registrator, id| { self.force_register_arbitrary_resource(
// SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue. type_id,
// SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor. ComponentDescriptor::new_non_send::<T>(StorageType::default()),
#[expect(unused_unsafe, reason = "More precise to specify.")] move |registrator, id, descriptor| {
unsafe { // SAFETY: We just checked that this is not currently registered or queued, and if it was registered since, this would have been dropped from the queue.
registrator.register_resource_unchecked_with(type_id, id, || { // SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor.
ComponentDescriptor::new_non_send::<T>(StorageType::default()) #[expect(unused_unsafe, reason = "More precise to specify.")]
}); unsafe {
} registrator.register_resource_unchecked(type_id, id, descriptor);
}) }
},
)
} }
}) })
} }
@ -1566,7 +1587,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
&self, &self,
descriptor: ComponentDescriptor, descriptor: ComponentDescriptor,
) -> ComponentId { ) -> ComponentId {
self.force_register_arbitrary_dynamic(|registrator, id| { self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
// SAFETY: Id uniqueness handled by caller. // SAFETY: Id uniqueness handled by caller.
unsafe { unsafe {
registrator.register_component_inner(id, descriptor); registrator.register_component_inner(id, descriptor);
@ -1870,7 +1891,7 @@ impl<'w> ComponentsRegistrator<'w> {
} }
} }
/// Same as [`Components::register_resource_unchecked_with`] but handles safety. /// Same as [`Components::register_resource_unchecked`] but handles safety.
/// ///
/// # Safety /// # Safety
/// ///
@ -1901,7 +1922,7 @@ impl<'w> ComponentsRegistrator<'w> {
let id = self.ids.next_mut(); let id = self.ids.next_mut();
// SAFETY: The resource is not currently registered, the id is fresh, and the [`ComponentDescriptor`] matches the [`TypeId`] // SAFETY: The resource is not currently registered, the id is fresh, and the [`ComponentDescriptor`] matches the [`TypeId`]
unsafe { unsafe {
self.register_resource_unchecked_with(type_id, id, descriptor); self.register_resource_unchecked(type_id, id, descriptor());
} }
id id
} }
@ -2027,13 +2048,53 @@ impl Components {
self.components.get(id.0).and_then(|info| info.as_ref()) self.components.get(id.0).and_then(|info| info.as_ref())
} }
/// Returns the name associated with the given component, if it is registered. /// Gets the [`ComponentDescriptor`] of the component with this [`ComponentId`] if it is present.
/// This will return `None` if the id is not regiserted or is queued. /// This will return `None` only if the id is neither regisered nor queued to be registered.
///
/// Currently, the [`Cow`] will be [`Cow::Owned`] if and only if the component is queued. It will be [`Cow::Borrowed`] otherwise.
/// ///
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value. /// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
#[inline] #[inline]
pub fn get_name(&self, id: ComponentId) -> Option<&str> { pub fn get_descriptor<'a>(&'a self, id: ComponentId) -> Option<Cow<'a, ComponentDescriptor>> {
self.get_info(id).map(ComponentInfo::name) self.components
.get(id.0)
.and_then(|info| info.as_ref().map(|info| Cow::Borrowed(&info.descriptor)))
.or_else(|| {
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
// first check components, then resources, then dynamic
queued
.components
.values()
.chain(queued.resources.values())
.chain(queued.dynamic_registrations.iter())
.find(|queued| queued.id == id)
.map(|queued| Cow::Owned(queued.descriptor.clone()))
})
}
/// Gets the name of the component with this [`ComponentId`] if it is present.
/// This will return `None` only if the id is neither regisered nor queued to be registered.
///
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
#[inline]
pub fn get_name<'a>(&'a self, id: ComponentId) -> Option<Cow<'a, str>> {
self.components
.get(id.0)
.and_then(|info| {
info.as_ref()
.map(|info| Cow::Borrowed(info.descriptor.name()))
})
.or_else(|| {
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
// first check components, then resources, then dynamic
queued
.components
.values()
.chain(queued.resources.values())
.chain(queued.dynamic_registrations.iter())
.find(|queued| queued.id == id)
.map(|queued| queued.descriptor.name.clone())
})
} }
/// Gets the metadata associated with the given component. /// Gets the metadata associated with the given component.
@ -2456,15 +2517,15 @@ impl Components {
/// The [`ComponentId`] must be unique. /// The [`ComponentId`] must be unique.
/// The [`TypeId`] and [`ComponentId`] must not be registered or queued. /// The [`TypeId`] and [`ComponentId`] must not be registered or queued.
#[inline] #[inline]
unsafe fn register_resource_unchecked_with( unsafe fn register_resource_unchecked(
&mut self, &mut self,
type_id: TypeId, type_id: TypeId,
component_id: ComponentId, component_id: ComponentId,
func: impl FnOnce() -> ComponentDescriptor, descriptor: ComponentDescriptor,
) { ) {
// SAFETY: ensured by caller // SAFETY: ensured by caller
unsafe { unsafe {
self.register_component_inner(component_id, func()); self.register_component_inner(component_id, descriptor);
} }
let prev = self.resource_indices.insert(type_id, component_id); let prev = self.resource_indices.insert(type_id, component_id);
debug_assert!(prev.is_none()); debug_assert!(prev.is_none());
@ -2940,13 +3001,13 @@ pub fn enforce_no_required_components_recursion(
"Recursive required components detected: {}\nhelp: {}", "Recursive required components detected: {}\nhelp: {}",
recursion_check_stack recursion_check_stack
.iter() .iter()
.map(|id| format!("{}", ShortName(components.get_name(*id).unwrap()))) .map(|id| format!("{}", ShortName(&components.get_name(*id).unwrap())))
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join(""), .join(""),
if direct_recursion { if direct_recursion {
format!( format!(
"Remove require({}).", "Remove require({}).",
ShortName(components.get_name(requiree).unwrap()) ShortName(&components.get_name(requiree).unwrap())
) )
} else { } else {
"If this is intentional, consider merging the components.".into() "If this is intentional, consider merging the components.".into()

View File

@ -968,11 +968,10 @@ impl AccessConflicts {
format!( format!(
"{}", "{}",
ShortName( ShortName(
world &world
.components .components
.get_info(ComponentId::get_sparse_set_index(index)) .get_name(ComponentId::get_sparse_set_index(index))
.unwrap() .unwrap()
.name()
) )
) )
}) })

View File

@ -1097,28 +1097,38 @@ mod tests {
let ambiguities: Vec<_> = schedule let ambiguities: Vec<_> = schedule
.graph() .graph()
.conflicts_to_string(schedule.graph().conflicting_systems(), world.components()) .conflicts_to_string(schedule.graph().conflicting_systems(), world.components())
.map(|item| {
(
item.0,
item.1,
item.2
.into_iter()
.map(|name| name.to_string())
.collect::<Vec<_>>(),
)
})
.collect(); .collect();
let expected = &[ let expected = &[
( (
"system_d".to_string(), "system_d".to_string(),
"system_a".to_string(), "system_a".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
), ),
( (
"system_d".to_string(), "system_d".to_string(),
"system_e".to_string(), "system_e".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
), ),
( (
"system_b".to_string(), "system_b".to_string(),
"system_a".to_string(), "system_a".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
), ),
( (
"system_b".to_string(), "system_b".to_string(),
"system_e".to_string(), "system_e".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
), ),
]; ];
@ -1146,6 +1156,16 @@ mod tests {
let ambiguities: Vec<_> = schedule let ambiguities: Vec<_> = schedule
.graph() .graph()
.conflicts_to_string(schedule.graph().conflicting_systems(), world.components()) .conflicts_to_string(schedule.graph().conflicting_systems(), world.components())
.map(|item| {
(
item.0,
item.1,
item.2
.into_iter()
.map(|name| name.to_string())
.collect::<Vec<_>>(),
)
})
.collect(); .collect();
assert_eq!( assert_eq!(
@ -1153,7 +1173,7 @@ mod tests {
( (
"resmut_system (in set (resmut_system, resmut_system))".to_string(), "resmut_system (in set (resmut_system, resmut_system))".to_string(),
"resmut_system (in set (resmut_system, resmut_system))".to_string(), "resmut_system (in set (resmut_system, resmut_system))".to_string(),
vec!["bevy_ecs::schedule::tests::system_ambiguity::R"], vec!["bevy_ecs::schedule::tests::system_ambiguity::R".into()],
) )
); );
} }

View File

@ -2,6 +2,7 @@
clippy::module_inception, clippy::module_inception,
reason = "This instance of module inception is being discussed; see #17344." reason = "This instance of module inception is being discussed; see #17344."
)] )]
use alloc::borrow::Cow;
use alloc::{ use alloc::{
boxed::Box, boxed::Box,
collections::{BTreeMap, BTreeSet}, collections::{BTreeMap, BTreeSet},
@ -1902,7 +1903,7 @@ impl ScheduleGraph {
&'a self, &'a self,
ambiguities: &'a [(NodeId, NodeId, Vec<ComponentId>)], ambiguities: &'a [(NodeId, NodeId, Vec<ComponentId>)],
components: &'a Components, components: &'a Components,
) -> impl Iterator<Item = (String, String, Vec<&'a str>)> + 'a { ) -> impl Iterator<Item = (String, String, Vec<Cow<'a, str>>)> + 'a {
ambiguities ambiguities
.iter() .iter()
.map(move |(system_a, system_b, conflicts)| { .map(move |(system_a, system_b, conflicts)| {

View File

@ -2876,6 +2876,7 @@ impl World {
&mut self, &mut self,
component_id: ComponentId, component_id: ComponentId,
) -> &mut ResourceData<true> { ) -> &mut ResourceData<true> {
self.flush_components();
let archetypes = &mut self.archetypes; let archetypes = &mut self.archetypes;
self.storages self.storages
.resources .resources
@ -2891,6 +2892,7 @@ impl World {
&mut self, &mut self,
component_id: ComponentId, component_id: ComponentId,
) -> &mut ResourceData<false> { ) -> &mut ResourceData<false> {
self.flush_components();
let archetypes = &mut self.archetypes; let archetypes = &mut self.archetypes;
self.storages self.storages
.non_send_resources .non_send_resources

View File

@ -80,7 +80,7 @@ impl World {
let component_name = self let component_name = self
.components() .components()
.get_name(component_id) .get_name(component_id)
.map(ToString::to_string); .map(|name| name.to_string());
return Err(GetComponentReflectError::EntityDoesNotHaveComponent { return Err(GetComponentReflectError::EntityDoesNotHaveComponent {
entity, entity,
@ -169,7 +169,7 @@ impl World {
let component_name = self let component_name = self
.components() .components()
.get_name(component_id) .get_name(component_id)
.map(ToString::to_string); .map(|name| name.to_string());
let Some(comp_mut_untyped) = self.get_mut_by_id(entity, component_id) else { let Some(comp_mut_untyped) = self.get_mut_by_id(entity, component_id) else {
return Err(GetComponentReflectError::EntityDoesNotHaveComponent { return Err(GetComponentReflectError::EntityDoesNotHaveComponent {