use bevy_ecs::component::Mutable;
use bevy_ecs::entity::*;
use bevy_ecs::prelude::*;
use bevy_ecs::system::lifetimeless::*;
use bevy_tasks::*;
use bevy_utils::*;
use std::ops::Mul;
use std::sync::atomic::*;
use std::sync::mpsc::{Receiver, Sender};
use std::sync::*;
pub fn sync_simple_transforms<L, G, M>(
mut query: ParamSet<(
Query<
(&L, &mut G),
(
Or<(Changed<L>, Added<G>)>,
Without<ChildOf>,
Without<Children>,
),
>,
Query<(Ref<L>, &mut G), (Without<ChildOf>, Without<Children>)>,
)>,
mut orphaned: RemovedComponents<ChildOf>,
) where
L: Component + Copy + Into<G>,
G: Component<Mutability = Mutable> + Copy + PartialEq + Mul<L, Output = G>,
M: Component<Mutability = Mutable>,
{
query
.p0()
.par_iter_mut()
.for_each(|(transform, mut global_transform)| {
*global_transform = (*transform).into();
});
let mut query = query.p1();
let mut iter = query.iter_many_mut(orphaned.read());
while let Some((transform, mut global_transform)) = iter.fetch_next() {
if !transform.is_changed() && !global_transform.is_added() {
*global_transform = (*transform).into();
}
}
}
pub fn mark_dirty_trees<L, G, M>(
changed_transforms: Query<Entity, Or<(Changed<L>, Changed<ChildOf>, Added<G>)>>,
mut orphaned: RemovedComponents<ChildOf>,
mut transforms: Query<(Option<&ChildOf>, &mut M)>,
) where
L: Component + Copy + Into<G>,
G: Component<Mutability = Mutable> + Copy + PartialEq + Mul<L, Output = G>,
M: Component<Mutability = Mutable>,
{
for entity in changed_transforms.iter().chain(orphaned.read()) {
let mut next = entity;
while let Ok((child_of, mut tree)) = transforms.get_mut(next) {
if tree.is_changed() && !tree.is_added() {
break;
}
tree.set_changed();
if let Some(parent) = child_of.map(ChildOf::parent) {
next = parent;
} else {
break;
};
}
}
}
pub fn propagate_parent_transforms<L, G, M>(
mut queue: Local<WorkQueue>,
mut roots: Query<(Entity, Ref<L>, &mut G, &Children), (Without<ChildOf>, Changed<M>)>,
nodes: NodeQuery<L, G, M>,
) where
L: Component + Copy + Into<G>,
G: Component<Mutability = Mutable> + Copy + PartialEq + Mul<L, Output = G>,
M: Component<Mutability = Mutable>,
{
roots.par_iter_mut().for_each_init(
|| queue.local_queue.borrow_local_mut(),
|outbox, (parent, transform, mut parent_transform, children)| {
*parent_transform = (*transform).into();
#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
unsafe {
propagate_descendants_unchecked::<L, G, M>(
parent,
parent_transform,
children,
&nodes,
outbox,
&queue,
1,
);
}
},
);
queue.send_batches();
if let Ok(rx) = queue.receiver.try_lock() {
if let Some(task) = rx.try_iter().next() {
queue.sender.send(task).ok();
} else {
return; }
}
let task_pool = ComputeTaskPool::get_or_init(TaskPool::default);
task_pool.scope(|s| {
(1..task_pool.thread_num()) .for_each(|_| s.spawn(async { propagation_worker::<L, G, M>(&queue, &nodes) }));
propagation_worker::<L, G, M>(&queue, &nodes);
});
}
#[inline]
fn propagation_worker<L, G, M>(queue: &WorkQueue, nodes: &NodeQuery<L, G, M>)
where
L: Component + Copy + Into<G>,
G: Component<Mutability = Mutable> + Copy + PartialEq + Mul<L, Output = G>,
M: Component<Mutability = Mutable>,
{
let mut outbox = queue.local_queue.borrow_local_mut();
loop {
let Ok(rx) = queue.receiver.try_lock() else {
core::hint::spin_loop(); continue;
};
let Some(mut tasks) = rx.try_iter().next() else {
if queue.busy_threads.load(Ordering::Relaxed) == 0 {
break; }
continue; };
if tasks.is_empty() {
continue; }
while tasks.len() < WorkQueue::CHUNK_SIZE / 2 {
let Some(mut extra_task) = rx.try_iter().next() else {
break;
};
tasks.append(&mut extra_task);
}
queue.busy_threads.fetch_add(1, Ordering::Relaxed);
drop(rx);
for parent in tasks.drain(..) {
#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
unsafe {
let (_, (_, p_global_transform, _), (p_children, _)) =
nodes.get_unchecked(parent).unwrap();
propagate_descendants_unchecked::<L, G, M>(
parent,
p_global_transform,
p_children.unwrap(), nodes,
&mut outbox,
queue,
10_000,
);
}
}
WorkQueue::send_batches_with(&queue.sender, &mut outbox);
queue.busy_threads.fetch_add(-1, Ordering::Relaxed);
}
}
#[inline]
#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
unsafe fn propagate_descendants_unchecked<L, G, M>(
parent: Entity,
p_global_transform: Mut<G>,
p_children: &Children,
nodes: &NodeQuery<L, G, M>,
outbox: &mut Vec<Entity>,
queue: &WorkQueue,
max_depth: usize,
) where
L: Component + Copy + Into<G>,
G: Component<Mutability = Mutable> + Copy + PartialEq + Mul<L, Output = G>,
M: Component<Mutability = Mutable>,
{
let (mut parent, mut p_global_transform, mut p_children) =
(parent, p_global_transform, p_children);
for depth in 1..=max_depth {
#[expect(unsafe_code, reason = "Mutating disjoint entities in parallel")]
let children_iter = unsafe {
nodes.iter_many_unique_unsafe(UniqueEntityIter::from_iter_unchecked(p_children.iter()))
};
let mut last_child = None;
let new_children = children_iter.filter_map(
|(child, (transform, mut global_transform, tree), (children, child_of))| {
if !tree.is_changed() && !p_global_transform.is_changed() {
return None;
}
assert_eq!(child_of.parent(), parent);
let a = *p_global_transform;
let b = *transform;
global_transform.set_if_neq(a * b);
children.map(|children| {
last_child = Some((child, global_transform, children));
child
})
},
);
outbox.extend(new_children);
if depth >= max_depth || last_child.is_none() {
break; }
if let Some(last_child) = last_child {
(parent, p_global_transform, p_children) = last_child;
outbox.pop();
if outbox.len() >= WorkQueue::CHUNK_SIZE {
WorkQueue::send_batches_with(&queue.sender, outbox);
}
}
}
}
pub type NodeQuery<'w, 's, L, G, M> = Query<
'w,
's,
(
Entity,
(Ref<'static, L>, Mut<'static, G>, Ref<'static, M>),
(Option<Read<Children>>, Read<ChildOf>),
),
>;
pub struct WorkQueue {
busy_threads: AtomicI32,
sender: Sender<Vec<Entity>>,
receiver: Arc<Mutex<Receiver<Vec<Entity>>>>,
local_queue: Parallel<Vec<Entity>>,
}
impl Default for WorkQueue {
fn default() -> Self {
let (tx, rx) = std::sync::mpsc::channel();
Self {
busy_threads: AtomicI32::default(),
sender: tx,
receiver: Arc::new(Mutex::new(rx)),
local_queue: Default::default(),
}
}
}
impl WorkQueue {
const CHUNK_SIZE: usize = 512;
#[inline]
fn send_batches_with(sender: &Sender<Vec<Entity>>, outbox: &mut Vec<Entity>) {
for chunk in outbox
.chunks(WorkQueue::CHUNK_SIZE)
.filter(|c| !c.is_empty())
{
sender.send(chunk.to_vec()).ok();
}
outbox.clear();
}
#[inline]
fn send_batches(&mut self) {
let Self {
sender,
local_queue,
..
} = self;
local_queue
.iter_mut()
.for_each(|outbox| Self::send_batches_with(sender, outbox));
}
}