Initial commit

This commit is contained in:
2025-12-07 11:34:37 -06:00
commit d97a8559ec
17 changed files with 8387 additions and 0 deletions

310
src/main.rs Normal file
View File

@@ -0,0 +1,310 @@
use std::{
collections::HashMap,
fmt,
io::{Read, stdin},
};
use iced::{
Color, Element, Font,
Length::Fill,
Point, Renderer, Size, Subscription, Task, Theme, Vector,
futures::io::Window,
mouse,
widget::{
Canvas, button,
canvas::{Frame, Program},
column, container, row, text,
},
window::{self, Id, Settings},
};
use nes_emu::{
NES, PPU, RenderBuffer,
header_menu::header_menu,
hex_view::{self, HexEvent, HexView},
};
use tracing::{debug, info};
use tracing_subscriber::EnvFilter;
extern crate nes_emu;
fn main() -> Result<(), iced::Error> {
tracing_subscriber::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
iced::daemon(Emulator::title, Emulator::update, Emulator::view)
.subscription(Emulator::subscriptions)
.theme(|_, _| Theme::Dark)
.run_with(Emulator::new)
// iced::application(title, Emulator::update, Emulator::view)
// .subscription(Emulator::subscriptions)
// .theme(|_| Theme::Dark)
// .centered()
// .run()
}
enum MemoryTy {
Cpu,
}
enum WindowType {
Main,
Memory(MemoryTy, HexView),
TileMap,
TileViewer,
}
#[derive(Debug, Clone, PartialEq, Eq)]
enum HeaderButton {
OpenMemory,
OpenTileMap,
OpenTileViewer,
Reset,
PowerCycle,
}
impl fmt::Display for HeaderButton {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OpenMemory => write!(f, "Open Memory Viewer"),
Self::OpenTileMap => write!(f, "Open TileMap Viewer"),
Self::OpenTileViewer => write!(f, "Open Tile Viewer"),
Self::Reset => write!(f, "Reset"),
Self::PowerCycle => write!(f, "Power Cycle"),
}
}
}
struct Emulator {
nes: NES,
windows: HashMap<Id, WindowType>,
}
#[derive(Debug, Clone)]
enum Message {
Tick(usize),
Frame,
DMA,
CPU,
DebugInt,
WindowOpened(Id),
WindowClosed(Id),
Header(HeaderButton),
Hex(Id, HexEvent),
}
impl Emulator {
fn new() -> (Self, Task<Message>) {
let rom_file = concat!(env!("ROM_DIR"), "/", "read_write.nes");
// let rom_file = "./Super Mario Bros. (World).nes";
let mut nes = nes_emu::NES::load_nes_file(rom_file)
.expect("Failed to load nes file");
nes.reset();
let (win, task) = iced::window::open(Settings::default());
(
Self {
nes,
windows: HashMap::from_iter([(win, WindowType::Main)]),
},
task.map(Message::WindowOpened),
)
}
fn title(&self, win: Id) -> String {
match self.windows.get(&win) {
Some(WindowType::Main) => "NES emu".into(),
Some(WindowType::Memory(_, _)) => "NES MemoryView".into(),
Some(WindowType::TileMap) => "NES TileMap".into(),
Some(WindowType::TileViewer) => "NES Tile Viewer".into(),
None => todo!(),
}
}
fn open(&mut self, ty: WindowType) -> Task<Message> {
let (win, task) = iced::window::open(Settings::default());
self.windows.insert(win, ty);
return task.map(Message::WindowOpened);
}
fn update(&mut self, message: Message) -> Task<Message> {
match message {
Message::Tick(count) => {
for _ in 0..count {
self.nes.run_one_clock_cycle();
}
}
Message::Frame => while !self.nes.run_one_clock_cycle().ppu_frame {},
Message::DMA => while !self.nes.run_one_clock_cycle().dma {},
Message::CPU => while !self.nes.run_one_clock_cycle().cpu_exec {},
Message::DebugInt => while !self.nes.run_one_clock_cycle().dbg_int {},
Message::WindowOpened(id) => {
// Window
}
Message::WindowClosed(id) => {
if let Some(WindowType::Main) = self.windows.remove(&id) {
return iced::exit();
}
}
Message::Header(HeaderButton::OpenMemory) => {
return self.open(WindowType::Memory(MemoryTy::Cpu, HexView::new()));
}
Message::Header(HeaderButton::OpenTileMap) => {
return self.open(WindowType::TileMap);
}
Message::Header(HeaderButton::OpenTileViewer) => {
return self.open(WindowType::TileViewer);
}
Message::Hex(id, val) => {
if let Some(WindowType::Memory(_, view)) = self.windows.get_mut(&id) {
return view.update(val).map(move |e| Message::Hex(id, e));
}
}
Message::Header(HeaderButton::Reset) => { self.nes.reset(); }
Message::Header(HeaderButton::PowerCycle) => { self.nes.power_cycle(); }
}
// self.image.0.clone_from(self.nes.image());
Task::none()
}
fn subscriptions(&self) -> Subscription<Message> {
window::close_events().map(Message::WindowClosed)
}
fn view(&self, win: Id) -> Element<'_, Message> {
match self.windows.get(&win) {
Some(WindowType::Main) => {
let content = column![
self.dropdowns(),
Element::from(Canvas::new(self).width(Fill).height(255. * 2.)),
self.cpu_state(),
self.controls(),
]
.height(Fill);
container(content).width(Fill).height(Fill).into()
}
Some(WindowType::Memory(ty, view)) => {
let hex = match ty {
MemoryTy::Cpu => view
.render(self.nes.cpu_mem())
.map(move |e| Message::Hex(win, e)),
};
let content = column![hex].width(Fill).height(Fill);
container(content).width(Fill).height(Fill).into()
}
Some(WindowType::TileMap) => {
container(Canvas::new(DbgImage::PatternTable(self.nes.ppu()))).width(Fill).height(Fill).into()
}
Some(WindowType::TileViewer) => {
container(Canvas::new(DbgImage::NameTable(self.nes.ppu()))).width(Fill).height(Fill).into()
}
_ => todo!(),
}
}
fn cpu_state(&self) -> Element<'_, Message> {
row![column![
// text!("Registers").font(Font::MONOSPACE),
text!("{:?}", self.nes).font(Font::MONOSPACE),
],]
.width(Fill)
.into()
}
fn controls(&self) -> Element<'_, Message> {
row![
button("Clock tick").on_press(Message::Tick(1)),
button("CPU tick").on_press(Message::CPU),
button("Next Frame").on_press(Message::Frame),
button("Next DMA").on_press(Message::DMA),
button("Next DBG").on_press(Message::DebugInt),
]
.width(Fill)
.into()
}
fn dropdowns(&self) -> Element<'_, Message> {
row![
header_menu(
"Console",
[
HeaderButton::Reset,
HeaderButton::PowerCycle,
],
Message::Header
),
header_menu(
"Debugging",
[
HeaderButton::OpenMemory,
HeaderButton::OpenTileMap,
HeaderButton::OpenTileViewer,
],
Message::Header
)
]
.width(Fill)
.into()
}
}
impl Program<Message> for Emulator {
type State = ();
fn draw(
&self,
state: &Self::State,
renderer: &Renderer,
theme: &Theme,
bounds: iced::Rectangle,
_cursor: mouse::Cursor,
) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
const SIZE: f32 = 2.;
let mut frame = Frame::new(
renderer,
iced::Size {
width: 256. * 2.,
height: 240. * 2.,
},
);
frame.scale(2.);
for y in 0..240 {
for x in 0..256 {
let c = self.nes.image().read(y, x);
frame.fill_rectangle(
Point::new(x as f32, y as f32),
Size::new(1., 1.),
Color::from_rgb8(c.r, c.g, c.b),
);
}
}
vec![frame.into_geometry()]
}
}
enum DbgImage<'a> {
NameTable(&'a PPU),
PatternTable(&'a PPU),
}
impl Program<Message> for DbgImage<'_> {
type State = ();
fn draw(
&self,
state: &Self::State,
renderer: &Renderer,
theme: &Theme,
bounds: iced::Rectangle,
_cursor: mouse::Cursor,
) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
const SIZE: f32 = 2.;
let mut name_table_frame =
Frame::new(renderer, Size::new(256. * 4. + 260. * 2., 256. * 4.));
name_table_frame.scale(2.);
match self {
DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame),
DbgImage::PatternTable(ppu) => ppu.render_pattern_tables(&mut name_table_frame),
}
vec![name_table_frame.into_geometry()]
}
}