use std::io::{stdout, Write}; use std::sync::mpsc::{Receiver, Sender}; use log::Level; pub struct Console { 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 (sender, reciever) = std::sync::mpsc::channel(); Ok(Self { messages: reciever, sender, }) } fn sender(&self) -> Sender { self.sender.clone() } pub fn logger(&self) -> ConsoleLogger { ConsoleLogger(self.sender()) } pub fn flush(&self) -> std::io::Result<()> { let mut stdout = stdout().lock(); while let Ok(message) = self.messages.try_recv() { let message = match message { ConsoleMessage::RuntimeLog { message, level, file, line, } => { let message = message.replace('\n', "\\n"); format!("runtimelog {level} {file}:{line} {message}\n") } ConsoleMessage::ScriptLog { message, level, file, line, } => { let message = message.replace('\n', "\\n"); format!("scriptlog {level} {file}:{line} {message}\n") } ConsoleMessage::FrameTime { timestamp: unix_timestamp, } => { format!("frametime {unix_timestamp}") } ConsoleMessage::ScopeStart { scope_name, timestamp: unix_timestamp, } => { format!("scopestart {scope_name} {unix_timestamp}") } ConsoleMessage::ScopeEnd { timestamp: unix_timestamp, } => { format!("scopeend {unix_timestamp}") } }; stdout.write_all(message.as_bytes())?; stdout.write_all(b"\n")?; } stdout.flush()?; Ok(()) } } impl ConsoleLogger { pub fn send(&self, message: ConsoleMessage) { _ = self.0.send(message); } } 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(), }); } fn flush(&self) {} }