BUGFIX: gemmaegui: Memory window was showing 16 bytes beyond 'limit'
This commit is contained in:
parent
fdf09daf0f
commit
0f40002053
@ -111,7 +111,7 @@ impl GemmaEguiSupport {
|
|||||||
pub fn memory_view(system: &Chip8Computer, ui: &mut Ui) {
|
pub fn memory_view(system: &Chip8Computer, ui: &mut Ui) {
|
||||||
ui.label("Memory View");
|
ui.label("Memory View");
|
||||||
|
|
||||||
for i in (0..=0x200).step_by(16) {
|
for i in (0..0x200).step_by(16) {
|
||||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
||||||
for y in 0..16 {
|
for y in 0..16 {
|
||||||
ui.label(format!("{:02x}", system.memory.peek((i + y) as u16)).as_str());
|
ui.label(format!("{:02x}", system.memory.peek((i + y) as u16)).as_str());
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "gemmasdl2"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
gemma = { path = "../gemma" }
|
|
||||||
egui_sdl2_platform = "0.3.0"
|
|
||||||
gl = "0.14"
|
|
||||||
egui = "0.27"
|
|
||||||
egui_glow = "0.27"
|
|
||||||
|
|
||||||
pollster = "0.2"
|
|
||||||
anyhow = "1.0"
|
|
||||||
@ -1,171 +0,0 @@
|
|||||||
mod support;
|
|
||||||
use crate::support::gemma_egui_support::GemmaEguiSupport;
|
|
||||||
use anyhow::Context;
|
|
||||||
use egui::TextBuffer;
|
|
||||||
use egui_glow::glow::{HasContext, COLOR_BUFFER_BIT};
|
|
||||||
use egui_sdl2_platform::sdl2;
|
|
||||||
use gemma::chip8::computer::Chip8Computer;
|
|
||||||
use sdl2::event::{Event, WindowEvent};
|
|
||||||
use std::{sync::Arc, time::Instant};
|
|
||||||
use support::timestep::TimeStep;
|
|
||||||
|
|
||||||
const SCREEN_WIDTH: u32 = 800;
|
|
||||||
const SCREEN_HEIGHT: u32 = 480;
|
|
||||||
|
|
||||||
async fn run() -> anyhow::Result<String> {
|
|
||||||
// Initialize SDL2 and video subsystem
|
|
||||||
let sdl = sdl2::init().map_err(|e| anyhow::anyhow!("Failed to create SDL context: {}", e))?;
|
|
||||||
let mut video = sdl
|
|
||||||
.video()
|
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to initialize SDL video subsystem: {}", e))?;
|
|
||||||
|
|
||||||
// Create SDL2 window and OpenGL context
|
|
||||||
let window = video
|
|
||||||
.window("Window", SCREEN_WIDTH, SCREEN_HEIGHT)
|
|
||||||
.opengl()
|
|
||||||
.position_centered()
|
|
||||||
.build()?;
|
|
||||||
let _gl_context = window
|
|
||||||
.gl_create_context()
|
|
||||||
.expect("Failed to create GL context");
|
|
||||||
|
|
||||||
// Load OpenGL functions
|
|
||||||
let gl = unsafe {
|
|
||||||
egui_glow::painter::Context::from_loader_function(|name| {
|
|
||||||
video.gl_get_proc_address(name) as *const _
|
|
||||||
})
|
|
||||||
};
|
|
||||||
let mut painter = egui_glow::Painter::new(Arc::new(gl), "", None)?;
|
|
||||||
|
|
||||||
// Setup Egui and SDL2 platform
|
|
||||||
let mut platform = egui_sdl2_platform::Platform::new(window.size())?;
|
|
||||||
let mut event_pump = sdl
|
|
||||||
.event_pump()
|
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to get SDL event pump: {}", e))?;
|
|
||||||
|
|
||||||
// Initial settings
|
|
||||||
let color = [0.0, 0.0, 0.0, 1.0]; // Background color
|
|
||||||
let start_time = Instant::now();
|
|
||||||
let mut timestep = TimeStep::new();
|
|
||||||
|
|
||||||
let mut computer = Chip8Computer::new();
|
|
||||||
let mut is_running: bool = false;
|
|
||||||
computer.load_bytes_to_memory(0x200, &std::fs::read("resources/roms/3-corax+.ch8")?);
|
|
||||||
|
|
||||||
// Main loop
|
|
||||||
'main: loop {
|
|
||||||
// Update the Egui platform with the current time
|
|
||||||
platform.update_time(start_time.elapsed().as_secs_f64());
|
|
||||||
|
|
||||||
// Begin Egui frame
|
|
||||||
let ctx = platform.context();
|
|
||||||
|
|
||||||
if is_running {
|
|
||||||
computer.step_system();
|
|
||||||
}
|
|
||||||
|
|
||||||
egui::Window::new("Hello, world!").show(&ctx, |ui| {
|
|
||||||
GemmaEguiSupport::video_view(&computer, ui);
|
|
||||||
GemmaEguiSupport::memory_view(&computer, ui);
|
|
||||||
GemmaEguiSupport::registers_view(&computer, ui);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process Egui frame
|
|
||||||
let full_output = platform.end_frame(&mut video)?;
|
|
||||||
let paint_jobs = platform.tessellate(&full_output);
|
|
||||||
|
|
||||||
// Clear the screen with the current color
|
|
||||||
unsafe {
|
|
||||||
painter.gl().clear_color(color[0], color[1], color[2], 1.0);
|
|
||||||
painter.gl().clear(COLOR_BUFFER_BIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paint Egui outputs and update textures
|
|
||||||
let size = window.size();
|
|
||||||
painter.paint_and_update_textures(
|
|
||||||
[size.0, size.1],
|
|
||||||
1.0,
|
|
||||||
paint_jobs.as_slice(),
|
|
||||||
&full_output.textures_delta,
|
|
||||||
);
|
|
||||||
window.gl_swap_window();
|
|
||||||
|
|
||||||
// Run the timestep logic
|
|
||||||
timestep.run_this(|_| {});
|
|
||||||
|
|
||||||
// Handle SDL2 events
|
|
||||||
for event in event_pump.poll_iter() {
|
|
||||||
match event {
|
|
||||||
Event::Quit { .. }
|
|
||||||
| Event::KeyDown {
|
|
||||||
keycode: Some(sdl2::keyboard::Keycode::Escape),
|
|
||||||
..
|
|
||||||
} => break 'main,
|
|
||||||
Event::Window {
|
|
||||||
window_id,
|
|
||||||
win_event,
|
|
||||||
..
|
|
||||||
} if window_id == window.id() => {
|
|
||||||
if let WindowEvent::Close = win_event {
|
|
||||||
break 'main;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Event::KeyUp {
|
|
||||||
keycode: Some(sdl2::keyboard::Keycode::F3),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
println!("USER PRESSED F3 -> running");
|
|
||||||
is_running = true;
|
|
||||||
}
|
|
||||||
Event::KeyUp {
|
|
||||||
keycode: Some(sdl2::keyboard::Keycode::F4),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
println!("USER PRESSED F4 -> stopping");
|
|
||||||
is_running = false;
|
|
||||||
}
|
|
||||||
Event::KeyDown {
|
|
||||||
keycode: Some(sdl2::keyboard::Keycode::F5),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
println!("USER PRESSED F5 -> Step");
|
|
||||||
computer.step_system();
|
|
||||||
}
|
|
||||||
Event::KeyDown {
|
|
||||||
keycode: Some(sdl2::keyboard::Keycode::F6),
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
println!("USER PRESSED F6 -> RESET");
|
|
||||||
computer.reset(computer.quirk_mode.clone());
|
|
||||||
}
|
|
||||||
Event::ControllerButtonDown { which, .. } => {
|
|
||||||
println!("PLAYER {which} DOWN");
|
|
||||||
}
|
|
||||||
Event::JoyButtonDown { button_idx, .. } => {
|
|
||||||
println!("JoyButtonDown {}", button_idx);
|
|
||||||
}
|
|
||||||
Event::JoyAxisMotion {
|
|
||||||
which, axis_idx, ..
|
|
||||||
} => {
|
|
||||||
println!("JoyAxismotion {which} {axis_idx}");
|
|
||||||
}
|
|
||||||
_ => platform.handle_event(&event, &sdl, &video),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally log the frame rate
|
|
||||||
if let Some(fps) = timestep.frame_rate() {
|
|
||||||
println!("{:?} fps", fps);
|
|
||||||
}
|
|
||||||
|
|
||||||
let num_js = sdl.joystick().unwrap().num_joysticks().unwrap();
|
|
||||||
println!("NUM JS = {num_js}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(("").parse()?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
|
||||||
pollster::block_on(run())?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@ -1,151 +0,0 @@
|
|||||||
use crate::Chip8Computer;
|
|
||||||
use egui::Rect;
|
|
||||||
use egui::Ui;
|
|
||||||
use egui::Vec2;
|
|
||||||
use egui::{Align, Color32, ComboBox, Pos2};
|
|
||||||
use std::fs::read_dir;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
const CELL_WIDTH: f32 = 5.0;
|
|
||||||
const CELL_HEIGHT: f32 = 5.0;
|
|
||||||
|
|
||||||
pub struct EGuiFileList {}
|
|
||||||
impl EGuiFileList {
|
|
||||||
pub fn display_path(root: PathBuf, selected_filename: &mut String, ui: &mut Ui) {
|
|
||||||
let working_filename = selected_filename.clone();
|
|
||||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
|
||||||
// ui.label(format!("Displaying {}", root.to_str().unwrap_or("Unable to Load Path")));
|
|
||||||
ComboBox::from_label("Select ROM")
|
|
||||||
.selected_text(selected_filename.clone())
|
|
||||||
.show_ui(ui, |ui| {
|
|
||||||
let mut sorted_options = vec![];
|
|
||||||
for option in read_dir(root.as_path()).unwrap() {
|
|
||||||
let to_push = option
|
|
||||||
.unwrap()
|
|
||||||
.file_name()
|
|
||||||
.into_string()
|
|
||||||
.unwrap_or(String::new());
|
|
||||||
sorted_options.push(to_push);
|
|
||||||
}
|
|
||||||
|
|
||||||
sorted_options.sort();
|
|
||||||
for item in sorted_options {
|
|
||||||
// Add each option to the ComboBox
|
|
||||||
if ui
|
|
||||||
.selectable_label(selected_filename.eq(&item.as_str()), item.clone())
|
|
||||||
.clicked()
|
|
||||||
{
|
|
||||||
*selected_filename = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// Display the selected option
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GemmaEguiSupport {}
|
|
||||||
|
|
||||||
impl GemmaEguiSupport {
|
|
||||||
pub fn controls_view(system: &mut Chip8Computer, ui: &mut Ui) {
|
|
||||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
|
||||||
if ui.button("Start").clicked() {
|
|
||||||
println!("Start");
|
|
||||||
// state.is_running = true;
|
|
||||||
}
|
|
||||||
if ui.button("Step").clicked() {
|
|
||||||
system.step_system();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ui.button("Stop").clicked() {
|
|
||||||
println!("STOP");
|
|
||||||
// state.is_running = false;
|
|
||||||
}
|
|
||||||
if ui.button("Reset").clicked() {
|
|
||||||
system.reset(system.quirk_mode.clone());
|
|
||||||
// state.is_running = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// if ui.button(format!("Load {}", state.selected_rom_filename)).clicked() {
|
|
||||||
// load the bin...
|
|
||||||
// let read_bin = std::fs::read(PathBuf::from(format!("resources/roms/{}", state.selected_rom_filename))).unwrap();
|
|
||||||
// ...then feed the system.
|
|
||||||
// system.load_bytes_to_memory(0x200, &read_bin);
|
|
||||||
// println!("Loaded {}", state.selected_rom_filename);
|
|
||||||
// }
|
|
||||||
// EGuiFileList::display_path(PathBuf::from("resources/roms"), &mut state.selected_rom_filename, ui);
|
|
||||||
|
|
||||||
ui.with_layout(egui::Layout::left_to_right(Align::TOP), |ui| {
|
|
||||||
// ui.checkbox(&mut state.display_memory, "Display Memory");
|
|
||||||
// ui.checkbox(&mut state.display_video, "Display Video");
|
|
||||||
// ui.checkbox(&mut state.display_registers, "Display Registers");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn registers_view(system: &Chip8Computer, ui: &mut Ui) {
|
|
||||||
ui.label(format!(
|
|
||||||
"V0-7: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
|
|
||||||
system.registers.peek(0x00),
|
|
||||||
system.registers.peek(0x01),
|
|
||||||
system.registers.peek(0x02),
|
|
||||||
system.registers.peek(0x03),
|
|
||||||
system.registers.peek(0x04),
|
|
||||||
system.registers.peek(0x05),
|
|
||||||
system.registers.peek(0x06),
|
|
||||||
system.registers.peek(0x07)
|
|
||||||
));
|
|
||||||
ui.label(format!(
|
|
||||||
"V8-F: {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} {:02x} ",
|
|
||||||
system.registers.peek(0x08),
|
|
||||||
system.registers.peek(0x09),
|
|
||||||
system.registers.peek(0x0A),
|
|
||||||
system.registers.peek(0x0B),
|
|
||||||
system.registers.peek(0x0C),
|
|
||||||
system.registers.peek(0x0D),
|
|
||||||
system.registers.peek(0x0E),
|
|
||||||
system.registers.peek(0x0F)
|
|
||||||
));
|
|
||||||
ui.label(format!(
|
|
||||||
"PC: {:04x}\tI: {:04x}",
|
|
||||||
system.registers.peek_pc(),
|
|
||||||
system.registers.peek_i()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn video_view(system: &Chip8Computer, ui: &mut Ui) {
|
|
||||||
let (_resp, painter) = ui.allocate_painter(Vec2::new(350.0, 165.0), egui::Sense::hover());
|
|
||||||
for current_row in 0..32 {
|
|
||||||
for current_col in 0..64 {
|
|
||||||
let data_offset = current_row * 64 + current_col;
|
|
||||||
let x_offset = current_col as f32 * CELL_WIDTH;
|
|
||||||
let y_offset = current_row as f32 * CELL_HEIGHT;
|
|
||||||
let origin = Pos2::new(x_offset, y_offset);
|
|
||||||
let colour = if system.video_memory.peek(data_offset) {
|
|
||||||
Color32::RED
|
|
||||||
} else {
|
|
||||||
Color32::WHITE
|
|
||||||
};
|
|
||||||
let rect = Rect::from_min_size(origin, Vec2::new(CELL_WIDTH, CELL_HEIGHT));
|
|
||||||
painter.rect_filled(rect, 0.0, colour);
|
|
||||||
// println!("Cell {current_col}x{current_row} at {}x{} -> {}",
|
|
||||||
// origin.x, origin.y,
|
|
||||||
// system.video_memory.peek(data_offset));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// thread::sleep(Duration::from_secs(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn memory_view(system: &Chip8Computer, ui: &mut Ui) {
|
|
||||||
ui.label("Memory View");
|
|
||||||
|
|
||||||
for i in (0..=0x200).step_by(16) {
|
|
||||||
ui.with_layout(egui::Layout::left_to_right(egui::Align::TOP), |ui| {
|
|
||||||
for y in 0..16 {
|
|
||||||
ui.label(format!("{:02x}", system.memory.peek((i + y) as u16)).as_str());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ui.label("Should have **something** to adjust the 'memory window'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,2 +0,0 @@
|
|||||||
pub mod timestep;
|
|
||||||
pub mod gemma_egui_support;
|
|
||||||
@ -1,95 +0,0 @@
|
|||||||
|
|
||||||
//! Gameplay speed control. It attempts to mimic/limit progression to
|
|
||||||
//! the 30 tics per second Doom used.
|
|
||||||
|
|
||||||
use std::{fmt, time::Instant};
|
|
||||||
|
|
||||||
const MS_PER_UPDATE: f32 = 28.57;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct TimeStep {
|
|
||||||
last_time: Instant,
|
|
||||||
delta_time: f32,
|
|
||||||
frame_count: u32,
|
|
||||||
frame_time: f32,
|
|
||||||
run_tics: u32,
|
|
||||||
last_tics: u32,
|
|
||||||
lag: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FrameData {
|
|
||||||
pub tics: u32,
|
|
||||||
pub frames: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FrameData {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
f.write_fmt(format_args!(
|
|
||||||
"FrameData (per-second):\n - tics: {}\n - fps: {}",
|
|
||||||
self.tics, self.frames
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TimeStep {
|
|
||||||
pub fn new() -> TimeStep {
|
|
||||||
TimeStep {
|
|
||||||
last_time: Instant::now(),
|
|
||||||
delta_time: 0.0,
|
|
||||||
frame_count: 0,
|
|
||||||
frame_time: 0.0,
|
|
||||||
run_tics: 0,
|
|
||||||
last_tics: 0,
|
|
||||||
lag: 0.0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delta(&mut self) -> f32 {
|
|
||||||
let current_time = Instant::now();
|
|
||||||
let delta = current_time.duration_since(self.last_time).as_micros() as f32 * 0.001;
|
|
||||||
self.last_time = current_time;
|
|
||||||
self.delta_time = delta;
|
|
||||||
delta
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Increments self time and returns current lag. `run_this` is run only for
|
|
||||||
/// `n` tics available.
|
|
||||||
pub fn run_this(&mut self, mut run_this: impl FnMut(f32)) {
|
|
||||||
let dt = self.delta();
|
|
||||||
self.lag += dt;
|
|
||||||
while self.lag >= MS_PER_UPDATE {
|
|
||||||
run_this(dt);
|
|
||||||
self.lag -= MS_PER_UPDATE;
|
|
||||||
self.run_tics += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn frame_rate(&mut self) -> Option<FrameData> {
|
|
||||||
self.frame_count += 1;
|
|
||||||
self.frame_time += self.delta_time;
|
|
||||||
let tmp;
|
|
||||||
let tmp2;
|
|
||||||
// per second
|
|
||||||
if self.frame_time >= 1000.0 {
|
|
||||||
tmp = self.frame_count;
|
|
||||||
tmp2 = self.last_tics;
|
|
||||||
self.frame_count = 0;
|
|
||||||
self.frame_time = 0.0;
|
|
||||||
self.last_tics = self.run_tics;
|
|
||||||
return Some(FrameData {
|
|
||||||
tics: self.run_tics - tmp2,
|
|
||||||
frames: tmp,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TimeStep {
|
|
||||||
// shutup clippy!
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user