pub trait Shape3 {
fn size(&self) -> [u32; 3];
fn linearize(&self, p: [u32; 3]) -> u32;
fn delinearize(&self, i: u32) -> [u32; 3];
#[inline]
fn element_count(&self) -> usize {
let [x, y, z] = self.size();
x as usize * y as usize * z as usize
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RuntimeShape3 {
size: [u32; 3],
}
impl RuntimeShape3 {
pub fn new(size: [u32; 3]) -> crate::Result<Self> {
let fits = size[0]
.checked_mul(size[1])
.and_then(|xy| xy.checked_mul(size[2]))
.is_some();
if fits {
Ok(Self { size })
} else {
Err(crate::Error::ShapeOverflow {
size,
product: u64::from(size[0]) * u64::from(size[1]) * u64::from(size[2]),
})
}
}
}
impl Shape3 for RuntimeShape3 {
#[inline]
fn size(&self) -> [u32; 3] {
self.size
}
#[inline]
fn linearize(&self, p: [u32; 3]) -> u32 {
debug_assert!(
p[0] < self.size[0] && p[1] < self.size[1] && p[2] < self.size[2],
"coordinate {p:?} out of range for shape {:?}",
self.size
);
p[0] + self.size[0] * (p[1] + self.size[1] * p[2])
}
#[inline]
fn delinearize(&self, i: u32) -> [u32; 3] {
debug_assert!(
(i as usize) < self.element_count(),
"index {i} out of range for shape {:?}",
self.size
);
let [sx, sy, _] = self.size;
[i % sx, (i / sx) % sy, i / (sx * sy)]
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ConstShape3<const X: u32, const Y: u32, const Z: u32>;
impl<const X: u32, const Y: u32, const Z: u32> ConstShape3<X, Y, Z> {
#[must_use]
pub const fn new() -> Self {
const { assert!(X as u64 * Y as u64 * Z as u64 <= u32::MAX as u64) }
Self
}
}
impl<const X: u32, const Y: u32, const Z: u32> Default for ConstShape3<X, Y, Z> {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl<const X: u32, const Y: u32, const Z: u32> Shape3 for ConstShape3<X, Y, Z> {
#[inline]
fn size(&self) -> [u32; 3] {
[X, Y, Z]
}
#[inline]
fn linearize(&self, p: [u32; 3]) -> u32 {
debug_assert!(
p[0] < X && p[1] < Y && p[2] < Z,
"coordinate {p:?} out of range for shape [{X}, {Y}, {Z}]"
);
p[0] + X * (p[1] + Y * p[2])
}
#[inline]
fn delinearize(&self, i: u32) -> [u32; 3] {
debug_assert!(
i < X * Y * Z,
"index {i} out of range for shape [{X}, {Y}, {Z}]"
);
[i % X, (i / X) % Y, i / (X * Y)]
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
const SX: u32 = 3;
const SY: u32 = 5;
const SZ: u32 = 7;
const N: u32 = SX * SY * SZ;
fn all_coords() -> Vec<[u32; 3]> {
let mut v = Vec::new();
for z in 0..SZ {
for y in 0..SY {
for x in 0..SX {
v.push([x, y, z]);
}
}
}
v
}
#[test]
fn runtime_shape_round_trips_non_cubic() {
let shape = RuntimeShape3::new([SX, SY, SZ]).expect("valid shape");
for i in 0..N {
assert_eq!(shape.linearize(shape.delinearize(i)), i);
}
for p in all_coords() {
assert_eq!(shape.delinearize(shape.linearize(p)), p);
}
}
#[test]
fn const_shape_round_trips_non_cubic() {
let shape = ConstShape3::<SX, SY, SZ>::new();
for i in 0..N {
assert_eq!(shape.linearize(shape.delinearize(i)), i);
}
for p in all_coords() {
assert_eq!(shape.delinearize(shape.linearize(p)), p);
}
}
#[test]
fn x_is_the_contiguous_axis() {
let shape = RuntimeShape3::new([SX, SY, SZ]).expect("valid shape");
assert_eq!(shape.linearize([1, 0, 0]), 1);
assert_eq!(shape.linearize([0, 1, 0]), SX);
assert_eq!(shape.linearize([0, 0, 1]), SX * SY);
}
#[test]
fn runtime_and_const_shapes_agree() {
let runtime = RuntimeShape3::new([SX, SY, SZ]).expect("valid shape");
let constant = ConstShape3::<SX, SY, SZ>::new();
assert_eq!(runtime.size(), constant.size());
assert_eq!(runtime.element_count(), constant.element_count());
for p in all_coords() {
assert_eq!(runtime.linearize(p), constant.linearize(p));
}
for i in 0..N {
assert_eq!(runtime.delinearize(i), constant.delinearize(i));
}
}
#[test]
fn element_count_is_the_product() {
assert_eq!(
RuntimeShape3::new([SX, SY, SZ])
.expect("valid shape")
.element_count(),
105
);
assert_eq!(ConstShape3::<SX, SY, SZ>::new().element_count(), 105);
}
#[test]
fn runtime_shape_rejects_overflow() {
let error = RuntimeShape3::new([u32::MAX, 2, 2]).expect_err("should not fit in u32");
assert!(
matches!(error, crate::Error::ShapeOverflow { .. }),
"{error}"
);
let text = alloc::format!("{error}");
assert!(text.contains("4294967295"), "{text}");
}
#[test]
fn shape3_is_dyn_compatible() {
const _: Option<&dyn Shape3> = None;
let shape = RuntimeShape3::new([SX, SY, SZ]).expect("valid shape");
let dynamic: &dyn Shape3 = &shape;
assert_eq!(dynamic.linearize([0, 1, 0]), SX);
}
}