Split EmuState to separate file

This commit is contained in:
pjht 2024-01-31 10:40:42 -06:00
parent b14d3e0ce4
commit 3d4c6b038c
Signed by: pjht
GPG Key ID: CA239FC6934E6F3A
2 changed files with 206 additions and 177 deletions

View File

@ -4,23 +4,23 @@ mod cpu;
mod frontpanel; mod frontpanel;
mod ram; mod ram;
mod window; mod window;
mod state;
use std::{collections::HashMap, sync::mpsc::Sender}; use std::{collections::HashMap, sync::mpsc::Sender};
use audio::{AudioMessage, AudioThread}; use audio::{AudioMessage, AudioThread};
use cpu::{MemCycle, Status, I8080};
use eframe::{ use eframe::{
egui::{self, menu, Button, Slider, Ui}, egui::{self, menu, Button, Slider, Ui},
NativeOptions, NativeOptions,
}; };
use egui_modal::Modal; use egui_modal::Modal;
use frontpanel::Textures; use frontpanel::Textures;
use rand::RngCore;
use rfd::FileDialog; use rfd::FileDialog;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use state::EmuState;
use window::Window; use window::Window;
use crate::frontpanel::{switch::SwitchState, Frontpanel, FrontpanelInteraction, FrontpanelState}; use crate::frontpanel::Frontpanel;
fn main() -> Result<(), eframe::Error> { fn main() -> Result<(), eframe::Error> {
env_logger::init(); env_logger::init();
@ -60,169 +60,6 @@ struct AltairEmulator {
windows: HashMap<&'static str, Box<dyn Window>>, windows: HashMap<&'static str, Box<dyn Window>>,
} }
struct EmuState {
mem: [u8; 65536],
cpu: I8080,
running: bool,
audio_tx: Sender<AudioMessage>,
options: Options,
fp_state: FrontpanelState,
}
impl EmuState {
fn new(audio_tx: Sender<AudioMessage>, 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 { impl AltairEmulator {
fn new(cc: &eframe::CreationContext<'_>, audio_tx: Sender<AudioMessage>) -> Self { fn new(cc: &eframe::CreationContext<'_>, audio_tx: Sender<AudioMessage>) -> Self {
@ -277,9 +114,7 @@ impl AltairEmulator {
let record = record.unwrap(); let record = record.unwrap();
match record { match record {
ihex::Record::Data { offset, value } => { ihex::Record::Data { offset, value } => {
for (i, &byte) in value.iter().enumerate() { self.state.write_binary(offset as usize, value);
self.state.mem[offset as usize + i] = byte;
}
} }
ihex::Record::StartLinearAddress(_) => todo!(), ihex::Record::StartLinearAddress(_) => todo!(),
ihex::Record::EndOfFile => (), ihex::Record::EndOfFile => (),
@ -298,7 +133,8 @@ impl AltairEmulator {
} }
fn show_frontpanel(&mut self, ui: &mut Ui) { 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); ui.add(&mut fp);
let interaction = fp.interaction(); let interaction = fp.interaction();
if let Some(interaction) = interaction { if let Some(interaction) = interaction {
@ -309,19 +145,16 @@ impl AltairEmulator {
impl eframe::App for AltairEmulator { impl eframe::App for AltairEmulator {
fn save(&mut self, storage: &mut dyn eframe::Storage) { 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) { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
ctx.set_pixels_per_point(1.0); ctx.set_pixels_per_point(1.0);
egui::TopBottomPanel::top("menu").show(ctx, |ui| self.show_menubar(ui)); egui::TopBottomPanel::top("menu").show(ctx, |ui| self.show_menubar(ui));
egui::CentralPanel::default().show(ctx, |ui| self.show_frontpanel(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 self.windows
.retain(|_, window| !window.draw(ctx, &mut self.state)); .retain(|_, window| !window.draw(ctx, &mut self.state));
if self.state.running { self.state.update();
if self.state.running() {
ctx.request_repaint(); ctx.request_repaint();
} }
} }
@ -342,7 +175,7 @@ impl OptionWindow {
fn new(ctx: &egui::Context, state: &EmuState) -> Self { fn new(ctx: &egui::Context, state: &EmuState) -> Self {
Modal::new(ctx, "options_modal").open(); Modal::new(ctx, "options_modal").open();
Self { Self {
options: state.options.clone(), options: state.options().clone(),
category: OptionsCategory::General, category: OptionsCategory::General,
} }
} }

196
src/state.rs Normal file
View File

@ -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<AudioMessage>,
options: Options,
fp_state: FrontpanelState,
}
impl EmuState {
pub fn new(audio_tx: Sender<AudioMessage>, 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<u8>) {
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
}
}