odem-rs-core 0.3.0

Core components of the odem-rs simulation framework
Documentation
//! The `calendar` module provides a flexible event scheduling mechanism for
//! managing continuations in a simulation.
//!
//! It introduces the `Scheduler` trait, a generic interface for unifying access
//! to an event calendar, along with the associated `PlanState` trait for
//! managing the state of scheduled continuations. The module also includes the
//! `Calendar` structure, implementing a continuation calendar based on a
//! user-provided `Scheduler`.
//!
//! ## Scheduler
//!
//! The [`Scheduler`] trait defines methods for scheduling, deferring,
//! activating, purging, and extracting continuations within a simulation. It
//! offers a unified interface for interacting with the event calendar, allowing
//! customization through configuration-specific implementations.
//!
//! ## PlanState
//!
//! The [`PlanState`] trait is implemented by the calendar-specific event state
//! for managed continuations. It provides methods to extract the model time of
//! the next activation if in the corresponding state.
//!
//! ## Calendar
//!
//! The [`Calendar`] structure acts as a container for managing scheduled
//! continuations based on the provided `Scheduler`. It offers methods for
//! scheduling, deferring, activating, removing, and extracting continuations.
//!
//! ## Note
//!
//! This module is currently not part of the public API, since more thought has
//! to be put into making these traits safely implementable by users.

mod ordering;
mod v5;

use core::{
	cell::{Cell, RefCell},
	fmt,
	ops::Deref,
};

use crate::{
	config::{Config, Time},
	continuation::{Continuation, token},
	fsm::*,
	ptr::Irc,
	simulator::Mark,
};

/// Type-alias for the default `Scheduler`.
///
/// # Note
///
/// It is currently not possible to provide custom implementations of the
/// scheduler because the associated trait is not part of the public API yet.
pub type DefaultPlan<C> = v5::Calendar<C>;

pub use ordering::Partition;

/* ********************************************************** Scheduler Trait */

/// A trait for unifying access to the event calendar.
///
/// # Note
///
/// This has to be thought about for longer before it can become part of the
/// public API. There are a lot of assumptions about what a valid implementation
/// can and should be doing that I don't want to cover by marking everything as
/// unsafe here.
///
/// I will revisit this trait once I have a better idea what I would like to
/// allow users to do with custom implementations.
///
/// # Safety
///
/// The methods in this calendar are expected to exhibit very specific behavior
/// that I haven't really thought through at the moment. The trait is private
/// for this reason.
pub unsafe trait Scheduler {
	/// The type of the associated [configuration](Config) for this scheduler.
	type Config: ?Sized + Config;

	/// The type of the state payload when managed by the calendar.
	type State: PlanState<Time = <Self::Config as Config>::Time>;

	/// Creates a new scheduler from a configuration.
	fn new(config: &Self::Config) -> Self;

	/// Updates the rank of all continuations with a certain mark.
	///
	/// This is called if the user changes the rank of an agent and expects an
	/// update to the jobs associated with the agents that are already
	/// scheduled. The scheduler doesn't have to change the rank if no
	/// rescheduling occurs - the caller will set the new rank after the method
	/// call returns.
	fn update_rank(
		&mut self,
		mark: Mark,
		rank: &Cell<<Self::Config as Config>::Rank>,
		new_rank: <Self::Config as Config>::Rank,
	);

	/// Schedules a continuation for a later model-time.
	///
	/// The simulation context will only call this method with a `time` later
	/// than `now`. Activations at the current model time are handled through
	/// [`Self::activate`].
	fn schedule<'brand>(
		&mut self,
		cont: Irc<Continuation<'brand, Self::Config>>,
		idle: token::Idle<'brand>,
		time: <Self::Config as Config>::Time,
	) -> token::Next<'brand>;

	/// Defers a continuation to be executed after all other continuations in
	/// this time-slice have completed, but before model time is advanced.
	fn defer<'brand>(
		&mut self,
		cont: Irc<Continuation<'brand, Self::Config>>,
		busy: token::Busy<'brand>,
		now: <Self::Config as Config>::Time,
	) -> token::Next<'brand>;

	/// Schedules an owned continuation for immediate execution.
	///
	/// The simulation context will call this method for activations at the
	/// current model time. Later activations use [`Self::schedule`].
	fn activate<'brand>(
		&mut self,
		cont: Irc<Continuation<'brand, Self::Config>>,
		idle: token::Idle<'brand>,
		now: <Self::Config as Config>::Time,
	) -> token::Next<'brand>;

	/// Removes a continuation from the calendar.
	fn remove<'brand>(
		&mut self,
		cont: &Continuation<'brand, Self::Config>,
		next: token::Next<'brand>,
		now: <Self::Config as Config>::Time,
	) -> token::Idle<'brand>;

	/// Extracts the next puck from the calendar, returning a (continuation,
	/// model-time) pair. Returns `None` if no puck has been scheduled.
	fn extract(&mut self) -> Option<ContTimePair<Self::Config>>;
}

