bevy_ecs/bundle/info.rs
1use alloc::{boxed::Box, vec, vec::Vec};
2use bevy_platform::{
3 collections::{HashMap, HashSet},
4 hash::FixedHasher,
5};
6use bevy_ptr::{MovingPtr, OwningPtr};
7use bevy_utils::TypeIdHashMap;
8use core::{any::TypeId, ptr::NonNull};
9use indexmap::{IndexMap, IndexSet};
10
11use crate::{
12 archetype::{Archetype, BundleComponentStatus, ComponentStatus},
13 bundle::{Bundle, DynamicBundle},
14 change_detection::{MaybeLocation, Tick},
15 component::{
16 ComponentId, Components, ComponentsRegistrator, RequiredComponentConstructor, StorageType,
17 },
18 entity::Entity,
19 query::DebugCheckedUnwrap as _,
20 storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
21};
22
23/// For a specific [`World`], this stores a unique value identifying a type of a registered [`Bundle`].
24///
25/// [`World`]: crate::world::World
26#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
27pub struct BundleId(usize);
28
29impl BundleId {
30 /// Returns the index of the associated [`Bundle`] type.
31 ///
32 /// Note that this is unique per-world, and should not be reused across them.
33 #[inline]
34 pub fn index(self) -> usize {
35 self.0
36 }
37}
38
39impl SparseSetIndex for BundleId {
40 #[inline]
41 fn sparse_set_index(&self) -> usize {
42 self.index()
43 }
44
45 #[inline]
46 fn get_sparse_set_index(value: usize) -> Self {
47 Self(value)
48 }
49}
50
51/// What to do on insertion if a component already exists.
52#[derive(Clone, Copy, Eq, PartialEq)]
53pub enum InsertMode {
54 /// Any existing components of a matching type will be overwritten.
55 Replace,
56 /// Any existing components of a matching type will be left unchanged.
57 Keep,
58}
59
60/// Stores metadata associated with a specific type of [`Bundle`] for a given [`World`].
61///
62/// [`World`]: crate::world::World
63pub struct BundleInfo {
64 pub(super) id: BundleId,
65
66 /// The list of all components contributed by the bundle (including Required Components). This is in
67 /// the order `[EXPLICIT_COMPONENTS][REQUIRED_COMPONENTS]`
68 ///
69 /// # Safety
70 /// Every ID in this list must be valid within the World that owns the [`BundleInfo`],
71 /// must have its storage initialized (i.e. columns created in tables, sparse set created),
72 /// and the range (0..`explicit_components_len`) must be in the same order as the source bundle
73 /// type writes its components in.
74 pub(super) contributed_component_ids: Box<[ComponentId]>,
75
76 /// The list of constructors for all required components indirectly contributed by this bundle.
77 pub(super) required_component_constructors: Box<[RequiredComponentConstructor]>,
78}
79
80impl BundleInfo {
81 /// Create a new [`BundleInfo`].
82 ///
83 /// # Safety
84 ///
85 /// Every ID in `component_ids` must be valid within the World that owns the `BundleInfo`
86 /// and must be in the same order as the source bundle type writes its components in.
87 unsafe fn new(
88 bundle_type_name: &'static str,
89 storages: &mut Storages,
90 components: &Components,
91 mut component_ids: Vec<ComponentId>,
92 id: BundleId,
93 ) -> BundleInfo {
94 let explicit_component_ids = component_ids
95 .iter()
96 .copied()
97 .collect::<IndexSet<_, FixedHasher>>();
98
99 // check for duplicates
100 if explicit_component_ids.len() != component_ids.len() {
101 // TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized
102 let mut seen = <HashSet<_>>::default();
103 let mut dups = Vec::new();
104 for id in component_ids {
105 if !seen.insert(id) {
106 dups.push(id);
107 }
108 }
109
110 let names = dups
111 .into_iter()
112 .map(|id| {
113 // SAFETY: the caller ensures component_id is valid.
114 unsafe { components.get_info_unchecked(id).name() }
115 })
116 .collect::<Vec<_>>();
117
118 panic!("Bundle {bundle_type_name} has duplicate components: {names:?}");
119 }
120
121 let mut depth_first_components = IndexMap::<_, _, FixedHasher>::default();
122 for &component_id in &component_ids {
123 // SAFETY: caller has verified that all ids are valid
124 let info = unsafe { components.get_info_unchecked(component_id) };
125
126 for (&required_id, required_component) in &info.required_components().all {
127 depth_first_components
128 .entry(required_id)
129 .or_insert_with(|| required_component.clone());
130 }
131
132 storages.prepare_component(component_id, info);
133 }
134
135 let required_components = depth_first_components
136 .into_iter()
137 .filter(|&(required_id, _)| !explicit_component_ids.contains(&required_id))
138 .inspect(|&(required_id, _)| {
139 // SAFETY: These ids came out of the passed `components`, so they must be valid.
140 storages.prepare_component(required_id, unsafe {
141 components.get_info_unchecked(required_id)
142 });
143 component_ids.push(required_id);
144 })
145 .map(|(_, required_component)| required_component.constructor)
146 .collect::<Box<_>>();
147
148 // SAFETY: The caller ensures that component_ids:
149 // - is valid for the associated world
150 // - has had its storage initialized
151 // - is in the same order as the source bundle type
152 BundleInfo {
153 id,
154 contributed_component_ids: component_ids.into(),
155 required_component_constructors: required_components,
156 }
157 }
158
159 /// Returns a value identifying the associated [`Bundle`] type.
160 #[inline]
161 pub const fn id(&self) -> BundleId {
162 self.id
163 }
164
165 /// Returns the length of the explicit components part of the [`contributed_components`](Self::contributed_components) list.
166 #[inline]
167 pub(super) fn explicit_components_len(&self) -> usize {
168 self.contributed_component_ids.len() - self.required_component_constructors.len()
169 }
170
171 /// Returns the [ID](ComponentId) of each component explicitly defined in this bundle (ex: Required Components are excluded).
172 ///
173 /// For all components contributed by this bundle (including Required Components), see [`BundleInfo::contributed_components`]
174 #[inline]
175 pub fn explicit_components(&self) -> &[ComponentId] {
176 &self.contributed_component_ids[0..self.explicit_components_len()]
177 }
178
179 /// Returns the [ID](ComponentId) of each Required Component needed by this bundle. This _does not include_ Required Components that are
180 /// explicitly provided by the bundle.
181 #[inline]
182 pub fn required_components(&self) -> &[ComponentId] {
183 &self.contributed_component_ids[self.explicit_components_len()..]
184 }
185
186 /// Returns the [ID](ComponentId) of each component contributed by this bundle. This includes Required Components.
187 ///
188 /// For only components explicitly defined in this bundle, see [`BundleInfo::explicit_components`]
189 #[inline]
190 pub fn contributed_components(&self) -> &[ComponentId] {
191 &self.contributed_component_ids
192 }
193
194 /// Returns an iterator over the [ID](ComponentId) of each component explicitly defined in this bundle (ex: this excludes Required Components).
195 /// To iterate all components contributed by this bundle (including Required Components), see [`BundleInfo::iter_contributed_components`]
196 #[inline]
197 pub fn iter_explicit_components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
198 self.explicit_components().iter().copied()
199 }
200
201 /// Returns an iterator over the [ID](ComponentId) of each component contributed by this bundle. This includes Required Components.
202 ///
203 /// To iterate only components explicitly defined in this bundle, see [`BundleInfo::iter_explicit_components`]
204 #[inline]
205 pub fn iter_contributed_components(&self) -> impl Iterator<Item = ComponentId> + Clone + '_ {
206 self.contributed_components().iter().copied()
207 }
208
209 /// Returns an iterator over the [ID](ComponentId) of each Required Component needed by this bundle. This _does not include_ Required Components that are
210 /// explicitly provided by the bundle.
211 pub fn iter_required_components(&self) -> impl Iterator<Item = ComponentId> + '_ {
212 self.required_components().iter().copied()
213 }
214
215 /// This writes components from a given [`Bundle`] to the given entity.
216 ///
217 /// # Safety
218 ///
219 /// `bundle_component_status` must return the "correct" [`ComponentStatus`] for each component
220 /// in the [`Bundle`], with respect to the entity's original archetype (prior to the bundle being added).
221 ///
222 /// For example, if the original archetype already has `ComponentA` and `T` also has `ComponentA`, the status
223 /// should be `Existing`. If the original archetype does not have `ComponentA`, the status should be `Added`.
224 ///
225 /// When "inserting" a bundle into an existing entity, [`ArchetypeAfterBundleInsert`](crate::archetype::SpawnBundleStatus)
226 /// should be used, which will report `Added` vs `Existing` status based on the current archetype's structure.
227 ///
228 /// When spawning a bundle, [`SpawnBundleStatus`](crate::archetype::SpawnBundleStatus) can be used instead,
229 /// which removes the need to look up the [`ArchetypeAfterBundleInsert`](crate::archetype::ArchetypeAfterBundleInsert)
230 /// in the archetype graph, which requires ownership of the entity's current archetype.
231 ///
232 /// Regardless of how this is used, if `T::Effect: !NoBundleEffect` then [`apply_effect`] must be called
233 /// exactly once on `bundle` after this function is called before returning to user-space safe code.
234 /// This is currently only doable via use of [`MovingPtr::partial_move`].
235 ///
236 /// `table` must be the "new" table for `entity`. `table_row` must have space allocated for the
237 /// `entity`, `bundle` must match this [`BundleInfo`]'s type
238 ///
239 /// [`apply_effect`]: crate::bundle::DynamicBundle::apply_effect
240 #[inline]
241 pub(super) unsafe fn write_components<'a, T: DynamicBundle, S: BundleComponentStatus>(
242 &self,
243 table: &mut Table,
244 sparse_sets: &mut SparseSets,
245 bundle_component_status: &S,
246 required_components: impl Iterator<Item = &'a RequiredComponentConstructor>,
247 entity: Entity,
248 table_row: TableRow,
249 change_tick: Tick,
250 bundle: MovingPtr<'_, T>,
251 insert_mode: InsertMode,
252 caller: MaybeLocation,
253 ) {
254 // NOTE: get_components calls `write_component` on each component in "bundle order".
255 // bundle_info.component_ids are also in "bundle order"
256 let mut bundle_component = 0;
257 let mut write_component = bind_lifetime(|storage_type, component_ptr| {
258 // SAFETY: bundle_component is a valid index per unsafe bundle impl
259 let component_id = *unsafe {
260 self.contributed_component_ids
261 .get_unchecked(bundle_component)
262 };
263 // SAFETY: bundle_component is a valid index per unsafe bundle impl
264 let status = unsafe { bundle_component_status.get_status(bundle_component) };
265 match storage_type {
266 StorageType::Table => {
267 let column =
268 // SAFETY: If component_id is in self.component_ids, BundleInfo::new ensures that
269 // the target table contains the component.
270 unsafe { table.get_column_mut(component_id).debug_checked_unwrap() };
271 match (status, insert_mode) {
272 // SAFETY:
273 // - table_row valid per preconditions
274 // - component_ptr valid per unsafe bundle impl
275 (ComponentStatus::Added, _) => unsafe {
276 column.initialize(table_row, component_ptr, change_tick, caller);
277 },
278 // SAFETY:
279 // - table_row valid per preconditions
280 // - component_ptr valid per unsafe bundle impl
281 // - component exists per bundle_component_status requirements
282 (ComponentStatus::Existing, InsertMode::Replace) => unsafe {
283 column.replace(table_row, component_ptr, change_tick, caller);
284 },
285 (ComponentStatus::Existing, InsertMode::Keep) => {
286 if let Some(drop_fn) = table.get_drop_for(component_id) {
287 // SAFETY: component_ptr valid per unsafe bundle impl
288 unsafe { drop_fn(component_ptr) };
289 }
290 }
291 }
292 }
293 StorageType::SparseSet => {
294 let sparse_set =
295 // SAFETY: If component_id is in self.component_ids, BundleInfo::new ensures that
296 // a sparse set exists for the component.
297 unsafe { sparse_sets.get_mut(component_id).debug_checked_unwrap() };
298 match (status, insert_mode) {
299 // SAFETY: component_ptr valid per unsafe bundle impl
300 (ComponentStatus::Added, _) | (_, InsertMode::Replace) => unsafe {
301 sparse_set.insert(entity, component_ptr, change_tick, caller);
302 },
303 (ComponentStatus::Existing, InsertMode::Keep) => {
304 if let Some(drop_fn) = sparse_set.get_drop() {
305 // SAFETY: component_ptr valid per unsafe bundle impl
306 unsafe { drop_fn(component_ptr) };
307 }
308 }
309 }
310 }
311 }
312 bundle_component += 1;
313 });
314 // Remove this once closure_lifetime_binder is stable
315 fn bind_lifetime<F: FnMut(StorageType, OwningPtr<'_>)>(func: F) -> F {
316 func
317 }
318 // SAFETY:
319 // - storage type correct per precondition
320 // - `apply_effect` called if required per precondition
321 unsafe { T::get_components(bundle, &mut write_component) };
322
323 for required_component in required_components {
324 // SAFETY: we're in write_components
325 unsafe {
326 required_component.initialize(
327 table,
328 sparse_sets,
329 change_tick,
330 table_row,
331 entity,
332 caller,
333 );
334 }
335 }
336 }
337
338 /// Internal method to initialize a required component from an [`OwningPtr`]. This should ultimately be called
339 /// in the context of [`BundleInfo::write_components`], via [`RequiredComponentConstructor::initialize`].
340 ///
341 /// # Safety
342 ///
343 /// `component_ptr` must point to a required component value that matches the given `component_id`. The `storage_type` must match
344 /// the type associated with `component_id`. The `entity` and `table_row` must correspond to an entity with an uninitialized
345 /// component matching `component_id`.
346 ///
347 /// This method _should not_ be called outside of [`BundleInfo::write_components`].
348 /// For more information, read the [`BundleInfo::write_components`] safety docs.
349 /// This function inherits the safety requirements defined there.
350 pub(crate) unsafe fn initialize_required_component(
351 table: &mut Table,
352 sparse_sets: &mut SparseSets,
353 change_tick: Tick,
354 table_row: TableRow,
355 entity: Entity,
356 component_id: ComponentId,
357 storage_type: StorageType,
358 component_ptr: OwningPtr,
359 caller: MaybeLocation,
360 ) {
361 // SAFETY:
362 // - if component_id is in required_components, `BundleInfo::new` ensured that the storage exists
363 // - caller ensures row and component_ptr are valid
364 unsafe {
365 match storage_type {
366 StorageType::Table => {
367 let column = table.get_column_mut(component_id).debug_checked_unwrap();
368 column.initialize(table_row, component_ptr, change_tick, caller);
369 }
370 StorageType::SparseSet => {
371 let sparse_set = sparse_sets.get_mut(component_id).debug_checked_unwrap();
372 sparse_set.insert(entity, component_ptr, change_tick, caller);
373 }
374 }
375 }
376 }
377}
378
379/// The type of archetype move (or lack thereof) that will result from a bundle
380/// being inserted into an entity.
381pub(crate) enum ArchetypeMoveType {
382 /// If the entity already has all of the components that are being inserted,
383 /// its archetype won't change.
384 SameArchetype,
385 /// If only [`sparse set`](StorageType::SparseSet) components are being added,
386 /// the entity's archetype will change while keeping the same table.
387 NewArchetypeSameTable { new_archetype: NonNull<Archetype> },
388 /// If any [`table-stored`](StorageType::Table) components are being added,
389 /// both the entity's archetype and table will change.
390 NewArchetypeNewTable { new_archetype: NonNull<Archetype> },
391}
392
393/// Metadata for bundles. Stores a [`BundleInfo`] for each type of [`Bundle`] in a given world.
394#[derive(Default)]
395pub struct Bundles {
396 bundle_infos: Vec<BundleInfo>,
397 /// Cache static [`BundleId`]
398 bundle_ids: TypeIdHashMap<BundleId>,
399 /// Cache bundles, which contains both explicit and required components of [`Bundle`]
400 contributed_bundle_ids: TypeIdHashMap<BundleId>,
401 /// Cache dynamic [`BundleId`] with multiple components
402 dynamic_bundle_ids: HashMap<Box<[ComponentId]>, BundleId>,
403 dynamic_bundle_storages: HashMap<BundleId, Vec<StorageType>>,
404 /// Cache optimized dynamic [`BundleId`] with single component
405 dynamic_component_bundle_ids: HashMap<ComponentId, BundleId>,
406 dynamic_component_storages: HashMap<BundleId, StorageType>,
407}
408
409impl Bundles {
410 /// The total number of [`Bundle`] registered in [`Storages`].
411 pub fn len(&self) -> usize {
412 self.bundle_infos.len()
413 }
414
415 /// Returns true if no [`Bundle`] registered in [`Storages`].
416 pub fn is_empty(&self) -> bool {
417 self.len() == 0
418 }
419
420 /// Iterate over [`BundleInfo`].
421 pub fn iter(&self) -> impl Iterator<Item = &BundleInfo> {
422 self.bundle_infos.iter()
423 }
424
425 /// Gets the metadata associated with a specific type of bundle.
426 /// Returns `None` if the bundle is not registered with the world.
427 #[inline]
428 pub fn get(&self, bundle_id: BundleId) -> Option<&BundleInfo> {
429 self.bundle_infos.get(bundle_id.index())
430 }
431
432 /// Gets the value identifying a specific type of bundle.
433 /// Returns `None` if the bundle does not exist in the world,
434 /// or if `type_id` does not correspond to a type of bundle.
435 #[inline]
436 pub fn get_id(&self, type_id: TypeId) -> Option<BundleId> {
437 self.bundle_ids.get(&type_id).copied()
438 }
439
440 /// Gets the value identifying a specific type of bundle
441 /// that contains both explicit and required components for a statically known type.
442 ///
443 /// Returns `None` if the bundle does not exist in the world,
444 /// or if `type_id` does not correspond to a type of bundle.
445 pub fn get_contributed_bundle_id(&self, type_id: TypeId) -> Option<BundleId> {
446 self.contributed_bundle_ids.get(&type_id).copied()
447 }
448
449 /// Registers a new [`BundleInfo`] for a statically known type.
450 ///
451 /// Also registers all the components in the bundle.
452 ///
453 /// # Safety
454 ///
455 /// `components` and `storages` must be from the same [`World`] as `self`.
456 ///
457 /// [`World`]: crate::world::World
458 pub(crate) unsafe fn register_info<T: Bundle>(
459 &mut self,
460 components: &mut ComponentsRegistrator,
461 storages: &mut Storages,
462 ) -> BundleId {
463 let bundle_infos = &mut self.bundle_infos;
464 *self.bundle_ids.entry(TypeId::of::<T>()).or_insert_with(|| {
465 let component_ids = T::component_ids(components).collect::<Vec<_>>();
466 let id = BundleId(bundle_infos.len());
467 let bundle_info =
468 // SAFETY: T::component_id ensures:
469 // - its info was created
470 // - appropriate storage for it has been initialized.
471 // - it was created in the same order as the components in T
472 unsafe { BundleInfo::new(core::any::type_name::<T>(), storages, components, component_ids, id) };
473 bundle_infos.push(bundle_info);
474 id
475 })
476 }
477
478 /// Registers a new [`BundleInfo`], which contains both explicit and required components for a statically known type.
479 ///
480 /// Also registers all the components in the bundle.
481 ///
482 /// # Safety
483 ///
484 /// `components` and `storages` must be from the same [`World`] as `self`.
485 ///
486 /// [`World`]: crate::world::World
487 pub(crate) unsafe fn register_contributed_bundle_info<T: Bundle>(
488 &mut self,
489 components: &mut ComponentsRegistrator,
490 storages: &mut Storages,
491 ) -> BundleId {
492 if let Some(id) = self.contributed_bundle_ids.get(&TypeId::of::<T>()).cloned() {
493 id
494 } else {
495 // SAFETY: as per the guarantees of this function, components and
496 // storages are from the same world as self
497 let explicit_bundle_id = unsafe { self.register_info::<T>(components, storages) };
498
499 // SAFETY: reading from `explicit_bundle_id` and creating new bundle in same time. Its valid because bundle hashmap allow this
500 let id = unsafe {
501 let (ptr, len) = {
502 // SAFETY: `explicit_bundle_id` is valid and defined above
503 let contributed = self
504 .get_unchecked(explicit_bundle_id)
505 .contributed_components();
506 (contributed.as_ptr(), contributed.len())
507 };
508 // SAFETY: this is sound because the contributed_components Vec for explicit_bundle_id will not be accessed mutably as
509 // part of init_dynamic_info. No mutable references will be created and the allocation will remain valid.
510 self.init_dynamic_info(storages, components, core::slice::from_raw_parts(ptr, len))
511 };
512 self.contributed_bundle_ids.insert(TypeId::of::<T>(), id);
513 id
514 }
515 }
516
517 /// # Safety
518 /// A [`BundleInfo`] with the given [`BundleId`] must have been initialized for this instance of `Bundles`.
519 pub(crate) unsafe fn get_unchecked(&self, id: BundleId) -> &BundleInfo {
520 // SAFETY: Bundle exists per precondition
521 unsafe { self.bundle_infos.get_unchecked(id.0) }
522 }
523
524 /// # Safety
525 /// This [`BundleId`] must have been initialized with a single [`Component`](crate::component::Component)
526 /// via [`init_component_info`](Self::init_dynamic_info)
527 pub(crate) unsafe fn get_storage_unchecked(&self, id: BundleId) -> StorageType {
528 // SAFETY: Added in `init_component_info`
529 *unsafe {
530 self.dynamic_component_storages
531 .get(&id)
532 .debug_checked_unwrap()
533 }
534 }
535
536 /// # Safety
537 /// This [`BundleId`] must have been initialized via [`init_dynamic_info`](Self::init_dynamic_info)
538 pub(crate) unsafe fn get_storages_unchecked(&mut self, id: BundleId) -> &mut Vec<StorageType> {
539 // SAFETY: Added in `init_dynamic_info`
540 unsafe {
541 self.dynamic_bundle_storages
542 .get_mut(&id)
543 .debug_checked_unwrap()
544 }
545 }
546
547 /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`].
548 ///
549 /// # Panics
550 ///
551 /// Panics if any of the provided [`ComponentId`]s do not exist in the
552 /// provided [`Components`].
553 pub(crate) fn init_dynamic_info(
554 &mut self,
555 storages: &mut Storages,
556 components: &Components,
557 component_ids: &[ComponentId],
558 ) -> BundleId {
559 let bundle_infos = &mut self.bundle_infos;
560
561 // Use `raw_entry_mut` to avoid cloning `component_ids` to access `Entry`
562 let (_, bundle_id) = self
563 .dynamic_bundle_ids
564 .raw_entry_mut()
565 .from_key(component_ids)
566 .or_insert_with(|| {
567 let (id, storages) = initialize_dynamic_bundle(
568 bundle_infos,
569 storages,
570 components,
571 Vec::from(component_ids),
572 );
573 // SAFETY: The ID always increases when new bundles are added, and so, the ID is unique.
574 unsafe {
575 self.dynamic_bundle_storages
576 .insert_unique_unchecked(id, storages);
577 }
578 (component_ids.into(), id)
579 });
580 *bundle_id
581 }
582
583 /// Initializes a new [`BundleInfo`] for a dynamic [`Bundle`] with single component.
584 ///
585 /// # Panics
586 ///
587 /// Panics if the provided [`ComponentId`] does not exist in the provided [`Components`].
588 pub(crate) fn init_component_info(
589 &mut self,
590 storages: &mut Storages,
591 components: &Components,
592 component_id: ComponentId,
593 ) -> BundleId {
594 let bundle_infos = &mut self.bundle_infos;
595 let bundle_id = self
596 .dynamic_component_bundle_ids
597 .entry(component_id)
598 .or_insert_with(|| {
599 let (id, storage_type) = initialize_dynamic_bundle(
600 bundle_infos,
601 storages,
602 components,
603 vec![component_id],
604 );
605 self.dynamic_component_storages.insert(id, storage_type[0]);
606 id
607 });
608 *bundle_id
609 }
610}
611
612/// Asserts that all components are part of [`Components`]
613/// and initializes a [`BundleInfo`].
614fn initialize_dynamic_bundle(
615 bundle_infos: &mut Vec<BundleInfo>,
616 storages: &mut Storages,
617 components: &Components,
618 component_ids: Vec<ComponentId>,
619) -> (BundleId, Vec<StorageType>) {
620 // Assert component existence
621 let storage_types = component_ids.iter().map(|&id| {
622 components.get_info(id).unwrap_or_else(|| {
623 panic!(
624 "init_dynamic_info called with component id {id:?} which doesn't exist in this world"
625 )
626 }).storage_type()
627 }).collect();
628
629 let id = BundleId(bundle_infos.len());
630 let bundle_info =
631 // SAFETY: `component_ids` are valid as they were just checked
632 unsafe { BundleInfo::new("<dynamic bundle>", storages, components, component_ids, id) };
633 bundle_infos.push(bundle_info);
634
635 (id, storage_types)
636}