Skip to content

Commit 202faf6

Browse files
ElliottjPiercechescock
authored andcommitted
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>
1 parent 7925e03 commit 202faf6

File tree

7 files changed

+145
-62
lines changed

7 files changed

+145
-62
lines changed

crates/bevy_ecs/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ bevy_reflect = ["dep:bevy_reflect"]
3434
reflect_functions = ["bevy_reflect", "bevy_reflect/functions"]
3535

3636
## Use the configurable global error handler as the default error handler.
37-
##
37+
##
3838
## This is typically used to turn panics from the ECS into loggable errors.
3939
## This may be useful for production builds,
4040
## 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-r
110110
] }
111111

112112
bitflags = { version = "2.3", default-features = false }
113-
concurrent-queue = { version = "2.5.0", default-features = false }
114113
disqualified = { version = "1.0", default-features = false }
115114
fixedbitset = { version = "0.5", default-features = false }
116115
serde = { version = "1", default-features = false, features = [
@@ -133,6 +132,7 @@ tracing = { version = "0.1", default-features = false, optional = true }
133132
log = { version = "0.4", default-features = false }
134133
bumpalo = "3"
135134

135+
concurrent-queue = { version = "2.5.0", default-features = false }
136136
[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]
137137
concurrent-queue = { version = "2.5.0", default-features = false, features = [
138138
"portable-atomic",

crates/bevy_ecs/src/component.rs

Lines changed: 110 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1243,8 +1243,9 @@ impl ComponentCloneBehavior {
12431243

12441244
/// A queued component registration.
12451245
struct QueuedRegistration {
1246-
registrator: Box<dyn FnOnce(&mut ComponentsRegistrator, ComponentId)>,
1246+
registrator: Box<dyn FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor)>,
12471247
id: ComponentId,
1248+
descriptor: ComponentDescriptor,
12481249
}
12491250

12501251
impl QueuedRegistration {
@@ -1255,17 +1256,19 @@ impl QueuedRegistration {
12551256
/// [`ComponentId`] must be unique.
12561257
unsafe fn new(
12571258
id: ComponentId,
1258-
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static,
1259+
descriptor: ComponentDescriptor,
1260+
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
12591261
) -> Self {
12601262
Self {
12611263
registrator: Box::new(func),
12621264
id,
1265+
descriptor,
12631266
}
12641267
}
12651268

12661269
/// Performs the registration, returning the now valid [`ComponentId`].
12671270
fn register(self, registrator: &mut ComponentsRegistrator) -> ComponentId {
1268-
(self.registrator)(registrator, self.id);
1271+
(self.registrator)(registrator, self.id, self.descriptor);
12691272
self.id
12701273
}
12711274
}
@@ -1362,6 +1365,7 @@ impl ComponentIds {
13621365
///
13631366
/// As a rule of thumb, if you have mutable access to [`ComponentsRegistrator`], prefer to use that instead.
13641367
/// 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.
1368+
#[derive(Clone, Copy)]
13651369
pub struct ComponentsQueuedRegistrator<'w> {
13661370
components: &'w Components,
13671371
ids: &'w ComponentIds,
@@ -1394,7 +1398,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
13941398
unsafe fn force_register_arbitrary_component(
13951399
&self,
13961400
type_id: TypeId,
1397-
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static,
1401+
descriptor: ComponentDescriptor,
1402+
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
13981403
) -> ComponentId {
13991404
let id = self.ids.next();
14001405
self.components
@@ -1405,7 +1410,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
14051410
.insert(
14061411
type_id,
14071412
// SAFETY: The id was just generated.
1408-
unsafe { QueuedRegistration::new(id, func) },
1413+
unsafe { QueuedRegistration::new(id, descriptor, func) },
14091414
);
14101415
id
14111416
}
@@ -1418,7 +1423,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
14181423
unsafe fn force_register_arbitrary_resource(
14191424
&self,
14201425
type_id: TypeId,
1421-
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static,
1426+
descriptor: ComponentDescriptor,
1427+
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
14221428
) -> ComponentId {
14231429
let id = self.ids.next();
14241430
self.components
@@ -1429,15 +1435,16 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
14291435
.insert(
14301436
type_id,
14311437
// SAFETY: The id was just generated.
1432-
unsafe { QueuedRegistration::new(id, func) },
1438+
unsafe { QueuedRegistration::new(id, descriptor, func) },
14331439
);
14341440
id
14351441
}
14361442

14371443
/// Queues this function to run as a dynamic registrator.
14381444
fn force_register_arbitrary_dynamic(
14391445
&self,
1440-
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId) + 'static,
1446+
descriptor: ComponentDescriptor,
1447+
func: impl FnOnce(&mut ComponentsRegistrator, ComponentId, ComponentDescriptor) + 'static,
14411448
) -> ComponentId {
14421449
let id = self.ids.next();
14431450
self.components
@@ -1447,7 +1454,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
14471454
.dynamic_registrations
14481455
.push(
14491456
// SAFETY: The id was just generated.
1450-
unsafe { QueuedRegistration::new(id, func) },
1457+
unsafe { QueuedRegistration::new(id, descriptor, func) },
14511458
);
14521459
id
14531460
}
@@ -1456,6 +1463,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
14561463
/// This will reserve an id and queue the registration.
14571464
/// These registrations will be carried out at the next opportunity.
14581465
///
1466+
/// If this has already been registered or queued, this returns the previous [`ComponentId`].
1467+
///
14591468
/// # Note
14601469
///
14611470
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
@@ -1465,13 +1474,17 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
14651474
self.component_id::<T>().unwrap_or_else(|| {
14661475
// SAFETY: We just checked that this type was not in the queue.
14671476
unsafe {
1468-
self.force_register_arbitrary_component(TypeId::of::<T>(), |registrator, id| {
1469-
// 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.
1470-
#[expect(unused_unsafe, reason = "More precise to specify.")]
1471-
unsafe {
1472-
registrator.register_component_unchecked::<T>(&mut Vec::new(), id);
1473-
}
1474-
})
1477+
self.force_register_arbitrary_component(
1478+
TypeId::of::<T>(),
1479+
ComponentDescriptor::new::<T>(),
1480+
|registrator, id, _descriptor| {
1481+
// 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.
1482+
#[expect(unused_unsafe, reason = "More precise to specify.")]
1483+
unsafe {
1484+
registrator.register_component_unchecked::<T>(&mut Vec::new(), id);
1485+
}
1486+
},
1487+
)
14751488
}
14761489
})
14771490
}
@@ -1489,7 +1502,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
14891502
&self,
14901503
descriptor: ComponentDescriptor,
14911504
) -> ComponentId {
1492-
self.force_register_arbitrary_dynamic(|registrator, id| {
1505+
self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
14931506
// SAFETY: Id uniqueness handled by caller.
14941507
unsafe {
14951508
registrator.register_component_inner(id, descriptor);
@@ -1501,6 +1514,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
15011514
/// This will reserve an id and queue the registration.
15021515
/// These registrations will be carried out at the next opportunity.
15031516
///
1517+
/// If this has already been registered or queued, this returns the previous [`ComponentId`].
1518+
///
15041519
/// # Note
15051520
///
15061521
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
@@ -1511,16 +1526,18 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
15111526
self.get_resource_id(type_id).unwrap_or_else(|| {
15121527
// SAFETY: We just checked that this type was not in the queue.
15131528
unsafe {
1514-
self.force_register_arbitrary_resource(type_id, move |registrator, id| {
1515-
// 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.
1516-
// SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor.
1517-
#[expect(unused_unsafe, reason = "More precise to specify.")]
1518-
unsafe {
1519-
registrator.register_resource_unchecked_with(type_id, id, || {
1520-
ComponentDescriptor::new_resource::<T>()
1521-
});
1522-
}
1523-
})
1529+
self.force_register_arbitrary_resource(
1530+
type_id,
1531+
ComponentDescriptor::new_resource::<T>(),
1532+
move |registrator, id, descriptor| {
1533+
// 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.
1534+
// SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor.
1535+
#[expect(unused_unsafe, reason = "More precise to specify.")]
1536+
unsafe {
1537+
registrator.register_resource_unchecked(type_id, id, descriptor);
1538+
}
1539+
},
1540+
)
15241541
}
15251542
})
15261543
}
@@ -1529,6 +1546,8 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
15291546
/// This will reserve an id and queue the registration.
15301547
/// These registrations will be carried out at the next opportunity.
15311548
///
1549+
/// If this has already been registered or queued, this returns the previous [`ComponentId`].
1550+
///
15321551
/// # Note
15331552
///
15341553
/// Technically speaking, the returned [`ComponentId`] is not valid, but it will become valid later.
@@ -1539,16 +1558,18 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
15391558
self.get_resource_id(type_id).unwrap_or_else(|| {
15401559
// SAFETY: We just checked that this type was not in the queue.
15411560
unsafe {
1542-
self.force_register_arbitrary_resource(type_id, move |registrator, id| {
1543-
// 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.
1544-
// SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor.
1545-
#[expect(unused_unsafe, reason = "More precise to specify.")]
1546-
unsafe {
1547-
registrator.register_resource_unchecked_with(type_id, id, || {
1548-
ComponentDescriptor::new_non_send::<T>(StorageType::default())
1549-
});
1550-
}
1551-
})
1561+
self.force_register_arbitrary_resource(
1562+
type_id,
1563+
ComponentDescriptor::new_non_send::<T>(StorageType::default()),
1564+
move |registrator, id, descriptor| {
1565+
// 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.
1566+
// SAFETY: Id uniqueness handled by caller, and the type_id matches descriptor.
1567+
#[expect(unused_unsafe, reason = "More precise to specify.")]
1568+
unsafe {
1569+
registrator.register_resource_unchecked(type_id, id, descriptor);
1570+
}
1571+
},
1572+
)
15521573
}
15531574
})
15541575
}
@@ -1566,7 +1587,7 @@ impl<'w> ComponentsQueuedRegistrator<'w> {
15661587
&self,
15671588
descriptor: ComponentDescriptor,
15681589
) -> ComponentId {
1569-
self.force_register_arbitrary_dynamic(|registrator, id| {
1590+
self.force_register_arbitrary_dynamic(descriptor, |registrator, id, descriptor| {
15701591
// SAFETY: Id uniqueness handled by caller.
15711592
unsafe {
15721593
registrator.register_component_inner(id, descriptor);
@@ -1870,7 +1891,7 @@ impl<'w> ComponentsRegistrator<'w> {
18701891
}
18711892
}
18721893

1873-
/// Same as [`Components::register_resource_unchecked_with`] but handles safety.
1894+
/// Same as [`Components::register_resource_unchecked`] but handles safety.
18741895
///
18751896
/// # Safety
18761897
///
@@ -1901,7 +1922,7 @@ impl<'w> ComponentsRegistrator<'w> {
19011922
let id = self.ids.next_mut();
19021923
// SAFETY: The resource is not currently registered, the id is fresh, and the [`ComponentDescriptor`] matches the [`TypeId`]
19031924
unsafe {
1904-
self.register_resource_unchecked_with(type_id, id, descriptor);
1925+
self.register_resource_unchecked(type_id, id, descriptor());
19051926
}
19061927
id
19071928
}
@@ -2027,13 +2048,53 @@ impl Components {
20272048
self.components.get(id.0).and_then(|info| info.as_ref())
20282049
}
20292050

2030-
/// Returns the name associated with the given component, if it is registered.
2031-
/// This will return `None` if the id is not regiserted or is queued.
2051+
/// Gets the [`ComponentDescriptor`] of the component with this [`ComponentId`] if it is present.
2052+
/// This will return `None` only if the id is neither regisered nor queued to be registered.
2053+
///
2054+
/// Currently, the [`Cow`] will be [`Cow::Owned`] if and only if the component is queued. It will be [`Cow::Borrowed`] otherwise.
20322055
///
20332056
/// 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.
20342057
#[inline]
2035-
pub fn get_name(&self, id: ComponentId) -> Option<&str> {
2036-
self.get_info(id).map(ComponentInfo::name)
2058+
pub fn get_descriptor<'a>(&'a self, id: ComponentId) -> Option<Cow<'a, ComponentDescriptor>> {
2059+
self.components
2060+
.get(id.0)
2061+
.and_then(|info| info.as_ref().map(|info| Cow::Borrowed(&info.descriptor)))
2062+
.or_else(|| {
2063+
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
2064+
// first check components, then resources, then dynamic
2065+
queued
2066+
.components
2067+
.values()
2068+
.chain(queued.resources.values())
2069+
.chain(queued.dynamic_registrations.iter())
2070+
.find(|queued| queued.id == id)
2071+
.map(|queued| Cow::Owned(queued.descriptor.clone()))
2072+
})
2073+
}
2074+
2075+
/// Gets the name of the component with this [`ComponentId`] if it is present.
2076+
/// This will return `None` only if the id is neither regisered nor queued to be registered.
2077+
///
2078+
/// 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.
2079+
#[inline]
2080+
pub fn get_name<'a>(&'a self, id: ComponentId) -> Option<Cow<'a, str>> {
2081+
self.components
2082+
.get(id.0)
2083+
.and_then(|info| {
2084+
info.as_ref()
2085+
.map(|info| Cow::Borrowed(info.descriptor.name()))
2086+
})
2087+
.or_else(|| {
2088+
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
2089+
// first check components, then resources, then dynamic
2090+
queued
2091+
.components
2092+
.values()
2093+
.chain(queued.resources.values())
2094+
.chain(queued.dynamic_registrations.iter())
2095+
.find(|queued| queued.id == id)
2096+
.map(|queued| queued.descriptor.name.clone())
2097+
})
20372098
}
20382099

20392100
/// Gets the metadata associated with the given component.
@@ -2456,15 +2517,15 @@ impl Components {
24562517
/// The [`ComponentId`] must be unique.
24572518
/// The [`TypeId`] and [`ComponentId`] must not be registered or queued.
24582519
#[inline]
2459-
unsafe fn register_resource_unchecked_with(
2520+
unsafe fn register_resource_unchecked(
24602521
&mut self,
24612522
type_id: TypeId,
24622523
component_id: ComponentId,
2463-
func: impl FnOnce() -> ComponentDescriptor,
2524+
descriptor: ComponentDescriptor,
24642525
) {
24652526
// SAFETY: ensured by caller
24662527
unsafe {
2467-
self.register_component_inner(component_id, func());
2528+
self.register_component_inner(component_id, descriptor);
24682529
}
24692530
let prev = self.resource_indices.insert(type_id, component_id);
24702531
debug_assert!(prev.is_none());
@@ -2940,13 +3001,13 @@ pub fn enforce_no_required_components_recursion(
29403001
"Recursive required components detected: {}\nhelp: {}",
29413002
recursion_check_stack
29423003
.iter()
2943-
.map(|id| format!("{}", ShortName(components.get_name(*id).unwrap())))
3004+
.map(|id| format!("{}", ShortName(&components.get_name(*id).unwrap())))
29443005
.collect::<Vec<_>>()
29453006
.join(" → "),
29463007
if direct_recursion {
29473008
format!(
29483009
"Remove require({}).",
2949-
ShortName(components.get_name(requiree).unwrap())
3010+
ShortName(&components.get_name(requiree).unwrap())
29503011
)
29513012
} else {
29523013
"If this is intentional, consider merging the components.".into()

crates/bevy_ecs/src/query/access.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -968,11 +968,10 @@ impl AccessConflicts {
968968
format!(
969969
"{}",
970970
ShortName(
971-
world
971+
&world
972972
.components
973-
.get_info(ComponentId::get_sparse_set_index(index))
973+
.get_name(ComponentId::get_sparse_set_index(index))
974974
.unwrap()
975-
.name()
976975
)
977976
)
978977
})

0 commit comments

Comments
 (0)