somewhat mad left align renderer

This commit is contained in:
Corwin 2023-06-26 23:47:19 +01:00
parent b75303863d
commit ce7bcacb3c
No known key found for this signature in database
2 changed files with 238 additions and 100 deletions

View file

@ -8,11 +8,12 @@ use agb::{
PaletteVram, Size, PaletteVram, Size,
}, },
palette16::Palette16, palette16::Palette16,
Font, Font, WIDTH,
}, },
include_font, include_font,
input::Button, input::Button,
}; };
use agb_fixnum::Rect;
use core::fmt::Write; use core::fmt::Write;
@ -31,13 +32,13 @@ fn main(mut gba: agb::Gba) -> ! {
let palette = Palette16::new(palette); let palette = Palette16::new(palette);
let palette = PaletteVram::new(&palette).unwrap(); let palette = PaletteVram::new(&palette).unwrap();
let config = Configuration::new(Size::S32x16, palette); let config = Configuration::new(Size::S16x16, palette);
let mut wr = BufferedWordRender::new(&FONT, config); let mut wr = BufferedWordRender::new(&FONT, config);
let _ = writeln!( let _ = writeln!(
wr, wr,
"Hello there!\nI spent this weekend\nwriting this text system!\nIs it any good?\n\nOh, by the way, you can\npress A to restart!" "Hello there!\nI spent this weekend writing this text system! Is it any good?\n\nOh, by the way, you can press A to restart!"
); );
let vblank = agb::interrupt::VBlank::get(); let vblank = agb::interrupt::VBlank::get();
let mut input = agb::input::ButtonController::new(); let mut input = agb::input::ButtonController::new();
@ -46,7 +47,7 @@ fn main(mut gba: agb::Gba) -> ! {
let mut timer: agb::timer::Timer = timer.timer2; let mut timer: agb::timer::Timer = timer.timer2;
timer.set_enabled(true); timer.set_enabled(true);
timer.set_divider(agb::timer::Divider::Divider64); timer.set_divider(agb::timer::Divider::Divider256);
let mut num_letters = 0; let mut num_letters = 0;
let mut frame = 0; let mut frame = 0;
@ -54,20 +55,21 @@ fn main(mut gba: agb::Gba) -> ! {
loop { loop {
vblank.wait_for_vblank(); vblank.wait_for_vblank();
input.update(); input.update();
let oam_frmae = &mut unmanaged.iter(); let oam = &mut unmanaged.iter();
wr.commit(oam);
let start = timer.value(); let start = timer.value();
wr.draw_partial(oam_frmae, (0, 0).into(), num_letters); wr.update(Rect::new((0, 0).into(), (WIDTH, 100).into()), num_letters);
wr.process();
let end = timer.value(); let end = timer.value();
agb::println!("Took {} cycles", 64 * (end.wrapping_sub(start) as u32)); agb::println!("Took {} cycles", 256 * (end.wrapping_sub(start) as u32));
frame += 1; frame += 1;
if frame % 4 == 0 { if frame % 4 == 0 {
num_letters += 1; num_letters += 1;
} }
wr.process();
if input.is_just_pressed(Button::A) { if input.is_just_pressed(Button::A) {
break; break;

View file

@ -1,6 +1,6 @@
use core::fmt::Write; use core::{cell::Cell, fmt::Write, num::NonZeroUsize};
use agb_fixnum::Vector2D; use agb_fixnum::{Rect, Vector2D};
use alloc::{collections::VecDeque, vec::Vec}; use alloc::{collections::VecDeque, vec::Vec};
use crate::display::{object::ObjectUnmanaged, Font}; use crate::display::{object::ObjectUnmanaged, Font};
@ -19,82 +19,209 @@ enum WhiteSpace {
NewLine, NewLine,
} }
enum TextElement { enum TextElementReference<'text> {
LetterGroup(LetterGroup), Word(Word<'text>),
WhiteSpace(WhiteSpace), WhiteSpace(WhiteSpace),
} }
#[cfg(test)] struct Word<'letters> {
mod tests { letters: &'letters [LetterGroup],
use super::*; width: Cell<Option<NonZeroUsize>>,
}
#[test_case] impl<'letters> Word<'letters> {
fn check_size_of_text_element_is_expected(_: &mut crate::Gba) { fn new(letters: &'letters [LetterGroup]) -> Self {
assert_eq!( Self {
core::mem::size_of::<TextElement>(), letters,
core::mem::size_of::<LetterGroup>() width: Cell::new(None),
); }
} }
} }
pub struct TextBlock { impl Word<'_> {
elements: Vec<TextElement>, fn width(&self) -> usize {
cache: CachedRender, match self.width.get() {
} Some(width) => width.get(),
None => {
let width = self.letters.iter().fold(0, |acc, letter| {
acc + (letter.width as i32 + letter.left as i32)
});
let width = width as usize;
pub struct CachedRender { self.width.set(NonZeroUsize::new(width));
objects: Vec<ObjectUnmanaged>, width
up_to: usize,
head_position: Vector2D<i32>,
origin: Vector2D<i32>,
}
impl TextBlock {
fn reset_cache(&mut self, position: Vector2D<i32>) {
self.cache.objects.clear();
self.cache.up_to = 0;
self.cache.head_position = position;
self.cache.origin = position;
}
fn generate_cache(&mut self, up_to: usize) {
let mut head_position = self.cache.head_position;
for element in self.elements.iter().take(up_to).skip(self.cache.up_to) {
match element {
TextElement::LetterGroup(group) => {
let mut object = ObjectUnmanaged::new(group.sprite.clone());
object.show();
head_position.x += group.left as i32;
object.set_position(head_position);
head_position.x += group.width as i32;
self.cache.objects.push(object);
}
TextElement::WhiteSpace(white) => match white {
WhiteSpace::Space => head_position.x += 10,
WhiteSpace::NewLine => {
head_position.x = self.cache.origin.x;
head_position.y += 15;
}
},
} }
} }
}
}
self.cache.head_position = head_position; #[derive(Clone, Copy)]
self.cache.up_to = up_to.min(self.elements.len()); struct WordLength(u8);
#[derive(Clone, Copy)]
enum Element {
Word(u8),
NewLine,
Space,
}
const NEW_LINE: u8 = 0xFF;
const SPACE: u8 = 0xFE;
impl WordLength {
fn parse(self) -> Element {
if self.0 == NEW_LINE {
Element::NewLine
} else if self.0 == SPACE {
Element::Space
} else {
Element::Word(self.0)
}
} }
fn draw(&mut self, oam: &mut OamIterator, position: Vector2D<i32>, up_to: usize) { fn from_element(e: Element) -> Self {
if position != self.cache.origin { WordLength(match e {
self.reset_cache(position); Element::Word(len) => len,
Element::NewLine => NEW_LINE,
Element::Space => SPACE,
})
}
}
struct Letters(Vec<LetterGroup>);
struct Words {
letters: Letters,
word_lengths: Vec<WordLength>,
}
struct WordRenderCache {
objects: Vec<ObjectUnmanaged>,
state: WordRenderCacheState,
poison_condition: WordRenderPoisonCondition,
}
struct WordRenderPoisonCondition {
area: Rect<i32>,
}
struct WordRenderCacheState {
depth_in_word_iterator: usize,
depth_in_word: usize,
depth_in_elements: usize,
head_position: Vector2D<i32>,
}
impl WordRenderCache {
fn new() -> Self {
WordRenderCache {
objects: Vec::new(),
state: WordRenderCacheState {
depth_in_word_iterator: 0,
depth_in_word: 0,
depth_in_elements: 0,
head_position: (0, 0).into(),
},
poison_condition: WordRenderPoisonCondition {
area: Rect::new((0, 0).into(), (0, 0).into()),
},
}
}
fn reset_state(&mut self, position: Rect<i32>) {
self.state = WordRenderCacheState {
depth_in_elements: 0,
depth_in_word: 0,
depth_in_word_iterator: 0,
head_position: position.position,
};
self.poison_condition = WordRenderPoisonCondition { area: position };
}
fn generate_cache(&mut self, words: &Words, desired_element_count: usize) {
let position = self.poison_condition.area;
if self.state.depth_in_elements >= desired_element_count {
return;
} }
self.generate_cache(up_to); 'outer: for elem in words.iter_words().skip(self.state.depth_in_word_iterator) {
match elem {
TextElementReference::Word(word) => {
let prospective_x = self.state.head_position.x + word.width() as i32;
for (obj, slot) in self.cache.objects.iter().zip(oam) { if self.state.depth_in_word == 0
slot.set(obj); && prospective_x > position.position.x + position.size.x
{
self.state.head_position.x = position.position.x;
self.state.head_position.y += 15;
}
for letter in word.letters.iter().skip(self.state.depth_in_word) {
self.state.head_position.x += letter.left as i32;
let mut object = ObjectUnmanaged::new(letter.sprite.clone());
object.show();
object.set_position(self.state.head_position);
self.objects.push(object);
self.state.head_position.x += letter.width as i32;
self.state.depth_in_elements += 1;
self.state.depth_in_word += 1;
if self.state.depth_in_elements >= desired_element_count {
break 'outer;
}
}
self.state.depth_in_word = 0;
}
TextElementReference::WhiteSpace(space) => {
match space {
WhiteSpace::Space => self.state.head_position.x += 10,
WhiteSpace::NewLine => {
self.state.head_position.x = position.position.x;
self.state.head_position.y += 15;
}
}
self.state.depth_in_elements += 1;
}
}
self.state.depth_in_word_iterator += 1;
if self.state.depth_in_elements >= desired_element_count {
break 'outer;
}
} }
} }
fn update(&mut self, words: &Words, desired_element_count: usize, position: Rect<i32>) {
if self.poison_condition.area != position {
self.reset_state(position);
}
self.generate_cache(words, desired_element_count);
}
fn commit(&self, oam: &mut OamIterator) {
for (object, slot) in self.objects.iter().zip(oam) {
slot.set(object);
}
}
}
impl Words {
fn iter_words(&self) -> impl Iterator<Item = TextElementReference> {
let mut letters_idx: usize = 0;
self.word_lengths.iter().map(move |x| match x.parse() {
Element::Word(length) => {
let idx = letters_idx;
let end_idx = idx + length as usize;
letters_idx = end_idx;
TextElementReference::Word(Word::new(&self.letters.0[idx..end_idx]))
}
Element::NewLine => TextElementReference::WhiteSpace(WhiteSpace::NewLine),
Element::Space => TextElementReference::WhiteSpace(WhiteSpace::Space),
})
}
} }
struct WorkingLetter { struct WorkingLetter {
@ -138,8 +265,11 @@ impl Configuration {
pub struct BufferedWordRender<'font> { pub struct BufferedWordRender<'font> {
word_render: WordRender<'font>, word_render: WordRender<'font>,
block: TextBlock, block: Words,
current_word_length: usize,
number_of_elements: usize,
buffered_chars: VecDeque<char>, buffered_chars: VecDeque<char>,
cache: WordRenderCache,
} }
impl Write for BufferedWordRender<'_> { impl Write for BufferedWordRender<'_> {
@ -157,16 +287,14 @@ impl<'font> BufferedWordRender<'font> {
pub fn new(font: &'font Font, config: Configuration) -> Self { pub fn new(font: &'font Font, config: Configuration) -> Self {
BufferedWordRender { BufferedWordRender {
word_render: WordRender::new(font, config), word_render: WordRender::new(font, config),
block: TextBlock { block: Words {
elements: Vec::new(), letters: Letters(Vec::new()),
cache: CachedRender { word_lengths: Vec::new(),
objects: Vec::new(),
up_to: 0,
head_position: (0, 0).into(),
origin: (0, 0).into(),
},
}, },
current_word_length: 0,
number_of_elements: 0,
buffered_chars: VecDeque::new(), buffered_chars: VecDeque::new(),
cache: WordRenderCache::new(),
} }
} }
} }
@ -176,43 +304,51 @@ impl BufferedWordRender<'_> {
if let Some(char) = self.buffered_chars.pop_front() { if let Some(char) = self.buffered_chars.pop_front() {
if char == '\n' { if char == '\n' {
if let Some(group) = self.word_render.finalise_letter() { if let Some(group) = self.word_render.finalise_letter() {
self.block.elements.push(TextElement::LetterGroup(group)); self.block.letters.0.push(group);
self.current_word_length += 1;
} }
self.block self.block
.elements .word_lengths
.push(TextElement::WhiteSpace(WhiteSpace::NewLine)); .push(WordLength::from_element(Element::Word(
self.current_word_length as u8,
)));
self.block
.word_lengths
.push(WordLength::from_element(Element::NewLine));
self.number_of_elements += self.current_word_length + 1;
self.current_word_length = 0;
} else if char == ' ' { } else if char == ' ' {
if let Some(group) = self.word_render.finalise_letter() { if let Some(group) = self.word_render.finalise_letter() {
self.block.elements.push(TextElement::LetterGroup(group)); self.block.letters.0.push(group);
self.current_word_length += 1;
} }
self.block self.block
.elements .word_lengths
.push(TextElement::WhiteSpace(WhiteSpace::Space)); .push(WordLength::from_element(Element::Word(
self.current_word_length as u8,
)));
self.block
.word_lengths
.push(WordLength::from_element(Element::Space));
self.number_of_elements += self.current_word_length + 1;
self.current_word_length = 0;
} else if let Some(group) = self.word_render.render_char(char) { } else if let Some(group) = self.word_render.render_char(char) {
self.block.elements.push(TextElement::LetterGroup(group)); self.block.letters.0.push(group);
self.current_word_length += 1;
} }
} }
} }
pub fn draw_partial( pub fn update(&mut self, position: Rect<i32>, number_of_elements: usize) {
&mut self, while !self.buffered_chars.is_empty() && self.number_of_elements < number_of_elements {
oam: &mut OamIterator,
position: Vector2D<i32>,
num_groups: usize,
) {
while self.block.elements.len() < num_groups && !self.buffered_chars.is_empty() {
self.process(); self.process();
} }
self.block.draw(oam, position, num_groups); self.cache.update(&self.block, number_of_elements, position);
} }
pub fn draw(&mut self, oam: &mut OamIterator, position: Vector2D<i32>) { pub fn commit(&self, oam: &mut OamIterator) {
while !self.buffered_chars.is_empty() { self.cache.commit(oam);
self.process();
}
self.block.draw(oam, position, usize::MAX);
} }
} }