···11use crate::EffectMode;
22-use bevy_ecs::prelude::{Bundle, Entity, Name, Resource, World};
22+use bevy_ecs::component::{ComponentCloneBehavior, ComponentId};
33+use bevy_ecs::prelude::{
44+ AppTypeRegistry, Bundle, Entity, EntityRef, Name, ReflectComponent, Resource, World,
55+};
66+use bevy_ecs::ptr::OwningPtr;
37use bevy_ecs::relationship::RelationshipHookMode;
88+use bevy_utils::prelude::DebugName;
99+use std::alloc::alloc;
1010+use std::any::TypeId;
1111+use std::error::Error;
1212+use std::fmt::Formatter;
1313+use std::ptr::{NonNull, copy_nonoverlapping};
414515#[derive(Resource)]
616pub(crate) struct BundleInspector {
···2030}
21312232impl BundleInspector {
2323- pub fn get_effect_meta<B: Bundle>(&mut self, bundle: B) -> (Option<Name>, EffectMode) {
2424- let e = self.scratch_entity;
3333+ /// Stashes a bundle so it can be inspected.
3434+ ///
3535+ /// Calls [`clear`](Self::clear) first to avoid leakage.
3636+ pub fn stash_bundle<B: Bundle>(&mut self, bundle: B) -> &mut Self {
3737+ self.clear();
3838+2539 self.world
2626- .entity_mut(e)
4040+ .entity_mut(self.scratch_entity)
2741 .insert_with_relationship_hook_mode(bundle, RelationshipHookMode::Skip);
28422929- let name = self.world.entity(e).get::<Name>().cloned();
4343+ self
4444+ }
4545+4646+ /// Clears the [stashed bundle](Self::stash_bundle).
4747+ pub fn clear(&mut self) -> &mut Self {
4848+ self.world.entity_mut(self.scratch_entity).clear();
4949+5050+ self
5151+ }
5252+5353+ /// Returns the [stashed bundle's](Self::stash_bundle) name and effect mode.
5454+ pub fn get_effect_meta(&self) -> (Option<Name>, EffectMode) {
5555+ let name = self
5656+ .world
5757+ .entity(self.scratch_entity)
5858+ .get::<Name>()
5959+ .cloned();
30603161 let mode = self
3262 .world
3333- .entity_mut(e)
6363+ .entity(self.scratch_entity)
3464 .get::<EffectMode>()
3565 .copied()
3666 .unwrap_or_default();
37673838- self.world.entity_mut(e).clear();
6868+ (name, mode)
6969+ }
39704040- (name, mode)
7171+ /// Returns a reference to the [stashed bundle](Self::stash_bundle).
7272+ pub fn get_ref(&'_ self) -> EntityRef<'_> {
7373+ self.world.entity(self.scratch_entity)
7474+ }
7575+7676+ /// Converts a component ID to a type ID, if registered.
7777+ /// The component ID must be from the inspector's world, using [`get_type_id`](Self::get_type_id).
7878+ pub fn get_type_id(&self, component_id: ComponentId) -> Option<TypeId> {
7979+ self.world
8080+ .components()
8181+ .get_info(component_id)
8282+ .and_then(|info| info.type_id())
8383+ }
8484+8585+ /// Copies a component from the [stashed bundle](Self::stash_bundle) into an entity in a different world.
8686+ /// The component ID must be from the inspector's world, using [`get_type_id`](Self::get_type_id).
8787+ ///
8888+ /// # Errors
8989+ /// Will return an error if:
9090+ /// - The component is not registered in `dst_world`.
9191+ /// - The component cannot be cloned ([`ComponentCloneBehavior::Ignore`]).
9292+ /// - The stashed bundle doesn't contain the component.
9393+ /// - The destination entity doesn't exist in `dst_world`.
9494+ /// - The resource AppTypeRegistry doesn't exist in `dst_world`.
9595+ ///
9696+ /// # Safety
9797+ /// `src_component_id` must be for the same component as `type_id`.
9898+ pub unsafe fn copy_to_world(
9999+ &self,
100100+ dst_world: &mut World,
101101+ dst_entity: Entity,
102102+ type_id: TypeId,
103103+ src_component_id: ComponentId,
104104+ ) -> Result<&Self, MultiWorldCopyError> {
105105+ let Some(dst_component_id) = dst_world.components().get_id(type_id) else {
106106+ return Err(MultiWorldCopyError::Unregistered(type_id));
107107+ };
108108+ let component_info = dst_world.components().get_info(dst_component_id).unwrap();
109109+110110+ match component_info.clone_behavior() {
111111+ ComponentCloneBehavior::Default | ComponentCloneBehavior::Custom(_) => {}
112112+ ComponentCloneBehavior::Ignore => {
113113+ return Err(MultiWorldCopyError::Uncloneable(component_info.name()));
114114+ }
115115+ }
116116+117117+ let Some(src) = self.world.get_by_id(self.scratch_entity, src_component_id) else {
118118+ return Err(MultiWorldCopyError::MissingSrcComponent(
119119+ component_info.name(),
120120+ self.scratch_entity,
121121+ ));
122122+ };
123123+124124+ if component_info.drop().is_none() {
125125+ unsafe {
126126+ // SAFETY: Contract is required to be upheld by the world.
127127+ let dst = alloc(component_info.layout());
128128+129129+ // SAFETY: `dst` is allocated from the component's layout.
130130+ // Both IDs provided by the caller must match, and `src` and `dst` are obtained using those IDs.
131131+ // `src` and `dst` are from different worlds, so cannot overlap.
132132+ copy_nonoverlapping(src.as_ptr(), dst, component_info.layout().size());
133133+134134+ // SAFETY: Both IDs provided by the caller must match, and `dst` was created from `src`.
135135+ let owning = OwningPtr::new(NonNull::new(dst).unwrap());
136136+137137+ // SAFETY: `existing_component_id` is extracted from `dst_world`.
138138+ // Both IDs provided by the caller must match, and `owning` was obtained using `src_component_id`.
139139+ dst_world
140140+ .get_entity_mut(dst_entity)
141141+ .map_err(|_| MultiWorldCopyError::MissingDstEntity(dst_entity))?
142142+ .insert_by_id(dst_component_id, owning);
143143+ }
144144+ } else {
145145+ let Some(registry) = dst_world.get_resource::<AppTypeRegistry>().cloned() else {
146146+ return Err(MultiWorldCopyError::MissingTypeRegistry);
147147+ };
148148+ let registry = registry.read();
149149+150150+ let reflect_component = registry
151151+ .get_type_data::<ReflectComponent>(type_id)
152152+ .ok_or(MultiWorldCopyError::Uncloneable(component_info.name()))?;
153153+154154+ reflect_component.copy(
155155+ &self.world,
156156+ dst_world,
157157+ self.scratch_entity,
158158+ dst_entity,
159159+ ®istry,
160160+ );
161161+ }
162162+163163+ Ok(self)
41164 }
42165}
43166167167+#[derive(Debug, Eq, PartialEq, Clone)]
168168+pub enum MultiWorldCopyError {
169169+ Unregistered(TypeId),
170170+ Uncloneable(DebugName),
171171+ MissingDstEntity(Entity),
172172+ MissingSrcComponent(DebugName, Entity),
173173+ MissingTypeRegistry,
174174+}
175175+176176+impl std::fmt::Display for MultiWorldCopyError {
177177+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
178178+ match self {
179179+ MultiWorldCopyError::Unregistered(type_id) => write!(
180180+ f,
181181+ "Component with {type_id:?} is not registered in the destination world, and therefor cannot be inserted using merge mode.",
182182+ ),
183183+ MultiWorldCopyError::Uncloneable(name) => write!(
184184+ f,
185185+ "Component {name} cannot be cloned, and therefor cannot be inserted using merge mode.",
186186+ ),
187187+ MultiWorldCopyError::MissingDstEntity(entity) => write!(
188188+ f,
189189+ "Entity {entity} does not exist in the destination world."
190190+ ),
191191+ MultiWorldCopyError::MissingSrcComponent(name, entity) => write!(
192192+ f,
193193+ "Component {name} does not exist on the scratch entity {entity}, and therefor cannot be cloned.",
194194+ ),
195195+ MultiWorldCopyError::MissingTypeRegistry => write!(
196196+ f,
197197+ "Resource AppTypeRegistry does not exist in the destination world, and therefor no components can be cloned.",
198198+ ),
199199+ }
200200+ }
201201+}
202202+203203+impl Error for MultiWorldCopyError {}
204204+44205#[cfg(test)]
45206mod tests {
46207 use super::*;
···54215 let mode = EffectMode::Insert;
5521656217 assert_eq!(
5757- inspector.get_effect_meta((name.clone(), mode)),
218218+ inspector
219219+ .stash_bundle((name.clone(), mode))
220220+ .get_effect_meta(),
58221 (Some(name), mode)
59222 );
60223 }
···6522866229 let mode = EffectMode::Insert;
672306868- assert_eq!(inspector.get_effect_meta(mode), (None, mode));
231231+ assert_eq!(inspector.stash_bundle(mode).get_effect_meta(), (None, mode));
69232 }
7023371234 #[test]
···75238 let name = Name::new("Effect");
7623977240 assert_eq!(
7878- inspector.get_effect_meta(name.clone()),
241241+ inspector.stash_bundle(name.clone()).get_effect_meta(),
79242 (Some(name), EffectMode::default())
80243 );
81244 }
···84247 fn get_effect_meta_nothing() {
85248 let mut inspector = BundleInspector::default();
862498787- assert_eq!(inspector.get_effect_meta(()), (None, EffectMode::default()));
250250+ assert_eq!(
251251+ inspector.stash_bundle(()).get_effect_meta(),
252252+ (None, EffectMode::default())
253253+ );
88254 }
8925590256 #[test]
···95261 let mode = EffectMode::Insert;
9626297263 assert_eq!(
9898- inspector.get_effect_meta((
9999- name.clone(),
100100- mode,
101101- Effecting(Entity::from_raw_u32(32).unwrap())
102102- )),
264264+ inspector
265265+ .stash_bundle((
266266+ name.clone(),
267267+ mode,
268268+ Effecting(Entity::from_raw_u32(32).unwrap())
269269+ ))
270270+ .get_effect_meta(),
103271 (Some(name), mode)
104272 );
105273 }
+43-61
src/command.rs
···11use crate::bundle_inspector::BundleInspector;
22-use crate::registry::{EffectMergeFn, EffectMergeRegistry};
22+use crate::registry::EffectMergeRegistry;
33use crate::{EffectMode, EffectedBy, Effecting};
44-use bevy_ecs::entity_disabling::Disabled;
54use bevy_ecs::prelude::*;
66-use bevy_log::warn_once;
77-use std::any::TypeId;
55+use bevy_log::{warn, warn_once};
8697/// Applies an effect to a target entity.
108/// This *might* spawn a new entity, depending on what effects are already applied to the target.
···1917}
20182119impl<B: Bundle> AddEffectCommand<B> {
2020+ /// Returns the bundle with the relationship component.
2221 fn bundle_full(self) -> (Effecting, B) {
2322 (Effecting(self.target), self.bundle)
2423 }
25242626- /// Inserts into the existing entity, and then merges the old effect into it using [`EffectMergeRegistry`].
2727- /// Only registered components that implement `Clone` will be merged.
2828- /// ## Steps
2929- /// 1. Copy unregistered components to a new temporary, disabled entity.
3030- /// 2. Insert new components into the existing entity.
3131- /// 3. Merge the old components (temp entity) with the new ones (existing entity).
3232- /// 4. Despawn temp entity.
2525+ /// Merges the [stashed bundle](Self::stash_bundle) with an entity from the given world.
2626+ /// This is done by calling the [`EffectMergeFn`] for all components in the [registry](EffectMergeRegistry).
2727+ /// Components not in the registry will be copied to the target entity.
3328 fn merge(self, world: &mut World, existing_entity: Entity) {
3434- if !world.contains_resource::<EffectMergeRegistry>() {
3535- warn_once!(
3636- "No `EffectComponentMergeRegistry` found. Did you forget to add the `AlchemyPlugin`?"
3737- );
3838- return;
3939- }
4040-4141- // Copy existing mergeable components to a temporary entity.
4242- let new_effect = existing_entity;
4343- let old_effect = {
4444- let registry = world.resource::<EffectMergeRegistry>();
4545- let allow: Vec<TypeId> = registry.merges.keys().copied().collect();
4646-4747- let temp = world.spawn(Disabled).id();
4848- world
4949- .entity_mut(existing_entity)
5050- .clone_with_opt_in(temp, |builder| {
5151- builder.without_required_components(|builder| {
5252- builder.allow_by_ids(allow);
5353- });
5454- });
5555-5656- temp
5757- };
2929+ world
3030+ .try_resource_scope::<BundleInspector, ()>(|world, inspector| {
3131+ world.try_resource_scope::<EffectMergeRegistry, ()>(|world, registry| {
3232+ for incoming_component_id in inspector.get_ref().archetype().components() {
3333+ let type_id = inspector.get_type_id(*incoming_component_id).unwrap();
58345959- world.entity_mut(new_effect).insert(self.bundle);
6060-6161- // Call merge function on those copied components.
6262- {
6363- let old = world.entity(old_effect);
6464- let archetype = old.archetype();
3535+ if let Some(merge) = registry.merges.get(&type_id) {
3636+ let entity_mut = world.entity_mut(existing_entity);
65376666- let registry = world.resource::<EffectMergeRegistry>();
3838+ if entity_mut.contains_type_id(type_id) {
3939+ merge(entity_mut, inspector.get_ref());
4040+ continue;
4141+ }
4242+ }
67436868- let merge_functions: Vec<EffectMergeFn> = archetype
6969- .components()
7070- .iter()
7171- .filter_map(|component_id| {
7272- world
7373- .components()
7474- .get_info(*component_id)
7575- .and_then(|info| info.type_id())
7676- .and_then(|id| registry.merges.get(&id).copied())
4444+ unsafe {
4545+ // SAFETY: `incoming_component_id` `type_id` were extracted from the inspector.
4646+ _ = inspector.copy_to_world(world, existing_entity, type_id, *incoming_component_id)
4747+ .inspect_err(|e| {
4848+ warn!("{e}");
4949+ });
5050+ }
5151+ }
7752 })
7878- .collect();
7979-8080- for merge in merge_functions {
8181- merge(world.entity_mut(new_effect), old_effect);
8282- }
8383- }
8484-8585- world.despawn(old_effect);
5353+ .or_else(|| {
5454+ warn_once!("No `EffectMergeRegistry` found. Did you forget to add the `AlchemyPlugin`?");
5555+ None
5656+ });
5757+ })
5858+ .or_else(|| {
5959+ warn_once!("No `BundleInspector` found. Did you forget to add the `AlchemyPlugin`?");
6060+ None
6161+ });
8662 }
8763}
88648965impl<B: Bundle + Clone> Command for AddEffectCommand<B> {
9066 fn apply(self, world: &mut World) {
9167 let mut inspector = world.get_resource_or_init::<BundleInspector>();
9292- let (name, mode) = inspector.get_effect_meta(self.bundle.clone());
6868+ let (name, mode) = inspector
6969+ .stash_bundle(self.bundle.clone())
7070+ .get_effect_meta();
93719472 if mode == EffectMode::Stack {
9573 world.spawn(self.bundle_full());
···131109 EffectMode::Insert => {
132110 world.entity_mut(old_entity).insert(self.bundle);
133111 }
134134- EffectMode::Merge => self.merge(world, old_entity),
112112+ EffectMode::Merge => {
113113+ // Ensure that all components are registered in the main world for cloning into.
114114+ world.register_bundle::<B>();
115115+ self.merge(world, old_entity)
116116+ }
135117 }
136118 }
137119}
···22use crate::registry::EffectMergeRegistry;
33use bevy_app::{App, Plugin, PreUpdate};
44use bevy_ecs::component::Mutable;
55-use bevy_ecs::prelude::{Commands, Component, Entity, Query, Res};
55+use bevy_ecs::prelude::{Commands, Component, Entity, EntityRef, Query, Res};
66use bevy_ecs::schedule::IntoScheduleConfigs;
77use bevy_ecs::world::EntityWorldMut;
88use bevy_reflect::Reflect;
99use bevy_time::{Time, Timer, TimerMode};
1010+use std::fmt::Debug;
1011use std::time::Duration;
11121213pub(crate) struct TimerPlugin;
···2223}
23242425/// A [merge function](crate::EffectMergeFn) for [`EffectTimer`] components ([`Lifetime`] and [`Delay`]).
2525-pub fn merge_effect_timer<T: EffectTimer + Component<Mutability = Mutable> + Clone>(
2626- mut new: EntityWorldMut,
2727- outgoing: Entity,
2626+pub fn merge_effect_timer<T: EffectTimer + Component<Mutability = Mutable>>(
2727+ mut existing: EntityWorldMut,
2828+ incoming: EntityRef,
2829) {
2929- let outgoing = new.world().get::<T>(outgoing).unwrap().clone();
3030- new.get_mut::<T>().unwrap().merge(&outgoing);
3030+ let incoming = incoming.get::<T>().unwrap();
3131+ existing.get_mut::<T>().unwrap().merge(incoming);
3132}
32333334/// A [timer](Timer) which is used for status effects and includes a [`TimerMergeMode`].
3434-pub trait EffectTimer: Sized {
3535+pub trait EffectTimer: Clone {
3536 /// Creates a new timer from a duration.
3637 fn new(duration: Duration) -> Self;
3738···5960 /// Behaviour depends on the current [`TimerMergeMode`].
6061 fn merge(&mut self, incoming: &Self) {
6162 match self.get_mode() {
6262- TimerMergeMode::Replace => {}
6363- TimerMergeMode::Keep => *self.get_timer_mut() = incoming.get_timer().clone(),
6363+ TimerMergeMode::Replace => self.clone_from(incoming),
6464+ TimerMergeMode::Keep => {}
6465 TimerMergeMode::Fraction => {
6565- let fraction = incoming.get_timer().fraction();
6666- let duration = self.get_timer().duration().as_secs_f32();
6666+ let fraction = self.get_timer().fraction();
6767+ let duration = incoming.get_timer().duration().as_secs_f32();
6868+6969+ self.clone_from(incoming);
6770 self.get_timer_mut()
6871 .set_elapsed(Duration::from_secs_f32(fraction * duration));
6972 }
7073 TimerMergeMode::Max => {
7171- let old = incoming.get_timer().remaining_secs();
7272- let new = self.get_timer().remaining_secs();
7474+ let old = self.get_timer().remaining_secs();
7575+ let new = incoming.get_timer().remaining_secs();
73767474- if old > new {
7575- *self.get_timer_mut() = incoming.get_timer().clone();
7777+ if new > old {
7878+ self.clone_from(incoming);
7679 }
7780 }
7881 TimerMergeMode::Sum => {
7979- let duration = incoming.get_timer().duration() + self.get_timer().duration();
8282+ let duration = self.get_timer().duration() + incoming.get_timer().duration();
8383+ self.clone_from(incoming);
8084 self.get_timer_mut().set_duration(duration);
8185 }
8286 }
+13-8
src/registry.rs
···55/// A function used to merge effects with [`EffectMode::Merge`](crate::EffectMode::Merge),
66/// which must be registered in the [registry](EffectMergeRegistry).
77///
88+/// The component the function is registered for is guaranteed to exist on both provided entities.
99+/// Note that the incoming entity exists in a **separate world**.
1010+///
811/// # Example
912/// ```rust
1013/// # use bevy_ecs::prelude::*;
···1215/// #[derive(Component, Clone)]
1316/// struct MyEffect(f32);
1417///
1515-/// fn merge_my_effect(mut new: EntityWorldMut, outgoing: Entity) {
1616-/// let outgoing = new.world().get::<MyEffect>(outgoing).unwrap().clone();
1717-/// new.get_mut::<MyEffect>().unwrap().0 += outgoing.0;
1818+/// fn merge_my_effect(mut existing: EntityWorldMut, incoming: EntityRef) {
1919+/// let mut existing = existing.get_mut::<MyEffect>().unwrap();
2020+/// let incoming = incoming.get::<MyEffect>().unwrap();
2121+/// existing.0 += incoming.0;
1822/// }
1923/// ```
2020-pub type EffectMergeFn = fn(new: EntityWorldMut, outgoing: Entity);
2424+pub type EffectMergeFn = fn(existing: EntityWorldMut, incoming: EntityRef);
21252226/// Stores the effect merge logic for each registered component.
2327/// New components can be registered by providing a [`EffectMergeFn`] to the [`register`](EffectMergeRegistry::register) method.
···3741/// .register::<MyEffect>(merge_my_effect);
3842/// }
3943///
4040-/// fn merge_my_effect(mut new: EntityWorldMut, outgoing: Entity) {
4141-/// let outgoing = new.world().get::<MyEffect>(outgoing).unwrap().clone();
4242-/// new.get_mut::<MyEffect>().unwrap().0 += outgoing.0;
4444+/// fn merge_my_effect(mut existing: EntityWorldMut, incoming: EntityRef) {
4545+/// let mut existing = existing.get_mut::<MyEffect>().unwrap();
4646+/// let incoming = incoming.get::<MyEffect>().unwrap();
4747+/// existing.0 += incoming.0;
4348/// }
4449/// ```
4550#[derive(Resource, Default)]
···4853}
49545055impl EffectMergeRegistry {
5151- /// Registers a [`EffectMergeFn`] to be run whenever two `T` status effects are merged.
5656+ /// Registers a [`EffectMergeFn`] to be run whenever two `T` status effect components are merged.
5257 pub fn register<T: Component + Clone>(&mut self, f: EffectMergeFn) -> &mut Self {
5358 self.merges.insert(TypeId::of::<T>(), f);
5459 self
···5566#[test]
77fn merge_replace() {
88- let first = Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Replace);
88+ let mut first = Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Replace);
99 let second = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Replace);
1010- let mut result = second.clone();
1111- result.merge(&first);
1010+ first.merge(&second);
12111313- assert_eq!(result, second);
1212+ assert_eq!(first, second);
1413}
15141615#[test]
1716fn merge_keep() {
1817 let first = Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Keep);
1918 let second = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Keep);
2020- let mut result = second.clone();
2121- result.merge(&first);
1919+ let mut result = first.clone();
2020+ result.merge(&second);
22212322 assert_eq!(result, first);
2423}
···2726fn merge_fraction() {
2827 let first = Lifetime::from_seconds(1.0).with_mode(TimerMergeMode::Fraction);
2928 let second = Lifetime::from_seconds(2.0).with_mode(TimerMergeMode::Fraction);
3030- let mut result = second.clone();
3131- result.merge(&first);
2929+ let mut result = first.clone();
3030+ result.merge(&second);
32313332 assert_eq!(result, second);
3433}