use std::{any::Any, marker::PhantomData, sync::Arc};
use super::{
errors::TaskError,
node::NodeId,
traits::{NodeArg, TaskNode},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct BranchId(pub(crate) usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum ConcurrencyModel {
#[default]
Sequential,
Parallel,
}
#[derive(Debug, Clone)]
pub(crate) struct NextNode {
pub(crate) node_id: usize,
pub(crate) context: Arc<dyn Any + Send + Sync>,
}
impl NextNode {
pub(crate) fn new<T: TaskNode + ?Sized>(node_id: NodeId<T>, context: T::Input) -> Self
where
<T as TaskNode>::Input: 'static,
{
Self {
node_id: node_id.id(),
context: Arc::new(context) as Arc<dyn Any + Send + Sync>,
}
}
}
type ErasedPayload = Arc<dyn Any + Send + Sync>;
#[derive(Clone, Copy)]
enum JoinInputFactory {
Fallible(fn(Vec<ErasedPayload>) -> Result<ErasedPayload, TaskError>),
Infallible(fn(Vec<ErasedPayload>) -> ErasedPayload),
}
impl JoinInputFactory {
fn build(self, payloads: Vec<ErasedPayload>) -> Result<ErasedPayload, TaskError> {
match self {
JoinInputFactory::Fallible(factory) => factory(payloads),
JoinInputFactory::Infallible(factory) => Ok(factory(payloads)),
}
}
}
#[doc(hidden)]
#[derive(Clone, Copy)]
pub struct JoinDefinition {
pub(crate) join_node_id: usize,
input_factory: JoinInputFactory,
}
impl std::fmt::Debug for JoinDefinition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JoinDefinition")
.field("join_node_id", &self.join_node_id)
.finish_non_exhaustive()
}
}
impl JoinDefinition {
fn typed<Payload: NodeArg>(join_node_id: usize) -> Self {
Self {
join_node_id,
input_factory: JoinInputFactory::Fallible(typed_join_input::<Payload>),
}
}
fn any(join_node_id: usize) -> Self {
Self {
join_node_id,
input_factory: JoinInputFactory::Infallible(any_join_input),
}
}
pub(crate) fn into_input(
self,
payloads: Vec<ErasedPayload>,
) -> Result<ErasedPayload, TaskError> {
self.input_factory.build(payloads)
}
}
fn typed_join_input<Payload: NodeArg>(
payloads: Vec<ErasedPayload>,
) -> Result<ErasedPayload, TaskError> {
let branches = payloads
.into_iter()
.map(|payload| {
payload.downcast::<Payload>().map_err(|_| {
TaskError::invalid_state(format!(
"Join payload expected type {}",
std::any::type_name::<Payload>()
))
})
})
.collect::<Result<Vec<_>, _>>()?;
Ok(Arc::new(JoinInput::<Payload>::new(branches)) as ErasedPayload)
}
fn any_join_input(payloads: Vec<ErasedPayload>) -> ErasedPayload {
Arc::new(AnyJoinInput::new(payloads)) as ErasedPayload
}
#[must_use]
pub struct JoinTarget<T: TaskNode<Input = JoinInput<Payload>> + ?Sized, Payload: NodeArg> {
pub(crate) definition: JoinDefinition,
_marker: PhantomData<(*const T, Payload)>,
}
#[must_use]
pub struct MappedJoinTarget<T: TaskNode<Input = JoinInput<Payload>> + ?Sized, Payload: NodeArg, F> {
pub(crate) join_target: JoinTarget<T, Payload>,
pub(crate) map: F,
}
#[must_use]
pub struct AnyJoinTarget<T: TaskNode<Input = AnyJoinInput> + ?Sized> {
pub(crate) definition: JoinDefinition,
_marker: PhantomData<T>,
}
#[doc(hidden)]
pub trait JoinDestination {
#[doc(hidden)]
fn into_definition(self) -> JoinDefinition;
}
impl<T, Payload> JoinDestination for JoinTarget<T, Payload>
where
T: TaskNode<Input = JoinInput<Payload>> + ?Sized,
Payload: NodeArg,
{
fn into_definition(self) -> JoinDefinition {
self.definition
}
}
impl<T> JoinDestination for AnyJoinTarget<T>
where
T: TaskNode<Input = AnyJoinInput> + ?Sized,
{
fn into_definition(self) -> JoinDefinition {
self.definition
}
}
impl<T, Payload> JoinTarget<T, Payload>
where
T: TaskNode<Input = JoinInput<Payload>> + ?Sized,
Payload: NodeArg,
{
pub(crate) fn new(node_id: NodeId<T>) -> Self {
Self {
definition: JoinDefinition::typed::<Payload>(node_id.id()),
_marker: PhantomData,
}
}
pub fn map<F>(self, map: F) -> MappedJoinTarget<T, Payload, F>
where
F: Send + Sync + 'static,
{
MappedJoinTarget {
join_target: self,
map,
}
}
pub fn map_async<F>(self, map: F) -> MappedJoinTarget<T, Payload, F>
where
F: Send + Sync + 'static,
{
MappedJoinTarget {
join_target: self,
map,
}
}
}
impl<T: TaskNode<Input = AnyJoinInput> + ?Sized> AnyJoinTarget<T> {
pub(crate) fn new(node_id: NodeId<T>) -> Self {
Self {
definition: JoinDefinition::any(node_id.id()),
_marker: PhantomData,
}
}
}
#[derive(Debug)]
#[must_use]
pub struct Transition {
pub(crate) action: TransitionAction,
pub(crate) concurrency_model: Option<ConcurrencyModel>,
}
#[must_use]
pub struct MarkedTransition<To: TaskNode + ?Sized>(Transition, PhantomData<To>);
impl<To: TaskNode + ?Sized> MarkedTransition<To> {
pub(crate) fn new(transition: Transition) -> Self {
Self(transition, PhantomData)
}
pub(crate) fn into_inner(self) -> Transition {
self.0
}
pub fn concurrency_model(self, concurrency_model: ConcurrencyModel) -> Self {
Self::new(self.0.concurrency_model(concurrency_model))
}
}
impl<To: TaskNode + ?Sized> From<MarkedTransition<To>> for Transition {
fn from(marked_transition: MarkedTransition<To>) -> Self {
marked_transition.into_inner()
}
}
#[derive(Debug)]
#[must_use]
pub struct FanOutTransition {
targets: Vec<NextNode>,
concurrency_model: Option<ConcurrencyModel>,
}
impl FanOutTransition {
pub fn and<T, Input>(mut self, node_id: &NodeId<T>, context: Input) -> Self
where
T: TaskNode + ?Sized,
Input: Into<T::Input>,
{
self.targets.push(NextNode::new(*node_id, context.into()));
self
}
pub fn join_with<J>(self, join_target: J) -> Transition
where
J: JoinDestination,
{
Transition {
action: TransitionAction::FanOut {
targets: self.targets,
join: join_target.into_definition(),
},
concurrency_model: self.concurrency_model,
}
}
pub fn concurrency_model(mut self, concurrency_model: ConcurrencyModel) -> Self {
self.concurrency_model = Some(concurrency_model);
self
}
}
#[derive(Debug)]
pub(crate) enum TransitionAction {
Next(NextNode),
FanOut {
targets: Vec<NextNode>,
join: JoinDefinition,
},
Pause,
Error(Box<dyn std::error::Error + Send + Sync>),
Finish(Arc<dyn Any + Send + Sync>),
}
impl Transition {
pub(crate) fn from_next_node(next_node: NextNode) -> Self {
Self {
action: TransitionAction::Next(next_node),
concurrency_model: None,
}
}
pub fn next<T: TaskNode + ?Sized>(
node_id: &NodeId<T>,
context: T::Input,
) -> MarkedTransition<T> {
MarkedTransition::new(Self::from_next_node(NextNode::new(*node_id, context)))
}
pub fn fan_out<T, Input>(node_id: &NodeId<T>, context: Input) -> FanOutTransition
where
T: TaskNode + ?Sized,
Input: Into<T::Input>,
{
FanOutTransition {
targets: vec![NextNode::new(*node_id, context.into())],
concurrency_model: None,
}
}
pub fn pause() -> Self {
Self {
action: TransitionAction::Pause,
concurrency_model: None,
}
}
pub fn error(error: impl Into<Box<dyn std::error::Error + Send + Sync>>) -> Self {
Self {
action: TransitionAction::Error(error.into()),
concurrency_model: None,
}
}
pub(crate) fn finish<T: NodeArg>(output: T) -> Self {
Self {
action: TransitionAction::Finish(Arc::new(output) as Arc<dyn Any + Send + Sync>),
concurrency_model: None,
}
}
pub fn concurrency_model(mut self, concurrency_model: ConcurrencyModel) -> Self {
self.concurrency_model = Some(concurrency_model);
self
}
}
#[derive(Clone)]
pub struct JoinInput<T: NodeArg> {
branches: Vec<Arc<T>>,
}
impl<T: NodeArg> std::fmt::Debug for JoinInput<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("JoinInput")
.field("payload_type", &std::any::type_name::<T>())
.field("len", &self.branches.len())
.finish()
}
}
impl<T: NodeArg> JoinInput<T> {
pub(crate) fn new(branches: Vec<Arc<T>>) -> Self {
Self { branches }
}
pub fn iter(&self) -> impl Iterator<Item = &T> {
self.branches.iter().map(Arc::as_ref)
}
pub fn len(&self) -> usize {
self.branches.len()
}
pub fn is_empty(&self) -> bool {
self.branches.is_empty()
}
}
#[derive(Clone)]
pub struct AnyJoinInput {
branches: Vec<ErasedPayload>,
}
impl std::fmt::Debug for AnyJoinInput {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AnyJoinInput")
.field("len", &self.branches.len())
.finish()
}
}
impl AnyJoinInput {
pub(crate) fn new(branches: Vec<ErasedPayload>) -> Self {
Self { branches }
}
pub fn iter_any(&self) -> impl Iterator<Item = &(dyn Any + Send + Sync)> {
self.branches.iter().map(Arc::as_ref)
}
pub fn iter<T: NodeArg>(&self) -> impl Iterator<Item = &T> {
self.iter_any()
.filter_map(|value| value.downcast_ref::<T>())
}
pub fn len(&self) -> usize {
self.branches.len()
}
pub fn is_empty(&self) -> bool {
self.branches.is_empty()
}
}