Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: Improve CPU handler controls #223

Merged
merged 1 commit into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions emu/src/cpu/arm7tdmi.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::convert::TryInto;

use serde::{Deserialize, Serialize};

#[cfg(feature = "logger")]
use logger::log;

use serde::{Deserialize, Serialize};
#[cfg(feature = "disassembler")]
use vecfixed::VecFixed;

Expand Down Expand Up @@ -38,6 +38,8 @@ pub struct Arm7tdmi {
decoded_arm: Option<ArmModeOpcode>,
fetched_thumb: Option<u16>,
decoded_thumb: Option<ThumbModeOpcode>,

pub current_cycle: u128,
}

#[derive(Copy, Clone)]
Expand Down Expand Up @@ -124,6 +126,7 @@ impl Default for Arm7tdmi {
decoded_arm: None,
fetched_thumb: None,
decoded_thumb: None,
current_cycle: u128::default(),
};

// Setting ARM mode at startup
Expand Down Expand Up @@ -449,6 +452,7 @@ impl Arm7tdmi {
}

pub fn step(&mut self) {
self.current_cycle += 1;
match self.cpsr.cpu_state() {
CpuState::Thumb => {
let to_execute = self.decoded_thumb;
Expand Down Expand Up @@ -705,11 +709,12 @@ impl std::fmt::Display for HalfwordTransferKind {

#[cfg(test)]
mod tests {
use pretty_assertions::assert_eq;

use crate::cpu::condition::Condition;
use crate::cpu::flags::{HalfwordDataTransferOffsetKind, Indexing, LoadStoreKind, Offsetting};
use crate::cpu::registers::{REG_LR, REG_PROGRAM_COUNTER, REG_SP};
use crate::cpu::thumb::instruction::ThumbModeInstruction;
use pretty_assertions::assert_eq;

use super::*;

Expand Down
238 changes: 166 additions & 72 deletions ui/src/cpu_handler.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
use emu::gba::Gba;

use crate::ui_traits::UiTool;

use std::collections::BTreeSet;
use std::ops::{Deref, DerefMut, Range};
use std::sync::atomic::AtomicBool;
use std::sync::{Arc, Mutex};
use std::thread;

use egui::text_selection::text_cursor_state::byte_index_from_char_index;
use egui::{TextBuffer, TextEdit};

use emu::gba::Gba;

use crate::ui_traits::UiTool;

pub struct CpuHandler {
gba: Arc<Mutex<Gba>>,
play: Arc<AtomicBool>,
thread_handle: Option<thread::JoinHandle<()>>,
breakpoints: Arc<Mutex<BTreeSet<Breakpoint>>>,
b_address: String,

b_address: UpperHexString,
breakpoint_combo: BreakpointType,
cycle_to_skip_custom_value: u64,
}

impl CpuHandler {
Expand All @@ -24,8 +28,9 @@ impl CpuHandler {
play: Arc::new(AtomicBool::new(false)),
thread_handle: None,
breakpoints: Arc::new(Mutex::new(BTreeSet::new())),
b_address: String::default(),
b_address: UpperHexString::default(),
breakpoint_combo: BreakpointType::Equal,
cycle_to_skip_custom_value: 5000,
}
}
}
Expand All @@ -42,6 +47,56 @@ enum BreakpointType {
Greater,
}

#[derive(Default)]
struct UpperHexString(String);

impl Deref for UpperHexString {
type Target = String;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl DerefMut for UpperHexString {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

impl TextBuffer for UpperHexString {
fn is_mutable(&self) -> bool {
true
}

fn as_str(&self) -> &str {
&self.0
}

fn insert_text(&mut self, text: &str, char_index: usize) -> usize {
let mut text_string = text.to_string();
text_string.retain(|c| c.is_ascii_hexdigit());
text_string.make_ascii_uppercase();

let byte_idx = byte_index_from_char_index(self.as_str(), char_index);

self.insert_str(byte_idx, text_string.as_str());

text.chars().count()
}

fn delete_char_range(&mut self, char_range: Range<usize>) {
assert!(char_range.start <= char_range.end);

// Get both byte indices
let byte_start = byte_index_from_char_index(self.as_str(), char_range.start);
let byte_end = byte_index_from_char_index(self.as_str(), char_range.end);

// Then drain all characters within this range
self.drain(byte_start..byte_end);
}
}

impl UiTool for CpuHandler {
fn name(&self) -> &'static str {
"Cpu Handler"
Expand Down Expand Up @@ -118,89 +173,128 @@ impl UiTool for CpuHandler {
self.play.swap(false, std::sync::atomic::Ordering::Relaxed);
self.thread_handle = None;
}
});

ui.collapsing("CPU Advanced controls", |ui| {
ui.label(format!(
"Current CPU cycle: {}",
&mut self.gba.lock().unwrap().cpu.current_cycle
));

if ui.button("⏭x1").clicked() {
if let Ok(mut gba) = self.gba.lock() {
gba.step()
ui.horizontal(|ui| {
ui.label("Step CPU cycles:");

if ui.button("⏭x1").clicked() {
if let Ok(mut gba) = self.gba.lock() {
gba.step()
}
}
}

if ui.button("⏭x10").clicked() {
if let Ok(mut gba) = self.gba.lock() {
(0..10).for_each(|_| gba.step());
if ui.button("⏭x10").clicked() {
if let Ok(mut gba) = self.gba.lock() {
(0..10).for_each(|_| gba.step());
}
}
}

if ui.button("⏭x100").clicked() {
if let Ok(mut gba) = self.gba.lock() {
(0..100).for_each(|_| gba.step());
if ui.button("⏭x100").clicked() {
if let Ok(mut gba) = self.gba.lock() {
(0..100).for_each(|_| gba.step());
}
}
}

if ui.button("⏭x500").clicked() {
if let Ok(mut gba) = self.gba.lock() {
(0..500).for_each(|_| gba.step());
if ui.button("⏭x500").clicked() {
if let Ok(mut gba) = self.gba.lock() {
(0..500).for_each(|_| gba.step());
}
}
}
});

ui.add_space(20.0);
if ui.button("⏭x1000").clicked() {
if let Ok(mut gba) = self.gba.lock() {
(0..1000).for_each(|_| gba.step());
}
}
});

ui.heading("Breakpoints");
ui.add_space(12.0);
ui.horizontal(|ui| {
ui.label("Step (custom) CPU cycles:");
ui.add(egui::DragValue::new(&mut self.cycle_to_skip_custom_value).speed(100));

ui.horizontal(|ui| {
ui.label("Address (hex) : ");
ui.text_edit_singleline(&mut self.b_address);

egui::ComboBox::from_label("Select breakpoint type")
.selected_text(if self.breakpoint_combo == BreakpointType::Equal {
"="
} else {
">"
})
.show_ui(ui, |ui| {
ui.style_mut().wrap = Some(false);
ui.set_min_width(60.0);
ui.selectable_value(&mut self.breakpoint_combo, BreakpointType::Equal, "=");
ui.selectable_value(&mut self.breakpoint_combo, BreakpointType::Greater, ">");
});

if ui.button("Add").clicked() {
if self.b_address.is_empty() {
return;
if ui.button("Step").clicked() {
if let Ok(mut gba) = self.gba.lock() {
(0..self.cycle_to_skip_custom_value).for_each(|_| gba.step());
}
}
})
});

let a = if self.b_address.starts_with("0x") {
self.b_address[2..].to_string()
} else {
self.b_address.clone()
};
ui.collapsing("Breakpoints", |ui| {
ui.horizontal(|ui| {
egui::ComboBox::from_id_source("breakpoint-type")
.selected_text(if self.breakpoint_combo == BreakpointType::Equal {
"Equal to"
} else {
"Greater than"
})
.show_ui(ui, |ui| {
ui.style_mut().wrap = Some(false);
ui.set_width(40.0);
ui.set_max_width(100.0);
ui.selectable_value(
&mut self.breakpoint_combo,
BreakpointType::Equal,
"Equal to",
);
ui.selectable_value(
&mut self.breakpoint_combo,
BreakpointType::Greater,
"Greater than",
);
});

let address = u32::from_str_radix(&a, 16).unwrap();
let b = Breakpoint {
address,
kind: self.breakpoint_combo,
};
ui.label("address (HEX):");

self.breakpoints.lock().unwrap().insert(b);
ui.add(
TextEdit::singleline(&mut self.b_address)
.desired_width(150.0)
.char_limit(16),
);

self.b_address.clear();
}
});
if ui.button("Set").clicked() {
if self.b_address.is_empty() {
return;
}

ui.add_space(10.0);
egui::containers::ScrollArea::new([false, true]).show(ui, |ui| {
let breakpoints = self.breakpoints.lock().unwrap().clone();
let a = if self.b_address.starts_with("0x") {
self.b_address[2..].to_string()
} else {
self.b_address.clone()
};

for b in breakpoints.iter() {
ui.horizontal(|ui| {
ui.label(format!("0x{:08X}", b.address));
if ui.button("x").clicked() {
self.breakpoints.lock().unwrap().remove(b);
}
});
}
let address = u32::from_str_radix(&a, 16).unwrap();
let b = Breakpoint {
address,
kind: self.breakpoint_combo,
};

self.breakpoints.lock().unwrap().insert(b);

self.b_address.clear();
}
});

egui::containers::ScrollArea::new([false, true]).show(ui, |ui| {
ui.label("Active breakpoints:");
let breakpoints = self.breakpoints.lock().unwrap().clone();

for b in breakpoints.iter() {
ui.horizontal(|ui| {
ui.label(format!("0x{:08X}", b.address));
if ui.button("X").clicked() {
self.breakpoints.lock().unwrap().remove(b);
}
});
}
});
});
}
}
Loading