/// Contains a pair of `Irc` to a [`Continuation`] and an activation [`Time`].
pub struct ContTimePair<C: ?Sized + Config> {
	/// The continuation.
	pub cont: Irc<Continuation<'static, C>>,
	/// The activation time.
	pub time: C::Time,
}

/* ***************************************************** Calendar Event State */

/// A trait implemented by the calendar-specific event state for managed continuations.
pub trait PlanState: fmt::Debug {
	/// The [Time] type of this state.
	type Time: Time;

	/// Extracts the model-time of the next activation if in the corresponding
	/// state.
	fn time(&self) -> Self::Time;
}

/* ***************************************************************** Calendar */

/// Structure that implements a [Continuation] calendar based on a user-provided
/// [Scheduler].
pub struct Calendar<C: ?Sized + Config> {
	plan: RefCell<C::Plan>,
	now: Cell<C::Time>,
}

impl<C: ?Sized + Config> Calendar<C> {
	/// Creates a new [Continuation] calendar from a [Scheduler].
	pub(crate) fn new(config: &C) -> Self {
		Calendar {
			plan: RefCell::new(C::Plan::new(config)),
			now: Cell::new(config.default_time()),
		}
	}

	/// Updates the rank of all continuations with a certain mark.
	///
	/// This is called if the user changes the rank of an agent.
	pub fn update_rank(&self, mark: Mark, rank: &Cell<C::Rank>, new_rank: C::Rank) {
		self.plan.borrow_mut().update_rank(mark, rank, new_rank);
	}

	/// Schedules a continuation at a later model-time.
	pub fn schedule<'brand>(
		&self,
		cont: Irc<Continuation<'brand, C>>,
		idle: token::Idle<'brand>,
		time: C::Time,
	) -> token::Next<'brand> {
		let _span = cont.enter_span();
		self.plan.borrow_mut().schedule(cont.clone(), idle, time)
	}

	/// Defers a continuation to be executed after all other continuations in
	/// this time-slice have completed, but before model time is advanced.
	pub fn defer<'brand>(
		&self,
		cont: Irc<Continuation<'brand, C>>,
		busy: token::Busy<'brand>,
	) -> token::Next<'brand> {
		self.plan.borrow_mut().defer(cont, busy, self.now())
	}

	/// Schedules an owned continuation for immediate execution.
	pub fn activate<'brand>(
		&self,
		cont: Irc<Continuation<'brand, C>>,
		idle: token::Idle<'brand>,
	) -> token::Next<'brand> {
		let _span = cont.enter_span();
		self.plan
			.borrow_mut()
			.activate(cont.clone(), idle, self.now())
	}

	/// Removes a continuation from the calendar.
	pub fn remove<'brand>(
		&self,
		cont: &Continuation<'brand, C>,
		next: token::Next<'brand>,
	) -> token::Idle<'brand> {
		let _span = cont.enter_span();
		self.plan.borrow_mut().remove(cont, next, self.now())
	}

	/// Extracts the next puck from the calendar, returning a (continuation,
	/// model-time) pair. Returns `None` if no puck has been scheduled.
	pub fn extract(&self) -> Option<Irc<Continuation<'static, C>>> {
		let mut plan = self.plan.borrow_mut();
		let ContTimePair { cont, time } = plan.extract()?;
		self.now.set(time);
		Some(cont)
	}

	/// Returns the current model-time.
	pub fn now(&self) -> C::Time {
		self.now.get()
	}
}

impl<C: ?Sized + Config> fmt::Debug for Calendar<C>
where
	C::Plan: fmt::Debug,
{
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		f.debug_struct("Calendar")
			.field("now", &self.now.get())
			.field("plan", &self.plan.borrow())
			.finish()
	}
}

