use core::ptr::NonNull;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use amity::flip_queue::FlipQueue;
use hashbrown::HashSet;
use crate::{action::ActionBuffer, system::ActionBufferQueue, world::World, Access};
use super::{ScheduledSystem, Scheduler};
#[derive(Clone, Copy)]
struct NonNullWorld {
ptr: NonNull<World>,
}
unsafe impl Send for NonNullWorld {}
pub trait ScopedExecutor<'scope> {
fn spawn<F>(&self, f: F)
where
F: FnOnce(&Self) + Send + 'scope;
}
#[cfg(feature = "rayon-scheduler")]
mod rayon_scope {
use super::ScopedExecutor;
impl<'scope> ScopedExecutor<'scope> for rayon::Scope<'scope> {
fn spawn<F>(&self, f: F)
where
F: FnOnce(&Self) + Send + 'scope,
{
self.spawn(f);
}
}
}
mod std_thread_scope {
use std::thread;
use super::ScopedExecutor;
impl<'scope> ScopedExecutor<'scope> for &'scope thread::Scope<'scope, '_> {
fn spawn<F>(&self, f: F)
where
F: FnOnce(&Self) + Send + 'scope,
{
let scope = *self;
scope.spawn(move || {
f(&scope);
});
}
}
}
pub(super) struct ThreadedSystem {
wait: AtomicUsize,
dependents: Vec<usize>,
dependencies: usize,
is_local: bool,
}
impl ThreadedSystem {
pub(super) fn new(is_local: bool) -> Self {
ThreadedSystem {
is_local,
wait: AtomicUsize::new(0),
dependents: Vec::new(),
dependencies: 0,
}
}
}
struct Queues<'scope> {
local_tasks: FlipQueue<Task<'scope>>,
actions: FlipQueue<ActionBuffer>,
}
impl ActionBufferQueue for &Queues<'_> {
#[inline]
fn get<'a>(&mut self) -> ActionBuffer {
if let Some(last) = self.actions.try_pop_sync() {
last
} else {
ActionBuffer::new()
}
}
#[inline]
fn flush(&mut self, buffer: ActionBuffer) {
self.actions.push_sync(buffer);
}
}
struct Task<'scope> {
system_idx: usize,
systems: &'scope [ScheduledSystem],
world: NonNullWorld,
queues: Arc<Queues<'scope>>,
}
impl<'scope> Task<'scope> {
fn run(self, executor: &impl ScopedExecutor<'scope>, is_local_run: bool) {
let Task {
system_idx,
systems,
world,
queues,
} = self;
let mut dependents = &systems[system_idx].threaded.dependents[..];
let mut unroll = Some(unsafe { &mut **systems[system_idx].system.get() });
while let Some(system) = unroll.take() {
unsafe {
system.run_unchecked(world.ptr, &mut &*queues);
}
for &dependent_idx in dependents {
let old = systems[dependent_idx]
.threaded
.wait
.fetch_sub(1, Ordering::AcqRel);
if old == 0 {
let is_local = systems[dependent_idx].threaded.is_local;
if is_local != is_local_run && unroll.is_none() {
unroll = Some(unsafe {
&mut **systems[dependent_idx].system.inner.get()
});
dependents = &systems[dependent_idx].threaded.dependents[..];
} else {
let task = Task {
system_idx: dependent_idx,
systems,
world,
queues: queues.clone(),
};
if is_local {
queues.local_tasks.push_sync(task);
} else {
executor.spawn(move |executor| task.run(executor, false));
}
}
}
}
}
}
}
impl Scheduler {
pub fn run_threaded(&mut self, world: &mut World) {
std::thread::scope(|scope| self.run_with(world, &scope));
}
#[cfg(feature = "rayon-scheduler")]
pub fn run_rayon(&mut self, world: &mut World) {
rayon::in_place_scope(|scope| self.run_with(world, scope));
}
pub fn run_with<'scope>(
&'scope mut self,
world: &'scope mut World,
executor: &impl ScopedExecutor<'scope>,
) {
use crate::action::ActionBufferSliceExt;
self.reschedule(world);
for system in &mut self.systems {
*system.threaded.wait.get_mut() = system.threaded.dependencies;
}
let task_queue: FlipQueue<Task<'scope>> = FlipQueue::with_capacity(256);
let mut action_queue = FlipQueue::with_capacity(256);
for buffer in self.action_buffers.drain() {
action_queue.push(buffer);
}
let mut world_ptr = NonNull::from(world);
let queues = Arc::new(Queues {
local_tasks: task_queue,
actions: action_queue,
});
for (idx, system) in self.systems.iter().enumerate() {
let old = system.threaded.wait.fetch_sub(1, Ordering::Acquire);
if old == 0 {
let is_local = system.threaded.is_local;
let task = Task::<'scope> {
system_idx: idx,
world: NonNullWorld { ptr: world_ptr },
systems: &self.systems,
queues: queues.clone(),
};
if is_local {
queues.local_tasks.push_sync(task);
} else {
executor.spawn(move |executor| task.run(executor, false));
}
}
}
loop {
while let Some(task) = queues.local_tasks.pop_sync() {
task.run(executor, true);
}
if Arc::strong_count(&queues) == 1 {
break;
}
std::thread::yield_now();
}
while let Some(buffer) = queues.actions.pop_sync() {
self.action_buffers.push(buffer);
}
let (front, back) = self.action_buffers.as_mut_slices();
unsafe {
front.execute_all(world_ptr.as_mut());
back.execute_all(world_ptr.as_mut());
}
}
fn reschedule(&mut self, world: &World) {
if self.schedule_cache_id == Some(world.archetype_set_id()) {
return;
}
for i in 0..self.systems.len() {
let a = &mut self.systems[i];
a.threaded.dependents.clear();
a.threaded.dependencies = 0;
let mut deps = HashSet::new();
'j: for j in (0..i).rev() {
let a = &self.systems[i];
let b = &self.systems[j];
for &d in &b.threaded.dependents {
if deps.contains(&d) {
deps.insert(j);
continue 'j;
}
}
let system_a = unsafe {
&*a.system.get()
};
let system_b = unsafe {
&*b.system.get()
};
if conflicts(system_a.world_access(), system_b.world_access()) {
self.systems[j].threaded.dependents.push(i);
self.systems[i].threaded.dependencies += 1;
deps.insert(j);
continue 'j;
}
for id in world.resource_types() {
if conflicts(
system_a.resource_type_access(id),
system_b.resource_type_access(id),
) {
self.systems[j].threaded.dependents.push(i);
self.systems[i].threaded.dependencies += 1;
deps.insert(j);
continue 'j;
}
}
for archetype in world.archetypes() {
let system_a = unsafe {
&*a.system.get()
};
let system_b = unsafe {
&*b.system.get()
};
if !system_a.visit_archetype(archetype) || !system_b.visit_archetype(archetype)
{
continue;
}
for info in archetype.infos() {
if conflicts(
system_a.component_access(archetype, info),
system_b.component_access(archetype, info),
) {
self.systems[j].threaded.dependents.push(i);
self.systems[i].threaded.dependencies += 1;
deps.insert(j);
continue 'j;
}
}
}
}
}
}
}
fn conflicts(lhs: Option<Access>, rhs: Option<Access>) -> bool {
matches!(
(lhs, rhs),
(Some(Access::Write), Some(_)) | (Some(_), Some(Access::Write))
)
}