Extract emulator state into separate struct
This commit is contained in:
parent
e5401824c3
commit
2bd3b4e489
322
src/main.rs
322
src/main.rs
@ -54,15 +54,161 @@ impl Options {
|
||||
|
||||
struct AltairEmulator {
|
||||
textures: Textures,
|
||||
option_window: Option<OptionWindow>,
|
||||
state: EmuState,
|
||||
}
|
||||
|
||||
struct EmuState {
|
||||
mem: [u8; 65536],
|
||||
cpu: I8080,
|
||||
running: bool,
|
||||
audio_tx: Sender<AudioMessage>,
|
||||
options: Options,
|
||||
option_window: Option<OptionWindow>,
|
||||
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<AudioMessage>) -> 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");
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user