rusty_emu/src/gui/state.rs
Jan-Peter von Hunnius 08f53d68fe 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.
2026-05-14 21:00:22 +02:00

404 lines
14 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! # 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,
}
}
}