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}