/* ******************************************* Guarded Continuation Reference */

/// Newtype wrapper that transitions continuations away on drop.
///
/// This simplifies clearing of the [`DefaultPlan`] since the destructor of the
/// owned pointer type also transitions the [`Continuation`] away from the
/// [`State`] indicating where in the calendar it is located. This prevents
/// hard-to-track bugs, e.g., when the calendar is cleared before the
/// continuation drops and the continuation's state indicates that it is still
/// scheduled, moving, or active.
///
/// [`State`]: crate::erased::State
#[repr(transparent)]
pub struct IdleOnDrop<C: ?Sized + Config>(Irc<Continuation<'static, C>>);

impl<C: ?Sized + Config> IdleOnDrop<C> {
	/// Creates a new [IdleOnDrop]-wrapper that accepts [Irc] of [Continuation] in
	/// state [Next](token::Next).
	///
	/// This constructor freezes the state of the underlying continuation until either
	/// its `Drop` is executed or the continuation is extracted using [Self::into_inner].
	pub fn new<'brand>(task: Irc<Continuation<'brand, C>>, next: &token::Next<'brand>) -> Self {
		// ignore the witness
		let _ = next;

		// freeze the state
		unsafe {
			task.enter();
		}

		// construct the instance
		Self(Irc::map(task, Continuation::detach))
	}

	/// Extracts the inner [Irc] without invoking the destructor.
	pub fn into_inner(self) -> Irc<Continuation<'static, C>> {
		let this = core::mem::ManuallyDrop::new(self);

		// unfreeze the state
		unsafe {
			this.0.leave();
		}

		// extract the inner `Irc` without running drop
		let ptr = &this.0 as *const Irc<Continuation<'static, C>>;
		unsafe { ptr.read() }
	}

	/// Specialized branding for the calendar state.
	pub fn brand<F, R>(&self, f: F) -> R
	where
		F: for<'b> FnOnce(&Continuation<'b, C>, &token::Next<'b>) -> R,
	{
		(*self.0).brand(|inner, once| {
			// SAFETY: We must be in state `Next` because `IdleOnDrop` ensures
			// initially that the state is `Next` and then freezes it.
			let next = match inner.token(once).into_next() {
				Ok(next) => next,
				#[cfg(debug_assertions)]
				Err(err) => panic!("{}", err),
				#[cfg(not(debug_assertions))]
				_ => unsafe { core::hint::unreachable_unchecked() },
			};

			f(inner, &next)
		})
	}
}

impl<C: ?Sized + Config> Deref for IdleOnDrop<C> {
	type Target = Irc<Continuation<'static, C>>;

	fn deref(&self) -> &Self::Target {
		&self.0
	}
}

impl<C: ?Sized + Config> From<Irc<Continuation<'_, C>>> for IdleOnDrop<C> {
	fn from(task: Irc<Continuation<'_, C>>) -> Self {
		task.brand(move |task, once| {
			let next = match task.token(once).into_next() {
				Ok(next) => next,
				#[cfg(debug_assertions)]
				Err(err) => panic!("{}", err),
				#[cfg(not(debug_assertions))]
				_ => unsafe { core::hint::unreachable_unchecked() },
			};

			Self::new(task.clone(), &next)
		})
	}
}

impl<C: ?Sized + Config> From<IdleOnDrop<C>> for Irc<Continuation<'static, C>> {
	fn from(guard: IdleOnDrop<C>) -> Self {
		guard.into_inner()
	}
}

impl<C: ?Sized + Config> Drop for IdleOnDrop<C> {
	fn drop(&mut self) {
		// unfreeze the state
		unsafe {
			self.0.leave();
		}

		// transition into the 'Idle' state
		(*self.0).brand(move |task, once| {
			// We must be in state `Next` because `IdleOnDrop` ensures
			// initially that the state is `Next` and then freezes it.
			let next = match task.token(once).into_next() {
				Ok(next) => next,
				#[cfg(debug_assertions)]
				Err(err) => panic!("{}", err),
				#[cfg(not(debug_assertions))]
				_ => unsafe { core::hint::unreachable_unchecked() },
			};

			let _: token::Idle<'_> = task.state().transition(next, ());
		});
	}
}

/* **************************************************************** Testsuite */

#[cfg(test)]
mod tests;