mod task_manager;
mod task_runner;
pub use task_manager::{
AlreadyJoined, FinalTag, MultiEdge, OnCompletion, SingleEdge, TaskManager, TaskManagerSystem,
UnexpectedEntity,
};
pub use task_runner::TaskRunnerSystem;
use specs::prelude::*;
use std::sync::atomic::{AtomicBool, Ordering};
pub trait TaskComponent<'a>: Component {
type Data: SystemData<'a>;
fn run(&mut self, data: &mut Self::Data) -> bool;
}
#[doc(hidden)]
#[derive(Default)]
pub struct TaskProgress {
is_complete: AtomicBool,
is_unblocked: bool,
}
impl Component for TaskProgress {
type Storage = VecStorage<Self>;
}
impl TaskProgress {
fn is_complete(&self) -> bool {
self.is_complete.load(Ordering::Relaxed)
}
fn complete(&self) {
self.is_complete.store(true, Ordering::Relaxed);
}
fn unblock(&mut self) {
self.is_unblocked = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, Default, Eq, PartialEq)]
struct AlreadyComplete {
was_run: bool,
}
impl Component for AlreadyComplete {
type Storage = VecStorage<Self>;
}
impl<'a> TaskComponent<'a> for AlreadyComplete {
type Data = ();
fn run(&mut self, _data: &mut Self::Data) -> bool {
self.was_run = true;
true
}
}
struct WriteValue {
value: usize,
}
impl Component for WriteValue {
type Storage = VecStorage<Self>;
}
impl<'a> TaskComponent<'a> for WriteValue {
type Data = Write<'a, usize>;
fn run(&mut self, data: &mut Self::Data) -> bool {
**data = self.value;
true
}
}
fn set_up<'a, 'b>() -> (World, Dispatcher<'a, 'b>) {
let mut world = World::new();
let mut dispatcher = DispatcherBuilder::new()
.with(
TaskRunnerSystem::<AlreadyComplete>::default(),
"already_complete",
&[],
)
.with(
TaskRunnerSystem::<WriteValue>::default(),
"write_value",
&[],
)
.with(
TaskManagerSystem,
"task_manager",
&["already_complete", "write_value"],
)
.build();
dispatcher.setup(&mut world);
(world, dispatcher)
}
enum MakeSingleTask {
Finalize(OnCompletion),
DontFinalize,
}
fn make_single_task<'a, T: TaskComponent<'a>>(
world: &mut World,
task: T,
option: MakeSingleTask,
) -> Entity {
world.exec(
|(mut task_man, mut tasks): (TaskManager, WriteStorage<T>)| {
let task = task_man.make_task(task, &mut tasks);
if let MakeSingleTask::Finalize(on_completion) = option {
task_man.finalize(task, on_completion);
}
task
},
)
}
fn make_fork(world: &mut World) -> Entity {
world.exec(|mut task_man: TaskManager| task_man.make_fork())
}
fn entity_is_complete(world: &mut World, entity: Entity) -> bool {
world.exec(|task_man: TaskManager| task_man.entity_is_complete(entity))
}
#[test]
fn single_task_not_run_until_finalized() {
let (mut world, mut dispatcher) = set_up();
let task = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::DontFinalize,
);
dispatcher.dispatch(&world);
dispatcher.dispatch(&world);
assert_eq!(
world.read_storage::<AlreadyComplete>().get(task),
Some(&AlreadyComplete { was_run: false })
);
world.exec(|mut task_man: TaskManager| task_man.finalize(task, OnCompletion::None));
dispatcher.dispatch(&world);
dispatcher.dispatch(&world);
world.maintain();
assert!(entity_is_complete(&mut world, task));
assert_eq!(
world.read_storage::<AlreadyComplete>().get(task),
Some(&AlreadyComplete { was_run: true }),
);
}
#[test]
fn single_task_deleted_on_completion() {
let (mut world, mut dispatcher) = set_up();
let task = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::Finalize(OnCompletion::Delete),
);
dispatcher.dispatch(&world);
dispatcher.dispatch(&world);
world.maintain();
assert!(entity_is_complete(&mut world, task));
assert_eq!(world.entities().is_alive(task), false);
}
#[test]
fn joined_tasks_run_in_order_and_deleted_on_completion() {
let (mut world, mut dispatcher) = set_up();
let task1 = make_single_task(
&mut world,
WriteValue { value: 1 },
MakeSingleTask::DontFinalize,
);
let task2 = make_single_task(
&mut world,
WriteValue { value: 2 },
MakeSingleTask::DontFinalize,
);
let task3 = make_single_task(
&mut world,
WriteValue { value: 3 },
MakeSingleTask::DontFinalize,
);
world.exec(|mut task_man: TaskManager| {
task_man.join(task3, task2).unwrap();
task_man.join(task2, task1).unwrap();
task_man.finalize(task3, OnCompletion::Delete);
});
dispatcher.dispatch(&world);
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, task1));
assert_eq!(*world.fetch::<usize>(), 1);
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, task2));
assert_eq!(*world.fetch::<usize>(), 2);
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, task3));
assert_eq!(*world.fetch::<usize>(), 3);
world.maintain();
for entity in [task1, task2, task3].iter() {
assert_eq!(world.entities().is_alive(*entity), false);
}
}
#[test]
fn all_prongs_of_fork_run_before_join_and_deleted_on_completion() {
let (mut world, mut dispatcher) = set_up();
let fork = make_fork(&mut world);
let initial_task = make_single_task(
&mut world,
WriteValue { value: 1 },
MakeSingleTask::DontFinalize,
);
let prong1_task = make_single_task(
&mut world,
WriteValue { value: 2 },
MakeSingleTask::DontFinalize,
);
let prong2_task = make_single_task(
&mut world,
WriteValue { value: 3 },
MakeSingleTask::DontFinalize,
);
let join_task = make_single_task(
&mut world,
WriteValue { value: 4 },
MakeSingleTask::DontFinalize,
);
world.exec(|mut task_man: TaskManager| {
task_man.join(fork, initial_task).unwrap();
task_man.add_prong(fork, prong1_task).unwrap();
task_man.add_prong(fork, prong2_task).unwrap();
task_man.join(join_task, fork).unwrap();
task_man.finalize(join_task, OnCompletion::Delete);
});
dispatcher.dispatch(&world);
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, initial_task));
assert_eq!(*world.fetch::<usize>(), 1);
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, prong1_task));
assert!(entity_is_complete(&mut world, prong2_task));
let cur_value = *world.fetch::<usize>();
assert!(cur_value == 2 || cur_value == 3);
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, join_task));
assert_eq!(*world.fetch::<usize>(), 4);
world.maintain();
for entity in [initial_task, prong1_task, prong2_task, join_task, fork].iter() {
assert_eq!(world.entities().is_alive(*entity), false);
}
}
#[test]
fn join_fork_with_nested_fork() {
let (mut world, mut dispatcher) = set_up();
let forka = make_fork(&mut world);
let forkb = make_fork(&mut world);
let t0 = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::DontFinalize,
);
let t1 = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::DontFinalize,
);
let tx = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::DontFinalize,
);
let ty = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::DontFinalize,
);
let tz = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::DontFinalize,
);
let t2 = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::DontFinalize,
);
world.exec(|mut task_man: TaskManager| {
task_man.add_prong(forka, tx).unwrap();
task_man.add_prong(forka, forkb).unwrap();
task_man.join(forka, t0).unwrap();
task_man.add_prong(forkb, ty).unwrap();
task_man.add_prong(forkb, tz).unwrap();
task_man.join(forkb, t1).unwrap();
task_man.join(t2, forka).unwrap();
task_man.finalize(t2, OnCompletion::Delete);
});
dispatcher.dispatch(&world);
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, t0));
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, t1));
assert!(entity_is_complete(&mut world, tx));
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, ty));
assert!(entity_is_complete(&mut world, tz));
dispatcher.dispatch(&world);
assert!(entity_is_complete(&mut world, t2));
world.maintain();
for entity in [t0, t1, tx, ty, tz, t2, forka, forkb].iter() {
assert_eq!(world.entities().is_alive(*entity), false);
}
}
#[test]
fn test_cant_add_prong_to_task() {
let (mut world, _) = set_up();
let task = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::Finalize(OnCompletion::Delete),
);
let fork = make_fork(&mut world);
world.exec(|mut task_man: TaskManager| {
assert_eq!(
task_man.add_prong(task, fork),
Err(UnexpectedEntity::ExpectedForkEntity(task)),
);
});
}
#[test]
fn test_already_joined_error() {
let (mut world, _) = set_up();
let task1 = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::Finalize(OnCompletion::Delete),
);
let task2 = make_single_task(
&mut world,
AlreadyComplete::default(),
MakeSingleTask::Finalize(OnCompletion::Delete),
);
world.exec(|mut task_man: TaskManager| {
assert!(task_man.join(task1, task2).is_ok());
assert_eq!(
task_man.join(task1, task2),
Err(AlreadyJoined {
parent: task1,
already_child: task2,
new_child: task2,
}),
);
});
}
}