use core::{
any::Any,
cell::Cell,
fmt,
future::Future,
num::NonZero,
ops::Add,
pin::{Pin, pin},
ptr::NonNull,
task::{Context, RawWaker, RawWakerVTable, Waker},
};
use crate::{
Active, Puck,
calendar::{Calendar, Partition},
config::{Config, DefaultConfig, Time},
continuation::{Continuation, Puck as ContPuck, Share, token},
erased,
error::{CausalityError, Deadlock},
fsm::*,
job::{Builder as JobBuilder, Job},
ptr::{AsIrc, IntrusivelyCounted, Irc, IrcBox, IrcBoxed, Lease, LeasedMut},
};
mod ops;
pub(crate) type Mark = u64;
pub type Prec = Partition<u32>;
#[track_caller]
pub fn simulation<C, F>(main: F) -> Result<C, Deadlock<C::Time>>
where
C: Config + Default,
F: AsyncFnOnce(&Sim<C>),
{
let sim = Simulator::default();
sim.run(main).map(move |()| sim.into_inner())
}
#[derive(Copy, Clone, Default)]
pub struct Simulator<C>(C);
impl<C> Simulator<C> {
pub const fn new(config: C) -> Self {
Self(config)
}
pub fn into_inner(self) -> C {
self.0
}
pub fn inner(&self) -> &C {
&self.0
}
pub fn inner_mut(&mut self) -> &mut C {
&mut self.0
}
}
impl<C: Config> Simulator<C> {
#[track_caller]
pub fn run<F, R>(&self, main: F) -> Result<R, Deadlock<C::Time>>
where
F: AsyncFnOnce(&Sim<C>) -> R,
{
let sim = pin!(unsafe { Sim::new(&self.0) });
let sim = Irc::new(sim);
let _hook = erased::hook(sim.clone());
let share = pin!(Share::root(sim.clone()));
let share = share.into_ref();
#[cfg(feature = "tracing")]
let span = tracing::error_span!(parent: None, "SimMain").entered();
let root = pin!(JobBuilder::root().with_actions(main(&sim)).finish());
let mut puck = Job::boot(root, share);
#[cfg(feature = "tracing")]
drop(span);
puck.as_irc().brand(|task, once| {
let idle = task.token(once).into_idle().unwrap();
sim.calendar().activate(task.clone(), idle);
});
{
let waker = ShallowWaker::new(sim.clone());
let waker = unsafe { waker.as_waker() };
let mut cx = Context::from_waker(&waker);
let root = puck.as_ref();
while let Some(cont) = sim.calendar.extract() {
let root_terminated = cont.brand(|cont, once| {
let next = cont.token(once).into_next().unwrap();
let _span = cont.enter_span();
let busy: token::Busy<'_> = cont.state().transition(next, ());
sim.active.set(Some(ContPuck::new(cont.clone(), &busy)));
cont.poll(busy, &mut cx).is_ready() && core::ptr::eq(cont.detach(), root)
});
if root_terminated {
break;
}
}
sim.active.set(None);
}
puck.result().ok_or(Deadlock(sim.now()))
}
#[track_caller]
pub fn with_result<F, T, E>(&self, main: F) -> Result<T, E>
where
F: AsyncFnOnce(&Sim<C>) -> Result<T, E>,
E: From<Deadlock<C::Time>>,
{
self.run(main).unwrap_or_else(|err| Err(E::from(err)))
}
}
impl<C> From<C> for Simulator<C> {
fn from(config: C) -> Self {
Self::new(config)
}
}
pub struct Sim<C: ?Sized + Config = DefaultConfig> {
calendar: Calendar<C>,
active: Cell<Option<ContPuck<C>>>,
sim_box: IrcBox<SimBox>,
#[cfg(not(feature = "alloc"))]
pid_gen: Cell<NonZero<usize>>,
#[cfg(feature = "alloc")]
pid_gen: core::cell::RefCell<hashbrown::HashMap<core::any::TypeId, NonZero<usize>>>,
#[cfg(feature = "std")]
thread_id: std::thread::ThreadId,
config: NonNull<C>,
}
impl<C: Config> Sim<C> {
#[track_caller]
unsafe fn new<'p>(config: &C) -> Lease<'p, Self> {
Lease::new(Sim {
calendar: Calendar::new(config),
active: Cell::new(None),
sim_box: IrcBox::new(SimBox::new()),
pid_gen: {
#[cfg(not(feature = "alloc"))]
{
Cell::new(NonZero::<usize>::MIN)
}
#[cfg(feature = "alloc")]
{
core::cell::RefCell::new(hashbrown::HashMap::new())
}
},
#[cfg(feature = "std")]
thread_id: std::thread::current().id(),
config: NonNull::from(config),
})
}
pub fn from_context(context: &Context<'_>) -> Option<Irc<Self>> {
let waker = context.waker();
if core::ptr::eq(waker.vtable(), &SIM_VTABLE) {
let waker = unsafe { &*(waker.data() as *const ShallowWaker) };
waker.0.clone().downcast::<Self>().ok()
} else {
None
}
}
}
impl<C: ?Sized + Config> Sim<C> {
pub(crate) fn unslot<'brand>(
&self,
task: &Continuation<'brand, C>,
busy: token::Busy<'brand>,
) -> token::Idle<'brand> {
let _puck = self.active.take();
debug_assert!(
_puck.map(|puck| puck.is_same(task)).unwrap_or(true),
"unslotted task in state `Busy` was not in the active slot"
);
task.state().transition(busy, ())
}
#[cfg(feature = "std")]
pub(crate) fn get_thread_id(&self) -> std::thread::ThreadId {
self.thread_id
}
pub fn config(&self) -> &C {
unsafe { self.config.as_ref() }
}
pub fn global(&self) -> &C::Data {
self.config().global_data()
}
pub fn active(&self) -> ContPuck<C> {
let active = self.active.take();
let result = active.clone();
self.active.set(active);
result.expect("no active continuation")
}
pub fn update_rank(&self, rank: C::Rank) {
self.active().share().update_rank(rank);
}
pub fn now(&self) -> C::Time {
self.calendar().now()
}
pub fn advance(&self, dt: impl Into<C::Time>) -> impl Future<Output = ()> + '_
where
C::Time: Add<Output = C::Time>,
{
ops::TryAdvance::new(self, dt.into()).unwrap()
}
pub fn advance_to(&self, time: impl Into<C::Time>) -> impl Future<Output = ()> + '_ {
ops::TryAdvanceTo::new(self, time.into()).unwrap()
}
pub fn try_advance(
&self,
dt: impl Into<C::Time>,
) -> impl Future<Output = Result<(), CausalityError<C::Time>>> + '_
where
C::Time: Add<Output = C::Time>,
{
ops::TryAdvance::new(self, dt.into())
}
pub fn try_advance_to(
&self,
time: impl Into<C::Time>,
) -> impl Future<Output = Result<(), CausalityError<C::Time>>> + '_ {
ops::TryAdvanceTo::new(self, time.into())
}
pub fn activate<'a, A>(&'a self, actions: Pin<LeasedMut<'a, A>>) -> A::Puck<'a>
where
A: Active<C>,
{
let puck = self.bind(actions);
puck.as_irc().brand(|task, once| {
let idle = task.token(once).into_idle().unwrap();
let _span = task.enter_span();
self.calendar().activate(task.clone(), idle);
puck
})
}
#[track_caller]
pub fn schedule<'a, A>(
&'a self,
actions: Pin<LeasedMut<'a, A>>,
dt: impl Into<C::Time>,
) -> A::Puck<'a>
where
A: Active<C>,
C::Time: Add<Output = C::Time>,
{
self.try_schedule(actions, dt).unwrap_or_else(|err| {
#[cfg(feature = "tracing")]
tracing::error!(%err);
panic!("{}", err);
})
}
pub fn try_schedule<'a, A>(
&'a self,
actions: Pin<LeasedMut<'a, A>>,
dt: impl Into<C::Time>,
) -> Result<A::Puck<'a>, CausalityError<C::Time>>
where
A: Active<C>,
C::Time: Add<Output = C::Time>,
{
use core::cmp::Ordering::*;
let time: C::Time = self.now() + dt.into();
match time.partial_cmp(&self.now()) {
None | Some(Less) => Err(CausalityError {
cause: self.now(),
effect: time,
}),
Some(order) => {
let puck = self.bind(actions);
puck.as_irc().brand(|task, once| {
let idle = task.token(once).into_idle().unwrap();
let _span = task.enter_span();
let calendar = self.calendar();
let irc = task.clone();
if order == Greater {
calendar.schedule(irc, idle, time);
} else {
calendar.activate(irc, idle);
}
Ok(puck)
})
}
}
}
pub fn defer(&self) -> impl Future<Output = ()> + '_ {
ops::Defer::new(self)
}
pub(crate) fn pid_gen<I: 'static>(&self) -> NonZero<usize> {
#[cfg(not(feature = "alloc"))]
{
let next = self.pid_gen.get();
self.pid_gen
.set(next.checked_add(1).unwrap_or(NonZero::<usize>::MIN));
next
}
#[cfg(feature = "alloc")]
{
let mut pid_gen = self.pid_gen.borrow_mut();
let counter = pid_gen
.entry(core::any::TypeId::of::<I>())
.or_insert(NonZero::<usize>::MIN);
let next = *counter;
*counter = next.checked_add(1).unwrap_or(NonZero::<usize>::MIN);
next
}
}
pub(crate) fn calendar(&self) -> &Calendar<C> {
&self.calendar
}
fn bind<'a, A>(&'a self, mut actions: Pin<LeasedMut<'a, A>>) -> A::Puck<'a>
where
A: Active<C>,
{
let vtab = NonNull::from(unsafe { actions.as_mut().project().get_unchecked_mut() });
let active = unsafe { &*self.active.as_ptr() }.as_ref().unwrap();
let share = active.share();
let puck = Active::bind(actions, share);
unsafe {
puck.as_ref().set_vptr(vtab);
}
puck
}
}
impl<C: Config> erased::Sim for Sim<C> {
fn active(&self) -> Option<erased::ContPuck> {
let active = self.active.take();
self.active.set(active.clone());
active.map(Into::into)
}
fn now(&self) -> &dyn fmt::Display {
unsafe { core::mem::transmute::<&Sim<C>, &DisplayHelper<C>>(self) }
}
fn global(&self) -> &dyn Any {
self.global()
}
fn waker(&self) -> RawWaker {
self.active().into_waker()
}
fn defer(&self) {
if let Some(active) = self.active.take() {
active.into_inner().brand(|active, once| {
let busy = active
.token(once)
.into_busy()
.expect("active continuation should be 'Busy'");
self.calendar().defer(active.clone(), busy);
});
}
}
}
impl<C: ?Sized + Config> fmt::Debug for Sim<C>
where
C::Plan: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug = f.debug_struct("Sim");
let active = self.active.take();
self.active.set(active.clone());
debug
.field("active", &active)
.field("calendar", &self.calendar)
.finish()
}
}
#[repr(transparent)]
struct DisplayHelper<C: Config>(Sim<C>);
impl<C: Config> fmt::Display for DisplayHelper<C> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.now().format(f)
}
}
unsafe impl<C: ?Sized + Config> IntrusivelyCounted for Sim<C> {
type Inner = SimBox;
fn irc_box(&self) -> &IrcBox<Self::Inner> {
&self.sim_box
}
}
#[doc(hidden)]
pub struct SimBox {
refs: Cell<usize>,
}
impl SimBox {
const fn new() -> Self {
Self { refs: Cell::new(0) }
}
}
unsafe impl IrcBoxed for SimBox {
fn ref_count(&self) -> usize {
self.refs.get()
}
fn acquire(&self, _: crate::ptr::Private) {
self.refs.set(self.refs.get() + 1);
}
fn release(&self, _: crate::ptr::Private) -> Option<fn(NonNull<Self>)> {
self.refs.set(self.refs.get() - 1);
None
}
}
struct ShallowWaker(Irc<dyn erased::Sim>);
static SIM_VTABLE: RawWakerVTable = RawWakerVTable::new(
ShallowWaker::clone,
ShallowWaker::nop,
ShallowWaker::wake_by_ref,
ShallowWaker::nop,
);
impl ShallowWaker {
fn new<C: Config>(sim: Irc<Sim<C>>) -> Self {
Self(Irc::map(sim, |inner| inner as &dyn erased::Sim))
}
unsafe fn as_waker(&self) -> Waker {
unsafe {
let waker = RawWaker::new(self as *const Self as *const (), &SIM_VTABLE);
Waker::from_raw(waker)
}
}
fn nop(_: *const ()) {}
unsafe fn clone(this: *const ()) -> RawWaker {
unsafe {
(*(this as *const Self)).0.waker()
}
}
unsafe fn wake_by_ref(this: *const ()) {
unsafe {
(*(this as *const Self)).0.defer();
}
}
}