Skip to main content

descartes_core/
lib.rs

1//! Core discrete event simulation engine.
2//!
3//! This crate provides the fundamental building blocks for discrete event simulation:
4//! time management, event scheduling, task execution, and component-based architecture.
5//!
6//! # Architecture Overview
7//!
8//! The simulation is built around two main types:
9//!
10//! - [`Simulation`]: The main entry point that owns the scheduler and components.
11//!   Use this to run simulations, add components, and access simulation state.
12//!
13//! - [`SchedulerHandle`]: A cloneable handle for scheduling events during simulation.
14//!   Pass this to Tower service layers and other components that need to schedule
15//!   events without direct access to the simulation.
16//!
17//! # Basic Usage
18//!
19//! ```rust,no_run
20//! use descartes_core::{Simulation, SimTime, Executor};
21//! use std::time::Duration;
22//!
23//! // Create a simulation
24//! let mut simulation = Simulation::default();
25//!
26//! // Get a scheduler handle for Tower layers or other components
27//! let scheduler = simulation.scheduler_handle();
28//!
29//! // Schedule events, add components, run simulation
30//! simulation.execute(Executor::unbound());
31//! ```
32//!
33//! # Scheduling Events
34//!
35//! For simple scheduling from within the simulation:
36//! ```rust,ignore
37//! simulation.schedule(SimTime::from_millis(100), component_key, MyEvent::Tick);
38//! ```
39//!
40//! For Tower layers or components that need a cloneable handle:
41//! ```rust,ignore
42//! let scheduler = simulation.scheduler_handle();
43//! scheduler.schedule(SimTime::from_millis(100), component_key, MyEvent::Tick);
44//! ```
45//!
46//! # Time Model
47//!
48//! All timing uses [`SimTime`], which represents simulation time (not wall-clock time).
49//! This ensures deterministic, reproducible behavior across simulation runs.
50
51pub mod async_runtime;
52pub mod dists;
53pub mod error;
54pub mod execute;
55pub mod ids;
56pub mod logging;
57pub mod randomness;
58pub mod request;
59pub mod scheduler;
60pub mod task;
61pub mod time;
62pub mod types;
63pub mod waker;
64
65pub mod formal;
66
67use std::any::Any;
68use std::collections::HashMap;
69use std::sync::{
70    atomic::{AtomicU64, Ordering as AtomicOrdering},
71    Arc, Mutex,
72};
73use tracing::{debug, info, instrument, trace, warn};
74
75//pub use environment::Environment;
76pub use error::{EventError, SimError};
77// pub use event::{Event, EventPayload, EventScheduler};
78// pub use process::{Process, ProcessManager, Delay, EventWaiter, delay, wait_for_event};
79pub use execute::{Execute, Executor};
80pub use logging::{
81    component_span, event_span, init_detailed_simulation_logging, init_simulation_logging,
82    init_simulation_logging_with_level, simulation_span, task_span,
83};
84pub use randomness::{DrawSite, RandomProvider};
85pub use request::{
86    AttemptStatus, Request, RequestAttempt, RequestAttemptId, RequestId, RequestStatus, Response,
87    ResponseStatus,
88};
89pub use scheduler::{
90    current_time, defer_wake, defer_wake_after, in_scheduler_context, ClockRef, EventEntry,
91    EventFrontierPolicy, FifoFrontierPolicy, FrontierEvent, FrontierEventKind, FrontierSignature,
92    Scheduler, SchedulerHandle, UniformRandomFrontierPolicy,
93};
94pub use task::{ClosureTask, PeriodicTask, RetryTask, Task, TaskHandle, TaskId, TimeoutTask};
95pub use time::SimTime;
96pub use types::EventId;
97pub use waker::create_des_waker;
98
99pub use formal::{CertificateError, LyapunovError, VerificationError};
100
101use uuid::Uuid;
102
103/// Global configuration for a simulation.
104///
105/// This currently only exposes a seed used to derive
106/// deterministic randomness across components, but can
107/// be extended in the future with additional options.
108#[derive(Debug, Clone)]
109pub struct SimulationConfig {
110    /// Global seed for deterministic randomness.
111    pub seed: u64,
112}
113
114impl Default for SimulationConfig {
115    fn default() -> Self {
116        Self { seed: 1 }
117    }
118}
119
120#[derive(Debug)]
121pub struct Key<T> {
122    id: Uuid,
123    _marker: std::marker::PhantomData<T>,
124}
125
126impl<T> Key<T> {
127    /// Create a new key with a generated UUID.
128    ///
129    /// This is deterministic (derived from a process-local counter) so that tests
130    /// and simulations are reproducible without relying on wall-clock UUIDs.
131    pub fn new() -> Self {
132        static NEXT_KEY_ID: AtomicU64 = AtomicU64::new(0);
133        let counter = NEXT_KEY_ID.fetch_add(1, AtomicOrdering::Relaxed) + 1;
134        let id = crate::ids::deterministic_uuid(0, crate::ids::UUID_DOMAIN_KEY, counter);
135        Self::new_with_id(id)
136    }
137
138    pub fn new_with_id(id: Uuid) -> Self {
139        Self {
140            id,
141            _marker: std::marker::PhantomData,
142        }
143    }
144
145    /// Get the UUID of this key
146    pub fn id(&self) -> Uuid {
147        self.id
148    }
149}
150
151impl<T> Default for Key<T> {
152    fn default() -> Self {
153        Self::new()
154    }
155}
156
157impl<T> Clone for Key<T> {
158    fn clone(&self) -> Self {
159        *self
160    }
161}
162impl<T> Copy for Key<T> {}
163
164pub trait ProcessEventEntry: Any {
165    fn process_event_entry(&mut self, entry: EventEntry, scheduler: &mut Scheduler);
166    fn as_any_mut(&mut self) -> &mut dyn Any;
167}
168
169pub trait Component: ProcessEventEntry {
170    type Event: 'static;
171
172    fn process_event(
173        &mut self,
174        self_id: Key<Self::Event>,
175        event: &Self::Event,
176        scheduler: &mut Scheduler,
177    );
178
179    // should we have a function to dump state?
180}
181
182impl<E, C> ProcessEventEntry for C
183where
184    E: std::fmt::Debug + 'static,
185    C: Component<Event = E> + 'static,
186{
187    fn process_event_entry(&mut self, entry: EventEntry, scheduler: &mut Scheduler) {
188        if let EventEntry::Component(component_entry) = entry {
189            let typed_entry = component_entry
190                .downcast::<E>()
191                .expect("Failed to downcast event entry.");
192            self.process_event(typed_entry.component_key, typed_entry.event, scheduler);
193        }
194    }
195
196    fn as_any_mut(&mut self) -> &mut dyn Any {
197        self
198    }
199}
200
201/// Container holding type-erased components.
202#[derive(Default)]
203pub struct Components {
204    components: HashMap<Uuid, Box<dyn ProcessEventEntry>>,
205    next_component_id: u64,
206    id_seed: u64,
207}
208
209impl Components {
210    #[allow(clippy::missing_panics_doc)]
211    /// Process the event on the component given by the event entry.
212    pub fn process_event_entry(&mut self, entry: EventEntry, scheduler: &mut Scheduler) {
213        match entry {
214            EventEntry::Component(component_entry) => {
215                if let Some(component) = self.components.get_mut(&component_entry.component) {
216                    component
217                        .process_event_entry(EventEntry::Component(component_entry), scheduler);
218                }
219            }
220            EventEntry::Task(task_entry) => {
221                // Execute the task
222                scheduler.execute_task(task_entry.task_id);
223            }
224        }
225    }
226
227    /// Registers a new component with the given ID.
228    #[must_use]
229    pub fn register_with_id<E: std::fmt::Debug + 'static, C: Component<Event = E> + 'static>(
230        &mut self,
231        id: Uuid,
232        component: C,
233    ) -> Key<E> {
234        self.components.insert(id, Box::new(component));
235        Key::new_with_id(id)
236    }
237
238    /// Registers a new component and returns its ID.
239    ///
240    /// This uses a deterministic ID generator (seed + counter). For simulation-wide
241    /// determinism prefer `Simulation::add_component`, which uses the simulation seed.
242    #[must_use]
243    pub fn register<E: std::fmt::Debug + 'static, C: Component<Event = E> + 'static>(
244        &mut self,
245        component: C,
246    ) -> Key<E> {
247        self.next_component_id += 1;
248        let id = crate::ids::deterministic_uuid(
249            self.id_seed,
250            crate::ids::UUID_DOMAIN_COMPONENT,
251            self.next_component_id,
252        );
253        self.register_with_id(id, component)
254    }
255
256    pub fn remove<E: 'static, C: Component<Event = E> + 'static>(
257        &mut self,
258        key: Key<E>,
259    ) -> Option<C> {
260        self.components.remove(&key.id).and_then(|boxed_trait| {
261            // Since ProcessEventEntry extends Any, we can cast the Box
262            let boxed_any: Box<dyn std::any::Any> = boxed_trait;
263            boxed_any.downcast::<C>().ok().map(|boxed_c| *boxed_c)
264        })
265    }
266
267    /// Get mutable access to a component
268    pub fn get_component_mut<E: 'static, C: Component<Event = E> + 'static>(
269        &mut self,
270        key: Key<E>,
271    ) -> Option<&mut C> {
272        self.components.get_mut(&key.id).and_then(|boxed_trait| {
273            // Cast to Any first, then downcast to the concrete type
274            let any_ref = boxed_trait.as_any_mut();
275            any_ref.downcast_mut::<C>()
276        })
277    }
278}
279
280/// Simulation struct that puts different parts of the simulation together.
281///
282/// See the [crate-level documentation](index.html) for more information.
283pub struct Simulation {
284    /// Event scheduler (wrapped in Arc<Mutex<>> for interior mutability).
285    scheduler: Arc<Mutex<Scheduler>>,
286    /// Same-time frontier tie-breaking policy.
287    frontier_policy: Box<dyn scheduler::EventFrontierPolicy>,
288    /// Deterministic component ID counter.
289    next_component_id: u64,
290    /// Component container.
291    pub components: Components,
292    /// Global configuration for this simulation instance.
293    config: SimulationConfig,
294}
295
296impl Default for Simulation {
297    fn default() -> Self {
298        // Use a well-known default seed for simulations created
299        // via `Simulation::default()` so that behavior is
300        // reproducible across runs unless overridden.
301        Self::new(SimulationConfig { seed: 42 })
302    }
303}
304
305#[allow(clippy::arc_with_non_send_sync)]
306impl Simulation {
307    /// Create a new simulation with the given configuration.
308    #[must_use]
309    pub fn new(config: SimulationConfig) -> Self {
310        Self {
311            scheduler: Arc::new(Mutex::new(Scheduler::with_seed(config.seed))),
312            frontier_policy: Box::new(scheduler::FifoFrontierPolicy),
313            next_component_id: 0,
314            components: Components::default(),
315            config,
316        }
317    }
318
319    /// Set the same-time event frontier tie-breaking policy.
320    ///
321    /// By default, simulations use deterministic FIFO ordering.
322    pub fn set_frontier_policy(&mut self, policy: Box<dyn scheduler::EventFrontierPolicy>) {
323        self.frontier_policy = policy;
324    }
325
326    /// Access the simulation configuration.
327    #[must_use]
328    pub fn config(&self) -> &SimulationConfig {
329        &self.config
330    }
331
332    /// Returns a handle for scheduling events during simulation stepping.
333    ///
334    /// The `SchedulerHandle` can be cloned and passed to Tower service layers
335    /// or other components that need to schedule events without locking the
336    /// entire simulation.
337    ///
338    /// # Example
339    ///
340    /// ```rust,ignore
341    /// let mut simulation = Simulation::default();
342    /// let scheduler = simulation.scheduler_handle();
343    ///
344    /// // Pass to Tower layers
345    /// let service = DesServiceBuilder::new("server".to_string())
346    ///     .timeout(Duration::from_secs(5), scheduler)
347    ///     .build(&mut simulation)?;
348    /// ```
349    #[must_use]
350    pub fn scheduler_handle(&self) -> SchedulerHandle {
351        SchedulerHandle::new(Arc::clone(&self.scheduler))
352    }
353
354    /// Returns the current simulation time.
355    #[must_use]
356    pub fn time(&self) -> SimTime {
357        self.scheduler.lock().unwrap().time()
358    }
359
360    /// Performs one step of the simulation. Returns `true` if there was in fact an event
361    /// available to process, and `false` otherwise, which signifies that the simulation
362    /// ended.
363    pub fn step(&mut self) -> bool {
364        // Pop the next event while holding the lock briefly
365        let event = {
366            let mut scheduler = self.scheduler.lock().unwrap();
367            scheduler.pop_with_policy(self.frontier_policy.as_mut())
368        };
369
370        event.is_some_and(|event| {
371            trace!(
372                event_time = ?event.time(),
373                event_type = match &event {
374                    EventEntry::Component(_) => "Component",
375                    EventEntry::Task(_) => "Task",
376                },
377                "Processing simulation step"
378            );
379
380            // Set scheduler context for deferred wakes
381            {
382                let scheduler = self.scheduler.lock().unwrap();
383                scheduler::set_scheduler_context(&scheduler);
384            }
385
386            // Process the event - need mutable access to scheduler for task execution
387            {
388                let mut scheduler = self.scheduler.lock().unwrap();
389                self.components.process_event_entry(event, &mut scheduler);
390            }
391
392            // Clear scheduler context
393            scheduler::clear_scheduler_context();
394
395            // Process any deferred wakes that were registered during event processing
396            {
397                let mut scheduler = self.scheduler.lock().unwrap();
398                scheduler::drain_deferred_wakes(&mut scheduler);
399            }
400
401            true
402        })
403    }
404
405    /// Runs the entire simulation.
406    ///
407    /// The stopping condition and other execution details depend on the executor used.
408    /// See [`Execute`] and [`Executor`] for more details.
409    #[instrument(skip(self, executor), fields(
410        initial_time = ?self.time()
411    ))]
412    pub fn execute<E: Execute>(&mut self, executor: E) {
413        info!("Starting simulation execution");
414        executor.execute(self);
415        info!(
416            final_time = ?self.time(),
417            "Simulation execution completed"
418        );
419    }
420
421    /// Adds a new component.
422    #[must_use]
423    #[instrument(skip(self, component), fields(component_type = std::any::type_name::<C>()))]
424    pub fn add_component<E: std::fmt::Debug + 'static, C: Component<Event = E> + 'static>(
425        &mut self,
426        component: C,
427    ) -> Key<E> {
428        self.next_component_id += 1;
429        let id = crate::ids::deterministic_uuid(
430            self.config.seed,
431            crate::ids::UUID_DOMAIN_COMPONENT,
432            self.next_component_id,
433        );
434
435        let key = self.components.register_with_id(id, component);
436        debug!(component_id = ?key.id(), "Added component to simulation");
437        key
438    }
439
440    /// Remove a component: usually at the end of the simulation to peek at the state
441    #[must_use]
442    #[instrument(skip(self), fields(component_id = ?key.id()))]
443    pub fn remove_component<E: std::fmt::Debug + 'static, C: Component<Event = E> + 'static>(
444        &mut self,
445        key: Key<E>,
446    ) -> Option<C> {
447        let result = self.components.remove(key);
448        if result.is_some() {
449            debug!("Removed component from simulation");
450        } else {
451            warn!("Attempted to remove non-existent component");
452        }
453        result
454    }
455
456    /// Get mutable access to a component
457    pub fn get_component_mut<E: std::fmt::Debug + 'static, C: Component<Event = E> + 'static>(
458        &mut self,
459        key: Key<E>,
460    ) -> Option<&mut C> {
461        self.components.get_component_mut(key)
462    }
463
464    /// Schedules a new event to be executed at time `time` in component `component`.
465    pub fn schedule<E: std::fmt::Debug + 'static>(
466        &mut self,
467        time: SimTime,
468        component: Key<E>,
469        event: E,
470    ) {
471        let mut scheduler = self.scheduler.lock().unwrap();
472        scheduler.schedule(time, component, event);
473    }
474
475    /// Schedules a new event to be executed right away in component `component`.
476    pub fn schedule_now<E: std::fmt::Debug + 'static>(&mut self, component: Key<E>, event: E) {
477        let mut scheduler = self.scheduler.lock().unwrap();
478        scheduler.schedule(SimTime::zero(), component, event);
479    }
480
481    /// Returns the time of the next scheduled event, or None if no events are scheduled.
482    pub fn peek_next_event_time(&self) -> Option<SimTime> {
483        let mut scheduler = self.scheduler.lock().unwrap();
484        scheduler.peek().map(|e| e.time())
485    }
486
487    /// Returns a ClockRef for reading the simulation time.
488    pub fn clock(&self) -> scheduler::ClockRef {
489        let scheduler = self.scheduler.lock().unwrap();
490        scheduler.clock()
491    }
492
493    /// Schedule a closure as a task
494    pub fn schedule_closure<F, R>(&mut self, delay: SimTime, closure: F) -> task::TaskHandle<R>
495    where
496        F: FnOnce(&mut Scheduler) -> R + 'static,
497        R: 'static,
498    {
499        let mut scheduler = self.scheduler.lock().unwrap();
500        scheduler.schedule_closure(delay, closure)
501    }
502
503    /// Schedule a timeout callback
504    pub fn timeout<F>(&mut self, delay: SimTime, callback: F) -> task::TaskHandle<()>
505    where
506        F: FnOnce(&mut Scheduler) + 'static,
507    {
508        let mut scheduler = self.scheduler.lock().unwrap();
509        scheduler.timeout(delay, callback)
510    }
511
512    /// Schedule a task to run at a specific time
513    pub fn schedule_task<T: task::Task>(
514        &mut self,
515        delay: SimTime,
516        task: T,
517    ) -> task::TaskHandle<T::Output> {
518        let mut scheduler = self.scheduler.lock().unwrap();
519        scheduler.schedule_task(delay, task)
520    }
521
522    /// Cancel a scheduled task
523    pub fn cancel_task<T>(&mut self, handle: task::TaskHandle<T>) -> bool {
524        let mut scheduler = self.scheduler.lock().unwrap();
525        scheduler.cancel_task(handle)
526    }
527
528    /// Get the result of a completed task
529    pub fn get_task_result<T: 'static>(&mut self, handle: task::TaskHandle<T>) -> Option<T> {
530        let mut scheduler = self.scheduler.lock().unwrap();
531        scheduler.get_task_result(handle)
532    }
533
534    /// Check if there are pending events
535    pub fn has_pending_events(&self) -> bool {
536        let mut scheduler = self.scheduler.lock().unwrap();
537        scheduler.peek().is_some()
538    }
539}