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
|
|
@ -49,6 +49,7 @@
|
|||
|
||||
use super::state::GuiState;
|
||||
use crate::system_config::{ConfigurableSystem, SystemController};
|
||||
use crate::virtual_clock::SimulationMode;
|
||||
use eframe::egui;
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -75,6 +76,8 @@ use std::sync::Arc;
|
|||
pub struct GuiComponents {
|
||||
/// System control and management interface
|
||||
control_panel: ControlPanel,
|
||||
/// Timing/speed/mode/step controls
|
||||
timing_controls: TimingControls,
|
||||
/// RAM content inspection and visualization
|
||||
memory_viewer: MemoryViewer,
|
||||
/// ROM content inspection and visualization
|
||||
|
|
@ -107,6 +110,7 @@ impl GuiComponents {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
control_panel: ControlPanel::new(),
|
||||
timing_controls: TimingControls::new(),
|
||||
memory_viewer: MemoryViewer::new(),
|
||||
rom_viewer: RomViewer::new(),
|
||||
register_viewer: RegisterViewer::new(),
|
||||
|
|
@ -133,6 +137,23 @@ impl GuiComponents {
|
|||
self.control_panel.render(ui, state, system);
|
||||
}
|
||||
|
||||
/// Render the timing controls component
|
||||
///
|
||||
/// Displays simulation speed, mode, step controls, and virtual time.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `ui` - egui UI context for rendering
|
||||
/// * `state` - Mutable reference to GUI state for display and interaction
|
||||
/// * `system` - Optional reference to the emulator system for clock control
|
||||
pub fn render_timing_controls(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
state: &mut GuiState,
|
||||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||||
) {
|
||||
self.timing_controls.render(ui, state, system);
|
||||
}
|
||||
|
||||
/// Render the memory viewer component
|
||||
///
|
||||
/// Displays RAM contents in a tabular format with bank selection
|
||||
|
|
@ -398,6 +419,191 @@ impl ControlPanel {
|
|||
}
|
||||
}
|
||||
|
||||
/// Timing controls component for simulation speed, mode, and step management.
|
||||
///
|
||||
/// This component provides controls for the virtual clock system:
|
||||
/// - **Speed slider**: Logarithmic scale from 0.001× to 100×
|
||||
/// - **Mode selector**: RealTime / MaxSpeed / SingleStep radio buttons
|
||||
/// - **Step button**: Advance one tick in SingleStep mode
|
||||
/// - **Virtual time display**: Current time, tick count, actual vs target speed
|
||||
///
|
||||
/// ## Logarithmic Slider Mapping
|
||||
///
|
||||
/// The slider position `t` (0.0 to 1.0) maps to speed via:
|
||||
/// `speed = 10^(t * 5 - 3)` giving range [0.001, 100.0]
|
||||
pub struct TimingControls {
|
||||
/// Current slider position (0.0 to 1.0), mapped logarithmically to speed
|
||||
speed_slider_pos: f32,
|
||||
}
|
||||
|
||||
impl TimingControls {
|
||||
/// Create a new timing controls component
|
||||
pub fn new() -> Self {
|
||||
// Default: 1.0x speed → log10(1.0) = 0, so t = (0 + 3) / 5 = 0.6
|
||||
Self {
|
||||
speed_slider_pos: 0.6,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert slider position (0..1) to speed multiplier using logarithmic scale.
|
||||
///
|
||||
/// Range: 0.001× (position 0.0) to 100.0× (position 1.0)
|
||||
/// Formula: speed = 10^(pos * 5 - 3)
|
||||
fn pos_to_speed(pos: f32) -> f64 {
|
||||
let pos = pos.clamp(0.0, 1.0) as f64;
|
||||
10f64.powf(pos * 5.0 - 3.0)
|
||||
}
|
||||
|
||||
/// Convert speed multiplier to slider position (0..1) using logarithmic scale.
|
||||
///
|
||||
/// Formula: pos = (log10(speed) + 3) / 5
|
||||
fn speed_to_pos(speed: f64) -> f32 {
|
||||
if speed <= 0.0 {
|
||||
return 0.0;
|
||||
}
|
||||
let pos = (speed.log10() + 3.0) / 5.0;
|
||||
pos.clamp(0.0, 1.0) as f32
|
||||
}
|
||||
|
||||
/// Render the timing controls interface.
|
||||
///
|
||||
/// # Layout
|
||||
/// ```text
|
||||
/// ┌──────────────────────────────────────────────────────────────────┐
|
||||
/// │ Timing Controls │
|
||||
/// │ Speed: [========●===========] 1.00× (actual: 0.98×) │
|
||||
/// │ Mode: (●) RealTime ( ) MaxSpeed ( ) SingleStep [Step] │
|
||||
/// │ Virtual Time: 1.23 ms | Ticks: 3,690 | Paused: No │
|
||||
/// └──────────────────────────────────────────────────────────────────┘
|
||||
/// ```
|
||||
pub fn render(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
state: &mut GuiState,
|
||||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||||
) {
|
||||
// Sync slider position from actual clock speed so external changes
|
||||
// (e.g., console "speed 0.1" command) are reflected in the GUI.
|
||||
self.speed_slider_pos = Self::speed_to_pos(state.timing_state.target_speed);
|
||||
|
||||
ui.vertical(|ui| {
|
||||
ui.heading("Timing Controls");
|
||||
|
||||
// Speed slider row
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Speed:");
|
||||
let slider = egui::Slider::new(&mut self.speed_slider_pos, 0.0..=1.0)
|
||||
.show_value(false)
|
||||
.step_by(0.001);
|
||||
let response = ui.add(slider);
|
||||
|
||||
if response.changed() {
|
||||
let new_speed = Self::pos_to_speed(self.speed_slider_pos);
|
||||
if let Some(sys) = system {
|
||||
let sys_guard = sys.lock();
|
||||
let clock = sys_guard.get_virtual_clock();
|
||||
clock.set_speed_multiplier(new_speed);
|
||||
// If in MaxSpeed mode, switch back to RealTime when slider moves
|
||||
if clock.mode() == SimulationMode::MaxSpeed {
|
||||
clock.set_mode(SimulationMode::RealTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show current target speed
|
||||
let speed_text = state.timing_state.format_speed();
|
||||
ui.label(format!("Target: {}", speed_text));
|
||||
|
||||
// Show actual speed
|
||||
let actual_text = state.timing_state.format_actual_speed();
|
||||
ui.label(format!("Actual: {}", actual_text));
|
||||
});
|
||||
|
||||
// Mode selector and step button row
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Mode:");
|
||||
|
||||
let current_mode = state.timing_state.mode.as_str();
|
||||
let is_realtime = current_mode == "RealTime";
|
||||
let is_maxspeed = current_mode == "MaxSpeed";
|
||||
let is_singlestep = current_mode == "SingleStep";
|
||||
|
||||
// RealTime radio
|
||||
if ui.radio(is_realtime, "RealTime").clicked() {
|
||||
self.apply_mode(system, SimulationMode::RealTime);
|
||||
}
|
||||
// MaxSpeed radio
|
||||
if ui.radio(is_maxspeed, "MaxSpeed").clicked() {
|
||||
self.apply_mode(system, SimulationMode::MaxSpeed);
|
||||
}
|
||||
// SingleStep radio
|
||||
if ui.radio(is_singlestep, "SingleStep").clicked() {
|
||||
self.apply_mode(system, SimulationMode::SingleStep);
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Step button (enabled only in SingleStep mode)
|
||||
let step_pending = state.timing_state.step_pending;
|
||||
|
||||
let step_button = ui.add_enabled(
|
||||
is_singlestep && !step_pending,
|
||||
egui::Button::new("Step"),
|
||||
);
|
||||
|
||||
if step_button.clicked() {
|
||||
if let Some(sys) = system {
|
||||
let sys_guard = sys.lock();
|
||||
let clock = sys_guard.get_virtual_clock();
|
||||
clock.trigger_step();
|
||||
}
|
||||
}
|
||||
|
||||
if is_singlestep && step_pending {
|
||||
ui.colored_label(egui::Color32::YELLOW, "⏳ Step pending...");
|
||||
}
|
||||
});
|
||||
|
||||
// Virtual time display row
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Virtual Time:");
|
||||
ui.strong(state.timing_state.format_virtual_time());
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Ticks:");
|
||||
ui.strong(format!("{}", state.timing_state.tick_count));
|
||||
|
||||
ui.separator();
|
||||
|
||||
// Paused indicator
|
||||
if state.timing_state.paused {
|
||||
ui.colored_label(egui::Color32::YELLOW, "⏸ Paused");
|
||||
} else if state.system_running {
|
||||
ui.colored_label(egui::Color32::GREEN, "▶ Running");
|
||||
} else {
|
||||
ui.colored_label(egui::Color32::RED, "⏹ Stopped");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
}
|
||||
|
||||
/// Apply a simulation mode change to the virtual clock.
|
||||
fn apply_mode(
|
||||
&mut self,
|
||||
system: Option<&Arc<Mutex<ConfigurableSystem>>>,
|
||||
mode: SimulationMode,
|
||||
) {
|
||||
if let Some(sys) = system {
|
||||
let sys_guard = sys.lock();
|
||||
let clock = sys_guard.get_virtual_clock();
|
||||
clock.set_mode(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory viewer component for displaying RAM contents and state
|
||||
///
|
||||
/// The memory viewer provides comprehensive RAM inspection capabilities,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue