lb 0.6.0

A TUI library with ASCII/Unicode graphics.
Documentation
use std::io::Read;
use std::time;

use lb::buf;
use lb::buf::Buffer;
use lb::color;
use lb::img;
use lb::mat;
use lb::prelude::*;
use lb::typeset;
use lb::typesetters;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let args: Vec<String> = std::env::args().collect();

    if args.len() < 3 {
        eprintln!("expected at least 2 CLI arguments");
        eprintln!("usage: cargo run --example bench -- <buffer-size> <repeats>");
    }

    let size: usize = args[1].as_str().parse()?;
    let repeats: usize = args[2].as_str().parse()?;

    let print = args
        .get(3)
        .filter(|a| *a == "-p" || *a == "--print")
        .is_some();

    let config = Config {
        size: (size, size).into(),
        repeats,
        print,
        random: Some(1928346841),
    };

    bench_buffer::<buf::DoubleBuffer>(config);
    bench_buffer::<buf::SimpleBuffer>(config);

    let config = Config {
        random: None,
        ..config
    };

    bench_buffer::<buf::DoubleBuffer>(config);
    bench_buffer::<buf::SimpleBuffer>(config);

    let config = Config {
        random: Some(1928346841),
        ..config
    };

    bench_typesetter(config, &typesetters::Asymmetric);
    bench_typesetter(config, &typesetters::Sextant);
    bench_typesetter(config, &typesetters::Quadrant);
    bench_typesetter(config, &typesetters::Half);
    bench_typesetter(config, &typesetters::Block);

    let config = Config {
        random: None,
        ..config
    };

    bench_typesetter(config, &typesetters::Asymmetric);
    bench_typesetter(config, &typesetters::Sextant);
    bench_typesetter(config, &typesetters::Quadrant);
    bench_typesetter(config, &typesetters::Half);
    bench_typesetter(config, &typesetters::Block);

    Ok(())
}

fn pause() {
    println!("Press enter to continue...");
    std::io::stdin().bytes().next();
}

#[derive(Clone, Copy)]
struct Config {
    size: mat::Size,
    repeats: usize,
    random: Option<u64>,
    print: bool,
}

fn bench_buffer<B>(config: Config)
where
    B: Buffer,
{
    if config.print {
        print!("\x1b[2J");
    }

    let mut buffer = B::new(config.size);

    let mut fill_times = time::Duration::ZERO;
    let mut render_times = time::Duration::ZERO;
    let mut print_times = time::Duration::ZERO;
    let mut char_count: usize = 0;

    let mut rnd = config.random.map(|s| rand::Rand::new(s));

    for _ in 0..config.repeats {
        let mut watch = Stopwatch::new();

        fill_buffer(&mut buffer, rnd.as_mut());

        fill_times += watch.lap();

        let string = buffer.render();

        char_count += string.len();

        render_times += watch.lap();

        if config.print {
            print!("\x1b[1;1H");
            print!("{}", string);

            print_times += watch.lap();
        }
    }

    println!(
        "{}, random: {}",
        std::any::type_name::<B>(),
        config.random.is_some()
    );
    println!("chars: {}", char_count);
    println!("fill:   {:?}/frame", fill_times / config.repeats as u32);
    println!("render: {:?}/frame", render_times / config.repeats as u32);

    if config.print {
        println!("print:  {:?}/frame", print_times / config.repeats as u32);

        pause();
    }

    println!();
}

fn fill_buffer<B>(buffer: &mut B, mut rnd: Option<&mut rand::Rand>)
where
    B: Buffer,
{
    let mut buf = [0u8; 6];

    let mut codepoint: char = 'x';

    let rows = buffer.rows_mut();
    for row in rows {
        for glyph in row {
            if let Some(ref mut rnd) = rnd {
                rnd.fill(&mut buf);
                // NOTE The random numbers never stop, so unwrap is fine.
                codepoint = rnd.next().unwrap().clamp(0x20, 0x7e) as char;
            }
            let fg = color::Rgb::new(buf[0], buf[1], buf[2]);
            let bg = color::Rgb::new(buf[3], buf[4], buf[5]);

            glyph.char = codepoint;
            glyph.foreground = Some(fg);
            glyph.background = Some(bg);
        }
    }
}

