Skip to content

Commit

Permalink
Finished setup for tombstone to have both a TUI and UI
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerBloom committed Oct 5, 2024
1 parent 544712b commit dabeb54
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 206 deletions.
4 changes: 4 additions & 0 deletions ghast/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@ impl Emulator {
];
Scrollable::new(col)
}

pub fn gb(&self) -> &Gameboy {
self.gb.gb()
}
}

impl Default for Emulator {
Expand Down
89 changes: 2 additions & 87 deletions tombstone/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ use std::{

use spirit::lookup::{Instruction, JumpOp};

use crate::{AppState, GameBoyLike};
use crate::AppState;
use untwine::{parse, ParserError};

#[derive(Debug)]
enum CommandParserError {
pub enum CommandParserError {
Number,
Command,
}
Expand Down Expand Up @@ -104,91 +104,6 @@ pub(crate) enum Command {
Stash(StashOptions),
}

impl Command {
pub(crate) fn process<GB: GameBoyLike>(self, gb: &mut GB) -> String {
match self {
Command::Info => gb.gb().cpu().to_string(),
Command::Step(n) => {
(0..n).any(|_| {
gb.step();
gb.is_complete()
});
String::new()
}
Command::Index(opts) => match opts {
IndexOptions::Single(i) => format!("ADDR=0x{i:0>4X} -> {:0>2X}", gb.gb().mem[i as u16]),
IndexOptions::Range(mut rng) => {
let mut digest = format!("ADDR=0x{:0>4X}..0x{:0>4X}", rng.start, rng.end);
digest.push('[');
if let Some(i) = rng.next() {
write!(digest, "0x{:0>2}", gb.gb().mem[i as u16]).unwrap();
}
for i in rng {
write!(digest, ", 0x{:0>2}", gb.gb().mem[i as u16]);
}
digest.push(']');
digest
}
},
Command::Run(until) => match until {
RunUntil::Loop => {
let mut ops = Vec::new();
let mut cpu_map: HashMap<_, HashMap<u64, usize>> = HashMap::new();
let mut index = 0;
while !gb.is_complete() {
let cpu = gb.gb().cpu().clone();
let ptr = cpu.pc.0;
ops.push((cpu, ptr, gb.next_op()));
match cpu_map.entry(gb.gb().cpu().clone()) {
Entry::Occupied(mut entry) => {
let mut hasher = DefaultHasher::new();
gb.gb().mem.hash(&mut hasher);
match entry.get_mut().entry(hasher.finish()) {
Entry::Vacant(entry) => {
entry.insert(ops.len());
}
Entry::Occupied(entry) => {
index = *entry.get();
break;
}
}
}
Entry::Vacant(entry) => {
entry.insert(HashMap::new());
}
}
gb.step()
}
if !gb.is_complete() {
let mut digest = "Infinite loop detected! Here are the instructions and CPU states in the loop:".to_string();
for (state, ptr, op) in &ops[index..] {
writeln!(digest, "\n{state}");
write!(digest, "0x{ptr:0>4X} -> {op}");
}
digest
} else {
"No loop detect during start up!".to_owned()
}
}
RunUntil::Return => {
while !matches!(gb.next_op(), Instruction::Jump(JumpOp::Return)) {
gb.step()
}
"End of subroutine. Next operation is `ret`.".to_owned()
}
RunUntil::Frame => {
gb.step_frame();
"Starting new frame".to_owned()
}
},
Command::Stash(_stash) => {
todo!()
}
Command::Interrupt(_interrupt) => todo!(),
}
}
}

pub(crate) enum StashOptions {
/// Save a snapshot of the current state. If no string is provided, it is given a number equal
/// to the number of saved snapshots.
Expand Down
136 changes: 31 additions & 105 deletions tombstone/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ use ratatui::{
use spirit::cpu::CpuState;
use spirit::{cpu::Cpu, lookup::Instruction, mem::MemoryMap, ppu::Ppu, Gameboy, StartUpSequence};

mod command;
mod window;
mod state;
pub mod command;
mod repl;
mod state;
mod window;
use command::*;
use state::*;
use tokio::sync::broadcast;
Expand All @@ -28,34 +28,19 @@ use window::WindowState;

static TMP_ROM: &[u8] = include_bytes!("../../spirit/tests/roms/acid/which.gb");

fn main() -> Result<(), Box<dyn Error>> {
fn main() {
let backend = CrosstermBackend::new(std::io::stdout());
let mut term = Terminal::new(backend)?;
term.clear()?;
let mut term = Terminal::new(backend).unwrap();
term.clear().unwrap();
let mut gb = Arc::new(Mutex::new(Emulator::default()));
let (send, recv) = broadcast::channel(100);
WindowState::new(gb.clone(), recv).run();
let mut state = AppState::new(gb);
let mut state = AppState::new(gb.clone());
state.mem_start = 0x8000;
state.run(&mut state, term)
}

/// This function abstracts over running the startup sequence and the gameboy itself.
fn run_until_complete<GB, B>(
mut gb: GB,
state: &mut AppState,
term: &mut Terminal<B>,
) -> Result<(), Box<dyn Error>>
where
GB: GameBoyLike,
B: Backend,
{
while !gb.is_complete() {
}
Ok(())
std::thread::spawn(move || state.run(&mut term));
WindowState::new(gb, recv).run();
}

fn render_frame(frame: &mut Frame, state: &mut AppState, gb: &Gameboy) {
fn render_frame(frame: &mut Frame, gb: &Gameboy) {
let sections = Layout::default()
.direction(Direction::Horizontal)
.constraints([Constraint::Fill(1), Constraint::Length(40)])
Expand All @@ -75,21 +60,22 @@ fn render_frame(frame: &mut Frame, state: &mut AppState, gb: &Gameboy) {
Constraint::Fill(1),
])
.split(right);
render_cli(frame, state, left[0]);
// render_pc_area(frame, state, left[1], gb);
render_mem(frame, state, left[1], &gb.mem);
render_cpu(frame, state, right[0], gb.cpu());
render_ppu(frame, state, right[1], &gb.ppu);
render_interrupts(frame, state, right[2], &gb.mem);
render_stack(frame, state, right[3], &gb.mem);
// render_mem(frame, state, right[2]);
render_cli(frame, left[0]);
// render_pc_area(frame, left[1], gb);
render_mem(frame, left[1], &gb.mem);
render_cpu(frame, right[0], gb.cpu());
render_ppu(frame, right[1], &gb.ppu);
render_interrupts(frame, right[2], &gb.mem);
render_stack(frame, right[3], &gb.mem);
// render_mem(frame, right[2]);
}

fn render_cli(frame: &mut Frame, state: &mut AppState, area: Rect) {
fn render_cli(frame: &mut Frame, area: Rect) {
let block = Block::bordered()
.title("CLI")
.title_alignment(ratatui::layout::Alignment::Center);
let Rect { x, y, height, .. } = block.inner(area);
/*
let cursor_y = y + std::cmp::min(state.cli_history.len(), (height - 1) as usize) as u16;
let iter = state
.cli_history
Expand All @@ -98,12 +84,13 @@ fn render_cli(frame: &mut Frame, state: &mut AppState, area: Rect) {
.take((height - 1) as usize)
.rev()
.map(|s| s.as_str());
let para = Paragraph::new(Text::from_iter(iter)).block(block);
frame.set_cursor_position(Position::new(x, cursor_y));
*/
let para = Paragraph::new(Text::from_iter(std::iter::once(String::new()))).block(block);
frame.set_cursor_position(Position::new(x, 0));
frame.render_widget(para, area);
}

fn render_pc_area(frame: &mut Frame, state: &mut AppState, area: Rect, gb: &Gameboy) {
fn render_pc_area(frame: &mut Frame, area: Rect, gb: &Gameboy) {
let block = Block::bordered()
.title(format!(" PC Area {} ", area.width))
.title_alignment(ratatui::layout::Alignment::Center);
Expand All @@ -119,7 +106,7 @@ fn render_pc_area(frame: &mut Frame, state: &mut AppState, area: Rect, gb: &Game

/* This is the right column. In order, the CPU, PPU, Interrupts, and then the stack are rendered */

fn render_cpu(frame: &mut Frame, state: &mut AppState, area: Rect, cpu: &Cpu) {
fn render_cpu(frame: &mut Frame, area: Rect, cpu: &Cpu) {
let block = Block::bordered()
.title("CPU")
.title_alignment(ratatui::layout::Alignment::Center);
Expand All @@ -140,15 +127,15 @@ fn render_cpu(frame: &mut Frame, state: &mut AppState, area: Rect, cpu: &Cpu) {
frame.render_widget(para, area);
}

fn render_ppu(frame: &mut Frame, state: &mut AppState, area: Rect, ppu: &Ppu) {
fn render_ppu(frame: &mut Frame, area: Rect, ppu: &Ppu) {
let block = Block::bordered()
.title("PPU")
.title_alignment(ratatui::layout::Alignment::Center);
let para = Paragraph::new(format!("{:#?}", ppu.inner)).block(block);
frame.render_widget(para, area);
}

fn render_interrupts(frame: &mut Frame, state: &mut AppState, area: Rect, mem: &MemoryMap) {
fn render_interrupts(frame: &mut Frame, area: Rect, mem: &MemoryMap) {
let block = Block::bordered()
.title("Interrupts")
.title_alignment(ratatui::layout::Alignment::Center);
Expand All @@ -160,7 +147,7 @@ fn render_interrupts(frame: &mut Frame, state: &mut AppState, area: Rect, mem: &
frame.render_widget(para, area);
}

fn render_stack(frame: &mut Frame, state: &mut AppState, area: Rect, mem: &MemoryMap) {
fn render_stack(frame: &mut Frame, area: Rect, mem: &MemoryMap) {
let block = Block::bordered()
.title("Stack")
.title_alignment(ratatui::layout::Alignment::Center);
Expand All @@ -175,11 +162,12 @@ fn render_stack(frame: &mut Frame, state: &mut AppState, area: Rect, mem: &Memor

// TODO: Move mem

fn render_mem(frame: &mut Frame, state: &mut AppState, area: Rect, mem: &MemoryMap) {
fn render_mem(frame: &mut Frame, area: Rect, mem: &MemoryMap) {
let block = Block::bordered()
.title("Memory")
.title_alignment(ratatui::layout::Alignment::Center);
let mem_start = state.mem_start & 0xFFF0;
// let mem_start = state.mem_start & 0xFFF0;
let mem_start = 0x8000;
let lines = area.height - 2;
let mut buffer = vec![0; 16 * lines as usize];
(mem_start..)
Expand All @@ -200,65 +188,3 @@ fn render_mem(frame: &mut Frame, state: &mut AppState, area: Rect, mem: &MemoryM
let para = Paragraph::new(data).block(block);
frame.render_widget(para, area);
}

trait GameBoyLike {
const PROMPT: &'static str;

fn gb(&self) -> &Gameboy;

fn next_op(&self) -> Instruction;

fn step(&mut self);

fn step_frame(&mut self);

fn is_complete(&self) -> bool;
}

impl GameBoyLike for Gameboy {
const PROMPT: &'static str = "running";

fn gb(&self) -> &Gameboy {
self
}

fn next_op(&self) -> Instruction {
self.cpu().read_op(&self.mem)
}

fn step(&mut self) {
self.step().complete()
}

fn step_frame(&mut self) {
self.next_frame().complete()
}

fn is_complete(&self) -> bool {
self.cpu().state != CpuState::Running
}
}

impl GameBoyLike for StartUpSequence {
const PROMPT: &'static str = "init";

fn gb(&self) -> &Gameboy {
self.gb()
}

fn next_op(&self) -> Instruction {
*self.next_op()
}

fn step(&mut self) {
self.step()
}

fn step_frame(&mut self) {
self.frame_step().complete()
}

fn is_complete(&self) -> bool {
self.is_complete()
}
}
Empty file removed tombstone/src/screen.rs
Empty file.
Loading

0 comments on commit dabeb54

Please sign in to comment.