#![deny(missing_docs)]
#![forbid(unsafe_code)]
use bevy_ecs::schedule::{IntoSystemDescriptor, SystemDescriptor, SystemLabel, SystemSet};
use bevy_utils::HashMap;
use std::{
cell::RefCell,
fmt::Debug,
rc::Rc,
sync::atomic::{AtomicU32, Ordering},
};
static NEXT_GRAPH_ID: AtomicU32 = AtomicU32::new(0);
#[derive(Clone)]
pub struct SystemGraph {
id: u32,
nodes: Rc<RefCell<HashMap<NodeId, SystemDescriptor>>>,
}
impl Default for SystemGraph {
fn default() -> Self {
Self {
id: NEXT_GRAPH_ID.fetch_add(1, Ordering::Relaxed),
nodes: Default::default(),
}
}
}
impl SystemGraph {
pub fn new() -> Self {
Self::default()
}
pub fn root<Params>(&self, system: impl IntoSystemDescriptor<Params>) -> SystemGraphNode {
self.create_node(system.into_descriptor())
}
pub fn is_same_graph(&self, other: &Self) -> bool {
self.id == other.id
}
fn create_node(&self, system: SystemDescriptor) -> SystemGraphNode {
let mut nodes = self.nodes.borrow_mut();
assert!(
nodes.len() <= u32::MAX as usize,
"Cannot add more than {} systems to a SystemGraph",
u32::MAX
);
let id = NodeId(self.id, nodes.len() as u32);
nodes.insert(id, system.label(id));
SystemGraphNode {
id,
graph: self.clone(),
}
}
fn add_dependency(&self, origin: NodeId, dependent: NodeId) {
let mut nodes = self.nodes.borrow_mut();
if let Some(system) = nodes.remove(&dependent) {
nodes.insert(dependent, system.after(origin));
} else {
panic!(
"Attempted to add dependency for {:?}, which doesn't exist.",
dependent
);
}
}
}
impl From<SystemGraph> for SystemSet {
fn from(graph: SystemGraph) -> Self {
let mut system_set = SystemSet::new();
for (_, system) in graph.nodes.borrow_mut().drain() {
system_set = system_set.with_system(system);
}
system_set
}
}
#[derive(Clone)]
pub struct SystemGraphNode {
id: NodeId,
graph: SystemGraph,
}
impl SystemGraphNode {
#[inline]
pub fn graph(&self) -> SystemGraph {
self.graph.clone()
}
pub fn then<Param>(&self, next: impl IntoSystemDescriptor<Param>) -> SystemGraphNode {
let node = self.graph.create_node(next.into_descriptor());
self.graph.add_dependency(self.id, node.id);
node
}
#[inline]
pub fn fork<Params, T: SystemGroup<Params>>(&self, system_group: T) -> T::Output {
system_group.fork_from(self)
}
}
pub trait SystemGroup<Params> {
type Output;
fn fork_from(self, src: &SystemGraphNode) -> Self::Output;
fn join_from<J: SystemJoin>(self, src: &J) -> Self::Output;
}
pub trait SystemJoin: Sized {
fn join<Param>(&self, next: impl IntoSystemDescriptor<Param>) -> SystemGraphNode;
#[inline]
fn join_all<Params, G: SystemGroup<Params>>(&self, next: G) -> G::Output {
next.join_from(self)
}
}
impl<Params, T: IntoSystemDescriptor<Params>> SystemGroup<Params> for Vec<T> {
type Output = Vec<SystemGraphNode>;
fn fork_from(self, src: &SystemGraphNode) -> Self::Output {
self.into_iter().map(|sys| src.then(sys)).collect()
}
fn join_from<J: SystemJoin>(self, src: &J) -> Self::Output {
self.into_iter().map(|sys| src.join(sys)).collect()
}
}
impl SystemJoin for Vec<SystemGraphNode> {
fn join<Param>(&self, next: impl IntoSystemDescriptor<Param>) -> SystemGraphNode {
let mut nodes = self.iter().peekable();
let output = nodes
.peek()
.map(|node| node.graph.create_node(next.into_descriptor()))
.expect("Attempted to join a collection of zero nodes.");
for node in nodes {
assert!(
output.graph.is_same_graph(&node.graph),
"Joined graph nodes should be from the same graph."
);
output.graph.add_dependency(node.id, output.id);
}
output
}
}
macro_rules! ignore_first {
($_first:ident, $second:ty) => {
$second
};
}
macro_rules! impl_system_tuple {
($($param: ident, $sys: ident),*) => {
impl<$($param, $sys: IntoSystemDescriptor<$param>),*> SystemGroup<($($param,)*)> for ($($sys,)*) {
type Output = ($(ignore_first!($sys, SystemGraphNode),)*);
#[inline]
#[allow(non_snake_case)]
fn fork_from(self, src: &SystemGraphNode) -> Self::Output {
let ($($sys,)*) = self;
($(src.then($sys),)*)
}
#[inline]
#[allow(non_snake_case)]
fn join_from<J: SystemJoin>(self, src: &J) -> Self::Output {
let ($($param,)*) = self;
($(src.join($param),)*)
}
}
impl SystemJoin for ($(ignore_first!($param, SystemGraphNode),)*) {
#[inline]
#[allow(non_snake_case)]
fn join<Param>(&self, next: impl IntoSystemDescriptor<Param>) -> SystemGraphNode {
let output = self.0.graph.create_node(next.into_descriptor());
let ($($param,)*) = self;
$(
assert!(output.graph.is_same_graph(&$param.graph),
"Joined graph nodes must be from the same graph.");
output.graph.add_dependency($param.id, output.id);
)*
output
}
}
};
}
impl_system_tuple!(P1, T1, P2, T2);
impl_system_tuple!(P1, T1, P2, T2, P3, T3);
impl_system_tuple!(P1, T1, P2, T2, P3, T3, P4, T4);
impl_system_tuple!(P1, T1, P2, T2, P3, T3, P4, T4, P5, T5);
impl_system_tuple!(P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6);
impl_system_tuple!(P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7);
impl_system_tuple!(P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8);
impl_system_tuple!(P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9);
impl_system_tuple!(
P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10
);
impl_system_tuple!(
P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, T11
);
impl_system_tuple!(
P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, T11,
P12, T12
);
impl_system_tuple!(
P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, T11,
P12, T12, P13, T13
);
impl_system_tuple!(
P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, T11,
P12, T12, P13, T13, P14, T14
);
impl_system_tuple!(
P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, T11,
P12, T12, P13, T13, P14, T14, P15, T15
);
impl_system_tuple!(
P1, T1, P2, T2, P3, T3, P4, T4, P5, T5, P6, T6, P7, T7, P8, T8, P9, T9, P10, T10, P11, T11,
P12, T12, P13, T13, P14, T14, P15, T15, P16, T16
);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct NodeId(u32, u32);
impl SystemLabel for NodeId {
fn as_str(&self) -> &'static str {
let label = format!("NodeId({}, {})", self.0, self.1);
Box::leak(label.into_boxed_str())
}
}
#[cfg(test)]
mod test {
}