diff --git a/src/main.rs b/src/main.rs index fb023e2..8b20cda 100644 --- a/src/main.rs +++ b/src/main.rs @@ -54,15 +54,161 @@ impl Options { struct AltairEmulator { textures: Textures, + option_window: Option, + state: EmuState, +} + +struct EmuState { mem: [u8; 65536], cpu: I8080, running: bool, audio_tx: Sender, options: Options, - option_window: Option, fp_state: FrontpanelState, } +impl EmuState { + 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; + 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 { let options = if cc.storage.unwrap().get_string("options").is_none() { @@ -114,61 +260,18 @@ impl AltairEmulator { // 21: 005 Self { textures: Textures::new(&cc.egui_ctx), - mem, - cpu, - running: false, - audio_tx, - options, option_window: None, - fp_state: FrontpanelState::new(), + state: EmuState { + mem, + cpu, + running: false, + audio_tx, + options, + fp_state: FrontpanelState::new(), + }, } } - 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 show_menubar(&mut self, ui: &mut Ui) { menu::bar(ui, |ui| { menu::menu_button(ui, "Edit", |ui| { @@ -176,7 +279,7 @@ impl AltairEmulator { .add_enabled(self.option_window.is_none(), Button::new("Options")) .clicked() { - self.option_window = Some(OptionWindow::new(ui.ctx(), self.options)); + self.option_window = Some(OptionWindow::new(ui.ctx(), &self.state)); ui.close_menu(); } if ui.button("Load binary file").clicked() { @@ -199,7 +302,7 @@ impl AltairEmulator { match record { ihex::Record::Data { offset, value } => { for (i, &byte) in value.iter().enumerate() { - self.mem[offset as usize + i] = byte; + self.state.mem[offset as usize + i] = byte; } } ihex::Record::StartLinearAddress(_) => todo!(), @@ -217,118 +320,33 @@ impl AltairEmulator { } fn show_frontpanel(&mut self, ui: &mut Ui) { - let mut fp = Frontpanel::new(&self.textures, &mut self.fp_state); + let mut fp = Frontpanel::new(&self.textures, &mut self.state.fp_state); ui.add(&mut fp); let interaction = fp.interaction(); if let Some(interaction) = interaction { - 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); - } - } - } + self.state.handle_fp_interaction(interaction); } } } impl eframe::App for AltairEmulator { fn save(&mut self, storage: &mut dyn eframe::Storage) { - eframe::set_value(storage, "options", &self.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.running { - self.run_cpu_cycle(); - self.update_fp(); - + if self.state.running { + self.state.run_cpu_cycle(); + self.state.update_fp(); } if let Some(option_window) = self.option_window.as_mut() { - if option_window.draw(ctx, &mut self.options) { + if option_window.draw(ctx, &mut self.state) { self.option_window = None; } - 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(); - } } - - if self.running { + if self.state.running { ctx.request_repaint(); } } @@ -346,14 +364,14 @@ enum OptionsCategory { } impl OptionWindow { - fn new(ctx: &egui::Context, options: Options) -> Self { + fn new(ctx: &egui::Context, state: &EmuState) -> Self { Modal::new(ctx, "options_modal").open(); Self { - options, + options: state.options.clone(), category: OptionsCategory::General, } } - fn draw(&mut self, ctx: &egui::Context, options: &mut Options) -> bool { + fn draw(&mut self, ctx: &egui::Context, state: &mut EmuState) -> bool { let modal = Modal::new(ctx, "options_modal"); modal.show(|ui| { modal.title(ui, "Options"); @@ -373,10 +391,10 @@ impl OptionWindow { } modal.buttons(ui, |ui| { if ui.button("Apply").clicked() { - *options = self.options; + state.update_options(self.options); } if modal.button(ui, "OK").clicked() { - *options = self.options; + state.update_options(self.options); } modal.caution_button(ui, "Cancel"); });