summaryrefslogtreecommitdiff
path: root/console/src/lib.rs
blob: d98183c776e7f68294bb46e366989154b1a3d799 (plain)
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<ConsoleMessage>,
	sender: Sender<ConsoleMessage>,
}

pub struct ConsoleLogger(Sender<ConsoleMessage>);

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<Self, std::io::Error> {
		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<ConsoleMessage> {
		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) {}
}