1use crate::{
2 components::{GlobalTransform, Transform, TransformTreeChanged},
3helper::TransformHelper,
4};
56use bevy_ecs::{prelude::*, query::QueryFilter};
78/// Generic system that propagates transforms,
9/// using [`TransformHelper`] for any entity matching the filter `F`.
10/// Useful for moving and rendering in the same frame.
11pub fn propagate_transforms_for<F: QueryFilter + 'static>(
12 tf_helper: TransformHelper,
13mut query: Query<(Entity, &mut GlobalTransform), F>,
14) {
15for (entity, mut gtf) in query.iter_mut() {
16let result = tf_helper
17 .compute_global_transform(entity)
18 .inspect_err(|_err| {
19#[cfg(feature = "trace")]
20{
static SHOULD_FIRE: ::bevy_utils::OnceFlag =
::bevy_utils::OnceFlag::new();
if SHOULD_FIRE.set() {
{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/systems.rs:20",
"bevy_transform::systems", ::tracing::Level::WARN,
::tracing_core::__macro_support::Option::Some("src/systems.rs"),
::tracing_core::__macro_support::Option::Some(20u32),
::tracing_core::__macro_support::Option::Some("bevy_transform::systems"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::WARN <=
::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::WARN <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Failed to compute GlobalTransform for entity {0:?}: {1:?}",
entity, _err) as &dyn ::tracing::field::Value))])
});
} else { ; }
};
}
};bevy_utils::once!(tracing::warn!(
21"Failed to compute GlobalTransform for entity {:?}: {:?}",
22 entity,
23 _err
24 ));
25 });
2627if let Ok(computed) = result {
28*gtf = computed;
29 }
30 }
31}
3233#[cfg(feature = "multi_threaded")]
34pub use parallel::propagate_parent_transforms;
35#[cfg(not(feature = "multi_threaded"))]
36pub use serial::propagate_parent_transforms;
3738/// Update [`GlobalTransform`] component of entities that aren't in the hierarchy
39///
40/// Third party plugins should ensure that this is used in concert with
41/// [`propagate_parent_transforms`] and [`mark_dirty_trees`].
42pub fn sync_simple_transforms(
43mut query: ParamSet<(
44Query<
45 (&Transform, &mut GlobalTransform),
46 (
47Or<(Changed<Transform>, Added<GlobalTransform>)>,
48Without<ChildOf>,
49Without<Children>,
50 ),
51 >,
52Query<(Ref<Transform>, &mut GlobalTransform), (Without<ChildOf>, Without<Children>)>,
53 )>,
54mut orphaned: RemovedComponents<ChildOf>,
55) {
56// Update changed entities.
57#[cfg(feature = "multi_threaded")]
58query59 .p0()
60 .par_iter_mut()
61 .for_each(|(transform, mut global_transform)| {
62*global_transform = GlobalTransform::from(*transform);
63 });
64#[cfg(not(feature = "multi_threaded"))]
65query
66 .p0()
67 .iter_mut()
68 .for_each(|(transform, mut global_transform)| {
69*global_transform = GlobalTransform::from(*transform);
70 });
71// Update orphaned entities.
72let mut query = query.p1();
73let mut iter = query.iter_many_mut(orphaned.read()).matched();
74while let Some((transform, mut global_transform)) = iter.fetch_next() {
75if !transform.is_changed() && !global_transform.is_added() {
76*global_transform = GlobalTransform::from(*transform);
77 }
78 }
79}
8081/// Configure the behavior of static scene optimizations for [`Transform`] propagation.
82///
83/// For scenes with many static entities, it is much faster to track trees of unchanged
84/// [`Transform`]s and skip these during the expensive transform propagation step. If your scene is
85/// very dynamic, the cost of tracking these trees can exceed the performance benefits. By default,
86/// static scene optimization is enabled.
87#[derive(impl bevy_ecs::component::Component for StaticTransformOptimizations where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {
const STORAGE_TYPE: bevy_ecs::component::StorageType =
bevy_ecs::component::StorageType::SparseSet;
type Mutability = bevy_ecs::component::Mutable;
fn register_required_components(_requiree:
bevy_ecs::component::ComponentId,
required_components:
&mut bevy_ecs::component::RequiredComponentsRegistrator) {
let resource_component_id =
if let ::core::option::Option::Some(id) =
required_components.components_registrator().component_id::<StaticTransformOptimizations>()
{
id
} else {
required_components.components_registrator().register_component::<StaticTransformOptimizations>()
};
required_components.register_required::<bevy_ecs::resource::IsResource>(move
||
bevy_ecs::resource::IsResource::new(resource_component_id));
}
fn clone_behavior() -> bevy_ecs::component::ComponentCloneBehavior {
use bevy_ecs::component::{
DefaultCloneBehaviorBase, DefaultCloneBehaviorViaClone,
};
(&&&bevy_ecs::component::DefaultCloneBehaviorSpecialization::<Self>::default()).default_clone_behavior()
}
fn map_entities<M: bevy_ecs::entity::EntityMapper>(this: &mut Self,
mapper: &mut M) {
use bevy_ecs::entity::MapEntities;
match this {
Self::Enabled { .. } => {}
Self::Disabled { .. } => {}
_ => {}
}
}
fn relationship_accessor()
->
::core::option::Option<bevy_ecs::relationship::ComponentRelationshipAccessor<Self>> {
::core::option::Option::None
}
}
impl bevy_ecs::resource::Resource for StaticTransformOptimizations where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource, #[automatically_derived]
impl ::core::fmt::Debug for StaticTransformOptimizations {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
StaticTransformOptimizations::Enabled => "Enabled",
StaticTransformOptimizations::Disabled => "Disabled",
})
}
}Debug, #[automatically_derived]
impl ::core::default::Default for StaticTransformOptimizations {
#[inline]
fn default() -> Self { Self::Enabled }
}Default, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for StaticTransformOptimizations { }
#[automatically_derived]
impl ::core::cmp::PartialEq for StaticTransformOptimizations {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for StaticTransformOptimizations { }Eq)]
88#[cfg_attr(feature = "bevy_reflect", derive(const _: () =
{
impl bevy_reflect::GetTypeRegistration for
StaticTransformOptimizations where {
fn get_type_registration() -> bevy_reflect::TypeRegistration {
let mut registration =
bevy_reflect::TypeRegistration::of::<Self>();
registration.insert(<bevy_reflect::ReflectFromPtr as
bevy_reflect::CreateTypeData<Self>>::create_type_data(()));
registration.insert(<bevy_reflect::ReflectFromReflect as
bevy_reflect::CreateTypeData<Self>>::create_type_data(()));
registration
}
#[inline(never)]
fn register_type_dependencies(registry:
&mut bevy_reflect::TypeRegistry) {}
}
impl bevy_reflect::Typed for StaticTransformOptimizations where {
#[inline(never)]
fn type_info() -> &'static bevy_reflect::TypeInfo {
static CELL: bevy_reflect::utility::NonGenericTypeInfoCell =
bevy_reflect::utility::NonGenericTypeInfoCell::new();
CELL.get_or_set(#[inline(never)] ||
{
bevy_reflect::TypeInfo::Enum(bevy_reflect::enums::EnumInfo::new::<Self>(&[bevy_reflect::enums::VariantInfo::Unit(bevy_reflect::enums::UnitVariantInfo::new("Enabled")),
bevy_reflect::enums::VariantInfo::Unit(bevy_reflect::enums::UnitVariantInfo::new("Disabled"))]))
})
}
}
#[allow(deprecated, reason =
"derives on a deprecated type shouldn't be considered a usage")]
impl bevy_reflect::TypePath for StaticTransformOptimizations where {
fn type_path() -> &'static str {
"bevy_transform::systems::StaticTransformOptimizations"
}
fn short_type_path() -> &'static str {
"StaticTransformOptimizations"
}
fn type_ident() -> ::core::option::Option<&'static str> {
::core::option::Option::Some("StaticTransformOptimizations")
}
fn crate_name() -> ::core::option::Option<&'static str> {
::core::option::Option::Some("bevy_transform::systems".split(':').next().unwrap())
}
fn module_path() -> ::core::option::Option<&'static str> {
::core::option::Option::Some("bevy_transform::systems")
}
}
impl bevy_reflect::Reflect for StaticTransformOptimizations where {
#[inline]
fn into_any(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
->
bevy_reflect::__macro_exports::alloc_utils::Box<dyn ::core::any::Any> {
self
}
#[inline]
fn as_any(&self) -> &dyn ::core::any::Any { self }
#[inline]
fn as_any_mut(&mut self) -> &mut dyn ::core::any::Any { self }
#[inline]
fn into_reflect(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
->
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect> {
self
}
#[inline]
fn as_reflect(&self) -> &dyn bevy_reflect::Reflect { self }
#[inline]
fn as_reflect_mut(&mut self) -> &mut dyn bevy_reflect::Reflect {
self
}
#[inline]
fn set(&mut self,
value:
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>)
->
::core::result::Result<(),
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>> {
*self = <dyn bevy_reflect::Reflect>::take(value)?;
::core::result::Result::Ok(())
}
}
impl bevy_reflect::enums::Enum for StaticTransformOptimizations where
{
fn field(&self, __name_param: &str)
-> ::core::option::Option<&dyn bevy_reflect::PartialReflect> {
match self { _ => ::core::option::Option::None, }
}
fn field_at(&self, __index_param: usize)
-> ::core::option::Option<&dyn bevy_reflect::PartialReflect> {
match self { _ => ::core::option::Option::None, }
}
fn field_mut(&mut self, __name_param: &str)
->
::core::option::Option<&mut dyn bevy_reflect::PartialReflect> {
match self { _ => ::core::option::Option::None, }
}
fn field_at_mut(&mut self, __index_param: usize)
->
::core::option::Option<&mut dyn bevy_reflect::PartialReflect> {
match self { _ => ::core::option::Option::None, }
}
fn index_of(&self, __name_param: &str)
-> ::core::option::Option<usize> {
match self { _ => ::core::option::Option::None, }
}
fn name_at(&self, __index_param: usize)
-> ::core::option::Option<&str> {
match self { _ => ::core::option::Option::None, }
}
fn iter_fields(&self) -> bevy_reflect::enums::VariantFieldIter {
bevy_reflect::enums::VariantFieldIter::new(self)
}
#[inline]
fn field_len(&self) -> usize {
match self {
StaticTransformOptimizations::Enabled { .. } => 0usize,
StaticTransformOptimizations::Disabled { .. } => 0usize,
_ => 0,
}
}
#[inline]
fn variant_name(&self) -> &str {
match self {
StaticTransformOptimizations::Enabled { .. } => "Enabled",
StaticTransformOptimizations::Disabled { .. } => "Disabled",
_ =>
::core::panicking::panic("internal error: entered unreachable code"),
}
}
#[inline]
fn variant_index(&self) -> usize {
match self {
StaticTransformOptimizations::Enabled { .. } => 0usize,
StaticTransformOptimizations::Disabled { .. } => 1usize,
_ =>
::core::panicking::panic("internal error: entered unreachable code"),
}
}
#[inline]
fn variant_type(&self) -> bevy_reflect::enums::VariantType {
match self {
StaticTransformOptimizations::Enabled { .. } =>
bevy_reflect::enums::VariantType::Unit,
StaticTransformOptimizations::Disabled { .. } =>
bevy_reflect::enums::VariantType::Unit,
_ =>
::core::panicking::panic("internal error: entered unreachable code"),
}
}
fn to_dynamic_enum(&self)
->
::core::result::Result<bevy_reflect::enums::DynamicEnum,
bevy_reflect::ReflectCloneError> {
bevy_reflect::enums::DynamicEnum::try_from_ref::<Self>(self)
}
}
impl bevy_reflect::PartialReflect for StaticTransformOptimizations
where {
#[inline]
fn get_represented_type_info(&self)
-> ::core::option::Option<&'static bevy_reflect::TypeInfo> {
::core::option::Option::Some(<Self as
bevy_reflect::Typed>::type_info())
}
#[inline]
fn try_apply(&mut self,
__value_param: &dyn bevy_reflect::PartialReflect)
-> ::core::result::Result<(), bevy_reflect::ApplyError> {
if let bevy_reflect::ReflectRef::Enum(__value_param) =
bevy_reflect::PartialReflect::reflect_ref(__value_param) {
if bevy_reflect::enums::Enum::variant_name(self) ==
bevy_reflect::enums::Enum::variant_name(__value_param) {
match bevy_reflect::enums::Enum::variant_type(__value_param)
{
bevy_reflect::enums::VariantType::Struct => {
for field in
bevy_reflect::enums::Enum::iter_fields(__value_param) {
let name = field.name().unwrap();
if let ::core::option::Option::Some(v) =
bevy_reflect::enums::Enum::field_mut(self, name) {
bevy_reflect::PartialReflect::try_apply(v, field.value())?;
}
}
}
bevy_reflect::enums::VariantType::Tuple => {
for (index, field) in
::core::iter::Iterator::enumerate(bevy_reflect::enums::Enum::iter_fields(__value_param))
{
if let ::core::option::Option::Some(v) =
bevy_reflect::enums::Enum::field_at_mut(self, index) {
bevy_reflect::PartialReflect::try_apply(v, field.value())?;
}
}
}
_ => {}
}
} else {
match bevy_reflect::enums::Enum::variant_name(__value_param)
{
"Enabled" => {
*self = StaticTransformOptimizations::Enabled {}
}
"Disabled" => {
*self = StaticTransformOptimizations::Disabled {}
}
name => {
return ::core::result::Result::Err(bevy_reflect::ApplyError::UnknownVariant {
enum_name: ::core::convert::Into::into(bevy_reflect::DynamicTypePath::reflect_type_path(self)),
variant_name: ::core::convert::Into::into(name),
});
}
}
}
} else {
return ::core::result::Result::Err(bevy_reflect::ApplyError::MismatchedKinds {
from_kind: bevy_reflect::PartialReflect::reflect_kind(__value_param),
to_kind: bevy_reflect::ReflectKind::Enum,
});
}
::core::result::Result::Ok(())
}
fn reflect_kind(&self) -> bevy_reflect::ReflectKind {
bevy_reflect::ReflectKind::Enum
}
fn reflect_ref(&self) -> bevy_reflect::ReflectRef {
bevy_reflect::ReflectRef::Enum(self)
}
fn reflect_mut(&mut self) -> bevy_reflect::ReflectMut {
bevy_reflect::ReflectMut::Enum(self)
}
fn reflect_owned(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
-> bevy_reflect::ReflectOwned {
bevy_reflect::ReflectOwned::Enum(self)
}
#[inline]
fn try_into_reflect(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
->
::core::result::Result<bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>,
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::PartialReflect>> {
::core::result::Result::Ok(self)
}
#[inline]
fn try_as_reflect(&self)
-> ::core::option::Option<&dyn bevy_reflect::Reflect> {
::core::option::Option::Some(self)
}
#[inline]
fn try_as_reflect_mut(&mut self)
-> ::core::option::Option<&mut dyn bevy_reflect::Reflect> {
::core::option::Option::Some(self)
}
#[inline]
fn into_partial_reflect(self:
bevy_reflect::__macro_exports::alloc_utils::Box<Self>)
->
bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::PartialReflect> {
self
}
#[inline]
fn as_partial_reflect(&self)
-> &dyn bevy_reflect::PartialReflect {
self
}
#[inline]
fn as_partial_reflect_mut(&mut self)
-> &mut dyn bevy_reflect::PartialReflect {
self
}
fn reflect_hash(&self) -> ::core::option::Option<u64> {
(bevy_reflect::enums::enum_hash)(self)
}
fn reflect_partial_eq(&self,
value: &dyn bevy_reflect::PartialReflect)
-> ::core::option::Option<bool> {
(bevy_reflect::enums::enum_partial_eq)(self, value)
}
fn reflect_partial_cmp(&self,
value: &dyn bevy_reflect::PartialReflect)
-> ::core::option::Option<::core::cmp::Ordering> {
(bevy_reflect::enums::enum_partial_cmp)(self, value)
}
#[inline]
#[allow(unreachable_code, reason =
"Ignored fields without a `clone` attribute will early-return with an error")]
fn reflect_clone(&self)
->
::core::result::Result<bevy_reflect::__macro_exports::alloc_utils::Box<dyn bevy_reflect::Reflect>,
bevy_reflect::ReflectCloneError> {
let this = self;
::core::result::Result::Ok(bevy_reflect::__macro_exports::alloc_utils::Box::new(match this
{
StaticTransformOptimizations::Enabled {} =>
StaticTransformOptimizations::Enabled {},
StaticTransformOptimizations::Disabled {} =>
StaticTransformOptimizations::Disabled {},
}))
}
}
impl bevy_reflect::FromReflect for StaticTransformOptimizations where
{
fn from_reflect(__param0: &dyn bevy_reflect::PartialReflect)
-> ::core::option::Option<Self> {
if let bevy_reflect::ReflectRef::Enum(__param0) =
bevy_reflect::PartialReflect::reflect_ref(__param0) {
match bevy_reflect::enums::Enum::variant_name(__param0) {
"Enabled" =>
::core::option::Option::Some(StaticTransformOptimizations::Enabled {}),
"Disabled" =>
::core::option::Option::Some(StaticTransformOptimizations::Disabled {}),
name => ::core::option::Option::None,
}
} else { ::core::option::Option::None }
}
}
};bevy_reflect::Reflect))]
89pub enum StaticTransformOptimizations {
90/// Enable static scene optimizations.
91#[default]
92Enabled,
93/// Disable static scene optimizations.
94Disabled,
95}
9697impl StaticTransformOptimizations {
98/// Returns `true` if static scene optimizations are enabled.
99#[inline]
100pub fn is_enabled(&self) -> bool {
101*self == StaticTransformOptimizations::Enabled102 }
103}
104105/// Optimization for static scenes.
106///
107/// Propagates a "dirty bit" up the hierarchy towards ancestors. Transform propagation can ignore
108/// entire subtrees of the hierarchy if it encounters an entity without the dirty bit.
109///
110/// Configure behavior with [`StaticTransformOptimizations`].
111pub fn mark_dirty_trees(
112 changed: Query<Entity, Or<(Changed<Transform>, Changed<ChildOf>, Added<GlobalTransform>)>>,
113mut orphaned: RemovedComponents<ChildOf>,
114mut transforms: Query<&mut TransformTreeChanged>,
115 parents: Query<&ChildOf>,
116 static_optimizations: Res<StaticTransformOptimizations>,
117// Cached allocations for multi-threaded parallel implementation
118#[cfg(feature = "multi_threaded")] mut shared_bitset: Local<
119 alloc::vec::Vec<core::sync::atomic::AtomicU64>,
120 >,
121#[cfg(feature = "multi_threaded")] mut local_bitset: Local<
122 bevy_utils::Parallel<alloc::vec::Vec<u64>>,
123 >,
124#[cfg(feature = "multi_threaded")] mut consumer_channels: Local<
125 bevy_utils::BufferedChannel<Entity>,
126 >,
127#[cfg(feature = "multi_threaded")] mut traversal_channels: Local<
128 bevy_utils::BufferedChannel<Entity>,
129 >,
130) {
131if !static_optimizations.is_enabled() {
132return;
133 }
134135// Simple serial implementation that iterates changed entities and traverses the tree.
136#[cfg(not(feature = "multi_threaded"))]
137for entity in changed.iter().chain(orphaned.read()) {
138let mut next = entity;
139while let Ok(mut tree) = transforms.get_mut(next) {
140if tree.is_changed() && !tree.is_added() {
141// If the component was changed, this part of the tree has already been processed.
142 // Ignore this if the change was caused by the component being added.
143break;
144 }
145 tree.set_changed();
146if let Ok(parent) = parents.get(next).map(ChildOf::parent) {
147 next = parent;
148 } else {
149break;
150 };
151 }
152 }
153154// Concurrent and parallel implementation with three sets of asynchronous workers:
155 //
156 // - producer: (single) finds all changed or orphaned entities and sends a message in a channel
157 // - traversal: (many) read incoming messages from producer, traverse hierarchy using atomics to
158 // cooperatively early exit across threads, send newly changed entities to consumer.
159 // - consumer: (single) read incoming messages from traversal
160 //
161 // These workers are all running both parallelly and concurrently. They are spawned at the start
162 // of the scope and asynchronously await incoming batches of work. This allows the entire
163 // pipeline to start working as soon as there is available work to process, instead of running
164 // each stage serially with inner parallelism.
165#[cfg(feature = "multi_threaded")]
166{
167use bevy_tasks::ComputeTaskPool;
168use core::sync::atomic::Ordering;
169#[cfg(feature = "trace")]
170use tracing::{info_span, Instrument};
171172ComputeTaskPool::get().scope(|scope| {
173traversal_channels.chunk_size = 1024;
174consumer_channels.chunk_size = 1024;
175let (traversal_rx, mut traversal_tx) = traversal_channels.unbounded();
176let (consumer_rx, mut consumer_tx) = consumer_channels.unbounded();
177let shared_bitset: &[core::sync::atomic::AtomicU64] = &shared_bitset;
178let local_bitset = &*local_bitset;
179let parents_ref = &parents;
180181// Consumer: drain the channel of moved entities and call set_changed() on the marker.
182scope.spawn({
183let fut = async move {
184while let Ok(mut chunk) = consumer_rx.recv().await {
185for entity in chunk.drain() {
186if let Ok(mut tree) = transforms.get_mut(entity) {
187 tree.set_changed();
188 }
189 }
190 }
191 };
192#[cfg(feature = "trace")]
193let fut = fut.instrument({
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("consumer_mark_dirty",
"bevy_transform::systems", ::tracing::Level::INFO,
::tracing_core::__macro_support::Option::Some("src/systems.rs"),
::tracing_core::__macro_support::Option::Some(193u32),
::tracing_core::__macro_support::Option::Some("bevy_transform::systems"),
::tracing_core::field::FieldSet::new(&[],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::INFO <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() } &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta, &{ meta.fields().value_set_all(&[]) })
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
span
}
}info_span!("consumer_mark_dirty"));
194fut195 });
196197// Traversal: each task loops until the producer channel is exhausted, walking each
198 // entity's ancestor chain and forwarding newly marked entities to the consumer task.
199for _ in 0..(ComputeTaskPool::get().thread_num() - 1).max(1) {
200let traversal_rx = traversal_rx.clone();
201let mut consumer_tx = consumer_tx.clone();
202 scope.spawn({
203let fut = async move {
204while let Ok(mut chunk) = traversal_rx.recv().await {
205for mut entity in chunk.drain() {
206let mut first_iteration = true;
207'traverse_hierarchy: loop {
208let idx = entity.index().index() as usize;
209let word = idx / 64;
210let bit = 1u64 << (idx % 64);
211212#[expect(
213 clippy::redundant_else,
214 reason = "Without the else, fails to compile due to async"
215)]
216if word < shared_bitset.len()
217 && shared_bitset[word].fetch_or(bit, Ordering::Relaxed)
218 & bit
219 != 0
220{
221// Common path: atomic OR into the shared bitset.
222 // If the entity was already visited, we can stop climbing.
223break 'traverse_hierarchy;
224 } else {
225// Overflow: entity index exceeds shared bitset capacity.
226 // Use a per-task local bitset for intra-task early exit.
227let overflow = &mut *local_bitset.borrow_local_mut();
228if word < overflow.len() && overflow[word] & bit != 0 {
229break 'traverse_hierarchy;
230 }
231if word >= overflow.len() {
232 overflow.resize(word + 1, 0u64);
233 }
234 overflow[word] |= bit;
235 }
236237// If we have not hit a break yet, it's the first time we've
238 // seen this entity, so it should be sent to the consumer.
239if first_iteration {
240 first_iteration = false;
241 } else {
242// The first iteration (leaf) has already been sent to the
243 // consumer by the producer; we don't need to send it again.
244consumer_tx.send(entity).await.ok();
245 }
246247match parents_ref.get(entity).ok().map(ChildOf::parent) {
248Some(parent) => entity = parent,
249None => break 'traverse_hierarchy,
250 }
251 }
252 }
253 }
254 };
255#[cfg(feature = "trace")]
256let fut = fut.instrument({
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("par_traversal_mark_dirty",
"bevy_transform::systems", ::tracing::Level::INFO,
::tracing_core::__macro_support::Option::Some("src/systems.rs"),
::tracing_core::__macro_support::Option::Some(256u32),
::tracing_core::__macro_support::Option::Some("bevy_transform::systems"),
::tracing_core::field::FieldSet::new(&[],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::INFO <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() } &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta, &{ meta.fields().value_set_all(&[]) })
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
span
}
}info_span!("par_traversal_mark_dirty"));
257 fut
258 });
259 }
260261// Producer: Feed changed entities and orphans into producer tasks. The senders are
262 // dropped at the end of this closure, closing the channel and allowing the other tasks
263 // to exit.
264 //
265 // Note that we send the entity directly to the consumer as well, we do this to start
266 // feeding it work as soon as possible. The traversal worker should skip sending these
267 // leaves to the consumer because it has already been sent here.
268let mut producer = move || {
269for entity in orphaned.read() {
270let _ = traversal_tx.send_blocking(entity);
271let _ = consumer_tx.send_blocking(entity);
272 }
273// Changed<> table scans are slow, so we parallelize them to improve performance.
274changed.par_iter().for_each_init(
275 || (traversal_tx.clone(), consumer_tx.clone()),
276 |(traversal_tx, consumer_tx), entity| {
277let _ = traversal_tx.send_blocking(entity);
278let _ = consumer_tx.send_blocking(entity);
279 },
280 );
281 };
282#[cfg(feature = "trace")]
283{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("producer_mark_dirty",
"bevy_transform::systems", ::tracing::Level::INFO,
::tracing_core::__macro_support::Option::Some("src/systems.rs"),
::tracing_core::__macro_support::Option::Some(283u32),
::tracing_core::__macro_support::Option::Some("bevy_transform::systems"),
::tracing_core::field::FieldSet::new(&[],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::INFO <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() } &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta, &{ meta.fields().value_set_all(&[]) })
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
span
}
}info_span!("producer_mark_dirty").in_scope(&mut producer);
284#[cfg(not(feature = "trace"))]
285producer();
286 });
287288// Merge thread-local bitsets into the shared bitset, growing it to accommodate the largest
289 // entity index we have encountered so far. At steady-state, these local bitsets stay empty.
290for local_bitset in local_bitset.iter_mut() {
291if local_bitset.is_empty() {
292continue;
293 }
294if local_bitset.len() > shared_bitset.len() {
295 shared_bitset.resize_with(local_bitset.len(), Default::default);
296 }
297 local_bitset.clear();
298 }
299300// Reset the bitset for the next frame while preserving the `Vec` length. Using `clear()`
301 // would shrink the length to 0 and force every entity through the overflow path next frame.
302for w in shared_bitset.iter() {
303 w.store(0, Ordering::Relaxed);
304 }
305 }
306}
307308// TODO: This serial implementation isn't actually serial, it parallelizes across the roots.
309// Additionally, this couples "no_std" with "single_threaded" when these two features should be
310// independent.
311//
312// What we want to do in a future refactor is take the current "single threaded" implementation, and
313// actually make it single threaded. This will remove any overhead associated with working on a task
314// pool when you only have a single thread, and will have the benefit of removing the need for any
315// unsafe. We would then make the multithreaded implementation work across std and no_std, but this
316// is blocked a no_std compatible Channel, which is why this TODO is not yet implemented.
317//
318// This complexity might also not be needed. If the multithreaded implementation on a single thread
319// is as fast as the single threaded implementation, we could simply remove the entire serial
320// module, and make the multithreaded module no_std compatible.
321//
322/// Serial hierarchy traversal. Useful in `no_std` or single threaded contexts.
323#[cfg(not(feature = "multi_threaded"))]
324mod serial {
325use crate::prelude::*;
326use alloc::vec::Vec;
327use bevy_ecs::prelude::*;
328329/// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`]
330 /// component.
331 ///
332 /// Third party plugins should ensure that this is used in concert with
333 /// [`sync_simple_transforms`](super::sync_simple_transforms) and
334 /// [`mark_dirty_trees`](super::mark_dirty_trees).
335pub fn propagate_parent_transforms(
336mut root_query: Query<
337 (Entity, &Children, Ref<Transform>, &mut GlobalTransform),
338 Without<ChildOf>,
339 >,
340mut orphaned: RemovedComponents<ChildOf>,
341 transform_query: Query<
342 (Ref<Transform>, &mut GlobalTransform, Option<&Children>),
343 With<ChildOf>,
344 >,
345 child_query: Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
346mut orphaned_entities: Local<Vec<Entity>>,
347 ) {
348 orphaned_entities.clear();
349 orphaned_entities.extend(orphaned.read());
350 orphaned_entities.sort_unstable();
351 root_query.par_iter_mut().for_each(
352 |(entity, children, transform, mut global_transform)| {
353let changed = transform.is_changed() || global_transform.is_added() || orphaned_entities.binary_search(&entity).is_ok();
354if changed {
355*global_transform = GlobalTransform::from(*transform);
356 }
357358for (child, child_of) in child_query.iter_many(children).matched() {
359assert_eq!(
360 child_of.parent(), entity,
361"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
362);
363// SAFETY:
364 // - `child` must have consistent parentage, or the above assertion would panic.
365 // Since `child` is parented to a root entity, the entire hierarchy leading to it
366 // is consistent.
367 // - We may operate as if all descendants are consistent, since
368 // `propagate_recursive` will panic before continuing to propagate if it
369 // encounters an entity with inconsistent parentage.
370 // - Since each root entity is unique and the hierarchy is consistent and
371 // forest-like, other root entities' `propagate_recursive` calls will not conflict
372 // with this one.
373 // - Since this is the only place where `transform_query` gets used, there will be
374 // no conflicting fetches elsewhere.
375#[expect(unsafe_code, reason = "`propagate_recursive()` is unsafe due to its use of `Query::get_unchecked()`.")]
376unsafe {
377 propagate_recursive(
378&global_transform,
379&transform_query,
380&child_query,
381 child,
382 changed || child_of.is_changed(),
383 );
384 }
385 }
386 },
387 );
388 }
389390/// Recursively propagates the transforms for `entity` and all of its descendants.
391 ///
392 /// # Panics
393 ///
394 /// If `entity`'s descendants have a malformed hierarchy, this function will panic occur before
395 /// propagating the transforms of any malformed entities and their descendants.
396 ///
397 /// # Safety
398 ///
399 /// - While this function is running, `transform_query` must not have any fetches for `entity`,
400 /// nor any of its descendants.
401 /// - The caller must ensure that the hierarchy leading to `entity` is well-formed and must
402 /// remain as a tree or a forest. Each entity must have at most one parent.
403#[expect(
404 unsafe_code,
405 reason = "This function uses `Query::get_unchecked()`, which can result in multiple mutable references if the preconditions are not met."
406)]
407unsafe fn propagate_recursive(
408 parent: &GlobalTransform,
409 transform_query: &Query<
410 (Ref<Transform>, &mut GlobalTransform, Option<&Children>),
411 With<ChildOf>,
412 >,
413 child_query: &Query<(Entity, Ref<ChildOf>), With<GlobalTransform>>,
414 entity: Entity,
415mut changed: bool,
416 ) {
417let (global_matrix, children) = {
418let Ok((transform, mut global_transform, children)) =
419// SAFETY: This call cannot create aliased mutable references.
420 // - The top level iteration parallelizes on the roots of the hierarchy.
421 // - The caller ensures that each child has one and only one unique parent throughout
422 // the entire hierarchy.
423 //
424 // For example, consider the following malformed hierarchy:
425 //
426 // A
427 // / \
428 // B C
429 // \ /
430 // D
431 //
432 // D has two parents, B and C. If the propagation passes through C, but the ChildOf
433 // component on D points to B, the above check will panic as the origin parent does
434 // match the recorded parent.
435 //
436 // Also consider the following case, where A and B are roots:
437 //
438 // A B
439 // \ /
440 // C D
441 // \ /
442 // E
443 //
444 // Even if these A and B start two separate tasks running in parallel, one of them will
445 // panic before attempting to mutably access E.
446(unsafe { transform_query.get_unchecked(entity) }) else {
447return;
448 };
449450 changed |= transform.is_changed() || global_transform.is_added();
451if changed {
452*global_transform = parent.mul_transform(*transform);
453 }
454 (global_transform, children)
455 };
456457let Some(children) = children else { return };
458for (child, child_of) in child_query.iter_many(children).matched() {
459assert_eq!(
460 child_of.parent(), entity,
461"Malformed hierarchy. This probably means that your hierarchy has been improperly maintained, or contains a cycle"
462);
463// SAFETY: The caller guarantees that `transform_query` will not be fetched for any
464 // descendants of `entity`, so it is safe to call `propagate_recursive` for each child.
465 //
466 // The above assertion ensures that each child has one and only one unique parent
467 // throughout the entire hierarchy.
468unsafe {
469 propagate_recursive(
470 global_matrix.as_ref(),
471 transform_query,
472 child_query,
473 child,
474 changed || child_of.is_changed(),
475 );
476 }
477 }
478 }
479}
480481// TODO: Relies on `std` until a `no_std` `mpsc` channel is available.
482//
483/// Parallel hierarchy traversal with a batched work sharing scheduler. Often 2-5 times faster than
484/// the serial version.
485#[cfg(feature = "multi_threaded")]
486mod parallel {
487use crate::prelude::*;
488// TODO: this implementation could be used in no_std if there are equivalents of these.
489use crate::systems::StaticTransformOptimizations;
490use alloc::{sync::Arc, vec::Vec};
491use bevy_ecs::{entity::UniqueEntitySlice, prelude::*, system::lifetimeless::Read};
492use bevy_tasks::{ComputeTaskPool, TaskPool};
493use bevy_utils::Parallel;
494use core::sync::atomic::{AtomicI32, Ordering};
495use std::sync::{
496 mpsc::{Receiver, Sender},
497Mutex,
498 };
499500/// Update [`GlobalTransform`] component of entities based on entity hierarchy and [`Transform`]
501 /// component.
502 ///
503 /// Third party plugins should ensure that this is used in concert with
504 /// [`sync_simple_transforms`](super::sync_simple_transforms) and
505 /// [`mark_dirty_trees`](super::mark_dirty_trees).
506pub fn propagate_parent_transforms(
507mut queue: Local<WorkQueue>,
508mut roots: Query<
509 (
510Entity,
511Ref<Transform>,
512&mut GlobalTransform,
513&Children,
514Ref<TransformTreeChanged>,
515 ),
516Without<ChildOf>,
517 >,
518 nodes: NodeQuery,
519 static_optimizations: Res<StaticTransformOptimizations>,
520 ) {
521// Process roots in parallel, seeding the work queue
522roots.par_iter_mut().for_each_init(
523 || queue.local_queue.borrow_local_mut(),
524 |outbox, (parent, transform, mut parent_transform, children, transform_tree)| {
525if static_optimizations.is_enabled() && !transform_tree.is_changed() {
526// Early exit if the subtree is static and the optimization is enabled.
527return;
528 }
529530*parent_transform = GlobalTransform::from(*transform);
531532// SAFETY: the parent entities passed into this function are taken from iterating
533 // over the root entity query. Queries iterate over disjoint entities, preventing
534 // mutable aliasing, and making this call safe.
535#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
536unsafe {
537propagate_descendants_unchecked(
538parent,
539parent_transform,
540children,
541&nodes,
542outbox,
543&queue,
544&static_optimizations,
545// Need to revisit this single-max-depth by profiling more representative
546 // scenes. It's possible that it is actually beneficial to go deep into the
547 // hierarchy to build up a good task queue before starting the workers.
548 // However, we avoid this for now to prevent cases where only a single
549 // thread is going deep into the hierarchy while the others sit idle, which
550 // is the problem that the tasks sharing workers already solve.
5511,
552 );
553 }
554 },
555 );
556// Send all tasks in thread local outboxes *after* roots are processed to reduce the total
557 // number of channel sends by avoiding sending partial batches.
558queue.send_batches();
559560if let Ok(rx) = queue.receiver.try_lock() {
561if let Some(task) = rx.try_iter().next() {
562// This is a bit silly, but the only way to see if there is any work is to grab a
563 // task. Peeking will remove the task even if you don't call `next`, resulting in
564 // dropping a task. What we do here is grab the first task if there is one, then
565 // immediately send it to the back of the queue.
566queue.sender.send(task).ok();
567 } else {
568return; // No work, don't bother spawning any tasks
569}
570 }
571572// Spawn workers on the task pool to recursively propagate the hierarchy in parallel.
573let task_pool = ComputeTaskPool::get_or_init(TaskPool::default);
574task_pool.scope(|s| {
575 (1..task_pool.thread_num()) // First worker is run locally instead of the task pool.
576.for_each(|_| {
577s.spawn(async { propagation_worker(&queue, &nodes, &static_optimizations) });
578 });
579propagation_worker(&queue, &nodes, &static_optimizations);
580 });
581 }
582583/// A parallel worker that will consume processed parent entities from the queue, and push
584 /// children to the queue once it has propagated their [`GlobalTransform`].
585#[inline]
586fn propagation_worker(
587 queue: &WorkQueue,
588 nodes: &NodeQuery,
589 static_optimizations: &StaticTransformOptimizations,
590 ) {
591#[cfg(feature = "trace")]
592let _span = {
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("transform propagation worker",
"bevy_transform::systems::parallel", ::tracing::Level::INFO,
::tracing_core::__macro_support::Option::Some("src/systems.rs"),
::tracing_core::__macro_support::Option::Some(592u32),
::tracing_core::__macro_support::Option::Some("bevy_transform::systems::parallel"),
::tracing_core::field::FieldSet::new(&[],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::SPAN)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let mut interest = ::tracing::subscriber::Interest::never();
if ::tracing::Level::INFO <= ::tracing::level_filters::STATIC_MAX_LEVEL &&
::tracing::Level::INFO <=
::tracing::level_filters::LevelFilter::current() &&
{ interest = __CALLSITE.interest(); !interest.is_never() } &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest) {
let meta = __CALLSITE.metadata();
::tracing::Span::new(meta, &{ meta.fields().value_set_all(&[]) })
} else {
let span =
::tracing::__macro_support::__disabled_span(__CALLSITE.metadata());
{};
span
}
}tracing::info_span!("transform propagation worker").entered();
593594let mut outbox = queue.local_queue.borrow_local_mut();
595loop {
596// Try to acquire a lock on the work queue in a tight loop. Profiling shows this is much
597 // more efficient than relying on `.lock()`, which causes gaps to form between tasks.
598let Ok(rx) = queue.receiver.try_lock() else {
599 core::hint::spin_loop(); // No apparent impact on profiles, but best practice.
600continue;
601 };
602// If the queue is empty and no other threads are busy processing work, we can conclude
603 // there is no more work to do, and end the task by exiting the loop.
604let Some(mut tasks) = rx.try_iter().next() else {
605if queue.busy_threads.load(Ordering::Relaxed) == 0 {
606break; // All work is complete, kill the worker
607}
608continue; // No work to do now, but another thread is busy creating more work.
609};
610if tasks.is_empty() {
611continue; // This shouldn't happen, but if it does, we might as well stop early.
612}
613614// If the task queue is extremely short, it's worthwhile to gather a few more tasks to
615 // reduce the amount of thread synchronization needed once this very short task is
616 // complete.
617while tasks.len() < WorkQueue::CHUNK_SIZE / 2 {
618let Some(mut extra_task) = rx.try_iter().next() else {
619break;
620 };
621 tasks.append(&mut extra_task);
622 }
623624// At this point, we know there is work to do, so we increment the busy thread counter,
625 // and drop the mutex guard *after* we have incremented the counter. This ensures that
626 // if another thread is able to acquire a lock, the busy thread counter will already be
627 // incremented.
628queue.busy_threads.fetch_add(1, Ordering::Relaxed);
629drop(rx); // Important: drop after atomic and before work starts.
630631for parent in tasks.drain(..) {
632// SAFETY: each task pushed to the worker queue represents an unprocessed subtree of
633 // the hierarchy, guaranteeing unique access.
634#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
635unsafe {
636let (_, (_, p_global_transform, _), (p_children, _)) =
637 nodes.get_unchecked(parent).unwrap();
638 propagate_descendants_unchecked(
639 parent,
640 p_global_transform,
641 p_children.unwrap(), // All entities in the queue should have children
642nodes,
643&mut outbox,
644 queue,
645 static_optimizations,
646// Only affects performance. Trees deeper than this will still be fully
647 // propagated, but the work will be broken into multiple tasks. This number
648 // was chosen to be larger than any reasonable tree depth, while not being
649 // so large the function could hang on a deep hierarchy.
65010_000,
651 );
652 }
653 }
654WorkQueue::send_batches_with(&queue.sender, &mut outbox);
655queue.busy_threads.fetch_add(-1, Ordering::Relaxed);
656 }
657 }
658659/// Propagate transforms from `parent` to its `children`, pushing updated child entities to the
660 /// `outbox`. This function will continue propagating transforms to descendants in a depth-first
661 /// traversal, while simultaneously pushing unvisited branches to the outbox, for other threads
662 /// to take when idle.
663 ///
664 /// # Safety
665 ///
666 /// Callers must ensure that concurrent calls to this function are given unique `parent`
667 /// entities. Calling this function concurrently with the same `parent` is unsound. This
668 /// function will validate that the entity hierarchy does not contain cycles to prevent mutable
669 /// aliasing during propagation, but it is unable to verify that it isn't being used to mutably
670 /// alias the same entity.
671 ///
672 /// ## Panics
673 ///
674 /// Panics if the parent of a child node is not the same as the supplied `parent`. This
675 /// assertion ensures that the hierarchy is acyclic, which in turn ensures that if the caller is
676 /// following the supplied safety rules, multi-threaded propagation is sound.
677#[inline]
678 #[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
679unsafe fn propagate_descendants_unchecked(
680 parent: Entity,
681 p_global_transform: Mut<GlobalTransform>,
682 p_children: &Children,
683 nodes: &NodeQuery,
684 outbox: &mut Vec<Entity>,
685 queue: &WorkQueue,
686 static_optimizations: &StaticTransformOptimizations,
687 max_depth: usize,
688 ) {
689// Create mutable copies of the input variables, used for iterative depth-first traversal.
690let (mut parent, mut p_global_transform, mut p_children) =
691 (parent, p_global_transform, p_children);
692693// See the optimization note at the end to understand why this loop is here.
694for depth in 1..=max_depth {
695// Safety: traversing the entity tree from the roots, we assert that the childof and
696 // children pointers match in both directions (see assert below) to ensure the hierarchy
697 // does not have any cycles. Because the hierarchy does not have cycles, we know we are
698 // visiting disjoint entities in parallel, which is safe.
699#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
700let children_iter = unsafe {
701 nodes.iter_many_unique_unsafe(UniqueEntitySlice::from_slice_unchecked(p_children))
702 }
703 .matched();
704705let mut last_child = None;
706let new_children = children_iter.filter_map(
707 |(child, (transform, mut global_transform, tree), (children, child_of))| {
708if static_optimizations.is_enabled()
709 && !tree.is_changed()
710 && !p_global_transform.is_changed()
711 {
712// Static scene optimization
713return None;
714 }
715{
match (&child_of.parent(), &parent) {
(left_val, right_val) => {
if !(*left_val == *right_val) {
let kind = ::core::panicking::AssertKind::Eq;
::core::panicking::assert_failed(kind, &*left_val,
&*right_val, ::core::option::Option::None);
}
}
}
};assert_eq!(child_of.parent(), parent);
716717// Transform prop is expensive - this helps avoid updating entire subtrees if
718 // the GlobalTransform is unchanged, at the cost of an added equality check.
719global_transform.set_if_neq(p_global_transform.mul_transform(*transform));
720721 children.map(|children| {
722// Only continue propagation if the entity has children.
723last_child = Some((child, global_transform, children));
724 child
725 })
726 },
727 );
728 outbox.extend(new_children);
729730if depth >= max_depth || last_child.is_none() {
731break; // Don't remove anything from the outbox or send any chunks, just exit.
732}
733734// Optimization: tasks should consume work locally as long as they can to avoid
735 // thread synchronization for as long as possible.
736if let Some(last_child) = last_child {
737// Overwrite parent data with children, and loop to iterate through descendants.
738(parent, p_global_transform, p_children) = last_child;
739 outbox.pop();
740741// Send chunks during traversal. This allows sharing tasks with other threads before
742 // fully completing the traversal.
743if outbox.len() >= WorkQueue::CHUNK_SIZE {
744 WorkQueue::send_batches_with(&queue.sender, outbox);
745 }
746 }
747 }
748 }
749750/// Alias for a large, repeatedly used query. Queries for transform entities that have both a
751 /// parent and possibly children, thus they are not roots.
752type NodeQuery<'w, 's> = Query<
753'w,
754's,
755 (
756Entity,
757 (
758Ref<'static, Transform>,
759Mut<'static, GlobalTransform>,
760Ref<'static, TransformTreeChanged>,
761 ),
762 (Option<Read<Children>>, Read<ChildOf>),
763 ),
764 >;
765766/// A queue shared between threads for transform propagation.
767pub struct WorkQueue {
768/// A semaphore that tracks how many threads are busy doing work. Used to determine when
769 /// there is no more work to do.
770busy_threads: AtomicI32,
771 sender: Sender<Vec<Entity>>,
772 receiver: Arc<Mutex<Receiver<Vec<Entity>>>>,
773 local_queue: Parallel<Vec<Entity>>,
774 }
775impl Defaultfor WorkQueue {
776fn default() -> Self {
777let (tx, rx) = std::sync::mpsc::channel();
778Self {
779 busy_threads: AtomicI32::default(),
780 sender: tx,
781 receiver: Arc::new(Mutex::new(rx)),
782 local_queue: Default::default(),
783 }
784 }
785 }
786impl WorkQueue {
787const CHUNK_SIZE: usize = 512;
788789#[inline]
790fn send_batches_with(sender: &Sender<Vec<Entity>>, outbox: &mut Vec<Entity>) {
791for chunk in outbox
792 .chunks(WorkQueue::CHUNK_SIZE)
793 .filter(|c| !c.is_empty())
794 {
795 sender.send(chunk.to_vec()).ok();
796 }
797outbox.clear();
798 }
799800#[inline]
801fn send_batches(&mut self) {
802let Self {
803 sender,
804 local_queue,
805 ..
806 } = self;
807// Iterate over the locals to send batched tasks, avoiding the need to drain the locals
808 // into a larger allocation.
809local_queue810 .iter_mut()
811 .for_each(|outbox| Self::send_batches_with(sender, outbox));
812 }
813 }
814}
815816#[cfg(test)]
817mod test {
818use alloc::{vec, vec::Vec};
819use bevy_app::prelude::*;
820use bevy_ecs::world::CommandQueue;
821use bevy_math::{vec3, Vec3};
822use bevy_tasks::{ComputeTaskPool, TaskPool};
823824use crate::systems::*;
825826#[test]
827fn correct_parent_removed() {
828 ComputeTaskPool::get_or_init(TaskPool::default);
829let mut world = World::default();
830let offset_global_transform =
831 |offset| GlobalTransform::from(Transform::from_xyz(offset, offset, offset));
832let offset_transform = |offset| Transform::from_xyz(offset, offset, offset);
833834let mut schedule = Schedule::default();
835 schedule.add_systems(
836 (
837 mark_dirty_trees,
838 sync_simple_transforms,
839 propagate_parent_transforms,
840 )
841 .chain(),
842 );
843 world.insert_resource(StaticTransformOptimizations::default());
844845let mut command_queue = CommandQueue::default();
846let mut commands = Commands::new(&mut command_queue, &world);
847let root = commands.spawn(offset_transform(3.3)).id();
848let parent = commands.spawn(offset_transform(4.4)).id();
849let child = commands.spawn(offset_transform(5.5)).id();
850 commands.entity(parent).insert(ChildOf(root));
851 commands.entity(child).insert(ChildOf(parent));
852 command_queue.apply(&mut world);
853 schedule.run(&mut world);
854855assert_eq!(
856 world.get::<GlobalTransform>(parent).unwrap(),
857&offset_global_transform(4.4 + 3.3),
858"The transform systems didn't run, ie: `GlobalTransform` wasn't updated",
859 );
860861// Remove parent of `parent`
862let mut command_queue = CommandQueue::default();
863let mut commands = Commands::new(&mut command_queue, &world);
864 commands.entity(parent).remove::<ChildOf>();
865 command_queue.apply(&mut world);
866 schedule.run(&mut world);
867868assert_eq!(
869 world.get::<GlobalTransform>(parent).unwrap(),
870&offset_global_transform(4.4),
871"The global transform of an orphaned entity wasn't updated properly",
872 );
873874// Remove parent of `child`
875let mut command_queue = CommandQueue::default();
876let mut commands = Commands::new(&mut command_queue, &world);
877 commands.entity(child).remove::<ChildOf>();
878 command_queue.apply(&mut world);
879 schedule.run(&mut world);
880881assert_eq!(
882 world.get::<GlobalTransform>(child).unwrap(),
883&offset_global_transform(5.5),
884"The global transform of an orphaned entity wasn't updated properly",
885 );
886 }
887888#[test]
889fn did_propagate() {
890 ComputeTaskPool::get_or_init(TaskPool::default);
891let mut world = World::default();
892893let mut schedule = Schedule::default();
894 schedule.add_systems(
895 (
896 mark_dirty_trees,
897 sync_simple_transforms,
898 propagate_parent_transforms,
899 )
900 .chain(),
901 );
902 world.insert_resource(StaticTransformOptimizations::default());
903904// Root entity
905world.spawn(Transform::from_xyz(1.0, 0.0, 0.0));
906907let mut children = Vec::new();
908 world
909 .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
910 .with_children(|parent| {
911 children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.)).id());
912 children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.)).id());
913 });
914 schedule.run(&mut world);
915916assert_eq!(
917*world.get::<GlobalTransform>(children[0]).unwrap(),
918 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
919 );
920921assert_eq!(
922*world.get::<GlobalTransform>(children[1]).unwrap(),
923 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
924 );
925 }
926927#[test]
928fn did_propagate_command_buffer() {
929let mut world = World::default();
930931let mut schedule = Schedule::default();
932 schedule.add_systems(
933 (
934 mark_dirty_trees,
935 sync_simple_transforms,
936 propagate_parent_transforms,
937 )
938 .chain(),
939 );
940 world.insert_resource(StaticTransformOptimizations::default());
941942// Root entity
943let mut queue = CommandQueue::default();
944let mut commands = Commands::new(&mut queue, &world);
945let mut children = Vec::new();
946 commands
947 .spawn(Transform::from_xyz(1.0, 0.0, 0.0))
948 .with_children(|parent| {
949 children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
950 children.push(parent.spawn(Transform::from_xyz(0.0, 0.0, 3.0)).id());
951 });
952 queue.apply(&mut world);
953 schedule.run(&mut world);
954955assert_eq!(
956*world.get::<GlobalTransform>(children[0]).unwrap(),
957 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 2.0, 0.0)
958 );
959960assert_eq!(
961*world.get::<GlobalTransform>(children[1]).unwrap(),
962 GlobalTransform::from_xyz(1.0, 0.0, 0.0) * Transform::from_xyz(0.0, 0.0, 3.0)
963 );
964 }
965966#[test]
967fn correct_children() {
968 ComputeTaskPool::get_or_init(TaskPool::default);
969let mut world = World::default();
970971let mut schedule = Schedule::default();
972 schedule.add_systems(
973 (
974 mark_dirty_trees,
975 sync_simple_transforms,
976 propagate_parent_transforms,
977 )
978 .chain(),
979 );
980 world.insert_resource(StaticTransformOptimizations::default());
981982// Add parent entities
983let mut children = Vec::new();
984let parent = {
985let mut command_queue = CommandQueue::default();
986let mut commands = Commands::new(&mut command_queue, &world);
987let parent = commands.spawn(Transform::from_xyz(1.0, 0.0, 0.0)).id();
988 commands.entity(parent).with_children(|parent| {
989 children.push(parent.spawn(Transform::from_xyz(0.0, 2.0, 0.0)).id());
990 children.push(parent.spawn(Transform::from_xyz(0.0, 3.0, 0.0)).id());
991 });
992 command_queue.apply(&mut world);
993 schedule.run(&mut world);
994 parent
995 };
996997assert_eq!(
998 world
999 .get::<Children>(parent)
1000 .unwrap()
1001 .iter()
1002 .collect::<Vec<_>>(),
1003 children,
1004 );
10051006// Parent `e1` to `e2`.
1007{
1008let mut command_queue = CommandQueue::default();
1009let mut commands = Commands::new(&mut command_queue, &world);
1010 commands.entity(children[1]).add_child(children[0]);
1011 command_queue.apply(&mut world);
1012 schedule.run(&mut world);
1013 }
10141015assert_eq!(
1016 world
1017 .get::<Children>(parent)
1018 .unwrap()
1019 .iter()
1020 .collect::<Vec<_>>(),
1021vec![children[1]]
1022 );
10231024assert_eq!(
1025 world
1026 .get::<Children>(children[1])
1027 .unwrap()
1028 .iter()
1029 .collect::<Vec<_>>(),
1030vec![children[0]]
1031 );
10321033assert!(world.despawn(children[0]));
10341035 schedule.run(&mut world);
10361037assert_eq!(
1038 world
1039 .get::<Children>(parent)
1040 .unwrap()
1041 .iter()
1042 .collect::<Vec<_>>(),
1043vec![children[1]]
1044 );
1045 }
10461047#[test]
1048fn correct_transforms_when_no_children() {
1049let mut app = App::new();
1050 ComputeTaskPool::get_or_init(TaskPool::default);
10511052 app.add_systems(
1053 Update,
1054 (
1055 mark_dirty_trees,
1056 sync_simple_transforms,
1057 propagate_parent_transforms,
1058 )
1059 .chain(),
1060 )
1061 .insert_resource(StaticTransformOptimizations::default());
10621063let translation = vec3(1.0, 0.0, 0.0);
10641065// These will be overwritten.
1066let mut child = Entity::from_raw_u32(0).unwrap();
1067let mut grandchild = Entity::from_raw_u32(1).unwrap();
1068let parent = app
1069 .world_mut()
1070 .spawn(Transform::from_translation(translation))
1071 .with_children(|builder| {
1072 child = builder
1073 .spawn(Transform::IDENTITY)
1074 .with_children(|builder| {
1075 grandchild = builder.spawn(Transform::IDENTITY).id();
1076 })
1077 .id();
1078 })
1079 .id();
10801081 app.update();
10821083// check the `Children` structure is spawned
1084assert_eq!(&**app.world().get::<Children>(parent).unwrap(), &[child]);
1085assert_eq!(
1086&**app.world().get::<Children>(child).unwrap(),
1087&[grandchild]
1088 );
1089// Note that at this point, the `GlobalTransform`s will not have updated yet, due to
1090 // `Commands` delay
1091app.update();
10921093let mut state = app.world_mut().query::<&GlobalTransform>();
1094for global in state.iter(app.world()) {
1095assert_eq!(global, &GlobalTransform::from_translation(translation));
1096 }
1097 }
10981099#[test]
1100 #[should_panic]
1101fn panic_when_hierarchy_cycle() {
1102 ComputeTaskPool::get_or_init(TaskPool::default);
1103// We cannot directly edit ChildOf and Children, so we use a temp world to break the
1104 // hierarchy's invariants.
1105let mut temp = World::new();
1106let mut app = App::new();
11071108 app.add_systems(
1109 Update,
1110// It is unsound for this unsafe system to encounter a cycle without panicking. This
1111 // requirement only applies to systems with unsafe parallel traversal that result in
1112 // aliased mutability during a cycle.
1113propagate_parent_transforms,
1114 );
11151116fn setup_world(world: &mut World) -> (Entity, Entity) {
1117let mut grandchild = Entity::from_raw_u32(0).unwrap();
1118let child = world
1119 .spawn(Transform::IDENTITY)
1120 .with_children(|builder| {
1121 grandchild = builder.spawn(Transform::IDENTITY).id();
1122 })
1123 .id();
1124 (child, grandchild)
1125 }
11261127let (temp_child, temp_grandchild) = setup_world(&mut temp);
1128let (child, grandchild) = setup_world(app.world_mut());
11291130assert_eq!(temp_child, child);
1131assert_eq!(temp_grandchild, grandchild);
11321133 app.world_mut()
1134 .spawn(Transform::IDENTITY)
1135 .add_children(&[child]);
11361137let mut child_entity = app.world_mut().entity_mut(child);
11381139let mut grandchild_entity = temp.entity_mut(grandchild);
11401141#[expect(
1142 unsafe_code,
1143 reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen"
1144)]
1145// SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that
1146 // cannot happen
1147let mut a = unsafe { child_entity.get_mut_assume_mutable::<ChildOf>().unwrap() };
11481149#[expect(
1150 unsafe_code,
1151 reason = "ChildOf is not mutable but this is for a test to produce a scenario that cannot happen"
1152)]
1153// SAFETY: ChildOf is not mutable but this is for a test to produce a scenario that
1154 // cannot happen
1155let mut b = unsafe {
1156 grandchild_entity
1157 .get_mut_assume_mutable::<ChildOf>()
1158 .unwrap()
1159 };
11601161 core::mem::swap(a.as_mut(), b.as_mut());
11621163 app.update();
1164 }
11651166#[test]
1167fn global_transform_should_not_be_overwritten_after_reparenting() {
1168let translation = Vec3::ONE;
1169let mut world = World::new();
11701171// Create transform propagation schedule
1172let mut schedule = Schedule::default();
1173 schedule.add_systems(
1174 (
1175 mark_dirty_trees,
1176 propagate_parent_transforms,
1177 sync_simple_transforms,
1178 )
1179 .chain(),
1180 );
1181 world.insert_resource(StaticTransformOptimizations::default());
11821183// Spawn a `Transform` entity with a local translation of `Vec3::ONE`
1184let mut spawn_transform_bundle =
1185 || world.spawn(Transform::from_translation(translation)).id();
11861187// Spawn parent and child with identical transform bundles
1188let parent = spawn_transform_bundle();
1189let child = spawn_transform_bundle();
1190 world.entity_mut(parent).add_child(child);
11911192// Run schedule to propagate transforms
1193schedule.run(&mut world);
11941195// Child should be positioned relative to its parent
1196let parent_global_transform = *world.entity(parent).get::<GlobalTransform>().unwrap();
1197let child_global_transform = *world.entity(child).get::<GlobalTransform>().unwrap();
1198assert!(parent_global_transform
1199 .translation()
1200 .abs_diff_eq(translation, 0.1));
1201assert!(child_global_transform
1202 .translation()
1203 .abs_diff_eq(2. * translation, 0.1));
12041205// Reparent child
1206world.entity_mut(child).remove::<ChildOf>();
1207 world.entity_mut(parent).add_child(child);
12081209// Run schedule to propagate transforms
1210schedule.run(&mut world);
12111212// Translations should be unchanged after update
1213assert_eq!(
1214 parent_global_transform,
1215*world.entity(parent).get::<GlobalTransform>().unwrap()
1216 );
1217assert_eq!(
1218 child_global_transform,
1219*world.entity(child).get::<GlobalTransform>().unwrap()
1220 );
1221 }
1222}