odem_rs_core/simulator/mod.rs
1//! This module contains the heart of the library: the [Simulator] and the
2//! [simulation context].
3//!
4//! Combined, these two structures implement *executor* and *spawner* for the
5//! *future*-based implementation of coroutines. Conceptually, the executor is
6//! responsible for selecting the next future to be executed, and the spawner
7//! allows new futures to be injected into the executor.
8//!
9//! In our implementation, the executor is realized by the [Simulator], the
10//! spawner is realized by the [simulation context], and futures are realized
11//! through [Continuations](Continuation).
12//!
13//! [simulation context]: Sim
14
15use core::{
16 any::Any,
17 cell::Cell,
18 fmt,
19 future::Future,
20 num::NonZero,
21 ops::Add,
22 pin::{Pin, pin},
23 ptr::NonNull,
24 task::{Context, RawWaker, RawWakerVTable, Waker},
25};
26
27use crate::{
28 Active, Puck,
29 calendar::{Calendar, Partition},
30 config::{Config, Time},
31 continuation::{Continuation, Puck as ContPuck, Share, token},
32 erased,
33 error::{CausalityError, Deadlock},
34 fsm::*,
35 job::{Builder as JobBuilder, Job},
36 ptr::{AsIrc, IntrusivelyCounted, Irc, IrcBox, IrcBoxed, Lease, LeasedMut},
37};
38
39pub mod details;
40mod ops;
41
42/// The type used to mark the agents of incoming continuations.
43///
44/// This is used to prevent interleaved execution of continuations from
45/// different agents by enforcing the following property: all continuations
46/// belonging to some agent `A` are sorted before any continuations belonging to
47/// agent `B` iff any continuation of agent `A` is scheduled before all the
48/// continuations of agent `B`.
49pub(crate) type Mark = u64;
50
51/// The precedence type for ordering the continuations that are executed as part
52/// of the same agent.
53///
54/// The way this type works is by partitioning an integer range into a set of
55/// non-overlapping intervals, each represented by the first integer that is
56/// part of the respective interval. Initially, the interval contains the whole
57/// range (`0..1`) but can be refined at every level into smaller subintervals
58/// that are sorted according to their first integer and nesting level. This has
59/// the effect of allowing subprocesses to inherit the precedence of their
60/// parent and to recursively allow the same for their children. Since the
61/// number of bits is finite, this is bound to fail at some level of nesting,
62/// but it should not place undue limitations on the user.
63pub type Prec = Partition<u32>;
64
65/* ****************************************************** Simulation Function */
66
67/// Simplified function that creates a new [default] configuration, runs the
68/// provided `async` closure to completion, and returns it.
69///
70/// Use it in situations when both input and output of the simulation are
71/// communicated through a configuration initialized to a default value.
72///
73/// [default]: Default
74#[track_caller]
75pub fn simulation<C, F>(main: F) -> Result<C, Deadlock<C::Time>>
76where
77 C: Config + Default,
78 F: AsyncFnOnce(&Sim<C>),
79{
80 let sim = Simulator::default();
81 sim.run(main).map(move |()| sim.into_inner())
82}
83
84/* **************************************************************** Simulator */
85
86/// The principal driver of a simulation run which can be initialized using a
87/// [configuration].
88///
89/// [configuration]: Config
90#[derive(Copy, Clone, Default)]
91pub struct Simulator<C>(C);
92
93impl<C> Simulator<C> {
94 /// Creates a new simulator from a configuration.
95 pub const fn new(config: C) -> Self {
96 Self(config)
97 }
98
99 /// Converts the simulator back into a configuration.
100 pub fn into_inner(self) -> C {
101 self.0
102 }
103
104 /// Grants shared access to the inner configuration.
105 pub fn inner(&self) -> &C {
106 &self.0
107 }
108
109 /// Grants mutable access to the inner configuration.
110 pub fn inner_mut(&mut self) -> &mut C {
111 &mut self.0
112 }
113}
114
115impl<C: Config> Simulator<C> {
116 /// Executes the simulation model provided in the closure and returns its
117 /// result in the `Ok`-variant of a [Result].
118 ///
119 /// The `Err`-variant is reserved for errors that occur during the
120 /// event-loop. Only a [Deadlock] can be reported.
121 #[track_caller]
122 pub fn run<F, R>(&self, main: F) -> Result<R, Deadlock<C::Time>>
123 where
124 F: AsyncFnOnce(&Sim<C>) -> R,
125 {
126 // Create a fresh simulation context.
127 // SAFETY: the reference to `self` outlives the simulation context.
128 // (done to prevent mentioning the lifetime in the simulation context)
129 let sim = pin!(unsafe { Sim::new(&self.0) });
130 let sim = Irc::new(sim);
131
132 // Register the simulation context thread-locally to support tracing.
133 // The hook will take care of deregistration in its destructor.
134 let _hook = erased::hook(sim.clone());
135
136 // Bootstrap the root process.
137 let share = pin!(Share::root(sim.clone()));
138 let share = share.into_ref();
139
140 #[cfg(feature = "tracing")]
141 let span = tracing::error_span!(parent: None, "SimMain").entered();
142 let root = pin!(JobBuilder::root().with_actions(main(&sim)).finish());
143 let mut puck = Job::boot(root, share);
144
145 #[cfg(feature = "tracing")]
146 drop(span);
147
148 // Schedule the root job at the current model-time.
149 puck.as_irc().brand(|task, once| {
150 let idle = task.token(once).into_idle().unwrap();
151 sim.calendar().activate(task.clone(), idle);
152 });
153
154 {
155 let waker = ShallowWaker::new(sim.clone());
156 // SAFETY: the `ShallowWaker` is valid for the lifetime of the waker
157 let waker = unsafe { waker.as_waker() };
158 let mut cx = Context::from_waker(&waker);
159 let root = puck.as_ref();
160
161 // Run the simulator until the root job terminates.
162 while let Some(cont) = sim.calendar.extract() {
163 let root_terminated = cont.brand(|cont, once| {
164 let next = cont.token(once).into_next().unwrap();
165 let _span = cont.enter_span();
166 let busy: token::Busy<'_> = cont.state().transition(next, ());
167
168 // set the active continuation
169 sim.active.set(Some(ContPuck::new(cont.clone(), &busy)));
170
171 // poll the continuation and check whether the root continuation terminated
172 cont.poll(busy, &mut cx).is_ready() && core::ptr::eq(cont.detach(), root)
173 });
174
175 if root_terminated {
176 break;
177 }
178 }
179
180 // Clear the active slot on exit.
181 sim.active.set(None);
182 }
183
184 // Extract and return the result.
185 puck.result().ok_or(Deadlock(sim.now()))
186 }
187
188 /// Executes the simulation model provided in the closure that has to
189 /// return a [`Result`].
190 ///
191 /// The result of a simulation run can also be a [`Deadlock`] if the
192 /// scheduler detects that it is not possible to schedule any more
193 /// continuations after some model-time has passed.
194 #[track_caller]
195 pub fn with_result<F, T, E>(&self, main: F) -> Result<T, E>
196 where
197 F: AsyncFnOnce(&Sim<C>) -> Result<T, E>,
198 E: From<Deadlock<C::Time>>,
199 {
200 self.run(main).unwrap_or_else(|err| Err(E::from(err)))
201 }
202}
203
204impl<C> From<C> for Simulator<C> {
205 fn from(config: C) -> Self {
206 Self::new(config)
207 }
208}
209
210/* ******************************************************* Simulation Context */
211
212/// Simulation context that can be used to advance the [model-time], spawn
213/// additional [Processes] or [Jobs], access [global data], the [current]
214/// model-time and the [active] process, among other things.
215///
216/// It can only be accessed during a simulation run.
217///
218/// [Processes]: crate::agent::Agent
219/// [Jobs]: Job
220/// [global data]: Config::Data
221/// [model-time]: Config::Time
222/// [current]: Sim::now
223/// [active]: Sim::active
224pub struct Sim<C: ?Sized + Config = ()> {
225 /// The event calendar.
226 calendar: Calendar<C>,
227 /// The currently active continuation.
228 active: Cell<Option<ContPuck<C>>>,
229 /// The inner box to enable intrusive reference counting.
230 sim_box: IrcBox,
231 /// A global counter for unique agent-IDs.
232 #[cfg(not(feature = "alloc"))]
233 pid_gen: Cell<NonZero<usize>>,
234 /// A per-agent-type counter for unique agent-IDs.
235 #[cfg(feature = "alloc")]
236 pid_gen: core::cell::RefCell<hashbrown::HashMap<core::any::TypeId, NonZero<usize>>>,
237 /// Pointer to the user-supplied configuration.
238 config: NonNull<C>,
239}
240
241impl<C: Config> Sim<C> {
242 /// Creates a new simulation context from a [configuration](Config).
243 ///
244 /// # Safety
245 /// The caller has to ensure that the reference to the configuration
246 /// outlives the created simulation context.
247 ///
248 /// This could also be solved via the borrow checker but would require the
249 /// introduction of an additional lifetime which makes referring to the
250 /// simulation context in function signatures quite a bit less ergonomic.
251 #[track_caller]
252 unsafe fn new<'p>(config: &C) -> Lease<'p, Self> {
253 Lease::new(Sim {
254 calendar: Calendar::new(config),
255 active: Cell::new(None),
256 sim_box: IrcBox::default(),
257 pid_gen: {
258 #[cfg(not(feature = "alloc"))]
259 {
260 Cell::new(NonZero::<usize>::MIN)
261 }
262 #[cfg(feature = "alloc")]
263 {
264 core::cell::RefCell::new(hashbrown::HashMap::new())
265 }
266 },
267 config: NonNull::from(config),
268 })
269 }
270
271 /// Gets a [simulation context] from a waker.
272 ///
273 /// This can be used to recover the current simulation context from the
274 /// context argument if the configuration is known, making it possible to
275 /// distribute the simulation context by piggybacking on Rust's implicit
276 /// [Context].
277 ///
278 /// [simulation context]: Sim
279 pub fn from_context(context: &Context<'_>) -> Option<Irc<Self>> {
280 let waker = context.waker();
281
282 // comparing for pointer equality is enough because there is only
283 // one vtable for all simulators
284 if core::ptr::eq(waker.vtable(), &SIM_VTABLE) {
285 // SAFETY: it's a shallow waker, so it is safe to reconstruct the
286 // simulation context
287 let waker = unsafe { &*(waker.data() as *const ShallowWaker) };
288 waker.0.clone().downcast::<Self>().ok()
289 } else {
290 // none of our wakers
291 None
292 }
293 }
294}
295
296impl<C: ?Sized + Config> Sim<C> {
297 /// Removes the currently active continuation.
298 pub(crate) fn unslot<'brand>(
299 &self,
300 task: &Continuation<'brand, C>,
301 busy: token::Busy<'brand>,
302 ) -> token::Idle<'brand> {
303 let _puck = self.active.take();
304
305 // sanity check in debug mode
306 debug_assert!(
307 _puck.map(|puck| puck.is_same(task)).unwrap_or(true),
308 "unslotted task in state `Busy` was not in the active slot"
309 );
310
311 task.state().transition(busy, ())
312 }
313
314 /// Returns a shared reference to the configuration.
315 pub fn config(&self) -> &C {
316 // SAFETY: The configuration outlives the simulation context per the
317 // invariant on the unsafe `Self::new`.
318 unsafe { self.config.as_ref() }
319 }
320
321 /// Returns a reference to the shared data.
322 pub fn global(&self) -> &C::Data {
323 self.config().global_data()
324 }
325
326 /// Returns the [Puck] of the currently active continuation.
327 pub fn active(&self) -> ContPuck<C> {
328 let active = self.active.take();
329 let result = active.clone();
330 self.active.set(active);
331 result.expect("no active continuation")
332 }
333
334 /// Changes the rank of the currently active [`Agent`](crate::agent::Agent).
335 ///
336 /// The new rank takes immediate effect and causes the rearrangement
337 /// of all jobs currently scheduled for the active agent, both in the
338 /// present and future.
339 ///
340 /// Lowering the rank of the active `Agent` can lead to another `Agent`
341 /// gaining control if one with a (now) higher rank has jobs scheduled at
342 /// the current model time. This change takes effect once the currently
343 /// active agent suspends.
344 pub fn update_rank(&self, rank: C::Rank) {
345 self.active().share().update_rank(rank);
346 }
347
348 /// Returns the current model time.
349 pub fn now(&self) -> C::Time {
350 self.calendar().now()
351 }
352
353 /// Schedules the currently active continuation at a later model time.
354 ///
355 /// Panics if no continuation is currently active or if the proposed model time has
356 /// already passed. For a non-panicking version of this method, see
357 /// [`try_advance`].
358 ///
359 /// [`try_advance`]: Self::try_advance
360 pub fn advance(&self, dt: impl Into<C::Time>) -> impl Future<Output = ()> + '_
361 where
362 C::Time: Add<Output = C::Time>,
363 {
364 ops::TryAdvance::new(self, dt.into()).unwrap()
365 }
366
367 /// Schedules the currently active continuation at a later model time.
368 ///
369 /// This method has the same function as [`advance`] but takes an absolute
370 /// time-point for the proposed reactivation time rather than a relative
371 /// one.
372 ///
373 /// For a non-panicking alternative, see [`try_advance_to`].
374 ///
375 /// [`advance`]: Self::advance
376 /// [`try_advance_to`]: Self::try_advance_to
377 pub fn advance_to(&self, time: impl Into<C::Time>) -> impl Future<Output = ()> + '_ {
378 ops::TryAdvanceTo::new(self, time.into()).unwrap()
379 }
380
381 /// Attempts to advance the currently active continuation to a later model time.
382 ///
383 /// Fails with an `Err`-result if either no continuation is active or if the
384 /// proposed model time has already passed.
385 pub fn try_advance(
386 &self,
387 dt: impl Into<C::Time>,
388 ) -> impl Future<Output = Result<(), CausalityError<C::Time>>> + '_
389 where
390 C::Time: Add<Output = C::Time>,
391 {
392 ops::TryAdvance::new(self, dt.into())
393 }
394
395 /// Attempts to advance the currently active continuation to a later model time.
396 ///
397 /// This method has the same function as [`try_advance`] but takes an
398 /// absolute time-point for the proposed reactivation time rather than a
399 /// relative one.
400 ///
401 /// [`try_advance`]: Self::try_advance
402 pub fn try_advance_to(
403 &self,
404 time: impl Into<C::Time>,
405 ) -> impl Future<Output = Result<(), CausalityError<C::Time>>> + '_ {
406 ops::TryAdvanceTo::new(self, time.into())
407 }
408
409 /// Activates a new continuation by binding it to the simulation context,
410 /// scheduling it at the current model time, and returning a [`Puck`] to it.
411 ///
412 /// # Example Usage
413 ///
414 /// You will usually want to [`pin`] the active object to pass it into the
415 /// function like this:
416 ///
417 /// ```
418 /// # use {core::pin::pin, odem_rs_core::{simulator::Sim, job::Job}};
419 /// # async fn sim_main(sim: &Sim) {
420 /// let job = pin!(Job::new(async { /* do important work */ }));
421 /// let puck = sim.activate(job);
422 /// # }
423 /// ```
424 ///
425 /// # Note
426 ///
427 /// The function expects a pinned mutable reference to a [`Lease`] to an
428 /// active object to utilize the borrow-checker to prevent certain
429 /// usage errors statically. Without going into too much technical detail,
430 /// the argument is made into the equivalent of a reference with move
431 /// semantics. Once this function has been called, it is *as if* the
432 /// value has been moved into the function, preventing access to it from the
433 /// caller's context, even though only a reference has been moved.
434 ///
435 /// Specifically, the following error is prevented:
436 /// ```compile_fail,E0505
437 /// # use {core::pin::pin, odem_rs_core::{simulator::Sim, job::Job}};
438 /// # async fn sim_main(sim: &Sim) {
439 /// let mut job = pin!(Job::new(async { /* do important work */ }));
440 /// sim.activate(job.as_mut()); // allowed
441 /// // later
442 /// sim.activate(job); // error: cannot move out of `job` because it is borrowed
443 /// # }
444 /// ```
445 ///
446 /// Please refer to the documentation of `Lease` for technical details.
447 pub fn activate<'a, A>(&'a self, actions: Pin<LeasedMut<'a, A>>) -> A::Puck<'a>
448 where
449 A: Active<C>,
450 {
451 // bind the simulation context
452 let puck = self.bind(actions);
453
454 // activate the puck
455 puck.as_irc().brand(|task, once| {
456 // successfully bound continuations are in state 'Idle'
457 let idle = task.token(once).into_idle().unwrap();
458
459 // set the correct span
460 let _span = task.enter_span();
461
462 // schedule the puck
463 self.calendar().activate(task.clone(), idle);
464
465 puck
466 })
467 }
468
469 /// Activates a new continuation by binding it to the simulation context,
470 /// scheduling it at a future model time, and returning a [`Puck`] to it.
471 ///
472 /// The function panics if the model time for activation has already passed.
473 /// For a non-panicking version of this method, see [`try_schedule`].
474 ///
475 /// # Example Usage
476 ///
477 /// You will usually want to [`pin`] the active object to pass it into the
478 /// function like this:
479 ///
480 /// ```
481 /// # use {core::pin::pin, odem_rs_core::{simulator::Sim, job::Job}};
482 /// # async fn sim_main(sim: &Sim) {
483 /// let job = pin!(Job::new(async { /* do important work */ }));
484 /// let puck = sim.schedule(job, 2.0); // schedule in 2 units of time
485 /// # }
486 /// ```
487 ///
488 /// # Note
489 ///
490 /// The notes from [`Self::activate`] apply here as well.
491 ///
492 /// [`try_schedule`]: Self::try_schedule
493 #[track_caller]
494 pub fn schedule<'a, A>(
495 &'a self,
496 actions: Pin<LeasedMut<'a, A>>,
497 dt: impl Into<C::Time>,
498 ) -> A::Puck<'a>
499 where
500 A: Active<C>,
501 C::Time: Add<Output = C::Time>,
502 {
503 self.try_schedule(actions, dt).unwrap_or_else(|err| {
504 #[cfg(feature = "tracing")]
505 tracing::error!(%err);
506 panic!("{}", err);
507 })
508 }
509
510 /// Activates a new continuation, schedule it at a later model time, and
511 /// returns a [`Puck`] to it.
512 ///
513 /// Doesn't panic if the proposed model time has already passed, returning
514 /// a [`CausalityError`] instead. For the panicking version, see
515 /// [`schedule`].
516 ///
517 /// [`schedule`]: Self::schedule
518 pub fn try_schedule<'a, A>(
519 &'a self,
520 actions: Pin<LeasedMut<'a, A>>,
521 dt: impl Into<C::Time>,
522 ) -> Result<A::Puck<'a>, CausalityError<C::Time>>
523 where
524 A: Active<C>,
525 C::Time: Add<Output = C::Time>,
526 {
527 use core::cmp::Ordering::*;
528 let time: C::Time = self.now() + dt.into();
529
530 match time.partial_cmp(&self.now()) {
531 None | Some(Less) => Err(CausalityError {
532 cause: self.now(),
533 effect: time,
534 }),
535 Some(order) => {
536 // bind the simulation context
537 let puck = self.bind(actions);
538
539 // schedule or activate the puck
540 puck.as_irc().brand(|task, once| {
541 // successfully bound continuations are in state 'Idle'
542 let idle = task.token(once).into_idle().unwrap();
543
544 // set the correct span
545 let _span = task.enter_span();
546
547 // schedule the puck
548 let calendar = self.calendar();
549
550 let irc = task.clone();
551 if order == Greater {
552 calendar.schedule(irc, idle, time);
553 } else {
554 calendar.activate(irc, idle);
555 }
556
557 Ok(puck)
558 })
559 }
560 }
561 }
562
563 /// Returns an awaitable [`Future`] that will suspend the currently executed
564 /// continuation and reschedule it after every other continuation scheduled
565 /// at the current model time had a chance to run.
566 ///
567 /// The free function [`defer`] has the same semantics but reconstructs the
568 /// simulation context through dynamic dispatching.
569 ///
570 /// [`defer`]: crate::ops::defer
571 pub fn defer(&self) -> impl Future<Output = ()> + '_ {
572 ops::Defer::new(self)
573 }
574
575 /// Generates a new, simulation-run-unique agent id.
576 pub(crate) fn pid_gen<I: 'static>(&self) -> NonZero<usize> {
577 #[cfg(not(feature = "alloc"))]
578 // increment the global agent counter to get a new pid
579 {
580 let next = self.pid_gen.get();
581 self.pid_gen
582 .set(next.checked_add(1).unwrap_or(NonZero::<usize>::MIN));
583 next
584 }
585
586 #[cfg(feature = "alloc")]
587 // look up the agent-type-specific pid to increment
588 {
589 let mut pid_gen = self.pid_gen.borrow_mut();
590 let counter = pid_gen
591 .entry(core::any::TypeId::of::<I>())
592 .or_insert(NonZero::<usize>::MIN);
593 let next = *counter;
594 *counter = next.checked_add(1).unwrap_or(NonZero::<usize>::MIN);
595 next
596 }
597 }
598
599 /// Returns a reference to the [event calendar].
600 ///
601 /// [event calendar]: crate::DefaultPlan
602 pub(crate) fn calendar(&self) -> &Calendar<C> {
603 &self.calendar
604 }
605
606 /// Activates the active object, returning a [`Puck`] on success and an
607 /// [`Error`] if the operation failed.
608 ///
609 /// This is slightly more complicated than simply calling [`bind`] on the
610 /// [`Active`]-trait because we also need to store the *actual* type of the
611 /// instance being activated to allow dynamic dispatching to it later.
612 ///
613 /// [`bind`]: Active::bind
614 fn bind<'a, A>(&'a self, mut actions: Pin<LeasedMut<'a, A>>) -> A::Puck<'a>
615 where
616 A: Active<C>,
617 {
618 // create a raw reference to the pinned continuation;
619 // this will be the most specialized version, enabling dynamic dispatch
620 let vtab = NonNull::from(unsafe { actions.as_mut().project().get_unchecked_mut() });
621
622 // create a reference to the context of the currently active object
623 // TODO: this seems unsafe due to the lifetime extension
624 let active = unsafe { &*self.active.as_ptr() }.as_ref().unwrap();
625 let share = active.share();
626
627 // create the puck by binding
628 let puck = Active::bind(actions, share);
629
630 // override the vptr
631 unsafe {
632 puck.as_ref().set_vptr(vtab);
633 }
634
635 // return the puck
636 puck
637 }
638}
639
640// Configuration-erased dyn-safe version of the simulation context.
641impl<C: Config> erased::Sim for Sim<C> {
642 fn active(&self) -> Option<erased::ContPuck> {
643 let active = self.active.take();
644 self.active.set(active.clone());
645 active.map(Into::into)
646 }
647
648 fn now(&self) -> &dyn fmt::Display {
649 // SAFETY: repr(transparent) guarantees that the two structures are laid
650 // out identically in memory, making this transmutation safe.
651 unsafe { core::mem::transmute::<&Sim<C>, &DisplayHelper<C>>(self) }
652 }
653
654 fn global(&self) -> &dyn Any {
655 self.global()
656 }
657
658 fn waker(&self) -> RawWaker {
659 self.active().into_waker()
660 }
661
662 fn defer(&self) {
663 if let Some(active) = self.active.take() {
664 active.into_inner().brand(|active, once| {
665 let busy = active
666 .token(once)
667 .into_busy()
668 .expect("active continuation should be 'Busy'");
669 self.calendar().defer(active.clone(), busy);
670 });
671 }
672 }
673}
674
675impl<C: ?Sized + Config> fmt::Debug for Sim<C>
676where
677 C::Plan: fmt::Debug,
678{
679 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
680 let mut debug = f.debug_struct("Sim");
681 let active = self.active.take();
682 self.active.set(active.clone());
683
684 debug
685 .field("active", &active)
686 .field("calendar", &self.calendar)
687 .finish()
688 }
689}
690
691/// *Newtype* adapter that implements [Display](fmt::Display) for a simulation
692/// context by displaying the current model-time.
693#[repr(transparent)]
694struct DisplayHelper<C: Config>(Sim<C>);
695
696impl<C: Config> fmt::Display for DisplayHelper<C> {
697 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
698 self.0.now().format(f)
699 }
700}
701
702unsafe impl<C: ?Sized + Config> IntrusivelyCounted for Sim<C> {
703 #[inline(always)]
704 fn irc_box(&self) -> &IrcBox<dyn IrcBoxed> {
705 &self.sim_box
706 }
707}
708
709/* ************************************************************ Shallow Waker */
710
711/// Internal waker that can only be used to create a copy of the currently
712/// active continuation or to defer it.
713struct ShallowWaker(Irc<dyn erased::Sim>);
714
715/// Virtual function table for the [simulation executor](Simulator), independent
716/// of the [configuration](Config).
717///
718/// This construction ensures that there is a globally unique address for the
719/// simulators, allowing us to restore the [simulation context](Sim) from the
720/// context and the configuration.
721static SIM_VTABLE: RawWakerVTable = RawWakerVTable::new(
722 ShallowWaker::clone,
723 ShallowWaker::nop,
724 ShallowWaker::wake_by_ref,
725 ShallowWaker::nop,
726);
727
728impl ShallowWaker {
729 /// Creates a [Waker] for a simulator that can be cloned into [Puck]
730 /// wakers. Waking this waker has the effect of deferring the currently
731 /// active continuation, i.e., the continuation is reawakened at the current
732 /// model time after all the other continuations have run.
733 fn new<C: Config>(sim: Irc<Sim<C>>) -> Self {
734 // erase the concrete configuration
735 Self(Irc::map(sim, |inner| inner as &dyn erased::Sim))
736 }
737
738 /// Constructs a [Waker] from the reference of a shallow waker.
739 ///
740 /// # Safety
741 /// The caller is responsible to ensure that the reference to the shallow
742 /// waker is valid for the lifetime of the waker.
743 unsafe fn as_waker(&self) -> Waker {
744 unsafe {
745 let waker = RawWaker::new(self as *const Self as *const (), &SIM_VTABLE);
746 Waker::from_raw(waker)
747 }
748 }
749
750 /// No-op waker-function.
751 fn nop(_: *const ()) {}
752
753 /// Constructs a [RawWaker] for the active process.
754 unsafe fn clone(this: *const ()) -> RawWaker {
755 unsafe {
756 // perform a dynamic dispatch to recover the configuration
757 (*(this as *const Self)).0.waker()
758 }
759 }
760
761 /// Defers the currently active process.
762 unsafe fn wake_by_ref(this: *const ()) {
763 unsafe {
764 // perform a dynamic dispatch to recover the configuration
765 (*(this as *const Self)).0.defer();
766 }
767 }
768}