229 lines
8.1 KiB
Rust
229 lines
8.1 KiB
Rust
use ruin_runtime::{
|
|
IntervalHandle, ThreadHandle, clear_interval, current_thread_handle, queue_future,
|
|
queue_microtask, queue_task, set_interval, set_timeout, spawn_worker, yield_now,
|
|
};
|
|
use std::cell::{Cell, RefCell};
|
|
use std::fmt;
|
|
use std::rc::Rc;
|
|
use std::sync::OnceLock;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
|
|
|
|
static START: OnceLock<Instant> = 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())
|
|
}
|
|
|
|
fn queue_log(handle: &ThreadHandle, expected: usize, message: impl Into<String>) {
|
|
let message = message.into();
|
|
let queued = handle.queue_task(move || {
|
|
log_event_impl(expected, format_args!("{message}"));
|
|
});
|
|
assert!(queued, "main thread should accept log task {expected}");
|
|
}
|
|
|
|
fn queue_log_microtask(handle: &ThreadHandle, expected: usize, message: impl Into<String>) {
|
|
let message = message.into();
|
|
let queued = handle.queue_microtask(move || {
|
|
log_event_impl(expected, format_args!("{message}"));
|
|
});
|
|
assert!(queued, "main thread should accept log microtask {expected}");
|
|
}
|
|
|
|
#[ruin_runtime::main]
|
|
fn main() {
|
|
START.get_or_init(Instant::now);
|
|
|
|
queue_microtask(|| log_event!(1, "[main] boot microtask: prime UI state"));
|
|
|
|
queue_future(async {
|
|
log_event!(2, "[main] future: fetch scene metadata");
|
|
yield_now().await;
|
|
log_event!(4, "[main] future: scene metadata cached");
|
|
});
|
|
|
|
queue_microtask(|| {
|
|
log_event!(3, "[main] microtask queued immediately");
|
|
});
|
|
|
|
let main_handle = current_thread_handle();
|
|
queue_task(move || {
|
|
log_event!(
|
|
5,
|
|
"[main] boot task: paint first frame and start background worker"
|
|
);
|
|
|
|
let dashboard_interval = Rc::new(RefCell::new(None::<IntervalHandle>));
|
|
let dashboard_ticks = Rc::new(Cell::new(0usize));
|
|
{
|
|
let slot = Rc::clone(&dashboard_interval);
|
|
let ticks = Rc::clone(&dashboard_ticks);
|
|
set_dashboard_interval(slot, ticks);
|
|
}
|
|
|
|
set_timeout(Duration::from_millis(30), || {
|
|
log_event!(11, "[main] timeout: network snapshot ready");
|
|
});
|
|
|
|
let main_for_worker = main_handle.clone();
|
|
let worker = spawn_worker(
|
|
move || {
|
|
queue_log(
|
|
&main_for_worker,
|
|
6,
|
|
"[worker->main] startup task: prepare upload queue",
|
|
);
|
|
|
|
{
|
|
let main_for_microtask = main_for_worker.clone();
|
|
queue_microtask(move || {
|
|
queue_log(
|
|
&main_for_microtask,
|
|
7,
|
|
"[worker->main] microtask: inspect staging buffers",
|
|
);
|
|
});
|
|
}
|
|
|
|
{
|
|
let main_for_future = main_for_worker.clone();
|
|
queue_future(async move {
|
|
queue_log(
|
|
&main_for_future,
|
|
8,
|
|
"[worker->main] future: compile shader variants",
|
|
);
|
|
yield_now().await;
|
|
queue_log(
|
|
&main_for_future,
|
|
9,
|
|
"[worker->main] future: shader cache is warm",
|
|
);
|
|
});
|
|
}
|
|
|
|
{
|
|
let main_for_task = main_for_worker.clone();
|
|
queue_task(move || {
|
|
queue_log(
|
|
&main_for_task,
|
|
10,
|
|
"[worker->main] task: upload static geometry",
|
|
);
|
|
});
|
|
}
|
|
|
|
let sample_interval = Rc::new(RefCell::new(None::<IntervalHandle>));
|
|
let sample_count = Rc::new(Cell::new(0usize));
|
|
{
|
|
let slot = Rc::clone(&sample_interval);
|
|
let count = Rc::clone(&sample_count);
|
|
let main_for_samples = main_for_worker.clone();
|
|
let handle = set_interval(Duration::from_millis(40), move || {
|
|
let next = count.get() + 1;
|
|
count.set(next);
|
|
queue_log(
|
|
&main_for_samples,
|
|
if next == 1 { 12 } else { 17 },
|
|
format!("[worker->main] interval: sample batch {next} ready"),
|
|
);
|
|
if next == 2 {
|
|
let interval = slot.borrow_mut().take().expect("interval should exist");
|
|
clear_interval(&interval);
|
|
queue_log(&main_for_samples, 18, "[worker->main] interval stopped");
|
|
}
|
|
});
|
|
*sample_interval.borrow_mut() = Some(handle);
|
|
}
|
|
|
|
{
|
|
let main_for_flush = main_for_worker.clone();
|
|
set_timeout(Duration::from_millis(110), move || {
|
|
queue_log_microtask(
|
|
&main_for_flush,
|
|
20,
|
|
"[worker->main] timeout: flushed final upload batch",
|
|
);
|
|
});
|
|
}
|
|
},
|
|
|| log_event!(21, "[main] worker exited"),
|
|
);
|
|
|
|
set_timeout(Duration::from_millis(70), move || {
|
|
let queued = worker.queue_task({
|
|
let main_from_remote_task = main_handle.clone();
|
|
move || {
|
|
queue_log(
|
|
&main_from_remote_task,
|
|
15,
|
|
"[worker->main] remote task: upload late texture atlas",
|
|
);
|
|
|
|
let main_from_remote_microtask = main_from_remote_task.clone();
|
|
queue_microtask(move || {
|
|
queue_log(
|
|
&main_from_remote_microtask,
|
|
16,
|
|
"[worker->main] remote microtask: retire staging pages",
|
|
);
|
|
});
|
|
}
|
|
});
|
|
|
|
log_event!(
|
|
14,
|
|
"[main] timeout: queue late texture upload on worker (queued={queued})"
|
|
);
|
|
});
|
|
|
|
set_timeout(Duration::from_millis(140), || {
|
|
log_event!(22, "[main] final timeout: commit frame statistics");
|
|
});
|
|
});
|
|
}
|
|
|
|
fn set_dashboard_interval(slot: Rc<RefCell<Option<IntervalHandle>>>, ticks: Rc<Cell<usize>>) {
|
|
let slot_for_callback = Rc::clone(&slot);
|
|
let handle = set_interval(Duration::from_millis(50), move || {
|
|
let next = ticks.get() + 1;
|
|
ticks.set(next);
|
|
if next == 1 {
|
|
log_event!(13, "[main] interval: dashboard tick 1");
|
|
return;
|
|
}
|
|
|
|
let interval = slot_for_callback
|
|
.borrow_mut()
|
|
.take()
|
|
.expect("interval should exist");
|
|
clear_interval(&interval);
|
|
log_event!(19, "[main] interval: dashboard tick 2 and stop");
|
|
});
|
|
*slot.borrow_mut() = Some(handle);
|
|
}
|