An experimental, status effects-as-entities system for Bevy.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Support non-copy types.

+68 -44
+52 -30
src/bundle_inspector.rs
··· 1 1 use crate::EffectMode; 2 - use bevy_ecs::component::ComponentId; 3 - use bevy_ecs::prelude::{Bundle, Entity, EntityRef, Name, Resource, World}; 2 + use bevy_ecs::component::{ComponentCloneBehavior, ComponentId}; 3 + use bevy_ecs::prelude::{ 4 + AppTypeRegistry, Bundle, Entity, EntityRef, Name, ReflectComponent, Resource, World, 5 + }; 4 6 use bevy_ecs::ptr::OwningPtr; 5 7 use bevy_ecs::relationship::RelationshipHookMode; 6 8 use bevy_utils::prelude::DebugName; ··· 32 34 /// 33 35 /// Should be [cleared](Self::clear) when finished. 34 36 pub fn stash_bundle<B: Bundle>(&mut self, bundle: B) -> &mut Self { 37 + self.clear(); 38 + 35 39 self.world 36 40 .entity_mut(self.scratch_entity) 37 41 .insert_with_relationship_hook_mode(bundle, RelationshipHookMode::Skip); ··· 97 101 type_id: TypeId, 98 102 src_component_id: ComponentId, 99 103 ) -> Result<&Self, MultiWorldCopyError> { 100 - let Some(existing_component_id) = dst_world.components().get_id(type_id) else { 104 + let Some(dst_component_id) = dst_world.components().get_id(type_id) else { 101 105 return Err(MultiWorldCopyError::Unregistered(type_id)); 102 106 }; 103 107 104 - let component_info = dst_world 105 - .components() 106 - .get_info(existing_component_id) 107 - .unwrap(); // Already checked that component is registered. 108 + let component_info = dst_world.components().get_info(dst_component_id).unwrap(); // Already checked that component is registered. 108 109 109 - if component_info.drop().is_some() { 110 - return Err(MultiWorldCopyError::UnCopyable(component_info.name())); 110 + match component_info.clone_behavior() { 111 + ComponentCloneBehavior::Default | ComponentCloneBehavior::Custom(_) => {} 112 + ComponentCloneBehavior::Ignore => { 113 + return Err(MultiWorldCopyError::Uncloneable(component_info.name())); 114 + } 111 115 } 112 116 113 117 let Some(src) = self.world.get_by_id(self.scratch_entity, src_component_id) else { 114 118 return Err(MultiWorldCopyError::MissingSrcComponent( 115 119 component_info.name(), 120 + self.scratch_entity, 116 121 )); 117 122 }; 118 123 119 - unsafe { 120 - // SAFETY: Contract is required to be upheld by the world. 121 - let dst = alloc(component_info.layout()); 124 + if component_info.drop().is_none() { 125 + unsafe { 126 + // SAFETY: Contract is required to be upheld by the world. 127 + let dst = alloc(component_info.layout()); 122 128 123 - // SAFETY: `dst` is allocated from the component's layout. 124 - // Both IDs provided by the caller must match, and `src` and `dst` obtained using the IDs. 125 - // `src` and `dst` are from different worlds, so cannot overlap. 126 - copy_nonoverlapping(src.as_ptr(), dst, component_info.layout().size()); 129 + // SAFETY: `dst` is allocated from the component's layout. 130 + // Both IDs provided by the caller must match, and `src` and `dst` obtained using the IDs. 131 + // `src` and `dst` are from different worlds, so cannot overlap. 132 + copy_nonoverlapping(src.as_ptr(), dst, component_info.layout().size()); 127 133 128 - let owning = OwningPtr::new(NonNull::new(dst).unwrap()); 134 + let owning = OwningPtr::new(NonNull::new(dst).unwrap()); 129 135 130 - // SAFETY: `existing_component_id` is extracted from `dst_world`. 131 - // Both IDs provided by the caller must match, `owning` was obtained using `src_component_id`. 132 - dst_world 133 - .get_entity_mut(dst_entity) 134 - .map_err(|_| MultiWorldCopyError::MissingDstEntity(dst_entity))? 135 - .insert_by_id(existing_component_id, owning); 136 + // SAFETY: `existing_component_id` is extracted from `dst_world`. 137 + // Both IDs provided by the caller must match, `owning` was obtained using `src_component_id`. 138 + dst_world 139 + .get_entity_mut(dst_entity) 140 + .map_err(|_| MultiWorldCopyError::MissingDstEntity(dst_entity))? 141 + .insert_by_id(dst_component_id, owning); 142 + } 143 + } else { 144 + let registry = dst_world.resource::<AppTypeRegistry>().clone(); 145 + let registry = registry.read(); 146 + 147 + let reflect_component = registry 148 + .get_type_data::<ReflectComponent>(type_id) 149 + .ok_or(MultiWorldCopyError::Uncloneable(component_info.name()))?; 150 + 151 + reflect_component.copy( 152 + &self.world, 153 + dst_world, 154 + self.scratch_entity, 155 + dst_entity, 156 + &registry, 157 + ); 136 158 } 137 159 138 160 Ok(self) ··· 142 164 #[derive(Debug, Eq, PartialEq, Clone)] 143 165 pub enum MultiWorldCopyError { 144 166 Unregistered(TypeId), 145 - UnCopyable(DebugName), 167 + Uncloneable(DebugName), 146 168 MissingDstEntity(Entity), 147 - MissingSrcComponent(DebugName), 169 + MissingSrcComponent(DebugName, Entity), 148 170 } 149 171 150 172 impl std::fmt::Display for MultiWorldCopyError { ··· 152 174 match self { 153 175 MultiWorldCopyError::Unregistered(type_id) => write!( 154 176 f, 155 - "Component with type ID {type_id:?} has not been registered in the inspector world, and therefor cannot be inserted using merge mode." 177 + "Component with {type_id:?} has not been registered in the destination world, and therefor cannot be cloned." 156 178 ), 157 - MultiWorldCopyError::UnCopyable(name) => write!( 179 + MultiWorldCopyError::Uncloneable(name) => write!( 158 180 f, 159 - "Component {name} cannot be copied, and therefor cannot be inserted using merge mode.", 181 + "Component {name} cannot be cloned, and therefor cannot be inserted using merge mode.", 160 182 ), 161 183 MultiWorldCopyError::MissingDstEntity(entity) => write!( 162 184 f, 163 185 "Entity {entity} does not exist in the destination world." 164 186 ), 165 - MultiWorldCopyError::MissingSrcComponent(name) => write!( 187 + MultiWorldCopyError::MissingSrcComponent(name, entity) => write!( 166 188 f, 167 - "Component {name} does not exist in inspector world, and therefor cannot be inserted using merge mode.", 189 + "Component {name} does not exist on the scratch entity {entity}, and therefor cannot be cloned.", 168 190 ), 169 191 } 170 192 }
+10 -8
src/command.rs
··· 33 33 let type_id = inspector.get_type_id(*incoming_component_id).unwrap(); 34 34 35 35 if let Some(merge) = registry.merges.get(&type_id) { 36 - merge(&mut world.entity_mut(existing_entity), &inspector.get_ref()); 37 - continue; 36 + let entity_mut = world.entity_mut(existing_entity); 37 + 38 + if entity_mut.contains_type_id(type_id) { 39 + merge(entity_mut, inspector.get_ref()); 40 + continue; 41 + } 38 42 } 39 43 40 44 unsafe { ··· 46 50 } 47 51 } 48 52 }) 49 - .or_else(|| { 50 - warn_once!("No `EffectMergeRegistry` found. Did you forget to add the `AlchemyPlugin`?"); 51 - None 52 - }); 53 + .or_else(|| { 54 + warn_once!("No `EffectMergeRegistry` found. Did you forget to add the `AlchemyPlugin`?"); 55 + None 56 + }); 53 57 }) 54 58 .or_else(|| { 55 59 warn_once!("No `BundleInspector` found. Did you forget to add the `AlchemyPlugin`?"); ··· 107 111 } 108 112 EffectMode::Merge => self.merge(world, old_entity), 109 113 } 110 - 111 - world.resource_mut::<BundleInspector>().clear(); 112 114 } 113 115 } 114 116
+1 -1
src/component/stack.rs
··· 82 82 } 83 83 84 84 /// A [merge function](crate::EffectMergeFn) for the [`EffectStacks`] component. 85 - pub fn merge_effect_stacks(existing: &mut EntityWorldMut, incoming: &EntityRef) { 85 + pub fn merge_effect_stacks(mut existing: EntityWorldMut, incoming: EntityRef) { 86 86 let incoming = incoming.get::<EffectStacks>().unwrap(); 87 87 *existing.get_mut::<EffectStacks>().unwrap() += incoming.0; 88 88 }
+2 -2
src/component/timer.rs
··· 24 24 25 25 /// A [merge function](crate::EffectMergeFn) for [`EffectTimer`] components ([`Lifetime`] and [`Delay`]). 26 26 pub fn merge_effect_timer<T: EffectTimer + Component<Mutability = Mutable>>( 27 - existing: &mut EntityWorldMut, 28 - incoming: &EntityRef, 27 + mut existing: EntityWorldMut, 28 + incoming: EntityRef, 29 29 ) { 30 30 let incoming = incoming.get::<T>().unwrap(); 31 31 existing.get_mut::<T>().unwrap().merge(incoming);
+3 -3
src/registry.rs
··· 12 12 /// #[derive(Component, Clone)] 13 13 /// struct MyEffect(f32); 14 14 /// 15 - /// fn merge_my_effect(existing: &mut EntityWorldMut, incoming: &EntityRef) { 15 + /// fn merge_my_effect(mut existing: EntityWorldMut, incoming: EntityRef) { 16 16 /// let incoming = incoming.get::<MyEffect>().unwrap(); 17 17 /// existing.get_mut::<MyEffect>().unwrap().0 += incoming.0; 18 18 /// } 19 19 /// ``` 20 - pub type EffectMergeFn = fn(existing: &mut EntityWorldMut, incoming: &EntityRef); 20 + pub type EffectMergeFn = fn(existing: EntityWorldMut, incoming: EntityRef); 21 21 22 22 /// Stores the effect merge logic for each registered component. 23 23 /// New components can be registered by providing a [`EffectMergeFn`] to the [`register`](EffectMergeRegistry::register) method. ··· 37 37 /// .register::<MyEffect>(merge_my_effect); 38 38 /// } 39 39 /// 40 - /// fn merge_my_effect(existing: &mut EntityWorldMut, incoming: &EntityRef) { 40 + /// fn merge_my_effect(mut existing: EntityWorldMut, incoming: EntityRef) { 41 41 /// let incoming = incoming.get::<MyEffect>().unwrap(); 42 42 /// existing.get_mut::<MyEffect>().unwrap().0 += incoming.0; 43 43 /// }