use std::net::Ipv6Addr; use log::Level; use pollster::FutureExt; use smol::channel::{Receiver, Sender}; use smol::io::AsyncWriteExt; use smol::net::{TcpListener, TcpStream}; pub struct Console { tcp_socket: TcpStream, messages: Receiver, sender: Sender, } pub struct ConsoleLogger(Sender); pub enum ConsoleMessage { RuntimeLog { message: String, level: Level, file: String, line: u32, }, ScriptLog { message: String, level: Level, file: String, line: u32, }, FrameTime { timestamp: i64, }, ScopeStart { scope_name: String, timestamp: i64, }, ScopeEnd { timestamp: i64, }, } impl Console { pub async fn new(port: u16) -> Result { let tcp_listener = TcpListener::bind((Ipv6Addr::LOCALHOST, port)).await?; let tcp_socket = tcp_listener.accept().await?.0; let (sender, reciever) = smol::channel::unbounded(); Ok(Self { tcp_socket, messages: reciever, sender, }) } fn sender(&self) -> Sender { self.sender.clone() } pub fn logger(&self) -> ConsoleLogger { ConsoleLogger(self.sender()) } pub async fn flush(&mut self) -> std::io::Result<()> { while let Ok(message) = self.messages.try_recv() { match message { ConsoleMessage::RuntimeLog { message, level, file, line, } => { let message = message.replace('\n', "\\n"); let msg = format!("runtimelog {level} {file}:{line} {message}\n"); self.tcp_socket.write(msg.as_bytes()).await?; } ConsoleMessage::ScriptLog { message, level, file, line, } => { let message = message.replace('\n', "\\n"); let msg = format!("scriptlog {level} {file}:{line} {message}\n"); self.tcp_socket.write(msg.as_bytes()).await?; } ConsoleMessage::FrameTime { timestamp: unix_timestamp, } => { let msg = format!("frametime {unix_timestamp}"); self.tcp_socket.write(msg.as_bytes()).await?; } ConsoleMessage::ScopeStart { scope_name, timestamp: unix_timestamp, } => { let msg = format!("scopestart {scope_name} {unix_timestamp}"); self.tcp_socket.write(msg.as_bytes()).await?; } ConsoleMessage::ScopeEnd { timestamp: unix_timestamp, } => { let msg = format!("scopeend {unix_timestamp}"); self.tcp_socket.write(msg.as_bytes()).await?; } } } self.tcp_socket.flush().await?; Ok(()) } } impl ConsoleLogger { pub fn send(&self, message: ConsoleMessage) { _ = self.0.send(message).block_on(); } } impl log::Log for ConsoleLogger { fn enabled(&self, _: &log::Metadata) -> bool { true } fn log(&self, record: &log::Record) { let _ = self .0 .send(ConsoleMessage::RuntimeLog { message: record.args().to_string(), level: record.level(), file: record.file().map(str::to_string).unwrap_or_default(), line: record.line().unwrap_or_default(), }) .block_on(); } fn flush(&self) {} }