Terminal #5

Merged
SonicSwordcane merged 4 commits from terminal into main 2025-05-31 04:47:17 +00:00
3 changed files with 80 additions and 18 deletions
Showing only changes of commit a2a5884a2a - Show all commits

View File

@ -169,6 +169,7 @@ pub struct Emulator {
renderers: HashMap<SimId, TextureSink>,
messages: HashMap<SimId, mpsc::Sender<Toast>>,
debuggers: HashMap<SimId, DebugInfo>,
stdouts: HashMap<SimId, mpsc::Sender<String>>,
watched_regions: HashMap<MemoryRange, Weak<MemoryRegion>>,
eye_contents: Vec<u8>,
audio_samples: Vec<f32>,
@ -195,6 +196,7 @@ impl Emulator {
renderers: HashMap::new(),
messages: HashMap::new(),
debuggers: HashMap::new(),
stdouts: HashMap::new(),
watched_regions: HashMap::new(),
eye_contents: vec![0u8; 384 * 224 * 2],
audio_samples: Vec::with_capacity(EXPECTED_FRAME_SIZE),
@ -471,6 +473,20 @@ impl Emulator {
self.state.store(EmulatorState::Paused, Ordering::Release);
}
// stdout
self.stdouts.retain(|sim_id, stdout| {
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
return false;
};
if let Some(text) = sim.take_stdout() {
if stdout.send(text).is_err() {
sim.watch_stdout(false);
return false;
}
}
true
});
// Debug state
if state == EmulatorState::Debugging {
for sim_id in SimId::values() {
@ -645,9 +661,12 @@ impl Emulator {
};
sim.remove_watchpoint(address, length, watch);
}
EmulatorCommand::ConnectTerminal(sim_id, terminal_sink) => {
// TODO
let _ = (sim_id, terminal_sink);
EmulatorCommand::WatchStdout(sim_id, stdout_sink) => {
self.stdouts.insert(sim_id, stdout_sink);
let Some(sim) = self.sims.get_mut(sim_id.to_index()) else {
return;
};
sim.watch_stdout(true);
}
EmulatorCommand::SetAudioEnabled(p1, p2) => {
self.audio_on[SimId::Player1.to_index()].store(p1, Ordering::Release);
@ -722,7 +741,7 @@ pub enum EmulatorCommand {
RemoveBreakpoint(SimId, u32),
AddWatchpoint(SimId, u32, usize, VBWatchpointType),
RemoveWatchpoint(SimId, u32, usize, VBWatchpointType),
ConnectTerminal(SimId, mpsc::Sender<String>),
WatchStdout(SimId, mpsc::Sender<String>),
SetAudioEnabled(bool, bool),
Link,
Unlink,

View File

@ -230,7 +230,7 @@ extern "C" fn on_write(
sim: *mut VB,
address: u32,
_type: VBDataType,
_value: *mut i32,
value: *mut i32,
_cycles: *mut u32,
_cancel: *mut c_int,
) -> c_int {
@ -238,6 +238,14 @@ extern "C" fn on_write(
// There is no way for the userdata to be null or otherwise invalid.
let data: &mut VBState = unsafe { &mut *vb_get_user_data(sim).cast() };
// If we're monitoring stdout, track this write
if let Some(stdout) = data.stdout.as_mut() {
let normalized_hw_address = address & 0x0700003f;
if normalized_hw_address == 0x02000030 {
stdout.push(unsafe { *value } as u8);
}
}
if let Some(start) = data.write_watchpoints.start_of_range_containing(address) {
let watch = if data.read_watchpoints.contains(address) {
VBWatchpointType::Access
@ -263,6 +271,7 @@ struct VBState {
breakpoints: Vec<u32>,
read_watchpoints: AddressSet,
write_watchpoints: AddressSet,
stdout: Option<Vec<u8>>,
}
impl VBState {
@ -306,6 +315,7 @@ impl Sim {
breakpoints: vec![],
read_watchpoints: AddressSet::new(),
write_watchpoints: AddressSet::new(),
stdout: None,
};
unsafe { vb_set_user_data(sim, Box::into_raw(Box::new(state)).cast()) };
unsafe { vb_set_frame_callback(sim, Some(on_frame)) };
@ -564,7 +574,9 @@ impl Sim {
state.write_watchpoints.remove(address, length);
let needs_execute = state.needs_execute_callback();
if state.write_watchpoints.is_empty() {
unsafe { vb_set_write_callback(self.sim, None) };
if state.stdout.is_none() {
unsafe { vb_set_write_callback(self.sim, None) };
}
if !needs_execute {
unsafe { vb_set_execute_callback(self.sim, None) };
}
@ -586,11 +598,40 @@ impl Sim {
data.breakpoints.clear();
data.read_watchpoints.clear();
data.write_watchpoints.clear();
let needs_write = data.stdout.is_some();
unsafe { vb_set_read_callback(self.sim, None) };
unsafe { vb_set_write_callback(self.sim, None) };
if !needs_write {
unsafe { vb_set_write_callback(self.sim, None) };
}
unsafe { vb_set_execute_callback(self.sim, None) };
}
pub fn watch_stdout(&mut self, watch: bool) {
let data = self.get_state();
if watch {
if data.stdout.is_none() {
data.stdout = Some(vec![]);
unsafe { vb_set_write_callback(self.sim, Some(on_write)) };
}
} else {
data.stdout.take();
if data.write_watchpoints.is_empty() {
unsafe { vb_set_write_callback(self.sim, None) };
}
}
}
pub fn take_stdout(&mut self) -> Option<String> {
let data = self.get_state();
let stdout = data.stdout.take()?;
let string = match String::from_utf8(stdout) {
Ok(str) => str,
Err(err) => String::from_utf8_lossy(err.as_bytes()).into_owned(),
};
data.stdout = Some(vec![]);
Some(string)
}
pub fn stop_reason(&mut self) -> Option<StopReason> {
let data = self.get_state();
let reason = data.stop_reason.take();

View File

@ -1,7 +1,8 @@
use std::sync::mpsc;
use egui::{
CentralPanel, Context, FontFamily, Label, RichText, ScrollArea, ViewportBuilder, ViewportId,
Align, CentralPanel, Context, FontFamily, Label, RichText, ScrollArea, ViewportBuilder,
ViewportId,
};
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
@ -14,19 +15,14 @@ pub struct TerminalWindow {
text: String,
}
const TEXT: &str = "
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Curabitur pretium tincidunt lacus. Nulla gravida orci a odio. Nullam various, turpis et commodo pharetra, est eros bibendum elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, feugiat in, orci. In hac habitasse platea dictumst.";
impl TerminalWindow {
pub fn new(sim_id: SimId, client: &EmulatorClient) -> Self {
let (sender, receiver) = mpsc::channel();
client.send_command(EmulatorCommand::ConnectTerminal(sim_id, sender));
client.send_command(EmulatorCommand::WatchStdout(sim_id, sender));
Self {
sim_id,
receiver,
text: TEXT.to_string(),
text: String::new(),
}
}
}
@ -51,9 +47,15 @@ impl AppWindow for TerminalWindow {
self.text.push_str(&text);
}
CentralPanel::default().show(ctx, |ui| {
ScrollArea::vertical().show(ui, |ui| {
ui.add(Label::new(RichText::new(&self.text).family(FontFamily::Monospace)).wrap());
});
ScrollArea::vertical()
.stick_to_bottom(true)
.auto_shrink([false, false])
.show(ui, |ui| {
let label = Label::new(RichText::new(&self.text).family(FontFamily::Monospace))
.halign(Align::LEFT)
.wrap();
ui.add(label);
});
});
}
}