use std::any::Any;
use std::cell::{Cell, Ref, RefCell};
use std::fmt;
use std::future::poll_fn;
use std::pin::Pin;
use std::rc::Rc;
use std::sync::Arc;
use std::task::Poll;
use crate::schema::{InputSpec, OutputSpec, Props};
use crate::signal::{LogicBlock, SignalCursor};
use crate::stream::{Broadcast, Event, Item, QueueControl, ReaderId};
use crate::time::{Tick, TickRange, TimeBase};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum Wait {
#[default]
Nothing,
Input,
Output,
Budget,
}
#[derive(Debug, Default)]
pub struct NodeCx {
budget: Cell<u32>,
waiting: Cell<Wait>,
ops: Cell<u64>,
}
impl NodeCx {
pub fn new() -> Rc<Self> {
Rc::new(Self::default())
}
pub fn refill(&self, budget: u32) {
self.budget.set(budget);
self.waiting.set(Wait::Nothing);
}
pub fn has_budget(&self) -> bool {
self.budget.get() > 0
}
pub fn spend(&self) {
self.budget.set(self.budget.get().saturating_sub(1));
self.ops.set(self.ops.get() + 1);
}
pub fn wait(&self, on: Wait) {
self.waiting.set(on);
}
pub fn waiting(&self) -> Wait {
self.waiting.get()
}
pub fn ops(&self) -> u64 {
self.ops.get()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Provenance {
pub span: TickRange,
pub settled: Tick,
}
impl Provenance {
pub const fn at(tick: Tick) -> Self {
Self {
span: TickRange::at(tick),
settled: tick,
}
}
pub const fn over(span: TickRange, settled: Tick) -> Self {
Self { span, settled }
}
pub const fn one(tick: Tick) -> Self {
Self {
span: TickRange::one(tick),
settled: tick,
}
}
pub fn join(self, other: Provenance) -> Provenance {
Provenance {
span: self.span.join(other.span),
settled: self.settled.max(other.settled),
}
}
}
pub(crate) type SharedQueue<T> = Rc<RefCell<Broadcast<T>>>;
pub struct Inbox<T> {
queue: SharedQueue<T>,
reader: ReaderId,
cx: Rc<NodeCx>,
}
impl<T> fmt::Debug for Inbox<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Inbox")
.field("reader", &self.reader)
.finish_non_exhaustive()
}
}
impl<T> Inbox<T> {
pub(crate) fn new(queue: SharedQueue<T>, cx: Rc<NodeCx>) -> Self {
let reader = queue.borrow_mut().add_reader();
Self { queue, reader, cx }
}
pub fn cx(&self) -> &Rc<NodeCx> {
&self.cx
}
pub fn poll_peek(&self) -> Poll<Ref<'_, Item<T>>> {
let reader = self.reader;
match Ref::filter_map(self.queue.borrow(), |queue| queue.peek(reader)) {
Ok(item) => Poll::Ready(item),
Err(_) => {
self.cx.wait(Wait::Input);
Poll::Pending
}
}
}
pub fn advance(&self) {
self.queue.borrow_mut().advance(self.reader);
}
pub fn peek_with<'a, R, F>(&'a self, f: F) -> impl Future<Output = R> + 'a
where
F: FnOnce(&Item<T>) -> R + 'a,
{
let mut f = Some(f);
poll_fn(move |_| {
if !self.cx.has_budget() {
self.cx.wait(Wait::Budget);
return Poll::Pending;
}
let item = match self.poll_peek() {
Poll::Pending => return Poll::Pending,
Poll::Ready(item) => item,
};
match f.take() {
Some(f) => Poll::Ready(f(&item)),
None => Poll::Pending,
}
})
}
pub fn take(&self) {
self.advance();
self.cx.spend();
}
pub fn head_time(&self) -> Option<Tick> {
match self.poll_peek() {
Poll::Ready(item) => Some(item.time()),
Poll::Pending => None,
}
}
pub fn recv(&mut self) -> impl Future<Output = Item<T>> + '_
where
T: Clone,
{
poll_fn(move |_| {
if !self.cx.has_budget() {
self.cx.wait(Wait::Budget);
return Poll::Pending;
}
let item = match self.poll_peek() {
Poll::Pending => return Poll::Pending,
Poll::Ready(item) => item.clone(),
};
self.advance();
self.cx.spend();
Poll::Ready(item)
})
}
}
impl<T> Drop for Inbox<T> {
fn drop(&mut self) {
self.queue.borrow_mut().remove_reader(self.reader);
}
}
pub trait Readable {
fn poll_ready(&self) -> Poll<()>;
}
impl<T> Readable for Inbox<T> {
fn poll_ready(&self) -> Poll<()> {
self.poll_peek().map(|_| ())
}
}
pub fn ready_all<'a>(inputs: &'a [&'a dyn Readable]) -> impl Future<Output = ()> + 'a {
poll_fn(move |_| {
let mut ready = true;
for input in inputs {
ready &= input.poll_ready().is_ready();
}
if ready {
Poll::Ready(())
} else {
Poll::Pending
}
})
}
pub struct Outbox<T> {
queue: SharedQueue<T>,
cx: Rc<NodeCx>,
sink: Rc<SinkInner>,
}
impl<T> fmt::Debug for Outbox<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Outbox").finish_non_exhaustive()
}
}
impl<T: 'static> Outbox<T> {
pub(crate) fn new(queue: SharedQueue<T>, cx: Rc<NodeCx>) -> Self {
let sink = Rc::new(SinkInner {
control: Rc::clone(&queue) as Rc<dyn QueueControl>,
cx: Rc::clone(&cx),
forwarded: Cell::new(None),
held: Cell::new(None),
});
Self { queue, cx, sink }
}
}
impl<T> Outbox<T> {
pub fn cx(&self) -> &Rc<NodeCx> {
&self.cx
}
pub fn has_room(&self) -> bool {
!self.queue.borrow().is_full()
}
fn poll_push(&mut self, slot: &mut Option<Item<T>>) -> Poll<()> {
let Some(item) = slot.take() else {
return Poll::Ready(());
};
if !self.cx.has_budget() {
*slot = Some(item);
self.cx.wait(Wait::Budget);
return Poll::Pending;
}
match self.queue.borrow_mut().try_push(item) {
Ok(()) => {
self.cx.spend();
Poll::Ready(())
}
Err(item) => {
*slot = Some(item);
self.cx.wait(Wait::Output);
Poll::Pending
}
}
}
fn push(&mut self, item: Item<T>) -> impl Future<Output = ()> + '_ {
let mut slot = Some(item);
poll_fn(move |_| self.poll_push(&mut slot))
}
pub fn emit(&mut self, at: Provenance, payload: T) -> impl Future<Output = ()> + '_ {
self.push(Item::Event(Event {
seq: 0,
span: at.span,
settled: at.settled,
payload,
}))
}
pub fn gap(&mut self, range: TickRange) -> impl Future<Output = ()> + '_ {
self.push(Item::Gap(range))
}
pub fn progress(&mut self, tick: Tick) -> impl Future<Output = ()> + '_ {
self.push(Item::Progress(tick))
}
pub fn end(&mut self, tick: Tick) -> impl Future<Output = ()> + '_ {
self.push(Item::End(tick))
}
pub fn progress_sink(&self) -> ProgressSink {
ProgressSink(Rc::clone(&self.sink))
}
}
struct SinkInner {
control: Rc<dyn QueueControl>,
cx: Rc<NodeCx>,
forwarded: Cell<Option<Tick>>,
held: Cell<Option<Tick>>,
}
#[derive(Clone)]
pub struct ProgressSink(Rc<SinkInner>);
impl fmt::Debug for ProgressSink {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("ProgressSink")
.field("forwarded", &self.0.forwarded.get())
.field("held", &self.0.held.get())
.finish()
}
}
impl ProgressSink {
pub fn progress(&self, tick: Tick) -> impl Future<Output = ()> + '_ {
let inner = &*self.0;
let mut pending = !inner.forwarded.get().is_some_and(|last| last >= tick);
poll_fn(move |_| {
if !pending {
return Poll::Ready(());
}
if !inner.cx.has_budget() {
inner.cx.wait(Wait::Budget);
return Poll::Pending;
}
if inner.control.try_progress(tick) {
inner.cx.spend();
inner.forwarded.set(Some(tick));
pending = false;
Poll::Ready(())
} else {
inner.cx.wait(Wait::Output);
Poll::Pending
}
})
}
pub fn forwarded(&self) -> Option<Tick> {
self.0.forwarded.get()
}
pub fn hold(&self, from: Option<Tick>) {
self.0.held.set(from);
}
pub fn held(&self) -> Option<Tick> {
self.0.held.get()
}
}
#[derive(Clone, Debug)]
pub struct NodeSpec {
pub name: String,
pub inputs: Vec<InputSpec>,
pub outputs: Vec<OutputSpec>,
}
pub type NodeFuture = Pin<Box<dyn Future<Output = ()>>>;
pub trait Node: 'static {
fn spec(&self) -> NodeSpec;
fn start(self: Box<Self>, io: Io) -> NodeFuture;
}
impl Node for Box<dyn Node> {
fn spec(&self) -> NodeSpec {
(**self).spec()
}
fn start(self: Box<Self>, io: Io) -> NodeFuture {
(*self).start(io)
}
}
pub struct Io {
cx: Rc<NodeCx>,
inputs: Vec<Option<Rc<dyn Any>>>,
outputs: Vec<Option<Rc<dyn Any>>>,
time_base: Option<TimeBase>,
input_props: Vec<Props>,
}
impl fmt::Debug for Io {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Io")
.field("inputs", &self.inputs.len())
.field("outputs", &self.outputs.len())
.finish_non_exhaustive()
}
}
impl Io {
pub(crate) fn new(
cx: Rc<NodeCx>,
inputs: Vec<Rc<dyn Any>>,
outputs: Vec<Rc<dyn Any>>,
time_base: Option<TimeBase>,
input_props: Vec<Props>,
) -> Self {
Self {
cx,
inputs: inputs.into_iter().map(Some).collect(),
outputs: outputs.into_iter().map(Some).collect(),
time_base,
input_props,
}
}
pub fn time_base(&self) -> Option<TimeBase> {
self.time_base
}
pub fn input_props(&self, index: usize) -> &Props {
self.input_props
.get(index)
.unwrap_or_else(|| panic!("the node declared no input port {index}"))
}
pub fn cx(&self) -> &Rc<NodeCx> {
&self.cx
}
fn take<T: 'static>(
slots: &mut [Option<Rc<dyn Any>>],
index: usize,
what: &str,
) -> SharedQueue<T> {
let slot = slots
.get_mut(index)
.unwrap_or_else(|| panic!("the node declared no {what} port {index}"))
.take()
.unwrap_or_else(|| panic!("{what} port {index} was bound twice"));
slot.downcast::<RefCell<Broadcast<T>>>()
.unwrap_or_else(|_| panic!("{what} port {index} carries another event type"))
}
pub fn input<T: 'static>(&mut self, index: usize) -> Inbox<T> {
Inbox::new(
Self::take(&mut self.inputs, index, "input"),
Rc::clone(&self.cx),
)
}
pub fn signal(&mut self, index: usize) -> SignalCursor {
SignalCursor::new(self.input::<Arc<LogicBlock>>(index))
}
pub fn output<T: 'static>(&mut self, index: usize) -> Outbox<T> {
Outbox::new(
Self::take(&mut self.outputs, index, "output"),
Rc::clone(&self.cx),
)
}
}
#[cfg(test)]
mod tests {
use std::task::{Context, Waker};
use super::*;
fn shared<T>(capacity: usize) -> SharedQueue<T> {
Rc::new(RefCell::new(Broadcast::new(capacity)))
}
fn poll<F: Future>(future: Pin<&mut F>) -> Poll<F::Output> {
future.poll(&mut Context::from_waker(Waker::noop()))
}
#[test]
fn reads_wait_for_input_and_writes_for_room() {
let queue = shared::<u8>(1);
let cx = NodeCx::new();
cx.refill(10);
let mut inbox = Inbox::new(Rc::clone(&queue), Rc::clone(&cx));
let mut outbox = Outbox::new(Rc::clone(&queue), Rc::clone(&cx));
let mut read = Box::pin(inbox.recv());
assert_eq!(poll(read.as_mut()), Poll::Pending);
assert_eq!(cx.waiting(), Wait::Input);
drop(read);
let mut write = Box::pin(outbox.emit(Provenance::at(Tick(3)), 7));
assert_eq!(poll(write.as_mut()), Poll::Ready(()));
drop(write);
let mut write = Box::pin(outbox.emit(Provenance::at(Tick(4)), 8));
assert_eq!(poll(write.as_mut()), Poll::Pending);
assert_eq!(cx.waiting(), Wait::Output);
drop(write);
let mut read = Box::pin(inbox.recv());
match poll(read.as_mut()) {
Poll::Ready(Item::Event(event)) => {
assert_eq!((event.seq, event.payload, event.settled), (0, 7, Tick(3)));
}
other => panic!("expected the event, got {other:?}"),
}
drop(read);
assert_eq!(cx.ops(), 2);
}
#[test]
fn a_spent_budget_pauses_without_consuming() {
let queue = shared::<u8>(4);
let cx = NodeCx::new();
cx.refill(1);
let mut inbox = Inbox::new(Rc::clone(&queue), Rc::clone(&cx));
let mut outbox = Outbox::new(Rc::clone(&queue), Rc::clone(&cx));
let mut write = Box::pin(outbox.emit(Provenance::at(Tick(0)), 1));
assert_eq!(poll(write.as_mut()), Poll::Ready(()));
drop(write);
let mut write = Box::pin(outbox.emit(Provenance::at(Tick(0)), 2));
assert_eq!(poll(write.as_mut()), Poll::Pending);
assert_eq!(cx.waiting(), Wait::Budget);
assert_eq!(queue.borrow().len(), 1);
drop(write);
cx.refill(1);
let mut read = Box::pin(inbox.recv());
assert!(matches!(poll(read.as_mut()), Poll::Ready(Item::Event(_))));
drop(read);
assert!(matches!(
poll(Box::pin(inbox.recv()).as_mut()),
Poll::Pending
));
assert_eq!(cx.waiting(), Wait::Budget);
}
#[test]
fn a_sink_passes_progress_on_once_and_waits_for_room() {
let queue = shared::<u8>(1);
let cx = NodeCx::new();
cx.refill(10);
let mut inbox = Inbox::new(Rc::clone(&queue), Rc::clone(&cx));
let mut outbox = Outbox::new(Rc::clone(&queue), Rc::clone(&cx));
let sink = outbox.progress_sink();
assert_eq!(
poll(Box::pin(sink.progress(Tick(5))).as_mut()),
Poll::Ready(())
);
assert_eq!(sink.forwarded(), Some(Tick(5)));
assert_eq!(
poll(Box::pin(sink.progress(Tick(3))).as_mut()),
Poll::Ready(())
);
assert_eq!(
poll(Box::pin(sink.progress(Tick(7))).as_mut()),
Poll::Ready(())
);
assert_eq!(queue.borrow().len(), 1);
assert!(matches!(
poll(Box::pin(inbox.recv()).as_mut()),
Poll::Ready(Item::Progress(Tick(7)))
));
assert_eq!(
poll(Box::pin(outbox.emit(Provenance::at(Tick(8)), 1)).as_mut()),
Poll::Ready(())
);
assert_eq!(
poll(Box::pin(sink.progress(Tick(9))).as_mut()),
Poll::Pending
);
assert_eq!(cx.waiting(), Wait::Output);
assert!(matches!(
poll(Box::pin(inbox.recv()).as_mut()),
Poll::Ready(Item::Event(_))
));
assert_eq!(
poll(Box::pin(sink.progress(Tick(9))).as_mut()),
Poll::Ready(())
);
let other = outbox.progress_sink();
other.hold(Some(Tick(4)));
assert_eq!(sink.held(), Some(Tick(4)));
sink.hold(None);
assert_eq!(other.held(), None);
}
#[test]
fn dropping_an_inbox_detaches_its_reader() {
let queue = shared::<u8>(1);
let cx = NodeCx::new();
cx.refill(10);
let inbox = Inbox::new(Rc::clone(&queue), Rc::clone(&cx));
let mut outbox = Outbox::new(Rc::clone(&queue), Rc::clone(&cx));
assert_eq!(
poll(Box::pin(outbox.emit(Provenance::at(Tick(0)), 1)).as_mut()),
Poll::Ready(())
);
assert!(!outbox.has_room());
drop(inbox);
assert!(outbox.has_room());
}
}