Create tcp server
This commit is contained in:
+6
-1
@@ -18,7 +18,7 @@ use crate::{
|
||||
emulator::{EmulatorClient, EmulatorCommand, SimId},
|
||||
input::MappingProvider,
|
||||
persistence::Persistence,
|
||||
window::{AboutWindow, AppWindow, GameWindow, InputWindow},
|
||||
window::{AboutWindow, AppWindow, GameWindow, GdbServerWindow, InputWindow},
|
||||
};
|
||||
|
||||
fn load_icon() -> anyhow::Result<IconData> {
|
||||
@@ -183,6 +183,10 @@ impl ApplicationHandler<UserEvent> for Application {
|
||||
let about = AboutWindow;
|
||||
self.open(event_loop, Box::new(about));
|
||||
}
|
||||
UserEvent::OpenDebugger(sim_id) => {
|
||||
let debugger = GdbServerWindow::new(sim_id, self.client.clone());
|
||||
self.open(event_loop, Box::new(debugger));
|
||||
}
|
||||
UserEvent::OpenInput => {
|
||||
let input = InputWindow::new(self.mappings.clone());
|
||||
self.open(event_loop, Box::new(input));
|
||||
@@ -374,6 +378,7 @@ impl Drop for Viewport {
|
||||
pub enum UserEvent {
|
||||
GamepadEvent(gilrs::Event),
|
||||
OpenAbout,
|
||||
OpenDebugger(SimId),
|
||||
OpenInput,
|
||||
OpenPlayer2,
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
fmt::Display,
|
||||
fs::{self, File},
|
||||
io::{Read, Seek, SeekFrom, Write},
|
||||
path::{Path, PathBuf},
|
||||
@@ -45,6 +46,14 @@ impl SimId {
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Display for SimId {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(match self {
|
||||
Self::Player1 => "Player 1",
|
||||
Self::Player2 => "Player 2",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct Cart {
|
||||
rom_path: PathBuf,
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
use std::{
|
||||
error::Error,
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt as _, BufReader, BufWriter},
|
||||
net::{
|
||||
tcp::{OwnedReadHalf, OwnedWriteHalf},
|
||||
TcpListener, TcpStream,
|
||||
},
|
||||
select,
|
||||
sync::oneshot,
|
||||
};
|
||||
|
||||
use crate::emulator::{EmulatorClient, EmulatorCommand, SimId};
|
||||
|
||||
pub struct GdbServer {
|
||||
sim_id: SimId,
|
||||
client: EmulatorClient,
|
||||
status: Arc<Mutex<GdbServerStatus>>,
|
||||
killer: Option<oneshot::Sender<()>>,
|
||||
}
|
||||
|
||||
impl GdbServer {
|
||||
pub fn new(sim_id: SimId, client: EmulatorClient) -> Self {
|
||||
Self {
|
||||
sim_id,
|
||||
client,
|
||||
status: Arc::new(Mutex::new(GdbServerStatus::Stopped)),
|
||||
killer: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn status(&self) -> GdbServerStatus {
|
||||
self.status.lock().unwrap().clone()
|
||||
}
|
||||
|
||||
pub fn start(&mut self, port: u16) {
|
||||
let sim_id = self.sim_id;
|
||||
let client = self.client.clone();
|
||||
let status = self.status.clone();
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.killer = Some(tx);
|
||||
thread::spawn(move || {
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async move {
|
||||
select! {
|
||||
_ = run_server(sim_id, client, port, &status) => {}
|
||||
_ = rx => {
|
||||
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn stop(&mut self) {
|
||||
if let Some(killer) = self.killer.take() {
|
||||
let _ = killer.send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn run_server(
|
||||
sim_id: SimId,
|
||||
client: EmulatorClient,
|
||||
port: u16,
|
||||
status: &Mutex<GdbServerStatus>,
|
||||
) {
|
||||
client.send_command(EmulatorCommand::Pause);
|
||||
let Some(stream) = try_connect(port, status).await else {
|
||||
return;
|
||||
};
|
||||
let connection = GdbConnection::new(sim_id, client, stream);
|
||||
match connection.run().await {
|
||||
Ok(()) => {
|
||||
*status.lock().unwrap() = GdbServerStatus::Stopped;
|
||||
}
|
||||
Err(error) => {
|
||||
*status.lock().unwrap() = GdbServerStatus::Error(error.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn try_connect(port: u16, status: &Mutex<GdbServerStatus>) -> Option<TcpStream> {
|
||||
*status.lock().unwrap() = GdbServerStatus::Connecting;
|
||||
let listener = match TcpListener::bind(("127.0.0.1", port)).await {
|
||||
Ok(l) => l,
|
||||
Err(err) => {
|
||||
*status.lock().unwrap() = GdbServerStatus::Error(err.to_string());
|
||||
return None;
|
||||
}
|
||||
};
|
||||
match listener.accept().await {
|
||||
Ok((stream, _)) => {
|
||||
*status.lock().unwrap() = GdbServerStatus::Running;
|
||||
Some(stream)
|
||||
}
|
||||
Err(err) => {
|
||||
*status.lock().unwrap() = GdbServerStatus::Error(err.to_string());
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum GdbServerStatus {
|
||||
Stopped,
|
||||
Connecting,
|
||||
Running,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl GdbServerStatus {
|
||||
pub fn running(&self) -> bool {
|
||||
matches!(self, Self::Connecting | Self::Running)
|
||||
}
|
||||
}
|
||||
|
||||
struct GdbConnection {
|
||||
sim_id: SimId,
|
||||
client: EmulatorClient,
|
||||
stream_in: BufReader<OwnedReadHalf>,
|
||||
stream_out: BufWriter<OwnedWriteHalf>,
|
||||
}
|
||||
|
||||
impl GdbConnection {
|
||||
fn new(sim_id: SimId, client: EmulatorClient, stream: TcpStream) -> Self {
|
||||
let (rx, tx) = stream.into_split();
|
||||
Self {
|
||||
sim_id,
|
||||
client,
|
||||
stream_in: BufReader::new(rx),
|
||||
stream_out: BufWriter::new(tx),
|
||||
}
|
||||
}
|
||||
async fn run(mut self) -> Result<(), Box<dyn Error>> {
|
||||
println!("Connected for {}", self.sim_id);
|
||||
self.client.send_command(EmulatorCommand::Resume);
|
||||
loop {
|
||||
let byte = self.read_byte().await?;
|
||||
println!("{byte}");
|
||||
self.stream_out.write_u8(byte).await?;
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_byte(&mut self) -> std::io::Result<u8> {
|
||||
self.stream_in.read_u8().await
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ mod app;
|
||||
mod audio;
|
||||
mod controller;
|
||||
mod emulator;
|
||||
mod gdbserver;
|
||||
mod graphics;
|
||||
mod input;
|
||||
mod persistence;
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
pub use about::AboutWindow;
|
||||
use egui::{Context, ViewportBuilder, ViewportId};
|
||||
pub use game::GameWindow;
|
||||
pub use gdb::GdbServerWindow;
|
||||
pub use input::InputWindow;
|
||||
use winit::event::KeyEvent;
|
||||
|
||||
mod about;
|
||||
mod game;
|
||||
mod game_screen;
|
||||
mod gdb;
|
||||
mod input;
|
||||
|
||||
pub trait AppWindow {
|
||||
|
||||
@@ -121,6 +121,14 @@ impl GameWindow {
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.menu_button("Tools", |ui| {
|
||||
if ui.button("GDB Server").clicked() {
|
||||
self.proxy
|
||||
.send_event(UserEvent::OpenDebugger(self.sim_id))
|
||||
.unwrap();
|
||||
ui.close_menu();
|
||||
}
|
||||
});
|
||||
ui.menu_button("About", |ui| {
|
||||
self.proxy.send_event(UserEvent::OpenAbout).unwrap();
|
||||
ui.close_menu();
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
use egui::{Button, CentralPanel, TextEdit, ViewportBuilder, ViewportId};
|
||||
|
||||
use crate::{
|
||||
emulator::{EmulatorClient, SimId},
|
||||
gdbserver::{GdbServer, GdbServerStatus},
|
||||
};
|
||||
|
||||
use super::AppWindow;
|
||||
|
||||
pub struct GdbServerWindow {
|
||||
sim_id: SimId,
|
||||
port_str: String,
|
||||
server: GdbServer,
|
||||
}
|
||||
|
||||
impl GdbServerWindow {
|
||||
pub fn new(sim_id: SimId, client: EmulatorClient) -> Self {
|
||||
Self {
|
||||
sim_id,
|
||||
port_str: (8080 + sim_id.to_index()).to_string(),
|
||||
server: GdbServer::new(sim_id, client),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppWindow for GdbServerWindow {
|
||||
fn viewport_id(&self) -> ViewportId {
|
||||
ViewportId::from_hash_of(format!("Debugger-{}", self.sim_id))
|
||||
}
|
||||
|
||||
fn initial_viewport(&self) -> ViewportBuilder {
|
||||
ViewportBuilder::default()
|
||||
.with_title(format!("GDB Server ({})", self.sim_id))
|
||||
.with_inner_size((300.0, 200.0))
|
||||
}
|
||||
|
||||
fn show(&mut self, ctx: &egui::Context) {
|
||||
let port_num: Option<u16> = self.port_str.parse().ok();
|
||||
let status = self.server.status();
|
||||
CentralPanel::default().show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
if port_num.is_none() {
|
||||
let style = ui.style_mut();
|
||||
let error = style.visuals.error_fg_color;
|
||||
style.visuals.widgets.active.bg_stroke.color = error;
|
||||
style.visuals.widgets.hovered.bg_stroke.color = error;
|
||||
}
|
||||
ui.label("Port");
|
||||
let port_editor = TextEdit::singleline(&mut self.port_str).desired_width(100.0);
|
||||
ui.add_enabled(!status.running(), port_editor);
|
||||
});
|
||||
|
||||
if !status.running() {
|
||||
let start_button = Button::new("Start");
|
||||
if ui.add_enabled(port_num.is_some(), start_button).clicked() {
|
||||
let port = port_num.unwrap();
|
||||
self.server.start(port);
|
||||
}
|
||||
} else {
|
||||
let stop_button = Button::new("Stop");
|
||||
if ui.add(stop_button).clicked() {
|
||||
self.server.stop();
|
||||
}
|
||||
}
|
||||
|
||||
match &status {
|
||||
GdbServerStatus::Stopped => {}
|
||||
GdbServerStatus::Connecting => {
|
||||
ui.label("Connecting...");
|
||||
}
|
||||
GdbServerStatus::Running => {
|
||||
ui.label("Running");
|
||||
}
|
||||
GdbServerStatus::Error(message) => {
|
||||
ui.label(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user