Split EmuState to separate file
This commit is contained in:
parent
b14d3e0ce4
commit
3d4c6b038c
187
src/main.rs
187
src/main.rs
@ -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
196
src/state.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user