From 984e6cdf8ac45c37e997360b8765ca5e2f5d59a8 Mon Sep 17 00:00:00 2001 From: janis Date: Fri, 12 Sep 2025 16:26:54 +0200 Subject: [PATCH 1/7] sync/send for movingptr --- crates/bevy_ptr/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index cd7e07ddee243..ee79f339a28c6 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -807,6 +807,9 @@ impl Drop for MovingPtr<'_, T, A> { } } +unsafe impl Send for MovingPtr<'_, T, A> {} +unsafe impl Sync for MovingPtr<'_, T, A> {} + impl<'a, A: IsAligned> Ptr<'a, A> { /// Creates a new instance from a raw pointer. /// From 3328cca69035d6b9d54cb502917077270203f22b Mon Sep 17 00:00:00 2001 From: janis Date: Wed, 8 Oct 2025 01:47:13 +0200 Subject: [PATCH 2/7] remove static bound, use typeid --- crates/bevy_app/src/app.rs | 2 +- crates/bevy_ecs/Cargo.toml | 1 + crates/bevy_ecs/src/bundle/info.rs | 12 ++++++--- crates/bevy_ecs/src/bundle/mod.rs | 15 +++++++++-- crates/bevy_ecs/src/hierarchy.rs | 2 +- .../src/observer/distributed_storage.rs | 6 +++-- crates/bevy_ecs/src/observer/mod.rs | 2 +- crates/bevy_ecs/src/observer/runner.rs | 6 ++++- .../src/relationship/related_methods.rs | 6 ++--- .../src/system/commands/entity_command.rs | 4 +-- crates/bevy_ecs/src/system/commands/mod.rs | 26 +++++++++++-------- crates/bevy_ecs/src/world/entity_ref.rs | 6 ++--- examples/3d/color_grading.rs | 9 ++++--- examples/helpers/widgets.rs | 4 +-- examples/no_std/library/src/lib.rs | 8 +++--- examples/ui/box_shadow.rs | 2 +- examples/ui/button.rs | 2 +- 17 files changed, 71 insertions(+), 42 deletions(-) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 789debfac3578..5b0cb2134bdc9 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -1414,7 +1414,7 @@ impl App { /// } /// }); /// ``` - pub fn add_observer( + pub fn add_observer( &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index eb39ed859e33a..3fff2eb13171b 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -96,6 +96,7 @@ bevy_platform = { path = "../bevy_platform", version = "0.18.0-dev", default-fea "alloc", ] } +typeid = { version = "1" } bitflags = { version = "2.3", default-features = false } fixedbitset = { version = "0.5", default-features = false } serde = { version = "1", default-features = false, features = [ diff --git a/crates/bevy_ecs/src/bundle/info.rs b/crates/bevy_ecs/src/bundle/info.rs index 589ec0b7c65e6..597cd94ea99e7 100644 --- a/crates/bevy_ecs/src/bundle/info.rs +++ b/crates/bevy_ecs/src/bundle/info.rs @@ -10,7 +10,7 @@ use indexmap::{IndexMap, IndexSet}; use crate::{ archetype::{Archetype, BundleComponentStatus, ComponentStatus}, - bundle::{Bundle, DynamicBundle}, + bundle::{bundle_id_of, Bundle, DynamicBundle}, change_detection::MaybeLocation, component::{ ComponentId, Components, ComponentsRegistrator, RequiredComponentConstructor, StorageType, @@ -435,7 +435,7 @@ impl Bundles { storages: &mut Storages, ) -> BundleId { let bundle_infos = &mut self.bundle_infos; - *self.bundle_ids.entry(TypeId::of::()).or_insert_with(|| { + *self.bundle_ids.entry(bundle_id_of::()).or_insert_with(|| { let mut component_ids= Vec::new(); T::component_ids(components, &mut |id| component_ids.push(id)); let id = BundleId(bundle_infos.len()); @@ -465,7 +465,11 @@ impl Bundles { components: &mut ComponentsRegistrator, storages: &mut Storages, ) -> BundleId { - if let Some(id) = self.contributed_bundle_ids.get(&TypeId::of::()).cloned() { + if let Some(id) = self + .contributed_bundle_ids + .get(&bundle_id_of::()) + .cloned() + { id } else { // SAFETY: as per the guarantees of this function, components and @@ -485,7 +489,7 @@ impl Bundles { // part of init_dynamic_info. No mutable references will be created and the allocation will remain valid. self.init_dynamic_info(storages, components, core::slice::from_raw_parts(ptr, len)) }; - self.contributed_bundle_ids.insert(TypeId::of::(), id); + self.contributed_bundle_ids.insert(bundle_id_of::(), id); id } } diff --git a/crates/bevy_ecs/src/bundle/mod.rs b/crates/bevy_ecs/src/bundle/mod.rs index 17d894d40b660..9d74fdaf61207 100644 --- a/crates/bevy_ecs/src/bundle/mod.rs +++ b/crates/bevy_ecs/src/bundle/mod.rs @@ -15,7 +15,7 @@ pub(crate) use remove::BundleRemover; pub(crate) use spawner::BundleSpawner; use bevy_ptr::MovingPtr; -use core::mem::MaybeUninit; +use core::{any::TypeId, mem::MaybeUninit}; pub use info::*; /// Derive the [`Bundle`] trait @@ -197,7 +197,7 @@ use bevy_ptr::OwningPtr; label = "invalid `Bundle`", note = "consider annotating `{Self}` with `#[derive(Component)]` or `#[derive(Bundle)]`" )] -pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { +pub unsafe trait Bundle: DynamicBundle + Send + Sync { /// Gets this [`Bundle`]'s component ids, in the order of this bundle's [`Component`]s #[doc(hidden)] fn component_ids(components: &mut ComponentsRegistrator, ids: &mut impl FnMut(ComponentId)); @@ -206,6 +206,17 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync + 'static { fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)); } +/// Returns the `TypeId` of the bundle `T`. +pub fn bundle_id_of() -> TypeId { + typeid::of::() +} + +/// Returns the `TypeId` of the bundle type of the value `val`. +pub fn bundle_id_of_val(val: T) -> TypeId { + _ = val; + typeid::of::() +} + /// Creates a [`Bundle`] by taking it from internal storage. /// /// # Safety diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index 83419c70eb964..0ed113a812c04 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -440,7 +440,7 @@ impl<'a> EntityCommands<'a> { /// For efficient spawning of multiple children, use [`with_children`]. /// /// [`with_children`]: EntityCommands::with_children - pub fn with_child(&mut self, bundle: impl Bundle) -> &mut Self { + pub fn with_child(&mut self, bundle: impl Bundle + 'static) -> &mut Self { self.with_related::(bundle); self } diff --git a/crates/bevy_ecs/src/observer/distributed_storage.rs b/crates/bevy_ecs/src/observer/distributed_storage.rs index 3de8b486b880f..d31bb8f1e45f6 100644 --- a/crates/bevy_ecs/src/observer/distributed_storage.rs +++ b/crates/bevy_ecs/src/observer/distributed_storage.rs @@ -219,7 +219,9 @@ impl Observer { /// # Panics /// /// Panics if the given system is an exclusive system. - pub fn new>(system: I) -> Self { + pub fn new>( + system: I, + ) -> Self { let system = Box::new(IntoObserverSystem::into_system(system)); assert!( !system.is_exclusive(), @@ -430,7 +432,7 @@ impl ObserverDescriptor { /// The type parameters of this function _must_ match those used to create the [`Observer`]. /// As such, it is recommended to only use this function within the [`Observer::new`] method to /// ensure type parameters match. -fn hook_on_add>( +fn hook_on_add>( mut world: DeferredWorld<'_>, HookContext { entity, .. }: HookContext, ) { diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index d268085c40e8f..ae88a70d5b7c3 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -51,7 +51,7 @@ impl World { /// # Panics /// /// Panics if the given system is an exclusive system. - pub fn add_observer( + pub fn add_observer( &mut self, system: impl IntoObserverSystem, ) -> EntityWorldMut<'_> { diff --git a/crates/bevy_ecs/src/observer/runner.rs b/crates/bevy_ecs/src/observer/runner.rs index dfffe3bec60cd..cd9d3778ac5a7 100644 --- a/crates/bevy_ecs/src/observer/runner.rs +++ b/crates/bevy_ecs/src/observer/runner.rs @@ -32,7 +32,11 @@ pub type ObserverRunner = // NOTE: The way `Trigger` and `On` interact in this implementation is _subtle_ and _easily invalidated_ // from a soundness perspective. Please read and understand the safety comments before making any changes, // either here or in `On`. -pub(super) unsafe fn observer_system_runner>( +pub(super) unsafe fn observer_system_runner< + E: Event, + B: Bundle + 'static, + S: ObserverSystem, +>( mut world: DeferredWorld, observer: Entity, trigger_context: &TriggerContext, diff --git a/crates/bevy_ecs/src/relationship/related_methods.rs b/crates/bevy_ecs/src/relationship/related_methods.rs index e9b7849441ece..cc690011a28bb 100644 --- a/crates/bevy_ecs/src/relationship/related_methods.rs +++ b/crates/bevy_ecs/src/relationship/related_methods.rs @@ -409,7 +409,7 @@ impl<'w> EntityWorldMut<'w> { impl<'a> EntityCommands<'a> { /// Spawns a entity related to this entity (with the `R` relationship) by taking a bundle - pub fn with_related(&mut self, bundle: impl Bundle) -> &mut Self { + pub fn with_related(&mut self, bundle: impl Bundle + 'static) -> &mut Self { let parent = self.id(); self.commands.spawn((bundle, R::from(parent))); self @@ -539,7 +539,7 @@ impl<'a> EntityCommands<'a> { /// Any cycles will cause this method to loop infinitely. pub fn insert_recursive( &mut self, - bundle: impl Bundle + Clone, + bundle: impl Bundle + Clone + 'static, ) -> &mut Self { self.queue(move |mut entity: EntityWorldMut| { entity.insert_recursive::(bundle); @@ -626,7 +626,7 @@ impl<'w, R: Relationship> RelatedSpawnerCommands<'w, R> { /// Spawns an entity with the given `bundle` and an `R` relationship targeting the `target` /// entity this spawner was initialized with. - pub fn spawn(&mut self, bundle: impl Bundle) -> EntityCommands<'_> { + pub fn spawn(&mut self, bundle: impl Bundle + 'static) -> EntityCommands<'_> { self.commands.spawn((R::from(self.target), bundle)) } diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index 4d79d789806a0..1a1b6cd26a042 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -105,7 +105,7 @@ where /// An [`EntityCommand`] that adds the components in a [`Bundle`] to an entity. #[track_caller] -pub fn insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand { +pub fn insert(bundle: impl Bundle + 'static, mode: InsertMode) -> impl EntityCommand { let caller = MaybeLocation::caller(); move |mut entity: EntityWorldMut| { move_as_ptr!(bundle); @@ -248,7 +248,7 @@ pub fn despawn() -> impl EntityCommand { /// watching for an [`EntityEvent`] of type `E` whose [`EntityEvent::event_target`] /// targets this entity. #[track_caller] -pub fn observe( +pub fn observe( observer: impl IntoObserverSystem, ) -> impl EntityCommand { let caller = MaybeLocation::caller(); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 35afc7a96a3ac..43eecf62cee98 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -377,7 +377,7 @@ impl<'w, 's> Commands<'w, 's> { /// - [`spawn_batch`](Self::spawn_batch) to spawn many entities /// with the same combination of components. #[track_caller] - pub fn spawn(&mut self, bundle: T) -> EntityCommands<'_> { + pub fn spawn(&mut self, bundle: T) -> EntityCommands<'_> { let entity = self.entities.reserve_entity(); let mut entity_commands = EntityCommands { entity, @@ -1131,7 +1131,7 @@ impl<'w, 's> Commands<'w, 's> { /// Panics if the given system is an exclusive system. /// /// [`On`]: crate::observer::On - pub fn add_observer( + pub fn add_observer( &mut self, observer: impl IntoObserverSystem, ) -> EntityCommands<'_> { @@ -1387,7 +1387,7 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(add_combat_stats_system); /// ``` #[track_caller] - pub fn insert(&mut self, bundle: impl Bundle) -> &mut Self { + pub fn insert(&mut self, bundle: impl Bundle + 'static) -> &mut Self { self.queue(entity_command::insert(bundle, InsertMode::Replace)) } @@ -1416,7 +1416,7 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(add_health_system); /// ``` #[track_caller] - pub fn insert_if(&mut self, bundle: impl Bundle, condition: F) -> &mut Self + pub fn insert_if(&mut self, bundle: impl Bundle + 'static, condition: F) -> &mut Self where F: FnOnce() -> bool, { @@ -1435,7 +1435,7 @@ impl<'a> EntityCommands<'a> { /// See also [`entry`](Self::entry), which lets you modify a [`Component`] if it's present, /// as well as initialize it with a default value. #[track_caller] - pub fn insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { + pub fn insert_if_new(&mut self, bundle: impl Bundle + 'static) -> &mut Self { self.queue(entity_command::insert(bundle, InsertMode::Keep)) } @@ -1445,7 +1445,7 @@ impl<'a> EntityCommands<'a> { /// This is the same as [`EntityCommands::insert_if`], but in case of duplicate /// components will leave the old values instead of replacing them with new ones. #[track_caller] - pub fn insert_if_new_and(&mut self, bundle: impl Bundle, condition: F) -> &mut Self + pub fn insert_if_new_and(&mut self, bundle: impl Bundle + 'static, condition: F) -> &mut Self where F: FnOnce() -> bool, { @@ -1556,7 +1556,7 @@ impl<'a> EntityCommands<'a> { /// # bevy_ecs::system::assert_is_system(add_combat_stats_system); /// ``` #[track_caller] - pub fn try_insert(&mut self, bundle: impl Bundle) -> &mut Self { + pub fn try_insert(&mut self, bundle: impl Bundle + 'static) -> &mut Self { self.queue_silenced(entity_command::insert(bundle, InsertMode::Replace)) } @@ -1569,7 +1569,7 @@ impl<'a> EntityCommands<'a> { /// If the entity does not exist when this command is executed, /// the resulting error will be ignored. #[track_caller] - pub fn try_insert_if(&mut self, bundle: impl Bundle, condition: F) -> &mut Self + pub fn try_insert_if(&mut self, bundle: impl Bundle + 'static, condition: F) -> &mut Self where F: FnOnce() -> bool, { @@ -1591,7 +1591,11 @@ impl<'a> EntityCommands<'a> { /// If the entity does not exist when this command is executed, /// the resulting error will be ignored. #[track_caller] - pub fn try_insert_if_new_and(&mut self, bundle: impl Bundle, condition: F) -> &mut Self + pub fn try_insert_if_new_and( + &mut self, + bundle: impl Bundle + 'static, + condition: F, + ) -> &mut Self where F: FnOnce() -> bool, { @@ -1612,7 +1616,7 @@ impl<'a> EntityCommands<'a> { /// If the entity does not exist when this command is executed, /// the resulting error will be ignored. #[track_caller] - pub fn try_insert_if_new(&mut self, bundle: impl Bundle) -> &mut Self { + pub fn try_insert_if_new(&mut self, bundle: impl Bundle + 'static) -> &mut Self { self.queue_silenced(entity_command::insert(bundle, InsertMode::Keep)) } @@ -2007,7 +2011,7 @@ impl<'a> EntityCommands<'a> { /// Creates an [`Observer`] watching for an [`EntityEvent`] of type `E` whose [`EntityEvent::event_target`] /// targets this entity. - pub fn observe( + pub fn observe( &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 3b003ef86ef1a..462384f7bbc96 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -2209,7 +2209,7 @@ impl<'w> EntityWorldMut<'w> { /// If the entity has been despawned while this `EntityWorldMut` is still alive. #[must_use] #[track_caller] - pub fn take(&mut self) -> Option { + pub fn take(&mut self) -> Option { let location = self.location(); let entity = self.entity; @@ -2857,14 +2857,14 @@ impl<'w> EntityWorldMut<'w> { /// /// Panics if the given system is an exclusive system. #[track_caller] - pub fn observe( + pub fn observe( &mut self, observer: impl IntoObserverSystem, ) -> &mut Self { self.observe_with_caller(observer, MaybeLocation::caller()) } - pub(crate) fn observe_with_caller( + pub(crate) fn observe_with_caller( &mut self, observer: impl IntoObserverSystem, caller: MaybeLocation, diff --git a/examples/3d/color_grading.rs b/examples/3d/color_grading.rs index 12c6bb2c7099f..83347099f4ba3 100644 --- a/examples/3d/color_grading.rs +++ b/examples/3d/color_grading.rs @@ -158,7 +158,10 @@ fn add_buttons(commands: &mut Commands, font: &Handle, color_grading: &Col /// Adds the buttons for the global controls (those that control the scene as a /// whole as opposed to shadows, midtones, or highlights). -fn buttons_for_global_controls(color_grading: &ColorGrading, font: &Handle) -> impl Bundle { +fn buttons_for_global_controls( + color_grading: &ColorGrading, + font: &Handle, +) -> impl Bundle + use<> + 'static { let make_button = |option: SelectedGlobalColorGradingOption| { button_for_value( SelectedColorGradingOption::Global(option), @@ -189,7 +192,7 @@ fn buttons_for_section( section: SelectedColorGradingSection, color_grading: &ColorGrading, font: &Handle, -) -> impl Bundle { +) -> impl Bundle + 'static { let make_button = |option| { button_for_value( SelectedColorGradingOption::Section(section, option), @@ -228,7 +231,7 @@ fn button_for_value( option: SelectedColorGradingOption, color_grading: &ColorGrading, font: &Handle, -) -> impl Bundle { +) -> impl Bundle + use<> + 'static { let label = match option { SelectedColorGradingOption::Global(option) => option.to_string(), SelectedColorGradingOption::Section(_, option) => option.to_string(), diff --git a/examples/helpers/widgets.rs b/examples/helpers/widgets.rs index 041ffaa59f5d8..7a44f37d53e60 100644 --- a/examples/helpers/widgets.rs +++ b/examples/helpers/widgets.rs @@ -65,7 +65,7 @@ pub fn option_button( is_selected: bool, is_first: bool, is_last: bool, -) -> impl Bundle +) -> impl Bundle + use where T: Clone + Send + Sync + 'static, { @@ -113,7 +113,7 @@ where /// The user may change the setting to any one of the labeled `options`. The /// value of the given type parameter will be packaged up and sent as a /// [`WidgetClickEvent`] when one of the radio buttons is clicked. -pub fn option_buttons(title: &str, options: &[(T, &str)]) -> impl Bundle +pub fn option_buttons(title: &str, options: &[(T, &str)]) -> impl Bundle + use where T: Clone + Send + Sync + 'static, { diff --git a/examples/no_std/library/src/lib.rs b/examples/no_std/library/src/lib.rs index e9759efc1144c..97dc2cb35054f 100644 --- a/examples/no_std/library/src/lib.rs +++ b/examples/no_std/library/src/lib.rs @@ -74,11 +74,11 @@ impl Plugin for DelayedComponentPlugin { /// Extension trait providing [`insert_delayed`](EntityCommandsExt::insert_delayed). pub trait EntityCommandsExt { /// Insert the provided [`Bundle`] `B` with a provided `delay`. - fn insert_delayed(&mut self, bundle: B, delay: Duration) -> &mut Self; + fn insert_delayed(&mut self, bundle: B, delay: Duration) -> &mut Self; } impl EntityCommandsExt for EntityCommands<'_> { - fn insert_delayed(&mut self, bundle: B, delay: Duration) -> &mut Self { + fn insert_delayed(&mut self, bundle: B, delay: Duration) -> &mut Self { self.insert(( DelayedComponentTimer(Timer::new(delay, TimerMode::Once)), DelayedComponent(bundle), @@ -88,7 +88,7 @@ impl EntityCommandsExt for EntityCommands<'_> { } impl EntityCommandsExt for EntityWorldMut<'_> { - fn insert_delayed(&mut self, bundle: B, delay: Duration) -> &mut Self { + fn insert_delayed(&mut self, bundle: B, delay: Duration) -> &mut Self { self.insert(( DelayedComponentTimer(Timer::new(delay, TimerMode::Once)), DelayedComponent(bundle), @@ -125,7 +125,7 @@ fn tick_timers( } } -fn unwrap(event: On, world: &mut World) { +fn unwrap(event: On, world: &mut World) { if let Ok(mut target) = world.get_entity_mut(event.event_target()) && let Some(DelayedComponent(bundle)) = target.take::>() { diff --git a/examples/ui/box_shadow.rs b/examples/ui/box_shadow.rs index 130134433ba5d..c8a83e3e22ceb 100644 --- a/examples/ui/box_shadow.rs +++ b/examples/ui/box_shadow.rs @@ -294,7 +294,7 @@ fn build_setting_row( inc: SettingsButton, value: f32, asset_server: &Res, -) -> impl Bundle { +) -> impl Bundle + use<> { let value_text = match setting_type { SettingType::Shape => SHAPES[value as usize % SHAPES.len()].0.to_string(), SettingType::Count => format!("{}", value as usize), diff --git a/examples/ui/button.rs b/examples/ui/button.rs index b2090ee4dd16b..d68da5d49f539 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -70,7 +70,7 @@ fn setup(mut commands: Commands, assets: Res) { commands.spawn(button(&assets)); } -fn button(asset_server: &AssetServer) -> impl Bundle { +fn button(asset_server: &AssetServer) -> impl Bundle + use<> { ( Node { width: percent(100), From f3c6526c4d45e64bc606f05829ac0e265863dda8 Mon Sep 17 00:00:00 2001 From: janis Date: Wed, 8 Oct 2025 01:54:40 +0200 Subject: [PATCH 3/7] safety comments --- crates/bevy_ptr/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_ptr/src/lib.rs b/crates/bevy_ptr/src/lib.rs index ee79f339a28c6..10d6066980d06 100644 --- a/crates/bevy_ptr/src/lib.rs +++ b/crates/bevy_ptr/src/lib.rs @@ -807,7 +807,10 @@ impl Drop for MovingPtr<'_, T, A> { } } +// These traits aren't auto-implemented as `MovingPtr` contains a raw pointer: +// SAFETY: MovingPtr owns the value it points to and so it is `Send` if `T` is `Send`. unsafe impl Send for MovingPtr<'_, T, A> {} +// SAFETY: MovingPtr owns the value it points to and so it is `Sync` if `T` is `Sync`. unsafe impl Sync for MovingPtr<'_, T, A> {} impl<'a, A: IsAligned> Ptr<'a, A> { From 7124297828658d9bd89c16532dec70e2d6c2a686 Mon Sep 17 00:00:00 2001 From: janis Date: Wed, 8 Oct 2025 01:54:55 +0200 Subject: [PATCH 4/7] doc comments on bundle_id_of(_val) --- crates/bevy_ecs/src/bundle/mod.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ecs/src/bundle/mod.rs b/crates/bevy_ecs/src/bundle/mod.rs index 9d74fdaf61207..0c8f8cc571a26 100644 --- a/crates/bevy_ecs/src/bundle/mod.rs +++ b/crates/bevy_ecs/src/bundle/mod.rs @@ -206,12 +206,16 @@ pub unsafe trait Bundle: DynamicBundle + Send + Sync { fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)); } -/// Returns the `TypeId` of the bundle `T`. +/// Retrieves the `TypeId` of the bundle type. Used for registering bundles. +/// +/// See also [`bundle_id_of_val`] for retrieving the bundle id without naming the type. pub fn bundle_id_of() -> TypeId { typeid::of::() } -/// Returns the `TypeId` of the bundle type of the value `val`. +/// Retrieves the `TypeId` of a bundle when the type may not be easily named. Used for registering bundles. +/// +/// See also [`bundle_id_of`] for retrieving the bundle id without an instance of the bundle. pub fn bundle_id_of_val(val: T) -> TypeId { _ = val; typeid::of::() From 65de5263f701d7a241b267ccf1e80e68bf0a81d9 Mon Sep 17 00:00:00 2001 From: janis Date: Wed, 8 Oct 2025 01:55:10 +0200 Subject: [PATCH 5/7] impl bundle for movingptr --- crates/bevy_ecs/src/bundle/impls.rs | 37 +++++++++++++++++++++++++++++ crates/bevy_ecs/src/bundle/tests.rs | 12 ++++++++++ 2 files changed, 49 insertions(+) diff --git a/crates/bevy_ecs/src/bundle/impls.rs b/crates/bevy_ecs/src/bundle/impls.rs index d4ab96414c19c..9327f48417dc0 100644 --- a/crates/bevy_ecs/src/bundle/impls.rs +++ b/crates/bevy_ecs/src/bundle/impls.rs @@ -10,6 +10,43 @@ use crate::{ world::EntityWorldMut, }; +// SAFETY: `MovingPtr` forwards its implementation of `Bundle` to another `Bundle`, so it is correct if that impl is correct +unsafe impl Bundle for MovingPtr<'_, B> { + fn component_ids(components: &mut ComponentsRegistrator, ids: &mut impl FnMut(ComponentId)) { + B::component_ids(components, ids); + } + + fn get_component_ids(components: &Components, ids: &mut impl FnMut(Option)) { + B::get_component_ids(components, ids); + } +} + +impl DynamicBundle for MovingPtr<'_, T> { + type Effect = T::Effect; + + unsafe fn get_components( + ptr: MovingPtr<'_, Self>, + func: &mut impl FnMut(StorageType, OwningPtr<'_>), + ) { + let this = ptr.read(); + + T::get_components(this, func); + } + + // SAFETY: `MovingPtr` forwards its implementation of `apply_effect` to another + // `DynamicBundle`, so it is correct if that impl is correct + unsafe fn apply_effect(ptr: MovingPtr<'_, MaybeUninit>, entity: &mut EntityWorldMut) { + // SAFETY: the `MovingPtr` is still init, but it's inner value may no + // longer be fully init + let this = unsafe { + core::mem::transmute::>, MovingPtr<'_, MaybeUninit>>( + ptr.read(), + ) + }; + T::apply_effect(this, entity); + } +} + // SAFETY: // - `Bundle::component_ids` calls `ids` for C's component id (and nothing else) // - `Bundle::get_components` is called exactly once for C and passes the component's storage type based on its associated constant. diff --git a/crates/bevy_ecs/src/bundle/tests.rs b/crates/bevy_ecs/src/bundle/tests.rs index 875e604f12409..662d7be576aef 100644 --- a/crates/bevy_ecs/src/bundle/tests.rs +++ b/crates/bevy_ecs/src/bundle/tests.rs @@ -1,3 +1,5 @@ +use bevy_ptr::move_as_ptr; + use crate::{ archetype::ArchetypeCreated, lifecycle::HookContext, prelude::*, world::DeferredWorld, }; @@ -263,3 +265,13 @@ struct Ignore { #[bundle(ignore)] bar: i32, } + +#[test] +fn moving_ptr_bundle() { + let mut world = World::new(); + let v = V("yogurt"); + move_as_ptr!(v); + + let e1 = world.spawn((v, B)).id(); + assert_eq!(world.entity(e1).get::(), Some(&V("yogurt"))); +} From 23bf647a5b01c9d45a68a58a600279e2001538fc Mon Sep 17 00:00:00 2001 From: janis Date: Wed, 8 Oct 2025 02:23:52 +0200 Subject: [PATCH 6/7] more 'static --- crates/bevy_ui_widgets/src/observe.rs | 8 ++++---- examples/ui/standard_widgets.rs | 14 +++++++++----- examples/ui/standard_widgets_observers.rs | 6 +++--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/crates/bevy_ui_widgets/src/observe.rs b/crates/bevy_ui_widgets/src/observe.rs index 6fb4d177a65ed..96f26aa9f7dc3 100644 --- a/crates/bevy_ui_widgets/src/observe.rs +++ b/crates/bevy_ui_widgets/src/observe.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ }; /// Helper struct that adds an observer when inserted as a [`Bundle`]. -pub struct AddObserver> { +pub struct AddObserver> { observer: I, marker: PhantomData<(E, B, M)>, } @@ -19,7 +19,7 @@ pub struct AddObserver + Send + Sync, > Bundle for AddObserver @@ -41,7 +41,7 @@ unsafe impl< } } -impl> DynamicBundle +impl> DynamicBundle for AddObserver { type Effect = Self; @@ -67,7 +67,7 @@ impl> DynamicBundle } /// Adds an observer as a bundle effect. -pub fn observe>( +pub fn observe>( observer: I, ) -> AddObserver { AddObserver { diff --git a/examples/ui/standard_widgets.rs b/examples/ui/standard_widgets.rs index a8a0db1d05db1..6961190b58a2a 100644 --- a/examples/ui/standard_widgets.rs +++ b/examples/ui/standard_widgets.rs @@ -127,7 +127,7 @@ fn setup(mut commands: Commands, assets: Res) { commands.spawn(demo_root(&assets)); } -fn demo_root(asset_server: &AssetServer) -> impl Bundle { +fn demo_root(asset_server: &AssetServer) -> impl Bundle + use<> + 'static { ( Node { width: percent(100), @@ -177,7 +177,7 @@ fn demo_root(asset_server: &AssetServer) -> impl Bundle { ) } -fn button(asset_server: &AssetServer) -> impl Bundle { +fn button(asset_server: &AssetServer) -> impl Bundle + use<> + 'static { ( Node { width: px(150), @@ -459,7 +459,7 @@ fn thumb_color(disabled: bool, hovered: bool) -> Color { } /// Create a demo checkbox -fn checkbox(asset_server: &AssetServer, caption: &str) -> impl Bundle { +fn checkbox(asset_server: &AssetServer, caption: &str) -> impl Bundle + use<> + 'static { ( Node { display: Display::Flex, @@ -651,7 +651,7 @@ fn set_checkbox_or_radio_style( } /// Create a demo radio group -fn radio_group(asset_server: &AssetServer) -> impl Bundle { +fn radio_group(asset_server: &AssetServer) -> impl Bundle + use<> + 'static { ( Node { display: Display::Flex, @@ -672,7 +672,11 @@ fn radio_group(asset_server: &AssetServer) -> impl Bundle { } /// Create a demo radio button -fn radio(asset_server: &AssetServer, value: TrackClick, caption: &str) -> impl Bundle { +fn radio( + asset_server: &AssetServer, + value: TrackClick, + caption: &str, +) -> impl Bundle + use<> + 'static { ( Node { display: Display::Flex, diff --git a/examples/ui/standard_widgets_observers.rs b/examples/ui/standard_widgets_observers.rs index 07a3bbda1fc0e..21feb77ecb7e5 100644 --- a/examples/ui/standard_widgets_observers.rs +++ b/examples/ui/standard_widgets_observers.rs @@ -90,7 +90,7 @@ fn setup(mut commands: Commands, assets: Res) { commands.spawn(demo_root(&assets)); } -fn demo_root(asset_server: &AssetServer) -> impl Bundle { +fn demo_root(asset_server: &AssetServer) -> impl Bundle + use<> + 'static { ( Node { width: percent(100), @@ -128,7 +128,7 @@ fn demo_root(asset_server: &AssetServer) -> impl Bundle { ) } -fn button(asset_server: &AssetServer) -> impl Bundle { +fn button(asset_server: &AssetServer) -> impl Bundle + use<> + 'static { ( Node { width: px(150), @@ -332,7 +332,7 @@ fn thumb_color(disabled: bool, hovered: bool) -> Color { } /// Create a demo checkbox -fn checkbox(asset_server: &AssetServer, caption: &str) -> impl Bundle { +fn checkbox(asset_server: &AssetServer, caption: &str) -> impl Bundle + use<> + 'static { ( Node { display: Display::Flex, From fa337797f6e0ee0095471f168ca29c393e58410c Mon Sep 17 00:00:00 2001 From: janis Date: Wed, 8 Oct 2025 13:21:03 +0200 Subject: [PATCH 7/7] migration guide --- .../migration-guides/movingptr_bundle.md | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 release-content/migration-guides/movingptr_bundle.md diff --git a/release-content/migration-guides/movingptr_bundle.md b/release-content/migration-guides/movingptr_bundle.md new file mode 100644 index 0000000000000..9e92d7d12864d --- /dev/null +++ b/release-content/migration-guides/movingptr_bundle.md @@ -0,0 +1,39 @@ +--- +title: Implement `Bundle` for `MovingPtr<'_, B: Bundle>` +pull_requests: [21454] +--- + +`MovingPtr<'_, B: Bundle>` now implements the `Bundle` trait. +`MovingPtr<'_, B: Bundle>` can also be part of another bundle, such as a tuple bundle: + +```rust +fn assert_bundle() {} +assert_bundle::(); +assert_bundle::>(); +assert_bundle::<(Name, MovingPtr<'_, Transform>)>(); +``` + +Implementers of traits such as `SpawnableList`, which work with `MovingPtr`s, can now spawn bundles directly from a `MovingPtr<'_, B: Bundle>` rather than having to copy the bundle to the stack first. + +```rust +struct MyList { + bundle: B +} + +// Previously +fn spawn(this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) { + deconstruct_moving_ptr!({ + let MyList { bundle } = this; + }); + let bundle = bundle.read(); + world.spawn((bundle, R::from(entity))); +} + +// Now +fn spawn(this: MovingPtr<'_, Self>, world: &mut World, entity: Entity) { + deconstruct_moving_ptr!({ + let MyList { bundle } = this; + }); + world.spawn((bundle, R::from(entity))); +} +```