blob: f708736ae0b4959f9409d9d3f49d6c50c153e64c [file] [log] [blame]
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<Message>,
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<String>) {
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<Message>) {
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<Message>, previous: Option<MessageCopyState>) -> 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
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,
}