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.
404 lines
14 KiB
Rust
404 lines
14 KiB
Rust
//! # GUI State Management
|
||
//!
|
||
//! This module manages the state of the GUI application, including system status,
|
||
//! component states, and user interface state. It provides thread-safe access
|
||
//! 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)]
|
||
pub struct GuiState {
|
||
/// Whether a system is currently loaded
|
||
pub system_loaded: bool,
|
||
/// Whether the system is currently running
|
||
pub system_running: bool,
|
||
/// Current cycle count
|
||
pub cycle_count: u64,
|
||
/// System information
|
||
pub system_info: Option<SystemInfo>,
|
||
/// Component states
|
||
pub component_states: ComponentStates,
|
||
/// Memory state
|
||
pub memory_state: MemoryState,
|
||
/// ROM state
|
||
pub rom_state: RomState,
|
||
/// CPU register state
|
||
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
|
||
#[derive(Debug, Clone)]
|
||
pub struct SystemInfo {
|
||
pub name: String,
|
||
pub description: String,
|
||
pub component_count: usize,
|
||
pub cpu_speed: f64,
|
||
pub rom_size: usize,
|
||
pub ram_size: usize,
|
||
}
|
||
|
||
/// Component states for monitoring
|
||
#[derive(Debug, Clone)]
|
||
pub struct ComponentStates {
|
||
pub cpu_running: bool,
|
||
pub ram_running: bool,
|
||
pub rom_running: bool,
|
||
pub clock_running: bool,
|
||
}
|
||
|
||
/// Memory state for display
|
||
#[derive(Debug, Clone)]
|
||
pub struct MemoryState {
|
||
pub ram_contents: Vec<[u8; 4]>, // 4 banks of RAM data
|
||
pub selected_bank: usize,
|
||
pub selected_address: usize,
|
||
/// Display mode: true for hexadecimal, false for decimal
|
||
pub show_hex: bool,
|
||
}
|
||
|
||
/// ROM state for display
|
||
#[derive(Debug, Clone)]
|
||
pub struct RomState {
|
||
pub rom_contents: Vec<u8>,
|
||
pub selected_address: usize,
|
||
pub chip_name: String,
|
||
/// Display mode: true for hexadecimal, false for decimal
|
||
pub show_hex: bool,
|
||
}
|
||
|
||
/// CPU register state for display
|
||
#[derive(Debug, Clone)]
|
||
pub struct RegisterState {
|
||
pub accumulator: u8,
|
||
pub carry_flag: bool,
|
||
pub program_counter: u16,
|
||
pub index_registers: [u8; 16], // 16 index registers (R0-R15) for Intel 4004
|
||
pub stack_pointer: u8,
|
||
/// Currently selected index register for detailed view (0-15)
|
||
pub selected_register: usize,
|
||
}
|
||
|
||
impl GuiState {
|
||
/// Create a new GUI state instance
|
||
pub fn new() -> Self {
|
||
Self {
|
||
system_loaded: false,
|
||
system_running: false,
|
||
cycle_count: 0,
|
||
system_info: None,
|
||
component_states: ComponentStates {
|
||
cpu_running: false,
|
||
ram_running: false,
|
||
rom_running: false,
|
||
clock_running: false,
|
||
},
|
||
memory_state: MemoryState {
|
||
ram_contents: vec![[0; 4]; 4], // Initialize 4 banks with 4 bytes each
|
||
selected_bank: 0,
|
||
selected_address: 0,
|
||
show_hex: true,
|
||
},
|
||
rom_state: RomState {
|
||
rom_contents: vec![0; 256], // Initialize with 256 bytes
|
||
selected_address: 0,
|
||
chip_name: String::new(),
|
||
show_hex: true,
|
||
},
|
||
register_state: RegisterState {
|
||
accumulator: 0,
|
||
carry_flag: false,
|
||
program_counter: 0,
|
||
index_registers: [0; 16],
|
||
stack_pointer: 0,
|
||
selected_register: 0,
|
||
},
|
||
last_error: None,
|
||
timing_state: SimulationTimingState::default(),
|
||
speed_snap_prev: None,
|
||
}
|
||
}
|
||
|
||
/// Update state from the current system
|
||
pub fn update_from_system(&mut self, system: &Arc<Mutex<ConfigurableSystem>>) {
|
||
let system_guard = system.lock();
|
||
// Update basic system state
|
||
self.system_running = system_guard.is_running();
|
||
|
||
// Update system info if not already set
|
||
if self.system_info.is_none() {
|
||
self.system_info = Some(system_guard.get_system_info().into());
|
||
}
|
||
|
||
// Update component states
|
||
self.update_component_states(&system_guard);
|
||
|
||
// Update CPU register state from real data
|
||
self.update_register_state(&system_guard);
|
||
|
||
// Update RAM state from real data
|
||
self.update_ram_state(&system_guard);
|
||
|
||
// 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;
|
||
}
|
||
}
|
||
|
||
/// Update component running states
|
||
/// Uses blocking locks for consistent GUI data - GUI can wait for simulation
|
||
fn update_component_states(&mut self, system: &ConfigurableSystem) {
|
||
let components = system.get_components();
|
||
|
||
// Use blocking locks for consistent data - GUI can wait for simulation
|
||
self.component_states.cpu_running = components
|
||
.get("CPU_4004")
|
||
.map_or(false, |comp| comp.lock().is_running());
|
||
|
||
self.component_states.ram_running = components
|
||
.get("RAM_4002")
|
||
.map_or(false, |comp| comp.lock().is_running());
|
||
|
||
self.component_states.rom_running = components
|
||
.get("ROM_4001_1")
|
||
.or_else(|| components.get("ROM_4001_2"))
|
||
.map_or(false, |comp| comp.lock().is_running());
|
||
|
||
self.component_states.clock_running = components
|
||
.get("SYSTEM_CLOCK")
|
||
.map_or(false, |comp| comp.lock().is_running());
|
||
}
|
||
|
||
/// Update CPU register state from real component data
|
||
fn update_register_state(&mut self, system: &ConfigurableSystem) {
|
||
if let Some(cpu_data) = system.get_cpu_register_data() {
|
||
self.register_state.accumulator = cpu_data.accumulator;
|
||
self.register_state.carry_flag = cpu_data.carry;
|
||
self.register_state.program_counter = cpu_data.program_counter;
|
||
self.register_state.stack_pointer = cpu_data.stack_pointer;
|
||
self.register_state
|
||
.index_registers
|
||
.copy_from_slice(&cpu_data.index_registers);
|
||
}
|
||
}
|
||
|
||
/// Update RAM state from real component data
|
||
fn update_ram_state(&mut self, system: &ConfigurableSystem) {
|
||
let ram_data = system.get_ram_data(4); // Get up to 4 rows per bank
|
||
|
||
if !ram_data.is_empty() {
|
||
// Take the first RAM chip's data
|
||
let (_, banks) = &ram_data[0];
|
||
|
||
// Convert bank data to our display format
|
||
self.memory_state.ram_contents.clear();
|
||
for bank in banks {
|
||
let mut row = [0u8; 4];
|
||
for (i, nibble) in bank.iter().take(4).enumerate() {
|
||
row[i] = *nibble;
|
||
}
|
||
self.memory_state.ram_contents.push(row);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Update ROM state from real component data
|
||
fn update_rom_state(&mut self, system: &ConfigurableSystem) {
|
||
let rom_data = system.get_rom_data(256); // Get up to 256 bytes
|
||
|
||
if !rom_data.is_empty() {
|
||
let (chip_name, data) = &rom_data[0];
|
||
self.rom_state.chip_name = chip_name.clone();
|
||
self.rom_state.rom_contents = data.clone();
|
||
}
|
||
}
|
||
|
||
/// 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);
|
||
}
|
||
|
||
/// Clear the error message
|
||
pub fn clear_error(&mut self) {
|
||
self.last_error = None;
|
||
}
|
||
|
||
/// Get the current error message
|
||
pub fn get_error(&self) -> Option<&str> {
|
||
self.last_error.as_deref()
|
||
}
|
||
}
|
||
|
||
impl From<crate::system_config::SystemInfo> for SystemInfo {
|
||
fn from(info: crate::system_config::SystemInfo) -> Self {
|
||
Self {
|
||
name: info.name,
|
||
description: info.description,
|
||
component_count: info.component_count,
|
||
cpu_speed: info.cpu_speed,
|
||
rom_size: info.rom_size,
|
||
ram_size: info.ram_size,
|
||
}
|
||
}
|
||
}
|