use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use super::scheduler::{wake_handle, WakeHandle};
use super::scope::Scope;
use super::signal::Signal;
enum Mode<T> {
Channel { target: Signal<Vec<T>> },
Latest {
slot: Mutex<Option<T>>,
scheduled: AtomicBool,
target: Signal<T>,
},
}
struct SourceShared<T> {
wake: WakeHandle,
mode: Mode<T>,
dead_sends: AtomicU64,
}
pub struct SourceSender<T> {
shared: Arc<SourceShared<T>>,
}
impl<T> Clone for SourceSender<T> {
fn clone(&self) -> Self {
SourceSender {
shared: self.shared.clone(),
}
}
}
impl<T: Send + 'static> SourceSender<T> {
pub fn send(&self, value: T) {
match &self.shared.mode {
Mode::Channel { target } => {
let target = *target;
let shared = self.shared.clone();
self.shared.wake.post(move || {
if target.is_alive() {
target.update(|vec| vec.push(value));
} else {
shared.dead_sends.fetch_add(1, Ordering::Relaxed);
}
});
}
Mode::Latest {
slot,
scheduled,
target,
} => {
*slot.lock().expect("latest-source slot") = Some(value);
if !scheduled.swap(true, Ordering::AcqRel) {
let target = *target;
let shared = self.shared.clone();
self.shared.wake.post(move || {
let Mode::Latest {
slot, scheduled, ..
} = &shared.mode
else {
unreachable!("latest sender carries latest mode");
};
scheduled.store(false, Ordering::Release);
let value = slot.lock().expect("latest-source slot").take();
if let Some(value) = value {
if target.is_alive() {
target.set(value);
} else {
shared.dead_sends.fetch_add(1, Ordering::Relaxed);
}
}
});
}
}
}
}
pub fn dead_sends(&self) -> u64 {
self.shared.dead_sends.load(Ordering::Relaxed)
}
}
pub fn channel_source<T: Send + 'static>(cx: Scope) -> (SourceSender<T>, Signal<Vec<T>>) {
let target = cx.signal(Vec::new());
let sender = SourceSender {
shared: Arc::new(SourceShared {
wake: wake_handle(),
mode: Mode::Channel { target },
dead_sends: AtomicU64::new(0),
}),
};
(sender, target)
}
pub fn latest_source<T: Send + 'static>(cx: Scope, initial: T) -> (SourceSender<T>, Signal<T>) {
let target = cx.signal(initial);
let sender = SourceSender {
shared: Arc::new(SourceShared {
wake: wake_handle(),
mode: Mode::Latest {
slot: Mutex::new(None),
scheduled: AtomicBool::new(false),
target,
},
dead_sends: AtomicU64::new(0),
}),
};
(sender, target)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reactive::{create_root, drain_posted};
#[test]
fn channel_delivers_every_value_in_send_order() {
let (root, ()) = create_root(|cx| {
let (tx, events) = channel_source::<u32>(cx);
let t = std::thread::spawn(move || {
for n in 0..100 {
tx.send(n);
}
});
t.join().expect("producer");
drain_posted();
let got = events.get_untracked();
assert_eq!(got.len(), 100);
assert!(got.windows(2).all(|w| w[0] < w[1]), "order preserved");
});
root.dispose();
}
#[test]
fn channel_preserves_per_sender_order_across_concurrent_senders() {
let (root, ()) = create_root(|cx| {
let (tx, events) = channel_source::<(u8, u32)>(cx);
let barrier = Arc::new(std::sync::Barrier::new(2));
let mk = |id: u8, tx: SourceSender<(u8, u32)>, b: Arc<std::sync::Barrier>| {
std::thread::spawn(move || {
b.wait();
for n in 0..500 {
tx.send((id, n));
}
})
};
let a = mk(0, tx.clone(), barrier.clone());
let b = mk(1, tx, barrier);
a.join().expect("sender a");
b.join().expect("sender b");
drain_posted();
events.with_untracked(|got| {
assert_eq!(got.len(), 1000);
let mut next = [0u32; 2];
for &(id, n) in got.iter() {
assert_eq!(n, next[id as usize], "sender {id} out of order");
next[id as usize] += 1;
}
});
});
root.dispose();
}
#[test]
fn latest_coalesces_bursts_to_the_newest_value() {
let (root, ()) = create_root(|cx| {
let (tx, latest) = latest_source(cx, 0u32);
let t = std::thread::spawn(move || {
for n in 1..=1000 {
tx.send(n);
}
});
t.join().expect("producer");
drain_posted();
assert_eq!(latest.get_untracked(), 1000, "newest value wins");
assert_eq!(drain_posted(), 0, "burst coalesced into one job");
});
root.dispose();
}
#[test]
fn latest_reschedules_after_each_drain() {
let (root, ()) = create_root(|cx| {
let (tx, latest) = latest_source(cx, 0u32);
tx.send(1);
drain_posted();
assert_eq!(latest.get_untracked(), 1);
tx.send(2); drain_posted();
assert_eq!(latest.get_untracked(), 2);
});
root.dispose();
}
#[test]
fn sends_after_scope_disposal_are_inert_and_counted() {
let mut handles = None;
let (root, ()) = create_root(|cx| {
let child = cx.child();
let (tx, events) = channel_source::<u32>(child);
tx.send(1);
drain_posted();
assert_eq!(events.get_untracked(), vec![1]);
child.dispose();
handles = Some((tx, events));
});
let (tx, events) = handles.expect("handles");
tx.send(2);
tx.send(3);
drain_posted();
assert!(!events.is_alive());
assert_eq!(events.try_get_untracked(), None);
assert_eq!(tx.dead_sends(), 2, "inert sends are counted");
root.dispose();
}
#[test]
fn latest_sends_after_disposal_are_inert_and_counted_once_per_cycle() {
let mut handles = None;
let (root, ()) = create_root(|cx| {
let child = cx.child();
let (tx, latest) = latest_source(child, 0u32);
child.dispose();
handles = Some((tx, latest));
});
let (tx, latest) = handles.expect("handles");
tx.send(7);
tx.send(8); drain_posted();
assert!(!latest.is_alive());
assert_eq!(tx.dead_sends(), 1, "the coalesced survivor is counted");
root.dispose();
}
}