use std::collections::VecDeque;
use std::fmt;
use std::sync::Arc;
use async_trait::async_trait;
use futures::future::FutureExt;
use futures::pin_mut;
use futures::stream::StreamExt;
use pipecrab_core::{DataFrame, Direction, Disposition, Processor, SystemFrame};
use crate::{Inbound, MaybeSend, MaybeSendSync, Outbound, Received};
#[derive(Debug, Clone)]
pub struct StageError {
pub message: Arc<str>,
pub fatal: bool,
}
impl StageError {
pub fn new(message: impl Into<Arc<str>>) -> Self {
Self {
message: message.into(),
fatal: false,
}
}
pub fn fatal(message: impl Into<Arc<str>>) -> Self {
Self {
message: message.into(),
fatal: true,
}
}
}
impl fmt::Display for StageError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let kind = if self.fatal {
"fatal stage error"
} else {
"stage error"
};
write!(f, "{kind}: {}", self.message)
}
}
impl std::error::Error for StageError {}
impl From<String> for StageError {
fn from(message: String) -> Self {
Self::new(message)
}
}
impl From<&str> for StageError {
fn from(message: &str) -> Self {
Self::new(message)
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait Stage: Processor + MaybeSendSync
where
Self::Effect: MaybeSend,
{
async fn perform(&self, effect: Self::Effect, out: &Outbound) -> Result<(), StageError>;
async fn run(self: Box<Self>, inbound: Inbound, out: Outbound) {
let mut stage = self;
let mut inbound = inbound;
let mut pending: VecDeque<DataFrame> = VecDeque::new();
loop {
let received = match pending.pop_front() {
Some(frame) => Received::Data(frame),
None => match inbound.recv().await {
Some(received) => received,
None => break,
},
};
match received {
Received::Sys(dir, frame) => {
let interrupted = matches!(frame, SystemFrame::Interrupt);
let stop = handle_system(&mut *stage, dir, frame, &out).await;
if interrupted {
pending.extend(inbound.flush_data());
}
if stop {
break;
}
}
Received::Data(frame) => {
let decision = stage.decide_data(&frame);
if decision.disposition == Disposition::Forward {
let _ = out.send_data(frame).await;
}
if decision.effects.is_empty() {
continue;
}
let mut stashed: Vec<(Direction, SystemFrame)> = Vec::new();
let mut interrupt: Option<(Direction, SystemFrame)> = None;
let mut should_stop = false;
{
let perform = run_effects(&*stage, decision.effects, &out).fuse();
pin_mut!(perform);
loop {
futures::select_biased! {
maybe = inbound.sys.next() => {
if let Some((d, f)) = maybe {
if matches!(f, SystemFrame::Interrupt) {
interrupt = Some((d, f));
break; }
stashed.push((d, f)); }
},
res = perform => {
if let Err(e) = res {
let fatal = e.fatal;
emit_error(&out, e).await;
should_stop |= fatal;
}
break;
},
complete => break,
}
}
}
for (d, f) in stashed.drain(..) {
should_stop |= handle_system(&mut *stage, d, f, &out).await;
}
if let Some((d, f)) = interrupt {
should_stop |= handle_system(&mut *stage, d, f, &out).await;
pending.extend(inbound.flush_data());
}
if should_stop {
break;
}
}
}
}
}
}
async fn handle_system<S: Stage + ?Sized>(
stage: &mut S,
dir: Direction,
frame: SystemFrame,
out: &Outbound,
) -> bool
where
S::Effect: MaybeSend,
{
let mut should_stop = matches!(frame, SystemFrame::Stop);
let decision = stage.decide_system(dir, &frame);
if decision.disposition == Disposition::Forward {
let _ = out.send_system(dir, frame).await;
}
for effect in decision.effects {
if let Err(e) = stage.perform(effect, out).await {
let fatal = e.fatal;
emit_error(out, e).await;
should_stop |= fatal;
}
}
should_stop
}
async fn run_effects<S: Stage + ?Sized>(
stage: &S,
effects: Vec<S::Effect>,
out: &Outbound,
) -> Result<(), StageError>
where
S::Effect: MaybeSend,
{
for effect in effects {
stage.perform(effect, out).await?;
}
Ok(())
}
async fn emit_error(out: &Outbound, e: StageError) {
let _ = out
.send_system(
Direction::Up,
SystemFrame::Error {
message: e.message,
fatal: e.fatal,
},
)
.await;
}