2026-02-07 update
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 8s
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 8s
This commit is contained in:
203
src/main.rs
203
src/main.rs
@@ -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()]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user