diff --git a/src/main.rs b/src/main.rs index 725ccea..eb61bed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,23 +4,23 @@ mod cpu; mod frontpanel; mod ram; mod window; +mod state; use std::{collections::HashMap, sync::mpsc::Sender}; use audio::{AudioMessage, AudioThread}; -use cpu::{MemCycle, Status, I8080}; use eframe::{ egui::{self, menu, Button, Slider, Ui}, NativeOptions, }; use egui_modal::Modal; use frontpanel::Textures; -use rand::RngCore; use rfd::FileDialog; use serde::{Deserialize, Serialize}; +use state::EmuState; use window::Window; -use crate::frontpanel::{switch::SwitchState, Frontpanel, FrontpanelInteraction, FrontpanelState}; +use crate::frontpanel::Frontpanel; fn main() -> Result<(), eframe::Error> { env_logger::init(); @@ -60,169 +60,6 @@ struct AltairEmulator { windows: HashMap<&'static str, Box>, } -struct EmuState { - mem: [u8; 65536], - cpu: I8080, - running: bool, - audio_tx: Sender, - options: Options, - fp_state: FrontpanelState, -} - -impl EmuState { - fn new(audio_tx: Sender, options: Options) -> Self { - let mut mem = [0; 65536]; - rand::thread_rng().fill_bytes(&mut mem); - let mut slf = EmuState { - mem, - cpu: I8080::new(), - running: false, - audio_tx, - options, - fp_state: FrontpanelState::new() - }; - slf.apply_options(); - slf - } - fn handle_fp_interaction(&mut self, interaction: FrontpanelInteraction) { - self.audio_tx.send(AudioMessage::PlaySwitchClick).unwrap(); - match interaction { - FrontpanelInteraction::ActionSwChanged(sw, state) => { - if self.fp_state.power() { - match sw { - frontpanel::ActionSwitch::RunStop => { - if state == SwitchState::Up { - self.running = false; - } else if state == SwitchState::Down { - self.running = true; - } - } - frontpanel::ActionSwitch::SingleStep => { - if state == SwitchState::Up { - self.run_cpu_cycle(); - self.update_fp(); - } - } - frontpanel::ActionSwitch::Examine => { - if state == SwitchState::Up { - // Assume M1 - self.cpu.finish_m_cycle(0xC3); // JMP - self.cpu.finish_m_cycle(self.fp_state.ad_sws() as u8); - self.cpu.finish_m_cycle((self.fp_state.ad_sws() >> 8) as u8); - self.update_fp(); - } else if state == SwitchState::Down { - // Assume M1 - self.cpu.finish_m_cycle(0x0); // NOP - self.update_fp(); - } - } - frontpanel::ActionSwitch::Deposit => { - if state == SwitchState::Up { - // Assume M1 - self.mem[self.cpu.get_mem_cycle().address() as usize] = - self.fp_state.ad_sws() as u8; - self.update_fp(); - } else if state == SwitchState::Down { - // Assume M1 - self.cpu.finish_m_cycle(0x0); // NOP - self.mem[self.cpu.get_mem_cycle().address() as usize] = - self.fp_state.ad_sws() as u8; - self.update_fp(); - } - } - frontpanel::ActionSwitch::Reset => { - if state == SwitchState::Up { - self.cpu.reset(); - self.update_fp(); - } - } - frontpanel::ActionSwitch::Protect => (), - frontpanel::ActionSwitch::Aux1 => (), - frontpanel::ActionSwitch::Aux2 => (), - } - } - } - FrontpanelInteraction::AdChanged(_) => (), - FrontpanelInteraction::PowerChanged(pwr) => { - if pwr { - self.audio_tx.send(AudioMessage::FanOn).unwrap(); - self.cpu = I8080::new(); - self.update_fp(); - } else { - self.audio_tx.send(AudioMessage::FanOff).unwrap(); - self.running = false; - self.fp_state.set_status(Status::empty()); - self.fp_state.set_addr(0); - self.fp_state.set_data(0); - } - } - } - } - - fn update_fp(&mut self) { - let cycle = self.cpu.get_mem_cycle(); - self.fp_state.set_status(cycle.get_status()); - match cycle { - MemCycle::Fetch(a) | MemCycle::Read(a) | MemCycle::StackRead(a) => { - self.fp_state.set_addr(a); - self.fp_state.set_data(self.mem[a as usize]); - } - MemCycle::Write(a, _) | MemCycle::StackWrite(a, _) | MemCycle::Out(a, _) => { - self.fp_state.set_addr(a); - self.fp_state.set_data(0xff); - } - MemCycle::In(a) => { - self.fp_state.set_addr(a); - self.fp_state.set_data(0); - } - MemCycle::Inta(_) => todo!(), - MemCycle::Hlta(_) => { - self.fp_state.set_addr(0xffff); - self.fp_state.set_data(0xff); - } - MemCycle::IntaHlt(_) => todo!(), - } - } - - fn run_cpu_cycle(&mut self) { - let cycle = self.cpu.get_mem_cycle(); - let data = match cycle { - MemCycle::Fetch(a) | MemCycle::Read(a) | MemCycle::StackRead(a) => self.mem[a as usize], - MemCycle::Write(a, d) | MemCycle::StackWrite(a, d) => { - self.mem[a as usize] = d; - 0 - } - MemCycle::In(_) => 0, - MemCycle::Out(_, _) => 0, - MemCycle::Inta(_) => todo!(), - MemCycle::Hlta(_) => { - self.running = false; - 0 - } - MemCycle::IntaHlt(_) => todo!(), - }; - self.cpu.finish_m_cycle(data); - } - - fn update_options(&mut self, new_opts: Options) { - self.options = new_opts; - self.apply_options(); - } - fn apply_options(&mut self) { - if self.options.mute { - self.audio_tx.send(AudioMessage::SetVolume(0.0)).unwrap(); - } else { - self.audio_tx - .send(AudioMessage::SetVolume(self.options.volume)) - .unwrap(); - } - if self.options.fan_enabled && self.fp_state.power() { - self.audio_tx.send(AudioMessage::FanOn).unwrap(); - } else { - self.audio_tx.send(AudioMessage::FanOff).unwrap(); - } - } -} impl AltairEmulator { fn new(cc: &eframe::CreationContext<'_>, audio_tx: Sender) -> Self { @@ -277,9 +114,7 @@ impl AltairEmulator { let record = record.unwrap(); match record { ihex::Record::Data { offset, value } => { - for (i, &byte) in value.iter().enumerate() { - self.state.mem[offset as usize + i] = byte; - } + self.state.write_binary(offset as usize, value); } ihex::Record::StartLinearAddress(_) => todo!(), ihex::Record::EndOfFile => (), @@ -298,7 +133,8 @@ impl AltairEmulator { } fn show_frontpanel(&mut self, ui: &mut Ui) { - let mut fp = Frontpanel::new(&self.textures, &mut self.state.fp_state); + let fp_state = self.state.fp_state_mut(); + let mut fp = Frontpanel::new(&self.textures, fp_state); ui.add(&mut fp); let interaction = fp.interaction(); if let Some(interaction) = interaction { @@ -309,19 +145,16 @@ impl AltairEmulator { impl eframe::App for AltairEmulator { fn save(&mut self, storage: &mut dyn eframe::Storage) { - eframe::set_value(storage, "options", &self.state.options); + eframe::set_value(storage, "options", &self.state.options()); } fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { ctx.set_pixels_per_point(1.0); egui::TopBottomPanel::top("menu").show(ctx, |ui| self.show_menubar(ui)); egui::CentralPanel::default().show(ctx, |ui| self.show_frontpanel(ui)); - if self.state.running { - self.state.run_cpu_cycle(); - self.state.update_fp(); - } self.windows .retain(|_, window| !window.draw(ctx, &mut self.state)); - if self.state.running { + self.state.update(); + if self.state.running() { ctx.request_repaint(); } } @@ -342,7 +175,7 @@ impl OptionWindow { fn new(ctx: &egui::Context, state: &EmuState) -> Self { Modal::new(ctx, "options_modal").open(); Self { - options: state.options.clone(), + options: state.options().clone(), category: OptionsCategory::General, } } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..a4d0aeb --- /dev/null +++ b/src/state.rs @@ -0,0 +1,196 @@ +use std::sync::mpsc::Sender; + +use rand::RngCore; + +use crate::{cpu::{I8080, Status, MemCycle}, audio::AudioMessage, Options, frontpanel::{FrontpanelState, FrontpanelInteraction, ActionSwitch, switch::SwitchState}}; + +pub struct EmuState { + mem: [u8; 65536], + cpu: I8080, + running: bool, + audio_tx: Sender, + options: Options, + fp_state: FrontpanelState, +} + +impl EmuState { + pub fn new(audio_tx: Sender, options: Options) -> Self { + let mut mem = [0; 65536]; + rand::thread_rng().fill_bytes(&mut mem); + let mut slf = EmuState { + mem, + cpu: I8080::new(), + running: false, + audio_tx, + options, + fp_state: FrontpanelState::new() + }; + slf.apply_options(); + slf + } + + pub fn handle_fp_interaction(&mut self, interaction: FrontpanelInteraction) { + self.audio_tx.send(AudioMessage::PlaySwitchClick).unwrap(); + match interaction { + FrontpanelInteraction::ActionSwChanged(sw, state) => { + if self.fp_state.power() { + match sw { + ActionSwitch::RunStop => { + if state == SwitchState::Up { + self.running = false; + } else if state == SwitchState::Down { + self.running = true; + } + } + ActionSwitch::SingleStep => { + if state == SwitchState::Up { + self.run_cpu_cycle(); + self.update_fp(); + } + } + ActionSwitch::Examine => { + if state == SwitchState::Up { + // Assume M1 + self.cpu.finish_m_cycle(0xC3); // JMP + self.cpu.finish_m_cycle(self.fp_state.ad_sws() as u8); + self.cpu.finish_m_cycle((self.fp_state.ad_sws() >> 8) as u8); + self.update_fp(); + } else if state == SwitchState::Down { + // Assume M1 + self.cpu.finish_m_cycle(0x0); // NOP + self.update_fp(); + } + } + ActionSwitch::Deposit => { + if state == SwitchState::Up { + // Assume M1 + self.mem[self.cpu.get_mem_cycle().address() as usize] = + self.fp_state.ad_sws() as u8; + self.update_fp(); + } else if state == SwitchState::Down { + // Assume M1 + self.cpu.finish_m_cycle(0x0); // NOP + self.mem[self.cpu.get_mem_cycle().address() as usize] = + self.fp_state.ad_sws() as u8; + self.update_fp(); + } + } + ActionSwitch::Reset => { + if state == SwitchState::Up { + self.cpu.reset(); + self.update_fp(); + } + } + ActionSwitch::Protect => (), + ActionSwitch::Aux1 => (), + ActionSwitch::Aux2 => (), + } + } + } + FrontpanelInteraction::AdChanged(_) => (), + FrontpanelInteraction::PowerChanged(pwr) => { + if pwr { + self.audio_tx.send(AudioMessage::FanOn).unwrap(); + self.cpu = I8080::new(); + self.update_fp(); + } else { + self.audio_tx.send(AudioMessage::FanOff).unwrap(); + self.running = false; + self.fp_state.set_status(Status::empty()); + self.fp_state.set_addr(0); + self.fp_state.set_data(0); + } + } + } + } + + pub fn update_fp(&mut self) { + let cycle = self.cpu.get_mem_cycle(); + self.fp_state.set_status(cycle.get_status()); + match cycle { + MemCycle::Fetch(a) | MemCycle::Read(a) | MemCycle::StackRead(a) => { + self.fp_state.set_addr(a); + self.fp_state.set_data(self.mem[a as usize]); + } + MemCycle::Write(a, _) | MemCycle::StackWrite(a, _) | MemCycle::Out(a, _) => { + self.fp_state.set_addr(a); + self.fp_state.set_data(0xff); + } + MemCycle::In(a) => { + self.fp_state.set_addr(a); + self.fp_state.set_data(0); + } + MemCycle::Inta(_) => todo!(), + MemCycle::Hlta(_) => { + self.fp_state.set_addr(0xffff); + self.fp_state.set_data(0xff); + } + MemCycle::IntaHlt(_) => todo!(), + } + } + + pub fn run_cpu_cycle(&mut self) { + let cycle = self.cpu.get_mem_cycle(); + let data = match cycle { + MemCycle::Fetch(a) | MemCycle::Read(a) | MemCycle::StackRead(a) => self.mem[a as usize], + MemCycle::Write(a, d) | MemCycle::StackWrite(a, d) => { + self.mem[a as usize] = d; + 0 + } + MemCycle::In(_) => 0, + MemCycle::Out(_, _) => 0, + MemCycle::Inta(_) => todo!(), + MemCycle::Hlta(_) => { + self.running = false; + 0 + } + MemCycle::IntaHlt(_) => todo!(), + }; + self.cpu.finish_m_cycle(data); + } + + pub fn write_binary(&mut self, start: usize, data: Vec) { + dbg!(&start, &data); + assert!(0x1_0000 - start >= data.len()); + self.mem[start..(start + data.len())].copy_from_slice(&data); + } + + pub fn update_options(&mut self, new_opts: Options) { + self.options = new_opts; + self.apply_options(); + } + + fn apply_options(&mut self) { + if self.options.mute { + self.audio_tx.send(AudioMessage::SetVolume(0.0)).unwrap(); + } else { + self.audio_tx + .send(AudioMessage::SetVolume(self.options.volume)) + .unwrap(); + } + if self.options.fan_enabled && self.fp_state.power() { + self.audio_tx.send(AudioMessage::FanOn).unwrap(); + } else { + self.audio_tx.send(AudioMessage::FanOff).unwrap(); + } + } + + pub fn update(&mut self) { + if self.running { + self.run_cpu_cycle(); + self.update_fp(); + } + } + + pub fn options(&self) -> Options { + self.options + } + + pub fn fp_state_mut(&mut self) -> &mut FrontpanelState { + &mut self.fp_state + } + + pub fn running(&self) -> bool { + self.running + } +}