odem_rs_core/continuation/share.rs
1use core::{any::Any, cell::Cell, fmt, num::NonZero, ptr::NonNull};
2
3use crate::{
4 config::Config,
5 ptr::Irc,
6 simulator::{Mark, Sim},
7};
8
9/* ************************************************************** Shared Data */
10
11/// This structure acts as a public interface for accessing the shared state of
12/// an agent.
13///
14/// It lets external observers inspect an [`Agent`]’s state in a controlled
15/// manner, by providing access to the agent’s human-readable name and unique
16/// identifier (which together form the agent’s [`Label`]), as well as a
17/// reference to the state the agent explicitly exposes. Note that an agent's
18/// own jobs can access state-internals directly, so this interface is intended
19/// solely for outside inspection through [Pucks].
20///
21/// [`Agent`]: crate::agent::Agent
22/// [Pucks]: crate::Puck
23pub struct Share<C: ?Sized + Config> {
24 /// Pointer to the simulator.
25 sim: Irc<Sim<C>>,
26 /// Contains the type name of the agent.
27 name: &'static str,
28 /// Contains the agent-unique ID for this specific instance.
29 pid: Option<NonZero<usize>>,
30 /// Marks the priority of continuations with shared data.
31 rank: Cell<C::Rank>,
32 /// Marks the order in which continuations associated with the same agent have
33 /// been inserted.
34 mark: Cell<Mark>,
35 /// A reference to the shared instance over which continuations with shared data
36 /// operate over.
37 item: NonNull<dyn Any>,
38}
39
40impl<C: ?Sized + Config> Share<C> {
41 /// Initialize the shared data for the root job of a simulation run.
42 pub(crate) fn root(sim: Irc<Sim<C>>) -> Self {
43 let default_rank = sim.config().default_rank();
44 Self {
45 sim,
46 name: "SimMain",
47 pid: None,
48 rank: Cell::new(default_rank),
49 mark: Cell::new(Mark::default()),
50 item: NonNull::from(&()),
51 }
52 }
53
54 /// Initialize the shared data for a new agent.
55 ///
56 /// # Safety
57 /// The caller is responsible for `item` outliving the shared data, but
58 /// `Share` guarantees that it will not access `item` in its `drop` impl.
59 pub(crate) unsafe fn new<I: Any>(
60 sim: Irc<Sim<C>>,
61 item: &I,
62 rank: C::Rank,
63 name: &'static str,
64 pid: NonZero<usize>,
65 ) -> Share<C> {
66 Share {
67 sim,
68 name,
69 pid: Some(pid),
70 rank: Cell::new(rank),
71 mark: Cell::new(Mark::default()),
72 item: NonNull::from(item),
73 }
74 }
75
76 /// Returns the current rank of the owning [`Agent`].
77 ///
78 /// [`Agent`]: crate::agent::Agent
79 pub fn rank(&self) -> C::Rank {
80 self.rank.get()
81 }
82
83 /// Returns the chosen identifier for the type of the shared instance.
84 pub fn name(&self) -> &'static str {
85 self.name
86 }
87
88 /// Returns the ID of the agent associated with the shared instance if
89 /// one has been set or `None` if it hasn't.
90 pub fn pid(&self) -> Option<NonZero<usize>> {
91 self.pid
92 }
93
94 /// Returns a reference of the contained instance.
95 pub fn subject(&self) -> &dyn Any {
96 // SAFETY: This is safe per the invariant on `Self::new`.
97 unsafe { self.item.as_ref() }
98 }
99
100 /// Returns the agent-unique [Label] for this instance.
101 pub fn label(&self) -> Label {
102 Label {
103 name: self.name(),
104 pid: self.pid(),
105 }
106 }
107
108 /// Returns a [Cell] with the currently stored [Mark].
109 ///
110 /// The mark is used to sort [jobs] belonging to the same [agent] together.
111 ///
112 /// [jobs]: crate::job
113 /// [agent]: crate::agent
114 pub(crate) fn mark(&self) -> &Cell<Mark> {
115 &self.mark
116 }
117
118 /// Sets the rank of the agent, influencing all agent's jobs.
119 ///
120 /// The new rank takes immediate effect and causes the rearrangement
121 /// of all jobs currently scheduled, both in the present and future.
122 ///
123 /// Lowering the rank of the active `Agent` can lead to another `Agent`
124 /// gaining control if one with a higher rank after the change has jobs
125 /// scheduled at the current model time. This change takes effect once the
126 /// currently active agent suspends.
127 pub fn update_rank(&self, rank: C::Rank) {
128 // notify the calendar first
129 self.sim
130 .calendar()
131 .update_rank(self.mark.get(), &self.rank, rank);
132 // now change the rank in case the calendar didn't have to reschedule
133 self.rank.set(rank);
134 }
135
136 /// Returns a reference to the simulation context.
137 pub(crate) fn sim(&self) -> &Irc<Sim<C>> {
138 &self.sim
139 }
140}
141
142impl<C: Config> fmt::Debug for Share<C> {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 f.debug_struct("Share")
145 .field("name", &self.name())
146 .field("pid", &self.pid())
147 .field("mark", &self.mark.get())
148 .field("rank", &self.rank())
149 .finish()
150 }
151}
152
153/* ************************************************************ Agent Label */
154
155/// Agent-specific identifier that uniquely identifies an instance over the
156/// course of a simulation run.
157///
158/// It [renders] as a string containing the base name - usually the type name,
159/// but user-definable by setting the name during [agent building] or by
160/// implementing [`Behavior::name`] - followed by a pound symbol and an instance
161/// number, e.g. `"JamesBond#7"`. The name is pretty printed by default but can
162/// be written out by using alternative formatting:
163///
164/// ```
165/// # use std::{pin::pin, rc::Rc};
166/// # use odem_rs_core::{agent::Agent, simulator::Sim, Puck};
167/// # struct MyAgent;
168/// # impl MyAgent { async fn actions(self: &Rc<Self>, _sim: &Sim) {} }
169///
170/// # async fn sim_main(sim: &Sim) {
171/// let agent = pin!(Agent::new((Rc::new(MyAgent), MyAgent::actions)));
172/// let puck = sim.activate(agent);
173///
174/// // outputs "Rc<MyAgent>#1"
175/// println!("{}", puck.label());
176///
177/// // outputs "alloc::rc::Rc<lab::MyAgent>#1"
178/// println!("{:#}", puck.label());
179/// # }
180///
181/// ```
182///
183/// [renders]: fmt::Display
184/// [agent building]: crate::agent::Builder
185/// [`Behavior::name`]: crate::agent::Behavior::name
186#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
187pub struct Label {
188 /// The agent name.
189 pub name: &'static str,
190 /// The agent-name-specific ID.
191 pub pid: Option<NonZero<usize>>,
192}
193
194impl Label {
195 /// Returns an iterator of string slices into the agent name that
196 /// corresponds to a pretty-printed version of the type path.
197 ///
198 /// The implementation is ~~stolen~~ inspired by [Jakob Hellermann's]
199 /// [`pretty-type-name`] crate, adapted to use a heapless iterator rather
200 /// than a `String`.
201 ///
202 /// [Jakob Hellermann's]: https://crates.io/users/jakobhellermann
203 /// [`pretty-type-name`]: https://crates.io/crates/pretty-type-name
204 pub fn pretty_name(&self) -> impl Iterator<Item = &'static str> {
205 self.name
206 .split_inclusive(&['<', '>', '(', ')', '[', ']', ',', ';'])
207 .filter_map(|part| part.rsplit(':').next())
208 }
209}
210
211impl fmt::Display for Label {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 if !f.alternate() {
214 // use the pretty-printed format
215 for ident in self.pretty_name() {
216 f.write_str(ident)?;
217 }
218 } else {
219 // print the whole name
220 f.write_str(self.name)?;
221 }
222
223 // only print the pid if it exists
224 if let Some(pid) = self.pid {
225 write!(f, "#{pid}")
226 } else {
227 Ok(())
228 }
229 }
230}