Major work
Some checks failed
Cargo Build & Test / Rust project - latest (stable) (push) Failing after 10s

This commit is contained in:
2026-01-19 01:36:58 -06:00
parent c535e4e76d
commit cd3de5e361
34 changed files with 1750 additions and 606 deletions

View File

@@ -1,13 +1,16 @@
use std::rc::Rc;
use iced::{
Element,
Length::Fill,
widget::{
self, button, checkbox, column, container::bordered_box, image, number_input, row,
scrollable, text,
},
advanced::{
layout::Node, widget::{
tree::{State, Tag}, Tree
}, Widget
}, mouse, widget::{
self, button, canvas::{Frame, Program}, checkbox, column, container::bordered_box, image, number_input, row, rule::horizontal, scrollable, text, Canvas, Text
}, window::Event, Element, Length::{self, Fill}, Point, Renderer, Size, Theme
};
use crate::{CycleResult, NES};
use crate::{CycleResult, NES, PPU};
#[derive(Debug, Clone)]
pub struct DebuggerState {
@@ -37,6 +40,35 @@ pub enum DebuggerMessage {
RunFrames,
}
pub fn hex16<'a, Theme, Renderer>(val: u16) -> Text<'a, Theme, Renderer>
where
Theme: text::Catalog + 'a,
Renderer: iced::advanced::text::Renderer,
{
text(format!("{val:04X}"))
}
pub fn hex8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer>
where
Theme: text::Catalog + 'a,
Renderer: iced::advanced::text::Renderer,
{
text(format!("{val:02X}"))
}
pub fn bin8<'a, Theme, Renderer>(val: u8) -> Text<'a, Theme, Renderer>
where
Theme: text::Catalog + 'a,
Renderer: iced::advanced::text::Renderer,
{
text(format!("{val:08b}"))
}
pub fn bin32<'a, Theme, Renderer>(val: u32) -> Text<'a, Theme, Renderer>
where
Theme: text::Catalog + 'a,
Renderer: iced::advanced::text::Renderer,
{
text(format!("{val:032b}"))
}
impl DebuggerState {
pub fn new() -> Self {
Self {
@@ -85,38 +117,56 @@ impl DebuggerState {
text("IRQs:"),
labelled_box("NMI", nes.peek_nmi()),
labelled_box("Cart", false),
labelled_box("Frame Counter", false),
labelled_box("Frame Counter", nes.apu().peek_irq()),
labelled_box("DMC", false),
]
.spacing(5.),
row![
column![
labelled("Cycle", text(nes.ppu.pixel)),
labelled("Scanline", text(nes.ppu.scanline)),
labelled("PPU Cycle", text(nes.ppu.cycle)),
labelled("Cycle", text(nes.ppu().pixel)),
labelled("Scanline", text(nes.ppu().scanline)),
labelled("PPU Cycle", text(nes.ppu().cycle)),
labelled("V:", hex16(nes.ppu().background.v)),
labelled("T:", hex16(nes.ppu().background.t)),
labelled("X:", hex8(nes.ppu().background.x)),
text(""),
labelled("NT:", hex8(nes.ppu().background.cur_nametable)),
labelled2(
"AT:",
hex8(nes.ppu().background.next_attr),
hex16(
0x23C0
| (nes.ppu().background.v & 0x0C00)
| ((nes.ppu().background.v >> 4) & 0x38)
| ((nes.ppu().background.v >> 2) & 0x07)
)
),
labelled("AT:", hex8(nes.ppu().background.cur_attr)),
labelled("HI:", bin32(nes.ppu().background.cur_shift_high)),
labelled("LO:", bin32(nes.ppu().background.cur_shift_low)),
],
column![
labelled_box("Sprite 0 Hit", false),
labelled_box("Sprite 0 Overflow", false),
labelled_box("Vertical Blank", nes.ppu.vblank),
labelled_box("Write Toggle", false),
labelled_box("", false),
labelled_box("Vertical Blank", nes.ppu().vblank),
labelled_box("Write Toggle", nes.ppu().background.w),
text(""),
labelled_box("Large Sprites", false),
labelled_box("Vertical Write", false),
labelled_box("NMI on VBlank", false),
labelled_box("BG at $1000", false),
labelled_box("Vertical Write", nes.ppu().background.vram_column),
labelled_box("NMI on VBlank", nes.ppu().nmi_on_vblank()),
labelled_box("BG at $1000", nes.ppu().background.second_pattern),
labelled_box("Sprites at $1000", false),
],
column![
labelled_box("Even frame", nes.ppu.even),
labelled_box("BG Enabled", false),
labelled_box("Sprites Enabled", false),
labelled_box("BG Mask", false),
labelled_box("Sprites Mask", false),
labelled_box("Grayscale", false),
labelled_box("Intensify Red", false),
labelled_box("Intensify Green", false),
labelled_box("Intensify Blue", false),
labelled_box("Even frame", nes.ppu().even),
labelled_box("BG Enabled", nes.ppu().mask.enable_background),
labelled_box("Sprites Enabled", nes.ppu().mask.enable_sprites),
labelled_box("BG Mask", nes.ppu().mask.background_on_left_edge),
labelled_box("Sprites Mask", nes.ppu().mask.sprites_on_left_edge),
labelled_box("Grayscale", nes.ppu().mask.grayscale),
labelled_box("Intensify Red", nes.ppu().mask.em_red),
labelled_box("Intensify Green", nes.ppu().mask.em_green),
labelled_box("Intensify Blue", nes.ppu().mask.em_blue),
],
column![
run_type(
@@ -158,6 +208,7 @@ impl DebuggerState {
],
]
.spacing(5.),
horizontal(2),
scrollable(
column(
nes.debug_log()
@@ -178,15 +229,15 @@ impl DebuggerState {
fn run_n_clock_cycles(nes: &mut NES, n: usize) {
for _ in 0..n {
nes.run_one_clock_cycle();
if nes.halted {
if nes.run_one_clock_cycle().dbg_int || nes.halted {
break;
}
}
}
fn run_until(nes: &mut NES, mut f: impl FnMut(CycleResult, &NES) -> bool, mut count: usize) {
loop {
if f(nes.run_one_clock_cycle(), nes) {
let res = nes.run_one_clock_cycle();
if res.dbg_int || f(res, nes) {
count -= 1;
if count <= 0 {
break;
@@ -250,8 +301,277 @@ pub fn labelled<'a, Message: 'a>(
.into()
}
pub fn labelled2<'a, Message: 'a>(
label: &'a str,
content: impl Into<Element<'a, Message>>,
content2: impl Into<Element<'a, Message>>,
) -> Element<'a, Message> {
row![
widget::container(text(label)).padding(2.),
widget::container(content).style(bordered_box).padding(2.),
widget::container(content2).style(bordered_box).padding(2.),
]
.spacing(1.)
.into()
}
pub fn labelled_box<'a, Message: 'a>(label: &'a str, value: bool) -> Element<'a, Message> {
row![checkbox(value), widget::container(text(label)),]
.spacing(1.)
.into()
}
#[derive(Clone, Copy)]
pub enum DbgImage<'a> {
NameTable(&'a PPU),
PatternTable(&'a PPU),
Palette(&'a PPU),
}
impl<Message, T> Program<Message, T> for DbgImage<'_> {
type State = ();
fn draw(
&self,
_state: &Self::State,
renderer: &Renderer,
_theme: &T,
_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.);
// println!("Position: {:?}", cursor.position());
match self {
DbgImage::NameTable(ppu) => ppu.render_name_table(&mut name_table_frame),
DbgImage::PatternTable(ppu) => ppu.render_pattern_tables(&mut name_table_frame),
DbgImage::Palette(ppu) => ppu.render_palette(&mut name_table_frame),
};
vec![name_table_frame.into_geometry()]
}
}
impl DbgImage<'_> {
fn width(&self) -> Length {
match self {
DbgImage::NameTable(_) => Length::Fixed(512. * 2.),
DbgImage::PatternTable(_) => Length::Fixed(16. * 8. * 2.),
DbgImage::Palette(_) => Length::Fixed(40. * 2.),
}
}
fn height(&self) -> Length {
match self {
DbgImage::NameTable(_) => Length::Fixed(512. * 2.),
DbgImage::PatternTable(_) => Length::Fixed(16. * 8. * 2. * 2.),
DbgImage::Palette(_) => Length::Fixed(80. * 2.),
}
}
fn help(&self, cursor: Point) -> Option<String> {
match self {
DbgImage::NameTable(ppu) => ppu.name_cursor_info(cursor),
DbgImage::PatternTable(ppu) => ppu.pattern_cursor_info(cursor),
DbgImage::Palette(ppu) => ppu.palette_cursor_info(cursor),
}
}
}
struct DbgImageSetup<'a, M, T: text::Catalog> {
dbg: DbgImage<'a>,
image: Canvas<DbgImage<'a>, M, T>,
text: Text<'a, T>,
padding: f32,
drawn: bool,
// image: DbgImage<'a>,
// text: te,
}
// pub trait Container<Message, Theme, Renderer> {
// // Not ideal
// fn children(&self) -> &[&dyn Widget<Message, Theme, Renderer>];
// }
impl<'s, Message, Theme> Widget<Message, Theme, Renderer> for DbgImageSetup<'s, Message, Theme>
where
Theme: text::Catalog + 's,
{
fn size(&self) -> Size<iced::Length> {
// self.
Size::new(Fill, Fill)
}
fn layout(
&mut self,
tree: &mut Tree,
renderer: &Renderer,
limits: &iced::advanced::layout::Limits,
) -> Node {
let img_node = self.image.layout(&mut tree.children[0], renderer, limits);
let txt_node = Widget::<Message, Theme, _>::layout(
&mut self.text,
&mut tree.children[1],
renderer,
&limits.shrink(Size::new(
img_node.size().width + self.padding * 2.,
self.padding * 2.,
)),
)
.move_to(Point::new(
img_node.size().width + self.padding,
self.padding,
));
Node::with_children(limits.max(), vec![img_node, txt_node])
}
fn draw(
&self,
tree: &Tree,
renderer: &mut Renderer,
theme: &Theme,
style: &iced::advanced::renderer::Style,
layout: iced::advanced::Layout<'_>,
cursor: iced::advanced::mouse::Cursor,
viewport: &iced::Rectangle,
) {
self.image.draw(
&tree.children[0],
renderer,
theme,
style,
layout.child(0),
cursor,
viewport,
);
Widget::<Message, Theme, _>::draw(
&self.text,
&tree.children[1],
renderer,
theme,
style,
layout.child(1),
cursor,
viewport,
)
}
fn tag(&self) -> Tag {
Tag::of::<Rc<String>>()
}
fn state(&self) -> State {
State::new(Rc::new(String::new()))
}
fn children(&self) -> Vec<Tree> {
vec![
Tree::new(&self.image as &dyn Widget<Message, Theme, Renderer>),
Tree::new(&self.text as &dyn Widget<Message, Theme, Renderer>),
]
}
fn diff(&self, tree: &mut Tree) {
tree.diff_children(&[
&self.image as &dyn Widget<Message, Theme, Renderer>,
&self.text as &dyn Widget<Message, Theme, Renderer>,
]);
}
fn operate(
&mut self,
tree: &mut Tree,
layout: iced::advanced::Layout<'_>,
renderer: &Renderer,
operation: &mut dyn iced::advanced::widget::Operation,
) {
operation.container(None, layout.bounds());
operation.traverse(&mut |op| {
self.image
.operate(&mut tree.children[0], layout.child(0), renderer, op);
Widget::<Message, Theme, _>::operate(
&mut self.text,
&mut tree.children[1],
layout.child(1),
renderer,
op,
);
});
}
fn update(
&mut self,
tree: &mut Tree,
event: &iced::Event,
layout: iced::advanced::Layout<'_>,
cursor: iced::advanced::mouse::Cursor,
renderer: &Renderer,
clipboard: &mut dyn iced::advanced::Clipboard,
shell: &mut iced::advanced::Shell<'_, Message>,
viewport: &iced::Rectangle,
) {
self.image.update(
&mut tree.children[0],
event,
layout.child(0),
cursor,
renderer,
clipboard,
shell,
viewport,
);
if matches!(event, iced::Event::Mouse(mouse::Event::CursorMoved { .. })) || !self.drawn {
if let Some(help) = cursor
.position_in(layout.child(0).bounds())
.and_then(|pos| self.dbg.help(Point::new(pos.x / 2., pos.y / 2.)))
{
self.text = text(help);
shell.invalidate_layout();
shell.request_redraw();
}
self.drawn = true;
}
Widget::<Message, Theme, _>::update(
&mut self.text,
&mut tree.children[1],
event,
layout.child(1),
cursor,
renderer,
clipboard,
shell,
viewport,
);
}
fn mouse_interaction(
&self,
_tree: &iced::advanced::widget::Tree,
_layout: iced::advanced::Layout<'_>,
_cursor: iced::advanced::mouse::Cursor,
_viewport: &iced::Rectangle,
_renderer: &Renderer,
) -> iced::advanced::mouse::Interaction {
iced::advanced::mouse::Interaction::default()
}
fn overlay<'a>(
&'a mut self,
_tree: &'a mut iced::advanced::widget::Tree,
_layout: iced::advanced::Layout<'a>,
_renderer: &Renderer,
_viewport: &iced::Rectangle,
_translation: iced::Vector,
) -> Option<iced::advanced::overlay::Element<'a, Message, Theme, Renderer>> {
None
}
}
pub fn dbg_image<'a, Message: 'a>(img: DbgImage<'a>) -> Element<'a, Message> {
Element::new(DbgImageSetup {
dbg: img,
image: Canvas::new(img).width(img.width()).height(img.height()),
text: text(""),
padding: 10.,
drawn: false,
})
}