use crate::valtron::executors::local::NotifyQueue;
use crate::{
valtron::{
spawn_broadcaster, spawn_builder, BoxedExecutionEngine, ConsumingIter, ExecutionAction,
ExecutorError, GenericResult, NoAction, SpawnInfo, SpawnType, TaskIterator, TaskStatus,
TaskStatusMapper,
},
};
use std::{marker::PhantomData, sync::Arc};
pub struct WrapTask<I, T>
where
I: Iterator<Item = T>,
{
iter: I,
}
impl<I, T> WrapTask<I, T>
where
I: Iterator<Item = T>,
{
pub fn new(iter: I) -> Self {
Self { iter }
}
}
impl<I, T> TaskIterator for WrapTask<I, T>
where
I: Iterator<Item = T>,
{
type Pending = ();
type Ready = T;
type Spawner = NoAction;
fn next_status(&mut self) -> Option<TaskStatus<Self::Ready, Self::Pending, Self::Spawner>> {
self.iter.next().map(TaskStatus::Ready)
}
}
pub struct LiftTask<I, D, P, S>
where
I: Iterator<Item = TaskStatus<D, P, S>>,
S: ExecutionAction,
{
iter: I,
}
impl<I, D, P, S> LiftTask<I, D, P, S>
where
I: Iterator<Item = TaskStatus<D, P, S>>,
S: ExecutionAction,
{
pub fn new(iter: I) -> Self {
Self { iter }
}
}
impl<I, D, P, S> TaskIterator for LiftTask<I, D, P, S>
where
I: Iterator<Item = TaskStatus<D, P, S>>,
S: ExecutionAction,
{
type Pending = P;
type Ready = D;
type Spawner = S;
fn next_status(&mut self) -> Option<TaskStatus<Self::Ready, Self::Pending, Self::Spawner>> {
self.iter.next()
}
}
pub struct WrapAction<I, T>
where
I: Iterator<Item = T> + 'static,
T: 'static,
{
iter: Option<I>,
_marker: PhantomData<T>,
}
impl<I, T> WrapAction<I, T>
where
I: Iterator<Item = T> + 'static,
T: 'static,
{
pub fn new(iter: I) -> Self {
Self {
iter: Some(iter),
_marker: PhantomData,
}
}
}
impl<I, T> ExecutionAction for WrapAction<I, T>
where
I: Iterator<Item = T> + 'static,
T: 'static,
{
fn apply(
&mut self,
key: Option<crate::synca::Entry>,
executor: BoxedExecutionEngine,
) -> GenericResult<SpawnInfo> {
if let Some(iter) = self.iter.take() {
let task = WrapTask::new(iter);
return spawn_builder(executor)
.maybe_parent(key)
.with_task(task)
.schedule()
.map_err(std::convert::Into::into);
}
Ok(SpawnInfo::new(SpawnType::None, None, None))
}
}
pub struct SpawnWithLift<I, D, P, S>
where
I: Iterator<Item = TaskStatus<D, P, S>> + 'static,
D: 'static,
P: 'static,
S: ExecutionAction + 'static,
{
iter: Option<I>,
_marker: PhantomData<(D, P, S)>,
}
impl<I, D, P, S> SpawnWithLift<I, D, P, S>
where
I: Iterator<Item = TaskStatus<D, P, S>> + 'static,
D: 'static,
P: 'static,
S: ExecutionAction + 'static,
{
pub fn new(iter: I) -> Self {
Self {
iter: Some(iter),
_marker: PhantomData,
}
}
}
impl<I, D, P, S> ExecutionAction for SpawnWithLift<I, D, P, S>
where
I: Iterator<Item = TaskStatus<D, P, S>> + 'static,
D: 'static,
P: 'static,
S: ExecutionAction + 'static,
{
fn apply(
&mut self,
key: Option<crate::synca::Entry>,
executor: BoxedExecutionEngine,
) -> GenericResult<SpawnInfo> {
if let Some(iter) = self.iter.take() {
let task = LiftTask::new(iter);
return spawn_builder(executor)
.maybe_parent(key)
.with_task(task)
.lift()
.map_err(Into::into);
}
Ok(SpawnInfo::new(SpawnType::None, None, None))
}
}
pub struct ScheduleTask<F>
where
F: FnOnce(),
{
closure: Option<F>,
}
impl<F> ScheduleTask<F>
where
F: FnOnce(),
{
pub fn new(closure: F) -> Self {
Self {
closure: Some(closure),
}
}
}
impl<F> TaskIterator for ScheduleTask<F>
where
F: FnOnce(),
{
type Pending = ();
type Ready = ();
type Spawner = NoAction;
fn next_status(&mut self) -> Option<TaskStatus<Self::Ready, Self::Pending, Self::Spawner>> {
if let Some(closure) = self.closure.take() {
closure();
Some(TaskStatus::Ready(()))
} else {
None
}
}
}
pub struct SpawnWithSchedule<F>
where
F: FnOnce() + 'static,
{
closure: Option<F>,
}
impl<F> SpawnWithSchedule<F>
where
F: FnOnce() + 'static,
{
pub fn new(closure: F) -> Self {
Self {
closure: Some(closure),
}
}
}
impl<F> ExecutionAction for SpawnWithSchedule<F>
where
F: FnOnce() + 'static,
{
fn apply(
&mut self,
key: Option<crate::synca::Entry>,
executor: BoxedExecutionEngine,
) -> GenericResult<SpawnInfo> {
if let Some(closure) = self.closure.take() {
let task = ScheduleTask::new(closure);
return spawn_builder(executor)
.maybe_parent(key)
.with_task(task)
.schedule()
.map_err(std::convert::Into::into);
}
Ok(SpawnInfo::new(SpawnType::None, None, None))
}
}
#[derive(Clone, Copy, Default)]
pub enum InlineSendActionBehaviour {
#[default]
Sequenced,
Lift,
LiftWithParent,
Schedule,
Broadcast,
}
#[allow(clippy::type_complexity)]
pub struct InlineSendAction<Done, Pending, Action, Task, Mapper>(
Option<(
InlineSendActionBehaviour,
Task,
Vec<Mapper>,
Arc<NotifyQueue<TaskStatus<Done, Pending, Action>>>,
)>,
)
where
Action: ExecutionAction + Send + 'static,
Mapper: TaskStatusMapper<Done, Pending, Action> + Send + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done, Spawner = Action> + Send + 'static;
impl<Done, Pending, Action, Task, Mapper> InlineSendAction<Done, Pending, Action, Task, Mapper>
where
Done: Send + 'static,
Pending: Send + 'static,
Action: ExecutionAction + Send + 'static,
Mapper: TaskStatusMapper<Done, Pending, Action> + Send + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done, Spawner = Action> + Send + 'static,
{
pub fn new(
behaviour: InlineSendActionBehaviour,
mappers: Vec<Mapper>,
task: Task,
wait_cycle: std::time::Duration,
) -> (
Self,
crate::valtron::executors::local::NotifyRecvIterator<TaskStatus<Done, Pending, Action>>,
) {
let iter_chan: Arc<NotifyQueue<TaskStatus<Done, Pending, Action>>> =
Arc::new(NotifyQueue::unbounded());
(
Self(Some((behaviour, task, mappers, iter_chan.clone()))),
crate::valtron::executors::local::NotifyRecvIterator::from_notify_queue(
iter_chan,
wait_cycle,
),
)
}
}
impl<Done, Pending, Action, Task>
InlineSendAction<
Done,
Pending,
Action,
Task,
Box<dyn TaskStatusMapper<Done, Pending, Action> + Send + 'static>,
>
where
Done: Send + 'static,
Pending: Send + 'static,
Action: ExecutionAction + Send + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done, Spawner = Action> + Send + 'static,
{
pub fn boxed_mapper(
behaviour: InlineSendActionBehaviour,
mappers: Vec<Box<dyn TaskStatusMapper<Done, Pending, Action> + Send + 'static>>,
task: Task,
wait_cycle: std::time::Duration,
) -> (
Self,
crate::valtron::executors::local::NotifyRecvIterator<TaskStatus<Done, Pending, Action>>,
) {
let iter_chan: Arc<NotifyQueue<TaskStatus<Done, Pending, Action>>> =
Arc::new(NotifyQueue::unbounded());
(
Self(Some((behaviour, task, mappers, iter_chan.clone()))),
crate::valtron::executors::local::NotifyRecvIterator::from_notify_queue(
iter_chan,
wait_cycle,
),
)
}
}
impl<Done, Pending, Action, Task, Mapper> ExecutionAction
for InlineSendAction<Done, Pending, Action, Task, Mapper>
where
Done: Send + 'static,
Pending: Send + 'static,
Action: ExecutionAction + Send + 'static,
Mapper: TaskStatusMapper<Done, Pending, Action> + Send + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done, Spawner = Action> + Send + 'static,
{
fn apply(
&mut self,
key: Option<crate::synca::Entry>,
executor: BoxedExecutionEngine,
) -> GenericResult<SpawnInfo> {
if let Some((behaviour, task, mappers, channel)) = self.0.take() {
match behaviour {
InlineSendActionBehaviour::Sequenced => {
tracing::debug!("Sequence action for InlineAction");
let Some(parent) = key else {
return Err(Box::new(ExecutorError::ParentMustBeSupplied));
};
let consuming_iter = ConsumingIter::new(task, mappers, channel.clone());
executor
.sequenced(consuming_iter.into(), parent)
.map_err(Into::into)
}
InlineSendActionBehaviour::LiftWithParent => {
tracing::debug!("Lift action for InlineAction");
let Some(parent) = key else {
return Err(Box::new(ExecutorError::ParentMustBeSupplied));
};
let consuming_iter = ConsumingIter::new(task, mappers, channel.clone());
executor
.lift(consuming_iter.into(), Some(parent))
.map_err(Into::into)
}
InlineSendActionBehaviour::Lift => {
tracing::debug!("Lift action for InlineSendAction");
let consuming_iter = ConsumingIter::new(task, mappers, channel.clone());
executor
.lift(consuming_iter.into(), key)
.map_err(Into::into)
}
InlineSendActionBehaviour::Schedule => {
tracing::debug!("Schedule action for InlineSendAction");
let consuming_iter = ConsumingIter::new(task, mappers, channel.clone());
executor.schedule(consuming_iter.into()).map_err(Into::into)
}
InlineSendActionBehaviour::Broadcast => {
tracing::debug!("Broadcast action for InlineSendAction");
let consuming_iter = ConsumingIter::new(task, mappers, channel.clone());
executor
.broadcast(consuming_iter.into())
.map_err(Into::into)
}
}
} else {
Err("Action has being used up".into())
}
}
}
#[derive(Clone, Copy, Default)]
pub enum InlineActionBehaviour {
#[default]
Sequenced,
Lift,
LiftWithParent,
Schedule,
}
#[allow(clippy::type_complexity)]
pub struct InlineAction<Done, Pending, Action, Task, Mapper>(
Option<(
InlineActionBehaviour,
Task,
Vec<Mapper>,
Arc<NotifyQueue<TaskStatus<Done, Pending, Action>>>,
)>,
)
where
Action: ExecutionAction,
Mapper: TaskStatusMapper<Done, Pending, Action>,
Task: TaskIterator<Pending = Pending, Ready = Done, Spawner = Action>;
impl<Done, Pending, Action, Task, Mapper> InlineAction<Done, Pending, Action, Task, Mapper>
where
Done: 'static,
Pending: 'static,
Action: ExecutionAction + 'static,
Mapper: TaskStatusMapper<Done, Pending, Action> + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done, Spawner = Action> + 'static,
{
pub fn new(
behaviour: InlineActionBehaviour,
mappers: Vec<Mapper>,
task: Task,
wait_cycle: std::time::Duration,
) -> (
Self,
crate::valtron::executors::local::NotifyRecvIterator<TaskStatus<Done, Pending, Action>>,
) {
let iter_chan: Arc<NotifyQueue<TaskStatus<Done, Pending, Action>>> =
Arc::new(NotifyQueue::unbounded());
(
Self(Some((behaviour, task, mappers, iter_chan.clone()))),
crate::valtron::executors::local::NotifyRecvIterator::from_notify_queue(
iter_chan,
wait_cycle,
),
)
}
}
impl<Done, Pending, Action, Task>
InlineAction<
Done,
Pending,
Action,
Task,
Box<dyn TaskStatusMapper<Done, Pending, Action> + 'static>,
>
where
Done: 'static,
Pending: 'static,
Action: ExecutionAction + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done, Spawner = Action> + 'static,
{
pub fn boxed_mapper(
behaviour: InlineActionBehaviour,
mappers: Vec<Box<dyn TaskStatusMapper<Done, Pending, Action> + 'static>>,
task: Task,
wait_cycle: std::time::Duration,
) -> (
Self,
crate::valtron::executors::local::NotifyRecvIterator<TaskStatus<Done, Pending, Action>>,
) {
let iter_chan: Arc<NotifyQueue<TaskStatus<Done, Pending, Action>>> =
Arc::new(NotifyQueue::unbounded());
(
Self(Some((behaviour, task, mappers, iter_chan.clone()))),
crate::valtron::executors::local::NotifyRecvIterator::from_notify_queue(
iter_chan,
wait_cycle,
),
)
}
}
impl<Done, Pending, Action, Task, Mapper> ExecutionAction
for InlineAction<Done, Pending, Action, Task, Mapper>
where
Done: 'static,
Pending: 'static,
Action: ExecutionAction + 'static,
Mapper: TaskStatusMapper<Done, Pending, Action> + 'static,
Task: TaskIterator<Pending = Pending, Ready = Done, Spawner = Action> + 'static,
{
fn apply(
&mut self,
key: Option<crate::synca::Entry>,
executor: BoxedExecutionEngine,
) -> GenericResult<SpawnInfo> {
if let Some((behaviour, task, mappers, channel)) = self.0.take() {
match behaviour {
InlineActionBehaviour::Sequenced => {
tracing::debug!("Sequence action for InlineAction");
let Some(parent) = key else {
return Err(Box::new(ExecutorError::ParentMustBeSupplied));
};
let consuming_iter = ConsumingIter::new(task, mappers, channel.clone());
executor
.sequenced(consuming_iter.into(), parent)
.map_err(Into::into)
}
InlineActionBehaviour::LiftWithParent => {
tracing::debug!("Lift action for InlineAction");
let Some(parent) = key else {
return Err(Box::new(ExecutorError::ParentMustBeSupplied));
};
let consuming_iter = ConsumingIter::new(task, mappers, channel.clone());
executor
.lift(consuming_iter.into(), Some(parent))
.map_err(Into::into)
}
InlineActionBehaviour::Lift => {
tracing::debug!("Lift action for InlineAction");
let consuming_iter = ConsumingIter::new(task, mappers, channel.clone());
executor
.lift(consuming_iter.into(), None)
.map_err(Into::into)
}
InlineActionBehaviour::Schedule => {
tracing::debug!("Schedule action for InlineAction");
let consuming_iter = ConsumingIter::new(task, mappers, channel.clone());
executor.schedule(consuming_iter.into()).map_err(Into::into)
}
}
} else {
Err("Action has being used up".into())
}
}
}
pub struct BroadcastTask<T>
where
T: Clone + Send,
{
value: Option<T>,
callbacks: Vec<Box<dyn FnOnce(T) + Send>>,
}
impl<T> BroadcastTask<T>
where
T: Clone + Send,
{
pub fn new(value: T, callbacks: Vec<Box<dyn FnOnce(T) + Send>>) -> Self {
Self {
value: Some(value),
callbacks,
}
}
}
impl<T> TaskIterator for BroadcastTask<T>
where
T: Clone + Send,
{
type Pending = ();
type Ready = ();
type Spawner = NoAction;
fn next_status(&mut self) -> Option<TaskStatus<Self::Ready, Self::Pending, Self::Spawner>> {
if let Some(value) = self.value.take() {
for callback in self.callbacks.drain(..) {
callback(value.clone());
}
Some(TaskStatus::Ready(()))
} else {
None
}
}
}
pub struct SpawnWithBroadcast<T>
where
T: Clone + 'static,
{
value: Option<T>,
callbacks: Vec<Box<dyn FnOnce(T) + Send>>,
}
impl<T> SpawnWithBroadcast<T>
where
T: Clone + 'static,
{
pub fn new(value: T, callbacks: Vec<Box<dyn FnOnce(T) + Send>>) -> Self {
Self {
value: Some(value),
callbacks,
}
}
}
impl<T> ExecutionAction for SpawnWithBroadcast<T>
where
T: Clone + Send + 'static,
{
fn apply(
&mut self,
key: Option<crate::synca::Entry>,
executor: BoxedExecutionEngine,
) -> GenericResult<SpawnInfo> {
if let (Some(value), true) = (self.value.take(), !self.callbacks.is_empty()) {
let callbacks = std::mem::take(&mut self.callbacks);
let task = BroadcastTask::new(value, callbacks);
return spawn_broadcaster(executor)
.maybe_parent(key)
.with_task(task)
.broadcast()
.map_err(std::convert::Into::into);
}
Ok(SpawnInfo::new(SpawnType::None, None, None))
}
}
pub enum SpawnStrategy<IW, TW, IL, DL, PL, SL, F, V, C>
where
IW: Iterator<Item = TW> + 'static,
TW: 'static,
IL: Iterator<Item = TaskStatus<DL, PL, SL>> + 'static,
DL: 'static,
PL: 'static,
SL: ExecutionAction + 'static,
F: FnOnce() + 'static,
V: Clone + 'static,
C: ExecutionAction,
{
None,
Wrap(WrapAction<IW, TW>),
Lift(SpawnWithLift<IL, DL, PL, SL>),
Schedule(SpawnWithSchedule<F>),
Broadcast(SpawnWithBroadcast<V>),
Custom(C),
}
impl<IW, TW, IL, DL, PL, SL, F, V, C> ExecutionAction
for SpawnStrategy<IW, TW, IL, DL, PL, SL, F, V, C>
where
IW: Iterator<Item = TW> + 'static,
TW: 'static,
IL: Iterator<Item = TaskStatus<DL, PL, SL>> + 'static,
DL: 'static,
PL: 'static,
SL: ExecutionAction + 'static,
F: FnOnce() + 'static,
V: Clone + Send + 'static,
C: ExecutionAction,
{
fn apply(
&mut self,
key: Option<crate::synca::Entry>,
engine: BoxedExecutionEngine,
) -> GenericResult<SpawnInfo> {
match self {
Self::None => Ok(SpawnInfo::new(SpawnType::None, None, None)),
Self::Wrap(action) => action.apply(key, engine),
Self::Lift(action) => action.apply(key, engine),
Self::Schedule(action) => action.apply(key, engine),
Self::Broadcast(action) => action.apply(key, engine),
Self::Custom(action) => action.apply(key, engine),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::{Arc, Mutex as StdMutex};
use std::time::Duration;
#[test]
fn test_wrap_task_from_vec_iterator() {
let items = vec![1, 2, 3];
let mut task = WrapTask::new(items.into_iter());
match task.next_status() {
Some(TaskStatus::Ready(1)) => {}
other => panic!("Expected Ready(1), got {:?}", other),
}
match task.next_status() {
Some(TaskStatus::Ready(2)) => {}
other => panic!("Expected Ready(2), got {:?}", other),
}
match task.next_status() {
Some(TaskStatus::Ready(3)) => {}
other => panic!("Expected Ready(3), got {:?}", other),
}
assert!(task.next_status().is_none());
}
#[test]
fn test_wrap_action_creates_spawnable_task() {
let items = vec![10, 20, 30];
let action = WrapAction::new(items.into_iter());
let _: Box<dyn ExecutionAction> = Box::new(action);
}
#[test]
fn test_wrap_task_with_range() {
let mut task = WrapTask::new(0..3);
assert_eq!(task.next_status(), Some(TaskStatus::Ready(0)));
assert_eq!(task.next_status(), Some(TaskStatus::Ready(1)));
assert_eq!(task.next_status(), Some(TaskStatus::Ready(2)));
assert_eq!(task.next_status(), None);
}
#[test]
fn test_lift_task_preserves_pending_state() {
let iter = vec![TaskStatus::<i32, (), NoAction>::Pending(())].into_iter();
let mut task = LiftTask::new(iter);
match task.next_status() {
Some(TaskStatus::Pending(())) => {} other => panic!("Expected Pending(()), got {:?}", other),
}
}
#[test]
fn test_lift_task_preserves_delayed_state() {
let dur = Duration::from_secs(1);
let iter = vec![TaskStatus::<i32, (), NoAction>::Delayed(dur)].into_iter();
let mut task = LiftTask::new(iter);
match task.next_status() {
Some(TaskStatus::Delayed(d)) if d == dur => {} other => panic!("Expected Delayed({:?}), got {:?}", dur, other),
}
}
#[test]
fn test_lift_task_preserves_ready_state() {
let iter = vec![TaskStatus::<i32, (), NoAction>::Ready(42)].into_iter();
let mut task = LiftTask::new(iter);
match task.next_status() {
Some(TaskStatus::Ready(42)) => {} other => panic!("Expected Ready(42), got {:?}", other),
}
}
#[test]
fn test_lift_task_with_mixed_statuses() {
let dur = Duration::from_millis(500);
let iter = vec![
TaskStatus::<i32, (), NoAction>::Pending(()),
TaskStatus::<i32, (), NoAction>::Delayed(dur),
TaskStatus::<i32, (), NoAction>::Ready(100),
TaskStatus::<i32, (), NoAction>::Pending(()),
]
.into_iter();
let mut task = LiftTask::new(iter);
assert!(matches!(task.next_status(), Some(TaskStatus::Pending(()))));
assert!(matches!(task.next_status(), Some(TaskStatus::Delayed(d)) if d == dur));
assert!(matches!(task.next_status(), Some(TaskStatus::Ready(100))));
assert!(matches!(task.next_status(), Some(TaskStatus::Pending(()))));
assert!(task.next_status().is_none());
}
#[test]
fn test_lift_action_is_execution_action() {
let iter = vec![TaskStatus::<i32, (), NoAction>::Ready(1)].into_iter();
let action = SpawnWithLift::new(iter);
let _: Box<dyn ExecutionAction> = Box::new(action);
}
#[test]
fn test_wrap_task_is_entry_point() {
let plain = vec![1, 2, 3].into_iter();
let mut wrapped = WrapTask::new(plain);
assert_eq!(wrapped.next_status(), Some(TaskStatus::Ready(1)));
assert_eq!(wrapped.next_status(), Some(TaskStatus::Ready(2)));
assert_eq!(wrapped.next_status(), Some(TaskStatus::Ready(3)));
assert_eq!(wrapped.next_status(), None);
}
#[test]
fn test_lift_task_accepts_status_iterator() {
let status_iter = vec![
TaskStatus::<i32, (), NoAction>::Ready(1),
TaskStatus::<i32, (), NoAction>::Pending(()),
TaskStatus::<i32, (), NoAction>::Ready(2),
]
.into_iter();
let mut lifted = LiftTask::new(status_iter);
assert_eq!(lifted.next_status(), Some(TaskStatus::Ready(1)));
assert_eq!(lifted.next_status(), Some(TaskStatus::Pending(())));
assert_eq!(lifted.next_status(), Some(TaskStatus::Ready(2)));
assert_eq!(lifted.next_status(), None);
}
#[cfg(feature = "std")]
#[test]
fn test_composition_wrap_then_timeout() {
use super::super::wrappers::TimeoutTask;
let plain = vec![1, 2, 3].into_iter();
let wrapped = WrapTask::new(plain);
let mut timed = TimeoutTask::new(wrapped, Duration::from_secs(10));
assert_eq!(timed.next_status(), Some(TaskStatus::Ready(1)));
assert_eq!(timed.next_status(), Some(TaskStatus::Ready(2)));
assert_eq!(timed.next_status(), Some(TaskStatus::Ready(3)));
assert!(timed.next_status().is_none());
}
#[cfg(feature = "std")]
#[test]
fn test_composition_lift_then_timeout_preserves_pending() {
use super::super::wrappers::TimeoutTask;
let statuses = vec![
TaskStatus::<i32, (), NoAction>::Pending(()),
TaskStatus::<i32, (), NoAction>::Ready(42),
]
.into_iter();
let lifted = LiftTask::new(statuses);
let mut timed = TimeoutTask::new(lifted, Duration::from_secs(10));
match timed.next_status() {
Some(TaskStatus::Pending(_)) => {} other => panic!("Expected Pending, got {:?}", other),
}
assert_eq!(timed.next_status(), Some(TaskStatus::Ready(42)));
assert!(timed.next_status().is_none());
}
#[test]
fn test_schedule_task_executes_closure() {
let executed = Arc::new(StdMutex::new(false));
let executed_clone = executed.clone();
let mut task = ScheduleTask::new(move || {
*executed_clone.lock().unwrap() = true;
});
match task.next_status() {
Some(TaskStatus::Ready(())) => {}
other => panic!("Expected Ready(()), got {:?}", other),
}
assert!(*executed.lock().unwrap());
assert!(task.next_status().is_none());
}
#[test]
fn test_schedule_action_is_execution_action() {
let counter = Arc::new(StdMutex::new(0));
let counter_clone = counter.clone();
let action = SpawnWithSchedule::new(move || {
*counter_clone.lock().unwrap() += 1;
});
let _: Box<dyn ExecutionAction> = Box::new(action);
}
#[test]
fn test_schedule_task_runs_once() {
let counter = Arc::new(StdMutex::new(0));
let counter_clone = counter.clone();
let mut task = ScheduleTask::new(move || {
*counter_clone.lock().unwrap() += 1;
});
assert!(task.next_status().is_some());
assert_eq!(*counter.lock().unwrap(), 1);
assert!(task.next_status().is_none());
assert_eq!(*counter.lock().unwrap(), 1);
}
#[test]
fn test_broadcast_task_sends_to_multiple_receivers() {
let receiver1 = Arc::new(StdMutex::new(None));
let receiver2 = Arc::new(StdMutex::new(None));
let receiver3 = Arc::new(StdMutex::new(None));
let r1 = receiver1.clone();
let r2 = receiver2.clone();
let r3 = receiver3.clone();
let callbacks: Vec<Box<dyn FnOnce(i32) + Send>> = vec![
Box::new(move |val| *r1.lock().unwrap() = Some(val)),
Box::new(move |val| *r2.lock().unwrap() = Some(val)),
Box::new(move |val| *r3.lock().unwrap() = Some(val)),
];
let mut task = BroadcastTask::new(42, callbacks);
match task.next_status() {
Some(TaskStatus::Ready(())) => {}
other => panic!("Expected Ready(()), got {:?}", other),
}
assert_eq!(*receiver1.lock().unwrap(), Some(42));
assert_eq!(*receiver2.lock().unwrap(), Some(42));
assert_eq!(*receiver3.lock().unwrap(), Some(42));
assert!(task.next_status().is_none());
}
#[test]
fn test_broadcast_action_is_execution_action() {
let action = SpawnWithBroadcast::new(100, vec![]);
let _: Box<dyn ExecutionAction> = Box::new(action);
}
#[test]
fn test_broadcast_task_with_no_callbacks() {
let mut task = BroadcastTask::new(42, vec![]);
assert!(task.next_status().is_some());
assert!(task.next_status().is_none());
}
#[test]
fn test_composite_action_none_variant() {
type TestComposite = SpawnStrategy<
std::ops::Range<i32>,
i32,
std::vec::IntoIter<TaskStatus<i32, (), NoAction>>,
i32,
(),
NoAction,
fn(),
i32,
NoAction,
>;
let composite: TestComposite = SpawnStrategy::None;
let _: Box<dyn ExecutionAction> = Box::new(composite);
}
#[test]
fn test_composite_action_with_wrap() {
type TestComposite = SpawnStrategy<
std::vec::IntoIter<i32>,
i32,
std::vec::IntoIter<TaskStatus<i32, (), NoAction>>,
i32,
(),
NoAction,
fn(),
i32,
NoAction,
>;
let wrap = WrapAction::new(vec![1, 2, 3].into_iter());
let composite: TestComposite = SpawnStrategy::Wrap(wrap);
let _: Box<dyn ExecutionAction> = Box::new(composite);
}
#[test]
fn test_composite_action_with_lift() {
type TestComposite = SpawnStrategy<
std::ops::Range<i32>,
i32,
std::vec::IntoIter<TaskStatus<i32, (), NoAction>>,
i32,
(),
NoAction,
fn(),
i32,
NoAction,
>;
let iter = vec![TaskStatus::<i32, (), NoAction>::Ready(1)].into_iter();
let lift = SpawnWithLift::new(iter);
let composite: TestComposite = SpawnStrategy::Lift(lift);
let _: Box<dyn ExecutionAction> = Box::new(composite);
}
#[test]
fn test_composite_action_with_schedule() {
fn dummy_fn() {}
type TestComposite = SpawnStrategy<
std::ops::Range<i32>,
i32,
std::vec::IntoIter<TaskStatus<i32, (), NoAction>>,
i32,
(),
NoAction,
fn(),
i32,
NoAction,
>;
let schedule = SpawnWithSchedule::new(dummy_fn as fn());
let composite: TestComposite = SpawnStrategy::Schedule(schedule);
let _: Box<dyn ExecutionAction> = Box::new(composite);
}
}