pixel_engine_console 0.1.0

An extension for the pixel_engine game engine that add an in-game console
Documentation
pub fn install(
    buffer_size: usize,
    pass_through: (&'static dyn log::Log, log::LevelFilter),
    line_size: usize,
) -> Result<(), log::SetLoggerError> {
    log::set_boxed_logger(Box::new(ConsoleLogger::new(
        buffer_size,
        line_size,
        pass_through,
    )))
    .map(|()| HAS_CONSOLE_LOGGER.store(true, std::sync::atomic::Ordering::SeqCst))
}
pub(crate) static HAS_CONSOLE_LOGGER: std::sync::atomic::AtomicBool =
    std::sync::atomic::AtomicBool::new(false);

#[derive(Clone)]
pub struct ConsoleLogger {
    pass_through: &'static dyn log::Log,
    line_size: usize,
    pub(crate) inner_buffer:
        std::sync::Arc<std::sync::RwLock<std::collections::VecDeque<LeveledMessage>>>,
}

impl std::fmt::Debug for ConsoleLogger {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.debug_struct("ConsoleLogger")
            .field("line_size", &self.line_size)
            .field("inner_buffer", &self.inner_buffer)
            .field("pass_through", &"&'static dyn log::Log")
            .finish()
    }
}

impl ConsoleLogger {
    pub fn new(
        buffer_size: usize,
        line_size: usize,
        pass_through: (&'static dyn log::Log, log::LevelFilter),
    ) -> ConsoleLogger {
        log::set_max_level(std::cmp::max(pass_through.1, CONSOLE_LOG_LEVEL));
        Self {
            pass_through: pass_through.0,
            line_size,
            inner_buffer: std::sync::Arc::from(std::sync::RwLock::from(
                std::collections::VecDeque::with_capacity(buffer_size),
            )),
        }
    }
}
#[derive(Clone, Debug)]
pub(crate) struct LeveledMessage {
    pub level: log::Level,
    pub message: String,
}

#[cfg(feature = "off")]
const CONSOLE_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Off;
#[cfg(all(feature = "error", not(feature = "off")))]
const CONSOLE_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Error;
#[cfg(all(feature = "warn", not(feature = "off")))]
const CONSOLE_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Warn;
#[cfg(all(feature = "info", not(feature = "off")))]
const CONSOLE_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Info;
#[cfg(all(feature = "debug", not(feature = "off")))]
const CONSOLE_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Debug;
#[cfg(all(feature = "trace", not(feature = "off")))]
const CONSOLE_LOG_LEVEL: log::LevelFilter = log::LevelFilter::Trace;

impl log::Log for ConsoleLogger {
    fn log(&self, record: &log::Record) {
        if record.metadata().target() == "console" && record.level() <= CONSOLE_LOG_LEVEL {
            let message = record.args().to_string();
            let message_wrapped = textwrap::wrap(&message, self.line_size)
                .into_iter()
                .map(std::borrow::Cow::into_owned);
            let mut write_lock = self.inner_buffer.write().unwrap();
            for m in message_wrapped {
                if write_lock.len() + 1 > write_lock.capacity() {
                    write_lock.pop_front();
                }
                write_lock.push_front(LeveledMessage {
                    level: record.level(),
                    message: m,
                });
            }
        } else {
            self.pass_through.log(record);
        }
    }

    fn flush(&self) {}
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        metadata.level() >= CONSOLE_LOG_LEVEL || self.pass_through.enabled(metadata)
    }
}