use std::time::SystemTime; /// The severity of a message #[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] pub enum MessageLevel { /// Rarely sent information related to the progress, not to be confused with the progress itself Info, /// Used to indicate that a task has failed, along with the reason Failure, /// Indicates a task was completed successfully Success, } /// A message to be stored along with the progress tree. /// /// It is created by [`Tree::message(…)`](./struct.Item.html#method.message). #[derive(Debug, Clone, Eq, PartialEq)] pub struct Message { /// The time at which the message was sent. pub time: SystemTime, /// The severity of the message pub level: MessageLevel, /// The name of the task that created the `Message` pub origin: String, /// The message itself pub message: String, } /// A ring buffer for messages. #[derive(Debug, Clone, Eq, PartialEq)] pub struct MessageRingBuffer { pub(crate) buf: Vec, cursor: usize, total: usize, } impl MessageRingBuffer { /// Create a new instance the ability to hold `capacity` amount of messages. pub fn with_capacity(capacity: usize) -> MessageRingBuffer { MessageRingBuffer { buf: Vec::with_capacity(capacity), cursor: 0, total: 0, } } /// Push a `message` from `origin` at severity `level` into the buffer, possibly overwriting the last message added. pub fn push_overwrite(&mut self, level: MessageLevel, origin: String, message: impl Into) { let msg = Message { time: SystemTime::now(), level, origin, message: message.into(), }; if self.has_capacity() { self.buf.push(msg) } else { self.buf[self.cursor] = msg; self.cursor = (self.cursor + 1) % self.buf.len(); } self.total = self.total.wrapping_add(1); } /// Copy all messages currently contained in the buffer to `out`. pub fn copy_all(&self, out: &mut Vec) { out.clear(); if self.buf.is_empty() { return; } out.extend_from_slice(&self.buf[self.cursor % self.buf.len()..]); if self.cursor != self.buf.len() { out.extend_from_slice(&self.buf[..self.cursor]); } } /// Copy all new messages into `out` that where received since the last time this method was called provided /// its `previous` return value. pub fn copy_new(&self, out: &mut Vec, previous: Option) -> MessageCopyState { out.clear(); match previous { Some(MessageCopyState { cursor, buf_len, total }) => { if self.total.saturating_sub(total) >= self.buf.capacity() { self.copy_all(out); } else { let new_elements_below_cap = self.buf.len().saturating_sub(buf_len); let cursor_ofs: isize = self.cursor as isize - cursor as isize; match cursor_ofs { // there was some capacity left without wrapping around c if c == 0 => { out.extend_from_slice(&self.buf[self.buf.len() - new_elements_below_cap..]); } // cursor advanced c if c > 0 => { out.extend_from_slice(&self.buf[(cursor % self.buf.len())..self.cursor]); } // cursor wrapped around c if c < 0 => { out.extend_from_slice(&self.buf[(cursor % self.buf.len())..]); out.extend_from_slice(&self.buf[..self.cursor]); } _ => unreachable!("logic dictates that… yeah, you really shouldn't ever see this!"), } } } None => self.copy_all(out), }; MessageCopyState { cursor: self.cursor, buf_len: self.buf.len(), total: self.total, } } fn has_capacity(&self) -> bool { self.buf.len() < self.buf.capacity() } } /// State used to keep track of what's new since the last time message were copied. /// /// Note that due to the nature of a ring buffer, there is no guarantee that you see all messages. pub struct MessageCopyState { cursor: usize, buf_len: usize, total: usize, }