Hello everyone,
I am currently developing a rust application, and I need some advice how I can achieve the following, while keeping the code organized. The application uses egui for its user interface, the module that takes care of setting up all the UI widgets is called AppUI.
When AppUI is initialized, I am creating a new EventLogger instance, as well as a new ImageProcessingPipeline instance.
ui.rs
pub struct AppUI {
logger: EventLogger,
image_pipeline: ImageProcessingPipeline,
// More fields ...
}
impl AppUI {
pub fn new(ctx: egui::Context) -> Self {
let logger = EventLogger::new(ctx);
let image_pipline = ImageProcessingPipeline::new(&logger);
Self {
// More fields ...
logger,
image_pipeline: image_pipline,
}
}
}
The logger is supposed to be accessible to various other modules, such as the ImageProcessingPipeline, which can add new logs to the logger.
image_pipeline.rs
use crate::logging::EventLogger;
pub struct ImageProcessingPipeline<'a> {
logger: &'a EventLogger,
}
impl<'a> ImageProcessingPipeline<'a> {
pub fn new(logger: &EventLogger) -> Self {
Self { logger }
}
pub fn process(&self) {
// I want to be able to log here like this, as well as in other modules ...
self.logger
.log(crate::logging::EventType::INFO, "Processing ...");
}
}
logging.rs
use chrono::Local;
use egui::{Color32, RichText};
pub struct EventLogger {
log_messages: Vec<LogEntry>,
ctx: egui::Context,
}
impl EventLogger {
pub fn new(ctx: egui::Context) -> Self {
Self {
ctx,
log_messages: vec![],
}
}
pub fn log(&mut self, event_type: EventType, event_message: &str) {
self.log_messages
.push(LogEntry::new(event_type, event_message));
self.ctx.request_repaint();
}
pub fn get_logs(&self) -> &Vec<LogEntry> {
&self.log_messages
}
}
pub enum EventType {
FAIL,
WARN,
PASS,
INFO,
}
pub struct LogEntry {
log_type: EventType,
log_time: String,
log_message: String,
}
impl LogEntry {
fn new(log_type: EventType, log_message: &str) -> Self {
Self {
log_type,
log_time: Local::now().format("%I:%M:%S %p").to_string(),
log_message: log_message.to_owned(),
}
}
pub fn get_log_type(&self) -> RichText {
match self.log_type {
EventType::FAIL => RichText::new("[FAIL]").color(Color32::RED),
EventType::WARN => RichText::new("[WARN]").color(Color32::YELLOW),
EventType::PASS => RichText::new("[PASS]").color(Color32::GREEN),
EventType::INFO => RichText::new("[INFO]").color(Color32::BLUE),
}
.into()
}
pub fn get_log_time(&self) -> &String {
&self.log_time
}
pub fn get_log_message(&self) -> &String {
&self.log_message
}
}
The idea here is that the pipeline runs multiple, long-running tasks, and I want to be able to populate the logger from all these tasks.
However, I am not sure what is the best way to make the logger available to the pipeline, and also, the indivudal pipeline tasks.
I don't think, cloning the logger would work, because then I have a different instance I suppose? I think, in that case, the logger instance in the ui thread would not contain the same data as a clone?
However, when I want to pass the logger as reference to the ImageProcessingPipeline module, I am running into lifetime issues.
Can someone guide me a bit, how I should approach this ideally?
Maybe there is a better concept all together for this?