layout 0.0.1

Optimized memory layout using struct of array, Data-oriented design in Rust, DOD SOA
Documentation
#![allow(unexpected_cfgs)]
#![cfg(rustc_is_at_least_1_78)]

mod particles;
use core::marker::PhantomData;
use core::fmt::Debug;

use particles::ParticleVec;
use layout::{SoAVec, SoASlice, SOA};

use self::particles::Particle;

fn may_iter<T: SOA, V: SoAVec<T>>(vec: &V) -> V::Iter<'_> {
    vec.iter()
}

fn may_push<T: SOA, V: SoAVec<T>>(vec: &mut V, val: T) {
    vec.push(val)
}

fn may_sort_generic<T: SOA, V: SoAVec<T>>(vec: &mut V) where for<'t> V::Ref<'t> : PartialOrd {
    let mut indices: Vec<_> = (0..vec.len()).collect();

    indices.sort_by(|j, k| {
        let a = vec.index(*j);
        let b = vec.index(*k);
        a.partial_cmp(&b).unwrap()
    });

    vec.apply_index(&indices);
}


fn may_closure_sort<V: SoAVec<Particle>, F>(vec: &mut V, mut f: F) where F: FnMut(V::Ref<'_>, V::Ref<'_>) -> core::cmp::Ordering {
    let mut indices: Vec<_> = (0..vec.len()).collect();

    indices.sort_by(|j, k| {
        let a = vec.index(*j);
        let b = vec.index(*k);
        f(a, b)
    });

    vec.apply_index(&indices);
}


#[test]
fn test_generic_type_behavior() {
    let mut x = ParticleVec::new();
    x.push(Particle::new("foo".into(), 100.0));
    let y: Vec<_> = may_iter::<Particle, ParticleVec>(&x).collect();
    assert_eq!(x.len(), y.len());
    assert_eq!(x.first().as_ref(), y.first());
    drop(y);

    let z = Particle::new("bar".into(), 1000.0);
    may_push(&mut x, z);
    assert_eq!(x.len(), 2);

    may_sort_generic(&mut x);
    assert_eq!(x.first().unwrap().name, "bar");

    x.sort_by(|a, b| a.mass.total_cmp(b.mass).reverse());
    // may_sort(&mut x);
    let a = x.index(0);
    let b = x.index(1);
    assert!(a.mass > b.mass);

    may_closure_sort(&mut x, |a, b| a.mass.total_cmp(b.mass));

    let a = x.index(0);
    let b = x.index(1);
    assert!(a.mass < b.mass);
}


#[derive(Debug, Clone)]
struct VecWrap<T: SOA, V: SoAVec<T>> {
    data: V,
    marker: PhantomData<T>
}

impl<T: SOA, V: SoAVec<T>> VecWrap<T, V> {
    fn new() -> Self {
        Self {
            data: V::new(),
            marker: PhantomData
        }
    }

    fn push(&mut self, value: T) {
        self.data.push(value);
    }

    fn iter(&self) -> V::Iter<'_> {
        self.data.iter()
    }

    fn view(&self) -> V::Slice<'_> {
        self.data.as_slice()
    }

    fn first(&self) -> Option<V::Ref<'_>> {
        self.data.first()
    }

    fn sort_by<F>(&mut self, f: F) where F: FnMut(V::Ref<'_>, V::Ref<'_>) -> core::cmp::Ordering {
        self.data.sort_by(f);
    }
}

#[test]
fn test_wrapped() {
    let mut this: VecWrap<Particle, ParticleVec> = VecWrap::new();
    let x = Particle::new("foo".into(), 100.0);
    this.push(x);
    let x = Particle::new("bar".into(), 1000.0);
    this.push(x);
    let x = Particle::new("baz".into(), 10.0);
    this.push(x);

    assert_eq!(this.iter().count(), 3);
    assert_eq!(this.view().len(), 3);
}

fn iter_max_generic<'a, T: SOA, V: SoASlice<T> + 'a>(vec: &'a V) -> Option<V::Ref<'a>>
where
    V::Ref<'a>: PartialOrd + Debug
{
    let x= vec.iter().reduce(|a, b| {
        if a.partial_cmp(&b).unwrap().is_ge() {
            a
        } else {
            b
        }
    });
    x
}

fn iter_max_generic_iter<'a, T: SOA, V: SoAVec<T>>(it: V::Iter<'a>) -> Option<V::Ref<'a>>
where
    V::Ref<'a>: PartialOrd
{
    it.reduce(|a: V::Ref<'_>, b: V::Ref<'_>| {
        if a.partial_cmp(&b).unwrap().is_ge() {
            a
        } else {
            b
        }
    })
}

fn slice_ref_len<T: SOA, V: SoAVec<T>>(vec: &V) -> usize {
    let view = vec.as_slice();
    let n = view.iter().count();
    assert_eq!(view.into_iter().count(), n);
    n
}


#[test]
fn test_ops() {
    let mut vec = ParticleVec::new();
    vec.push(Particle::new("foo".into(), 100.0));
    vec.push(Particle::new("bar".into(), 1000.0));
    vec.push(Particle::new("baz".into(), 50.0));
    // let x = iter_max_generic(&view);
    // eprintln!("{x:?}");
    let y = iter_max_generic_iter::<Particle, ParticleVec>(vec.iter());
    eprintln!("{y:?}");
    let k = slice_ref_len(&vec);
    assert_eq!(k, 3);

    let mut view = vec.as_mut_slice();
    view.iter_mut().for_each(|f| {
        *f.mass *= 2.0;
    });

    let view = vec.as_slice();
    let z = iter_max_generic(&view).unwrap();
    assert_eq!(z.name, "foo");

    let n = view.iter().count();
    assert!(n > 0);

    let mut vec = VecWrap::<Particle, ParticleVec>::new();
    vec.push(Particle::new("foo".into(), 100.0));
    vec.push(Particle::new("bar".into(), 1000.0));
    vec.push(Particle::new("baz".into(), 50.0));
    vec.sort_by(|a, b| a.mass.total_cmp(b.mass));

    assert_eq!(vec.first().unwrap().name, "baz");
}