1use crate::prelude::*;
12use alloc::collections::VecDeque;
13use alloc::collections::vec_deque;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[non_exhaustive]
18pub enum LogLevel {
19 Error,
21 Warn,
23 Info,
25 Debug,
27}
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[non_exhaustive]
32pub enum LogCategory {
33 Assets,
35 Audio,
37 Input,
39 Screens,
41 Storage,
43 Platform,
45 Game,
47}
48
49impl LogLevel {
50 pub const ALL: [Self; 4] = [Self::Error, Self::Warn, Self::Info, Self::Debug];
52
53 pub fn name(self) -> &'static str {
55 match self {
56 Self::Error => "error",
57 Self::Warn => "warn",
58 Self::Info => "info",
59 Self::Debug => "debug",
60 }
61 }
62
63 pub fn from_name(name: &str) -> Option<Self> {
65 Self::ALL.into_iter().find(|level| level.name() == name)
66 }
67}
68
69impl LogCategory {
70 pub const ALL: [Self; 7] = [
72 Self::Assets,
73 Self::Audio,
74 Self::Input,
75 Self::Screens,
76 Self::Storage,
77 Self::Platform,
78 Self::Game,
79 ];
80
81 pub fn name(self) -> &'static str {
83 match self {
84 Self::Assets => "assets",
85 Self::Audio => "audio",
86 Self::Input => "input",
87 Self::Screens => "screens",
88 Self::Storage => "storage",
89 Self::Platform => "platform",
90 Self::Game => "game",
91 }
92 }
93
94 pub fn from_name(name: &str) -> Option<Self> {
96 Self::ALL
97 .into_iter()
98 .find(|category| category.name() == name)
99 }
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
104#[non_exhaustive]
105pub struct LogLine {
106 pub frame: u32,
109 pub level: LogLevel,
111 pub category: LogCategory,
113 pub text: String,
115}
116
117impl LogLine {
118 pub fn new(
120 frame: u32,
121 level: LogLevel,
122 category: LogCategory,
123 text: impl Into<String>,
124 ) -> Self {
125 Self {
126 frame,
127 level,
128 category,
129 text: text.into(),
130 }
131 }
132}
133
134pub const LOG_LINES: usize = 500;
136
137#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct LogRing {
143 lines: VecDeque<LogLine>,
144 capacity: usize,
145 dropped: u32,
146 next: u64,
148}
149
150impl LogRing {
151 pub fn new(capacity: usize) -> Self {
153 Self {
154 lines: VecDeque::new(),
155 capacity,
156 dropped: 0,
157 next: 0,
158 }
159 }
160
161 pub fn push(&mut self, line: LogLine) {
163 self.next += 1;
164 if self.capacity == 0 {
165 self.add_dropped(1);
166 return;
167 }
168 if self.lines.len() == self.capacity {
169 self.lines.pop_front();
170 self.add_dropped(1);
171 }
172 self.lines.push_back(line);
173 }
174
175 pub fn lines(&self) -> vec_deque::Iter<'_, LogLine> {
177 self.lines.iter()
178 }
179
180 pub fn dropped(&self) -> u32 {
182 self.dropped
183 }
184
185 pub fn add_dropped(&mut self, n: u32) {
187 self.dropped = self.dropped.saturating_add(n);
188 }
189
190 pub fn next_seq(&self) -> u64 {
192 self.next
193 }
194
195 pub fn since(&self, seq: u64) -> vec_deque::Iter<'_, LogLine> {
199 let skip = skip_before(seq, self.next, self.lines.len());
200 self.lines.range(skip..)
201 }
202
203 pub fn take(&mut self) -> (Vec<LogLine>, u32) {
206 let lines = self.lines.drain(..).collect();
207 (lines, core::mem::take(&mut self.dropped))
208 }
209}
210
211pub(crate) fn skip_before(seq: u64, next: u64, kept: usize) -> usize {
214 let kept_u64 = u64::try_from(kept).unwrap_or(u64::MAX);
215 let skip = seq.saturating_sub(next.saturating_sub(kept_u64));
216 usize::try_from(skip).unwrap_or(usize::MAX).min(kept)
217}
218
219#[derive(Debug, Clone, PartialEq, Eq)]
223pub struct LogFilter {
224 level: LogLevel,
225 categories: Vec<(LogCategory, LogLevel)>,
226}
227
228impl Default for LogFilter {
229 fn default() -> Self {
231 Self::new(LogLevel::Info)
232 }
233}
234
235impl LogFilter {
236 pub fn new(level: LogLevel) -> Self {
238 Self {
239 level,
240 categories: Vec::new(),
241 }
242 }
243
244 #[must_use]
246 pub fn with(mut self, category: LogCategory, level: LogLevel) -> Self {
247 self.categories.retain(|(c, _)| *c != category);
248 self.categories.push((category, level));
249 self
250 }
251
252 pub fn allows(&self, category: LogCategory, level: LogLevel) -> bool {
254 let at = self
255 .categories
256 .iter()
257 .find(|(c, _)| *c == category)
258 .map_or(self.level, |(_, l)| *l);
259 level <= at
260 }
261}
262
263#[cfg(test)]
264mod tests;