Preserve spawned RelationshipTarget order and other improvements (#17858)
Fixes #17720 ## Objective Spawning RelationshipTargets from scenes currently fails to preserve RelationshipTarget ordering (ex: `Children` has an arbitrary order). This is because it uses the normal hook flow to set up the collection, which means we are pushing onto the collection in _spawn order_ (which is currently in archetype order, which will often produce mismatched orderings). We need to preserve the ordering in the original RelationshipTarget collection. Ideally without expensive checking / fixups. ## Solution One solution would be to spawn in hierarchy-order. However this gets complicated as there can be multiple hierarchies, and it also means we can't spawn in more cache-friendly orders (ex: the current per-archetype spawning, or future even-smarter per-table spawning). Additionally, same-world cloning has _slightly_ more nuanced needs (ex: recursively clone linked relationships, while maintaining _original_ relationships outside of the tree via normal hooks). The preferred approach is to directly spawn the remapped RelationshipTarget collection, as this trivially preserves the ordering. Unfortunately we can't _just_ do that, as when we spawn the children with their Relationships (ex: `ChildOf`), that will insert a duplicate. We could "fixup" the collection retroactively by just removing the back half of duplicates, but this requires another pass / more lookups / allocating twice as much space. Additionally, it becomes complicated because observers could insert additional children, making it harder (aka more expensive) to determine which children are dupes and which are not. The path I chose is to support "opting out" of the relationship target hook in the contexts that need that, as this allows us to just cheaply clone the mapped collection. The relationship hook can look for this configuration when it runs and skip its logic when that happens. A "simple" / small-amount-of-code way to do this would be to add a "skip relationship spawn" flag to World. Sadly, any hook / observer that runs _as the result of an insert_ would also read this flag. We really need a way to scope this setting to a _specific_ insert. Therefore I opted to add a new `RelationshipInsertHookMode` enum and an `entity.insert_with_relationship_insert_hook_mode` variant. Obviously this is verbose and ugly. And nobody wants _more_ insert variants. But sadly this was the best I could come up with from a performance and capability perspective. If you have alternatives let me know! There are three variants: 1. `RelationshipInsertHookMode::Run`: always run relationship insert hooks (this is the default) 2. `RelationshipInsertHookMode::Skip`: do not run any relationship insert hooks for this insert (this is used by spawner code) 3. `RelationshipInsertHookMode::RunIfNotLinked`: only run hooks for _unlinked_ relationships (this is used in same-world recursive entity cloning to preserve relationships outside of the deep-cloned tree) Note that I have intentionally only added "insert with relationship hook mode" variants to the cases we absolutely need (everything else uses the default `Run` mode), just to keep the code size in check. I do not think we should add more without real _very necessary_ use cases. I also made some other minor tweaks: 1. I split out `SourceComponent` from `ComponentCloneCtx`. Reading the source component no longer needlessly blocks mutable access to `ComponentCloneCtx`. 2. Thanks to (1), I've removed the `RefCell` wrapper over the cloned component queue. 3. (1) also allowed me to write to the EntityMapper while queuing up clones, meaning we can reserve entities during the component clone and write them to the mapper _before_ inserting the component, meaning cloned collections can be mapped on insert. 4. I've removed the closure from `write_target_component_ptr` to simplify the API / make it compatible with the split `SourceComponent` approach. 5. I've renamed `EntityCloner::recursive` to `EntityCloner::linked_cloning` to connect that feature more directly with `RelationshipTarget::LINKED_SPAWN` 6. I've removed `EntityCloneBehavior::RelationshipTarget`. This was always intended to be temporary, and this new behavior removes the need for it. --------- Co-authored-by: Viktor Gustavsson <villor94@gmail.com>
This commit is contained in:
parent
1f6642df4c
commit
a530c07bc5
@ -55,7 +55,7 @@ type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10);
|
|||||||
/// use the [`Reflect`] trait instead of [`Clone`].
|
/// use the [`Reflect`] trait instead of [`Clone`].
|
||||||
fn reflection_cloner<B: Bundle + GetTypeRegistration>(
|
fn reflection_cloner<B: Bundle + GetTypeRegistration>(
|
||||||
world: &mut World,
|
world: &mut World,
|
||||||
recursive: bool,
|
linked_cloning: bool,
|
||||||
) -> EntityCloner {
|
) -> EntityCloner {
|
||||||
// Get mutable access to the type registry, creating it if it does not exist yet.
|
// Get mutable access to the type registry, creating it if it does not exist yet.
|
||||||
let registry = world.get_resource_or_init::<AppTypeRegistry>();
|
let registry = world.get_resource_or_init::<AppTypeRegistry>();
|
||||||
@ -77,7 +77,7 @@ fn reflection_cloner<B: Bundle + GetTypeRegistration>(
|
|||||||
for component in component_ids {
|
for component in component_ids {
|
||||||
builder.override_clone_behavior_with_id(component, ComponentCloneBehavior::reflect());
|
builder.override_clone_behavior_with_id(component, ComponentCloneBehavior::reflect());
|
||||||
}
|
}
|
||||||
builder.recursive(recursive);
|
builder.linked_cloning(linked_cloning);
|
||||||
|
|
||||||
builder.finish()
|
builder.finish()
|
||||||
}
|
}
|
||||||
@ -136,7 +136,7 @@ fn bench_clone_hierarchy<B: Bundle + Default + GetTypeRegistration>(
|
|||||||
reflection_cloner::<B>(&mut world, true)
|
reflection_cloner::<B>(&mut world, true)
|
||||||
} else {
|
} else {
|
||||||
let mut builder = EntityCloner::build(&mut world);
|
let mut builder = EntityCloner::build(&mut world);
|
||||||
builder.recursive(true);
|
builder.linked_cloning(true);
|
||||||
builder.finish()
|
builder.finish()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -71,7 +71,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
Err(err) => err.into_compile_error().into(),
|
Err(err) => err.into_compile_error().into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let visit_entities = visit_entities(&ast.data, &bevy_ecs_path, relationship.is_some());
|
let visit_entities = visit_entities(
|
||||||
|
&ast.data,
|
||||||
|
&bevy_ecs_path,
|
||||||
|
relationship.is_some(),
|
||||||
|
relationship_target.is_some(),
|
||||||
|
);
|
||||||
|
|
||||||
let storage = storage_path(&bevy_ecs_path, attrs.storage);
|
let storage = storage_path(&bevy_ecs_path, attrs.storage);
|
||||||
|
|
||||||
@ -207,7 +212,7 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
|
.unwrap_or(quote! { #bevy_ecs_path::component::Mutable });
|
||||||
|
|
||||||
let clone_behavior = if relationship_target.is_some() {
|
let clone_behavior = if relationship_target.is_some() {
|
||||||
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::RelationshipTarget(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
|
quote!(#bevy_ecs_path::component::ComponentCloneBehavior::Custom(#bevy_ecs_path::relationship::clone_relationship_target::<Self>))
|
||||||
} else {
|
} else {
|
||||||
quote!(
|
quote!(
|
||||||
use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};
|
use #bevy_ecs_path::component::{DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone};
|
||||||
@ -255,7 +260,12 @@ pub fn derive_component(input: TokenStream) -> TokenStream {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> TokenStream2 {
|
fn visit_entities(
|
||||||
|
data: &Data,
|
||||||
|
bevy_ecs_path: &Path,
|
||||||
|
is_relationship: bool,
|
||||||
|
is_relationship_target: bool,
|
||||||
|
) -> TokenStream2 {
|
||||||
match data {
|
match data {
|
||||||
Data::Struct(DataStruct { fields, .. }) => {
|
Data::Struct(DataStruct { fields, .. }) => {
|
||||||
let mut visited_fields = Vec::new();
|
let mut visited_fields = Vec::new();
|
||||||
@ -288,7 +298,9 @@ fn visit_entities(data: &Data, bevy_ecs_path: &Path, is_relationship: bool) -> T
|
|||||||
}
|
}
|
||||||
Fields::Unnamed(fields) => {
|
Fields::Unnamed(fields) => {
|
||||||
for (index, field) in fields.unnamed.iter().enumerate() {
|
for (index, field) in fields.unnamed.iter().enumerate() {
|
||||||
if field
|
if index == 0 && is_relationship_target {
|
||||||
|
visited_indices.push(Index::from(0));
|
||||||
|
} else if field
|
||||||
.attrs
|
.attrs
|
||||||
.iter()
|
.iter()
|
||||||
.any(|a| a.meta.path().is_ident(ENTITIES_ATTR))
|
.any(|a| a.meta.path().is_ident(ENTITIES_ATTR))
|
||||||
|
@ -18,6 +18,7 @@ use crate::{
|
|||||||
observer::Observers,
|
observer::Observers,
|
||||||
prelude::World,
|
prelude::World,
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
|
relationship::RelationshipInsertHookMode,
|
||||||
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
|
||||||
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REPLACE},
|
world::{unsafe_world_cell::UnsafeWorldCell, EntityWorldMut, ON_ADD, ON_INSERT, ON_REPLACE},
|
||||||
};
|
};
|
||||||
@ -1100,6 +1101,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
bundle: T,
|
bundle: T,
|
||||||
insert_mode: InsertMode,
|
insert_mode: InsertMode,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode,
|
||||||
) -> (EntityLocation, T::Effect) {
|
) -> (EntityLocation, T::Effect) {
|
||||||
let bundle_info = self.bundle_info.as_ref();
|
let bundle_info = self.bundle_info.as_ref();
|
||||||
let archetype_after_insert = self.archetype_after_insert.as_ref();
|
let archetype_after_insert = self.archetype_after_insert.as_ref();
|
||||||
@ -1312,6 +1314,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
entity,
|
entity,
|
||||||
archetype_after_insert.iter_inserted(),
|
archetype_after_insert.iter_inserted(),
|
||||||
caller,
|
caller,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
);
|
);
|
||||||
if new_archetype.has_insert_observer() {
|
if new_archetype.has_insert_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
@ -1330,6 +1333,7 @@ impl<'w> BundleInserter<'w> {
|
|||||||
entity,
|
entity,
|
||||||
archetype_after_insert.iter_added(),
|
archetype_after_insert.iter_added(),
|
||||||
caller,
|
caller,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
);
|
);
|
||||||
if new_archetype.has_insert_observer() {
|
if new_archetype.has_insert_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
@ -1474,6 +1478,7 @@ impl<'w> BundleSpawner<'w> {
|
|||||||
entity,
|
entity,
|
||||||
bundle_info.iter_contributed_components(),
|
bundle_info.iter_contributed_components(),
|
||||||
caller,
|
caller,
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
);
|
);
|
||||||
if archetype.has_insert_observer() {
|
if archetype.has_insert_observer() {
|
||||||
deferred_world.trigger_observers(
|
deferred_world.trigger_observers(
|
||||||
|
@ -4,8 +4,9 @@ use crate::{
|
|||||||
archetype::ArchetypeFlags,
|
archetype::ArchetypeFlags,
|
||||||
bundle::BundleInfo,
|
bundle::BundleInfo,
|
||||||
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
|
change_detection::{MaybeLocation, MAX_CHANGE_AGE},
|
||||||
entity::{ComponentCloneCtx, Entity},
|
entity::{ComponentCloneCtx, Entity, SourceComponent},
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
|
relationship::RelationshipInsertHookMode,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
storage::{SparseSetIndex, SparseSets, Table, TableRow},
|
storage::{SparseSetIndex, SparseSets, Table, TableRow},
|
||||||
system::{Commands, Local, SystemParam},
|
system::{Commands, Local, SystemParam},
|
||||||
@ -544,6 +545,8 @@ pub struct HookContext {
|
|||||||
pub component_id: ComponentId,
|
pub component_id: ComponentId,
|
||||||
/// The caller location is `Some` if the `track_caller` feature is enabled.
|
/// The caller location is `Some` if the `track_caller` feature is enabled.
|
||||||
pub caller: MaybeLocation,
|
pub caller: MaybeLocation,
|
||||||
|
/// Configures how relationship hooks will run
|
||||||
|
pub relationship_insert_hook_mode: RelationshipInsertHookMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
|
/// [`World`]-mutating functions that run as part of lifecycle events of a [`Component`].
|
||||||
@ -1085,7 +1088,7 @@ impl ComponentDescriptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Function type that can be used to clone an entity.
|
/// Function type that can be used to clone an entity.
|
||||||
pub type ComponentCloneFn = fn(&mut Commands, &mut ComponentCloneCtx);
|
pub type ComponentCloneFn = fn(&mut Commands, &SourceComponent, &mut ComponentCloneCtx);
|
||||||
|
|
||||||
/// The clone behavior to use when cloning a [`Component`].
|
/// The clone behavior to use when cloning a [`Component`].
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
@ -1097,11 +1100,6 @@ pub enum ComponentCloneBehavior {
|
|||||||
Ignore,
|
Ignore,
|
||||||
/// Uses a custom [`ComponentCloneFn`].
|
/// Uses a custom [`ComponentCloneFn`].
|
||||||
Custom(ComponentCloneFn),
|
Custom(ComponentCloneFn),
|
||||||
/// Uses a [`ComponentCloneFn`] that produces an empty version of the given relationship target.
|
|
||||||
// TODO: this exists so that the current scene spawning code can know when to skip these components.
|
|
||||||
// When we move to actually cloning entities in scene spawning code, this should be removed in favor of Custom, as the
|
|
||||||
// distinction will no longer be necessary.
|
|
||||||
RelationshipTarget(ComponentCloneFn),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ComponentCloneBehavior {
|
impl ComponentCloneBehavior {
|
||||||
@ -1132,8 +1130,7 @@ impl ComponentCloneBehavior {
|
|||||||
match self {
|
match self {
|
||||||
ComponentCloneBehavior::Default => default,
|
ComponentCloneBehavior::Default => default,
|
||||||
ComponentCloneBehavior::Ignore => component_clone_ignore,
|
ComponentCloneBehavior::Ignore => component_clone_ignore,
|
||||||
ComponentCloneBehavior::Custom(custom)
|
ComponentCloneBehavior::Custom(custom) => *custom,
|
||||||
| ComponentCloneBehavior::RelationshipTarget(custom) => *custom,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2166,9 +2163,10 @@ pub fn enforce_no_required_components_recursion(
|
|||||||
///
|
///
|
||||||
pub fn component_clone_via_clone<C: Clone + Component>(
|
pub fn component_clone_via_clone<C: Clone + Component>(
|
||||||
_commands: &mut Commands,
|
_commands: &mut Commands,
|
||||||
|
source: &SourceComponent,
|
||||||
ctx: &mut ComponentCloneCtx,
|
ctx: &mut ComponentCloneCtx,
|
||||||
) {
|
) {
|
||||||
if let Some(component) = ctx.read_source_component::<C>() {
|
if let Some(component) = source.read::<C>() {
|
||||||
ctx.write_target_component(component.clone());
|
ctx.write_target_component(component.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2189,17 +2187,21 @@ pub fn component_clone_via_clone<C: Clone + Component>(
|
|||||||
///
|
///
|
||||||
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
|
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
|
||||||
#[cfg(feature = "bevy_reflect")]
|
#[cfg(feature = "bevy_reflect")]
|
||||||
pub fn component_clone_via_reflect(commands: &mut Commands, ctx: &mut ComponentCloneCtx) {
|
pub fn component_clone_via_reflect(
|
||||||
|
commands: &mut Commands,
|
||||||
|
source: &SourceComponent,
|
||||||
|
ctx: &mut ComponentCloneCtx,
|
||||||
|
) {
|
||||||
let Some(app_registry) = ctx.type_registry().cloned() else {
|
let Some(app_registry) = ctx.type_registry().cloned() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let Some(source_component_reflect) = ctx.read_source_component_reflect() else {
|
let registry = app_registry.read();
|
||||||
|
let Some(source_component_reflect) = source.read_reflect(®istry) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let component_info = ctx.component_info();
|
let component_info = ctx.component_info();
|
||||||
// checked in read_source_component_reflect
|
// checked in read_source_component_reflect
|
||||||
let type_id = component_info.type_id().unwrap();
|
let type_id = component_info.type_id().unwrap();
|
||||||
let registry = app_registry.read();
|
|
||||||
|
|
||||||
// Try to clone using ReflectFromReflect
|
// Try to clone using ReflectFromReflect
|
||||||
if let Some(reflect_from_reflect) =
|
if let Some(reflect_from_reflect) =
|
||||||
@ -2284,7 +2286,12 @@ pub fn component_clone_via_reflect(commands: &mut Commands, ctx: &mut ComponentC
|
|||||||
/// Noop implementation of component clone handler function.
|
/// Noop implementation of component clone handler function.
|
||||||
///
|
///
|
||||||
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
|
/// See [`EntityClonerBuilder`](crate::entity::EntityClonerBuilder) for details.
|
||||||
pub fn component_clone_ignore(_commands: &mut Commands, _ctx: &mut ComponentCloneCtx) {}
|
pub fn component_clone_ignore(
|
||||||
|
_commands: &mut Commands,
|
||||||
|
_source: &SourceComponent,
|
||||||
|
_ctx: &mut ComponentCloneCtx,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
/// Wrapper for components clone specialization using autoderef.
|
/// Wrapper for components clone specialization using autoderef.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
|
@ -1,25 +1,77 @@
|
|||||||
use alloc::{borrow::ToOwned, vec::Vec};
|
use alloc::{borrow::ToOwned, collections::VecDeque, vec::Vec};
|
||||||
use bevy_platform_support::collections::{HashMap, HashSet};
|
use bevy_platform_support::collections::{HashMap, HashSet};
|
||||||
use bevy_ptr::{Ptr, PtrMut};
|
use bevy_ptr::{Ptr, PtrMut};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use core::{any::TypeId, ptr::NonNull};
|
use core::any::TypeId;
|
||||||
|
|
||||||
#[cfg(feature = "bevy_reflect")]
|
#[cfg(feature = "bevy_reflect")]
|
||||||
use alloc::boxed::Box;
|
use alloc::boxed::Box;
|
||||||
|
|
||||||
use crate::component::{ComponentCloneBehavior, ComponentCloneFn};
|
use crate::component::{ComponentCloneBehavior, ComponentCloneFn};
|
||||||
use crate::entity::hash_map::EntityHashMap;
|
use crate::entity::hash_map::EntityHashMap;
|
||||||
use crate::entity::EntityMapper;
|
use crate::entity::{Entities, EntityMapper};
|
||||||
|
use crate::relationship::RelationshipInsertHookMode;
|
||||||
use crate::system::Commands;
|
use crate::system::Commands;
|
||||||
use crate::{
|
use crate::{
|
||||||
bundle::Bundle,
|
bundle::Bundle,
|
||||||
component::{Component, ComponentId, ComponentInfo, Components},
|
component::{Component, ComponentId, ComponentInfo},
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
query::DebugCheckedUnwrap,
|
query::DebugCheckedUnwrap,
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
use alloc::collections::VecDeque;
|
|
||||||
use core::cell::RefCell;
|
/// Provides read access to the source component (the component being cloned) in a [`ComponentCloneFn`].
|
||||||
|
pub struct SourceComponent<'a> {
|
||||||
|
ptr: Ptr<'a>,
|
||||||
|
info: &'a ComponentInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> SourceComponent<'a> {
|
||||||
|
/// Returns a reference to the component on the source entity.
|
||||||
|
///
|
||||||
|
/// Will return `None` if `ComponentId` of requested component does not match `ComponentId` of source component
|
||||||
|
pub fn read<C: Component>(&self) -> Option<&C> {
|
||||||
|
if self
|
||||||
|
.info
|
||||||
|
.type_id()
|
||||||
|
.is_some_and(|id| id == TypeId::of::<C>())
|
||||||
|
{
|
||||||
|
// SAFETY:
|
||||||
|
// - Components and ComponentId are from the same world
|
||||||
|
// - source_component_ptr holds valid data of the type referenced by ComponentId
|
||||||
|
unsafe { Some(self.ptr.deref::<C>()) }
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the "raw" pointer to the source component.
|
||||||
|
pub fn ptr(&self) -> Ptr<'a> {
|
||||||
|
self.ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the component on the source entity as [`&dyn Reflect`](bevy_reflect::Reflect).
|
||||||
|
///
|
||||||
|
/// Will return `None` if:
|
||||||
|
/// - World does not have [`AppTypeRegistry`](`crate::reflect::AppTypeRegistry`).
|
||||||
|
/// - Component does not implement [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr).
|
||||||
|
/// - Component is not registered.
|
||||||
|
/// - Component does not have [`TypeId`]
|
||||||
|
/// - Registered [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr)'s [`TypeId`] does not match component's [`TypeId`]
|
||||||
|
#[cfg(feature = "bevy_reflect")]
|
||||||
|
pub fn read_reflect(
|
||||||
|
&self,
|
||||||
|
registry: &bevy_reflect::TypeRegistry,
|
||||||
|
) -> Option<&dyn bevy_reflect::Reflect> {
|
||||||
|
let type_id = self.info.type_id()?;
|
||||||
|
let reflect_from_ptr = registry.get_type_data::<bevy_reflect::ReflectFromPtr>(type_id)?;
|
||||||
|
if reflect_from_ptr.type_id() != type_id {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// SAFETY: `source_component_ptr` stores data represented by `component_id`, which we used to get `ReflectFromPtr`.
|
||||||
|
unsafe { Some(reflect_from_ptr.as_reflect(self.ptr)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Context for component clone handlers.
|
/// Context for component clone handlers.
|
||||||
///
|
///
|
||||||
@ -27,13 +79,12 @@ use core::cell::RefCell;
|
|||||||
/// and allows component clone handler to get information about component being cloned.
|
/// and allows component clone handler to get information about component being cloned.
|
||||||
pub struct ComponentCloneCtx<'a, 'b> {
|
pub struct ComponentCloneCtx<'a, 'b> {
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
source_component_ptr: Ptr<'a>,
|
|
||||||
target_component_written: bool,
|
target_component_written: bool,
|
||||||
bundle_scratch: &'a mut BundleScratch<'b>,
|
bundle_scratch: &'a mut BundleScratch<'b>,
|
||||||
bundle_scratch_allocator: &'b Bump,
|
bundle_scratch_allocator: &'b Bump,
|
||||||
|
entities: &'a Entities,
|
||||||
source: Entity,
|
source: Entity,
|
||||||
target: Entity,
|
target: Entity,
|
||||||
components: &'a Components,
|
|
||||||
component_info: &'a ComponentInfo,
|
component_info: &'a ComponentInfo,
|
||||||
entity_cloner: &'a mut EntityCloner,
|
entity_cloner: &'a mut EntityCloner,
|
||||||
mapper: &'a mut dyn EntityMapper,
|
mapper: &'a mut dyn EntityMapper,
|
||||||
@ -49,16 +100,16 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// Caller must ensure that:
|
/// Caller must ensure that:
|
||||||
/// - `components` and `component_id` are from the same world.
|
/// - `component_info` corresponds to the `component_id` in the same world,.
|
||||||
/// - `source_component_ptr` points to a valid component of type represented by `component_id`.
|
/// - `source_component_ptr` points to a valid component of type represented by `component_id`.
|
||||||
unsafe fn new(
|
unsafe fn new(
|
||||||
component_id: ComponentId,
|
component_id: ComponentId,
|
||||||
source: Entity,
|
source: Entity,
|
||||||
target: Entity,
|
target: Entity,
|
||||||
source_component_ptr: Ptr<'a>,
|
|
||||||
bundle_scratch_allocator: &'b Bump,
|
bundle_scratch_allocator: &'b Bump,
|
||||||
bundle_scratch: &'a mut BundleScratch<'b>,
|
bundle_scratch: &'a mut BundleScratch<'b>,
|
||||||
components: &'a Components,
|
entities: &'a Entities,
|
||||||
|
component_info: &'a ComponentInfo,
|
||||||
entity_cloner: &'a mut EntityCloner,
|
entity_cloner: &'a mut EntityCloner,
|
||||||
mapper: &'a mut dyn EntityMapper,
|
mapper: &'a mut dyn EntityMapper,
|
||||||
#[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>,
|
#[cfg(feature = "bevy_reflect")] type_registry: Option<&'a crate::reflect::AppTypeRegistry>,
|
||||||
@ -68,13 +119,12 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
component_id,
|
component_id,
|
||||||
source,
|
source,
|
||||||
target,
|
target,
|
||||||
source_component_ptr,
|
|
||||||
bundle_scratch,
|
bundle_scratch,
|
||||||
target_component_written: false,
|
target_component_written: false,
|
||||||
bundle_scratch_allocator,
|
bundle_scratch_allocator,
|
||||||
components,
|
entities,
|
||||||
mapper,
|
mapper,
|
||||||
component_info: components.get_info_unchecked(component_id),
|
component_info,
|
||||||
entity_cloner,
|
entity_cloner,
|
||||||
type_registry,
|
type_registry,
|
||||||
}
|
}
|
||||||
@ -109,8 +159,8 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
/// entities stored in a cloned entity's [`RelationshipTarget`](crate::relationship::RelationshipTarget) component with
|
/// entities stored in a cloned entity's [`RelationshipTarget`](crate::relationship::RelationshipTarget) component with
|
||||||
/// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) will also be cloned.
|
/// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN) will also be cloned.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_recursive(&self) -> bool {
|
pub fn linked_cloning(&self) -> bool {
|
||||||
self.entity_cloner.is_recursive
|
self.entity_cloner.linked_cloning
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns this context's [`EntityMapper`].
|
/// Returns this context's [`EntityMapper`].
|
||||||
@ -118,44 +168,6 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
self.mapper
|
self.mapper
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a reference to the component on the source entity.
|
|
||||||
///
|
|
||||||
/// Will return `None` if `ComponentId` of requested component does not match `ComponentId` of source component
|
|
||||||
pub fn read_source_component<T: Component>(&self) -> Option<&T> {
|
|
||||||
if self
|
|
||||||
.component_info
|
|
||||||
.type_id()
|
|
||||||
.is_some_and(|id| id == TypeId::of::<T>())
|
|
||||||
{
|
|
||||||
// SAFETY:
|
|
||||||
// - Components and ComponentId are from the same world
|
|
||||||
// - source_component_ptr holds valid data of the type referenced by ComponentId
|
|
||||||
unsafe { Some(self.source_component_ptr.deref::<T>()) }
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a reference to the component on the source entity as [`&dyn Reflect`](bevy_reflect::Reflect).
|
|
||||||
///
|
|
||||||
/// Will return `None` if:
|
|
||||||
/// - World does not have [`AppTypeRegistry`](`crate::reflect::AppTypeRegistry`).
|
|
||||||
/// - Component does not implement [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr).
|
|
||||||
/// - Component is not registered.
|
|
||||||
/// - Component does not have [`TypeId`]
|
|
||||||
/// - Registered [`ReflectFromPtr`](bevy_reflect::ReflectFromPtr)'s [`TypeId`] does not match component's [`TypeId`]
|
|
||||||
#[cfg(feature = "bevy_reflect")]
|
|
||||||
pub fn read_source_component_reflect(&self) -> Option<&dyn bevy_reflect::Reflect> {
|
|
||||||
let registry = self.type_registry?.read();
|
|
||||||
let type_id = self.component_info.type_id()?;
|
|
||||||
let reflect_from_ptr = registry.get_type_data::<bevy_reflect::ReflectFromPtr>(type_id)?;
|
|
||||||
if reflect_from_ptr.type_id() != type_id {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
// SAFETY: `source_component_ptr` stores data represented by `component_id`, which we used to get `ReflectFromPtr`.
|
|
||||||
unsafe { Some(reflect_from_ptr.as_reflect(self.source_component_ptr)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Writes component data to target entity.
|
/// Writes component data to target entity.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
@ -186,33 +198,24 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
self.target_component_written = true;
|
self.target_component_written = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes component data to target entity by providing a pointer to source component data and a pointer to uninitialized target component data.
|
/// Writes component data to target entity by providing a pointer to source component data.
|
||||||
///
|
|
||||||
/// This method allows caller to provide a function (`clone_fn`) to clone component using untyped pointers.
|
|
||||||
/// First argument to `clone_fn` points to source component data ([`Ptr`]), second argument points to uninitialized buffer ([`NonNull`]) allocated with layout
|
|
||||||
/// described by [`ComponentInfo`] stored in this [`ComponentCloneCtx`]. If cloning is successful and uninitialized buffer contains a valid clone of
|
|
||||||
/// source component, `clone_fn` should return `true`, otherwise it should return `false`.
|
|
||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// Caller must ensure that if `clone_fn` is called and returns `true`, the second argument ([`NonNull`] pointer) points to a valid component data
|
/// Caller must ensure that the passed in `ptr` references data that corresponds to the type of the source / target [`ComponentId`].
|
||||||
/// described by [`ComponentInfo`] stored in this [`ComponentCloneCtx`].
|
/// `ptr` must also contain data that the written component can "own" (for example, this should not directly copy non-Copy data).
|
||||||
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// This will panic if component has already been written once.
|
/// This will panic if component has already been written once.
|
||||||
pub unsafe fn write_target_component_ptr(
|
pub unsafe fn write_target_component_ptr(&mut self, ptr: Ptr) {
|
||||||
&mut self,
|
|
||||||
clone_fn: impl FnOnce(Ptr, NonNull<u8>) -> bool,
|
|
||||||
) {
|
|
||||||
if self.target_component_written {
|
if self.target_component_written {
|
||||||
panic!("Trying to write component multiple times")
|
panic!("Trying to write component multiple times")
|
||||||
}
|
}
|
||||||
let layout = self.component_info.layout();
|
let layout = self.component_info.layout();
|
||||||
let target_component_data_ptr = self.bundle_scratch_allocator.alloc_layout(layout);
|
let target_ptr = self.bundle_scratch_allocator.alloc_layout(layout);
|
||||||
|
core::ptr::copy_nonoverlapping(ptr.as_ptr(), target_ptr.as_ptr(), layout.size());
|
||||||
if clone_fn(self.source_component_ptr, target_component_data_ptr) {
|
self.bundle_scratch
|
||||||
self.bundle_scratch
|
.push_ptr(self.component_id, PtrMut::new(target_ptr));
|
||||||
.push_ptr(self.component_id, PtrMut::new(target_component_data_ptr));
|
self.target_component_written = true;
|
||||||
self.target_component_written = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes component data to target entity.
|
/// Writes component data to target entity.
|
||||||
@ -259,11 +262,6 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
self.target_component_written = true;
|
self.target_component_written = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns instance of [`Components`].
|
|
||||||
pub fn components(&self) -> &Components {
|
|
||||||
self.components
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns [`AppTypeRegistry`](`crate::reflect::AppTypeRegistry`) if it exists in the world.
|
/// Returns [`AppTypeRegistry`](`crate::reflect::AppTypeRegistry`) if it exists in the world.
|
||||||
///
|
///
|
||||||
/// NOTE: Prefer this method instead of manually reading the resource from the world.
|
/// NOTE: Prefer this method instead of manually reading the resource from the world.
|
||||||
@ -273,11 +271,10 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Queues the `entity` to be cloned by the current [`EntityCloner`]
|
/// Queues the `entity` to be cloned by the current [`EntityCloner`]
|
||||||
pub fn queue_entity_clone(&self, entity: Entity) {
|
pub fn queue_entity_clone(&mut self, entity: Entity) {
|
||||||
self.entity_cloner
|
let target = self.entities.reserve_entity();
|
||||||
.clone_queue
|
self.mapper.set_mapped(entity, target);
|
||||||
.borrow_mut()
|
self.entity_cloner.clone_queue.push_back(entity);
|
||||||
.push_back(entity);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -346,9 +343,9 @@ pub struct EntityCloner {
|
|||||||
filter: HashSet<ComponentId>,
|
filter: HashSet<ComponentId>,
|
||||||
clone_behavior_overrides: HashMap<ComponentId, ComponentCloneBehavior>,
|
clone_behavior_overrides: HashMap<ComponentId, ComponentCloneBehavior>,
|
||||||
move_components: bool,
|
move_components: bool,
|
||||||
is_recursive: bool,
|
linked_cloning: bool,
|
||||||
default_clone_fn: ComponentCloneFn,
|
default_clone_fn: ComponentCloneFn,
|
||||||
clone_queue: RefCell<VecDeque<Entity>>,
|
clone_queue: VecDeque<Entity>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for EntityCloner {
|
impl Default for EntityCloner {
|
||||||
@ -358,7 +355,7 @@ impl Default for EntityCloner {
|
|||||||
filter: Default::default(),
|
filter: Default::default(),
|
||||||
clone_behavior_overrides: Default::default(),
|
clone_behavior_overrides: Default::default(),
|
||||||
move_components: false,
|
move_components: false,
|
||||||
is_recursive: false,
|
linked_cloning: false,
|
||||||
default_clone_fn: ComponentCloneBehavior::global_default_fn(),
|
default_clone_fn: ComponentCloneBehavior::global_default_fn(),
|
||||||
clone_queue: Default::default(),
|
clone_queue: Default::default(),
|
||||||
}
|
}
|
||||||
@ -410,14 +407,20 @@ impl<'a> BundleScratch<'a> {
|
|||||||
///
|
///
|
||||||
/// # Safety
|
/// # Safety
|
||||||
/// All [`ComponentId`] values in this instance must come from `world`.
|
/// All [`ComponentId`] values in this instance must come from `world`.
|
||||||
pub(crate) unsafe fn write(self, world: &mut World, entity: Entity) {
|
pub(crate) unsafe fn write(
|
||||||
|
self,
|
||||||
|
world: &mut World,
|
||||||
|
entity: Entity,
|
||||||
|
relationship_hook_insert_mode: RelationshipInsertHookMode,
|
||||||
|
) {
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - All `component_ids` are from the same world as `target` entity
|
// - All `component_ids` are from the same world as `target` entity
|
||||||
// - All `component_data_ptrs` are valid types represented by `component_ids`
|
// - All `component_data_ptrs` are valid types represented by `component_ids`
|
||||||
unsafe {
|
unsafe {
|
||||||
world.entity_mut(entity).insert_by_ids(
|
world.entity_mut(entity).insert_by_ids_internal(
|
||||||
&self.component_ids,
|
&self.component_ids,
|
||||||
self.component_ptrs.into_iter().map(|ptr| ptr.promote()),
|
self.component_ptrs.into_iter().map(|ptr| ptr.promote()),
|
||||||
|
relationship_hook_insert_mode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -433,10 +436,11 @@ impl EntityCloner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns `true` if this cloner is configured to clone entities recursively.
|
/// Returns `true` if this cloner is configured to clone entities referenced in cloned components via [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN).
|
||||||
|
/// This will produce "deep" / recursive clones of relationship trees that have "linked spawn".
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_recursive(&self) -> bool {
|
pub fn linked_cloning(&self) -> bool {
|
||||||
self.is_recursive
|
self.linked_cloning
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clones and inserts components from the `source` entity into the entity mapped by `mapper` from `source` using the stored configuration.
|
/// Clones and inserts components from the `source` entity into the entity mapped by `mapper` from `source` using the stored configuration.
|
||||||
@ -445,6 +449,7 @@ impl EntityCloner {
|
|||||||
world: &mut World,
|
world: &mut World,
|
||||||
source: Entity,
|
source: Entity,
|
||||||
mapper: &mut dyn EntityMapper,
|
mapper: &mut dyn EntityMapper,
|
||||||
|
relationship_hook_insert_mode: RelationshipInsertHookMode,
|
||||||
) -> Entity {
|
) -> Entity {
|
||||||
let target = mapper.get_mapped(source);
|
let target = mapper.get_mapped(source);
|
||||||
// PERF: reusing allocated space across clones would be more efficient. Consider an allocation model similar to `Commands`.
|
// PERF: reusing allocated space across clones would be more efficient. Consider an allocation model similar to `Commands`.
|
||||||
@ -486,12 +491,20 @@ impl EntityCloner {
|
|||||||
.unwrap_or(self.default_clone_fn),
|
.unwrap_or(self.default_clone_fn),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SAFETY: This component exists because it is present on the archetype.
|
||||||
|
let info = unsafe { world.components().get_info_unchecked(component) };
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - There are no other mutable references to source entity.
|
// - There are no other mutable references to source entity.
|
||||||
// - `component` is from `source_entity`'s archetype
|
// - `component` is from `source_entity`'s archetype
|
||||||
let source_component_ptr =
|
let source_component_ptr =
|
||||||
unsafe { source_entity.get_by_id(component).debug_checked_unwrap() };
|
unsafe { source_entity.get_by_id(component).debug_checked_unwrap() };
|
||||||
|
|
||||||
|
let source_component = SourceComponent {
|
||||||
|
info,
|
||||||
|
ptr: source_component_ptr,
|
||||||
|
};
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - `components` and `component` are from the same world
|
// - `components` and `component` are from the same world
|
||||||
// - `source_component_ptr` is valid and points to the same type as represented by `component`
|
// - `source_component_ptr` is valid and points to the same type as represented by `component`
|
||||||
@ -500,17 +513,17 @@ impl EntityCloner {
|
|||||||
component,
|
component,
|
||||||
source,
|
source,
|
||||||
target,
|
target,
|
||||||
source_component_ptr,
|
|
||||||
&bundle_scratch_allocator,
|
&bundle_scratch_allocator,
|
||||||
&mut bundle_scratch,
|
&mut bundle_scratch,
|
||||||
world.components(),
|
world.entities(),
|
||||||
|
info,
|
||||||
self,
|
self,
|
||||||
mapper,
|
mapper,
|
||||||
app_registry.as_ref(),
|
app_registry.as_ref(),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
(handler)(&mut commands, &mut ctx);
|
(handler)(&mut commands, &source_component, &mut ctx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -529,12 +542,12 @@ impl EntityCloner {
|
|||||||
// SAFETY:
|
// SAFETY:
|
||||||
// - All `component_ids` are from the same world as `target` entity
|
// - All `component_ids` are from the same world as `target` entity
|
||||||
// - All `component_data_ptrs` are valid types represented by `component_ids`
|
// - All `component_data_ptrs` are valid types represented by `component_ids`
|
||||||
unsafe { bundle_scratch.write(world, target) };
|
unsafe { bundle_scratch.write(world, target, relationship_hook_insert_mode) };
|
||||||
target
|
target
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clones and inserts components from the `source` entity into `target` entity using the stored configuration.
|
/// Clones and inserts components from the `source` entity into `target` entity using the stored configuration.
|
||||||
/// If this [`EntityCloner`] has [`EntityCloner::is_recursive`], then it will recursively spawn entities as defined
|
/// If this [`EntityCloner`] has [`EntityCloner::linked_cloning`], then it will recursively spawn entities as defined
|
||||||
/// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with
|
/// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with
|
||||||
/// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN)
|
/// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN)
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
@ -545,7 +558,7 @@ impl EntityCloner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Clones and inserts components from the `source` entity into a newly spawned entity using the stored configuration.
|
/// Clones and inserts components from the `source` entity into a newly spawned entity using the stored configuration.
|
||||||
/// If this [`EntityCloner`] has [`EntityCloner::is_recursive`], then it will recursively spawn entities as defined
|
/// If this [`EntityCloner`] has [`EntityCloner::linked_cloning`], then it will recursively spawn entities as defined
|
||||||
/// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with
|
/// by [`RelationshipTarget`](crate::relationship::RelationshipTarget) components with
|
||||||
/// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN)
|
/// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN)
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
@ -563,13 +576,22 @@ impl EntityCloner {
|
|||||||
source: Entity,
|
source: Entity,
|
||||||
mapper: &mut dyn EntityMapper,
|
mapper: &mut dyn EntityMapper,
|
||||||
) -> Entity {
|
) -> Entity {
|
||||||
let target = self.clone_entity_internal(world, source, mapper);
|
// All relationships on the root should have their hooks run
|
||||||
|
let target =
|
||||||
|
self.clone_entity_internal(world, source, mapper, RelationshipInsertHookMode::Run);
|
||||||
|
let child_hook_insert_mode = if self.linked_cloning {
|
||||||
|
// When spawning "linked relationships", we want to ignore hooks for relationships we are spawning, while
|
||||||
|
// still registering with original relationship targets that are "not linked" to the current recursive spawn.
|
||||||
|
RelationshipInsertHookMode::RunIfNotLinked
|
||||||
|
} else {
|
||||||
|
// If we are not cloning "linked relationships" recursively, then we want any cloned relationship components to
|
||||||
|
// register themselves with their original relationship target.
|
||||||
|
RelationshipInsertHookMode::Run
|
||||||
|
};
|
||||||
loop {
|
loop {
|
||||||
let queued = self.clone_queue.borrow_mut().pop_front();
|
let queued = self.clone_queue.pop_front();
|
||||||
if let Some(queued) = queued {
|
if let Some(queued) = queued {
|
||||||
let target = world.entities.reserve_entity();
|
self.clone_entity_internal(world, queued, mapper, child_hook_insert_mode);
|
||||||
mapper.set_mapped(queued, target);
|
|
||||||
self.clone_entity_internal(world, queued, mapper);
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -764,10 +786,10 @@ impl<'w> EntityClonerBuilder<'w> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If `true`, makes the built [`EntityCloner`] recursively clone entities, as defined by
|
/// When true this cloner will be configured to clone entities referenced in cloned components via [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN).
|
||||||
/// [`RelationshipTarget::LINKED_SPAWN`](crate::relationship::RelationshipTarget::LINKED_SPAWN).
|
/// This will produce "deep" / recursive clones of relationship trees that have "linked spawn".
|
||||||
pub fn recursive(&mut self, is_recursive: bool) -> &mut Self {
|
pub fn linked_cloning(&mut self, linked_cloning: bool) -> &mut Self {
|
||||||
self.entity_cloner.is_recursive = is_recursive;
|
self.entity_cloner.linked_cloning = linked_cloning;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -817,10 +839,9 @@ mod tests {
|
|||||||
use super::ComponentCloneCtx;
|
use super::ComponentCloneCtx;
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType},
|
component::{Component, ComponentCloneBehavior, ComponentDescriptor, StorageType},
|
||||||
entity::{hash_map::EntityHashMap, Entity, EntityCloner},
|
entity::{hash_map::EntityHashMap, Entity, EntityCloner, SourceComponent},
|
||||||
prelude::{ChildOf, Children, Resource},
|
prelude::{ChildOf, Children, Resource},
|
||||||
reflect::AppTypeRegistry,
|
reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld},
|
||||||
reflect::{ReflectComponent, ReflectFromWorld},
|
|
||||||
system::Commands,
|
system::Commands,
|
||||||
world::{FromWorld, World},
|
world::{FromWorld, World},
|
||||||
};
|
};
|
||||||
@ -835,7 +856,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{Component, ComponentCloneBehavior},
|
component::{Component, ComponentCloneBehavior},
|
||||||
entity::EntityCloner,
|
entity::{EntityCloner, SourceComponent},
|
||||||
reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld},
|
reflect::{AppTypeRegistry, ReflectComponent, ReflectFromWorld},
|
||||||
system::Commands,
|
system::Commands,
|
||||||
};
|
};
|
||||||
@ -939,8 +960,13 @@ mod tests {
|
|||||||
#[derive(Component, Reflect)]
|
#[derive(Component, Reflect)]
|
||||||
struct B;
|
struct B;
|
||||||
|
|
||||||
fn test_handler(_commands: &mut Commands, ctx: &mut ComponentCloneCtx) {
|
fn test_handler(
|
||||||
assert!(ctx.read_source_component_reflect().is_none());
|
_commands: &mut Commands,
|
||||||
|
source: &SourceComponent,
|
||||||
|
ctx: &mut ComponentCloneCtx,
|
||||||
|
) {
|
||||||
|
let registry = ctx.type_registry().unwrap();
|
||||||
|
assert!(source.read_reflect(®istry.read()).is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
@ -1230,17 +1256,14 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn clone_entity_with_dynamic_components() {
|
fn clone_entity_with_dynamic_components() {
|
||||||
const COMPONENT_SIZE: usize = 10;
|
const COMPONENT_SIZE: usize = 10;
|
||||||
fn test_handler(_commands: &mut Commands, ctx: &mut ComponentCloneCtx) {
|
fn test_handler(
|
||||||
// SAFETY: this handler is only going to be used with a component represented by [u8; COMPONENT_SIZE]
|
_commands: &mut Commands,
|
||||||
|
source: &SourceComponent,
|
||||||
|
ctx: &mut ComponentCloneCtx,
|
||||||
|
) {
|
||||||
|
// SAFETY: the passed in ptr corresponds to copy-able data that matches the type of the source / target component
|
||||||
unsafe {
|
unsafe {
|
||||||
ctx.write_target_component_ptr(move |source_ptr, target_ptr| {
|
ctx.write_target_component_ptr(source.ptr());
|
||||||
core::ptr::copy_nonoverlapping(
|
|
||||||
source_ptr.as_ptr(),
|
|
||||||
target_ptr.as_ptr(),
|
|
||||||
COMPONENT_SIZE,
|
|
||||||
);
|
|
||||||
true
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1297,7 +1320,7 @@ mod tests {
|
|||||||
|
|
||||||
let clone_root = world.spawn_empty().id();
|
let clone_root = world.spawn_empty().id();
|
||||||
EntityCloner::build(&mut world)
|
EntityCloner::build(&mut world)
|
||||||
.recursive(true)
|
.linked_cloning(true)
|
||||||
.clone_entity(root, clone_root);
|
.clone_entity(root, clone_root);
|
||||||
|
|
||||||
let root_children = world
|
let root_children = world
|
||||||
|
@ -2,7 +2,7 @@ use crate::{
|
|||||||
component::{
|
component::{
|
||||||
Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType,
|
Component, ComponentCloneBehavior, ComponentHook, HookContext, Mutable, StorageType,
|
||||||
},
|
},
|
||||||
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder},
|
entity::{ComponentCloneCtx, Entity, EntityClonerBuilder, SourceComponent},
|
||||||
observer::ObserverState,
|
observer::ObserverState,
|
||||||
system::Commands,
|
system::Commands,
|
||||||
world::World,
|
world::World,
|
||||||
@ -64,7 +64,11 @@ impl EntityClonerBuilder<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component_clone_observed_by(commands: &mut Commands, ctx: &mut ComponentCloneCtx) {
|
fn component_clone_observed_by(
|
||||||
|
commands: &mut Commands,
|
||||||
|
_source: &SourceComponent,
|
||||||
|
ctx: &mut ComponentCloneCtx,
|
||||||
|
) {
|
||||||
let target = ctx.target();
|
let target = ctx.target();
|
||||||
let source = ctx.source();
|
let source = ctx.source();
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ use crate::{
|
|||||||
bundle::BundleFromComponents,
|
bundle::BundleFromComponents,
|
||||||
entity::EntityMapper,
|
entity::EntityMapper,
|
||||||
prelude::Bundle,
|
prelude::Bundle,
|
||||||
|
relationship::RelationshipInsertHookMode,
|
||||||
world::{EntityMut, EntityWorldMut},
|
world::{EntityMut, EntityWorldMut},
|
||||||
};
|
};
|
||||||
use bevy_reflect::{
|
use bevy_reflect::{
|
||||||
@ -36,8 +37,13 @@ pub struct ReflectBundleFns {
|
|||||||
/// Function pointer implementing [`ReflectBundle::apply`].
|
/// Function pointer implementing [`ReflectBundle::apply`].
|
||||||
pub apply: fn(EntityMut, &dyn PartialReflect, &TypeRegistry),
|
pub apply: fn(EntityMut, &dyn PartialReflect, &TypeRegistry),
|
||||||
/// Function pointer implementing [`ReflectBundle::apply_or_insert_mapped`].
|
/// Function pointer implementing [`ReflectBundle::apply_or_insert_mapped`].
|
||||||
pub apply_or_insert_mapped:
|
pub apply_or_insert_mapped: fn(
|
||||||
fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry, &mut dyn EntityMapper),
|
&mut EntityWorldMut,
|
||||||
|
&dyn PartialReflect,
|
||||||
|
&TypeRegistry,
|
||||||
|
&mut dyn EntityMapper,
|
||||||
|
RelationshipInsertHookMode,
|
||||||
|
),
|
||||||
/// Function pointer implementing [`ReflectBundle::remove`].
|
/// Function pointer implementing [`ReflectBundle::remove`].
|
||||||
pub remove: fn(&mut EntityWorldMut),
|
pub remove: fn(&mut EntityWorldMut),
|
||||||
/// Function pointer implementing [`ReflectBundle::take`].
|
/// Function pointer implementing [`ReflectBundle::take`].
|
||||||
@ -87,8 +93,15 @@ impl ReflectBundle {
|
|||||||
bundle: &dyn PartialReflect,
|
bundle: &dyn PartialReflect,
|
||||||
registry: &TypeRegistry,
|
registry: &TypeRegistry,
|
||||||
mapper: &mut dyn EntityMapper,
|
mapper: &mut dyn EntityMapper,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode,
|
||||||
) {
|
) {
|
||||||
(self.0.apply_or_insert_mapped)(entity, bundle, registry, mapper);
|
(self.0.apply_or_insert_mapped)(
|
||||||
|
entity,
|
||||||
|
bundle,
|
||||||
|
registry,
|
||||||
|
mapper,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes this [`Bundle`] type from the entity. Does nothing if it doesn't exist.
|
/// Removes this [`Bundle`] type from the entity. Does nothing if it doesn't exist.
|
||||||
@ -170,32 +183,46 @@ impl<B: Bundle + Reflect + TypePath + BundleFromComponents> FromType<B> for Refl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
apply_or_insert_mapped: |entity, reflected_bundle, registry, mapper| {
|
apply_or_insert_mapped:
|
||||||
if let Some(reflect_component) =
|
|entity, reflected_bundle, registry, mapper, relationship_insert_hook_mode| {
|
||||||
registry.get_type_data::<ReflectComponent>(TypeId::of::<B>())
|
if let Some(reflect_component) =
|
||||||
{
|
registry.get_type_data::<ReflectComponent>(TypeId::of::<B>())
|
||||||
reflect_component.apply_or_insert_mapped(
|
{
|
||||||
entity,
|
reflect_component.apply_or_insert_mapped(
|
||||||
reflected_bundle,
|
entity,
|
||||||
registry,
|
reflected_bundle,
|
||||||
mapper,
|
registry,
|
||||||
);
|
mapper,
|
||||||
} else {
|
relationship_insert_hook_mode,
|
||||||
match reflected_bundle.reflect_ref() {
|
);
|
||||||
ReflectRef::Struct(bundle) => bundle.iter_fields().for_each(|field| {
|
} else {
|
||||||
apply_or_insert_field_mapped(entity, field, registry, mapper);
|
match reflected_bundle.reflect_ref() {
|
||||||
}),
|
ReflectRef::Struct(bundle) => bundle.iter_fields().for_each(|field| {
|
||||||
ReflectRef::Tuple(bundle) => bundle.iter_fields().for_each(|field| {
|
apply_or_insert_field_mapped(
|
||||||
apply_or_insert_field_mapped(entity, field, registry, mapper);
|
entity,
|
||||||
}),
|
field,
|
||||||
_ => panic!(
|
registry,
|
||||||
"expected bundle `{}` to be a named struct or tuple",
|
mapper,
|
||||||
// FIXME: once we have unique reflect, use `TypePath`.
|
relationship_insert_hook_mode,
|
||||||
core::any::type_name::<B>(),
|
);
|
||||||
),
|
}),
|
||||||
|
ReflectRef::Tuple(bundle) => bundle.iter_fields().for_each(|field| {
|
||||||
|
apply_or_insert_field_mapped(
|
||||||
|
entity,
|
||||||
|
field,
|
||||||
|
registry,
|
||||||
|
mapper,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
_ => panic!(
|
||||||
|
"expected bundle `{}` to be a named struct or tuple",
|
||||||
|
// FIXME: once we have unique reflect, use `TypePath`.
|
||||||
|
core::any::type_name::<B>(),
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
|
||||||
remove: |entity| {
|
remove: |entity| {
|
||||||
entity.remove::<B>();
|
entity.remove::<B>();
|
||||||
},
|
},
|
||||||
@ -232,6 +259,7 @@ fn apply_or_insert_field_mapped(
|
|||||||
field: &dyn PartialReflect,
|
field: &dyn PartialReflect,
|
||||||
registry: &TypeRegistry,
|
registry: &TypeRegistry,
|
||||||
mapper: &mut dyn EntityMapper,
|
mapper: &mut dyn EntityMapper,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode,
|
||||||
) {
|
) {
|
||||||
let Some(type_id) = field.try_as_reflect().map(Any::type_id) else {
|
let Some(type_id) = field.try_as_reflect().map(Any::type_id) else {
|
||||||
panic!(
|
panic!(
|
||||||
@ -241,9 +269,21 @@ fn apply_or_insert_field_mapped(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(reflect_component) = registry.get_type_data::<ReflectComponent>(type_id) {
|
if let Some(reflect_component) = registry.get_type_data::<ReflectComponent>(type_id) {
|
||||||
reflect_component.apply_or_insert_mapped(entity, field, registry, mapper);
|
reflect_component.apply_or_insert_mapped(
|
||||||
|
entity,
|
||||||
|
field,
|
||||||
|
registry,
|
||||||
|
mapper,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
);
|
||||||
} else if let Some(reflect_bundle) = registry.get_type_data::<ReflectBundle>(type_id) {
|
} else if let Some(reflect_bundle) = registry.get_type_data::<ReflectBundle>(type_id) {
|
||||||
reflect_bundle.apply_or_insert_mapped(entity, field, registry, mapper);
|
reflect_bundle.apply_or_insert_mapped(
|
||||||
|
entity,
|
||||||
|
field,
|
||||||
|
registry,
|
||||||
|
mapper,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let is_component = entity.world().components().get_id(type_id).is_some();
|
let is_component = entity.world().components().get_id(type_id).is_some();
|
||||||
|
|
||||||
|
@ -63,6 +63,7 @@ use crate::{
|
|||||||
component::{ComponentId, ComponentMutability},
|
component::{ComponentId, ComponentMutability},
|
||||||
entity::{Entity, EntityMapper},
|
entity::{Entity, EntityMapper},
|
||||||
prelude::Component,
|
prelude::Component,
|
||||||
|
relationship::RelationshipInsertHookMode,
|
||||||
world::{
|
world::{
|
||||||
unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityWorldMut, FilteredEntityMut,
|
unsafe_world_cell::UnsafeEntityCell, EntityMut, EntityWorldMut, FilteredEntityMut,
|
||||||
FilteredEntityRef, World,
|
FilteredEntityRef, World,
|
||||||
@ -105,8 +106,13 @@ pub struct ReflectComponentFns {
|
|||||||
/// Function pointer implementing [`ReflectComponent::apply()`].
|
/// Function pointer implementing [`ReflectComponent::apply()`].
|
||||||
pub apply: fn(EntityMut, &dyn PartialReflect),
|
pub apply: fn(EntityMut, &dyn PartialReflect),
|
||||||
/// Function pointer implementing [`ReflectComponent::apply_or_insert_mapped()`].
|
/// Function pointer implementing [`ReflectComponent::apply_or_insert_mapped()`].
|
||||||
pub apply_or_insert_mapped:
|
pub apply_or_insert_mapped: fn(
|
||||||
fn(&mut EntityWorldMut, &dyn PartialReflect, &TypeRegistry, &mut dyn EntityMapper),
|
&mut EntityWorldMut,
|
||||||
|
&dyn PartialReflect,
|
||||||
|
&TypeRegistry,
|
||||||
|
&mut dyn EntityMapper,
|
||||||
|
RelationshipInsertHookMode,
|
||||||
|
),
|
||||||
/// Function pointer implementing [`ReflectComponent::remove()`].
|
/// Function pointer implementing [`ReflectComponent::remove()`].
|
||||||
pub remove: fn(&mut EntityWorldMut),
|
pub remove: fn(&mut EntityWorldMut),
|
||||||
/// Function pointer implementing [`ReflectComponent::contains()`].
|
/// Function pointer implementing [`ReflectComponent::contains()`].
|
||||||
@ -174,8 +180,15 @@ impl ReflectComponent {
|
|||||||
component: &dyn PartialReflect,
|
component: &dyn PartialReflect,
|
||||||
registry: &TypeRegistry,
|
registry: &TypeRegistry,
|
||||||
map: &mut dyn EntityMapper,
|
map: &mut dyn EntityMapper,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode,
|
||||||
) {
|
) {
|
||||||
(self.0.apply_or_insert_mapped)(entity, component, registry, map);
|
(self.0.apply_or_insert_mapped)(
|
||||||
|
entity,
|
||||||
|
component,
|
||||||
|
registry,
|
||||||
|
map,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes this [`Component`] type from the entity. Does nothing if it doesn't exist.
|
/// Removes this [`Component`] type from the entity. Does nothing if it doesn't exist.
|
||||||
@ -320,31 +333,40 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
|
|||||||
let mut component = unsafe { entity.get_mut_assume_mutable::<C>() }.unwrap();
|
let mut component = unsafe { entity.get_mut_assume_mutable::<C>() }.unwrap();
|
||||||
component.apply(reflected_component);
|
component.apply(reflected_component);
|
||||||
},
|
},
|
||||||
apply_or_insert_mapped: |entity, reflected_component, registry, mapper| {
|
apply_or_insert_mapped:
|
||||||
// TODO: if we can externalize this impl to cut down on monomorphization that would be great
|
|entity, reflected_component, registry, mapper, relationship_insert_hook_mode| {
|
||||||
let map_fn = move |entity: &mut Entity| {
|
let map_fn = map_function(mapper);
|
||||||
*entity = mapper.get_mapped(*entity);
|
if C::Mutability::MUTABLE {
|
||||||
};
|
// SAFETY: guard ensures `C` is a mutable component
|
||||||
if C::Mutability::MUTABLE {
|
if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::<C>() }
|
||||||
// SAFETY: guard ensures `C` is a mutable component
|
{
|
||||||
if let Some(mut component) = unsafe { entity.get_mut_assume_mutable::<C>() } {
|
component.apply(reflected_component.as_partial_reflect());
|
||||||
component.apply(reflected_component.as_partial_reflect());
|
C::visit_entities_mut(&mut component, map_fn);
|
||||||
C::visit_entities_mut(&mut component, map_fn);
|
} else {
|
||||||
|
let mut component = entity.world_scope(|world| {
|
||||||
|
from_reflect_with_fallback::<C>(
|
||||||
|
reflected_component,
|
||||||
|
world,
|
||||||
|
registry,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
C::visit_entities_mut(&mut component, map_fn);
|
||||||
|
entity.insert_with_relationship_insert_hook_mode(
|
||||||
|
component,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut component = entity.world_scope(|world| {
|
let mut component = entity.world_scope(|world| {
|
||||||
from_reflect_with_fallback::<C>(reflected_component, world, registry)
|
from_reflect_with_fallback::<C>(reflected_component, world, registry)
|
||||||
});
|
});
|
||||||
C::visit_entities_mut(&mut component, map_fn);
|
C::visit_entities_mut(&mut component, map_fn);
|
||||||
entity.insert(component);
|
entity.insert_with_relationship_insert_hook_mode(
|
||||||
|
component,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
let mut component = entity.world_scope(|world| {
|
|
||||||
from_reflect_with_fallback::<C>(reflected_component, world, registry)
|
|
||||||
});
|
|
||||||
C::visit_entities_mut(&mut component, map_fn);
|
|
||||||
entity.insert(component);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
remove: |entity| {
|
remove: |entity| {
|
||||||
entity.remove::<C>();
|
entity.remove::<C>();
|
||||||
},
|
},
|
||||||
@ -397,3 +419,9 @@ impl<C: Component + Reflect + TypePath> FromType<C> for ReflectComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn map_function(mapper: &mut dyn EntityMapper) -> impl FnMut(&mut Entity) + '_ {
|
||||||
|
move |entity: &mut Entity| {
|
||||||
|
*entity = mapper.get_mapped(*entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -12,7 +12,7 @@ pub use relationship_source_collection::*;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component::{Component, HookContext, Mutable},
|
component::{Component, HookContext, Mutable},
|
||||||
entity::{ComponentCloneCtx, Entity},
|
entity::{ComponentCloneCtx, Entity, SourceComponent},
|
||||||
system::{
|
system::{
|
||||||
command::HandleError,
|
command::HandleError,
|
||||||
entity_command::{self, CommandWithEntity},
|
entity_command::{self, CommandWithEntity},
|
||||||
@ -85,7 +85,24 @@ pub trait Relationship: Component + Sized {
|
|||||||
fn from(entity: Entity) -> Self;
|
fn from(entity: Entity) -> Self;
|
||||||
|
|
||||||
/// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
/// The `on_insert` component hook that maintains the [`Relationship`] / [`RelationshipTarget`] connection.
|
||||||
fn on_insert(mut world: DeferredWorld, HookContext { entity, caller, .. }: HookContext) {
|
fn on_insert(
|
||||||
|
mut world: DeferredWorld,
|
||||||
|
HookContext {
|
||||||
|
entity,
|
||||||
|
caller,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
..
|
||||||
|
}: HookContext,
|
||||||
|
) {
|
||||||
|
match relationship_insert_hook_mode {
|
||||||
|
RelationshipInsertHookMode::Run => {}
|
||||||
|
RelationshipInsertHookMode::Skip => return,
|
||||||
|
RelationshipInsertHookMode::RunIfNotLinked => {
|
||||||
|
if <Self::RelationshipTarget as RelationshipTarget>::LINKED_SPAWN {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
let target_entity = world.entity(entity).get::<Self>().unwrap().get();
|
let target_entity = world.entity(entity).get::<Self>().unwrap().get();
|
||||||
if target_entity == entity {
|
if target_entity == entity {
|
||||||
warn!(
|
warn!(
|
||||||
@ -155,7 +172,7 @@ pub type SourceIter<'w, R> =
|
|||||||
/// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type.
|
/// A [`Component`] containing the collection of entities that relate to this [`Entity`] via the associated `Relationship` type.
|
||||||
/// See the [`Relationship`] documentation for more information.
|
/// See the [`Relationship`] documentation for more information.
|
||||||
pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
|
pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
|
||||||
/// If this is true, when despawning or cloning (when [recursion is enabled](crate::entity::EntityClonerBuilder::recursive)), the related entities targeting this entity will also be despawned or cloned.
|
/// If this is true, when despawning or cloning (when [linked cloning is enabled](crate::entity::EntityClonerBuilder::linked_cloning)), the related entities targeting this entity will also be despawned or cloned.
|
||||||
///
|
///
|
||||||
/// For example, this is set to `true` for Bevy's built-in parent-child relation, defined by [`ChildOf`](crate::prelude::ChildOf) and [`Children`](crate::prelude::Children).
|
/// For example, this is set to `true` for Bevy's built-in parent-child relation, defined by [`ChildOf`](crate::prelude::ChildOf) and [`Children`](crate::prelude::Children).
|
||||||
/// This means that when a parent is despawned, any children targeting that parent are also despawned (and the same applies to cloning).
|
/// This means that when a parent is despawned, any children targeting that parent are also despawned (and the same applies to cloning).
|
||||||
@ -284,18 +301,33 @@ pub trait RelationshipTarget: Component<Mutability = Mutable> + Sized {
|
|||||||
/// to spawn recursively.
|
/// to spawn recursively.
|
||||||
pub fn clone_relationship_target<T: RelationshipTarget>(
|
pub fn clone_relationship_target<T: RelationshipTarget>(
|
||||||
_commands: &mut Commands,
|
_commands: &mut Commands,
|
||||||
|
source: &SourceComponent,
|
||||||
context: &mut ComponentCloneCtx,
|
context: &mut ComponentCloneCtx,
|
||||||
) {
|
) {
|
||||||
if let Some(component) = context.read_source_component::<T>() {
|
if let Some(component) = source.read::<T>() {
|
||||||
if context.is_recursive() && T::LINKED_SPAWN {
|
let mut cloned = T::with_capacity(component.len());
|
||||||
|
if context.linked_cloning() && T::LINKED_SPAWN {
|
||||||
|
let collection = cloned.collection_mut_risky();
|
||||||
for entity in component.iter() {
|
for entity in component.iter() {
|
||||||
|
collection.add(entity);
|
||||||
context.queue_entity_clone(entity);
|
context.queue_entity_clone(entity);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
context.write_target_component(T::with_capacity(component.len()));
|
context.write_target_component(cloned);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Configures the conditions under which the Relationship insert hook will be run.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub enum RelationshipInsertHookMode {
|
||||||
|
/// Relationship insert hooks will always run
|
||||||
|
Run,
|
||||||
|
/// Relationship insert hooks will run if [`RelationshipTarget::LINKED_SPAWN`] is false
|
||||||
|
RunIfNotLinked,
|
||||||
|
/// Relationship insert hooks will always be skipped
|
||||||
|
Skip,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::world::World;
|
use crate::world::World;
|
||||||
|
@ -172,27 +172,6 @@ mod tests {
|
|||||||
assert_eq!(collection, &alloc::vec!(a));
|
assert_eq!(collection, &alloc::vec!(a));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn entity_hash_set_relationship_source_collection() {
|
|
||||||
#[derive(Component)]
|
|
||||||
#[relationship(relationship_target = RelTarget)]
|
|
||||||
struct Rel(Entity);
|
|
||||||
|
|
||||||
#[derive(Component)]
|
|
||||||
#[relationship_target(relationship = Rel, linked_spawn)]
|
|
||||||
struct RelTarget(EntityHashSet);
|
|
||||||
|
|
||||||
let mut world = World::new();
|
|
||||||
let a = world.spawn_empty().id();
|
|
||||||
let b = world.spawn_empty().id();
|
|
||||||
|
|
||||||
world.entity_mut(a).insert(Rel(b));
|
|
||||||
|
|
||||||
let rel_target = world.get::<RelTarget>(b).unwrap();
|
|
||||||
let collection = rel_target.collection();
|
|
||||||
assert_eq!(collection, &EntityHashSet::from([a]));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn smallvec_relationship_source_collection() {
|
fn smallvec_relationship_source_collection() {
|
||||||
#[derive(Component)]
|
#[derive(Component)]
|
||||||
|
@ -14,6 +14,7 @@ use crate::{
|
|||||||
component::{Component, ComponentId, ComponentInfo},
|
component::{Component, ComponentId, ComponentInfo},
|
||||||
entity::{Entity, EntityClonerBuilder},
|
entity::{Entity, EntityClonerBuilder},
|
||||||
event::Event,
|
event::Event,
|
||||||
|
relationship::RelationshipInsertHookMode,
|
||||||
result::Result,
|
result::Result,
|
||||||
system::{command::HandleError, Command, IntoObserverSystem},
|
system::{command::HandleError, Command, IntoObserverSystem},
|
||||||
world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld, World},
|
world::{error::EntityMutableFetchError, EntityWorldMut, FromWorld, World},
|
||||||
@ -156,7 +157,7 @@ where
|
|||||||
pub fn insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand {
|
pub fn insert(bundle: impl Bundle, mode: InsertMode) -> impl EntityCommand {
|
||||||
let caller = MaybeLocation::caller();
|
let caller = MaybeLocation::caller();
|
||||||
move |mut entity: EntityWorldMut| {
|
move |mut entity: EntityWorldMut| {
|
||||||
entity.insert_with_caller(bundle, mode, caller);
|
entity.insert_with_caller(bundle, mode, caller, RelationshipInsertHookMode::Run);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,7 +179,13 @@ pub unsafe fn insert_by_id<T: Send + 'static>(
|
|||||||
// - `component_id` safety is ensured by the caller
|
// - `component_id` safety is ensured by the caller
|
||||||
// - `ptr` is valid within the `make` block
|
// - `ptr` is valid within the `make` block
|
||||||
OwningPtr::make(value, |ptr| unsafe {
|
OwningPtr::make(value, |ptr| unsafe {
|
||||||
entity.insert_by_id_with_caller(component_id, ptr, mode, caller);
|
entity.insert_by_id_with_caller(
|
||||||
|
component_id,
|
||||||
|
ptr,
|
||||||
|
mode,
|
||||||
|
caller,
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,7 +197,7 @@ pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl Ent
|
|||||||
let caller = MaybeLocation::caller();
|
let caller = MaybeLocation::caller();
|
||||||
move |mut entity: EntityWorldMut| {
|
move |mut entity: EntityWorldMut| {
|
||||||
let value = entity.world_scope(|world| T::from_world(world));
|
let value = entity.world_scope(|world| T::from_world(world));
|
||||||
entity.insert_with_caller(value, mode, caller);
|
entity.insert_with_caller(value, mode, caller, RelationshipInsertHookMode::Run);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ use crate::{
|
|||||||
observer::{Observers, TriggerTargets},
|
observer::{Observers, TriggerTargets},
|
||||||
prelude::{Component, QueryState},
|
prelude::{Component, QueryState},
|
||||||
query::{QueryData, QueryFilter},
|
query::{QueryData, QueryFilter},
|
||||||
|
relationship::RelationshipInsertHookMode,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
system::{Commands, Query},
|
system::{Commands, Query},
|
||||||
traversal::Traversal,
|
traversal::Traversal,
|
||||||
@ -159,6 +160,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
entity,
|
entity,
|
||||||
[component_id].into_iter(),
|
[component_id].into_iter(),
|
||||||
MaybeLocation::caller(),
|
MaybeLocation::caller(),
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
);
|
);
|
||||||
if archetype.has_insert_observer() {
|
if archetype.has_insert_observer() {
|
||||||
self.trigger_observers(
|
self.trigger_observers(
|
||||||
@ -515,6 +517,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
entity,
|
entity,
|
||||||
component_id,
|
component_id,
|
||||||
caller,
|
caller,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode::Run,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -533,6 +536,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
entity: Entity,
|
entity: Entity,
|
||||||
targets: impl Iterator<Item = ComponentId>,
|
targets: impl Iterator<Item = ComponentId>,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode,
|
||||||
) {
|
) {
|
||||||
if archetype.has_insert_hook() {
|
if archetype.has_insert_hook() {
|
||||||
for component_id in targets {
|
for component_id in targets {
|
||||||
@ -545,6 +549,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
entity,
|
entity,
|
||||||
component_id,
|
component_id,
|
||||||
caller,
|
caller,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -575,6 +580,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
entity,
|
entity,
|
||||||
component_id,
|
component_id,
|
||||||
caller,
|
caller,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode::Run,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -605,6 +611,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
entity,
|
entity,
|
||||||
component_id,
|
component_id,
|
||||||
caller,
|
caller,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode::Run,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -635,6 +642,7 @@ impl<'w> DeferredWorld<'w> {
|
|||||||
entity,
|
entity,
|
||||||
component_id,
|
component_id,
|
||||||
caller,
|
caller,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode::Run,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ use crate::{
|
|||||||
event::Event,
|
event::Event,
|
||||||
observer::Observer,
|
observer::Observer,
|
||||||
query::{Access, ReadOnlyQueryData},
|
query::{Access, ReadOnlyQueryData},
|
||||||
|
relationship::RelationshipInsertHookMode,
|
||||||
removal_detection::RemovedComponentEvents,
|
removal_detection::RemovedComponentEvents,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
storage::Storages,
|
storage::Storages,
|
||||||
@ -1526,7 +1527,40 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn insert<T: Bundle>(&mut self, bundle: T) -> &mut Self {
|
pub fn insert<T: Bundle>(&mut self, bundle: T) -> &mut Self {
|
||||||
self.insert_with_caller(bundle, InsertMode::Replace, MaybeLocation::caller())
|
self.insert_with_caller(
|
||||||
|
bundle,
|
||||||
|
InsertMode::Replace,
|
||||||
|
MaybeLocation::caller(),
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Adds a [`Bundle`] of components to the entity.
|
||||||
|
/// [`Relationship`](crate::relationship::Relationship) components in the bundle will follow the configuration
|
||||||
|
/// in `relationship_insert_hook_mode`.
|
||||||
|
///
|
||||||
|
/// This will overwrite any previous value(s) of the same component type.
|
||||||
|
///
|
||||||
|
/// # Warning
|
||||||
|
///
|
||||||
|
/// This can easily break the integrity of relationships. This is intended to be used for cloning and spawning code internals,
|
||||||
|
/// not most user-facing scenarios.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||||
|
#[track_caller]
|
||||||
|
pub fn insert_with_relationship_insert_hook_mode<T: Bundle>(
|
||||||
|
&mut self,
|
||||||
|
bundle: T,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.insert_with_caller(
|
||||||
|
bundle,
|
||||||
|
InsertMode::Replace,
|
||||||
|
MaybeLocation::caller(),
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a [`Bundle`] of components to the entity without overwriting.
|
/// Adds a [`Bundle`] of components to the entity without overwriting.
|
||||||
@ -1539,7 +1573,12 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
/// If the entity has been despawned while this `EntityWorldMut` is still alive.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn insert_if_new<T: Bundle>(&mut self, bundle: T) -> &mut Self {
|
pub fn insert_if_new<T: Bundle>(&mut self, bundle: T) -> &mut Self {
|
||||||
self.insert_with_caller(bundle, InsertMode::Keep, MaybeLocation::caller())
|
self.insert_with_caller(
|
||||||
|
bundle,
|
||||||
|
InsertMode::Keep,
|
||||||
|
MaybeLocation::caller(),
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Split into a new function so we can pass the calling location into the function when using
|
/// Split into a new function so we can pass the calling location into the function when using
|
||||||
@ -1550,14 +1589,23 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
bundle: T,
|
bundle: T,
|
||||||
mode: InsertMode,
|
mode: InsertMode,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
|
relationship_insert_hook_mode: RelationshipInsertHookMode,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.assert_not_despawned();
|
self.assert_not_despawned();
|
||||||
let change_tick = self.world.change_tick();
|
let change_tick = self.world.change_tick();
|
||||||
let mut bundle_inserter =
|
let mut bundle_inserter =
|
||||||
BundleInserter::new::<T>(self.world, self.location.archetype_id, change_tick);
|
BundleInserter::new::<T>(self.world, self.location.archetype_id, change_tick);
|
||||||
// SAFETY: location matches current entity. `T` matches `bundle_info`
|
// SAFETY: location matches current entity. `T` matches `bundle_info`
|
||||||
let (location, after_effect) =
|
let (location, after_effect) = unsafe {
|
||||||
unsafe { bundle_inserter.insert(self.entity, self.location, bundle, mode, caller) };
|
bundle_inserter.insert(
|
||||||
|
self.entity,
|
||||||
|
self.location,
|
||||||
|
bundle,
|
||||||
|
mode,
|
||||||
|
caller,
|
||||||
|
relationship_insert_hook_mode,
|
||||||
|
)
|
||||||
|
};
|
||||||
self.location = location;
|
self.location = location;
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
self.update_location();
|
self.update_location();
|
||||||
@ -1590,6 +1638,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
component,
|
component,
|
||||||
InsertMode::Replace,
|
InsertMode::Replace,
|
||||||
MaybeLocation::caller(),
|
MaybeLocation::caller(),
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1604,6 +1653,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
component: OwningPtr<'_>,
|
component: OwningPtr<'_>,
|
||||||
mode: InsertMode,
|
mode: InsertMode,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
|
relationship_hook_insert_mode: RelationshipInsertHookMode,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.assert_not_despawned();
|
self.assert_not_despawned();
|
||||||
let change_tick = self.world.change_tick();
|
let change_tick = self.world.change_tick();
|
||||||
@ -1629,6 +1679,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
Some(storage_type).iter().cloned(),
|
Some(storage_type).iter().cloned(),
|
||||||
mode,
|
mode,
|
||||||
caller,
|
caller,
|
||||||
|
relationship_hook_insert_mode,
|
||||||
);
|
);
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
self.update_location();
|
self.update_location();
|
||||||
@ -1656,6 +1707,20 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
component_ids: &[ComponentId],
|
component_ids: &[ComponentId],
|
||||||
iter_components: I,
|
iter_components: I,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.insert_by_ids_internal(
|
||||||
|
component_ids,
|
||||||
|
iter_components,
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
pub(crate) unsafe fn insert_by_ids_internal<'a, I: Iterator<Item = OwningPtr<'a>>>(
|
||||||
|
&mut self,
|
||||||
|
component_ids: &[ComponentId],
|
||||||
|
iter_components: I,
|
||||||
|
relationship_hook_insert_mode: RelationshipInsertHookMode,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
self.assert_not_despawned();
|
self.assert_not_despawned();
|
||||||
let change_tick = self.world.change_tick();
|
let change_tick = self.world.change_tick();
|
||||||
@ -1681,6 +1746,7 @@ impl<'w> EntityWorldMut<'w> {
|
|||||||
(*storage_types).iter().cloned(),
|
(*storage_types).iter().cloned(),
|
||||||
InsertMode::Replace,
|
InsertMode::Replace,
|
||||||
MaybeLocation::caller(),
|
MaybeLocation::caller(),
|
||||||
|
relationship_hook_insert_mode,
|
||||||
);
|
);
|
||||||
*self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types);
|
*self.world.bundles.get_storages_unchecked(bundle_id) = core::mem::take(&mut storage_types);
|
||||||
self.world.flush();
|
self.world.flush();
|
||||||
@ -4165,6 +4231,7 @@ unsafe fn insert_dynamic_bundle<
|
|||||||
storage_types: S,
|
storage_types: S,
|
||||||
mode: InsertMode,
|
mode: InsertMode,
|
||||||
caller: MaybeLocation,
|
caller: MaybeLocation,
|
||||||
|
relationship_hook_insert_mode: RelationshipInsertHookMode,
|
||||||
) -> EntityLocation {
|
) -> EntityLocation {
|
||||||
struct DynamicInsertBundle<'a, I: Iterator<Item = (StorageType, OwningPtr<'a>)>> {
|
struct DynamicInsertBundle<'a, I: Iterator<Item = (StorageType, OwningPtr<'a>)>> {
|
||||||
components: I,
|
components: I,
|
||||||
@ -4186,7 +4253,14 @@ unsafe fn insert_dynamic_bundle<
|
|||||||
// SAFETY: location matches current entity.
|
// SAFETY: location matches current entity.
|
||||||
unsafe {
|
unsafe {
|
||||||
bundle_inserter
|
bundle_inserter
|
||||||
.insert(entity, location, bundle, mode, caller)
|
.insert(
|
||||||
|
entity,
|
||||||
|
location,
|
||||||
|
bundle,
|
||||||
|
mode,
|
||||||
|
caller,
|
||||||
|
relationship_hook_insert_mode,
|
||||||
|
)
|
||||||
.0
|
.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,7 @@ use crate::{
|
|||||||
event::{Event, EventId, Events, SendBatchIds},
|
event::{Event, EventId, Events, SendBatchIds},
|
||||||
observer::Observers,
|
observer::Observers,
|
||||||
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
|
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
|
||||||
|
relationship::RelationshipInsertHookMode,
|
||||||
removal_detection::RemovedComponentEvents,
|
removal_detection::RemovedComponentEvents,
|
||||||
resource::Resource,
|
resource::Resource,
|
||||||
schedule::{Schedule, ScheduleLabel, Schedules},
|
schedule::{Schedule, ScheduleLabel, Schedules},
|
||||||
@ -2219,6 +2220,7 @@ impl World {
|
|||||||
bundle,
|
bundle,
|
||||||
InsertMode::Replace,
|
InsertMode::Replace,
|
||||||
caller,
|
caller,
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -2240,6 +2242,7 @@ impl World {
|
|||||||
bundle,
|
bundle,
|
||||||
InsertMode::Replace,
|
InsertMode::Replace,
|
||||||
caller,
|
caller,
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
spawn_or_insert =
|
spawn_or_insert =
|
||||||
@ -2374,6 +2377,7 @@ impl World {
|
|||||||
first_bundle,
|
first_bundle,
|
||||||
insert_mode,
|
insert_mode,
|
||||||
caller,
|
caller,
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2395,9 +2399,14 @@ impl World {
|
|||||||
}
|
}
|
||||||
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||||
unsafe {
|
unsafe {
|
||||||
cache
|
cache.inserter.insert(
|
||||||
.inserter
|
entity,
|
||||||
.insert(entity, location, bundle, insert_mode, caller)
|
location,
|
||||||
|
bundle,
|
||||||
|
insert_mode,
|
||||||
|
caller,
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::<B>(), self.entities.entity_does_not_exist_error_details(entity));
|
panic!("error[B0003]: Could not insert a bundle (of type `{}`) for entity {entity}, which {}. See: https://bevyengine.org/learn/errors/b0003", core::any::type_name::<B>(), self.entities.entity_does_not_exist_error_details(entity));
|
||||||
@ -2515,6 +2524,7 @@ impl World {
|
|||||||
first_bundle,
|
first_bundle,
|
||||||
insert_mode,
|
insert_mode,
|
||||||
caller,
|
caller,
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
break Some(cache);
|
break Some(cache);
|
||||||
@ -2545,9 +2555,14 @@ impl World {
|
|||||||
}
|
}
|
||||||
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
// SAFETY: `entity` is valid, `location` matches entity, bundle matches inserter
|
||||||
unsafe {
|
unsafe {
|
||||||
cache
|
cache.inserter.insert(
|
||||||
.inserter
|
entity,
|
||||||
.insert(entity, location, bundle, insert_mode, caller)
|
location,
|
||||||
|
bundle,
|
||||||
|
insert_mode,
|
||||||
|
caller,
|
||||||
|
RelationshipInsertHookMode::Run,
|
||||||
|
)
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
invalid_entities.push(entity);
|
invalid_entities.push(entity);
|
||||||
|
@ -11,6 +11,7 @@ use bevy_reflect::{PartialReflect, TypePath, TypeRegistry};
|
|||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
use crate::serde::SceneSerializer;
|
use crate::serde::SceneSerializer;
|
||||||
use bevy_ecs::component::ComponentCloneBehavior;
|
use bevy_ecs::component::ComponentCloneBehavior;
|
||||||
|
use bevy_ecs::relationship::RelationshipInsertHookMode;
|
||||||
#[cfg(feature = "serialize")]
|
#[cfg(feature = "serialize")]
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
@ -110,10 +111,8 @@ impl DynamicScene {
|
|||||||
#[expect(unsafe_code, reason = "this is faster")]
|
#[expect(unsafe_code, reason = "this is faster")]
|
||||||
let component_info =
|
let component_info =
|
||||||
unsafe { world.components().get_info_unchecked(component_id) };
|
unsafe { world.components().get_info_unchecked(component_id) };
|
||||||
match component_info.clone_behavior() {
|
if *component_info.clone_behavior() == ComponentCloneBehavior::Ignore {
|
||||||
ComponentCloneBehavior::Ignore
|
continue;
|
||||||
| ComponentCloneBehavior::RelationshipTarget(_) => continue,
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +122,7 @@ impl DynamicScene {
|
|||||||
component.as_partial_reflect(),
|
component.as_partial_reflect(),
|
||||||
&type_registry,
|
&type_registry,
|
||||||
mapper,
|
mapper,
|
||||||
|
RelationshipInsertHookMode::Skip,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ use bevy_ecs::{
|
|||||||
entity::{hash_map::EntityHashMap, Entity, SceneEntityMapper},
|
entity::{hash_map::EntityHashMap, Entity, SceneEntityMapper},
|
||||||
entity_disabling::DefaultQueryFilters,
|
entity_disabling::DefaultQueryFilters,
|
||||||
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
|
reflect::{AppTypeRegistry, ReflectComponent, ReflectResource},
|
||||||
|
relationship::RelationshipInsertHookMode,
|
||||||
world::World,
|
world::World,
|
||||||
};
|
};
|
||||||
use bevy_reflect::{PartialReflect, TypePath};
|
use bevy_reflect::{PartialReflect, TypePath};
|
||||||
@ -124,10 +125,8 @@ impl Scene {
|
|||||||
.get_info(component_id)
|
.get_info(component_id)
|
||||||
.expect("component_ids in archetypes should have ComponentInfo");
|
.expect("component_ids in archetypes should have ComponentInfo");
|
||||||
|
|
||||||
match component_info.clone_behavior() {
|
if *component_info.clone_behavior() == ComponentCloneBehavior::Ignore {
|
||||||
ComponentCloneBehavior::Ignore
|
continue;
|
||||||
| ComponentCloneBehavior::RelationshipTarget(_) => continue,
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let registration = type_registry
|
let registration = type_registry
|
||||||
@ -157,6 +156,7 @@ impl Scene {
|
|||||||
component.as_partial_reflect(),
|
component.as_partial_reflect(),
|
||||||
&type_registry,
|
&type_registry,
|
||||||
mapper,
|
mapper,
|
||||||
|
RelationshipInsertHookMode::Skip,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -874,4 +874,73 @@ mod tests {
|
|||||||
app.update();
|
app.update();
|
||||||
check(app.world_mut(), 0);
|
check(app.world_mut(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scene_child_order_preserved_when_archetype_order_mismatched() {
|
||||||
|
let mut app = App::new();
|
||||||
|
|
||||||
|
app.add_plugins(ScheduleRunnerPlugin::default())
|
||||||
|
.add_plugins(AssetPlugin::default())
|
||||||
|
.add_plugins(ScenePlugin)
|
||||||
|
.register_type::<ComponentA>()
|
||||||
|
.register_type::<ComponentF>();
|
||||||
|
app.update();
|
||||||
|
|
||||||
|
let mut scene_world = World::new();
|
||||||
|
let root = scene_world.spawn_empty().id();
|
||||||
|
let temporary_root = scene_world.spawn_empty().id();
|
||||||
|
// Spawn entities with different parent first before parenting them to the actual root, allowing us
|
||||||
|
// to decouple child order from archetype-creation-order
|
||||||
|
let child1 = scene_world
|
||||||
|
.spawn((
|
||||||
|
ChildOf {
|
||||||
|
parent: temporary_root,
|
||||||
|
},
|
||||||
|
ComponentA { x: 1.0, y: 1.0 },
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
let child2 = scene_world
|
||||||
|
.spawn((
|
||||||
|
ChildOf {
|
||||||
|
parent: temporary_root,
|
||||||
|
},
|
||||||
|
ComponentA { x: 2.0, y: 2.0 },
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
// the "first" child is intentionally spawned with a different component to force it into a "newer" archetype,
|
||||||
|
// meaning it will be iterated later in the spawn code.
|
||||||
|
let child0 = scene_world
|
||||||
|
.spawn((
|
||||||
|
ChildOf {
|
||||||
|
parent: temporary_root,
|
||||||
|
},
|
||||||
|
ComponentF,
|
||||||
|
))
|
||||||
|
.id();
|
||||||
|
|
||||||
|
scene_world
|
||||||
|
.entity_mut(root)
|
||||||
|
.add_children(&[child0, child1, child2]);
|
||||||
|
|
||||||
|
let scene = Scene::new(scene_world);
|
||||||
|
let scene_handle = app.world_mut().resource_mut::<Assets<Scene>>().add(scene);
|
||||||
|
|
||||||
|
let spawned = app.world_mut().spawn(SceneRoot(scene_handle.clone())).id();
|
||||||
|
|
||||||
|
app.update();
|
||||||
|
let world = app.world_mut();
|
||||||
|
|
||||||
|
let spawned_root = world.entity(spawned).get::<Children>().unwrap()[0];
|
||||||
|
let children = world.entity(spawned_root).get::<Children>().unwrap();
|
||||||
|
assert_eq!(children.len(), 3);
|
||||||
|
assert!(world.entity(children[0]).get::<ComponentF>().is_some());
|
||||||
|
assert_eq!(
|
||||||
|
world.entity(children[1]).get::<ComponentA>().unwrap().x,
|
||||||
|
1.0
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
world.entity(children[2]).get::<ComponentA>().unwrap().x,
|
||||||
|
2.0
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +79,7 @@ fn setup(world: &mut World) {
|
|||||||
entity,
|
entity,
|
||||||
component_id,
|
component_id,
|
||||||
caller,
|
caller,
|
||||||
|
..
|
||||||
}| {
|
}| {
|
||||||
// You can access component data from within the hook
|
// You can access component data from within the hook
|
||||||
let value = world.get::<MyComponent>(entity).unwrap().0;
|
let value = world.get::<MyComponent>(entity).unwrap().0;
|
||||||
@ -116,6 +117,7 @@ fn setup(world: &mut World) {
|
|||||||
entity,
|
entity,
|
||||||
component_id,
|
component_id,
|
||||||
caller,
|
caller,
|
||||||
|
..
|
||||||
}| {
|
}| {
|
||||||
let value = world.get::<MyComponent>(entity).unwrap().0;
|
let value = world.get::<MyComponent>(entity).unwrap().0;
|
||||||
println!(
|
println!(
|
||||||
|
Loading…
Reference in New Issue
Block a user