#![allow(clippy::float_cmp)]
use std::{
array,
cell::Cell,
ops::{
Bound,
RangeBounds,
},
slice,
};
use assert2::assert;
use flense::prelude::*;
use super::*;
#[test]
fn lens_can_lens() {
fn use_lens<'a>(original: &'a Vertex, lens: LensSlice<'a, (Normal, Other, Color, Position)>) {
let (lhs, rhs) = lens.split::<(Position,), _>();
assert!(lhs.get::<Position, _>(0).unwrap() == &original.position);
assert!(rhs.get::<Color, _>(0).unwrap() == &original.color);
assert!(rhs.get::<Normal, _>(0).unwrap() == &original.normal);
}
let some_vertex = Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: !0,
};
let some_vertex_slice = slice::from_ref(&some_vertex);
use_lens(&some_vertex, some_vertex_slice.lens_slice());
}
#[test]
fn split_last_element() {
let verts = [
Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
},
Vertex {
position: [10.0, 20.0, 30.0],
color: [40.0, 50.0, 60.0],
normal: [70, 80],
other: 0xBB,
},
];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
let (lhs, rhs) = lens.split::<(Position,), _>();
for (i, v) in verts.iter().enumerate() {
assert!(lhs.get::<Position, _>(i).unwrap() == &v.position);
assert!(rhs.get::<Color, _>(i).unwrap() == &v.color);
assert!(rhs.get::<Normal, _>(i).unwrap() == &v.normal);
assert!(rhs.get::<Other, _>(i).unwrap() == &v.other);
}
assert!(lhs.len() == 2);
assert!(rhs.len() == 2);
}
#[test]
fn split_first_element() {
let verts = [
Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
},
Vertex {
position: [10.0, 20.0, 30.0],
color: [40.0, 50.0, 60.0],
normal: [70, 80],
other: 0xBB,
},
];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
let (lhs, rhs) = lens.split::<(Normal,), _>();
for (i, v) in verts.iter().enumerate() {
assert!(lhs.get::<Normal, _>(i).unwrap() == &v.normal);
assert!(rhs.get::<Other, _>(i).unwrap() == &v.other);
assert!(rhs.get::<Color, _>(i).unwrap() == &v.color);
assert!(rhs.get::<Position, _>(i).unwrap() == &v.position);
}
}
#[test]
fn split_multi_element_non_contiguous() {
let verts = [
Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
},
Vertex {
position: [10.0, 20.0, 30.0],
color: [40.0, 50.0, 60.0],
normal: [70, 80],
other: 0xBB,
},
Vertex {
position: [100.0, 200.0, 300.0],
color: [400.0, 500.0, 600.0],
normal: [9, 10],
other: 0xCC,
},
];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
let (lhs, rhs) = lens.split::<(Other, Position), _>();
for (i, v) in verts.iter().enumerate() {
assert!(lhs.get::<Other, _>(i).unwrap() == &v.other);
assert!(lhs.get::<Position, _>(i).unwrap() == &v.position);
assert!(rhs.get::<Normal, _>(i).unwrap() == &v.normal);
assert!(rhs.get::<Color, _>(i).unwrap() == &v.color);
}
}
#[test]
fn split_reordered_lhs() {
let v = Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
};
let verts = slice::from_ref(&v);
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.lens_slice();
let (lhs, rhs) = lens.split::<(Position, Normal), _>();
assert!(lhs.get::<Position, _>(0).unwrap() == &v.position);
assert!(lhs.get::<Normal, _>(0).unwrap() == &v.normal);
assert!(rhs.get::<Other, _>(0).unwrap() == &v.other);
assert!(rhs.get::<Color, _>(0).unwrap() == &v.color);
}
#[test]
fn split_empty_lhs() {
let v = Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
};
let verts = slice::from_ref(&v);
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.lens_slice();
let (_lhs, rhs): (LensSlice<'_, ()>, _) = lens.split::<(), _>();
assert!(rhs.get::<Normal, _>(0).unwrap() == &v.normal);
assert!(rhs.get::<Other, _>(0).unwrap() == &v.other);
assert!(rhs.get::<Color, _>(0).unwrap() == &v.color);
assert!(rhs.get::<Position, _>(0).unwrap() == &v.position);
}
#[expect(clippy::type_complexity)]
#[test]
fn split_full_lhs() {
let v = Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
};
let verts = slice::from_ref(&v);
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.lens_slice();
let (lhs, _rhs): (
LensSlice<'_, (Position, Color, Other, Normal)>,
LensSlice<'_, ()>,
) = lens.split::<(Position, Color, Other, Normal), _>();
assert!(lhs.get::<Position, _>(0).unwrap() == &v.position);
assert!(lhs.get::<Color, _>(0).unwrap() == &v.color);
assert!(lhs.get::<Other, _>(0).unwrap() == &v.other);
assert!(lhs.get::<Normal, _>(0).unwrap() == &v.normal);
}
#[test]
fn split_chained() {
let v = Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
};
let verts = slice::from_ref(&v);
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.lens_slice();
let (a, rest1) = lens.split::<(Color,), _>();
let (b, rest2) = rest1.split::<(Other,), _>();
assert!(a.get::<Color, _>(0).unwrap() == &v.color);
assert!(b.get::<Other, _>(0).unwrap() == &v.other);
assert!(rest2.get::<Normal, _>(0).unwrap() == &v.normal);
assert!(rest2.get::<Position, _>(0).unwrap() == &v.position);
}
#[allow(
clippy::cast_possible_wrap,
reason = "Vertex size is far below isize::MAX"
)]
#[test]
fn split_preserves_stride() {
let verts = [Vertex::default(); 3];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
let (lhs, rhs) = lens.split::<(Other, Position), _>();
let stride = size_of::<Vertex>() as isize;
assert!(*lhs.strides::<Other, _>() == stride);
assert!(*lhs.strides::<Position, _>() == stride);
assert!(*rhs.strides::<Normal, _>() == stride);
assert!(*rhs.strides::<Color, _>() == stride);
}
#[test]
fn split_permuted_lhs_size_mismatch() {
let v = Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xDEAD_BEEF_CAFE_BABE,
};
let verts = slice::from_ref(&v);
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.lens_slice();
macro_rules! check_split {
($lens:expr, ($($lhs:ident),+ $(,)?), in_lhs: [$($l_field:ident: $l_expr:expr),* $(,)?], in_rhs: [$($r_field:ident: $r_expr:expr),* $(,)?] $(,)?) => {{
let (lhs, rhs) = $lens.split::<($($lhs,)+), _>();
$(
assert!(lhs.get::<$l_field, _>(0).unwrap() == &$l_expr);
)*
$(
assert!(rhs.get::<$r_field, _>(0).unwrap() == &$r_expr);
)*
}};
}
check_split!(lens, (Normal),
in_lhs: [Normal: v.normal],
in_rhs: [Other: v.other, Color: v.color, Position: v.position]);
check_split!(lens, (Other),
in_lhs: [Other: v.other],
in_rhs: [Normal: v.normal, Color: v.color, Position: v.position]);
check_split!(lens, (Color),
in_lhs: [Color: v.color],
in_rhs: [Normal: v.normal, Other: v.other, Position: v.position]);
check_split!(lens, (Position),
in_lhs: [Position: v.position],
in_rhs: [Normal: v.normal, Other: v.other, Color: v.color]);
check_split!(lens, (Normal, Other),
in_lhs: [Normal: v.normal, Other: v.other],
in_rhs: [Color: v.color, Position: v.position]);
check_split!(lens, (Other, Normal),
in_lhs: [Other: v.other, Normal: v.normal],
in_rhs: [Color: v.color, Position: v.position]);
check_split!(lens, (Normal, Position),
in_lhs: [Normal: v.normal, Position: v.position],
in_rhs: [Other: v.other, Color: v.color]);
check_split!(lens, (Position, Normal),
in_lhs: [Position: v.position, Normal: v.normal],
in_rhs: [Other: v.other, Color: v.color]);
check_split!(lens, (Color, Other),
in_lhs: [Color: v.color, Other: v.other],
in_rhs: [Normal: v.normal, Position: v.position]);
check_split!(lens, (Other, Color),
in_lhs: [Other: v.other, Color: v.color],
in_rhs: [Normal: v.normal, Position: v.position]);
check_split!(lens, (Color, Position),
in_lhs: [Color: v.color, Position: v.position],
in_rhs: [Normal: v.normal, Other: v.other]);
check_split!(lens, (Position, Color),
in_lhs: [Position: v.position, Color: v.color],
in_rhs: [Normal: v.normal, Other: v.other]);
check_split!(lens, (Normal, Color),
in_lhs: [Normal: v.normal, Color: v.color],
in_rhs: [Other: v.other, Position: v.position]);
check_split!(lens, (Color, Normal),
in_lhs: [Color: v.color, Normal: v.normal],
in_rhs: [Other: v.other, Position: v.position]);
check_split!(lens, (Other, Position),
in_lhs: [Other: v.other, Position: v.position],
in_rhs: [Normal: v.normal, Color: v.color]);
check_split!(lens, (Position, Other),
in_lhs: [Position: v.position, Other: v.other],
in_rhs: [Normal: v.normal, Color: v.color]);
check_split!(lens, (Position, Color, Normal),
in_lhs: [Position: v.position, Color: v.color, Normal: v.normal],
in_rhs: [Other: v.other]);
check_split!(lens, (Other, Position, Normal),
in_lhs: [Other: v.other, Position: v.position, Normal: v.normal],
in_rhs: [Color: v.color]);
check_split!(lens, (Normal, Position, Color),
in_lhs: [Normal: v.normal, Position: v.position, Color: v.color],
in_rhs: [Other: v.other]);
check_split!(lens, (Color, Normal, Other),
in_lhs: [Color: v.color, Normal: v.normal, Other: v.other],
in_rhs: [Position: v.position]);
}
#[test]
fn lens_slice_get() {
let verts = [
Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
},
Vertex {
position: [10.0, 20.0, 30.0],
color: [40.0, 50.0, 60.0],
normal: [70, 80],
other: 0xBB,
},
Vertex {
position: [100.0, 200.0, 300.0],
color: [400.0, 500.0, 600.0],
normal: [9, 10],
other: 0xCC,
},
];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
for (i, v) in verts.iter().enumerate() {
assert!(*lens.get::<Position, _>(i).unwrap() == v.position);
assert!(*lens.get::<Color, _>(i).unwrap() == v.color);
assert!(*lens.get::<Normal, _>(i).unwrap() == v.normal);
assert!(*lens.get::<Other, _>(i).unwrap() == v.other);
}
assert!(lens.get::<Position, _>(verts.len()).is_none());
}
#[test]
fn lens_slice_get_all() {
let verts = [
Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
},
Vertex {
position: [10.0, 20.0, 30.0],
color: [40.0, 50.0, 60.0],
normal: [70, 80],
other: 0xBB,
},
Vertex {
position: [100.0, 200.0, 300.0],
color: [400.0, 500.0, 600.0],
normal: [9, 10],
other: 0xCC,
},
];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
for (i, v) in verts.iter().enumerate() {
let lens = lens.get_all(i).unwrap();
assert!(*lens.as_ref::<Position, _>() == v.position);
assert!(*lens.as_ref::<Color, _>() == v.color);
assert!(*lens.as_ref::<Normal, _>() == v.normal);
assert!(*lens.as_ref::<Other, _>() == v.other);
}
assert!(lens.get_all(verts.len()).is_none());
}
#[test]
fn lens_slice_get_all_out_of_bounds() {
let verts: [Vertex; 0] = [];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
assert!(lens.is_empty());
assert!(lens.get_all(0).is_none());
}
#[test]
fn slice_subrange() {
let verts = [Vertex::default(); 4];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
assert!(lens.slice(..).unwrap().len() == 4);
let sub = lens.slice(1..3).unwrap();
assert!(sub.len() == 2);
assert!(sub.get::<Position, _>(0).unwrap() == &verts[1].position);
assert!(sub.get::<Position, _>(1).unwrap() == &verts[2].position);
assert!(sub.get::<Position, _>(2).is_none());
let end = lens.slice(4..4).unwrap();
assert!(end.is_empty());
assert!(end.get::<Position, _>(0).is_none());
assert!(lens.slice(3..5).is_none());
#[allow(
clippy::reversed_empty_ranges,
reason = "deliberately testing a reversed range"
)]
let reversed = lens.slice(3..1);
assert!(reversed.is_none());
}
#[test]
fn slice_overflow_bounds_are_none() {
let verts = [Vertex::default(); 4];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
assert!(lens.slice(0..=usize::MAX).is_none());
assert!(lens.slice(2..=usize::MAX).is_none());
assert!(
lens.slice((Bound::Excluded(usize::MAX), Bound::Unbounded))
.is_none()
);
assert!(
lens.slice((Bound::Excluded(usize::MAX), Bound::Included(usize::MAX)))
.is_none()
);
assert!(
lens.slice((Bound::Unbounded, Bound::Included(usize::MAX)))
.is_none()
);
}
struct OnceThenOob {
evaluations: Cell<usize>,
}
impl RangeBounds<usize> for OnceThenOob {
fn start_bound(&self) -> Bound<&usize> {
let n = self.evaluations.get();
self.evaluations.set(n + 1);
if n == 0 {
Bound::Included(&0)
} else {
Bound::Included(&1000)
}
}
fn end_bound(&self) -> Bound<&usize> {
if self.evaluations.get() <= 1 {
Bound::Excluded(&1)
} else {
Bound::Excluded(&1001)
}
}
}
#[test]
fn slice_resolves_stateful_range_once() {
let verts = [
Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
},
Vertex {
position: [10.0, 20.0, 30.0],
color: [40.0, 50.0, 60.0],
normal: [70, 80],
other: 0xBB,
},
];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
let range = OnceThenOob {
evaluations: Cell::new(0),
};
let sub = lens.slice(range).unwrap();
assert!(sub.len() == 1);
assert!(sub.get::<Position, _>(0).unwrap() == &verts[0].position);
}
#[test]
fn empty_slice_lens() {
let verts: [Vertex; 0] = [];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
assert!(lens.is_empty());
assert!(lens.len() == 0);
assert!(lens.get::<Position, _>(0).is_none());
assert!(lens.into_iter().count() == 0);
assert!(lens.into_iter().rev().count() == 0);
}
#[test]
fn rev_iter_yields_reverse_order() {
let verts: [Vertex; 4] = array::from_fn(|i| Vertex {
other: i as u64,
..Vertex::default()
});
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
let collected: Vec<u64> = lens
.into_iter()
.rev()
.map(|l| *l.as_ref::<Other, _>())
.collect();
assert!(collected == [3, 2, 1, 0]);
}
#[test]
fn lens_slice_get_all_after_split() {
let verts = [
Vertex {
position: [1.0, 2.0, 3.0],
color: [4.0, 5.0, 6.0],
normal: [7, 8],
other: 0xAA,
},
Vertex {
position: [10.0, 20.0, 30.0],
color: [40.0, 50.0, 60.0],
normal: [70, 80],
other: 0xBB,
},
];
let lens: LensSlice<'_, (Normal, Other, Color, Position)> = verts.as_slice().lens_slice();
let (lhs, rhs) = lens.split::<(Position,), _>();
let l = lhs.get_all(0).unwrap();
assert!(*l.as_ref::<Position, _>() == verts[0].position);
let r = rhs.get_all(1).unwrap();
assert!(*r.as_ref::<Color, _>() == verts[1].color);
assert!(*r.as_ref::<Normal, _>() == verts[1].normal);
assert!(*r.as_ref::<Other, _>() == verts[1].other);
}