odem_rs_core/agent.rs
1//! This module provides the `Agent` structure, representing an isolated
2//! execution context within the simulation, along with supporting traits
3//! and types.
4//!
5//! # Overview
6//!
7//! An `Agent` acts as the root of a potential tree of [jobs](Job), managing its
8//! own isolated state. This isolation ensures that an agent's internal
9//! execution is independent of the lexical context where it was created and
10//! prevents side effects from influencing its behavior, except through
11//! controlled simulation interactions (like scheduling events or waiting).
12//!
13//! Agent isolation is primarily achieved through the [`Actions`] trait and its
14//! use of Higher-Ranked Trait Bounds (HRTBs). This mechanism prevents the
15//! agent's core asynchronous logic (its "lifecycle") from capturing references
16//! to non-static data from its creation environment.
17//!
18//! # Key Parts
19//!
20//! - **Agent:** The [`Agent`] struct encapsulates an isolated execution
21//! context. It holds the agent's state and manages its lifecycle via an
22//! internal state machine. It automatically dereferences to its internal
23//! state, but mutable access is only possible *before* the agent is
24//! activated (enforced at compile-time via borrowing rules around
25//! [`Sim::activate`]).
26//!
27//! - **Behavior:** The [`Behavior`] trait offers a user-friendly way to define
28//! an agent's actions. Implementing this trait for a type allows instances
29//! of that type to be easily converted into an [`Agent`]. It specifies the
30//! agent's return type (`Output`) and provides the core `async fn actions`.
31//! A blanket implementation exists for tuples `(I, A)` where `I` is the
32//! state and `A` is a compatible async function, allowing agent definition
33//! on foreign types.
34//!
35//! - **Actions:** The [`Actions`] trait is the core abstraction defining an
36//! agent's lifecycle logic. It uses HRTBs (`for<'p>`) over the private
37//! `Action` trait to ensure isolation. It's parameterized on the simulation
38//! configuration and the agent's state type. Users cannot implement this
39//! directly but rely on implementations generated via the [`Behavior`] trait
40//! or the `(I, A)` tuple blanket implementation.
41//!
42//! - **Builder:** The [`Builder`] provides a fluent API for constructing
43//! [`Agent`]s with detailed configuration options, such as a custom name,
44//! initial scheduling rank, source code location tracking, and custom
45//! finalization logic ([`Settle`]).
46//!
47//! - **Puck:** An [`agent::Puck`](Puck) is the handle returned when an
48//! [`Agent`] is activated via [`Sim::activate`]. It allows interaction with
49//! the running agent, such as awaiting its completion, checking its status,
50//! accessing its shared state, or aborting it.
51//!
52//! # Examples
53//!
54//! ## Simple agent using `Behavior`
55//!
56//! ```
57//! # use odem_rs_core::{agent::{Agent, Behavior}, simulator::Sim};
58//! # use core::pin::pin;
59//! struct MyData(usize);
60//!
61//! impl Behavior for MyData {
62//! type Output = (); // This agent doesn't return a meaningful value
63//!
64//! async fn actions(&self, sim: &Sim) {
65//! println!("Agent starting with data: {}", self.0);
66//! sim.advance(10.0).await; // Simulate work
67//! println!("Agent finished.");
68//! }
69//! }
70//!
71//! async fn sim_main(sim: &Sim) {
72//! // Create the agent state
73//! let data = MyData(42);
74//! // Create the agent using Agent::new, which leverages the Behavior impl
75//! let agent = pin!(Agent::new(data));
76//! // Activate the agent and get a handle (Puck)
77//! let handle = sim.activate(agent);
78//! // We can wait for the agent to finish (though it returns ())
79//! handle.await;
80//! }
81//! ```
82//!
83//! ## Agent returning a value
84//!
85//! ```
86//! # use odem_rs_core::{agent::{Agent, Behavior}, simulator::Sim};
87//! # use core::pin::pin;
88//! struct Calculator(i32, i32);
89//!
90//! impl Behavior for Calculator {
91//! type Output = i32; // This agent returns a calculation result
92//!
93//! async fn actions(&self, sim: &Sim) -> Self::Output {
94//! sim.advance(1.0).await; // Simulate calculation time
95//! self.0 + self.1
96//! }
97//! }
98//!
99//! async fn sim_main(sim: &Sim) {
100//! let agent = pin!(Agent::new(Calculator(10, 5)));
101//! // Activate and await the result directly
102//! let result: i32 = sim.activate(agent).await;
103//! assert_eq!(result, 15);
104//! println!("Calculation result: {}", result);
105//! }
106//! ```
107//!
108//! ## Agent defined using a tuple `(State, AsyncFn)`
109//!
110//! This uses the blanket implementation `Behavior for (I, A)`.
111//!
112//! ```
113//! # use odem_rs_core::{agent::Agent, simulator::Sim};
114//! # use core::pin::pin;
115//! struct SharedState { id: u32 }
116//!
117//! // Define the async logic as a standalone function or inherent method
118//! async fn agent_logic(state: &SharedState, sim: &Sim) -> u32 {
119//! println!("Agent {} starting", state.id);
120//! sim.advance(5.0).await;
121//! println!("Agent {} finished", state.id);
122//! state.id * 2
123//! }
124//!
125//! async fn sim_main(sim: &Sim) {
126//! let state = SharedState { id: 101 };
127//! // Create agent by pairing state and the async function
128//! let agent = pin!(Agent::new((state, agent_logic)));
129//! let result: u32 = sim.activate(agent).await;
130//! assert_eq!(result, 202);
131//! }
132//! ```
133//!
134//! ## Agent configured with the Builder
135//!
136//! ```
137//! # use odem_rs_core::{agent::{Agent, Behavior}, config::Config, simulator::Sim, job::Checked};
138//! # use core::pin::pin;
139//! # #[derive(Config)] struct MyConfig { #[rank] rank: i32 }
140//! struct Task { data: &'static str }
141//!
142//! impl Task {
143//! async fn actions(&self, sim: &Sim<MyConfig>) -> Result<String, &'static str> {
144//! if self.data.is_empty() {
145//! Err("Data cannot be empty")
146//! } else {
147//! sim.advance(2.0).await;
148//! Ok(format!("Processed: {}", self.data))
149//! }
150//! }
151//! }
152//!
153//! async fn sim_main(sim: &Sim<MyConfig>) {
154//! let task = Task { data: "Valid Data" };
155//!
156//! let agent = Agent::build()
157//! .with_subject(task) // Does not require `impl Behavior`
158//! .with_actions(Task::actions) // Provide the async fn
159//! .with_name("MyTask") // Custom name
160//! .with_rank(10) // Custom rank (C::Rank=i32)
161//! .checked() // Use checked finalization (for Result/Option/bool)
162//! .finish(); // Get the Lease<'p, Agent<...>>
163//!
164//! let pinned_agent = pin!(agent);
165//! let result = sim.activate(pinned_agent).await;
166//!
167//! match result {
168//! Ok(output) => println!("Task succeeded: {}", output),
169//! Err(e) => println!("Task failed: {}", e),
170//! }
171//! // Note: .checked() ensures the ExitStatus reflects the Ok/Err outcome.
172//! // Accessing the result requires the Agent's Puck or awaiting it.
173//! }
174//! ```
175
176use core::{
177 any::{Any, type_name},
178 future::{Future, IntoFuture, Pending, pending},
179 marker::PhantomData,
180 ops::{Deref, DerefMut},
181 panic::Location,
182 pin::Pin,
183 task::{Context, Poll},
184};
185
186use crate::{
187 Active, Dispatch, ExitStatus,
188 config::Config,
189 continuation::{Continuation, Label, Share, erased::State as ContState},
190 error,
191 job::{Builder as JobBuilder, Checked, Job, Puck as JobPuck, Settle, Unchecked},
192 ptr::{AsIrc, Irc, Lease, LeasedMut},
193 simulator::{Prec, Sim},
194};
195
196/* *************************************************** Internal Actions-Trait */
197
198/// Defines the primary asynchronous behavior and output type for an [`Agent`].
199///
200/// This trait provides a convenient way to specify what an agent does. By
201/// implementing `Behavior` for a type `T`, you allow `Agent::new(T)` to
202/// automatically construct an agent using the provided `actions` method.
203///
204/// The `name` method determines the default base name for the agent's label,
205/// defaulting to the type name of `T`.
206///
207/// A blanket implementation `Behavior<C> for (I, A)` exists, allowing you to
208/// define agents by simply pairing a state type `I` with an appropriate
209/// `async fn(&I, &Sim<C>) -> R` function, without needing a separate `impl`
210/// block or a newtype wrapper.
211///
212/// # Examples
213///
214/// ```
215/// # use odem_rs_core::{agent::{Agent, Behavior}, simulator::Sim};
216/// # use core::pin::pin;
217/// struct MyAgentState(f64);
218///
219/// impl Behavior for MyAgentState {
220/// type Output = (); // No return value
221///
222/// async fn actions(&self, sim: &Sim) {
223/// println!("Waiting for {} time units.", self.0);
224/// sim.advance(self.0).await;
225/// println!("Done waiting.");
226/// }
227/// }
228/// # async fn run(sim: &Sim) {
229/// // Use Agent::new thanks to the Behavior implementation
230/// let agent = pin!(Agent::new(MyAgentState(5.0)));
231/// sim.activate(agent).await;
232/// # }
233/// ```
234///
235/// Using the tuple blanket implementation:
236/// ```
237/// # use odem_rs_core::{agent::Agent, simulator::Sim};
238/// # use core::pin::pin;
239/// struct MyState(u32);
240/// async fn my_async_fn(state: &MyState, sim: &Sim) { /* ... */ }
241/// # async fn run(sim: &Sim) {
242/// // Use Agent::new with a tuple (state, function)
243/// let agent = pin!(Agent::new((MyState(10), my_async_fn)));
244/// sim.activate(agent);
245/// # }
246/// ```
247pub trait Behavior<C: ?Sized + Config = ()> {
248 /// The type returned by the agent's `actions` method upon successful
249 /// completion.
250 type Output;
251
252 /// The core asynchronous logic of the agent.
253 ///
254 /// This method receives an immutable reference to the agent's state and the
255 /// simulation context. It defines the sequence of operations and simulation
256 /// interactions (like `sim.advance()`) the agent performs.
257 ///
258 /// The returned `Future` cannot capture non-`'static` data from the
259 /// surrounding environment, enforced implicitly by how [`Agent`] constructs
260 /// the underlying task. `Send` is not required as simulations are
261 /// single-threaded.
262 #[allow(async_fn_in_trait)]
263 async fn actions(&self, sim: &Sim<C>) -> Self::Output;
264
265 /// Returns the base name for this agent type.
266 ///
267 /// Defaults to the [`type_name`] of `Self`. Override this if a different
268 /// base name is desired for logging or identification.
269 fn name(&self) -> &'static str {
270 type_name::<Self>()
271 }
272}
273
274/// Abstracts over the asynchronous lifecycle logic of an [`Agent`].
275///
276/// This trait is the core mechanism for ensuring agent isolation. It uses a
277/// Higher-Ranked Trait Bound (HRTB) over the sealed `Action` trait.
278/// This HRTB (`for<'p> ...`) ensures that the future produced by the agent's
279/// logic (`bind` method in `Action`) can only borrow data with lifetime `'p`
280/// (namely, the agent's state `&'p I` and the sim context `&'p Sim<C>`),
281/// preventing it from capturing references from the surrounding lexical scope
282/// where the agent was created.
283///
284/// Users cannot implement this trait directly. Instead, they use types that
285/// implement [`Behavior`], or tuples `(I, async_fn)`, which have blanket
286/// implementations providing the necessary `Actions` implementation.
287///
288/// The main purpose of this trait is to act as a type parameter bound for
289/// [`Agent`], allowing the agent's lifecycle (an unnameable `Future` type)
290/// to be handled generically without needing to explicitly name it.
291pub trait Actions<C: ?Sized + Config, I: ?Sized + Any>:
292 // The implementation must satisfy `private::Action` for *any* lifetime 'p.
293 for<'p> private::Action<'p, C, I, Future: Future<Output = Self::Output>>
294{
295 /// The result type produced by the agent's lifecycle future when it
296 /// completes.
297 type Output;
298}
299
300mod private {
301 use super::*;
302
303 /// **(Private)** Defines the function signature for creating an agent's
304 /// lifecycle future.
305 ///
306 /// This trait is an internal detail used by the [`Actions`] trait's HRTB.
307 /// It represents a function that can be called once with temporarily
308 /// borrowed references to the agent's state and the simulation context.
309 /// It returns a [`Future`] that is allowed to borrow these inputs for the
310 /// duration of lifetime `'p`.
311 ///
312 /// The HRTB in [`Actions`] ensures that this `'p` lifetime cannot be tied
313 /// to anything outside the scope of the `bind` call itself, thus enforcing
314 /// agent isolation.
315 ///
316 /// [HRTB]: https://doc.rust-lang.org/nomicon/hrtb.html
317 pub trait Action<'p, C: ?Sized + Config, I: ?Sized + Any> {
318 /// The type of `Future` returned by `bind`. Must live at least as long
319 /// as `'p`.
320 type Future: Future + 'p;
321
322 /// Creates the agent's lifecycle `Future`.
323 ///
324 /// This method consumes the action provider (`self`) and uses the
325 /// provided borrowed state (`item`) and simulation context (`sim`)
326 /// to construct the `Future` that represents the agent's execution.
327 fn bind(self, item: &'p I, sim: &'p Sim<C>) -> Self::Future;
328 }
329}
330
331/* ****************************************************************** Agent */
332
333/// Represents an isolated execution context (an *agent*) within the simulation.
334///
335/// An `Agent` encapsulates:
336/// - Subject: The data associated with this agent.
337/// - Lifecycle: The asynchronous logic defining its behavior.
338/// - Execution State: Manages whether the agent is ready, running, or finished.
339///
340/// Agents provide isolation: their internal logic, defined via the [`Actions`]
341/// (and typically [`Behavior`]) trait, cannot directly access or be influenced
342/// by the lexical scope where the `Agent` struct was created, due to the HRTB
343/// mechanism employed by [`Actions`]. Communication happens through simulation
344/// primitives (scheduling, waiting) and the agent's final `Output`.
345///
346/// `Agent` implements [`Deref`] and [`DerefMut`] to its subject.
347/// However, mutable access is only practically possible *before* the agent is
348/// passed to [`Sim::activate`], as activation blocks the access path. This is
349/// enforced by Rust's borrowing rules at compile time.
350///
351/// Agents are typically created using [`Agent::new`] (for types implementing
352/// [`Behavior`]) or [`Agent::build`] for more configuration options. They must
353/// be pinned (e.g., using [`core::pin::pin!`]) before being passed to
354/// [`Sim::activate`].
355///
356/// The generic parameter `S` defines the [`Settle`] strategy, controlling how
357/// the agent's `Output` is translated into an [`ExitStatus`]. [`Unchecked`] is
358/// the default, treating all outcomes as success. [`Checked`] interprets
359/// `Result`, `Option`, and `bool` as success/failure indicators.
360///
361/// # Type Parameters
362///
363/// - `C`: The simulation configuration type ([`Config`]).
364/// - `I`: The type of the agent's internal state (the "item" or "subject").
365/// - `A`: The type implementing [`Actions`], defining the agent's lifecycle logic.
366/// - `S`: The [`Settle`] strategy (defaults to [`Unchecked`]).
367///
368/// # Safety Warning
369///
370/// The order of fields `inner` and `item` **must not** be changed. `inner` must
371/// come *before* `item`. Reversing this order will lead to use-after-free and
372/// undefined behavior because the `Share` within `Inner::Live` internally holds
373/// a pointer derived from `item`, and drop order relies on `inner` being
374/// dropped first.
375#[pin_project::pin_project(!Unpin)]
376pub struct Agent<C: ?Sized + Config, I: ?Sized + Any, A: Actions<C, I>, S = Unchecked> {
377 /// Internal state machine (`Born`, `Bust`, `Live`). Must be declared before
378 /// `item`.
379 #[pin]
380 inner: Inner<C, I, A, S>,
381
382 /// The user-provided state associated with this agent.
383 #[pin]
384 item: I,
385}
386
387/// Internal runtime states of an [`Agent`].
388///
389/// - `Born`: The initial state before activation. Holds the configuration
390/// needed to construct the agent's lifecycle future.
391/// - `Bust`: A transient state entered if the `action.bind()` call panics
392/// during activation. The agent is effectively dead if observed in this state.
393/// - `Live`: The state after successful activation. Contains the running root
394/// [`Job`] and the shared context ([`Share`]) for the agent's job tree.
395///
396/// # Safety Warning
397///
398/// - Field Order (within `Live` variant): The order of fields `job` and `share`
399/// **must not** be changed. `job` must come *before* `share`. Reversing this
400/// order can lead to undefined behavior because the `job`'s future may hold
401/// references derived from data pointed to by `share` (specifically, the
402/// agent's `item` and `Sim` context). Drop order guarantees that `job` is
403/// dropped before `share`, ensuring these references are not dangling.
404#[pin_project::pin_project(
405 project = StateProject,
406 project_ref = StateProjectRef,
407 project_replace = StateOwn
408)]
409enum Inner<C: ?Sized + Config, I: ?Sized + Any, A: Actions<C, I>, S> {
410 /// Initial state before activation.
411 Born {
412 /// The action provider that will create the agent's lifecycle future.
413 action: A,
414 /// The base name used for the agent's label.
415 name: &'static str,
416 /// Optional initial scheduling rank. Defaults according to `C`.
417 rank: Option<C::Rank>,
418 /// Partially configured builder for the root job, awaiting the future.
419 builder: JobBuilder<true, (), S>,
420 },
421 /// Transient state if `action.bind()` panics during activation.
422 Bust,
423 /// State after successful activation, agent is running or completed.
424 Live {
425 /// The root job executing the agent's lifecycle future. Must be
426 /// declared before `share`.
427 #[pin]
428 job: RootJob<'static, C, I, A, S>,
429 /// Shared context data (sim, subject ptr, rank, label) for this agent
430 /// and its jobs.
431 #[pin]
432 share: Share<C>,
433 },
434}
435
436/// Type alias for the `Future` created by the agent's [`Actions`].
437type RootFuture<'l, C, I, A> = <A as private::Action<'l, C, I>>::Future;
438
439/// Type alias for the root [`Job`] wrapping the agent's `RootFuture`.
440type RootJob<'l, C, I, A, S> = Job<C, RootFuture<'l, C, I, A>, S>;
441
442/// Type alias for the [`JobPuck`] associated with the agent's `RootJob`.
443type RootPuck<'l, C, I, A, S> = JobPuck<'l, C, RootFuture<'l, C, I, A>, S>;
444
445impl Agent<(), (), Pending<()>> {
446 /// Creates a new [`Agent`] using a type that implements [`Behavior`].
447 ///
448 /// This is the simplest way to create an agent. It infers the agent's
449 /// actions, output type, and name from the `Behavior` implementation of
450 /// `I`. The agent uses the default [`Unchecked`] settle strategy, meaning
451 /// its `ExitStatus` will always be `Ok` unless it panics or is aborted,
452 /// regardless of the `Output` type (even if it's a `Result::Err`).
453 ///
454 /// The provided `subject` (of type `I`) becomes the agent's internal state.
455 ///
456 /// Returns a [`Lease`], which must be pinned before activation.
457 ///
458 /// # Example
459 ///
460 /// ```
461 /// # use odem_rs_core::{agent::{Agent, Behavior}, simulator::Sim};
462 /// # use core::pin::pin;
463 /// struct MyAgent;
464 /// impl Behavior for MyAgent {
465 /// type Output = ();
466 /// async fn actions(&self, sim: &Sim) { /* ... */ }
467 /// }
468 /// # fn make_agent() {
469 /// let agent_lease = Agent::new(MyAgent);
470 /// let pinned_agent = pin!(agent_lease);
471 /// // Pass pinned_agent to sim.activate(...)
472 /// # }
473 /// ```
474 #[track_caller]
475 pub fn new<'p, C, I>(
476 subject: I,
477 ) -> Lease<'p, Agent<C, I, impl Actions<C, I, Output = I::Output> + use<C, I>>>
478 where
479 C: ?Sized + Config,
480 I: Any + Behavior<C>,
481 {
482 Self::build()
483 .with_name(subject.name())
484 .with_subject(subject)
485 .with_actions(I::actions)
486 .finish()
487 }
488
489 /// Creates a [`Builder`] for configuring an [`Agent`] instance.
490 ///
491 /// Use the builder when you need to customize options like the agent's name,
492 /// initial rank, settle strategy ([`Checked`]), or source code location,
493 /// or when defining the agent using a state/function tuple instead of a
494 /// dedicated `Behavior` implementation.
495 ///
496 /// # Example
497 ///
498 /// ```
499 /// # use odem_rs_core::{agent::Agent, simulator::Sim, config::Config};
500 /// # #[derive(Config)] struct MyConfig { #[rank] rank: i32 }
501 /// # struct MyState;
502 /// # async fn my_actions(_s: &MyState, _sim: &Sim<MyConfig>) {}
503 /// # fn build_agent() {
504 /// let agent = Agent::build()
505 /// .with_subject(MyState)
506 /// .with_actions(my_actions)
507 /// .with_name("CustomAgent")
508 /// .finish();
509 /// // Pin and activate agent...
510 /// # }
511 /// ```
512 pub const fn build<C: ?Sized + Config>() -> Builder<C> {
513 Builder::new()
514 }
515}
516
517impl<C, I, A, S> Deref for Agent<C, I, A, S>
518where
519 C: ?Sized + Config,
520 I: ?Sized + Any,
521 A: Actions<C, I>,
522{
523 type Target = I;
524
525 fn deref(&self) -> &Self::Target {
526 &self.item
527 }
528}
529
530impl<C, I, A, S> DerefMut for Agent<C, I, A, S>
531where
532 C: ?Sized + Config,
533 I: ?Sized + Any,
534 A: Actions<C, I>,
535{
536 fn deref_mut(&mut self) -> &mut Self::Target {
537 &mut self.item
538 }
539}
540
541impl<C, I, A, S> Dispatch for Agent<C, I, A, S>
542where
543 C: ?Sized + Config,
544 I: ?Sized + Any,
545 A: Actions<C, I>,
546 S: Settle<A::Output>,
547{
548 fn poll(self: Pin<&Self>, cx: &mut Context<'_>) -> Poll<ExitStatus> {
549 // Project to the 'inner' field
550 match self.project_ref().inner.project_ref() {
551 // If the agent is live, poll its root job
552 StateProjectRef::Live { job, .. } => job.poll(cx),
553 // Should not be polled in Born or Bust state.
554 StateProjectRef::Born { .. } => {
555 unreachable!("Agent::poll called before activation (state is Born)")
556 }
557 StateProjectRef::Bust => {
558 unreachable!("Agent::poll called after activation panicked (state is Bust)")
559 }
560 }
561 }
562}
563
564impl<C, I, A, S> Active<C> for Agent<C, I, A, S>
565where
566 C: ?Sized + Config,
567 I: Any,
568 A: Actions<C, I>,
569 S: Settle<A::Output>,
570{
571 type Output = A::Output;
572 type Puck<'p>
573 = Puck<'p, C, I, A, S>
574 where
575 Self: 'p;
576
577 /// Binds the agent to the simulation context, transitioning it from `Born`
578 /// to `Live`.
579 ///
580 /// This method is called internally by [`Sim::activate`]. It performs the
581 /// critical steps:
582 /// 1. Temporarily replaces the `Inner::Born` state with `Inner::Bust`.
583 /// 2. Calls the `action.bind()` method (from `private::Action`) to create
584 /// the agent's lifecycle future (`RootFuture`).
585 /// 3. If `bind` succeeds, creates the `Share` context and the `RootJob`.
586 /// 4. Replaces the `Inner::Bust` state with `Inner::Live`, containing the
587 /// `RootJob` and `Share`.
588 /// 5. Returns the agent's [`Puck`] handle.
589 ///
590 /// If `action.bind()` panics, the agent remains in the `Inner::Bust` state.
591 ///
592 /// # Safety
593 /// Relies on `unsafe` blocks for lifetime transmutation (`'p` to `'static`
594 /// and back) and creating the `Share` context. These are justified because:
595 /// - The `'static` lifetime on `RootJob` within `Inner::Live` is an
596 /// internal implementation detail; the actual future only borrows data
597 /// for `'p`. The lifetime is transmuted back to `'p` when creating the
598 /// `Puck`.
599 /// - `Share::new` requires ensuring the `item` pointer outlives `Share`.
600 /// This is guaranteed by the struct field ordering (`inner` before
601 /// `item`), and Rust's drop order guarantees (`inner` is dropped first).
602 fn bind<'p>(this: Pin<LeasedMut<'p, Self>>, sctx: &'p Share<C>) -> Self::Puck<'p> {
603 let mut this = this.project().project();
604
605 // replace the `Born` state by `Bust` for the transition
606 match this.inner.as_mut().project_replace(Inner::Bust) {
607 StateOwn::Born {
608 action,
609 name,
610 rank,
611 builder,
612 } => {
613 use core::mem::transmute;
614
615 // Get references needed for `action.bind` and `Share::new`.
616 let item_ref: &'p I = this.item.into_ref().get_ref();
617 let sim: &'p Irc<Sim<C>> = sctx.sim();
618 let pid = sim.pid_gen::<I>();
619
620 // Optional: Tracing span for the agent's lifetime
621 #[cfg(feature = "tracing")]
622 let _span = tracing::error_span!(
623 parent: None, "Agent",
624 label = %Label { name, pid: Some(pid) }
625 )
626 .entered();
627
628 // *** Critical Section Start ***
629 // Call the action's bind method to create the actual lifecycle
630 // future. This is the point that might panic, leaving the agent
631 // in `Bust` state.
632 let lifecycle = action.bind(item_ref, sim);
633 // *** Critical Section End ***
634
635 // Complete the root job builder with the created future
636 let root_job = builder.with_actions(lifecycle).finish();
637
638 // Now, transition from Bust to Live state.
639 this.inner.set(Inner::Live {
640 // SAFETY: Transmuting lifetime from 'p to 'static for storage.
641 // The actual future inside `RootJob` correctly captures 'p.
642 // We transmute back to 'p when returning the Puck.
643 job: unsafe {
644 transmute::<RootJob<'p, C, I, A, S>, RootJob<'static, C, I, A, S>>(
645 root_job.into_inner(),
646 )
647 },
648 // SAFETY: `Share::new` takes a pointer to `item`. This is
649 // safe because `item` (in the `Agent` struct) outlives
650 // `share` (in the Inner struct) due to field declaration
651 // order and drop order guarantees.
652 // `inner` is dropped before `item`.
653 share: unsafe {
654 Share::new(
655 sim.clone(),
656 item_ref,
657 rank.unwrap_or(sim.config().default_rank()),
658 name,
659 pid,
660 )
661 },
662 });
663
664 // Project the now guaranteed `Live` state to get Pin<&mut Job>
665 // and Pin<&mut Share>
666 match this.inner.project() {
667 StateProject::Live { job, share } => {
668 // Bind the root job to its share context
669
670 // SAFETY: Transmuting the 'static lifetime back to 'p
671 // is safe because the underlying future only borrows
672 // for 'p. Adding LeasedMut wrapper is safe as this
673 // specific `job` reference is consumed here.
674 let job_pin = unsafe {
675 transmute::<
676 Pin<&mut RootJob<'static, C, I, A, S>>,
677 Pin<LeasedMut<'p, RootJob<'p, C, I, A, S>>>,
678 >(job)
679 };
680
681 let share_ref = share.into_ref().get_ref();
682
683 // Create the Job's Puck by binding it
684 let job_puck = Active::bind(job_pin, share_ref);
685
686 // Wrap the JobPuck in our Agent::Puck
687 Puck(job_puck)
688 }
689 _ => unreachable!("State should be Live after successful transition"),
690 }
691 }
692 StateOwn::Live { .. } | StateOwn::Bust => {
693 unreachable!("Agent::bind called on an already bound or busted agent")
694 }
695 }
696 }
697}
698
699/* ************************************************************* Agent Puck */
700
701/// A handle to an activated [`Agent`], returned by [`Sim::activate`].
702///
703/// This handle allows interaction with the running (or completed) agent. It
704/// wraps the [`JobPuck`] of the agent's root job, providing access to
705/// agent-specific information and actions.
706///
707/// The `Puck` implements [`IntoFuture`], allowing you to `.await` it to get the
708/// agent's final `Output`. It also implements [`crate::Puck`], providing common
709/// methods for interacting with active simulation entities (checking state,
710/// time, etc.).
711pub struct Puck<'p, C: ?Sized + Config, I: ?Sized + Any, A: Actions<C, I>, S>(
712 RootPuck<'p, C, I, A, S>,
713);
714
715impl<C, I, A, S> Puck<'_, C, I, A, S>
716where
717 C: ?Sized + Config,
718 I: ?Sized + Any,
719 A: Actions<C, I>,
720 S: Settle<A::Output>,
721{
722 /// Sets the rank of the root job.
723 ///
724 /// The new rank takes immediate effect and causes the rearrangement
725 /// of all jobs currently scheduled, both in the present and future.
726 ///
727 /// Lowering the rank of the active `Agent` can lead to another `Agent`
728 /// gaining control if one with a higher rank after the change has jobs
729 /// scheduled at the current model time. This change takes effect once the
730 /// currently active agent suspends.
731 pub fn update_rank(&self, rank: C::Rank) {
732 self.0.share().update_rank(rank);
733 }
734
735 /// Returns an immutable reference to the agent's internal state (`item`).
736 ///
737 /// This allows observing the agent's state while it's running or after it
738 /// has completed. Mutable access is not possible via the `Puck`.
739 pub fn subject(&self) -> &I
740 where
741 I: Sized,
742 {
743 use crate::Puck;
744
745 self.0.subject().downcast_ref::<I>().unwrap()
746 }
747
748 /// Aborts the agent's execution prematurely.
749 ///
750 /// This terminates the agent's root job and any descendant jobs.
751 /// The agent's `ExitStatus` will reflect the abortion. The `Output` value
752 /// will not be produced.
753 ///
754 /// Consumes the `Puck` to prevent further interaction after aborting.
755 pub fn abort(self) {
756 self.0.abort();
757 }
758}
759
760// Allow awaiting the Agent::Puck to get the final result.
761impl<C, I, A, S> IntoFuture for Puck<'_, C, I, A, S>
762where
763 C: ?Sized + Config,
764 I: ?Sized + Any,
765 A: Actions<C, I>,
766 S: Settle<A::Output>,
767{
768 type Output = A::Output;
769 type IntoFuture = crate::ops::Join<C, Self>;
770
771 fn into_future(self) -> Self::IntoFuture {
772 crate::ops::join(self)
773 }
774}
775
776// Implement the base Puck trait for Agent::Puck by delegating.
777impl<C, I, A, S> crate::Puck<C> for Puck<'_, C, I, A, S>
778where
779 C: ?Sized + Config,
780 I: ?Sized + Any,
781 A: Actions<C, I>,
782 S: Settle<A::Output>,
783{
784 fn result(&mut self) -> Option<Self::Output> {
785 self.0.result()
786 }
787
788 fn wake(&mut self) -> Result<(), error::NotIdle> {
789 self.0.wake()
790 }
791
792 fn subject(&self) -> &dyn Any {
793 self.0.subject()
794 }
795
796 fn sim(&self) -> &Sim<C> {
797 self.0.sim()
798 }
799
800 fn label(&self) -> Label {
801 self.0.label()
802 }
803
804 fn time(&self) -> Option<C::Time> {
805 self.0.time()
806 }
807
808 fn rank(&self) -> C::Rank {
809 self.0.rank()
810 }
811
812 fn prec(&self) -> Prec {
813 self.0.prec()
814 }
815
816 fn state(&self) -> ContState {
817 self.0.state()
818 }
819
820 fn location(&self) -> &'static Location<'static> {
821 self.0.location()
822 }
823}
824
825// Allow getting a reference to the underlying Continuation.
826impl<C, I, A, S> AsRef<Continuation<'static, C>> for Puck<'_, C, I, A, S>
827where
828 C: ?Sized + Config,
829 I: ?Sized + Any,
830 A: Actions<C, I>,
831{
832 fn as_ref(&self) -> &Continuation<'static, C> {
833 self.0.as_ref()
834 }
835}
836
837// Allow getting an intrusive reference counter (Irc) to the Continuation.
838impl<C, I, A, S> AsIrc<Continuation<'static, C>> for Puck<'_, C, I, A, S>
839where
840 C: ?Sized + Config,
841 I: ?Sized + Any,
842 A: Actions<C, I>,
843{
844 fn as_irc(&self) -> Irc<Continuation<'static, C>> {
845 self.0.as_irc()
846 }
847}
848
849/* ********************************************************** Agent Builder */
850
851/// A builder pattern for configuring and creating [`Agent`] instances.
852///
853/// Use the builder when you need more control over the agent's properties than
854/// [`Agent::new`] provides, such as setting a custom name, initial rank,
855/// source location, or finalization strategy ([`Settle`]).
856///
857/// Start with [`Agent::build()`] and chain `with_*` methods to configure,
858/// finally calling [`finish()`](Builder::finish) to create the [`Agent`]
859/// wrapped in a [`Lease`].
860///
861/// # Type Parameters
862///
863/// - `C`: The simulation configuration ([`Config`]).
864/// - `I`: The type of the agent's state (`item`). Initially `()`.
865/// - `A`: The type implementing [`Actions`]. Initially `()`.
866/// - `S`: The [`Settle`] strategy. Initially [`Unchecked`].
867pub struct Builder<C: ?Sized + Config, I = (), A = (), S = Unchecked> {
868 /// The agent's state (subject). Set via `with_subject`.
869 subject: I,
870 /// The agent's lifecycle logic provider. Set via `with_actions`.
871 actions: A,
872 /// Optional custom base name for the agent's label. Set via `with_name`.
873 name: Option<&'static str>,
874 /// Optional initial scheduling rank. Set via `with_rank`.
875 rank: Option<C::Rank>,
876 /// The finalization strategy. Set via `with_finalizer` or `checked`.
877 settle: S,
878 /// Optional source code location.
879 /// Set via `with_location` or implicitly by `with_actions`.
880 location: Option<&'static Location<'static>>,
881 /// Marker for the configuration type `C`.
882 _config: PhantomData<C>,
883}
884
885impl<C: ?Sized + Config> Builder<C, (), ()> {
886 /// Creates a new, empty agent builder.
887 /// Called via [`Agent::build()`].
888 const fn new() -> Self {
889 Builder {
890 subject: (),
891 actions: (),
892 name: None,
893 rank: None,
894 settle: Unchecked,
895 location: None,
896 _config: PhantomData,
897 }
898 }
899}
900
901// Methods to configure the Builder
902impl<C: ?Sized + Config, I: Any, A, S> Builder<C, I, A, S> {
903 /// Sets the state (`item`) for the agent being built.
904 pub fn with_subject<X: Any>(self, object: X) -> Builder<C, X, A, S> {
905 Builder {
906 subject: object,
907 actions: self.actions,
908 name: self.name,
909 rank: self.rank,
910 settle: self.settle,
911 location: self.location,
912 _config: self._config,
913 }
914 }
915
916 /// Sets the lifecycle logic (actions) for the agent.
917 ///
918 /// This accepts any type `X` that implements [`Actions<C, I>`], where `I`
919 /// is the type set by `with_subject`. Typically, this is an `async fn`
920 /// reference compatible with the state `I`, or the `actions` method from a
921 /// `Behavior` impl.
922 ///
923 /// This method also captures the caller's source code location using
924 /// `#[track_caller]` as the default location for the agent, unless
925 /// explicitly overridden by `with_location`.
926 #[track_caller]
927 pub fn with_actions<X>(self, actions: X) -> Builder<C, I, X, S>
928 where
929 X: Actions<C, I>,
930 {
931 Builder {
932 subject: self.subject,
933 actions,
934 name: self.name,
935 rank: self.rank,
936 settle: self.settle,
937 location: Some(self.location.unwrap_or_else(Location::caller)),
938 _config: self._config,
939 }
940 }
941
942 /// Explicitly sets the source code [`Location`] associated with the agent.
943 ///
944 /// Overrides the location captured by `with_actions`. Useful if the agent's
945 /// definition site is different from where the builder is called.
946 pub const fn with_location(mut self, location: &'static Location<'static>) -> Self {
947 self.location = Some(location);
948 self
949 }
950
951 /// Sets a custom base name for the agent's [`Label`].
952 ///
953 /// If not set, the name defaults to the type name of the subject (`I`)
954 /// when [`finish()`](Self::finish) is called.
955 pub const fn with_name(mut self, name: &'static str) -> Self {
956 self.name = Some(name);
957 self
958 }
959
960 /// Sets a custom initial scheduling rank for the agent.
961 ///
962 /// If not set, the rank defaults to `sim.config().default_rank()`
963 /// during activation.
964 pub fn with_rank(self, rank: C::Rank) -> Self {
965 Builder {
966 rank: Some(rank),
967 ..self
968 }
969 }
970
971 /// Sets a custom [`Settle`] strategy for the agent's finalization.
972 ///
973 /// The `Settle` trait determines how the agent's `Output` value (of type
974 /// `R`) is converted into an [`ExitStatus`].
975 pub fn with_finalizer<X>(self, finalizer: X) -> Builder<C, I, A, X> {
976 Builder {
977 subject: self.subject,
978 actions: self.actions,
979 name: self.name,
980 rank: self.rank,
981 settle: finalizer,
982 location: self.location,
983 _config: self._config,
984 }
985 }
986
987 /// Sets the [`Settle`] strategy to [`Checked`].
988 ///
989 /// This is a shortcut for `with_finalizer(Checked)`. The `Checked` strategy
990 /// interprets `bool`, `Option`, and `Result` outputs to determine the
991 /// agent's [`ExitStatus`] (`Success` vs. `Failure`).
992 pub fn checked(self) -> Builder<C, I, A, Checked> {
993 self.with_finalizer(Checked)
994 }
995}
996
997impl<C: ?Sized + Config, I: Any, R, A, S> Builder<C, I, A, S>
998where
999 A: Actions<C, I, Output = R>,
1000 S: Settle<R>,
1001{
1002 /// Constructs the [`Agent`] instance from the builder configuration.
1003 ///
1004 /// This method consumes the builder and returns the configured `Agent`
1005 /// wrapped in a [`Lease`]. The `Lease` provides ownership transfer
1006 /// semantics for mutable borrows and requires pinning before activation.
1007 ///
1008 /// Requires that `with_subject` and `with_actions` have been called
1009 /// previously. It uses the type name of `I` as the default agent name if
1010 /// `with_name` was not called.
1011 pub fn finish<'p>(self) -> Lease<'p, Agent<C, I, A, S>> {
1012 // Create the Agent in the Born state
1013 Lease::new(Agent {
1014 item: self.subject,
1015 inner: Inner::Born {
1016 action: self.actions,
1017 name: self.name.unwrap_or_else(|| type_name::<I>()),
1018 rank: self.rank,
1019 // Create the root job builder with finalizer and location
1020 builder: JobBuilder::root() // Mark as the root job
1021 .with_finalizer(self.settle)
1022 .with_location(self.location.unwrap()),
1023 },
1024 })
1025 }
1026}
1027
1028/* **************************************************** Trait Implementations */
1029
1030impl<C, I, A, R> Behavior<C> for (I, A)
1031where
1032 C: ?Sized + Config,
1033 I: Any,
1034 A: AsyncFn(&I, &Sim<C>) -> R,
1035{
1036 type Output = R;
1037
1038 fn actions(&self, sim: &Sim<C>) -> impl Future<Output = R> {
1039 (self.1)(&self.0, sim)
1040 }
1041
1042 fn name(&self) -> &'static str {
1043 type_name::<I>()
1044 }
1045}
1046
1047// Blanket implementation forwarding `Actions` to `private::Action`.
1048// This connects the public `Actions` trait to the private HRTB machinery.
1049impl<A, C, I, R> Actions<C, I> for A
1050where
1051 A: for<'p> private::Action<'p, C, I, Future: Future<Output = R>>,
1052 C: ?Sized + Config,
1053 I: ?Sized + Any,
1054{
1055 type Output = R;
1056}
1057
1058// Implement `private::Action` for `Pending<()>` used in the empty builder.
1059impl<'p, C, I> private::Action<'p, C, I> for Pending<()>
1060where
1061 C: ?Sized + Config,
1062 I: ?Sized + Any,
1063{
1064 type Future = Pending<()>;
1065
1066 fn bind(self, _item: &'p I, _sim: &'p Sim<C>) -> Self::Future {
1067 pending()
1068 }
1069}
1070
1071// Implement `private::Action` for function pointers and closures that match the
1072// signature. This is the core implementation that allows `async fn` references
1073// to be used as actions.
1074impl<'p, C, I, F, R> private::Action<'p, C, I> for F
1075where
1076 C: ?Sized + Config,
1077 I: ?Sized + Any,
1078 F: FnOnce(&'p I, &'p Sim<C>) -> R,
1079 R: Future + 'p,
1080{
1081 type Future = R;
1082
1083 fn bind(self, item: &'p I, sim: &'p Sim<C>) -> Self::Future {
1084 self(item, sim)
1085 }
1086}