use crate::index::Index;
use halfling::Nibble;
use std::simd::u64x4;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct RawQuadboard {
channels: u64x4,
}
impl RawQuadboard {
#[inline(always)]
pub const fn into_channels(self) -> [u64; 4] {
self.channels.to_array()
}
#[inline(always)]
pub const fn as_channels(&self) -> &[u64; 4] {
self.channels.as_array()
}
#[inline(always)]
pub fn splat(value: Nibble) -> Self {
let value: u8 = value.get();
let (bit1, bit2, bit3, bit4) = unsafe { lower_nibble_bits(value) };
let bit_channels = u64x4::from_array([bit1, bit2, bit3, bit4]);
let channels = bit_channels * u64x4::splat(u64::MAX);
Self { channels }
}
#[inline(always)]
pub fn get(&self, index: Index) -> Nibble {
unsafe { self.get_unchecked(index.get()) }
}
#[inline(always)]
pub fn set(&mut self, index: Index, value: Nibble) {
unsafe { self.set_unchecked(index.get(), value) }
}
#[inline(always)]
pub unsafe fn get_unchecked(&self, index: u8) -> Nibble {
let mask = u64x4::splat(1 << index);
let masked_board = self.channels & mask;
let bits = masked_board >> (index as u64);
let values = bits << u64x4::from_array([0, 1, 2, 3]);
unsafe { Nibble::new_unchecked(u64x4_channel_sum(values) as u8) }
}
#[inline(always)]
pub unsafe fn set_unchecked(&mut self, index: u8, value: Nibble) {
let value: u8 = value.get();
let (bit1, bit2, bit3, bit4) = unsafe { lower_nibble_bits(value) };
let channel1 = bit1 << index;
let channel2 = bit2 << index;
let channel3 = bit3 << index;
let channel4 = bit4 << index;
let mut mask = u64x4::splat(u64::MAX);
let clear_mask = !(1 << index);
mask &= u64x4::splat(clear_mask);
self.channels &= mask;
self.channels |= u64x4::from_array([channel1, channel2, channel3, channel4]);
}
}
#[inline(always)]
const fn u64x4_channel_sum(value: u64x4) -> u64 {
let arr = value.to_array();
arr[0] + arr[1] + arr[2] + arr[3]
}
#[inline(always)]
const unsafe fn lower_nibble_bits(value: u8) -> (u64, u64, u64, u64) {
let bit1 = (value & 0b0001) as u64;
let bit2 = ((value & 0b0010) >> 1) as u64;
let bit3 = ((value & 0b0100) >> 2) as u64;
let bit4 = (value >> 3) as u64;
(bit1, bit2, bit3, bit4)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn raw_quadboard_new_is_all_zero() {
let qb = RawQuadboard::default();
let prod: u64 = qb.into_channels().into_iter().product();
assert!(prod == 0);
}
#[test]
fn raw_quadboard_set_unchecked_is_correct() {
let mut rqb = RawQuadboard::default();
unsafe {
rqb.set_unchecked(0, Nibble::try_from(0b1111).unwrap());
rqb.set_unchecked(5, Nibble::try_from(0b1101).unwrap());
rqb.set_unchecked(32, Nibble::try_from(0b1111).unwrap());
rqb.set_unchecked(63, Nibble::try_from(0b0111).unwrap());
}
let channels = rqb.into_channels();
for (i, lane) in channels.iter().enumerate() {
eprintln!("channel {}: 0x{:016x}", i, lane);
}
assert_eq!(channels[0], 0x8000000100000021);
assert_eq!(channels[1], 0x8000000100000001);
assert_eq!(channels[2], 0x8000000100000021);
assert_eq!(channels[3], 0x0000000100000021);
}
#[test]
fn raw_quadboard_get_unchecked_is_correct() {
let mut rqb = RawQuadboard::default();
unsafe {
rqb.set_unchecked(17, Nibble::try_from(0b1111).unwrap());
rqb.set_unchecked(3, Nibble::try_from(0b1001).unwrap());
rqb.set_unchecked(38, Nibble::try_from(0b0100).unwrap());
assert_eq!(0b1111, rqb.get_unchecked(17).get());
assert_eq!(0b1001, rqb.get_unchecked(3).get());
assert_eq!(0b0100, rqb.get_unchecked(38).get());
}
}
}