use core::{
array,
iter::FusedIterator,
marker::PhantomData,
ops::RangeBounds,
};
use crate::{
Field,
dim::Dim,
lens::raw,
lenses::LensesBase,
type_lists::{
Concat,
ConsSet,
GetMetadata,
GetPtr,
Mutate,
Sculpt,
TupleSet,
},
};
pub struct LensBase<'a, T, const N: usize>
where
T: TupleSet,
T::Cons<N>: 'a,
{
storage: T::Cons<N>,
dimensions: Dim<usize, N>,
_phantom: PhantomData<&'a T>,
}
impl<'a, T, const N: usize> LensesBase<'a, T, N> for LensBase<'a, T, N>
where
T: TupleSet,
{
#[inline]
fn lens_base(self) -> Self {
self
}
}
impl<T, const N: usize> Clone for LensBase<'_, T, N>
where
T: TupleSet,
T::Cons<N>: Clone,
{
#[inline]
fn clone(&self) -> Self {
Self {
storage: self.storage.clone(),
dimensions: self.dimensions,
_phantom: PhantomData,
}
}
}
impl<T, const N: usize> Copy for LensBase<'_, T, N>
where
T: TupleSet,
T::Cons<N>: Copy,
{
}
impl<'a, T> LensBase<'a, T, 0>
where
T: TupleSet,
{
#[expect(
clippy::should_implement_trait,
reason = "signature is generic over the field type, not interchangeable with `AsRef`"
)]
#[inline]
#[must_use]
pub fn as_ref<Elt, Index>(&self) -> &'a Elt::Type
where
Elt: Field,
T::Cons<0>: GetPtr<0, Elt, Index>,
{
unsafe { &*self.storage.get_ptr().as_ptr() }
}
#[expect(clippy::type_complexity, reason = "necessary type inference")]
#[inline]
#[must_use]
pub fn join<Rhs>(
self,
other: impl LensesBase<'a, Rhs, 0>,
) -> LensBase<
'a,
<<T::Cons<0> as Concat<0, T::Cons<0>, Rhs::Cons<0>>>::Result as ConsSet<0>>::Tuple,
0,
>
where
Rhs: 'a + TupleSet,
Rhs::Cons<0>: 'a,
T::Cons<0>: Concat<
0,
T::Cons<0>,
Rhs::Cons<0>,
Result: ConsSet<
0,
Tuple: TupleSet<
Cons<0> = <T::Cons<0> as Concat<0, T::Cons<0>, Rhs::Cons<0>>>::Result,
>,
>,
>,
{
LensBase {
storage: self.storage.concat(other.lens_base().storage),
dimensions: self.dimensions,
_phantom: PhantomData,
}
}
}
impl<'a, T, const N: usize> LensBase<'a, T, N>
where
T: TupleSet,
{
#[inline]
#[must_use]
pub(crate) const fn new(storage: T::Cons<N>, dimensions: Dim<usize, N>) -> Self {
Self {
storage,
dimensions,
_phantom: PhantomData,
}
}
#[inline]
#[must_use]
pub fn len(&self) -> usize {
self.dimensions.0.into_iter().product()
}
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self.dimensions.0.into_iter().any(|elt| elt == 0)
}
#[inline]
#[must_use]
pub fn sizes(&self) -> Dim<usize, N> {
self.dimensions
}
#[inline]
#[must_use]
pub fn strides<Elt, Index>(&self) -> Dim<isize, N>
where
Elt: Field,
T::Cons<N>: GetMetadata<N, Elt, Index>,
{
*self.storage.get_metadata()
}
#[inline]
#[must_use]
pub fn as_ptr<Elt, Index>(&self) -> *const Elt::Type
where
Elt: Field,
T::Cons<N>: GetPtr<N, Elt, Index>,
{
self.storage.get_ptr().as_ptr()
}
#[inline]
#[must_use]
pub fn get<Elt, Index>(&self, pos: impl Into<Dim<usize, N>>) -> Option<&'a Elt::Type>
where
Elt: Field,
T::Cons<N>: GetPtr<N, Elt, Index> + GetMetadata<N, Elt, Index>,
{
let pos = pos.into();
if self.dimensions.contains(&pos) {
Some(unsafe { self.get_unchecked(pos) })
} else {
None
}
}
#[inline]
#[must_use]
pub unsafe fn get_unchecked<Elt, Index>(&self, pos: impl Into<Dim<usize, N>>) -> &'a Elt::Type
where
Elt: Field,
T::Cons<N>: GetPtr<N, Elt, Index> + GetMetadata<N, Elt, Index>,
{
unsafe { &*raw::elem_ptr::<T, Elt, Index, N>(&self.storage, pos.into()) }
}
#[inline]
#[must_use]
pub fn get_all(&self, pos: impl Into<Dim<usize, N>>) -> Option<LensBase<'a, T, 0>>
where
T::Cons<N>: Mutate<N, 0, Result = T::Cons<0>>,
{
let pos = pos.into();
if self.dimensions.contains(&pos) {
Some(unsafe { self.get_all_unchecked(pos) })
} else {
None
}
}
#[inline]
#[must_use]
pub unsafe fn get_all_unchecked(&self, pos: impl Into<Dim<usize, N>>) -> LensBase<'a, T, 0>
where
T::Cons<N>: Mutate<N, 0, Result = T::Cons<0>>,
{
LensBase::new(
unsafe { raw::project_point::<T, N>(&self.storage, pos.into()) },
Dim([]),
)
}
#[inline]
#[must_use]
pub fn slice<R>(&self, range: impl Into<Dim<R, N>>) -> Option<Self>
where
T::Cons<N>: Mutate<N, N, Result = T::Cons<N>>,
R: RangeBounds<usize>,
{
let (start, end) = range.into().to_indices(self.dimensions)?;
if start.is_compatible_with(&end) && end.is_compatible_with(&self.dimensions) {
Some(unsafe { self.slice_from_indices(start, end) })
} else {
None
}
}
#[inline]
#[must_use]
pub unsafe fn slice_unchecked<R>(&self, range: impl Into<Dim<R, N>>) -> Self
where
T::Cons<N>: Mutate<N, N, Result = T::Cons<N>>,
R: RangeBounds<usize>,
{
let (start, end) = unsafe { range.into().to_indices(self.dimensions).unwrap_unchecked() };
unsafe { self.slice_from_indices(start, end) }
}
#[inline]
#[must_use]
unsafe fn slice_from_indices(&self, start: Dim<usize, N>, end: Dim<usize, N>) -> Self
where
T::Cons<N>: Mutate<N, N, Result = T::Cons<N>>,
{
Self::new(
unsafe { raw::project_slice::<T, N>(&self.storage, start) },
end - start,
)
}
#[inline]
pub fn materialize<U, URet>(self, f: U) -> (Dim<usize, N>, impl AsRef<[URet]> + 'a)
where
U: 'a + Fn(*const (), usize, Dim<isize, N>) -> URet,
URet: 'a,
{
(
self.dimensions,
self.storage
.materialize(move |ptr, size, strides| f(ptr.as_ptr().cast_const(), size, strides)),
)
}
#[expect(clippy::type_complexity, reason = "necessary type inference")]
#[inline]
#[must_use]
pub fn split<Lhs, Indices>(
self,
) -> (
LensBase<'a, Lhs, N>,
LensBase<
'a,
<<T::Cons<N> as Sculpt<Lhs::Cons<N>, Indices>>::Remainder as ConsSet<N>>::Tuple,
N,
>,
)
where
Lhs: TupleSet,
T::Cons<N>: Sculpt<
Lhs::Cons<N>,
Indices,
Remainder: ConsSet<
N,
Tuple: TupleSet<
Cons<N> = <T::Cons<N> as Sculpt<Lhs::Cons<N>, Indices>>::Remainder,
>,
>,
>,
{
let (lhs_storage, rem_storage) = self.storage.sculpt();
(
LensBase {
storage: lhs_storage,
dimensions: self.dimensions,
_phantom: PhantomData,
},
LensBase {
storage: rem_storage,
dimensions: self.dimensions,
_phantom: PhantomData,
},
)
}
#[expect(clippy::type_complexity, reason = "necessary type inference")]
#[inline]
#[must_use]
pub fn try_join<Rhs>(
self,
other: impl LensesBase<'a, Rhs, N>,
) -> Option<
LensBase<
'a,
<<T::Cons<N> as Concat<N, T::Cons<N>, Rhs::Cons<N>>>::Result as ConsSet<N>>::Tuple,
N,
>,
>
where
Rhs: 'a + TupleSet,
Rhs::Cons<N>: 'a,
T::Cons<N>: Concat<
N,
T::Cons<N>,
Rhs::Cons<N>,
Result: ConsSet<
N,
Tuple: TupleSet<
Cons<N> = <T::Cons<N> as Concat<N, T::Cons<N>, Rhs::Cons<N>>>::Result,
>,
>,
>,
{
let other = other.lens_base();
if self.dimensions.is_compatible_with(&other.dimensions) {
Some(LensBase {
storage: self.storage.concat(other.storage),
dimensions: self.dimensions,
_phantom: PhantomData,
})
} else {
None
}
}
#[expect(clippy::type_complexity, reason = "necessary type inference")]
#[inline]
#[must_use]
pub fn zip<Rhs>(
self,
other: impl LensesBase<'a, Rhs, N>,
) -> LensBase<
'a,
<<T::Cons<N> as Concat<N, T::Cons<N>, Rhs::Cons<N>>>::Result as ConsSet<N>>::Tuple,
N,
>
where
Rhs: 'a + TupleSet,
Rhs::Cons<N>: 'a,
T::Cons<N>: Concat<
N,
T::Cons<N>,
Rhs::Cons<N>,
Result: ConsSet<
N,
Tuple: TupleSet<
Cons<N> = <T::Cons<N> as Concat<N, T::Cons<N>, Rhs::Cons<N>>>::Result,
>,
>,
>,
{
let other = other.lens_base();
LensBase {
storage: self.storage.concat(other.storage),
dimensions: self.dimensions.min(&other.dimensions),
_phantom: PhantomData,
}
}
}
#[expect(clippy::type_complexity, reason = "necessary type inference")]
#[inline]
#[must_use]
pub fn join_lens<'a, Lhs, Rhs>(
lhs: impl LensesBase<'a, Lhs, 0>,
rhs: impl LensesBase<'a, Rhs, 0>,
) -> LensBase<
'a,
<<Lhs::Cons<0> as Concat<0, Lhs::Cons<0>, Rhs::Cons<0>>>::Result as ConsSet<0>>::Tuple,
0,
>
where
Lhs: 'a + TupleSet,
Lhs::Cons<0>: 'a
+ Concat<
0,
Lhs::Cons<0>,
Rhs::Cons<0>,
Result: ConsSet<
0,
Tuple: TupleSet<
Cons<0> = <Lhs::Cons<0> as Concat<0, Lhs::Cons<0>, Rhs::Cons<0>>>::Result,
>,
>,
>,
Rhs: 'a + TupleSet,
Rhs::Cons<0>: 'a,
{
lhs.lens_base().join(rhs.lens_base())
}
#[expect(clippy::type_complexity, reason = "necessary type inference")]
#[inline]
#[must_use]
pub fn try_join_lens<'a, Lhs, Rhs, const N: usize>(
lhs: impl LensesBase<'a, Lhs, N>,
rhs: impl LensesBase<'a, Rhs, N>,
) -> Option<
LensBase<
'a,
<<Lhs::Cons<N> as Concat<N, Lhs::Cons<N>, Rhs::Cons<N>>>::Result as ConsSet<N>>::Tuple,
N,
>,
>
where
Lhs: 'a + TupleSet,
Lhs::Cons<N>: 'a
+ Concat<
N,
Lhs::Cons<N>,
Rhs::Cons<N>,
Result: ConsSet<
N,
Tuple: TupleSet<
Cons<N> = <Lhs::Cons<N> as Concat<N, Lhs::Cons<N>, Rhs::Cons<N>>>::Result,
>,
>,
>,
Rhs: 'a + TupleSet,
Rhs::Cons<N>: 'a,
{
lhs.lens_base().try_join(rhs.lens_base())
}
#[expect(clippy::type_complexity, reason = "necessary type inference")]
#[inline]
#[must_use]
pub fn zip_lens<'a, Lhs, Rhs, const N: usize>(
lhs: impl LensesBase<'a, Lhs, N>,
rhs: impl LensesBase<'a, Rhs, N>,
) -> LensBase<
'a,
<<Lhs::Cons<N> as Concat<N, Lhs::Cons<N>, Rhs::Cons<N>>>::Result as ConsSet<N>>::Tuple,
N,
>
where
Lhs: 'a + TupleSet,
Lhs::Cons<N>: 'a
+ Concat<
N,
Lhs::Cons<N>,
Rhs::Cons<N>,
Result: ConsSet<
N,
Tuple: TupleSet<
Cons<N> = <Lhs::Cons<N> as Concat<N, Lhs::Cons<N>, Rhs::Cons<N>>>::Result,
>,
>,
>,
Rhs: 'a + TupleSet,
Rhs::Cons<N>: 'a,
{
lhs.lens_base().zip(rhs.lens_base())
}
macro_rules! impl_into_iterator {
($($n:literal => $m:literal),* $(,)?) => {$(
impl<'a, T> IntoIterator for LensBase<'a, T, $n>
where
T: TupleSet,
T::Cons<$m>: 'a,
T::Cons<$n>: Mutate<$n, $m, Result = T::Cons<$m>>,
T::Cons<$n>: Mutate<$n, $n, Result = T::Cons<$n>>,
{
type Item = <LensBaseIter<'a, T, $n, $m> as Iterator>::Item;
type IntoIter = LensBaseIter<'a, T, $n, $m>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
LensBaseIter { lens: self }
}
}
)*};
}
impl_into_iterator!(
1 => 0,
2 => 1,
3 => 2,
4 => 3,
5 => 4,
6 => 5,
7 => 6,
8 => 7,
);
pub struct LensBaseIter<'a, T, const N: usize, const M: usize>
where
T: TupleSet,
{
lens: LensBase<'a, T, N>,
}
impl<'a, T, const N: usize, const M: usize> LensBaseIter<'a, T, N, M>
where
T: TupleSet,
T::Cons<M>: 'a,
T::Cons<N>: Mutate<N, M, Result = T::Cons<M>>,
{
#[expect(
clippy::indexing_slicing,
reason = "N is at least 1, enforced by the `IntoIterator` impls"
)]
#[inline]
#[must_use]
const fn end(&self) -> usize {
self.lens.dimensions.0[N - 1]
}
#[expect(
clippy::indexing_slicing,
reason = "axis indices are bounded by N, and M is always N - 1"
)]
#[inline]
#[must_use]
unsafe fn get_unchecked(&self, index: usize) -> LensBase<'a, T, M> {
#[expect(
clippy::cast_possible_wrap,
reason = "index is less than a dimension length, which must fit in `isize`"
)]
let index = index as isize;
LensBase::new(
unsafe { raw::project_hyperplane::<T, N, M>(&self.lens.storage, index) },
Dim(array::from_fn(|axis| self.lens.dimensions.0[axis])),
)
}
}
impl<'a, T, const N: usize, const M: usize> Iterator for LensBaseIter<'a, T, N, M>
where
T: TupleSet,
T::Cons<M>: 'a,
T::Cons<N>: Mutate<N, M, Result = T::Cons<M>>,
T::Cons<N>: Mutate<N, N, Result = T::Cons<N>>,
{
type Item = LensBase<'a, T, M>;
#[expect(
clippy::indexing_slicing,
reason = "N is at least 1, enforced by the `IntoIterator` impls"
)]
#[inline]
fn next(&mut self) -> Option<Self::Item> {
if self.end() > 0 {
self.lens.dimensions.0[N - 1] -= 1;
let item = LensBase::new(
unsafe { raw::drop_last_axis::<T, N, M>(&self.lens.storage) },
Dim(array::from_fn(|axis| self.lens.dimensions.0[axis])),
);
self.lens.storage = unsafe { raw::advance_last_axis::<T, N>(&self.lens.storage) };
Some(item)
} else {
None
}
}
#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.end();
(len, Some(len))
}
}
impl<'a, T, const N: usize, const M: usize> ExactSizeIterator for LensBaseIter<'a, T, N, M>
where
T: TupleSet,
T::Cons<M>: 'a,
T::Cons<N>: Mutate<N, M, Result = T::Cons<M>>,
T::Cons<N>: Mutate<N, N, Result = T::Cons<N>>,
{
#[inline]
fn len(&self) -> usize {
self.end()
}
}
impl<'a, T, const N: usize, const M: usize> DoubleEndedIterator for LensBaseIter<'a, T, N, M>
where
T: TupleSet,
T::Cons<M>: 'a,
T::Cons<N>: Mutate<N, M, Result = T::Cons<M>>,
T::Cons<N>: Mutate<N, N, Result = T::Cons<N>>,
{
#[expect(
clippy::indexing_slicing,
reason = "N is at least 1, enforced by the `IntoIterator` impls"
)]
#[inline]
fn next_back(&mut self) -> Option<Self::Item> {
if self.end() > 0 {
self.lens.dimensions.0[N - 1] -= 1;
Some(unsafe { self.get_unchecked(self.end()) })
} else {
None
}
}
}
impl<'a, T, const N: usize, const M: usize> FusedIterator for LensBaseIter<'a, T, N, M>
where
T: TupleSet,
T::Cons<M>: 'a,
T::Cons<N>: Mutate<N, M, Result = T::Cons<M>>,
T::Cons<N>: Mutate<N, N, Result = T::Cons<N>>,
{
}