1use std::collections::VecDeque;
5
6use crate::palette::{self, Rgb, VGA};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum Color {
13 Pc(u8),
15 Xterm(u8),
17 Rgb(Rgb),
19}
20
21pub mod flags {
22 pub const BOLD: u8 = 1 << 0;
23 pub const BLINK: u8 = 1 << 1;
24 pub const UNDERLINE: u8 = 1 << 2;
25 pub const REVERSE: u8 = 1 << 3;
26 pub const CONCEAL: u8 = 1 << 4;
27 pub const FAINT: u8 = 1 << 5;
28 pub const DOUBLE_TOP: u8 = 1 << 6;
30 pub const DOUBLE_BOTTOM: u8 = 1 << 7;
32}
33
34pub const DEFAULT_FG: Color = Color::Pc(7);
35pub const DEFAULT_BG: Color = Color::Pc(0);
36
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct Cell {
39 pub ch: u8,
41 pub fg: Color,
42 pub bg: Color,
43 pub flags: u8,
44 pub tile: u32,
47}
48
49impl Default for Cell {
50 fn default() -> Self {
51 Cell { ch: b' ', fg: DEFAULT_FG, bg: DEFAULT_BG, flags: 0, tile: 0 }
52 }
53}
54
55impl Cell {
56 pub fn new(ch: u8, fg: Color, bg: Color) -> Self {
57 Cell { ch, fg, bg, flags: 0, tile: 0 }
58 }
59
60 pub fn is_blank(&self) -> bool {
61 (self.ch == b' ' || self.ch == 0) && self.bg == DEFAULT_BG && self.flags & flags::REVERSE == 0 && self.tile == 0
62 }
63
64 pub fn resolve(&self, ice: bool) -> (Rgb, Rgb, bool) {
69 self.resolve_with(ice, Look::standard())
70 }
71
72 pub fn resolve_with(&self, ice: bool, look: &Look) -> (Rgb, Rgb, bool) {
74 let bold = self.flags & flags::BOLD != 0 && !look.bright_plain;
75 let blink = self.flags & flags::BLINK != 0;
76 let mut fg = match self.fg {
77 Color::Pc(i) if bold && i < 8 => look.rgb(Color::Pc(i + 8)),
78 c => look.rgb(c),
79 };
80 let mut bg = match self.bg {
81 Color::Pc(i) if blink && ice && i < 8 => look.rgb(Color::Pc(i + 8)),
82 c => look.rgb(c),
83 };
84 if self.flags & flags::REVERSE != 0 {
85 std::mem::swap(&mut fg, &mut bg);
86 }
87 if self.flags & flags::CONCEAL != 0 {
88 fg = bg;
89 }
90 (fg, bg, blink && !ice && !look.blink_steady)
91 }
92}
93
94#[derive(Clone, Debug, PartialEq, Eq)]
97pub struct Look {
98 pub palette: [Rgb; 256],
101 pub fonts: [u8; 4],
103 pub bright_font: bool,
105 pub bright_plain: bool,
107 pub blink_font: bool,
109 pub blink_steady: bool,
111 pub loaded: Vec<LoadedFont>,
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
117pub struct LoadedFont {
118 pub slot: u8,
119 pub height: u8,
120 pub data: &'static [u8],
121}
122
123pub const FIRST_LOADABLE_FONT: u8 = 43;
125
126pub const ENTRY_FONT: u8 = 255;
128
129impl Default for Look {
130 fn default() -> Look {
131 Look::standard().clone()
132 }
133}
134
135impl Look {
136 pub fn standard() -> &'static Look {
138 static STANDARD: std::sync::LazyLock<Look> = std::sync::LazyLock::new(|| Look {
139 palette: std::array::from_fn(|i| palette::xterm256(i as u8)),
140 fonts: [0; 4],
141 bright_font: false,
142 bright_plain: false,
143 blink_font: false,
144 blink_steady: false,
145 loaded: Vec::new(),
146 });
147 &STANDARD
148 }
149
150 pub fn load_font(&mut self, slot: u8, data: &[u8]) -> bool {
153 let height = match data.len() {
154 4096 => 16,
155 3584 => 14,
156 2048 => 8,
157 _ => return false,
158 };
159 if slot < FIRST_LOADABLE_FONT {
160 return false;
161 }
162 let font = LoadedFont { slot, height, data: intern(data) };
163 self.loaded.retain(|f| f.slot != slot);
164 self.loaded.push(font);
165 true
166 }
167
168 pub fn loaded_font(&self, slot: u8) -> Option<LoadedFont> {
170 self.loaded.iter().find(|f| f.slot == slot).copied()
171 }
172
173 pub fn rgb(&self, c: Color) -> Rgb {
174 match c {
175 Color::Pc(i) => {
177 let i = i & 15;
178 let ansi = palette::ANSI_TO_VGA.iter().position(|&v| v == i & 7).unwrap_or(0) as u8;
179 self.palette[(ansi + (i & 8)) as usize]
180 }
181 Color::Xterm(i) => self.palette[i as usize],
182 Color::Rgb(rgb) => rgb,
183 }
184 }
185
186 pub fn font_for(&self, cell: &Cell) -> u8 {
188 let bright = self.bright_font && cell.flags & flags::BOLD != 0;
189 let blink = self.blink_font && cell.flags & flags::BLINK != 0;
190 self.fonts[bright as usize | (blink as usize) << 1]
191 }
192}
193
194fn intern(data: &[u8]) -> &'static [u8] {
197 use std::collections::HashSet;
198 use std::sync::Mutex;
199 static FONTS: Mutex<Option<HashSet<&'static [u8]>>> = Mutex::new(None);
200 let mut fonts = FONTS.lock().unwrap_or_else(|e| e.into_inner());
201 let set = fonts.get_or_insert_with(HashSet::new);
202 if let Some(&have) = set.get(data) {
203 return have;
204 }
205 let kept: &'static [u8] = Box::leak(data.to_vec().into_boxed_slice());
206 set.insert(kept);
207 kept
208}
209
210pub fn color_rgb(c: Color) -> Rgb {
211 match c {
212 Color::Pc(i) => VGA[(i & 15) as usize],
213 Color::Xterm(i) => palette::xterm256(i),
214 Color::Rgb(rgb) => rgb,
215 }
216}
217
218#[derive(Clone, Copy, Debug)]
219struct SavedCursor {
220 x: usize,
221 y: usize,
222 pen: Cell,
223}
224
225pub struct Screen {
226 cols: usize,
227 rows: usize,
228 cells: Vec<Cell>,
229 x: usize,
230 y: usize,
231 pending_wrap: bool,
234 pub pen: Cell,
236 saved: Option<SavedCursor>,
237 scroll_top: usize,
238 scroll_bottom: usize,
239 pub autowrap: bool,
240 pub cursor_visible: bool,
241 pub ice_colors: bool,
242 pub look: Look,
244 pub base_font: u8,
246 pub base_ice: bool,
248 scrollback: VecDeque<Vec<Cell>>,
249 scrollback_limit: usize,
250 pub tiles: Tiles,
252}
253
254impl Screen {
255 pub fn new(cols: usize, rows: usize, scrollback_limit: usize) -> Self {
256 let cols = cols.max(1);
257 let rows = rows.max(1);
258 Screen {
259 cols,
260 rows,
261 cells: vec![Cell::default(); cols * rows],
262 x: 0,
263 y: 0,
264 pending_wrap: false,
265 pen: Cell::default(),
266 saved: None,
267 scroll_top: 0,
268 scroll_bottom: rows - 1,
269 autowrap: true,
270 cursor_visible: true,
271 ice_colors: false,
272 look: Look::default(),
273 base_font: 0,
274 base_ice: false,
275 scrollback: VecDeque::new(),
276 scrollback_limit,
277 tiles: Tiles::default(),
278 }
279 }
280
281 pub fn cols(&self) -> usize {
282 self.cols
283 }
284
285 pub fn rows(&self) -> usize {
286 self.rows
287 }
288
289 pub fn cursor(&self) -> (usize, usize) {
290 (self.x, self.y)
291 }
292
293 pub fn row(&self, y: usize) -> &[Cell] {
294 &self.cells[y * self.cols..(y + 1) * self.cols]
295 }
296
297 pub fn cell_mut(&mut self, x: usize, y: usize) -> Option<&mut Cell> {
298 if x < self.cols && y < self.rows { Some(&mut self.cells[y * self.cols + x]) } else { None }
299 }
300
301 pub fn scrollback_len(&self) -> usize {
302 self.scrollback.len()
303 }
304
305 pub fn scrollback_line(&self, i: usize) -> &[Cell] {
307 &self.scrollback[i]
308 }
309
310 pub fn clear_scrollback(&mut self) {
311 self.scrollback.clear();
312 }
313
314 pub fn reset(&mut self) {
316 self.pen = Cell::default();
317 self.cells.fill(Cell::default());
318 self.x = 0;
319 self.y = 0;
320 self.pending_wrap = false;
321 self.saved = None;
322 self.scroll_top = 0;
323 self.scroll_bottom = self.rows - 1;
324 self.autowrap = true;
325 self.cursor_visible = true;
326 self.ice_colors = self.base_ice;
327 let loaded = std::mem::take(&mut self.look.loaded);
329 self.look = Look { loaded, ..Look::default() };
330 self.look.fonts[0] = self.base_font;
331 }
332
333 pub fn resize(&mut self, cols: usize, rows: usize) {
334 let cols = cols.max(1);
335 let rows = rows.max(1);
336 if cols == self.cols && rows == self.rows {
337 return;
338 }
339 let mut cells = vec![Cell::default(); cols * rows];
340 for y in 0..rows.min(self.rows) {
341 let n = cols.min(self.cols);
342 cells[y * cols..y * cols + n].copy_from_slice(&self.row(y)[..n]);
343 }
344 self.cells = cells;
345 self.cols = cols;
346 self.rows = rows;
347 self.x = self.x.min(cols - 1);
348 self.y = self.y.min(rows - 1);
349 self.scroll_top = 0;
350 self.scroll_bottom = rows - 1;
351 self.pending_wrap = false;
352 }
353
354 fn blank(&self) -> Cell {
357 Cell { ch: b' ', flags: self.pen.flags & (flags::BLINK | flags::REVERSE), ..self.pen }
358 }
359
360 pub fn put(&mut self, ch: u8) {
362 if self.pending_wrap {
363 self.pending_wrap = false;
364 self.x = 0;
365 self.line_feed();
366 }
367 let idx = self.y * self.cols + self.x;
368 self.cells[idx] = Cell { ch, ..self.pen };
369 if self.x + 1 < self.cols {
370 self.x += 1;
371 } else if self.autowrap {
372 self.pending_wrap = true;
373 }
374 }
375
376 pub fn carriage_return(&mut self) {
377 self.x = 0;
378 self.pending_wrap = false;
379 }
380
381 pub fn line_feed(&mut self) {
383 self.pending_wrap = false;
384 if self.y == self.scroll_bottom {
385 self.scroll_up(1);
386 } else if self.y + 1 < self.rows {
387 self.y += 1;
388 }
389 }
390
391 pub fn reverse_index(&mut self) {
393 self.pending_wrap = false;
394 if self.y == self.scroll_top {
395 self.scroll_down(1);
396 } else if self.y > 0 {
397 self.y -= 1;
398 }
399 }
400
401 pub fn backspace(&mut self) {
402 self.pending_wrap = false;
403 self.x = self.x.saturating_sub(1);
404 }
405
406 pub fn tab(&mut self) {
407 self.pending_wrap = false;
408 self.x = ((self.x / 8 + 1) * 8).min(self.cols - 1);
409 }
410
411 pub fn back_tab(&mut self, n: usize) {
412 for _ in 0..n {
413 self.x = self.x.saturating_sub(1) / 8 * 8;
414 }
415 self.pending_wrap = false;
416 }
417
418 pub fn move_to(&mut self, x: usize, y: usize) {
420 self.x = x.min(self.cols - 1);
421 self.y = y.min(self.rows - 1);
422 self.pending_wrap = false;
423 }
424
425 pub fn move_by(&mut self, dx: isize, dy: isize) {
426 let x = (self.x as isize + dx).max(0) as usize;
427 let y = (self.y as isize + dy).max(0) as usize;
428 self.move_to(x, y);
429 }
430
431 pub fn set_scroll_region(&mut self, top: usize, bottom: usize) {
432 let bottom = bottom.min(self.rows - 1);
433 if top < bottom {
434 self.scroll_top = top;
435 self.scroll_bottom = bottom;
436 } else {
437 self.scroll_top = 0;
438 self.scroll_bottom = self.rows - 1;
439 }
440 self.move_to(0, 0);
441 }
442
443 pub fn save_cursor(&mut self) {
444 self.saved = Some(SavedCursor { x: self.x, y: self.y, pen: self.pen });
445 }
446
447 pub fn restore_cursor(&mut self) {
448 if let Some(s) = self.saved {
449 self.move_to(s.x, s.y);
450 self.pen = s.pen;
451 }
452 }
453
454 fn push_scrollback(&mut self, line: Vec<Cell>) {
455 if self.scrollback_limit == 0 {
456 return;
457 }
458 if self.scrollback.len() >= self.scrollback_limit {
459 self.scrollback.pop_front();
460 }
461 self.scrollback.push_back(line);
462 }
463
464 pub fn scroll_up(&mut self, n: usize) {
467 let (top, bottom) = (self.scroll_top, self.scroll_bottom);
468 let n = n.min(bottom - top + 1);
469 if top == 0 && bottom == self.rows - 1 {
470 for y in 0..n {
471 let line = self.row(y).to_vec();
472 self.push_scrollback(line);
473 }
474 }
475 let c = self.cols;
476 self.cells.copy_within((top + n) * c..(bottom + 1) * c, top * c);
477 let blank = self.blank();
478 self.cells[(bottom + 1 - n) * c..(bottom + 1) * c].fill(blank);
479 }
480
481 pub fn scroll_down(&mut self, n: usize) {
482 let (top, bottom) = (self.scroll_top, self.scroll_bottom);
483 let n = n.min(bottom - top + 1);
484 let c = self.cols;
485 self.cells.copy_within(top * c..(bottom + 1 - n) * c, (top + n) * c);
486 let blank = self.blank();
487 self.cells[top * c..(top + n) * c].fill(blank);
488 }
489
490 pub fn erase_display(&mut self, mode: u32) {
496 let blank = self.blank();
497 let pos = self.y * self.cols + self.x;
498 match mode {
499 0 => self.cells[pos..].fill(blank),
500 1 => self.cells[..=pos].fill(blank),
501 _ => {
502 let used = (0..self.rows).rev().find(|&y| self.row(y).iter().any(|c| !c.is_blank()));
503 if let Some(last) = used {
504 for y in 0..=last {
505 let line = self.row(y).to_vec();
506 self.push_scrollback(line);
507 }
508 }
509 self.cells.fill(blank);
510 self.move_to(0, 0);
511 }
512 }
513 self.pending_wrap = false;
514 }
515
516 pub fn erase_line(&mut self, mode: u32) {
518 let blank = self.blank();
519 let start = self.y * self.cols;
520 let (a, b) = match mode {
521 0 => (self.x, self.cols),
522 1 => (0, self.x + 1),
523 _ => (0, self.cols),
524 };
525 self.cells[start + a..start + b].fill(blank);
526 self.pending_wrap = false;
527 }
528
529 pub fn erase_chars(&mut self, n: usize) {
531 let blank = self.blank();
532 let start = self.y * self.cols;
533 let end = (self.x + n).min(self.cols);
534 self.cells[start + self.x..start + end].fill(blank);
535 }
536
537 pub fn insert_chars(&mut self, n: usize) {
539 let blank = self.blank();
540 let start = self.y * self.cols;
541 let n = n.min(self.cols - self.x);
542 let line = &mut self.cells[start..start + self.cols];
543 line.copy_within(self.x..self.cols - n, self.x + n);
544 line[self.x..self.x + n].fill(blank);
545 }
546
547 pub fn delete_chars(&mut self, n: usize) {
549 let blank = self.blank();
550 let start = self.y * self.cols;
551 let n = n.min(self.cols - self.x);
552 let line = &mut self.cells[start..start + self.cols];
553 line.copy_within(self.x + n.., self.x);
554 let cols = self.cols;
555 line[cols - n..].fill(blank);
556 }
557
558 pub fn insert_lines(&mut self, n: usize) {
560 if self.y < self.scroll_top || self.y > self.scroll_bottom {
561 return;
562 }
563 let top = self.scroll_top;
564 self.scroll_top = self.y;
565 self.scroll_down(n);
566 self.scroll_top = top;
567 self.x = 0;
568 }
569
570 pub fn delete_lines(&mut self, n: usize) {
572 if self.y < self.scroll_top || self.y > self.scroll_bottom {
573 return;
574 }
575 let (top, bottom) = (self.scroll_top, self.scroll_bottom);
576 self.scroll_top = self.y;
578 let n = n.min(bottom - self.y + 1);
579 let c = self.cols;
580 self.cells.copy_within((self.y + n) * c..(bottom + 1) * c, self.y * c);
581 let blank = self.blank();
582 self.cells[(bottom + 1 - n) * c..(bottom + 1) * c].fill(blank);
583 self.scroll_top = top;
584 self.x = 0;
585 }
586
587 pub fn draw_text(&mut self, x: usize, y: usize, text: &[u8], fg: Color, bg: Color) {
590 for (i, &ch) in text.iter().enumerate() {
591 if let Some(c) = self.cell_mut(x + i, y) {
592 *c = Cell::new(ch, fg, bg);
593 }
594 }
595 }
596
597 pub fn fill_rect(&mut self, x: usize, y: usize, w: usize, h: usize, cell: Cell) {
598 for yy in y..(y + h).min(self.rows) {
599 for xx in x..(x + w).min(self.cols) {
600 self.cells[yy * self.cols + xx] = cell;
601 }
602 }
603 }
604
605 pub fn cell_px(&self) -> (usize, usize) {
608 (8, if self.rows >= 43 { 8 } else { 16 })
609 }
610
611 pub fn size_px(&self) -> (usize, usize) {
613 let (w, h) = self.cell_px();
614 (self.cols * w, self.rows * h)
615 }
616
617 pub fn place_image(&mut self, img: &crate::sixel::Image, scrolling: bool) {
623 if img.width == 0 || img.height == 0 {
624 return;
625 }
626 let (tw, th) = self.cell_px();
627 let cx = self.x;
628 let rows = img.height.div_ceil(th);
629 let cols = img.width.div_ceil(tw);
630 if !scrolling {
631 for r in 0..rows.min(self.rows) {
632 self.tile_row(img, r, 0, r, cols);
633 }
634 return;
635 }
636 self.pending_wrap = false;
637 let last = img.height / th;
638 for r in 0..=last {
639 if r > 0 {
640 self.line_feed();
641 }
642 if r < rows {
643 let y = self.y;
644 self.tile_row(img, r, cx, y, cols);
645 }
646 }
647 self.x = cx;
648 }
649
650 fn tile_row(&mut self, img: &crate::sixel::Image, r: usize, x0: usize, y: usize, cols: usize) {
652 let (tw, th) = self.cell_px();
653 let (left, top) = ((x0 * tw) as isize, (y * th) as isize);
654 let rect = (left, top, cols * tw, th);
655 self.draw_pixels(rect, |px, py| {
656 let (ix, iy) = ((px - left) as usize, (py - top) as usize + r * th);
657 if ix < img.width && iy < img.height { img.pixels[iy * img.width + ix] } else { 0 }
658 });
659 }
660
661 pub fn draw_pixels(&mut self, rect: (isize, isize, usize, usize), pixel: impl Fn(isize, isize) -> u32) {
665 let (tw, th) = self.cell_px();
666 let (sw, sh) = self.size_px();
667 let (x0, y0) = (rect.0.max(0) as usize, rect.1.max(0) as usize);
668 let x1 = ((rect.0 + rect.2 as isize).max(0) as usize).min(sw);
669 let y1 = ((rect.1 + rect.3 as isize).max(0) as usize).min(sh);
670 if x0 >= x1 || y0 >= y1 {
671 return;
672 }
673 for cy in y0 / th..y1.div_ceil(th) {
674 for cx in x0 / tw..x1.div_ceil(tw) {
675 let idx = cy * self.cols + cx;
676 let mut px = match self.tiles.get(self.cells[idx].tile) {
677 Some(t) if (t.width, t.height) == (tw, th) => t.pixels.to_vec(),
678 _ => vec![0; tw * th],
679 };
680 let mut any = false;
681 for ty in 0..th {
682 let y = cy * th + ty;
683 if y < y0 || y >= y1 {
684 continue;
685 }
686 for tx in 0..tw {
687 let x = cx * tw + tx;
688 if x < x0 || x >= x1 {
689 continue;
690 }
691 let p = pixel(x as isize, y as isize);
692 if p != 0 {
693 px[ty * tw + tx] = p;
694 any = true;
695 }
696 }
697 }
698 if !any {
699 continue;
700 }
701 if self.tiles.full() {
702 let mut used = std::collections::HashSet::new();
703 used.extend(self.cells.iter().map(|c| c.tile));
704 used.extend(self.scrollback.iter().flatten().map(|c| c.tile));
705 self.tiles.collect(&used);
706 }
707 self.cells[idx].tile = self.tiles.add(Tile { width: tw, height: th, pixels: px.into() });
708 }
709 }
710 }
711
712 pub fn pixel_at(&self, x: usize, y: usize) -> crate::palette::Rgb {
716 let (tw, th) = self.cell_px();
717 let Some(cell) = self.cells.get((y / th) * self.cols + x / tw).filter(|_| x / tw < self.cols) else {
718 return 0;
719 };
720 if let Some(t) = self.tiles.get(cell.tile) {
721 let p = t.pixels.get((y % th) * t.width + x % tw).copied().unwrap_or(0);
722 if p != 0 {
723 return p & 0x00FF_FFFF;
724 }
725 }
726 cell.resolve_with(self.ice_colors, &self.look).1
727 }
728
729 pub fn row_text(row: &[Cell]) -> String {
731 let s: String = row.iter().map(|c| crate::cp437::to_unicode(c.ch.max(b' '))).collect();
732 s.trim_end().to_string()
733 }
734}
735
736#[cfg(test)]
737mod tests {
738 use super::*;
739
740 fn text(s: &Screen, y: usize) -> String {
741 Screen::row_text(s.row(y))
742 }
743
744 #[test]
745 fn delayed_wrap_at_last_column() {
746 let mut s = Screen::new(4, 3, 10);
747 for &b in b"abcd" {
748 s.put(b);
749 }
750 assert_eq!(s.cursor(), (3, 0));
751 s.carriage_return();
752 s.line_feed();
753 s.put(b'e');
754 assert_eq!(text(&s, 0), "abcd");
755 assert_eq!(text(&s, 1), "e");
756 }
757
758 #[test]
759 fn scrolling_feeds_scrollback() {
760 let mut s = Screen::new(4, 2, 10);
761 for &b in b"ab" {
762 s.put(b);
763 s.carriage_return();
764 s.line_feed();
765 }
766 assert_eq!(s.scrollback_len(), 1);
767 assert_eq!(Screen::row_text(s.scrollback_line(0)), "a");
768 assert_eq!(text(&s, 0), "b");
769 }
770
771 #[test]
772 fn clear_screen_saves_to_scrollback() {
773 let mut s = Screen::new(4, 3, 10);
774 s.put(b'x');
775 s.erase_display(2);
776 assert_eq!(s.scrollback_len(), 1);
777 assert_eq!(s.cursor(), (0, 0));
778 }
779
780 #[test]
781 fn insert_delete_chars() {
782 let mut s = Screen::new(5, 1, 0);
783 for &b in b"abcde" {
784 s.put(b);
785 }
786 s.move_to(1, 0);
787 s.delete_chars(2);
788 assert_eq!(text(&s, 0), "ade");
789 s.insert_chars(1);
790 assert_eq!(text(&s, 0), "a de");
791 }
792
793 #[test]
794 fn ice_colors_resolve() {
795 let c = Cell { ch: b'A', fg: Color::Pc(1), bg: Color::Pc(4), flags: flags::BOLD | flags::BLINK, tile: 0 };
796 assert_eq!(c.resolve(false), (VGA[9], VGA[4], true));
797 assert_eq!(c.resolve(true), (VGA[9], VGA[12], false));
798 }
799}
800
801#[derive(Clone, Debug, PartialEq, Eq)]
804pub struct Tile {
805 pub width: usize,
806 pub height: usize,
807 pub pixels: Box<[u32]>,
808}
809
810#[derive(Debug, Default)]
813pub struct Tiles {
814 store: Vec<Option<Tile>>,
815 free: Vec<u32>,
816 next_collect: usize,
818}
819
820impl Tiles {
821 pub fn get(&self, id: u32) -> Option<&Tile> {
822 id.checked_sub(1).and_then(|i| self.store.get(i as usize)).and_then(|t| t.as_ref())
823 }
824
825 fn full(&self) -> bool {
826 self.free.is_empty() && self.store.len() >= self.next_collect.max(4096)
827 }
828
829 fn collect(&mut self, used: &std::collections::HashSet<u32>) {
831 self.free.clear();
832 for (i, t) in self.store.iter_mut().enumerate() {
833 let id = i as u32 + 1;
834 if !used.contains(&id) {
835 *t = None;
836 self.free.push(id);
837 }
838 }
839 self.next_collect = (self.store.len() - self.free.len()) * 2;
840 }
841
842 fn add(&mut self, tile: Tile) -> u32 {
843 match self.free.pop() {
844 Some(id) => {
845 self.store[id as usize - 1] = Some(tile);
846 id
847 }
848 None => {
849 self.store.push(Some(tile));
850 self.store.len() as u32
851 }
852 }
853 }
854}