feat: implement virtual clock timing architecture (Phases 5-8)
Introduces a VirtualClock-based timing system replacing wall-clock Instant-based timing for hardware-accurate simulation. New modules: - virtual_clock.rs: Shared virtual time source with condvar sync - real_time_governor.rs: Thread that advances virtual time - component_timing.rs: Per-component timing constraints Key changes: - Component trait: added update_at_time(), timing(), set_virtual_clock_optional() - All Intel 400x components migrated to virtual-time-aware operation - ConfigurableSystem: start() returns Result, spawns governor thread - GUI: timing controls (speed slider, mode selector, step button) - Console: speed/mode/step/time commands - Backward compatible: legacy fallback for components without VirtualClock Reviewed-by: code review found 2 warnings (clock resume() dead code, reset() safety docs) — both fixed.
This commit is contained in:
parent
add7ace674
commit
08f53d68fe
28 changed files with 7186 additions and 270 deletions
156
src/gui/state.rs
156
src/gui/state.rs
|
|
@ -5,8 +5,11 @@
|
|||
//! to emulator state for real-time GUI updates.
|
||||
|
||||
use crate::system_config::ConfigurableSystem;
|
||||
use crate::real_time_governor::RealTimeGovernor;
|
||||
use crate::virtual_clock::SimulationMode;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
/// GUI state structure containing all UI-relevant data
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -29,6 +32,104 @@ pub struct GuiState {
|
|||
pub register_state: RegisterState,
|
||||
/// Last error message
|
||||
pub last_error: Option<String>,
|
||||
/// Timing/speed control state
|
||||
pub timing_state: SimulationTimingState,
|
||||
/// Speed snapshot for measuring actual simulation speed.
|
||||
/// Uses RealTimeGovernor::SpeedSnapshot which wraps (Instant, u64) properly.
|
||||
speed_snap_prev: Option<crate::real_time_governor::SpeedSnapshot>,
|
||||
}
|
||||
|
||||
/// Timing and speed control state for the simulation.
|
||||
///
|
||||
/// Named `SimulationTimingState` to avoid confusion with the hardware
|
||||
/// [`TimingState`](crate::components::common::intel_400x::TimingState) enum
|
||||
/// that represents component-level timing states (e.g., WaitLatency, DriveData).
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimulationTimingState {
|
||||
/// Current virtual time in nanoseconds
|
||||
pub virtual_time_ns: u64,
|
||||
/// Total ticks elapsed
|
||||
pub tick_count: u64,
|
||||
/// Current target speed multiplier (from VirtualClock)
|
||||
pub target_speed: f64,
|
||||
/// Actual measured speed ratio (from RealTimeGovernor snapshots)
|
||||
/// 1.0 = real-time, 0.5 = half speed, 2.0 = double speed
|
||||
pub actual_speed: f64,
|
||||
/// Current simulation mode as string: "RealTime", "MaxSpeed", "SingleStep"
|
||||
pub mode: String,
|
||||
/// Whether a step is pending (SingleStep mode)
|
||||
pub step_pending: bool,
|
||||
/// Whether the simulation is paused
|
||||
pub paused: bool,
|
||||
}
|
||||
|
||||
impl Default for SimulationTimingState {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
virtual_time_ns: 0,
|
||||
tick_count: 0,
|
||||
target_speed: 1.0,
|
||||
actual_speed: 0.0,
|
||||
mode: "RealTime".to_string(),
|
||||
step_pending: false,
|
||||
paused: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Format a virtual time value in nanoseconds as a human-readable string.
|
||||
///
|
||||
/// Automatically selects the most appropriate unit (ns, µs, ms, s).
|
||||
/// This is the single source of truth for virtual time formatting,
|
||||
/// used by both the GUI and console interfaces.
|
||||
pub fn format_virtual_time_ns(ns: u64) -> String {
|
||||
if ns == 0 {
|
||||
return "0 ns".to_string();
|
||||
}
|
||||
if ns < 1_000 {
|
||||
format!("{} ns", ns)
|
||||
} else if ns < 1_000_000 {
|
||||
format!("{:.2} µs", ns as f64 / 1_000.0)
|
||||
} else if ns < 1_000_000_000 {
|
||||
format!("{:.2} ms", ns as f64 / 1_000_000.0)
|
||||
} else {
|
||||
format!("{:.3} s", ns as f64 / 1_000_000_000.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl SimulationTimingState {
|
||||
/// Format virtual time as a human-readable string.
|
||||
///
|
||||
/// Automatically selects the most appropriate unit (ns, µs, ms, s).
|
||||
/// Delegates to [`format_virtual_time_ns`] for the actual formatting.
|
||||
pub fn format_virtual_time(&self) -> String {
|
||||
format_virtual_time_ns(self.virtual_time_ns)
|
||||
}
|
||||
|
||||
/// Format the speed multiplier for display.
|
||||
pub fn format_speed(&self) -> String {
|
||||
if self.target_speed >= 100.0 {
|
||||
format!("{:.0}×", self.target_speed)
|
||||
} else if self.target_speed >= 1.0 {
|
||||
format!("{:.1}×", self.target_speed)
|
||||
} else {
|
||||
format!("{:.3}×", self.target_speed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Format the actual measured speed for display.
|
||||
pub fn format_actual_speed(&self) -> String {
|
||||
let speed = self.actual_speed;
|
||||
if speed <= 0.0 {
|
||||
"—".to_string()
|
||||
} else if speed >= 100.0 {
|
||||
format!("{:.0}×", speed)
|
||||
} else if speed >= 1.0 {
|
||||
format!("{:.1}×", speed)
|
||||
} else {
|
||||
format!("{:.3}×", speed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// System information for display
|
||||
|
|
@ -118,6 +219,8 @@ impl GuiState {
|
|||
selected_register: 0,
|
||||
},
|
||||
last_error: None,
|
||||
timing_state: SimulationTimingState::default(),
|
||||
speed_snap_prev: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -144,6 +247,9 @@ impl GuiState {
|
|||
// Update ROM state from real data
|
||||
self.update_rom_state(&system_guard);
|
||||
|
||||
// Update timing state from virtual clock
|
||||
self.update_timing_state(&system_guard);
|
||||
|
||||
// Update cycle count (simulate for now)
|
||||
if self.system_running {
|
||||
self.cycle_count += 1;
|
||||
|
|
@ -218,6 +324,56 @@ impl GuiState {
|
|||
}
|
||||
}
|
||||
|
||||
/// Update timing state from the virtual clock
|
||||
fn update_timing_state(&mut self, system: &ConfigurableSystem) {
|
||||
let clock = system.get_virtual_clock();
|
||||
|
||||
// Read current values from the virtual clock
|
||||
self.timing_state.virtual_time_ns = clock.now_ns();
|
||||
self.timing_state.tick_count = clock.tick_count();
|
||||
self.timing_state.target_speed = clock.speed_multiplier();
|
||||
self.timing_state.step_pending = clock.is_step_pending();
|
||||
self.timing_state.paused = clock.is_paused();
|
||||
|
||||
let mode = clock.mode();
|
||||
self.timing_state.mode = match mode {
|
||||
SimulationMode::RealTime => "RealTime".to_string(),
|
||||
SimulationMode::MaxSpeed => "MaxSpeed".to_string(),
|
||||
SimulationMode::SingleStep => "SingleStep".to_string(),
|
||||
};
|
||||
|
||||
// Compute actual speed using RealTimeGovernor's snapshot infrastructure.
|
||||
// Shift current → previous, take new measurement, then compute delta.
|
||||
// This avoids the precision loss from accumulating nanos in a single u128
|
||||
// and delegates the calculation to the well-tested compute_speed() method.
|
||||
//
|
||||
// We require at least 100ms of wall-clock time between snapshots to produce
|
||||
// a stable measurement. At 60 FPS, consecutive frames are ~16ms apart, which
|
||||
// is too short for accurate speed calculation — especially at low simulation
|
||||
// speeds where virtual time barely advances between frames.
|
||||
const MIN_SPEED_SAMPLE_INTERVAL: Duration = Duration::from_millis(100);
|
||||
|
||||
let new_snap = RealTimeGovernor::take_speed_snapshot(clock);
|
||||
let should_compute = self.speed_snap_prev.as_ref().map_or(false, |prev| {
|
||||
new_snap.wall.duration_since(prev.wall) >= MIN_SPEED_SAMPLE_INTERVAL
|
||||
});
|
||||
|
||||
if should_compute {
|
||||
if let Some(prev) = &self.speed_snap_prev {
|
||||
let speed = RealTimeGovernor::compute_speed(prev, &new_snap);
|
||||
if speed > 0.0 {
|
||||
self.timing_state.actual_speed = speed;
|
||||
}
|
||||
}
|
||||
// Only shift snapshots when we actually computed a new speed value,
|
||||
// so the next computation uses a baseline at least 100ms old.
|
||||
self.speed_snap_prev = Some(new_snap);
|
||||
} else if self.speed_snap_prev.is_none() {
|
||||
// First frame — initialize the baseline snapshot
|
||||
self.speed_snap_prev = Some(new_snap);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set an error message
|
||||
pub fn set_error(&mut self, error: String) {
|
||||
self.last_error = Some(error);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue