diff --git a/src/lib.rs b/src/lib.rs index 72e6c69f..d0d86d4f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1376,6 +1376,8 @@ fn main_thread(event_loop: EventLoopProxy, state: State, reciever: Re let mut script = script::ScriptContext::new(None, script_reload_send.clone()) .expect("Unable to create initial script context"); + let _debugger = script::debugger::start_debugger(); + const SPF: f64 = 1.0 / 60.0; loop { frame_mark(); diff --git a/src/script.rs b/src/script.rs index ac9905b4..81b9faf9 100644 --- a/src/script.rs +++ b/src/script.rs @@ -11,6 +11,7 @@ use std::time::Instant; use tracy_client::span; use winit::event::*; +pub mod debugger; pub mod graphics; mod input; mod io; diff --git a/src/script/debugger.rs b/src/script/debugger.rs new file mode 100644 index 00000000..4f5a5c3a --- /dev/null +++ b/src/script/debugger.rs @@ -0,0 +1,139 @@ +use std::collections::HashMap; +use std::io; +use std::io::{BufRead, BufReader, Write}; +use std::net::{TcpListener, TcpStream}; +use std::thread; + +pub struct Debugger { + _thread: thread::JoinHandle<()>, +} + +fn write_http_response( + stream: &mut TcpStream, + code: u16, + phrase: &str, + content_type: &str, + data: &[u8], +) -> io::Result<()> { + let length = data.len(); + let buffer = format!( + "HTTP/1.1 {code} {phrase}\r\n\ + content-length: {length}\r\n\ + content-type: {content_type}\r\n\ + \r\n" + ); + stream.write_all(buffer.as_bytes())?; + stream.write_all(data) +} + +fn write_http_error(stream: &mut TcpStream, code: u16, phrase: &str) -> io::Result<()> { + write_http_response(stream, code, phrase, "text/plain", phrase.as_bytes()) +} + +fn write_http_ok(stream: &mut TcpStream, content_type: &str, data: &[u8]) -> io::Result<()> { + write_http_response(stream, 200, "OK", content_type, data) +} + +fn handle_connection(mut stream: TcpStream) -> io::Result<()> { + let mut buf_reader = BufReader::new(&mut stream); + + let mut buffer = String::new(); + buf_reader.read_line(&mut buffer)?; + + let parts: Vec<_> = buffer.trim().split(" ").collect(); + if parts.len() != 3 { + eprintln!("Invalid request line: {buffer}"); + write_http_error(&mut stream, 400, "Invalid request")?; + return Ok(()); + } + + let method = parts[0]; + let path = parts[1]; + eprintln!("Debugger: {method} {path}"); + + let mut headers = HashMap::new(); + let mut header_line_buffer = String::new(); + loop { + header_line_buffer.clear(); + buf_reader.read_line(&mut header_line_buffer)?; + let header_line = header_line_buffer.trim(); + if header_line.is_empty() { + break; + } + + let sep_idx = match header_line.find(":") { + Some(idx) => idx, + None => { + write_http_error(&mut stream, 400, "Invalid request")?; + return Ok(()); + } + }; + let header = &header_line[0..sep_idx]; + let value = &header_line[sep_idx + 1..]; + eprintln!("HEADER: {header} {value}"); + headers.insert(header.trim().to_string(), value.trim().to_string()); + } + + if method == "GET" { + if path == "/json/version" { + // NOTE: Deno spits out a V8 version here but we're not running v8. + write_http_ok( + &mut stream, + "application/json", + "{\"Browser\": \"Oden/0.0.1\", \"Protocol-Version\": \"1.3\"}".as_bytes(), + )?; + } else if path == "/json" || path == "/json/list" { + // [ + // { + // "description": "deno", + // "devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/ws/01b9bd23-8810-43ed-86f7-5feef5d120fa&experiments=true&v8only=true", + // "faviconUrl": "https://deno.land/favicon.ico", + // "id": "01b9bd23-8810-43ed-86f7-5feef5d120fa", + // "title": "deno - main [pid: 40483]", + // "type": "node", + // "url": "file:///Users/doty/src/ioniq/status.ts", + // "webSocketDebuggerUrl": "ws://127.0.0.1:9229/ws/01b9bd23-8810-43ed-86f7-5feef5d120fa" + // } + // ] + } else if path == "/ws/dd5cfe85-f0b1-4241-a643-8ed81e436188" { + // I don't feel like making a new guid for each thing. + // Websocket upgrade and then debugger messages. + } else { + write_http_error(&mut stream, 404, "Not Found")?; + } + } else { + write_http_error(&mut stream, 404, "Not Found")?; + } + Ok(()) +} + +pub fn debugger_listener() -> io::Result<()> { + let listener = TcpListener::bind("127.0.0.1:0")?; + let port = listener.local_addr()?.port(); + println!("Debugger listening on http://127.0.0.1:{port}"); + for stream in listener.incoming() { + let stream = match stream { + Ok(s) => s, + Err(e) => { + eprintln!("Error accepting incoming connection: {:?}", e); + continue; + } + }; + + // TODO: Thread pool extraction here. + if let Err(e) = handle_connection(stream) { + eprintln!("Error handling incoming connection: {e}"); + } + } + Ok(()) +} + +#[allow(dead_code)] +pub fn start_debugger() -> Debugger { + let thread = thread::spawn(move || { + if let Err(e) = debugger_listener() { + eprintln!("ERROR STARTING DEBUGGER: {:?}", e); + } + }); + Debugger { _thread: thread } +}