use core::{mem, ops::Deref};
use alloc::{collections::BTreeMap, string::String, vec::Vec};
use anyhow::Context;
use itertools::Itertools;
use crate::{
system::{access_info, AccessInfo, IntoInput, SystemContext},
util::Verbatim,
BoxedSystem, CommandBuffer, System, World,
};
fn flush_system() -> BoxedSystem {
System::builder()
.with_name("flush")
.with_world_mut()
.with_cmd_mut()
.build(|world: &mut World, cmd: &mut CommandBuffer| {
profile_scope!("flush");
cmd.apply(world)
.context("Failed to flush commandbuffer in schedule\n")
})
.boxed()
}
#[derive(Default, Debug)]
pub struct ScheduleBuilder {
systems: Vec<BoxedSystem>,
}
impl ScheduleBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn with_system(&mut self, system: impl Into<BoxedSystem>) -> &mut Self {
self.systems.push(system.into());
self
}
pub fn flush(&mut self) -> &mut Self {
self.with_system(flush_system())
}
pub fn build(&mut self) -> Schedule {
Schedule::from_systems(mem::take(&mut self.systems))
}
}
#[derive(Debug, Clone)]
pub struct SystemInfo {
name: String,
desc: Verbatim,
access: AccessInfo,
}
impl SystemInfo {
pub fn desc(&self) -> &str {
&self.desc.0
}
pub fn name(&self) -> &str {
self.name.as_ref()
}
pub fn access(&self) -> &AccessInfo {
&self.access
}
}
#[derive(Default)]
pub struct Schedule {
systems: Vec<Vec<BoxedSystem>>,
cmd: CommandBuffer,
archetype_gen: u32,
}
#[derive(Debug, Clone)]
pub struct BatchInfos(Vec<BatchInfo>);
impl BatchInfos {
pub fn to_names(&self) -> Vec<Vec<String>> {
self.iter()
.map(|v| v.iter().map(|v| v.name().into()).collect_vec())
.collect_vec()
}
}
impl Deref for BatchInfos {
type Target = [BatchInfo];
fn deref(&self) -> &Self::Target {
&self.0
}
}
#[derive(Debug, Clone)]
pub struct BatchInfo(Vec<SystemInfo>);
impl Deref for BatchInfo {
type Target = [SystemInfo];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl core::fmt::Debug for Schedule {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Schedule")
.field("systems", &self.systems)
.field("archetype_gen", &self.archetype_gen)
.finish()
}
}
impl FromIterator<BoxedSystem> for Schedule {
fn from_iter<I: IntoIterator<Item = BoxedSystem>>(iter: I) -> Self {
Self::from_systems(iter.into_iter().collect_vec())
}
}
impl<U> From<U> for Schedule
where
U: IntoIterator<Item = BoxedSystem>,
{
fn from(v: U) -> Self {
Self::from_systems(v.into_iter().collect_vec())
}
}
impl Schedule {
pub fn new() -> Self {
Default::default()
}
pub fn builder() -> ScheduleBuilder {
ScheduleBuilder::default()
}
pub fn execute_seq(&mut self, world: &mut World) -> anyhow::Result<()> {
self.execute_seq_with(world, &mut ())
}
#[cfg(feature = "rayon")]
pub fn execute_par(&mut self, world: &mut World) -> anyhow::Result<()> {
self.execute_par_with(world, &mut ())
}
pub fn from_systems(systems: impl Into<Vec<BoxedSystem>>) -> Self {
Self {
systems: alloc::vec![systems.into()],
archetype_gen: 0,
cmd: CommandBuffer::new(),
}
}
pub fn append(&mut self, other: Self) {
self.archetype_gen = 0;
self.systems.extend(other.systems)
}
pub fn with_system(mut self, system: impl Into<BoxedSystem>) -> Self {
self.archetype_gen = 0;
let v = match self.systems.first_mut() {
Some(v) => v,
None => {
self.systems.push(Default::default());
&mut self.systems[0]
}
};
v.push(system.into());
self
}
pub fn flush(self) -> Self {
self.with_system(flush_system())
}
pub fn batch_info(&mut self, world: &World) -> BatchInfos {
self.systems = Self::build_dependencies(mem::take(&mut self.systems), world);
let batches = self
.systems
.iter()
.map(|batch| {
let systems = batch
.iter()
.map(|system| {
let mut access = Vec::new();
system.access(world, &mut access);
SystemInfo {
name: system.name().into(),
desc: Verbatim(alloc::format!("{system:#?}")),
access: access_info(&access, world),
}
})
.collect_vec();
BatchInfo(systems)
})
.collect_vec();
BatchInfos(batches)
}
pub fn execute_seq_with<'a>(
&'a mut self,
world: &'a mut World,
input: impl IntoInput<'a>,
) -> anyhow::Result<()> {
profile_function!();
let input = input.into_input();
let ctx = SystemContext::new(world, &mut self.cmd, &input);
#[cfg(feature = "tracing")]
let _span = tracing::info_span!("execute_seq").entered();
for system in self.systems.iter_mut().flatten() {
system.execute(&ctx)?
}
self.cmd
.apply(world)
.context("Failed to apply commandbuffer")
}
#[cfg(feature = "rayon")]
pub fn execute_par_with<'a>(
&'a mut self,
world: &'a mut World,
input: impl IntoInput<'a>,
) -> anyhow::Result<()> {
profile_function!();
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
#[cfg(feature = "tracing")]
let _span = tracing::info_span!("execute_par").entered();
let w_gen = world.archetype_gen();
if self.archetype_gen != w_gen {
self.archetype_gen = w_gen;
self.systems = Self::build_dependencies(mem::take(&mut self.systems), world);
}
let input = input.into_input();
let mut ctx = SystemContext::new(world, &mut self.cmd, &input);
let mut batches = self.systems.iter_mut();
for batch in &mut batches {
batch
.par_iter_mut()
.try_for_each(|system| system.execute(&ctx))?;
if self.archetype_gen != ctx.world.get_mut().archetype_gen() {
return Self::bail_seq(batches, &mut ctx);
}
}
self.cmd
.apply(world)
.context("Failed to apply commandbuffer")
}
#[cfg(feature = "rayon")]
fn bail_seq(
batches: core::slice::IterMut<Vec<BoxedSystem>>,
ctx: &mut SystemContext<'_, '_, '_>,
) -> anyhow::Result<()> {
for system in batches.flatten() {
system.execute(ctx)?;
}
ctx.cmd
.get_mut()
.apply(ctx.world.get_mut())
.context("Failed to apply commandbuffer")
}
fn build_dependencies(systems: Vec<Vec<BoxedSystem>>, world: &World) -> Vec<Vec<BoxedSystem>> {
profile_function!();
let accesses = systems
.iter()
.flatten()
.map(|v| {
let mut access = Vec::new();
v.access(world, &mut access);
access
})
.collect_vec();
let mut deps = BTreeMap::new();
for (dst_idx, dst) in accesses.iter().enumerate() {
let accesses = &accesses;
let dst_deps =
dst.iter()
.flat_map(|dst_access| {
accesses.iter().take(dst_idx).enumerate().filter_map(
move |(src_idx, src)| {
if src.iter().any(move |v| !v.is_compatible_with(dst_access)) {
Some(src_idx)
} else {
None
}
},
)
})
.dedup()
.collect_vec();
deps.insert(dst_idx, dst_deps);
}
topo_sort(systems, &deps)
}
}
#[derive(Debug, Clone, Copy)]
enum VisitedState {
Pending,
Visited(u32),
}
fn topo_sort<T>(items: Vec<Vec<T>>, deps: &BTreeMap<usize, Vec<usize>>) -> Vec<Vec<T>> {
let mut visited = BTreeMap::new();
let mut result = Vec::new();
fn inner(
idx: usize,
deps: &BTreeMap<usize, Vec<usize>>,
visited: &mut BTreeMap<usize, VisitedState>,
result: &mut Vec<usize>,
) -> u32 {
match visited.get_mut(&idx) {
Some(VisitedState::Pending) => unreachable!("cyclic dependency"),
Some(VisitedState::Visited(d)) => *d,
None => {
visited.insert(idx, VisitedState::Pending);
let depth = deps
.get(&idx)
.into_iter()
.flatten()
.map(|&dep| inner(dep, deps, visited, result))
.max()
.map(|v| v + 1)
.unwrap_or(0);
visited.insert(idx, VisitedState::Visited(depth));
result.push(idx);
depth
}
}
}
let mut batches = Vec::new();
for (idx, system) in items.into_iter().flatten().enumerate() {
let depth = inner(idx, deps, &mut visited, &mut result) as usize;
if depth >= batches.len() {
batches.resize_with(depth + 1, Vec::default);
}
batches[depth].push(system);
}
batches
}
#[cfg(test)]
mod test {
use alloc::vec;
use super::topo_sort;
#[test]
fn test_topo_sort() {
let items = vec![vec!["a", "b", "c", "d", "e", "f"]];
let deps = [(0, vec![1, 2]), (1, vec![2, 3]), (4, vec![0, 2])].into();
let sorted = topo_sort(items, &deps);
assert_eq!(
sorted,
[vec!["c", "d", "f"], vec!["b"], vec!["a"], vec!["e"]]
)
}
}