Skip to content

Commit

Permalink
Working on adding window UI to tombstone
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerBloom committed Oct 4, 2024
1 parent 7a6cfb9 commit 544712b
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 18 deletions.
10 changes: 8 additions & 2 deletions ghast/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ enum EmulatorInner {
}

impl EmulatorInner {
fn gb(&self) -> &Gameboy {
pub fn gb(&self) -> &Gameboy {
match self {
EmulatorInner::StartUp(seq) => seq.as_ref().unwrap().gb(),
EmulatorInner::Ready(gb) => gb,
Expand Down Expand Up @@ -73,7 +73,7 @@ impl EmulatorInner {
}

impl Emulator {
fn screen(&self) -> impl Into<Element<Message>> {
fn screen(&self) -> impl Into<Element<'static, Message>> {
let gb = self.gb.gb();
let screen = &gb.ppu.screen;
let col = row![
Expand Down Expand Up @@ -134,6 +134,12 @@ impl Emulator {
}

pub fn view(&self) -> Element<Message> {
self.view_owned()
}

/// This function only exists because `view` can not explicitly return an `Element<'static,
/// Message>` for... reasons.
pub fn view_owned(&self) -> Element<'static, Message> {
column![
row![
Button::new(text(format!("To frame {}", self.frame + 1)))
Expand Down
4 changes: 4 additions & 0 deletions tombstone/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ version = "0.1.0"
edition = "2021"

[dependencies]
ghast = { path = "../ghast" }
clap = { version = "4.5.18", features = ["derive"] }
crossterm = "0.28.1"
ratatui = { version = "0.28.1", features = ["all-widgets"] }
spirit = { path = "../spirit" }
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
untwine = "0.8.0"
tokio = { version = "1.40.0", features = ["full"] }
iced = "0.13.1"
tokio-stream = { version = "0.1.16", features = ["sync"] }
25 changes: 12 additions & 13 deletions tombstone/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#![allow(dead_code, unused)]
use std::fmt::Write;
use std::sync::{Arc, Mutex};
use std::{error::Error, io::Write as _};

use ghast::state::Emulator;
use ratatui::layout::Position;
use ratatui::{
backend::{Backend, CrosstermBackend},
Expand All @@ -14,26 +16,28 @@ use spirit::cpu::CpuState;
use spirit::{cpu::Cpu, lookup::Instruction, mem::MemoryMap, ppu::Ppu, Gameboy, StartUpSequence};

mod command;
mod window;
mod state;
mod repl;
use command::*;
use state::*;
use tokio::sync::broadcast;
use window::WindowState;

// TODO: handle ctrl-C and ctrl-d

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

fn main() -> Result<(), Box<dyn Error>> {
let mut state = AppState::default();
state.mem_start = 0x8000;
let backend = CrosstermBackend::new(std::io::stdout());
let mut term = Terminal::new(backend)?;
term.clear()?;
let mut gb = Gameboy::new(TMP_ROM);
run_until_complete(gb, &mut state, &mut term)?;
println!("Startup sequence complete!");
// run_until_complete(gb, &mut state, &mut term)?;
// Ok(())
todo!()
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);
state.mem_start = 0x8000;
state.run(&mut state, term)
}

/// This function abstracts over running the startup sequence and the gameboy itself.
Expand All @@ -47,11 +51,6 @@ where
B: Backend,
{
while !gb.is_complete() {
term.clear()?;
term.draw(|frame| render_frame(frame, state, gb.gb()))?;
// print!("{} $ ", GB::PROMPT);
// std::io::stdout().flush().unwrap();
get_input(state).process(&mut gb);
}
Ok(())
}
Expand Down
35 changes: 32 additions & 3 deletions tombstone/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use std::{
sync::{Arc, Mutex},
};

use ghast::state::Emulator;
use ratatui::{prelude::Backend, Terminal};
use tracing::level_filters::LevelFilter;
use tracing_subscriber::{
fmt::{
Expand All @@ -14,6 +16,8 @@ use tracing_subscriber::{
FmtSubscriber,
};

use crate::render_frame;

/// This is the app's state which holds all of the CLI data. This includes all previous commands
/// that were ran and all data to be displayed in the TUI (prompts, inputs, command outputs).
///
Expand All @@ -26,11 +30,11 @@ use tracing_subscriber::{
/// Unfortunately, subscribers are designed with threading in mind. Even though this application is
/// strickly single-threaded, we must wrap the buffer in an Arc<Mutex> (rather than an
/// Rc<RefCell>).
#[derive(Debug, Default)]
pub(crate) struct AppState {
// TODO: Add command history (don't store the actual command, store the string that then get
// parsed into commands.
// TODO: We should limit the command and output history. We don't want it to grow forever.
gb: Arc<Mutex<Emulator>>,
pub(crate) cli_history: Vec<String>,
pub(crate) subscriber:
Option<FmtSubscriber<DefaultFields, Format<Compact, ()>, LevelFilter, GameboySubscriber>>,
Expand All @@ -46,8 +50,33 @@ pub enum Verbosity {
}

impl AppState {
pub fn new() -> Self {
Self::default()
pub fn new(gb: Arc<Mutex<Emulator>>) -> Self {
Self {
gb,
cli_history: Vec::new(),
subscriber: None,
buffer: Arc::new(Mutex::new(Vec::new())),
mem_start: 0,
}
}

pub fn run<B: Backend>(self, state: &mut AppState, term: &mut Terminal<B>) {
loop {
term.clear().unwrap();
term.draw(|frame| render_frame(frame, state, self.gb.lock().unwrap().gb()))
.unwrap();
// print!("{} $ ", GB::PROMPT);
// std::io::stdout().flush().unwrap();
self.get_input().process(&mut *self.gb.lock().unwrap());
}
}

fn get_input(&mut self) -> crate::command::Command {
let mut input = String::new();
std::io::stdin().read_line(&mut input).unwrap();
let cmd = untwine::parse(crate::command::command, input.trim()).unwrap();
self.cli_history.push(input);
cmd
}

// TODO: Because the state now tracks the command history and any output from the emulator, we
Expand Down
43 changes: 43 additions & 0 deletions tombstone/src/window.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use std::sync::{Arc, Mutex};

use ghast::state::{Emulator, Message};
use iced::{Element, Subscription, Task};
use tokio::sync::broadcast::Receiver;
use tokio_stream::{wrappers::BroadcastStream, StreamExt};

pub struct WindowState {
gb: Arc<Mutex<Emulator>>,
inbound: Receiver<Message>,
}

impl WindowState {
pub fn new(gb: Arc<Mutex<Emulator>>, inbound: Receiver<Message>) -> Self {
Self { gb, inbound }
}

pub fn run(self) {
std::thread::spawn(move || {
iced::application(
"Specters - Tombstone GBC Debugger",
WindowState::update,
WindowState::view,
)
.subscription(WindowState::subscription)
.run_with(move || (self, Task::none()))
});
}

fn update(&mut self, msg: Message) {
self.gb.lock().unwrap().update(msg)
}

fn view(&self) -> Element<Message> {
self.gb.lock().unwrap().view_owned()
}

fn subscription(&self) -> Subscription<Message> {
let sub = self.gb.lock().unwrap().subscription();
let recv = BroadcastStream::new(self.inbound.resubscribe()).map(Result::unwrap);
Subscription::batch([sub, Subscription::run_with_id(0, recv)])
}
}

0 comments on commit 544712b

Please sign in to comment.