use ruin_runtime::channel::{mpsc, oneshot}; use ruin_runtime::{queue_future, spawn_worker, time::sleep}; use std::fmt; use std::sync::OnceLock; use std::sync::atomic::{AtomicUsize, Ordering}; use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH}; static START: OnceLock = OnceLock::new(); static ACTUAL_ORDER: AtomicUsize = AtomicUsize::new(1); macro_rules! log_event { ($expected:literal, $($arg:tt)*) => {{ log_event_impl($expected, format_args!($($arg)*)); }}; } fn log_event_impl(expected: usize, message: fmt::Arguments<'_>) { let actual = ACTUAL_ORDER.fetch_add(1, Ordering::SeqCst); let elapsed = START .get() .expect("showcase start time should be initialized") .elapsed() .as_millis(); println!( "[actual {actual:02} | expected {expected:02} | +{elapsed:04}ms | ts {}] {message}", unix_timestamp_millis(), ); } fn unix_timestamp_millis() -> String { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .expect("system clock should be after the Unix epoch"); format!("{}.{:03}", now.as_secs(), now.subsec_millis()) } enum WorkerEvent { Log(String), PresentRequest { frame: &'static str, ack: oneshot::Sender<&'static str>, }, } #[ruin_runtime::async_main] async fn main() -> Result<(), Box> { START.get_or_init(Instant::now); let (job_tx, mut job_rx) = mpsc::channel::<&'static str>(1); let (event_tx, mut event_rx) = mpsc::unbounded_channel::(); let worker = spawn_worker( move || { queue_future(async move { while let Some(job) = job_rx.recv().await { event_tx .send(WorkerEvent::Log(format!( "[worker] accepted job `{job}` from main thread" ))) .unwrap_or_else(|_| { panic!("worker should be able to report accepted jobs") }); sleep(Duration::from_millis(20)).await; if job == "upload-frame" { let (ack_tx, mut ack_rx) = oneshot::channel(); event_tx .send(WorkerEvent::PresentRequest { frame: job, ack: ack_tx, }) .unwrap_or_else(|_| { panic!("worker should be able to request presentation") }); let ack = ack_rx .recv() .await .expect("main thread should acknowledge frame"); event_tx .send(WorkerEvent::Log(format!( "[worker] got oneshot ack `{ack}` for `{job}`" ))) .unwrap_or_else(|_| { panic!("worker should be able to report ack reception") }); } } event_tx .send(WorkerEvent::Log( "[worker] bounded command channel closed; worker is done".into(), )) .unwrap_or_else(|_| panic!("worker should be able to report shutdown")); }); }, || log_event!(12, "[main] worker exited"), ); queue_future(async move { log_event!(1, "[main] bounded mpsc send: enqueue `prepare-scene`"); job_tx .send("prepare-scene") .await .expect("prepare-scene should be sent"); log_event!( 2, "[main] bounded mpsc send: enqueue `upload-frame` (fits once worker drains capacity)" ); job_tx .send("upload-frame") .await .expect("upload-frame should be sent"); log_event!( 3, "[main] bounded mpsc send: enqueue `flush-stats` (waits for capacity/backpressure)" ); job_tx .send("flush-stats") .await .expect("flush-stats should be sent"); log_event!( 5, "[main] drop bounded sender to close worker command stream" ); drop(job_tx); }); let mut event_count = 0usize; while let Some(event) = event_rx.recv().await { event_count += 1; match event { WorkerEvent::Log(message) => { let expected = match event_count { 1 => 4, 2 => 6, 4 => 9, 5 => 10, 6 => 11, _ => 10 + event_count, }; log_event_impl(expected, format_args!("{message}")); } WorkerEvent::PresentRequest { frame, ack } => { log_event!( 7, "[main] unbounded mpsc recv: worker requests presentation for `{frame}`" ); ack.send("presented") .expect("main thread should be able to answer oneshot"); log_event!(8, "[main] oneshot send: acknowledged frame presentation"); } } } let _ = worker; Ok(()) }