Nothing to see here, move along
at main 391 lines 12 kB view raw
1use core::fmt::Write; 2 3use lancer_log::phase::PhaseStatus; 4 5pub static mut BOOT_DASHBOARD: Dashboard = Dashboard::new(); 6#[allow(clippy::deref_addrof)] 7pub fn dash() -> &'static mut Dashboard { 8 unsafe { &mut *(&raw mut BOOT_DASHBOARD) } 9} 10 11#[allow(dead_code)] 12pub fn domain_to_section(domain: &str) -> Option<usize> { 13 let d = dash(); 14 let bytes = domain.as_bytes(); 15 (0..d.section_count).fold(None, |result, i| { 16 let section = &d.sections[i]; 17 match section.label_len == bytes.len() && section.label[..section.label_len] == *bytes { 18 true => Some(i), 19 false => result, 20 } 21 }) 22} 23 24const MAX_SECTIONS: usize = 24; 25const GUTTER_WIDTH: usize = 7; 26const MAX_LABEL_LEN: usize = GUTTER_WIDTH - 1; 27const VIEWPORT_LINE_LEN: usize = 160; 28const MAX_VIEWPORT_LINES: usize = 8; 29#[allow(dead_code)] 30const TERMINAL_ROWS: usize = 50; 31 32#[derive(Clone, Copy)] 33struct ViewportLine { 34 buf: [u8; VIEWPORT_LINE_LEN], 35 len: usize, 36} 37 38impl ViewportLine { 39 const fn empty() -> Self { 40 Self { 41 buf: [0u8; VIEWPORT_LINE_LEN], 42 len: 0, 43 } 44 } 45 46 fn set(&mut self, s: &str) { 47 let copy_len = s.len().min(VIEWPORT_LINE_LEN); 48 self.buf[..copy_len].copy_from_slice(&s.as_bytes()[..copy_len]); 49 self.len = copy_len; 50 } 51 52 fn as_str(&self) -> &str { 53 unsafe { core::str::from_utf8_unchecked(&self.buf[..self.len]) } 54 } 55} 56 57#[derive(Clone, Copy)] 58struct Section { 59 label: [u8; MAX_LABEL_LEN], 60 label_len: usize, 61 row_budget: u8, 62 status: PhaseStatus, 63 viewport: [ViewportLine; MAX_VIEWPORT_LINES], 64 viewport_count: usize, 65 viewport_head: usize, 66 start_row: u16, 67 active_rows: u8, 68} 69 70impl Section { 71 const fn empty() -> Self { 72 Self { 73 label: [0u8; MAX_LABEL_LEN], 74 label_len: 0, 75 row_budget: 1, 76 status: PhaseStatus::Pending, 77 viewport: [const { ViewportLine::empty() }; MAX_VIEWPORT_LINES], 78 viewport_count: 0, 79 viewport_head: 0, 80 start_row: 0, 81 active_rows: 0, 82 } 83 } 84 85 fn set_label(&mut self, name: &str) { 86 let copy_len = name.len().min(MAX_LABEL_LEN); 87 self.label[..copy_len].copy_from_slice(&name.as_bytes()[..copy_len]); 88 self.label_len = copy_len; 89 } 90 91 fn label_str(&self) -> &str { 92 unsafe { core::str::from_utf8_unchecked(&self.label[..self.label_len]) } 93 } 94 95 fn push_line(&mut self, line: &str) { 96 let idx = match self.viewport_count < MAX_VIEWPORT_LINES { 97 true => { 98 let i = self.viewport_count; 99 self.viewport_count += 1; 100 i 101 } 102 false => { 103 let i = self.viewport_head; 104 self.viewport_head = (self.viewport_head + 1) % MAX_VIEWPORT_LINES; 105 i 106 } 107 }; 108 self.viewport[idx].set(line); 109 } 110 111 fn visible_lines(&self, budget: usize) -> VisibleLines<'_> { 112 let total = self.viewport_count; 113 let visible = total.min(budget); 114 let start = match total <= budget { 115 true => 0, 116 false => (self.viewport_head + total - visible) % MAX_VIEWPORT_LINES, 117 }; 118 VisibleLines { 119 viewport: &self.viewport, 120 count: self.viewport_count, 121 start, 122 remaining: visible, 123 } 124 } 125} 126 127struct VisibleLines<'a> { 128 viewport: &'a [ViewportLine; MAX_VIEWPORT_LINES], 129 count: usize, 130 start: usize, 131 remaining: usize, 132} 133 134impl<'a> Iterator for VisibleLines<'a> { 135 type Item = &'a str; 136 137 fn next(&mut self) -> Option<Self::Item> { 138 match self.remaining { 139 0 => None, 140 _ => { 141 let idx = self.start % self.count.max(1); 142 self.start += 1; 143 self.remaining -= 1; 144 Some(self.viewport[idx].as_str()) 145 } 146 } 147 } 148} 149 150pub struct Dashboard { 151 sections: [Section; MAX_SECTIONS], 152 section_count: usize, 153 streaming: bool, 154 altscreen: bool, 155 last_streaming_label: [u8; MAX_LABEL_LEN], 156 last_streaming_label_len: usize, 157} 158 159impl Dashboard { 160 pub const fn new() -> Self { 161 Self { 162 sections: [const { Section::empty() }; MAX_SECTIONS], 163 section_count: 0, 164 streaming: false, 165 altscreen: false, 166 last_streaming_label: [0u8; MAX_LABEL_LEN], 167 last_streaming_label_len: 0, 168 } 169 } 170 171 pub fn add_section(&mut self, label: &str, row_budget: u8) -> usize { 172 let idx = self.section_count; 173 assert!(idx < MAX_SECTIONS, "too many dashboard sections"); 174 self.sections[idx].set_label(label); 175 self.sections[idx].row_budget = row_budget; 176 self.section_count += 1; 177 idx 178 } 179 180 pub fn section_status(&self, idx: usize) -> PhaseStatus { 181 self.sections[idx].status 182 } 183 184 pub fn begin_phase(&mut self, idx: usize) { 185 self.sections[idx].status = PhaseStatus::Active; 186 self.relayout(); 187 self.render_full(); 188 } 189 190 pub fn log(&mut self, idx: usize, msg: &str) { 191 match self.streaming { 192 true => self.stream_log(idx, msg), 193 false => { 194 self.sections[idx].push_line(msg); 195 self.render_section(idx); 196 } 197 } 198 } 199 200 pub fn end_phase(&mut self, idx: usize) { 201 self.sections[idx].status = PhaseStatus::Done; 202 self.sections[idx].active_rows = 1; 203 self.relayout(); 204 self.render_full(); 205 } 206 207 pub fn fail_phase(&mut self, idx: usize) { 208 self.sections[idx].status = PhaseStatus::Failed; 209 self.sections[idx].active_rows = 1; 210 self.relayout(); 211 self.render_full(); 212 } 213 214 pub fn transition_to_streaming(&mut self) { 215 self.streaming = true; 216 match self.altscreen { 217 true => { 218 crate::kprint!("\x1b[?1049l\x1b[?25h\x1b[J"); 219 self.altscreen = false; 220 } 221 false => crate::kprint!("\x1b[?25h"), 222 } 223 let w = crate::log::KLOG_GUTTER; 224 (0..self.section_count).fold((), |(), i| { 225 let section = &self.sections[i]; 226 let label = section.label_str(); 227 let dim = matches!(section.status, PhaseStatus::Pending | PhaseStatus::Done); 228 write_gutter_colored(label, dim, w); 229 match section.status { 230 PhaseStatus::Pending => crate::kprint!("\x1b[2m--\x1b[0m\n"), 231 PhaseStatus::Active => crate::kprint!("...\n"), 232 PhaseStatus::Done => crate::kprint!( 233 "{}ok{}\n", 234 lancer_log::color::Fg(lancer_log::palette::DONE_GREEN), 235 lancer_log::color::Reset 236 ), 237 PhaseStatus::Failed => crate::kprint!( 238 "{}FAILED{}\n", 239 lancer_log::color::Fg(lancer_log::palette::ERROR_RED), 240 lancer_log::color::Reset 241 ), 242 } 243 }); 244 } 245 246 fn relayout(&mut self) { 247 let mut row: u16 = 1; 248 (0..self.section_count).fold((), |(), i| { 249 let section = &mut self.sections[i]; 250 section.start_row = row; 251 let rows = match section.status { 252 PhaseStatus::Pending => 1u8, 253 PhaseStatus::Active => section.row_budget, 254 PhaseStatus::Done | PhaseStatus::Failed => 1, 255 }; 256 section.active_rows = rows; 257 row += rows as u16; 258 }); 259 } 260 261 fn render_full(&mut self) { 262 match self.altscreen { 263 false => { 264 crate::kprint!("\x1b[?1049h"); 265 self.altscreen = true; 266 } 267 true => {} 268 } 269 crate::kprint!("\x1b[?25l"); 270 (0..self.section_count).fold((), |(), i| { 271 self.render_section(i); 272 }); 273 } 274 275 fn render_section(&self, idx: usize) { 276 let section = &self.sections[idx]; 277 let label = section.label_str(); 278 let start = section.start_row; 279 let rows = section.active_rows as usize; 280 281 match section.status { 282 PhaseStatus::Pending => { 283 crate::kprint!("\x1b[{};1H", start); 284 write_gutter_colored(label, true, GUTTER_WIDTH); 285 crate::kprint!("\x1b[2m--\x1b[0m\x1b[K"); 286 } 287 PhaseStatus::Active => { 288 let mut line_idx = 0usize; 289 section.visible_lines(rows).fold((), |(), line| { 290 crate::kprint!("\x1b[{};1H", start as usize + line_idx); 291 match line_idx { 292 0 => write_gutter_colored(label, false, GUTTER_WIDTH), 293 _ => write_continuation_colored(GUTTER_WIDTH), 294 } 295 crate::kprint!("{}\x1b[K", line); 296 line_idx += 1; 297 }); 298 (line_idx..rows).fold((), |(), r| { 299 crate::kprint!("\x1b[{};1H", start as usize + r); 300 match r { 301 0 => write_gutter_colored(label, false, GUTTER_WIDTH), 302 _ => write_continuation_colored(GUTTER_WIDTH), 303 } 304 crate::kprint!("\x1b[K"); 305 }); 306 } 307 PhaseStatus::Done => { 308 crate::kprint!("\x1b[{};1H", start); 309 write_gutter_colored(label, true, GUTTER_WIDTH); 310 crate::kprint!( 311 "{}ok{}\x1b[K", 312 lancer_log::color::Fg(lancer_log::palette::DONE_GREEN), 313 lancer_log::color::Reset 314 ); 315 } 316 PhaseStatus::Failed => { 317 crate::kprint!("\x1b[{};1H", start); 318 write_gutter_colored(label, false, GUTTER_WIDTH); 319 crate::kprint!( 320 "{}FAILED{}\x1b[K", 321 lancer_log::color::Fg(lancer_log::palette::ERROR_RED), 322 lancer_log::color::Reset 323 ); 324 } 325 } 326 } 327 328 fn stream_log(&mut self, idx: usize, msg: &str) { 329 let section = &self.sections[idx]; 330 let label = section.label_str(); 331 let same_label = self.last_streaming_label_len == label.len() 332 && self.last_streaming_label[..self.last_streaming_label_len] 333 == section.label[..section.label_len]; 334 335 let w = crate::log::KLOG_GUTTER; 336 match same_label { 337 true => write_continuation_colored(w), 338 false => { 339 write_gutter_colored(label, false, w); 340 let copy_len = label.len().min(MAX_LABEL_LEN); 341 self.last_streaming_label[..copy_len] 342 .copy_from_slice(&label.as_bytes()[..copy_len]); 343 self.last_streaming_label_len = copy_len; 344 } 345 } 346 crate::kprint!("{}\n", msg); 347 } 348} 349 350fn write_gutter_colored(label: &str, dim: bool, width: usize) { 351 let mut w = crate::arch::serial::SerialWriter; 352 let _ = lancer_log::format::write_gutter_label(&mut w, label, dim, width); 353} 354 355fn write_continuation_colored(width: usize) { 356 let mut w = crate::arch::serial::SerialWriter; 357 let _ = lancer_log::format::write_continuation(&mut w, width); 358} 359 360pub struct FmtBuf { 361 buf: [u8; VIEWPORT_LINE_LEN], 362 pos: usize, 363} 364 365impl FmtBuf { 366 pub const fn new() -> Self { 367 Self { 368 buf: [0u8; VIEWPORT_LINE_LEN], 369 pos: 0, 370 } 371 } 372 373 pub fn as_str(&self) -> &str { 374 unsafe { core::str::from_utf8_unchecked(&self.buf[..self.pos]) } 375 } 376 377 #[allow(dead_code)] 378 pub fn clear(&mut self) { 379 self.pos = 0; 380 } 381} 382 383impl Write for FmtBuf { 384 fn write_str(&mut self, s: &str) -> core::fmt::Result { 385 let bytes = s.as_bytes(); 386 let copy_len = bytes.len().min(VIEWPORT_LINE_LEN - self.pos); 387 self.buf[self.pos..self.pos + copy_len].copy_from_slice(&bytes[..copy_len]); 388 self.pos += copy_len; 389 Ok(()) 390 } 391}