2026-02-07 update
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 8s

This commit is contained in:
2026-02-07 04:52:13 -06:00
parent 825e245df1
commit 22c586f15a
15 changed files with 1062 additions and 162 deletions

View File

@@ -1,26 +1,31 @@
use std::{collections::HashMap, fmt, time::Duration};
use std::{
collections::HashMap,
fmt,
time::{Duration, Instant},
};
use iced::{
Color, Element,
Length::{Fill, Shrink},
Point, Renderer, Size, Subscription, Task, Theme,
advanced::graphics::compositor::Display,
mouse,
Point, Rectangle, Renderer, Size, Subscription, Task, Theme,
keyboard::{self, Key, Modifiers, key::Named},
mouse, time,
widget::{
Canvas,
self, Button, Canvas, button,
canvas::{Frame, Program},
column, container, row,
column, container, image, row,
},
window::{self, Id, Settings},
};
use nes_emu::{
NES,
Break, NES,
debugger::{DbgImage, DebuggerMessage, DebuggerState, dbg_image},
header_menu::header_menu,
hex_view::{HexEvent, HexView},
resize_watcher::resize_watcher,
};
use tokio::{io::AsyncWriteExt, runtime::Runtime};
use tracing::instrument::WithSubscriber;
use tracing_subscriber::EnvFilter;
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "even_odd.nes");
@@ -28,10 +33,27 @@ use tracing_subscriber::EnvFilter;
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_red.nes");
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_fill_name_table.nes");
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "int_nmi_exit_timing.nes");
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "render-updating.nes");
// const ROM_FILE: &str = concat!(env!("ROM_DIR"), "/", "ppu_palette_shared.nes");
const ROM_FILE: &str = "./Super Mario Bros. (World).nes";
// const ROM_FILE: &str = "./cpu_timing_test.nes";
// const ROM_FILE: &str = "../nes-test-roms/instr_test-v5/official_only.nes";
/// Disable button in debug mode - used to disable play/pause buttons since the performance isn't fast enough
fn debug_disable<'a, Message, Theme, Renderer>(
btn: Button<'a, Message, Theme, Renderer>,
) -> Button<'a, Message, Theme, Renderer>
where
Theme: button::Catalog + 'a,
Renderer: iced::advanced::Renderer,
{
if cfg!(debug_assertions) {
btn.on_press_maybe(None)
} else {
btn
}
}
extern crate nes_emu;
fn main() -> Result<(), iced::Error> {
@@ -101,10 +123,12 @@ impl fmt::Display for HeaderButton {
}
struct Emulator {
running: bool,
nes: NES,
windows: HashMap<Id, WindowType>,
debugger: DebuggerState,
main_win_size: Size,
prev: [Instant; 2],
}
#[derive(Debug, Clone)]
@@ -117,6 +141,9 @@ enum Message {
// DebugInt,
WindowClosed(Id),
WindowOpened(Id),
Key(keyboard::Event),
Periodic(Instant),
SetRunning(bool),
Header(HeaderButton),
Hex(Id, HexEvent),
Debugger(DebuggerMessage),
@@ -142,20 +169,30 @@ impl Emulator {
min_size: None,
..Settings::default()
});
let (win_2, task_2) = iced::window::open(Settings {
min_size: None,
..Settings::default()
});
(
Self {
nes,
windows: HashMap::from_iter([(win, WindowType::Main)]),
windows: HashMap::from_iter([
(win, WindowType::Main),
(win_2, WindowType::Memory(MemoryTy::OAM, HexView {}))
]),
debugger: DebuggerState::new(),
main_win_size: Size::new(0., 0.),
running: false,
prev: [Instant::now(); 2],
},
task.discard(),
Task::batch([task, task_2]).discard()
// task.discard(),
)
}
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::Memory(ty, _)) => format!("NES {ty:?} Memory"),
Some(WindowType::TileMap) => "NES TileMap".into(),
Some(WindowType::TileViewer) => "NES Tile Viewer".into(),
Some(WindowType::Palette) => "NES Palette Viewer".into(),
@@ -266,6 +303,77 @@ impl Emulator {
})
.discard();
}
Message::Key(key) => match key {
keyboard::Event::KeyPressed {
key: Key::Character(val),
modifiers: Modifiers::CTRL,
repeat: false,
..
} => {
if val == "t" {
self.nes.reset();
}
}
keyboard::Event::KeyPressed {
key,
modifiers: Modifiers::NONE,
repeat: false,
..
} => {
if key == Key::Character("z".into()) {
self.nes.controller_1().set_a(true);
} else if key == Key::Character("x".into()) {
self.nes.controller_1().set_b(true);
} else if key == Key::Character("a".into()) {
self.nes.controller_1().set_select(true);
} else if key == Key::Character("s".into()) {
self.nes.controller_1().set_start(true);
} else if key == Key::Named(Named::ArrowDown) {
self.nes.controller_1().set_down(true);
} else if key == Key::Named(Named::ArrowUp) {
self.nes.controller_1().set_up(true);
} else if key == Key::Named(Named::ArrowLeft) {
self.nes.controller_1().set_left(true);
} else if key == Key::Named(Named::ArrowRight) {
self.nes.controller_1().set_right(true);
}
}
keyboard::Event::KeyReleased {
key,
modifiers: Modifiers::NONE,
..
} => {
if key == Key::Character("z".into()) {
self.nes.controller_1().set_a(false);
} else if key == Key::Character("x".into()) {
self.nes.controller_1().set_b(false);
} else if key == Key::Character("a".into()) {
self.nes.controller_1().set_select(false);
} else if key == Key::Character("s".into()) {
self.nes.controller_1().set_start(false);
} else if key == Key::Named(Named::ArrowDown) {
self.nes.controller_1().set_down(false);
} else if key == Key::Named(Named::ArrowUp) {
self.nes.controller_1().set_up(false);
} else if key == Key::Named(Named::ArrowLeft) {
self.nes.controller_1().set_left(false);
} else if key == Key::Named(Named::ArrowRight) {
self.nes.controller_1().set_right(false);
}
}
_ => (),
},
Message::Periodic(i) => {
if self.running {
// TODO: this should skip updating to avoid multiple frame skips
self.prev[1] = self.prev[0];
self.prev[0] = i;
self.nes.run_one_clock_cycle(&Break::default());
while !self.nes.run_one_clock_cycle(&Break::default()).ppu_frame {}
}
}
Message::SetRunning(running) => self.running = running,
}
// self.image.0.clone_from(self.nes.image());
Task::none()
@@ -275,10 +383,13 @@ impl Emulator {
Subscription::batch([
window::close_events().map(Message::WindowClosed),
window::open_events().map(Message::WindowOpened),
keyboard::listen().map(Message::Key),
time::every(Duration::from_millis(1000 / 60)).map(Message::Periodic),
])
}
fn view(&self, win: Id) -> Element<'_, Message> {
// println!("Running view");
match self.windows.get(&win) {
Some(WindowType::Main) => {
let content = column![
@@ -300,7 +411,7 @@ impl Emulator {
.render_cpu(self.nes.mem())
.map(move |e| Message::Hex(win, e)),
MemoryTy::PPU => view
.render_ppu(self.nes.mem())
.render_ppu(self.nes.mem(), self.nes.ppu())
.map(move |e| Message::Hex(win, e)),
MemoryTy::OAM => view
.render_oam(self.nes.ppu())
@@ -320,12 +431,22 @@ impl Emulator {
Some(WindowType::Palette) => {
dbg_image(DbgImage::Palette(self.nes.mem(), self.nes.ppu())).into()
}
Some(WindowType::Debugger) => {
container(self.debugger.view(&self.nes).map(Message::Debugger))
.width(Fill)
.height(Fill)
.into()
}
Some(WindowType::Debugger) => column![
row![
debug_disable(
button(image("./images/ic_fluent_play_24_filled.png"))
.on_press(Message::SetRunning(true))
),
debug_disable(
button(image("./images/ic_fluent_pause_24_filled.png"))
.on_press(Message::SetRunning(false))
),
],
self.debugger.view(&self.nes).map(Message::Debugger)
]
.width(Fill)
.height(Fill)
.into(),
None => panic!("Window not found"),
// _ => todo!(),
}
@@ -364,6 +485,21 @@ impl Emulator {
impl Program<Message> for Emulator {
type State = ();
fn update(
&self,
_state: &mut Self::State,
_event: &iced::Event,
_bounds: Rectangle,
_cursor: iced::advanced::mouse::Cursor,
) -> Option<widget::Action<Message>> {
// ~ 60 fps, I think?
// Some(widget::Action::request_redraw_at(
// Instant::now() + Duration::from_millis(10),
// ))
// Some(widget::Action::request_redraw())
None
}
fn draw(
&self,
_state: &Self::State,
@@ -372,6 +508,7 @@ impl Program<Message> for Emulator {
bounds: iced::Rectangle,
_cursor: mouse::Cursor,
) -> Vec<iced::widget::canvas::Geometry<Renderer>> {
// let start = Instant::now();
// const SIZE: f32 = 2.;
let mut frame = Frame::new(
renderer,
@@ -381,16 +518,28 @@ impl Program<Message> for Emulator {
// },
);
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),
);
}
}
// TODO: use image for better? performance
frame.draw_image(
Rectangle::new(Point::new(0., 0.), Size::new(256., 240.)),
widget::canvas::Image::new(widget::image::Handle::from_rgba(
256,
240,
self.nes.image().image(),
))
.filter_method(image::FilterMethod::Nearest)
.snap(true),
);
// 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),
// );
// }
// }
// println!("Rendered frame in {}ms", start.elapsed().as_millis());
vec![frame.into_geometry()]
}
}