odem-rs-core 0.1.0

Core components of the ODEM-rs simulation framework
use core::{any::Any, cell::Cell, fmt, num::NonZero, ptr::NonNull};

use crate::{
	config::Config,
	ptr::Irc,
	simulator::{Mark, Sim},
};

/* ************************************************************** Shared Data */

/// This structure acts as a public interface for accessing the shared state of
/// an agent.
///
/// It lets external observers inspect an [`Agent`]’s state in a controlled
/// manner, by providing access to the agent’s human-readable name and unique
/// identifier (which together form the agent’s [`Label`]), as well as a
/// reference to the state the agent explicitly exposes. Note that an agent's
/// own jobs can access state-internals directly, so this interface is intended
/// solely for outside inspection through [Pucks].
/// 
/// [`Agent`]: crate::agent::Agent
/// [Pucks]: crate::Puck
pub struct Share<C: ?Sized + Config> {
	/// Pointer to the simulator.
	sim: Irc<Sim<C>>,
	/// Contains the type name of the agent.
	name: &'static str,
	/// Contains the agent-unique ID for this specific instance.
	pid: Option<NonZero<usize>>,
	/// Marks the priority of continuations with shared data.
	rank: Cell<C::Rank>,
	/// Marks the order in which continuations associated with the same agent have
	/// been inserted.
	mark: Cell<Mark>,
	/// A reference to the shared instance over which continuations with shared data
	/// operate over.
	item: NonNull<dyn Any>,
}

impl<C: ?Sized + Config> Share<C> {
	/// Initialize the shared data for the root job of a simulation run.
	pub(crate) fn root(sim: Irc<Sim<C>>) -> Self {
		let default_rank = sim.config().default_rank();
		Self {
			sim,
			name: "SimMain",
			pid: None,
			rank: Cell::new(default_rank),
			mark: Cell::new(Mark::default()),
			item: NonNull::from(&()),
		}
	}

	/// Initialize the shared data for a new agent.
	///
	/// # Safety
	/// The caller is responsible that `item` outlives the shared data.
	pub(crate) unsafe fn new<I: Any>(
		sim: Irc<Sim<C>>,
		item: &I,
		rank: C::Rank,
		name: &'static str,
		pid: NonZero<usize>,
	) -> Share<C> {
		Share {
			sim,
			name,
			pid: Some(pid),
			rank: Cell::new(rank),
			mark: Cell::new(Mark::default()),
			item: NonNull::from(item),
		}
	}

	/// Returns the current rank of the owning [`Agent`].
	///
	/// [`Agent`]: crate::agent::Agent
	pub fn rank(&self) -> C::Rank {
		self.rank.get()
	}

	/// Returns the chosen identifier for the type of the shared instance.
	pub fn name(&self) -> &'static str {
		self.name
	}

	/// Returns the ID of the agent associated with the shared instance if
	/// one has been set or `None` if it hasn't.
	pub fn pid(&self) -> Option<NonZero<usize>> {
		self.pid
	}

	/// Returns a reference of the contained instance.
	pub fn item(&self) -> &dyn Any {
		// SAFETY: This is safe per the invariant on `Self::new`.
		unsafe { self.item.as_ref() }
	}

	/// Returns the agent-unique [Label] for this instance.
	pub fn label(&self) -> Label {
		Label {
			name: self.name(),
			pid: self.pid(),
		}
	}

	/// Returns a [Cell] with the currently stored [Mark].
	///
	/// The mark is used to sort [jobs] belonging to the same [agent] together.
	///
	/// [jobs]: crate::job
	/// [agent]: crate::agent
	pub(crate) fn mark(&self) -> &Cell<Mark> {
		&self.mark
	}

	/// Sets the rank of the agent, influencing all agent's jobs.
	///
	/// The new rank takes immediate effect and causes the rearrangement
	/// of all jobs currently scheduled, both in the present and future.
	///
	/// Lowering the rank of the active `Agent` can lead to another `Agent`
	/// gaining control if one with a higher rank after the change has jobs
	/// scheduled at the current model time. This change takes effect once the
	/// currently active agent suspends.
	pub fn update_rank(&self, rank: C::Rank) {
		// notify the calendar first
		self.sim
			.calendar()
			.update_rank(self.mark.get(), &self.rank, rank);
		// now change the rank in case the calendar didn't have to reschedule
		self.rank.set(rank);
	}

	/// Returns a reference to the simulation context.
	pub(crate) fn sim(&self) -> &Irc<Sim<C>> {
		&self.sim
	}
}

impl<C: Config> fmt::Debug for Share<C> {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		f.debug_struct("Share")
			.field("name", &self.name())
			.field("pid", &self.pid())
			.field("mark", &self.mark.get())
			.field("rank", &self.rank())
			.finish()
	}
}

/* ************************************************************ Agent Label */

/// Agent-specific identifier that uniquely identifies an instance over the
/// course of a simulation run.
///
/// It [renders] as a string containing the base name - usually the type name,
/// but user-definable by setting the name during [agent building] or by
/// implementing [`Behavior::name`] - followed by a pound symbol and an instance
/// number, e.g. `"JamesBond#7"`. The name is pretty printed by default but can
/// be written out by using alternative formatting:
///
/// ```
/// # use std::{pin::pin, rc::Rc};
/// # use odem_rs_core::{agent::Agent, simulator::Sim, Puck};
/// # struct MyAgent;
/// # impl MyAgent { async fn actions(self: &Rc<Self>, _sim: &Sim) {} }
///
/// # async fn sim_main(sim: &Sim) {
/// let agent = pin!(Agent::new((Rc::new(MyAgent), MyAgent::actions)));
/// let puck = sim.activate(agent);
///
/// // outputs "Rc<MyAgent>#1"
/// println!("{}", puck.label());
///
/// // outputs "alloc::rc::Rc<lab::MyAgent>#1"
/// println!("{:#}", puck.label());
/// # }
///
/// ```
///
/// [renders]: fmt::Display
/// [agent building]: crate::agent::Builder
/// [`Behavior::name`]: crate::agent::Behavior::name
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct Label {
	/// The agent name.
	pub name: &'static str,
	/// The agent-name-specific ID.
	pub pid: Option<NonZero<usize>>,
}

impl Label {
	/// Returns an iterator of string slices into the agent name that
	/// corresponds to a pretty-printed version of the type path.
	/// 
	/// The implementation is ~~stolen~~ inspired by [Jakob Hellermann's]
	/// [`pretty-type-name`] crate, adapted to use a heapless iterator rather
	/// than a `String`.
	/// 
	/// [Jakob Hellermann's]: https://crates.io/users/jakobhellermann
	/// [`pretty-type-name`]: https://crates.io/crates/pretty-type-name
	pub fn pretty_name(&self) -> impl Iterator<Item = &'static str> {
		self.name
			.split_inclusive(&['<', '>', '(', ')', '[', ']', ',', ';'])
			.filter_map(|part| part.rsplit(':').next())
	}
}

impl fmt::Display for Label {
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		if !f.alternate() {
			// use the pretty-printed format
			for ident in self.pretty_name() {
				f.write_str(ident)?;
			}
		} else {
			// print the whole name
			f.write_str(self.name)?;
		}

		// only print the pid if it exists
		if let Some(pid) = self.pid {
			write!(f, "#{pid}")
		} else {
			Ok(())
		}
	}
}