1use std::cell::{Cell, RefCell};
4use std::future::Future;
5use std::pin::Pin;
6use std::rc::Rc;
7use std::sync::Arc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::sync::mpsc::{self, RecvTimeoutError};
10use std::task::{Context, Poll, RawWaker, RawWakerVTable, Waker};
11use std::time::Duration;
12use std::{mem, thread};
13
14use gwr_track::entity::Entity;
15use rand::SeedableRng;
16use rand::rngs::StdRng;
17use rand::seq::SliceRandom;
18
19use crate::time::clock::Clock;
20use crate::time::simtime::SimTime;
21use crate::types::SimResult;
22
23fn no_op(_: *const ()) {}
24
25unsafe fn drop_task(data: *const ()) {
26 unsafe {
27 drop(Rc::from_raw(data as *const Task));
28 }
29}
30
31static VTABLE: RawWakerVTable = RawWakerVTable::new(clone_raw_waker, wake_task, no_op, drop_task);
32
33fn task_raw_waker(task: Rc<Task>) -> RawWaker {
34 let ptr = Rc::into_raw(task) as *const ();
35 RawWaker::new(ptr, &VTABLE)
36}
37
38fn waker_for_task(task: Rc<Task>) -> Waker {
39 unsafe { Waker::from_raw(task_raw_waker(task)) }
40}
41
42unsafe fn clone_raw_waker(data: *const ()) -> RawWaker {
43 unsafe {
44 let rc_task = Rc::from_raw(data as *const Task);
49 let clone = rc_task.clone();
50 mem::forget(rc_task);
51 let ptr = Rc::into_raw(clone) as *const ();
52 RawWaker::new(ptr, &VTABLE)
53 }
54}
55
56unsafe fn wake_task(data: *const ()) {
57 unsafe {
58 let rc_task = Rc::from_raw(data as *const Task);
61 let cloned = rc_task.clone();
62 rc_task.executor_state.new_tasks.borrow_mut().push(cloned);
63 }
64}
65
66struct Task {
67 future: RefCell<Option<Pin<Box<dyn Future<Output = SimResult>>>>>,
68 executor_state: Rc<ExecutorState>,
69}
70
71impl Task {
72 pub fn new(
73 future: impl Future<Output = SimResult> + 'static,
74 executor_state: Rc<ExecutorState>,
75 ) -> Task {
76 Task {
77 future: RefCell::new(Some(Box::pin(future))),
78 executor_state,
79 }
80 }
81
82 fn poll(&self, context: &mut Context) -> Option<Poll<SimResult>> {
83 let mut future_slot = self.future.borrow_mut();
84 let future = future_slot.as_mut()?;
85
86 let poll_result = future.as_mut().poll(context);
87 if poll_result.is_ready() {
88 future_slot.take();
89 }
90
91 Some(poll_result)
92 }
93}
94
95#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
97pub struct ExecutorStats {
98 pub futures_polled: usize,
100 pub futures_completed: usize,
102}
103
104#[derive(Clone, Copy, Debug, Eq, PartialEq)]
106pub struct ExecutorSnapshot {
107 pub stats: ExecutorStats,
109 pub active_task_count: usize,
111}
112
113struct ExecutorState {
114 task_queue: RefCell<Vec<Rc<Task>>>,
115 new_tasks: RefCell<Vec<Rc<Task>>>,
116 time: RefCell<SimTime>,
117 randomize_task_order: Cell<bool>,
118 task_order_rng: RefCell<StdRng>,
119 stats: Cell<ExecutorStats>,
120}
121
122impl ExecutorState {
123 pub fn new(top: &Rc<Entity>) -> Self {
124 Self {
125 task_queue: RefCell::new(Vec::new()),
126 new_tasks: RefCell::new(Vec::new()),
127 time: RefCell::new(SimTime::new(top)),
128 randomize_task_order: Cell::new(false),
129 task_order_rng: RefCell::new(StdRng::seed_from_u64(rand::random())),
130 stats: Cell::new(ExecutorStats::default()),
131 }
132 }
133}
134
135#[derive(Clone)]
144pub struct Executor {
145 state: Rc<ExecutorState>,
146}
147
148impl Executor {
149 pub(crate) fn run(&self) -> SimResult {
151 self.run_internal(None, |_| Ok(()))
152 }
153
154 pub(crate) fn run_with_observer(
157 &self,
158 minimum_interval: Duration,
159 observer: impl FnMut(ExecutorSnapshot) -> SimResult,
160 ) -> SimResult {
161 if minimum_interval.is_zero() {
162 return Err(crate::types::SimError(
163 "executor observer interval must be positive".to_string(),
164 ));
165 }
166
167 let refresh_due = Arc::new(AtomicBool::new(false));
168 let timer_refresh_due = Arc::clone(&refresh_due);
169 let (stop_timer, timer_stop) = mpsc::channel::<()>();
170 let timer = thread::spawn(move || {
171 while let Err(RecvTimeoutError::Timeout) = timer_stop.recv_timeout(minimum_interval) {
175 timer_refresh_due.store(true, Ordering::Relaxed);
176 }
177 });
178
179 let result = self.run_internal(Some(&refresh_due), observer);
180 drop(stop_timer);
181 timer
182 .join()
183 .expect("executor observer timer thread panicked");
184 result
185 }
186
187 fn run_internal(
188 &self,
189 refresh_due: Option<&AtomicBool>,
190 mut observer: impl FnMut(ExecutorSnapshot) -> SimResult,
191 ) -> SimResult {
192 let mut stats = ExecutorStats::default();
193 let result = (|| {
194 loop {
195 self.step(&mut stats)?;
196
197 if refresh_due.is_some_and(|due| {
198 due.load(Ordering::Relaxed) && due.swap(false, Ordering::Relaxed)
199 }) {
200 observer(ExecutorSnapshot {
201 stats,
202 active_task_count: self.state.new_tasks.borrow().len(),
203 })?;
204 }
205
206 if self.state.new_tasks.borrow().is_empty() {
207 if self.state.time.borrow().can_exit() {
208 break;
209 }
210
211 if let Some(wakers) = self.state.time.borrow_mut().advance_time() {
212 for task_waker in wakers.into_iter() {
214 task_waker.waker.wake();
215 }
216 } else {
217 break;
218 }
219 }
220 }
221 Ok(())
222 })();
223 self.state.stats.set(stats);
224 result
225 }
226
227 fn step(&self, stats: &mut ExecutorStats) -> SimResult {
228 let mut task_queue = self.state.task_queue.borrow_mut();
230 task_queue.append(&mut self.state.new_tasks.borrow_mut());
231 if self.state.randomize_task_order.get() {
232 task_queue.shuffle(&mut *self.state.task_order_rng.borrow_mut());
233 }
234
235 for task in task_queue.drain(..) {
238 let waker = waker_for_task(task.clone());
240 let mut context = Context::from_waker(&waker);
241
242 if let Some(poll_result) = task.poll(&mut context) {
243 stats.futures_polled += 1;
244 match poll_result {
245 Poll::Ready(Err(e)) => {
246 stats.futures_completed += 1;
247 return Err(e);
248 }
249 Poll::Ready(Ok(())) => {
250 stats.futures_completed += 1;
251 }
252 Poll::Pending => {}
253 }
254 }
255 }
256 Ok(())
257 }
258
259 #[must_use]
261 pub fn stats(&self) -> ExecutorStats {
262 self.state.stats.get()
263 }
264
265 #[must_use]
266 pub fn get_clock(&self, freq_mhz: f64) -> Clock {
267 self.state.time.borrow_mut().get_clock(freq_mhz)
268 }
269
270 #[must_use]
271 pub fn time_now_ns(&self) -> f64 {
272 self.state.time.borrow().time_now_ns()
273 }
274
275 pub fn set_randomize_task_order(&self, randomize: bool) {
276 self.state.randomize_task_order.set(randomize);
277 }
278
279 pub fn set_task_order_seed(&self, seed: u64) {
280 *self.state.task_order_rng.borrow_mut() = StdRng::seed_from_u64(seed);
281 }
282}
283
284#[derive(Clone)]
286pub struct Spawner {
287 state: Rc<ExecutorState>,
288}
289
290impl Spawner {
291 pub fn spawn(&self, future: impl Future<Output = SimResult> + 'static) {
292 self.state
293 .new_tasks
294 .borrow_mut()
295 .push(Rc::new(Task::new(future, self.state.clone())));
296 }
297}
298
299#[must_use]
300pub fn new_executor_and_spawner(top: &Rc<Entity>) -> (Executor, Spawner) {
301 let state = Rc::new(ExecutorState::new(top));
302 (
303 Executor {
304 state: state.clone(),
305 },
306 Spawner { state },
307 )
308}
309
310#[cfg(test)]
311mod tests {
312 use std::future::Future;
313 use std::pin::Pin;
314 use std::rc::Rc;
315 use std::sync::atomic::{AtomicBool, Ordering};
316 use std::task::{Context, Poll};
317 use std::time::Duration;
318
319 use futures::task::noop_waker;
320 use gwr_track::entity::toplevel;
321 use gwr_track::tracker::dev_null_tracker;
322
323 use super::*;
324 use crate::time::clock::TaskWaker;
325
326 struct PendingOnce {
327 was_polled: bool,
328 }
329
330 impl Future for PendingOnce {
331 type Output = SimResult;
332
333 fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
334 if self.was_polled {
335 Poll::Ready(Ok(()))
336 } else {
337 self.was_polled = true;
338 #[expect(clippy::waker_clone_wake)]
339 cx.waker().clone().wake();
340 Poll::Pending
341 }
342 }
343 }
344
345 #[test]
346 fn run_exits_when_time_cannot_advance() {
347 let tracker = dev_null_tracker();
348 let top = toplevel(&tracker, "top");
349 let (executor, _spawner) = new_executor_and_spawner(&top);
350 let clock = executor.get_clock(1000.0);
351
352 clock
353 .shared_state
354 .waiting
355 .borrow_mut()
356 .push(vec![TaskWaker {
357 id: 0,
358 waker: noop_waker(),
359 can_exit: false,
360 }]);
361
362 executor.run().unwrap();
363 }
364
365 #[test]
366 fn executor_observer_reports_once_when_refresh_is_due() {
367 let tracker = dev_null_tracker();
368 let top = toplevel(&tracker, "top");
369 let (executor, spawner) = new_executor_and_spawner(&top);
370
371 for _ in 0..5 {
372 spawner.spawn(async { Ok(()) });
373 }
374
375 let mut snapshots = Vec::new();
376 let refresh_due = AtomicBool::new(true);
377 executor
378 .run_internal(Some(&refresh_due), |snapshot| {
379 snapshots.push(snapshot);
380 Ok(())
381 })
382 .unwrap();
383
384 assert!(!refresh_due.load(Ordering::Relaxed));
385
386 assert_eq!(
387 snapshots,
388 vec![ExecutorSnapshot {
389 stats: ExecutorStats {
390 futures_polled: 5,
391 futures_completed: 5,
392 },
393 active_task_count: 0,
394 }]
395 );
396 assert_eq!(
397 executor.stats(),
398 ExecutorStats {
399 futures_polled: 5,
400 futures_completed: 5,
401 }
402 );
403 }
404
405 #[test]
406 fn executor_observer_skips_steps_without_a_refresh_signal() {
407 let tracker = dev_null_tracker();
408 let top = toplevel(&tracker, "top");
409 let (executor, spawner) = new_executor_and_spawner(&top);
410 spawner.spawn(async { Ok(()) });
411
412 let refresh_due = AtomicBool::new(false);
413 let mut snapshots = Vec::new();
414 executor
415 .run_internal(Some(&refresh_due), |snapshot| {
416 snapshots.push(snapshot);
417 Ok(())
418 })
419 .unwrap();
420
421 assert!(snapshots.is_empty());
422 }
423
424 #[test]
425 fn executor_stats_count_pending_future_polls_separately_from_completion() {
426 let tracker = dev_null_tracker();
427 let top = toplevel(&tracker, "top");
428 let (executor, spawner) = new_executor_and_spawner(&top);
429 spawner.spawn(PendingOnce { was_polled: false });
430
431 executor.run().unwrap();
432
433 assert_eq!(
434 executor.stats(),
435 ExecutorStats {
436 futures_polled: 2,
437 futures_completed: 1,
438 }
439 );
440 }
441
442 #[test]
443 fn executor_stats_include_futures_that_complete_with_an_error() {
444 let tracker = dev_null_tracker();
445 let top = toplevel(&tracker, "top");
446 let (executor, spawner) = new_executor_and_spawner(&top);
447 spawner.spawn(async { Err(crate::types::SimError("failed".to_string())) });
448
449 let result = executor.run();
450
451 assert!(result.is_err());
452 assert_eq!(
453 executor.stats(),
454 ExecutorStats {
455 futures_polled: 1,
456 futures_completed: 1,
457 }
458 );
459 }
460
461 #[test]
462 fn executor_observer_is_not_called_from_a_step_that_returns_a_task_error() {
463 let tracker = dev_null_tracker();
464 let top = toplevel(&tracker, "top");
465 let (executor, spawner) = new_executor_and_spawner(&top);
466 spawner.spawn(async { Err(crate::types::SimError("task failed".to_string())) });
467
468 let mut snapshots = Vec::new();
469 let refresh_due = AtomicBool::new(true);
470 let error = executor
471 .run_internal(Some(&refresh_due), |snapshot| {
472 snapshots.push(snapshot);
473 Err(crate::types::SimError("observer failed".to_string()))
474 })
475 .unwrap_err();
476
477 assert_eq!(error.to_string(), "task failed");
478 assert!(snapshots.is_empty());
479 assert_eq!(
480 executor.stats(),
481 ExecutorStats {
482 futures_polled: 1,
483 futures_completed: 1,
484 }
485 );
486 }
487
488 #[test]
489 fn observer_error_occurs_after_the_entire_step_batch() {
490 let tracker = dev_null_tracker();
491 let top = toplevel(&tracker, "top");
492 let (executor, spawner) = new_executor_and_spawner(&top);
493 let completions = Rc::new(Cell::new(0));
494 for _ in 0..3 {
495 let completions = completions.clone();
496 spawner.spawn(async move {
497 completions.set(completions.get() + 1);
498 Ok(())
499 });
500 }
501
502 let refresh_due = AtomicBool::new(true);
503 let first_error = executor
504 .run_internal(Some(&refresh_due), |_| {
505 Err(crate::types::SimError("observer failed".to_string()))
506 })
507 .unwrap_err();
508 assert_eq!(first_error.to_string(), "observer failed");
509 assert_eq!(completions.get(), 3);
510 assert_eq!(
511 executor.stats(),
512 ExecutorStats {
513 futures_polled: 3,
514 futures_completed: 3,
515 }
516 );
517 }
518
519 #[test]
520 fn executor_observer_rejects_a_zero_wall_clock_interval() {
521 let tracker = dev_null_tracker();
522 let top = toplevel(&tracker, "top");
523 let (executor, _spawner) = new_executor_and_spawner(&top);
524 let error = executor
525 .run_with_observer(Duration::ZERO, |_| Ok(()))
526 .unwrap_err();
527
528 assert_eq!(
529 error.to_string(),
530 "executor observer interval must be positive"
531 );
532 }
533}