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