//! Virtual Clock for simulation timing synchronization. //! //! The `VirtualClock` provides a shared virtual time source that all component threads //! synchronize against. Instead of using wall-clock time (`Instant::now()`) for timing //! decisions, components use virtual time — a monotonically increasing nanosecond counter //! that is independent of OS scheduling granularity. //! //! # Architecture //! //! ```text //! Governor Thread Component Thread 1 Component Thread N //! | | | //! advance_by(tick_size) wait_until(target) wait_until(target) //! | | | //! v v v //! [VirtualClock] <-- notify_all wakes all waiting threads //! time_ns: AtomicU64 //! ``` //! //! # How It Works //! //! 1. The `RealTimeGovernor` advances virtual time by `tick_size` nanoseconds //! 2. All component threads that called `wait_until()` are woken via `condvar.notify_all()` //! 3. Each component checks if virtual time has reached its target //! 4. If yes: process the update, schedule next wake-up //! 5. If no: go back to waiting //! //! This models real hardware where each chip operates independently but shares //! a common timeline. Components can race each other, just like real chips. use std::sync::atomic::{AtomicBool, AtomicU64, AtomicU8, AtomicUsize, Ordering}; use std::sync::{Condvar, Mutex}; use std::time::{Duration, Instant}; /// Convert a Duration to u64 nanoseconds, panicking if it exceeds u64::MAX (~584 years). /// /// This is a safety check to prevent silent truncation when converting from /// `Duration::as_nanos()` (u128) to u64. In practice, durations exceeding /// u64::MAX nanoseconds are impossible in simulation, so a panic indicates a bug. fn duration_to_nanos(d: Duration) -> u64 { u64::try_from(d.as_nanos()).expect("Duration exceeds u64::MAX nanoseconds") } /// Simulation mode controlling how virtual time advances. #[derive(Debug, Clone, Copy, PartialEq)] #[repr(u8)] pub enum SimulationMode { /// Virtual time advances at a rate determined by `speed_multiplier`. /// At 1.0x, one virtual nanosecond maps to one wall-clock nanosecond. /// At 0.1x, one virtual nanosecond takes 10 wall-clock nanoseconds. RealTime = 0, /// Virtual time advances as fast as the host CPU allows. /// No wall-clock sleeping — pure computation speed. MaxSpeed = 1, /// Virtual time advances only when the user triggers a step. /// Each step advances by `tick_size` nanoseconds. SingleStep = 2, } impl SimulationMode { fn from_u8(value: u8) -> Self { match value { 0 => SimulationMode::RealTime, 1 => SimulationMode::MaxSpeed, 2 => SimulationMode::SingleStep, _ => { debug_assert!(false, "Invalid SimulationMode value: {}", value); SimulationMode::RealTime } } } } /// Granularity for single-step mode. /// /// TODO: Integrate into `trigger_step()` / `consume_step()` to allow stepping /// by clock phase, cycle, or instruction granularity. #[derive(Debug, Clone, Copy, PartialEq)] #[allow(dead_code)] // Planned for future use in single-step control pub enum StepGranularity { /// Advance by one clock phase (e.g., PHI1 high → PHI1 low). /// For a 750 kHz clock, this is ~666 ns of virtual time. ClockPhase, /// Advance by one full clock cycle (PHI1 high → PHI1 low → PHI2 high → PHI2 low). /// For a 750 kHz clock, this is ~1333 ns of virtual time. ClockCycle, /// Advance by one complete CPU instruction (8 or 16 clock cycles). /// For a 750 kHz clock, this is ~10.6–21.3 µs of virtual time. Instruction, } /// Internal state protected by the condvar mutex. struct ClockState { /// Flag indicating the simulation is paused. paused: bool, /// Flag indicating the simulation has been stopped (cancelled). /// Used to wake threads in `wait_for_start()` that are blocking /// before the simulation was ever started. stopped: bool, } /// Shared virtual clock for simulation timing synchronization. /// /// This struct is wrapped in `Arc` and shared between: /// - The `RealTimeGovernor` thread (advances time) /// - All component threads (wait for time to reach their scheduled update) /// /// # Thread Safety /// /// All time values are stored as `AtomicU64` for lock-free reads. /// The `Condvar` is used to efficiently wake waiting threads when time advances. /// /// # Lock Ordering /// /// The `VirtualClock` has an internal `state_mutex` used for condvar synchronization. /// When integrating with external mutexes (e.g., `parking_lot::Mutex`), /// the following lock ordering must be observed to prevent deadlocks: /// /// **Order: External mutex → VirtualClock.state_mutex** /// /// ```text /// ConfigurableSystem mutex (parking_lot) /// └→ VirtualClock.state_mutex (std::sync) /// └→ No further locks acquired /// ``` /// /// This ordering is safe because: /// - The GUI thread acquires `ConfigurableSystem` first, then reads VirtualClock atomics /// - Component threads never acquire `ConfigurableSystem`; they only access VirtualClock /// - The governor thread only accesses VirtualClock /// /// **Do NOT** acquire `ConfigurableSystem` mutex while holding `VirtualClock.state_mutex`. /// /// # Example /// /// ```rust,ignore /// use std::sync::Arc; /// use std::time::Duration; /// use rusty_emu::virtual_clock::VirtualClock; /// /// let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333))); /// /// // In governor thread: /// clock.advance_by(Duration::from_nanos(333)); /// /// // In component thread: /// let current_time = clock.wait_until(Duration::from_nanos(666)); /// // Component can now process its update at virtual time 666ns /// ``` pub struct VirtualClock { /// Current virtual time in nanoseconds (monotonically increasing). time_ns: AtomicU64, /// Virtual time advance per tick in nanoseconds. tick_size_ns: AtomicU64, /// Speed multiplier stored as fixed-point: actual_value = raw / 1_000_000_000. /// This allows atomic updates without floating-point. /// Examples: /// 1.0x = 1_000_000_000 /// 0.1x = 100_000_000 /// 0.001x = 1_000_000 /// 10.0x = 10_000_000_000 speed_multiplier_fp: AtomicU64, /// Current simulation mode (RealTime, MaxSpeed, SingleStep). mode: AtomicU8, /// Whether the simulation is running (used to signal component threads to exit). running: AtomicBool, /// Single-step trigger: set to true by user, consumed by governor. step_pending: AtomicBool, /// Counter for batching notify_all calls. Only every NOTIFY_INTERVAL-th /// call to advance_by() actually notifies waiting threads. This reduces /// mutex contention in MaxSpeed mode where advance_by() is called in a /// tight loop. notify_counter: AtomicU64, /// Debug-only: tracks which thread last called advance_by() to detect /// concurrent callers. 0 means no owner yet. Each thread gets a unique /// ID via a global counter (see advance_by). #[cfg(debug_assertions)] advance_owner: AtomicUsize, /// Condvar for notifying component threads when virtual time advances. time_advanced: Condvar, /// Mutex for condvar wait. state_mutex: Mutex, } impl VirtualClock { /// Create a new VirtualClock with the specified tick size. /// /// # Parameters /// - `tick_size`: Virtual time advance per tick. For a 750 kHz two-phase clock, /// use `Duration::from_nanos(333)` (1/4 of the 1333ns cycle). /// /// # Returns /// A new VirtualClock initialized at virtual time 0, speed 1.0x, RealTime mode. pub fn new(tick_size: Duration) -> Self { VirtualClock { time_ns: AtomicU64::new(0), tick_size_ns: AtomicU64::new(duration_to_nanos(tick_size)), speed_multiplier_fp: AtomicU64::new(1_000_000_000), // 1.0x mode: AtomicU8::new(SimulationMode::RealTime as u8), running: AtomicBool::new(false), step_pending: AtomicBool::new(false), notify_counter: AtomicU64::new(0), #[cfg(debug_assertions)] advance_owner: AtomicUsize::new(0), time_advanced: Condvar::new(), state_mutex: Mutex::new(ClockState { paused: false, stopped: false }), } } // ========================================================================= // Time Read Operations (lock-free) // ========================================================================= /// Get the current virtual time. /// /// This is lock-free and can be called from any thread at any time. #[inline] pub fn now(&self) -> Duration { Duration::from_nanos(self.time_ns.load(Ordering::Acquire)) } /// Get the current virtual time as raw nanoseconds. /// /// Useful for high-performance comparisons where `Duration` overhead matters. #[inline] pub fn now_ns(&self) -> u64 { self.time_ns.load(Ordering::Acquire) } /// Get the current tick size. pub fn tick_size(&self) -> Duration { Duration::from_nanos(self.tick_size_ns.load(Ordering::Acquire)) } /// Get the current tick size as raw nanoseconds. /// /// Useful for high-performance calculations where `Duration` overhead matters. #[inline] pub fn tick_size_ns(&self) -> u64 { self.tick_size_ns.load(Ordering::Acquire) } /// Get the total number of ticks elapsed since start. /// /// Computed from `time_ns / tick_size_ns` for consistency — avoids /// maintaining a separate counter that could diverge from the actual time. /// /// **Note:** This is an approximate value. The two atomic loads (`time_ns` /// and `tick_size_ns`) are not performed atomically. If `set_tick_size()` /// is called concurrently from another thread, the result may be briefly /// inconsistent. For precise timing decisions, use `wait_until()` instead. pub fn tick_count(&self) -> u64 { let time = self.time_ns.load(Ordering::Acquire); let tick = self.tick_size_ns.load(Ordering::Acquire); if tick == 0 { return 0; } time / tick } // ========================================================================= // Time Wait Operations (blocking) // ========================================================================= /// Block the calling thread until virtual time reaches at least `target`. /// /// This is the primary synchronization mechanism for component threads. /// The thread will sleep efficiently (via condvar) until the governor /// advances virtual time past the target. /// /// # Parameters /// - `target`: The minimum virtual time to wait for. /// /// # Returns /// The actual virtual time when the thread was woken (>= target). /// /// # Cancellation /// Returns `None` if the simulation is stopped while waiting. /// /// # Pause Behavior /// When the simulation is paused, this method blocks indefinitely (the condvar /// wait will not return until `resume()` or `stop()` is called). If `resume()` /// is called, the thread re-enters the wait loop. If `stop()` is called, the /// thread returns `None`. This means paused components do not consume CPU — they /// efficiently sleep until the simulation is resumed or stopped. pub fn wait_until(&self, target: Duration) -> Option { // NOTE: All component threads are woken on every tick via notify_all(), // even if their individual target hasn't been reached. For large numbers // of components, this causes O(N) spurious wakeups per tick. With // NOTIFY_INTERVAL=10 batching at 750 kHz, this means ~300K wakeups/sec. // Acceptable for current component counts (~10), but if this becomes a // bottleneck, consider per-target notification (e.g., min-heap of targets) // or timed wait batches. let target_ns = duration_to_nanos(target); // Fast path: check if time has already reached target (lock-free) let current = self.time_ns.load(Ordering::Acquire); if current >= target_ns { // SeqCst to match stop()'s SeqCst store — ensures we honor the // "Returns None if the simulation is stopped" contract even on // the fast path. if !self.running.load(Ordering::SeqCst) { return None; } return Some(Duration::from_nanos(current)); } // Slow path: acquire mutex to safely wait on condvar. // All checks are done under the mutex to prevent missed-wakeup races // with stop() — if stop() calls notify_all() while we hold the lock, // we'll see the running=false on the next iteration. let mut guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); loop { // Fast path: check if time has reached target (common case). // Acquire is sufficient for time_ns: we need to see the latest value // written by advance_by()'s Release store. The condvar wait already // provides the necessary synchronization guarantee (the mutex ensures // we can't miss a notify_all from advance_by). let current = self.time_ns.load(Ordering::Acquire); if current >= target_ns { return Some(Duration::from_nanos(current)); } // SeqCst to match stop()'s SeqCst store — ensures we see the // stopped state even if notify_all races with our wait(). if !self.running.load(Ordering::SeqCst) { return None; } // If paused, stay sleeping efficiently (no CPU waste). // When resume() notifies, we'll re-check conditions. if guard.paused { guard = self.time_advanced.wait(guard).unwrap_or_else(|e| e.into_inner()); continue; } guard = self.time_advanced.wait(guard).unwrap_or_else(|e| e.into_inner()); } } /// Non-blocking check: has virtual time reached the target? /// /// Use this for polling-based component loops that prefer to do useful work /// rather than block. #[inline] pub fn has_reached(&self, target: Duration) -> bool { self.time_ns.load(Ordering::Acquire) >= duration_to_nanos(target) } /// Wait until virtual time reaches target, with a timeout. /// /// Useful for component threads that need to periodically check for /// external events (e.g., GUI updates) even if virtual time hasn't advanced. /// /// # Returns /// - `Ok(Duration)`: Virtual time reached the target /// - `Err(())`: Timed out or simulation stopped pub fn wait_until_with_timeout(&self, target: Duration, timeout: Duration) -> Result { let target_ns = duration_to_nanos(target); let deadline = Instant::now() + timeout; let mut guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); loop { // If paused, stay sleeping (timeout still applies). // When resume() notifies, we'll re-check conditions. if guard.paused { // SeqCst to match stop()'s SeqCst store if !self.running.load(Ordering::SeqCst) { return Err(()); } let remaining = deadline.saturating_duration_since(Instant::now()); if remaining.is_zero() { return Err(()); } let result = self.time_advanced.wait_timeout(guard, remaining); match result { Ok((new_guard, timeout_result)) => { guard = new_guard; if timeout_result.timed_out() { return Err(()); } } Err(poisoned) => { (guard, _) = poisoned.into_inner(); } } continue; } let current = self.time_ns.load(Ordering::Acquire); if current >= target_ns { return Ok(Duration::from_nanos(current)); } // SeqCst to match stop()'s SeqCst store if !self.running.load(Ordering::SeqCst) { return Err(()); } let remaining = deadline.saturating_duration_since(Instant::now()); if remaining.is_zero() { return Err(()); } let result = self.time_advanced.wait_timeout(guard, remaining); match result { Ok((new_guard, timeout_result)) => { guard = new_guard; if timeout_result.timed_out() { return Err(()); } } Err(poisoned) => { // Mutex was poisoned — recover the guard and re-check conditions (guard, _) = poisoned.into_inner(); } } } } // ========================================================================= // Time Advance Operations (called by governor only) // ========================================================================= /// Advance virtual time by the specified duration. /// /// This is called by the `RealTimeGovernor` to move simulation time forward. /// All component threads waiting in `wait_until()` will be woken. /// /// # Parameters /// - `delta`: Amount of virtual time to advance. /// /// # Visibility /// This method is `pub(crate)` because it must only be called by the /// governor thread. Calling it from component threads would corrupt /// the simulation timeline. pub(crate) fn advance_by(&self, delta: Duration) { let delta_ns = duration_to_nanos(delta); if delta_ns == 0 { return; } // Debug-only guard: advance_by must only be called from a single thread // (the governor). Concurrent calls would produce correct fetch_add results // but could wake components at intermediate time values. #[cfg(debug_assertions)] { use std::cell::Cell; use std::sync::atomic::AtomicUsize; // Global counter for assigning unique per-thread IDs. static NEXT_THREAD_ID: AtomicUsize = AtomicUsize::new(1); thread_local! { static THREAD_ID: Cell = const { Cell::new(0) }; } let tid = THREAD_ID.with(|t| { let id = t.get(); if id == 0 { let assigned = NEXT_THREAD_ID.fetch_add(1, Ordering::Relaxed); t.set(assigned); assigned } else { id } }); // Per-instance owner tracking: different VirtualClock instances // (e.g., in different tests) can have different owner threads. let prev = self.advance_owner.load(Ordering::SeqCst); debug_assert!( prev == 0 || prev == tid, "advance_by() called from multiple threads — only the governor should advance time" ); self.advance_owner.store(tid, Ordering::SeqCst); } // SeqCst ensures that woken component threads always see the latest time_ns value, // even on weakly-ordered architectures (ARM, RISC-V). The Release/Acquire pair that // was here previously is sufficient on x86-64 (TSO) but could allow stale reads on // weaker memory models when combined with the NOTIFY_INTERVAL batching — a thread // woken by a later notify_all() might not see the cumulative effect of intermediate // Release stores. SeqCst provides total ordering at negligible cost on x86-64. self.time_ns.fetch_add(delta_ns, Ordering::SeqCst); // Batch notifications: only acquire mutex and call notify_all() every // NOTIFY_INTERVAL ticks. This reduces syscall overhead from ~3M/s to ~300K/s // at 750 kHz simulation speed, while keeping wakeup latency under ~3.3µs. let count = self.notify_counter.fetch_add(1, Ordering::Relaxed); if count % Self::NOTIFY_INTERVAL == 0 { // Hold the mutex while notifying to prevent a missed-wakeup race: // Without this, a waiter could load time_ns (see old value), then we // update time_ns AND call notify_all(), then the waiter enters wait() // — missing the notification entirely. let _guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); // We use notify_all (not notify_one) because multiple components // may be waiting for different target times, and any of them might // now have their target satisfied. self.time_advanced.notify_all(); } } /// Number of advance_by() calls between notify_all() calls. /// Reduces mutex/syscall overhead in MaxSpeed mode where ticks happen /// in a tight loop. Components may observe slightly stale time values /// (up to NOTIFY_INTERVAL ticks behind), but this is acceptable because: /// - Components that need precise timing use wait_until() which always /// wakes on the next notification /// - The maximum staleness is NOTIFY_INTERVAL × tick_size (e.g., 10 × 333ns = 3.33µs) const NOTIFY_INTERVAL: u64 = 10; /// Advance virtual time by one tick size. /// /// Convenience method equivalent to `self.advance_by(self.tick_size())`. /// /// # Visibility /// This method is `pub(crate)` because it must only be called by the /// governor thread. Calling it from component threads would corrupt /// the simulation timeline. pub(crate) fn advance_one_tick(&self) { let tick_ns = self.tick_size_ns.load(Ordering::Acquire); self.advance_by(Duration::from_nanos(tick_ns)); } // ========================================================================= // Speed Control // ========================================================================= /// Get the current speed multiplier. /// /// Returns the multiplier as a floating-point value: /// - 1.0 = real-time /// - 0.1 = 10× slower than real-time /// - 10.0 = 10× faster than real-time (limited by host CPU) pub fn speed_multiplier(&self) -> f64 { self.speed_multiplier_fp.load(Ordering::Acquire) as f64 / 1_000_000_000.0 } /// Set the speed multiplier. /// /// # Parameters /// - `multiplier`: Speed multiplier. Must be > 0.0. /// - 0.001 = 1000× slower than real-time /// - 1.0 = real-time /// - 100.0 = 100× faster than real-time (limited by host CPU) /// /// # Panics /// Panics if `multiplier` is not positive (<= 0.0). pub fn set_speed_multiplier(&self, multiplier: f64) { assert!(multiplier > 0.0, "Speed multiplier must be positive, got {}", multiplier); let fp_value = (multiplier * 1_000_000_000.0).round().clamp(1.0, u64::MAX as f64) as u64; // Verify round-trip to detect precision loss from f64 → u64 fixed-point conversion. // This catches values that cannot be represented in our fixed-point format. let reconstructed = fp_value as f64 / 1_000_000_000.0; assert!( (reconstructed - multiplier).abs() <= 0.000_000_001, "Speed multiplier {} cannot be represented with sufficient precision (stored as {}). \ Use a value with at most ~9 significant decimal digits.", multiplier, reconstructed ); self.speed_multiplier_fp.store(fp_value, Ordering::Release); } /// Get the simulation mode. pub fn mode(&self) -> SimulationMode { SimulationMode::from_u8(self.mode.load(Ordering::Acquire)) } /// Set the simulation mode. pub fn set_mode(&self, mode: SimulationMode) { self.mode.store(mode as u8, Ordering::Release); // Hold mutex during notify to prevent missed-wakeup race with wait_until() { let _guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); self.time_advanced.notify_all(); } } /// Set the tick size. /// /// This changes the virtual time advance per tick. /// Can be called while the simulation is running. /// /// # Panics /// Panics if `tick_size` is zero. A zero tick size would cause division-by-zero /// in `tick_count()` and produce no time advancement in the governor loop. pub fn set_tick_size(&self, tick_size: Duration) { let ns = duration_to_nanos(tick_size); assert!(ns > 0, "tick_size must be non-zero"); self.tick_size_ns.store(ns, Ordering::Release); } // ========================================================================= // Single-Step Control // ========================================================================= /// Trigger a single step in SingleStep mode. /// /// Returns `false` if a step is already pending (not yet consumed by governor). pub fn trigger_step(&self) -> bool { // Only allow stepping in SingleStep mode if self.mode() != SimulationMode::SingleStep { return false; } // Try to set step_pending from false to true self.step_pending .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) .is_ok() } /// Consume a pending step trigger (called by governor). /// /// Returns `true` if a step was pending and has been consumed. pub fn consume_step(&self) -> bool { self.step_pending .compare_exchange(true, false, Ordering::SeqCst, Ordering::SeqCst) .is_ok() } /// Check if a step is pending without consuming it. pub fn is_step_pending(&self) -> bool { self.step_pending.load(Ordering::Acquire) } // ========================================================================= // Lifecycle Control // ========================================================================= /// Start the simulation. /// /// Sets the running flag and wakes all waiting threads. /// This method is idempotent — calling it when already running has no effect /// beyond waking currently waiting threads. pub fn start(&self) { let was_stopped = self.running.compare_exchange( false, true, Ordering::SeqCst, Ordering::SeqCst ).is_ok(); if was_stopped { // Hold mutex during notify to prevent missed-wakeup race with wait_until() let _guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); self.time_advanced.notify_all(); } } /// Stop the simulation. /// /// Clears the running flag and wakes all waiting threads so they can exit. /// Also sets the internal `stopped` flag so that threads blocked in /// `wait_for_start()` can detect cancellation and return `false`. pub fn stop(&self) { self.running.store(false, Ordering::SeqCst); // Hold mutex during notify to prevent missed-wakeup race with wait_until() { let mut guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); guard.stopped = true; self.time_advanced.notify_all(); } } /// Check if the simulation is running. #[inline] pub fn is_running(&self) -> bool { self.running.load(Ordering::SeqCst) } /// Pause the simulation (virtual time stops advancing). /// /// The governor will stop advancing time but component threads remain alive. /// Waiting threads are notified so they can re-check the pause state. pub fn pause(&self) { { let mut guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); guard.paused = true; // Hold mutex during notify to prevent missed-wakeup race with wait_until() self.time_advanced.notify_all(); } } /// Resume the simulation from paused state. pub fn resume(&self) { { let mut guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); guard.paused = false; // Hold mutex during notify to prevent missed-wakeup race with wait_until() self.time_advanced.notify_all(); } } /// Check if the simulation is paused. pub fn is_paused(&self) -> bool { self.state_mutex .lock() .map(|g| g.paused) .unwrap_or(false) } /// Block the calling thread until the simulation is unpaused. /// /// This is used by the governor to efficiently wait (zero CPU) while paused, /// instead of busy-polling with `thread::sleep`. Returns immediately if not /// currently paused. /// /// # Returns /// - `true`: Simulation was resumed (or was never paused). /// - `false`: Simulation was stopped while paused. pub fn wait_for_resume(&self) -> bool { let mut guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); while guard.paused { // SeqCst to match stop()'s SeqCst store if !self.running.load(Ordering::SeqCst) { return false; } guard = self.time_advanced.wait(guard).unwrap_or_else(|e| e.into_inner()); } true } /// Block the calling thread until the simulation has been started. /// /// This is used by component threads to safely wait for the governor to call /// `start()` before entering their main loop. Without this, components that call /// `wait_until()` before the governor starts will see `running == false` and /// immediately return `None`. /// /// # Returns /// - `true`: Simulation was started. /// - `false`: Simulation was stopped before starting (should not normally happen). /// /// # Deadlock Prevention /// If `stop()` is called before `start()`, this method returns `false` instead /// of blocking forever. This prevents deadlock when the simulation is torn down /// during initialization. /// /// # Panic Safety /// If the governor thread panics before calling `start()` and no other thread /// calls `stop()`, component threads will block forever in this method. To /// prevent this, ensure `stop()` is called in the governor's cleanup path /// (e.g., via `Drop` or a panic hook), or use `wait_until_with_timeout()` /// instead for time-bounded waiting. pub fn wait_for_start(&self) -> bool { let mut guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); loop { // SeqCst to match start()'s SeqCst compare_exchange if self.running.load(Ordering::SeqCst) { return true; } if guard.stopped { return false; } guard = self.time_advanced.wait(guard).unwrap_or_else(|e| e.into_inner()); } } /// Reset the virtual clock to time zero and clear all state. /// /// This is equivalent to calling `stop()` followed by a full state reset. /// After `reset()`, the clock is in the same state as a freshly created one /// (except `tick_size_ns` is preserved as a config parameter). /// /// Safe to call whether the clock is running or stopped — if running, it will /// be stopped first (setting `running = false` under the mutex to prevent /// TOCTOU races with `start()`). pub fn reset(&self) { // Hold the state mutex while stopping AND performing the stores. // This prevents a TOCTOU race where another thread calls start() between // dropping the mutex and the subsequent atomic stores. let mut guard = self.state_mutex.lock().unwrap_or_else(|e| e.into_inner()); // Stop the clock under the mutex so start() cannot interleave self.running.store(false, Ordering::SeqCst); guard.paused = false; guard.stopped = false; // Perform stores while still holding the mutex so that start() // (which also acquires the mutex) cannot interleave. self.time_ns.store(0, Ordering::Release); self.step_pending.store(false, Ordering::Release); // Reset to initial state — callers expect a clean slate after reset(). self.speed_multiplier_fp.store(1_000_000_000, Ordering::Release); // 1.0× self.mode.store(SimulationMode::RealTime as u8, Ordering::Release); // Reset notification counter to ensure the first advance_by() after // restart triggers an immediate notify_all(). Without this, the counter // retains its pre-reset value, shifting the batching phase unpredictably. self.notify_counter.store(0, Ordering::Release); // tick_size_ns is intentionally NOT reset — it's a config parameter // that should persist across resets (e.g., set during system construction). } } #[cfg(test)] mod tests { use super::*; use std::sync::Arc; use std::thread; use std::time::Duration; #[test] fn test_virtual_clock_creation() { let clock = VirtualClock::new(Duration::from_nanos(333)); assert_eq!(clock.now(), Duration::ZERO); assert_eq!(clock.tick_size(), Duration::from_nanos(333)); assert!((clock.speed_multiplier() - 1.0).abs() < 0.001); assert_eq!(clock.mode(), SimulationMode::RealTime); assert!(!clock.is_running()); } #[test] fn test_advance_by() { let clock = VirtualClock::new(Duration::from_nanos(333)); clock.advance_by(Duration::from_nanos(100)); assert_eq!(clock.now(), Duration::from_nanos(100)); clock.advance_by(Duration::from_nanos(200)); assert_eq!(clock.now(), Duration::from_nanos(300)); } #[test] fn test_advance_one_tick() { let clock = VirtualClock::new(Duration::from_nanos(333)); clock.advance_one_tick(); assert_eq!(clock.now(), Duration::from_nanos(333)); clock.advance_one_tick(); assert_eq!(clock.now(), Duration::from_nanos(666)); } #[test] fn test_has_reached() { let clock = VirtualClock::new(Duration::from_nanos(333)); assert!(!clock.has_reached(Duration::from_nanos(100))); clock.advance_by(Duration::from_nanos(100)); assert!(clock.has_reached(Duration::from_nanos(100))); assert!(!clock.has_reached(Duration::from_nanos(200))); } #[test] fn test_speed_multiplier() { let clock = VirtualClock::new(Duration::from_nanos(333)); clock.set_speed_multiplier(0.5); assert!((clock.speed_multiplier() - 0.5).abs() < 0.001); clock.set_speed_multiplier(10.0); assert!((clock.speed_multiplier() - 10.0).abs() < 0.01); clock.set_speed_multiplier(0.001); assert!((clock.speed_multiplier() - 0.001).abs() < 0.0001); } #[test] fn test_simulation_mode() { let clock = VirtualClock::new(Duration::from_nanos(333)); clock.set_mode(SimulationMode::MaxSpeed); assert_eq!(clock.mode(), SimulationMode::MaxSpeed); clock.set_mode(SimulationMode::SingleStep); assert_eq!(clock.mode(), SimulationMode::SingleStep); clock.set_mode(SimulationMode::RealTime); assert_eq!(clock.mode(), SimulationMode::RealTime); } #[test] fn test_single_step() { let clock = VirtualClock::new(Duration::from_nanos(333)); clock.set_mode(SimulationMode::SingleStep); // Can't step in non-SingleStep mode clock.set_mode(SimulationMode::RealTime); assert!(!clock.trigger_step()); clock.set_mode(SimulationMode::SingleStep); // Trigger a step assert!(clock.trigger_step()); assert!(clock.is_step_pending()); // Can't trigger again while pending assert!(!clock.trigger_step()); // Consume the step assert!(clock.consume_step()); assert!(!clock.is_step_pending()); // Can't consume when none pending assert!(!clock.consume_step()); } #[test] fn test_lifecycle() { let clock = VirtualClock::new(Duration::from_nanos(333)); assert!(!clock.is_running()); clock.start(); assert!(clock.is_running()); clock.pause(); assert!(clock.is_paused()); clock.resume(); assert!(!clock.is_paused()); clock.stop(); assert!(!clock.is_running()); } #[test] fn test_reset() { let clock = VirtualClock::new(Duration::from_nanos(333)); clock.advance_by(Duration::from_nanos(1000)); assert_eq!(clock.now(), Duration::from_nanos(1000)); clock.reset(); assert_eq!(clock.now(), Duration::ZERO); } #[test] fn test_wait_until_immediate() { let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333))); clock.start(); // If time has already passed, wait_until should return immediately clock.advance_by(Duration::from_nanos(1000)); let result = clock.wait_until(Duration::from_nanos(500)); assert!(result.is_some()); assert!(result.unwrap() >= Duration::from_nanos(500)); } #[test] fn test_wait_until_blocks_then_returns() { let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333))); clock.start(); let clock_clone = Arc::clone(&clock); let handle = thread::spawn(move || { // This should block until time reaches 1000ns clock_clone.wait_until(Duration::from_nanos(1000)) }); // Give the thread time to start waiting thread::sleep(Duration::from_millis(50)); // Advance time past the target clock.advance_by(Duration::from_nanos(1500)); let result = handle.join().expect("Thread panicked"); assert!(result.is_some()); assert!(result.unwrap() >= Duration::from_nanos(1000)); } #[test] fn test_wait_until_returns_none_on_stop() { let clock = Arc::new(VirtualClock::new(Duration::from_nanos(333))); clock.start(); let clock_clone = Arc::clone(&clock); let handle = thread::spawn(move || { // This should block until stopped clock_clone.wait_until(Duration::from_nanos(1_000_000)) }); // Give the thread time to start waiting thread::sleep(Duration::from_millis(50)); // Stop the simulation clock.stop(); let result = handle.join().expect("Thread panicked"); assert!(result.is_none()); } #[test] fn test_multiple_waiters() { let clock = Arc::new(VirtualClock::new(Duration::from_nanos(100))); clock.start(); let clock1 = Arc::clone(&clock); let clock2 = Arc::clone(&clock); let clock3 = Arc::clone(&clock); let h1 = thread::spawn(move || clock1.wait_until(Duration::from_nanos(300))); let h2 = thread::spawn(move || clock2.wait_until(Duration::from_nanos(200))); let h3 = thread::spawn(move || clock3.wait_until(Duration::from_nanos(500))); // Advance past all targets thread::sleep(Duration::from_millis(10)); clock.advance_by(Duration::from_nanos(600)); assert!(h1.join().unwrap().is_some()); assert!(h2.join().unwrap().is_some()); assert!(h3.join().unwrap().is_some()); } #[test] fn test_tick_count() { let clock = VirtualClock::new(Duration::from_nanos(333)); assert_eq!(clock.tick_count(), 0); clock.advance_one_tick(); assert_eq!(clock.tick_count(), 1); clock.advance_one_tick(); assert_eq!(clock.tick_count(), 2); // advance_by(100ns) with tick_size=333ns → total 766ns / 333 = 2 (integer division) clock.advance_by(Duration::from_nanos(100)); assert_eq!(clock.tick_count(), 2); // 766 / 333 = 2 // One more full tick → total 1099ns / 333 = 3 clock.advance_one_tick(); assert_eq!(clock.tick_count(), 3); // 1099 / 333 = 3 } }