Skip to content

Commit 6dda873

Browse files
james7132joseph-giocart
authored
Reduce branching when inserting components (#8053)
# Objective We're currently using an unconditional `unwrap` in multiple locations when inserting bundles into an entity when we know it will never fail. This adds a large amount of extra branching that could be avoided on in release builds. ## Solution Use `DebugCheckedUnwrap` in bundle insertion code where relevant. Add and update the safety comments to match. This should remove the panicking branches from release builds, which has a significant impact on the generated code: https://github.com/james7132/bevy_asm_tests/compare/less-panicking-bundles#diff-e55a27cfb1615846ed3b6472f15a1aed66ed394d3d0739b3117f95cf90f46951R2086 shows about a 10% reduction in the number of generated instructions for `EntityMut::insert`, `EntityMut::remove`, `EntityMut::take`, and related functions. --------- Co-authored-by: JoJoJet <[email protected]> Co-authored-by: Carter Anderson <[email protected]>
1 parent 2c21d42 commit 6dda873

File tree

1 file changed

+95
-65
lines changed

1 file changed

+95
-65
lines changed

crates/bevy_ecs/src/bundle.rs

Lines changed: 95 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use crate::{
1212
},
1313
component::{Component, ComponentId, ComponentStorage, Components, StorageType, Tick},
1414
entity::{Entities, Entity, EntityLocation},
15+
query::DebugCheckedUnwrap,
1516
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
1617
TypeIdMap,
1718
};
@@ -269,10 +270,59 @@ impl SparseSetIndex for BundleId {
269270

270271
pub struct BundleInfo {
271272
id: BundleId,
273+
// SAFETY: Every ID in this list must be valid within the World that owns the BundleInfo,
274+
// must have its storage initialized (i.e. columns created in tables, sparse set created),
275+
// and must be in the same order as the source bundle type writes its components in.
272276
component_ids: Vec<ComponentId>,
273277
}
274278

275279
impl BundleInfo {
280+
/// Create a new [`BundleInfo`].
281+
///
282+
/// # Safety
283+
///
284+
// Every ID in `component_ids` must be valid within the World that owns the BundleInfo,
285+
// must have its storage initialized (i.e. columns created in tables, sparse set created),
286+
// and must be in the same order as the source bundle type writes its components in.
287+
unsafe fn new(
288+
bundle_type_name: &'static str,
289+
components: &Components,
290+
component_ids: Vec<ComponentId>,
291+
id: BundleId,
292+
) -> BundleInfo {
293+
let mut deduped = component_ids.clone();
294+
deduped.sort();
295+
deduped.dedup();
296+
297+
if deduped.len() != component_ids.len() {
298+
// TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized
299+
let mut seen = HashSet::new();
300+
let mut dups = Vec::new();
301+
for id in component_ids {
302+
if !seen.insert(id) {
303+
dups.push(id);
304+
}
305+
}
306+
307+
let names = dups
308+
.into_iter()
309+
.map(|id| {
310+
// SAFETY: the caller ensures component_id is valid.
311+
unsafe { components.get_info_unchecked(id).name() }
312+
})
313+
.collect::<Vec<_>>()
314+
.join(", ");
315+
316+
panic!("Bundle {bundle_type_name} has duplicate components: {names}");
317+
}
318+
319+
// SAFETY: The caller ensures that component_ids:
320+
// - is valid for the associated world
321+
// - has had its storage initialized
322+
// - is in the same order as the source bundle type
323+
BundleInfo { id, component_ids }
324+
}
325+
276326
#[inline]
277327
pub const fn id(&self) -> BundleId {
278328
self.id
@@ -400,7 +450,10 @@ impl BundleInfo {
400450
let component_id = *self.component_ids.get_unchecked(bundle_component);
401451
match storage_type {
402452
StorageType::Table => {
403-
let column = table.get_column_mut(component_id).unwrap();
453+
let column =
454+
// SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that
455+
// the target table contains the component.
456+
unsafe { table.get_column_mut(component_id).debug_checked_unwrap() };
404457
// SAFETY: bundle_component is a valid index for this bundle
405458
match bundle_component_status.get_status(bundle_component) {
406459
ComponentStatus::Added => {
@@ -412,11 +465,11 @@ impl BundleInfo {
412465
}
413466
}
414467
StorageType::SparseSet => {
415-
sparse_sets.get_mut(component_id).unwrap().insert(
416-
entity,
417-
component_ptr,
418-
change_tick,
419-
);
468+
let sparse_set =
469+
// SAFETY: If component_id is in self.component_ids, BundleInfo::new requires that
470+
// a sparse set exists for the component.
471+
unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() };
472+
sparse_set.insert(entity, component_ptr, change_tick);
420473
}
421474
}
422475
bundle_component += 1;
@@ -543,11 +596,13 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
543596
match &mut self.result {
544597
InsertBundleResult::SameArchetype => {
545598
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
546-
let add_bundle = self
547-
.archetype
548-
.edges()
549-
.get_add_bundle_internal(self.bundle_info.id)
550-
.unwrap();
599+
// SAFETY: The edge is assured to be initialized when creating the BundleInserter
600+
let add_bundle = unsafe {
601+
self.archetype
602+
.edges()
603+
.get_add_bundle_internal(self.bundle_info.id)
604+
.debug_checked_unwrap()
605+
};
551606
self.bundle_info.write_components(
552607
self.table,
553608
self.sparse_sets,
@@ -562,7 +617,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
562617
InsertBundleResult::NewArchetypeSameTable { new_archetype } => {
563618
let result = self.archetype.swap_remove(location.archetype_row);
564619
if let Some(swapped_entity) = result.swapped_entity {
565-
let swapped_location = self.entities.get(swapped_entity).unwrap();
620+
let swapped_location =
621+
// SAFETY: If the swap was successful, swapped_entity must be valid.
622+
unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() };
566623
self.entities.set(
567624
swapped_entity.index(),
568625
EntityLocation {
@@ -577,11 +634,13 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
577634
self.entities.set(entity.index(), new_location);
578635

579636
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
580-
let add_bundle = self
581-
.archetype
582-
.edges()
583-
.get_add_bundle_internal(self.bundle_info.id)
584-
.unwrap();
637+
// SAFETY: The edge is assured to be initialized when creating the BundleInserter
638+
let add_bundle = unsafe {
639+
self.archetype
640+
.edges()
641+
.get_add_bundle_internal(self.bundle_info.id)
642+
.debug_checked_unwrap()
643+
};
585644
self.bundle_info.write_components(
586645
self.table,
587646
self.sparse_sets,
@@ -599,7 +658,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
599658
} => {
600659
let result = self.archetype.swap_remove(location.archetype_row);
601660
if let Some(swapped_entity) = result.swapped_entity {
602-
let swapped_location = self.entities.get(swapped_entity).unwrap();
661+
let swapped_location =
662+
// SAFETY: If the swap was successful, swapped_entity must be valid.
663+
unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() };
603664
self.entities.set(
604665
swapped_entity.index(),
605666
EntityLocation {
@@ -620,7 +681,9 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
620681

621682
// if an entity was moved into this entity's table spot, update its table row
622683
if let Some(swapped_entity) = move_result.swapped_entity {
623-
let swapped_location = self.entities.get(swapped_entity).unwrap();
684+
let swapped_location =
685+
// SAFETY: If the swap was successful, swapped_entity must be valid.
686+
unsafe { self.entities.get(swapped_entity).debug_checked_unwrap() };
624687
let swapped_archetype = if self.archetype.id() == swapped_location.archetype_id
625688
{
626689
&mut *self.archetype
@@ -647,11 +710,13 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
647710
}
648711

649712
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
650-
let add_bundle = self
651-
.archetype
652-
.edges()
653-
.get_add_bundle_internal(self.bundle_info.id)
654-
.unwrap();
713+
// SAFETY: The edge is assured to be initialized when creating the BundleInserter
714+
let add_bundle = unsafe {
715+
self.archetype
716+
.edges()
717+
.get_add_bundle_internal(self.bundle_info.id)
718+
.debug_checked_unwrap()
719+
};
655720
self.bundle_info.write_components(
656721
new_table,
657722
self.sparse_sets,
@@ -750,8 +815,11 @@ impl Bundles {
750815
T::component_ids(components, storages, &mut |id| component_ids.push(id));
751816
let id = BundleId(bundle_infos.len());
752817
let bundle_info =
753-
// SAFETY: T::component_id ensures info was created
754-
unsafe { initialize_bundle(std::any::type_name::<T>(), components, component_ids, id) };
818+
// SAFETY: T::component_id ensures its:
819+
// - info was created
820+
// - appropriate storage for it has been initialized.
821+
// - was created in the same order as the components in T
822+
unsafe { BundleInfo::new(std::any::type_name::<T>(), components, component_ids, id) };
755823
bundle_infos.push(bundle_info);
756824
id
757825
});
@@ -818,44 +886,6 @@ impl Bundles {
818886
}
819887
}
820888

821-
/// # Safety
822-
///
823-
/// `component_id` must be valid [`ComponentId`]'s
824-
unsafe fn initialize_bundle(
825-
bundle_type_name: &'static str,
826-
components: &Components,
827-
component_ids: Vec<ComponentId>,
828-
id: BundleId,
829-
) -> BundleInfo {
830-
let mut deduped = component_ids.clone();
831-
deduped.sort();
832-
deduped.dedup();
833-
834-
if deduped.len() != component_ids.len() {
835-
// TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized
836-
let mut seen = HashSet::new();
837-
let mut dups = Vec::new();
838-
for id in component_ids {
839-
if !seen.insert(id) {
840-
dups.push(id);
841-
}
842-
}
843-
844-
let names = dups
845-
.into_iter()
846-
.map(|id| {
847-
// SAFETY: component_id exists and is therefore valid
848-
unsafe { components.get_info_unchecked(id).name() }
849-
})
850-
.collect::<Vec<_>>()
851-
.join(", ");
852-
853-
panic!("Bundle {bundle_type_name} has duplicate components: {names}");
854-
}
855-
856-
BundleInfo { id, component_ids }
857-
}
858-
859889
/// Asserts that all components are part of [`Components`]
860890
/// and initializes a [`BundleInfo`].
861891
fn initialize_dynamic_bundle(
@@ -875,7 +905,7 @@ fn initialize_dynamic_bundle(
875905
let id = BundleId(bundle_infos.len());
876906
let bundle_info =
877907
// SAFETY: `component_ids` are valid as they were just checked
878-
unsafe { initialize_bundle("<dynamic bundle>", components, component_ids, id) };
908+
unsafe { BundleInfo::new("<dynamic bundle>", components, component_ids, id) };
879909
bundle_infos.push(bundle_info);
880910

881911
(id, storage_types)

0 commit comments

Comments
 (0)