yglnk-core 0.0.2

basic on-disk structured data helpers
Documentation
use super::decode_location;

/// rotate/flip a quadrant
fn rot(n: u32, x: &mut u32, y: &mut u32, rx: bool, ry: bool) {
    if ry {
        return;
    }
    if rx {
        *x = n - 1 - *x;
        *y = n - 1 - *y;
    }
    core::mem::swap(x, y);
}

fn bool2int(x: bool) -> u32 {
    if x {
        1
    } else {
        0
    }
}

pub fn xy2d(nh: u8, mut x: u32, mut y: u32) -> u32 {
    let n = 1 << nh;
    (0..nh)
        .rev()
        .map(|sh| {
            let s = 1u32 << sh;
            let rx = (x & s) != 0;
            let ry = (y & s) != 0;
            rot(n, &mut x, &mut y, rx, ry);
            s * s * ((3 * bool2int(rx)) ^ bool2int(ry))
        })
        .sum()
}

#[derive(Clone, Copy)]
pub struct X2dhc<'a> {
    xybits: u8,
    data: &'a [u8],
}

#[derive(Clone, Copy)]
pub enum X2dhcDim {
    X(u8),
    Y(u8),
}

impl X2dhcDim {
    pub fn value(&self) -> u8 {
        match *self {
            X2dhcDim::X(x) => x,
            X2dhcDim::Y(y) => y,
        }
    }
}

impl<'a> X2dhc<'a> {
    pub fn parse(data: &'a [u8]) -> Option<Self> {
        if data.len() < 32 {
            return None;
        }
        let xybits = data[0];
        if xybits >= 8 {
            return None;
        }
        let exp_len = 1usize.checked_shl(xybits.into())?.checked_mul(16)?;
        Some(Self {
            xybits,
            data: data.get(16..16 + exp_len)?,
        })
    }

    fn lookup_internal(data: &'a [u8], xybits2: u8, x: u8, y: u8) -> (u64, u64) {
        // SAFETY: the `exp_len` check above should ensure that this is always valid
        let loc = decode_location(xy2d(xybits2, x.into(), y.into())).unwrap();
        let dloc = &data[loc..loc + 16];
        (
            u64::from_be_bytes(dloc[0..8].try_into().unwrap()),
            u64::from_be_bytes(dloc[8..16].try_into().unwrap()),
        )
    }

    pub fn lookup(&self, x: u8, y: u8) -> (u64, u64) {
        let xybr = 8 - self.xybits;
        Self::lookup_internal(self.data, 2 * self.xybits, x >> xybr, y >> xybr)
    }

    pub fn iter_dim(&self, dim: X2dhcDim) -> impl Iterator<Item = ((u8, u8), (u64, u64))> + 'a {
        let X2dhc { xybits, data } = *self;
        let (xybr, xybdup) = (8 - xybits, 2 * xybits);
        let xy = dim.value() >> xybr;
        let xyv = xy << xybr;
        (0u8..=(0xff >> xybr)).map(move |i| {
            let xy2 = i << xybr;
            let (x, y) = match dim {
                X2dhcDim::X(_) => (xyv, xy2),
                X2dhcDim::Y(_) => (xy2, xyv),
            };
            ((x, y), Self::lookup_internal(data, xybdup, x, y))
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn simple() {
        let exp: [u32; 16] = [0, 3, 4, 5, 1, 2, 7, 6, 14, 13, 8, 9, 15, 12, 11, 10];
        for x in 0..4u16 {
            for y in 0..4u16 {
                assert_eq!(exp[usize::from(x * 4 + y)], xy2d(4, x.into(), y.into()));
            }
        }
    }
}