1use std::any::Any;
11use std::cell::RefCell;
12use std::fmt;
13use std::rc::Rc;
14use std::sync::Arc;
15use std::task::{Context, Waker};
16
17use crate::graph::{
18 DynExposedRef, ExposedRef, Exposure, GraphConfig, Owner, PortKey, SourceId, SourceSpec,
19};
20use crate::node::{Io, Node, NodeCx, NodeFuture, NodeSpec, SharedQueue, Wait};
21use crate::schema::Props;
22use crate::signal::{LogicBlock, SignalItem};
23use crate::stream::{Broadcast, Event, Item, QueueControl, QueueHandles, ReaderId};
24use crate::time::{Tick, TimeBase};
25
26#[derive(Debug, thiserror::Error)]
28pub enum PushError {
29 #[error("the source is full")]
32 Full(SignalItem),
33 #[error("the item starts at tick {found} but the source stands at {expected}")]
35 Discontinuity {
36 expected: Tick,
38 found: Tick,
40 },
41 #[error("the block has {found} channels but the source has {expected}")]
43 Channels {
44 expected: u8,
46 found: u8,
48 },
49 #[error("the source has ended")]
51 Ended,
52 #[error(
56 "the pipeline is stalled: the source is full, nothing can be read and every node waits ({waiting:?})"
57 )]
58 Stalled {
59 waiting: Vec<(String, Wait)>,
61 },
62 #[error("no source {0} in the pipeline")]
64 UnknownSource(usize),
65}
66
67struct SourceState {
68 name: String,
69 channels: u8,
70 time_base: TimeBase,
71 queue: SharedQueue<Arc<LogicBlock>>,
72 handles: QueueHandles,
73 next: Option<Tick>,
75 ended: bool,
76}
77
78struct Running {
79 name: String,
80 cx: Rc<NodeCx>,
81 future: Option<NodeFuture>,
83}
84
85struct QueueEntry {
86 name: String,
87 control: Rc<dyn QueueControl>,
88}
89
90struct ExposedEntry {
91 name: String,
92 queue: Rc<dyn Any>,
93 control: Rc<dyn QueueControl>,
94 reader_id: ReaderId,
96 reader: Option<ReaderId>,
98}
99
100#[derive(Clone, Debug)]
102struct LastRun {
103 ops: u64,
104 exposed_pending: usize,
105 waiting: Vec<(String, Wait)>,
106}
107
108#[derive(Clone, Debug, Default, PartialEq, Eq)]
110pub struct RunReport {
111 pub ops: u64,
113 pub polls: u64,
115 pub rounds: u32,
117 pub waiting: Vec<(String, Wait)>,
119}
120
121#[derive(Clone, Debug, PartialEq, Eq)]
123pub struct QueueStat {
124 pub name: String,
126 pub len: usize,
128 pub capacity: usize,
130 pub high_water: usize,
132 pub ended: bool,
134}
135
136#[derive(Clone, Debug, Default, PartialEq, Eq)]
138pub struct Stats {
139 pub queues: Vec<QueueStat>,
141}
142
143impl Stats {
144 pub fn high_water(&self) -> usize {
146 self.queues
147 .iter()
148 .map(|queue| queue.high_water)
149 .max()
150 .unwrap_or(0)
151 }
152
153 pub fn within_capacity(&self) -> bool {
156 self.queues
157 .iter()
158 .all(|queue| queue.high_water <= queue.capacity)
159 }
160}
161
162pub struct Pipeline {
164 budget: u32,
165 sources: Vec<SourceState>,
166 nodes: Vec<Running>,
168 queues: Vec<QueueEntry>,
169 exposed: Vec<ExposedEntry>,
170 last_run: Option<LastRun>,
172}
173
174impl fmt::Debug for Pipeline {
175 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176 f.debug_struct("Pipeline")
177 .field("sources", &self.sources.len())
178 .field("nodes", &self.nodes.len())
179 .field("exposed", &self.exposed.len())
180 .finish()
181 }
182}
183
184impl Pipeline {
185 #[allow(clippy::too_many_arguments)]
186 pub(crate) fn start(
187 config: GraphConfig,
188 sources: Vec<SourceSpec>,
189 nodes: Vec<Box<dyn Node>>,
190 specs: Vec<NodeSpec>,
191 inputs: Vec<Vec<Option<PortKey>>>,
192 order: Vec<usize>,
193 domains: Vec<Option<usize>>,
194 input_props: Vec<Vec<Props>>,
195 exposed: Vec<Exposure>,
196 ) -> Result<Self, crate::graph::GraphError> {
197 let mut queues = Vec::new();
198 let sources: Vec<SourceState> = sources
199 .into_iter()
200 .enumerate()
201 .map(|(index, spec)| {
202 let handles = spec.output().make_queue(spec.capacity);
203 queues.push(QueueEntry {
204 name: format!("{}#{index}.samples", spec.name),
205 control: Rc::clone(&handles.control),
206 });
207 let queue = Rc::clone(&handles.any)
208 .downcast::<RefCell<Broadcast<Arc<LogicBlock>>>>()
209 .unwrap_or_else(|_| unreachable!("source queues carry blocks"));
210 SourceState {
211 name: spec.name,
212 channels: spec.channels,
213 time_base: spec.time_base,
214 queue,
215 handles,
216 next: None,
217 ended: false,
218 }
219 })
220 .collect();
221
222 let outputs: Vec<Vec<QueueHandles>> = specs
224 .iter()
225 .enumerate()
226 .map(|(index, spec)| {
227 spec.outputs
228 .iter()
229 .map(|output| {
230 let handles = output.make_queue(config.queue_capacity);
231 queues.push(QueueEntry {
232 name: format!("{}#{index}.{}", spec.name, output.name),
233 control: Rc::clone(&handles.control),
234 });
235 handles
236 })
237 .collect()
238 })
239 .collect();
240 let queue_of = |key: PortKey| -> QueueHandles {
241 match key.owner {
242 Owner::Source(index) => sources[index].handles.clone(),
243 Owner::Node(index) => outputs[index][key.index].clone(),
244 }
245 };
246
247 let exposed = exposed
249 .into_iter()
250 .map(|exposure| {
251 let handles = queue_of(exposure.from);
252 let reader = handles.control.add_reader();
253 ExposedEntry {
254 name: exposure.name,
255 queue: handles.any,
256 control: handles.control,
257 reader_id: reader,
258 reader: Some(reader),
259 }
260 })
261 .collect();
262
263 let mut running: Vec<Option<Running>> = (0..nodes.len()).map(|_| None).collect();
264 let mut specs = specs.into_iter().map(Some).collect::<Vec<_>>();
265 let mut nodes = nodes.into_iter().map(Some).collect::<Vec<_>>();
266 for &index in &order {
267 let (Some(node), Some(spec)) = (nodes[index].take(), specs[index].take()) else {
268 continue;
269 };
270 let cx = NodeCx::new();
271 let bound_inputs = inputs[index]
272 .iter()
273 .flatten()
274 .map(|key| queue_of(*key).any)
275 .collect();
276 let bound_outputs = outputs[index]
277 .iter()
278 .map(|handles| Rc::clone(&handles.any))
279 .collect();
280 let time_base = domains[index].map(|domain| sources[domain].time_base);
281 let io = Io::new(
282 Rc::clone(&cx),
283 bound_inputs,
284 bound_outputs,
285 time_base,
286 input_props[index].clone(),
287 );
288 let future = node.start(io);
289 running[index] = Some(Running {
290 name: format!("{}#{index}", spec.name),
291 cx,
292 future: Some(future),
293 });
294 }
295 let nodes = order
296 .into_iter()
297 .filter_map(|index| running[index].take())
298 .collect();
299 Ok(Self {
300 budget: config.budget,
301 sources,
302 nodes,
303 queues,
304 exposed,
305 last_run: None,
306 })
307 }
308
309 pub fn sources(&self) -> impl Iterator<Item = (SourceId, &str, TimeBase)> {
311 self.sources
312 .iter()
313 .enumerate()
314 .map(|(index, source)| (SourceId(index), source.name.as_str(), source.time_base))
315 }
316
317 pub fn push(&mut self, source: SourceId, item: SignalItem) -> Result<(), PushError> {
322 let state = self
323 .sources
324 .get_mut(source.0)
325 .ok_or(PushError::UnknownSource(source.0))?;
326 if state.ended {
327 return Err(PushError::Ended);
328 }
329 let expected = state.next.unwrap_or(match &item {
330 SignalItem::Data(block) => block.start(),
331 SignalItem::Gap(range) => range.start,
332 SignalItem::Progress(tick) | SignalItem::End(tick) => *tick,
333 });
334 let found = match &item {
335 SignalItem::Data(block) => {
336 if block.channels() != state.channels {
337 return Err(PushError::Channels {
338 expected: state.channels,
339 found: block.channels(),
340 });
341 }
342 block.start()
343 }
344 SignalItem::Gap(range) => range.start,
345 SignalItem::End(tick) => *tick,
346 SignalItem::Progress(tick) => {
347 if *tick < expected {
348 return Err(PushError::Discontinuity {
349 expected,
350 found: *tick,
351 });
352 }
353 expected
354 }
355 };
356 if found != expected {
357 return Err(PushError::Discontinuity { expected, found });
358 }
359 if state.next.is_none() {
362 let mut queue = state.queue.borrow_mut();
363 if queue.is_full() {
364 drop(queue);
365 return Err(full_error(self.last_run.as_ref(), &self.exposed, item));
366 }
367 let _ = queue.try_push(Item::Progress(expected));
368 drop(queue);
369 state.next = Some(expected);
370 }
371 let (queued, next, ended) = match item {
372 SignalItem::Data(block) => {
373 let end = block.end();
374 let last = Tick(end.0 - 1);
375 let event = Event {
376 seq: 0,
377 span: block.range(),
378 settled: last,
379 payload: block,
380 };
381 (Item::Event(event), end, false)
382 }
383 SignalItem::Gap(range) => (Item::Gap(range), range.end.max(expected), false),
384 SignalItem::Progress(tick) => (Item::Progress(tick), expected, false),
385 SignalItem::End(tick) => (Item::End(tick), tick, true),
386 };
387 if let Err(item) = state.queue.borrow_mut().try_push(queued) {
388 let item = match item {
389 Item::Event(event) => SignalItem::Data(event.payload),
390 Item::Gap(range) => SignalItem::Gap(range),
391 Item::Progress(tick) => SignalItem::Progress(tick),
392 Item::End(tick) => SignalItem::End(tick),
393 };
394 return Err(full_error(self.last_run.as_ref(), &self.exposed, item));
395 }
396 state.next = Some(next);
397 state.ended = ended;
398 self.last_run = None;
399 Ok(())
400 }
401
402 pub fn next_tick(&self, source: SourceId) -> Option<Tick> {
405 self.sources.get(source.0).and_then(|state| state.next)
406 }
407
408 pub fn has_room(&self, source: SourceId) -> bool {
410 self.sources
411 .get(source.0)
412 .is_some_and(|state| !state.queue.borrow().is_full())
413 }
414
415 pub fn run(&mut self) -> RunReport {
418 let mut report = RunReport::default();
419 let mut context = Context::from_waker(Waker::noop());
420 loop {
421 report.rounds += 1;
422 let mut moved = false;
423 for node in &mut self.nodes {
424 let Some(future) = node.future.as_mut() else {
425 continue;
426 };
427 node.cx.refill(self.budget);
428 let before = node.cx.ops();
429 report.polls += 1;
430 let poll = future.as_mut().poll(&mut context);
431 let done = node.cx.ops() - before;
432 report.ops += done;
433 moved |= done > 0;
434 if poll.is_ready() {
437 node.future = None;
438 }
439 }
440 if !moved {
441 break;
442 }
443 }
444 report.waiting = self
445 .nodes
446 .iter()
447 .filter(|node| node.future.is_some())
448 .map(|node| (node.name.clone(), node.cx.waiting()))
449 .collect();
450 self.last_run = Some(LastRun {
451 ops: report.ops,
452 exposed_pending: exposed_pending(&self.exposed),
453 waiting: report.waiting.clone(),
454 });
455 report
456 }
457
458 pub fn is_finished(&self) -> bool {
460 self.nodes.iter().all(|node| node.future.is_none())
461 }
462
463 pub fn stats(&self) -> Stats {
465 Stats {
466 queues: self
467 .queues
468 .iter()
469 .map(|entry| QueueStat {
470 name: entry.name.clone(),
471 len: entry.control.len(),
472 capacity: entry.control.capacity(),
473 high_water: entry.control.high_water(),
474 ended: entry.control.is_ended(),
475 })
476 .collect(),
477 }
478 }
479
480 pub fn exposed<T: 'static>(&mut self, exposed: ExposedRef<T>) -> Option<Exposed<T>> {
486 let name = self.exposed.get(exposed.index)?.name.clone();
487 let taken = self.exposed_dyn::<T>(DynExposedRef {
488 index: exposed.index,
489 });
490 if taken.is_none() && self.exposed[exposed.index].reader.is_some() {
491 panic!("exposed output {name} carries another event type");
492 }
493 taken
494 }
495
496 pub fn exposed_dyn<T: 'static>(&mut self, exposed: DynExposedRef) -> Option<Exposed<T>> {
500 let entry = self.exposed.get_mut(exposed.index)?;
501 let queue = Rc::clone(&entry.queue)
502 .downcast::<RefCell<Broadcast<T>>>()
503 .ok()?;
504 let reader = entry.reader.take()?;
505 Some(Exposed {
506 name: entry.name.clone(),
507 queue,
508 reader,
509 })
510 }
511}
512
513fn full_error(last_run: Option<&LastRun>, exposed: &[ExposedEntry], item: SignalItem) -> PushError {
516 match last_run {
517 Some(last)
518 if last.ops == 0 && last.exposed_pending == 0 && exposed_pending(exposed) == 0 =>
519 {
520 PushError::Stalled {
521 waiting: last.waiting.clone(),
522 }
523 }
524 _ => PushError::Full(item),
525 }
526}
527
528fn exposed_pending(exposed: &[ExposedEntry]) -> usize {
531 exposed
532 .iter()
533 .filter(|entry| entry.reader.is_none())
534 .map(|entry| entry.control.pending(entry.reader_id))
535 .sum()
536}
537
538pub struct Exposed<T> {
540 name: String,
541 queue: SharedQueue<T>,
542 reader: ReaderId,
543}
544
545impl<T> fmt::Debug for Exposed<T> {
546 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
547 f.debug_struct("Exposed")
548 .field("name", &self.name)
549 .finish_non_exhaustive()
550 }
551}
552
553impl<T> Exposed<T> {
554 pub fn name(&self) -> &str {
556 &self.name
557 }
558
559 pub fn with_next<R>(&self, f: impl FnOnce(&Item<T>) -> R) -> Option<R> {
561 self.queue.borrow().peek(self.reader).map(f)
562 }
563
564 pub fn advance(&self) {
566 self.queue.borrow_mut().advance(self.reader);
567 }
568
569 pub fn next(&self) -> Option<Item<T>>
572 where
573 T: Clone,
574 {
575 let item = self.with_next(Item::clone)?;
576 self.advance();
577 Some(item)
578 }
579
580 pub fn is_ended(&self) -> bool {
582 self.with_next(Item::is_end).unwrap_or(false)
583 }
584
585 pub fn pending(&self) -> usize {
587 self.queue.borrow().pending(self.reader)
588 }
589}
590
591impl<T> Drop for Exposed<T> {
592 fn drop(&mut self) {
593 self.queue.borrow_mut().remove_reader(self.reader);
594 }
595}