use std::fmt;
use std::mem;
use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
use pragma_core::graph::{FrameGraph, FrameGraphBuilder, GraphError, TaskContext};
use pragma_core::jobs::ThreadPool;
use crate::access::Access;
use crate::event::EventType;
use crate::lend::{Lender, WorldSlot, data_name};
use crate::system::{BoxError, Env, IntoSystem, Prepared, RunSystem, SystemTicks};
use crate::{EcsError, World};
#[derive(Debug, Default)]
#[must_use]
pub struct SystemConfig {
after: Vec<String>,
before: Vec<String>,
}
impl SystemConfig {
pub fn after(mut self, system: impl Into<String>) -> Self {
self.after.push(system.into());
self
}
pub fn before(mut self, system: impl Into<String>) -> Self {
self.before.push(system.into());
self
}
}
type Failure = (String, Option<EcsError>);
struct Cell {
system: Box<dyn RunSystem>,
position: u64,
last_run: u64,
world: Option<u64>,
ok: bool,
}
type Shared = Arc<Mutex<Cell>>;
fn lock(cell: &Shared) -> MutexGuard<'_, Cell> {
cell.lock().unwrap_or_else(PoisonError::into_inner)
}
struct SystemInfo {
name: String,
access: Access,
failure: Arc<Mutex<Option<Failure>>>,
cell: Shared,
}
struct Task {
cell: Shared,
failure: Arc<Mutex<Option<Failure>>>,
}
impl Task {
fn run(&self, ctx: &TaskContext<'_>) -> Result<(), BoxError> {
let world = ctx.read::<WorldSlot>().map_err(EcsError::graph)?;
let mut cell = lock(&self.cell);
let id = world.0.id();
if cell.world != Some(id) {
cell.world = Some(id);
cell.last_run = 0;
}
let ticks = SystemTicks {
last_run: cell.last_run,
this_run: world.0.frame_base().saturating_add(cell.position),
};
cell.ok = false;
let result = cell.system.run(Env::new(ctx, &world.0, ticks));
cell.last_run = ticks.this_run;
cell.ok = result.is_ok();
if let Err(error) = &result {
let typed = error.downcast_ref::<EcsError>().cloned();
*self.failure.lock().unwrap_or_else(PoisonError::into_inner) =
Some((error.to_string(), typed));
}
result
}
}
#[must_use]
pub struct ScheduleBuilder {
graph: FrameGraphBuilder,
systems: Vec<SystemInfo>,
lenders: Vec<Lender>,
events: Vec<EventType>,
}
impl fmt::Debug for ScheduleBuilder {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ScheduleBuilder")
.field(
"systems",
&self.systems.iter().map(|s| &s.name).collect::<Vec<_>>(),
)
.finish_non_exhaustive()
}
}
impl ScheduleBuilder {
pub fn ordered(self) -> Self {
Self {
graph: self.graph.ordered(),
..self
}
}
pub fn add_system<M>(
&mut self,
name: impl Into<String>,
system: impl IntoSystem<M>,
) -> Result<&mut Self, EcsError> {
self.add_system_with(name, system, |config| config)
}
pub fn add_system_with<M>(
&mut self,
name: impl Into<String>,
system: impl IntoSystem<M>,
configure: impl FnOnce(SystemConfig) -> SystemConfig,
) -> Result<&mut Self, EcsError> {
let oom = |_| EcsError::OutOfMemory { entities: 0 };
let name = name.into();
let Prepared { access, system } = system.into_system()?;
let config = configure(SystemConfig::default());
self.systems.try_reserve(1).map_err(oom)?;
for lender in access.lenders() {
if !self.lenders.iter().any(|known| known.ty == lender.ty) {
self.lenders.try_reserve(1).map_err(oom)?;
self.lenders.push(lender);
}
}
for event in access.events() {
if !self.events.contains(&event) {
self.events.try_reserve(1).map_err(oom)?;
self.events.push(event);
}
}
let failure = Arc::new(Mutex::new(None));
let cell = Arc::new(Mutex::new(Cell {
system,
position: 0,
last_run: 0,
world: None,
ok: false,
}));
let task = Task {
cell: Arc::clone(&cell),
failure: Arc::clone(&failure),
};
self.graph.task(
name.as_str(),
|task| {
let task = access.configure(task.reads::<WorldSlot>());
let task = config.after.into_iter().fold(task, |t, s| t.after(s));
config.before.into_iter().fold(task, |t, s| t.before(s))
},
move |ctx: &TaskContext<'_>| task.run(ctx),
);
self.systems.push(SystemInfo {
name,
access,
failure,
cell,
});
Ok(self)
}
pub fn build(self) -> Result<Schedule, EcsError> {
let Self {
graph,
systems,
lenders,
events,
} = self;
let graph = graph.build().map_err(|error| map_error(error, &systems))?;
for (position, name) in (0_u64..).zip(graph.order()) {
if let Some(system) = systems.iter().find(|s| s.name == name) {
lock(&system.cell).position = position;
}
}
Ok(Schedule {
graph,
systems,
lenders,
events,
})
}
}
pub struct Schedule {
graph: FrameGraph,
systems: Vec<SystemInfo>,
lenders: Vec<Lender>,
events: Vec<EventType>,
}
impl fmt::Debug for Schedule {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Schedule")
.field("order", &self.order().collect::<Vec<_>>())
.field("edges", &self.edges().count())
.finish()
}
}
impl Schedule {
pub fn builder() -> ScheduleBuilder {
ScheduleBuilder {
graph: FrameGraph::builder(),
systems: Vec::new(),
lenders: Vec::new(),
events: Vec::new(),
}
}
#[must_use]
pub fn len(&self) -> usize {
self.systems.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.systems.is_empty()
}
pub fn systems(&self) -> impl Iterator<Item = (&str, &Access)> {
self.systems.iter().map(|s| (s.name.as_str(), &s.access))
}
pub fn order(&self) -> impl Iterator<Item = &str> {
self.graph.order()
}
pub fn edges(&self) -> impl Iterator<Item = (&str, &str)> {
self.graph.edges()
}
pub fn run(&mut self, world: &mut World, pool: &ThreadPool) -> Result<(), EcsError> {
for system in &self.systems {
*system
.failure
.lock()
.unwrap_or_else(PoisonError::into_inner) = None;
lock(&system.cell).ok = false;
}
if !world.resources_mut().contains::<WorldSlot>() {
world.resources_mut().insert(WorldSlot(World::new()));
}
for event in &self.events {
event.ensure(world);
}
for lender in &self.lenders {
lender.prepare(world)?;
}
world.begin_frame(self.systems.len());
for lender in &self.lenders {
lender.swap(world);
}
let mut resources = mem::take(world.resources_mut());
if let Some(slot) = resources.get_mut::<WorldSlot>() {
mem::swap(&mut slot.0, world);
}
let result = self.graph.run(pool, &mut resources);
if let Some(slot) = resources.get_mut::<WorldSlot>() {
mem::swap(&mut slot.0, world);
}
*world.resources_mut() = resources;
for lender in &self.lenders {
lender.swap(world);
}
let started = !matches!(result, Err(GraphError::MissingResource { .. }));
let applied = if started { self.sync(world) } else { Ok(()) };
result.map_err(|error| map_error(error, &self.systems))?;
applied
}
fn sync(&self, world: &mut World) -> Result<(), EcsError> {
for event in &self.events {
event.update(world);
}
let mut applied = Ok(());
for system in &self.systems {
let mut cell = lock(&system.cell);
if !cell.ok || applied.is_err() {
cell.system.discard();
continue;
}
if let Err(source) = cell.system.apply(world) {
cell.system.discard();
applied = Err(EcsError::CommandFailed {
system: system.name.clone(),
source: Box::new(source),
});
}
}
applied
}
}
fn map_error(error: GraphError, systems: &[SystemInfo]) -> EcsError {
match error {
GraphError::AmbiguousOrder { a, b, resource, .. } => EcsError::AmbiguousOrder {
first: a,
second: b,
data: data_name(resource),
},
GraphError::MissingResource { task, resource, .. } => EcsError::MissingResource {
system: task,
resource,
},
GraphError::TaskPanicked { task, message, .. } => EcsError::SystemPanicked {
system: task,
message,
},
GraphError::Job(source) => EcsError::Job { source },
GraphError::TaskFailed { ref task, .. } => {
let returned = systems.iter().find(|s| s.name == *task).and_then(|s| {
s.failure
.lock()
.unwrap_or_else(PoisonError::into_inner)
.take()
});
match returned {
Some((message, source)) => EcsError::SystemFailed {
system: task.clone(),
message,
source: source.map(Box::new),
},
None => EcsError::graph(error),
}
}
other => EcsError::graph(other),
}
}