fn bench_typesetter<T>(config: Config, typesetter: &T)
where
    T: typeset::Typesetter,
{
    if config.print {
        print!("\x1b[2J");
    }

    let mut buffer = buf::DoubleBuffer::new(config.size);
    let mut img = img::ImgVec::with_default(buffer.size().to_image_size(typesetter));
    let mut rnd = config.random.map(|s| rand::Rand::new(s));

    let mut fill_times = time::Duration::ZERO;
    let mut compose_times = time::Duration::ZERO;
    let mut render_times = time::Duration::ZERO;
    let mut print_times = time::Duration::ZERO;

    for _ in 0..config.repeats {
        let mut watch = Stopwatch::new();

        fill_img(&mut img, rnd.as_mut());

        fill_times += watch.lap();

        typesetter.compose(buffer.as_mut(), img.as_ref());

        compose_times += watch.lap();

        if config.print {
            let string = buffer.render();

            render_times += watch.lap();

            print!("\x1b[1;1H");
            print!("{}", string);

            print_times += watch.lap();
        }
    }

    println!(
        "{}, random: {}",
        std::any::type_name::<T>(),
        config.random.is_some()
    );
    println!("fill:    {:?}/frame", fill_times / config.repeats as u32);
    println!("compose: {:?}/frame", compose_times / config.repeats as u32);

    if config.print {
        println!("render:  {:?}/frame", render_times / config.repeats as u32);
        println!("print:   {:?}/frame", print_times / config.repeats as u32);

        pause();
    }

    println!();
}

fn fill_img(img: &mut img::ImgVec, mut rnd: Option<&mut rand::Rand>) {
    let mut buf = [0u8; 3];

    let rows = img.rows_mut();
    for row in rows {
        for rgb in row {
            if let Some(ref mut rnd) = rnd {
                rnd.fill(&mut buf);
            }
            *rgb = color::Rgb::new(buf[0], buf[1], buf[2]);
        }
    }
}

struct Stopwatch {
    last: time::Instant,
}

impl Stopwatch {
    pub fn new() -> Self {
        Self {
            last: time::Instant::now(),
        }
    }

    pub fn lap(&mut self) -> time::Duration {
        let last = self.last;
        self.last = time::Instant::now();
        self.last - last
    }
}

mod rand {
    use std::num;

    pub struct Rand {
        state: u64,
        key: u64,
        buf: [u8; 8],
        i: usize,
    }

    impl Rand {
        pub fn new(seed: u64) -> Rand {
            Rand {
                state: seed,
                key: DEFAULT_KEY,
                buf: [0u8; 8],
                i: 8,
            }
        }

        pub fn fill(&mut self, bytes: &mut [u8]) {
            for (byte, rnd) in bytes.iter_mut().zip(self) {
                *byte = rnd;
            }
        }
    }

    impl Iterator for Rand {
        type Item = u8;

        fn next(&mut self) -> Option<u8> {
            if self.i >= self.buf.len() {
                self.buf = squares64(self.state, self.key).to_ne_bytes();
                self.state += 1;
                self.i = 0;
            }

            let i = self.i;
            self.i += 1;

            Some(self.buf[i])
        }
    }

    const DEFAULT_KEY: u64 = 0x1fb9463761e329f5;

    #[allow(dead_code)]
    fn squares32(i: u64, key: u64) -> u32 {
        let i = num::Wrapping(i);
        let key = num::Wrapping(key);

        let mut x = i * key;
        let y = x;
        let z = y + key;

        x = x * x + y;
        x = (x >> 32) | (x << 32);

        x = x * x + z;
        x = (x >> 32) | (x << 32);

        x = x * x + y;
        x = (x >> 32) | (x << 32);

        ((x * x + z) >> 32).0 as u32
    }

    fn squares64(i: u64, key: u64) -> u64 {
        let i = num::Wrapping(i);
        let key = num::Wrapping(key);

        let mut x = i * key;
        let y = x;
        let z = y + key;

        x = x * x + y;
        x = (x >> 32) | (x << 32);

        x = x * x + z;
        x = (x >> 32) | (x << 32);

        x = x * x + y;
        x = (x >> 32) | (x << 32);

        x = x * x + z;
        let t = x;
        x = (x >> 32) | (x << 32);

        (t ^ ((x * x + y) >> 32)).0
    }

    #[test]
    fn test_rand_next() {
        let seed = 0xf08aca531c67f7a7;

        let bytes_rand: Vec<u8> = Rand::new(seed).take(16).collect();

        let fst = squares64(seed, DEFAULT_KEY).to_ne_bytes();
        assert_eq!(bytes_rand[..8], fst);

        let snd = squares64(seed + 1, DEFAULT_KEY).to_ne_bytes();
        assert_eq!(bytes_rand[8..], snd);
    }

    #[test]
    fn test_squares32() {
        let seed = 0x7098117a5705d5b8;

        assert_eq!(squares32(seed + 0, DEFAULT_KEY), 309968457);
        assert_eq!(squares32(seed + 1, DEFAULT_KEY), 245018196);
    }
}