use crate::line_sink::LineSink;
use crate::scoped_start::{Started, start_or_run_with};
use crate::side_by_side_threads::{ScopeBody, in_thread_scope};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::mpsc::{Receiver, Sender, channel};
use std::thread::Scope;
#[derive(Debug, Eq, PartialEq)]
pub enum Event<'line, R> {
Begin(usize),
Line(usize, &'line str),
End(usize, R),
}
enum Message<R> {
Line(usize, String),
Done(usize, R),
}
pub trait OrderedWork<T, R>: Sync {
fn run(&self, index: usize, item: &T, say: &mut dyn LineSink) -> R;
}
pub trait OrderedEvents<R> {
fn on_event(&mut self, event: Event<'_, R>);
}
pub struct OrderedRun<'run, T, R> {
pub items: &'run [T],
pub lanes: &'run [Vec<usize>],
pub workers: usize,
pub work: &'run dyn OrderedWork<T, R>,
pub on_event: &'run mut dyn OrderedEvents<R>,
}
struct Cursor<R> {
head: usize,
begun: bool,
held_lines: Vec<Vec<String>>,
finished: Vec<Option<R>>,
}
impl<R> Cursor<R> {
fn advance(&mut self, on_event: &mut dyn OrderedEvents<R>) {
loop {
if self.head >= self.finished.len() {
return;
}
if !self.begun {
on_event.on_event(Event::Begin(self.head));
self.begun = true;
for text in std::mem::take(&mut self.held_lines[self.head]) {
on_event.on_event(Event::Line(self.head, text.as_str()));
}
}
match self.finished[self.head].take() {
Some(result) => {
on_event.on_event(Event::End(self.head, result));
self.head += 1;
self.begun = false;
}
None => return,
}
}
}
}
struct LinesToConsumer<'sender, R> {
sender: &'sender Sender<Message<R>>,
index: usize,
}
impl<R> LineSink for LinesToConsumer<'_, R> {
fn say(&mut self, text: &str) {
let _sent = self.sender.send(Message::Line(self.index, text.to_string()));
}
}
type WorkerState<'a, T, R> = (&'a [T], &'a [Vec<usize>], &'a AtomicUsize, &'a dyn OrderedWork<T, R>, Sender<Message<R>>);
fn work_lanes<T, R>((items, lanes, next_lane, work, sender): WorkerState<'_, T, R>) {
loop {
let lane_index: usize = next_lane.fetch_add(1, Ordering::Relaxed);
let Some(lane) = lanes.get(lane_index) else { return };
for &index in lane {
let Some(item) = items.get(index) else { continue };
let result: R = work.run(index, item, &mut LinesToConsumer { sender: &sender, index });
let _sent = sender.send(Message::Done(index, result));
}
}
}
struct OrderedScope<'env, T, R> {
items: &'env [T],
lanes: &'env [Vec<usize>],
workers: usize,
work: &'env dyn OrderedWork<T, R>,
on_event: &'env mut dyn OrderedEvents<R>,
next_lane: &'env AtomicUsize,
sender: Sender<Message<R>>,
receiver: Receiver<Message<R>>,
cursor: Cursor<R>,
}
impl<'env, T: Sync + 'env, R: Send + 'env> ScopeBody<'env> for OrderedScope<'env, T, R> {
type Output = ();
fn run<'scope>(self, scope: &'scope Scope<'scope, 'env>) {
let OrderedScope { items, lanes, workers, work, on_event, next_lane, sender, receiver, mut cursor } = self;
let mut inline_panic: Option<Box<dyn std::any::Any + Send>> = None;
for _worker in 0..workers.max(1).min(lanes.len().max(1)) {
let worker_sender: Sender<Message<R>> = sender.clone();
if let Started::Inline(Err(payload)) = start_or_run_with(scope, (items, lanes, next_lane, work, worker_sender), work_lanes::<T, R>) {
inline_panic.get_or_insert(payload);
}
}
drop(sender);
cursor.advance(on_event);
while let Ok(message) = receiver.recv() {
match message {
Message::Line(index, text) => {
if index == cursor.head && cursor.begun {
on_event.on_event(Event::Line(index, text.as_str()));
} else {
cursor.held_lines[index].push(text);
}
}
Message::Done(index, result) => cursor.finished[index] = Some(result),
}
cursor.advance(on_event);
}
if let Some(payload) = inline_panic {
std::panic::resume_unwind(payload);
}
}
}
pub fn run_ordered<T: Sync, R: Send>(run: OrderedRun<'_, T, R>) {
let OrderedRun { items, lanes, workers, work, on_event } = run;
let mut covered: Vec<bool> = vec![false; items.len()];
for &index in lanes.iter().flatten() {
let Some(slot) = covered.get_mut(index) else {
panic!("a lane names item {index}, but there are {} items", items.len());
};
assert!(!*slot, "item {index} is in two lanes");
*slot = true;
}
assert!(!covered.contains(&false), "a lane set must cover every item");
let next_lane: AtomicUsize = AtomicUsize::new(0);
let (sender, receiver) = channel::<Message<R>>();
let mut held_lines: Vec<Vec<String>> = Vec::new();
held_lines.resize_with(items.len(), Vec::new);
let mut finished: Vec<Option<R>> = Vec::new();
finished.resize_with(items.len(), Option::default);
let cursor: Cursor<R> = Cursor { head: 0, begun: false, held_lines, finished };
in_thread_scope(OrderedScope { items, lanes, workers, work, on_event, next_lane: &next_lane, sender, receiver, cursor });
}
#[cfg(test)]
#[path = "parallel_runner_tests.rs"]
mod tests;