use std::{
any::TypeId,
collections::HashMap,
fmt::{Debug, Display},
ops::{Deref, DerefMut},
};
use hecs::World;
use smallvec::SmallVec;
#[cfg(feature = "parallel")]
use rayon::iter::{IntoParallelRefMutIterator, ParallelIterator};
use crate::{
borrow::{Borrows, MaybeWrite},
Access, CommandBuffer, Context, IntoData, Result, System, SystemName, Write,
};
#[derive(Default, Debug, Clone)]
pub struct BatchInfo<'a> {
batches: &'a [Batch],
}
impl<'a> Display for BatchInfo<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Batches: \n")?;
for batch in self.batches {
for system in &batch.systems {
write!(f, " - {}\n", system.name())?;
}
write!(f, "\n")?;
}
Ok(())
}
}
#[derive(Default)]
pub struct Batch {
systems: SmallVec<[DynamicSystem; 8]>,
has_flush: bool,
}
impl Debug for Batch {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut list = f.debug_list();
for system in self.systems() {
list.entry(&system.name());
}
list.finish()
}
}
impl Batch {
fn push(&mut self, system: DynamicSystem) {
self.systems.push(system)
}
pub fn systems(&self) -> &SmallVec<[DynamicSystem; 8]> {
&self.systems
}
}
impl Deref for Batch {
type Target = [DynamicSystem];
fn deref(&self) -> &Self::Target {
&self.systems
}
}
impl DerefMut for Batch {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.systems
}
}
#[doc(hidden)]
pub struct DynamicSystem {
func: Box<dyn FnMut(&Context) -> Result<()> + Send>,
name: SystemName,
borrows: Borrows,
}
#[doc(hidden)]
impl DynamicSystem {
fn new<S, Args, Ret>(mut system: S) -> Self
where
S: 'static + System<Args, Ret> + Send,
{
let borrows = S::borrows();
let name = system.name();
Self {
func: Box::new(move |context| system.execute(context)),
name,
borrows,
}
}
fn execute(&mut self, context: &Context) -> Result<()> {
(self.func)(context)
}
pub fn name(&self) -> &str {
self.name.as_ref()
}
}
pub struct Schedule {
batches: Vec<Batch>,
cmd: CommandBuffer,
}
impl Schedule {
pub fn new(batches: Vec<Batch>) -> Self {
Self {
batches,
cmd: Default::default(),
}
}
pub fn batch_info(&self) -> BatchInfo {
BatchInfo {
batches: &self.batches,
}
}
pub fn builder() -> ScheduleBuilder {
ScheduleBuilder::default()
}
pub fn execute_seq<D: IntoData<CommandBuffer>>(&mut self, data: D) -> Result<()> {
let data = unsafe { data.into_data(&mut self.cmd) };
let context = Context::new(&data);
self.batches.iter_mut().try_for_each(|batch| {
batch
.iter_mut()
.try_for_each(|system| system.execute(&context))
})
}
#[cfg(feature = "parallel")]
pub fn execute<D: IntoData<CommandBuffer> + Send + Sync>(&mut self, data: D) -> Result<()> {
let data = unsafe { data.into_data(&mut self.cmd) };
let context = Context::new(&data);
self.batches.iter_mut().try_for_each(|batch| {
batch
.par_iter_mut()
.try_for_each(|system| system.execute(&context))
})
}
pub fn cmd(&self) -> &CommandBuffer {
&self.cmd
}
pub fn cmd_mut(&mut self) -> &mut CommandBuffer {
&mut self.cmd
}
}
#[derive(Default)]
pub struct ScheduleBuilder {
batches: Vec<Batch>,
current_batch: Batch,
current_borrows: HashMap<TypeId, Access>,
}
impl ScheduleBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn add_system<Args, Ret, S>(&mut self, system: S) -> &mut Self
where
S: 'static + System<Args, Ret> + Send,
{
self.add_internal(DynamicSystem::new(system));
self
}
fn add_internal(&mut self, system: DynamicSystem) {
let borrows = &system.borrows;
if !self.check_compatible(borrows) {
self.barrier();
}
self.add_borrows(borrows);
self.current_batch.push(system);
}
pub fn append(&mut self, other: &mut ScheduleBuilder) -> &mut Self {
other.barrier();
other.batches.drain(..).for_each(|mut batch| {
batch
.systems
.drain(..)
.for_each(|system| self.add_internal(system))
});
self
}
pub fn barrier(&mut self) -> &mut Self {
let batch = std::mem::take(&mut self.current_batch);
self.batches.push(batch);
self.current_borrows.clear();
self
}
pub fn flush(&mut self) -> &mut Self {
self.current_batch.has_flush = true;
self.add_system(flush_system)
}
fn add_borrows(&mut self, borrows: &Borrows) {
self.current_borrows
.extend(borrows.into_iter().map(|val| (val.id(), val.clone())))
}
fn check_compatible(&self, borrows: &Borrows) -> bool {
for borrow in borrows {
if let Some(curr) = self.current_borrows.get(&borrow.id()) {
if curr.exclusive() || borrow.exclusive() {
return false;
}
}
}
true
}
pub fn build(&mut self) -> Schedule {
self.flush();
self.barrier();
let builder = std::mem::take(self);
Schedule::new(builder.batches)
}
}
fn flush_system(mut world: MaybeWrite<World>, mut cmd: Write<CommandBuffer>) -> Result<()> {
if let Some(world) = world.option_mut() {
cmd.execute(world);
}
Ok(())
}