use std::fmt; use std::sync::{Arc, Mutex}; use std::time::Duration; use crate::channel::ChannelObserver; use crate::executor::{Executor, SimulationContext}; use crate::model::ProtoModel; use crate::time::{AtomicTime, MonotonicTime, TearableAtomicTime}; use crate::time::{Clock, NoClock}; use crate::util::priority_queue::PriorityQueue; use crate::util::sync_cell::SyncCell; use super::{add_model, ExecutionError, Mailbox, Scheduler, SchedulerQueue, Signal, Simulation}; /// Builder for a multi-threaded, discrete-event simulation. pub struct SimInit { executor: Executor, scheduler_queue: Arc>, time: AtomicTime, clock: Box, timeout: Duration, observers: Vec<(String, Box)>, abort_signal: Signal, } impl SimInit { /// Creates a builder for a multithreaded simulation running on all /// available logical threads. pub fn new() -> Self { Self::with_num_threads(num_cpus::get()) } /// Creates a builder for a simulation running on the specified number of /// threads. /// /// Note that the number of worker threads is automatically constrained to /// be between 1 and `usize::BITS` (inclusive). It is always set to 1 on /// `wasm` targets. pub fn with_num_threads(num_threads: usize) -> Self { let num_threads = if cfg!(target_family = "wasm") { 1 } else { num_threads.clamp(1, usize::BITS as usize) }; let time = SyncCell::new(TearableAtomicTime::new(MonotonicTime::EPOCH)); let simulation_context = SimulationContext { #[cfg(feature = "tracing")] time_reader: time.reader(), }; let abort_signal = Signal::new(); let executor = if num_threads == 1 { Executor::new_single_threaded(simulation_context, abort_signal.clone()) } else { Executor::new_multi_threaded(num_threads, simulation_context, abort_signal.clone()) }; Self { executor, scheduler_queue: Arc::new(Mutex::new(PriorityQueue::new())), time, clock: Box::new(NoClock::new()), timeout: Duration::ZERO, observers: Vec::new(), abort_signal, } } /// Adds a model and its mailbox to the simulation bench. /// /// The `name` argument needs not be unique. If an empty string is provided, /// it is replaced by the string ``. This name serves an identifier /// for logging or error-reporting purposes. pub fn add_model( mut self, model: P, mailbox: Mailbox, name: impl Into, ) -> Self { let name = name.into(); self.observers .push((name.clone(), Box::new(mailbox.0.observer()))); let scheduler = Scheduler::new(self.scheduler_queue.clone(), self.time.reader()); add_model( model, mailbox, name, scheduler, &self.executor, &self.abort_signal, ); self } /// Synchronizes the simulation with the provided [`Clock`]. /// /// If the clock isn't explicitly set then the default [`NoClock`] is used, /// resulting in the simulation running as fast as possible. pub fn set_clock(mut self, clock: impl Clock + 'static) -> Self { self.clock = Box::new(clock); self } /// Sets a timeout for the call to [`SimInit::init`] and for any subsequent /// simulation step. /// /// The timeout corresponds to the maximum wall clock time allocated for the /// completion of a single simulation step before an /// [`ExecutionError::Timeout`] error is raised. /// /// A null duration disables the timeout, which is the default behavior. /// /// See also [`Simulation::set_timeout`]. #[cfg(not(target_family = "wasm"))] pub fn set_timeout(mut self, timeout: Duration) -> Self { self.timeout = timeout; self } /// Builds a simulation initialized at the specified simulation time, /// executing the [`Model::init()`](crate::model::Model::init) method on all /// model initializers. pub fn init(mut self, start_time: MonotonicTime) -> Result { self.time.write(start_time); self.clock.synchronize(start_time); let mut simulation = Simulation::new( self.executor, self.scheduler_queue, self.time, self.clock, self.timeout, self.observers, ); simulation.run()?; Ok(simulation) } } impl Default for SimInit { fn default() -> Self { Self::new() } } impl fmt::Debug for SimInit { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("SimInit").finish_non_exhaustive() } }