diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2d9b712..1c6261d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - rust-version: ["1.63", stable, beta, nightly] + rust-version: ["1.78", stable, beta, nightly] steps: - uses: actions/checkout@v3 with: diff --git a/.gitignore b/.gitignore index fa8d85a..867b25e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ Cargo.lock target +rust-toolchain.toml \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 2988869..af9db43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "soa_derive" version = "0.13.0" edition = "2018" -rust-version = "1.63" +rust-version = "1.78" authors = ["Guillaume Fraux "] license = "MIT/Apache-2.0" @@ -30,4 +30,4 @@ serde_json = "1" [[bench]] name = "soa" -harness = false +harness = false \ No newline at end of file diff --git a/example/Cargo.toml b/example/Cargo.toml index 9996d8f..6df3c78 100644 --- a/example/Cargo.toml +++ b/example/Cargo.toml @@ -8,7 +8,7 @@ publish = false edition = "2021" [dependencies] -soa_derive = {path = ".."} +soa_derive = { path = ".." } [lib] path = "lib.rs" diff --git a/soa-derive-internal/Cargo.toml b/soa-derive-internal/Cargo.toml index 5a8a299..33ff78b 100644 --- a/soa-derive-internal/Cargo.toml +++ b/soa-derive-internal/Cargo.toml @@ -17,6 +17,7 @@ documentation = "https://docs.rs/soa_derive/" [lib] proc-macro = true + [dependencies] syn = {version = "2", features = ["derive", "extra-traits"]} quote = "1" diff --git a/soa-derive-internal/src/generic.rs b/soa-derive-internal/src/generic.rs new file mode 100644 index 0000000..9406440 --- /dev/null +++ b/soa-derive-internal/src/generic.rs @@ -0,0 +1,351 @@ +use proc_macro2::TokenStream; +use quote::quote; + +use crate::input::Input; +use crate::names; + + +pub fn derive_slice(input: &Input) -> TokenStream { + let name = &input.name; + let slice_name = names::slice_name(name); + let ref_name = names::ref_name(&input.name); + let ptr_name = names::ptr_name(&input.name); + let iter_name = names::iter_name(name); + + let generated = quote! { + impl<'a> ::soa_derive::SoASlice<#name> for #slice_name<'a> { + type Ref<'t> = #ref_name<'t> where Self: 't, 'a: 't; + type Slice<'t> = #slice_name<'t> where Self: 't, 'a: 't; + type Iter<'t> = #iter_name<'t> where Self: 't, 'a: 't; + type Ptr = #ptr_name; + + fn len(&self) -> usize { + self.len() + } + + fn is_empty(&self) -> bool { + self.is_empty() + } + + fn as_slice<'c>(&'c self) -> Self::Slice<'c> { + self.reborrow::<'c>() + } + + fn slice<'c, 'b: 'c>(&'c self, index: impl core::ops::RangeBounds) -> Self::Slice<'c> where Self: 'b { + let start = match index.start_bound() { + std::ops::Bound::Included(i) | std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => 0, + }; + let n = self.len(); + let end = match index.end_bound() { + std::ops::Bound::Included(i) => (*i + 1).min(n), + std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => n, + }; + self.index(start..end) + } + + fn get<'c>(&'c self, index: usize) -> Option> { + self.get(index) + } + + fn index<'c>(&'c self, index: usize) -> Self::Ref<'c> { + self.index(index) + } + + fn iter<'c>(&'c self) -> Self::Iter<'c> { + self.iter() + } + + fn as_ptr(&self) -> Self::Ptr { + self.as_ptr() + } + } + }; + + return generated +} + +pub fn derive_slice_mut(input: &Input) -> TokenStream { + let name = &input.name; + let slice_name = names::slice_name(name); + let slice_mut_name = names::slice_mut_name(&input.name); + let ref_name = names::ref_name(&input.name); + let ref_mut_name = names::ref_mut_name(&input.name); + let ptr_name = names::ptr_name(&input.name); + let ptr_mut_name = names::ptr_mut_name(&input.name); + let iter_name = names::iter_name(name); + let iter_mut_name = names::iter_mut_name(name); + + let generated = quote! { + + impl<'a> ::soa_derive::SoASliceMut<#name> for #slice_mut_name<'a> { + type Ref<'t> = #ref_name<'t> where Self: 't; + type Slice<'t> = #slice_name<'t> where Self: 't; + type Iter<'t> = #iter_name<'t> where Self: 't; + type Ptr = #ptr_name; + + type RefMut<'t> = #ref_mut_name<'t> where Self: 't; + type SliceMut<'t> = #slice_mut_name<'t> where Self: 't; + type IterMut<'t> = #iter_mut_name<'t> where Self: 't; + type PtrMut = #ptr_mut_name; + + fn len(&self) -> usize { + self.len() + } + + fn is_empty(&self) -> bool { + self.is_empty() + } + + fn as_slice<'c>(&'c self) -> Self::Slice<'c> { + self.as_slice() + } + + fn slice<'c, 'b: 'c>(&'c self, index: impl core::ops::RangeBounds) -> Self::Slice<'c> where Self: 'b { + let start = match index.start_bound() { + std::ops::Bound::Included(i) | std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => 0, + }; + let n = self.len(); + let end = match index.end_bound() { + std::ops::Bound::Included(i) => (*i + 1).min(n), + std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => n, + }; + self.index(start..end) + } + + fn get<'c>(&'c self, index: usize) -> Option> { + self.get(index) + } + + fn index<'c>(&'c self, index: usize) -> Self::Ref<'c> { + self.index(index) + } + + fn iter<'c>(&'c self) -> Self::Iter<'c> { + self.as_ref().into_iter() + } + + fn as_mut_slice<'c: 'b, 'b>(&'c mut self) -> Self::SliceMut<'c> where Self: 'b { + self.reborrow() + } + + fn slice_mut<'c>(&'c mut self, index: impl core::ops::RangeBounds) -> Self::SliceMut<'c> { + let start = match index.start_bound() { + std::ops::Bound::Included(i) | std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => 0, + }; + let n = self.len(); + let end = match index.end_bound() { + std::ops::Bound::Included(i) => (*i + 1).min(n), + std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => n, + }; + self.index_mut(start..end) + } + + fn get_mut<'c>(&'c mut self, index: usize) -> Option> { + self.get_mut(index) + } + + fn index_mut<'c>(&'c mut self, index: usize) -> Self::RefMut<'c> { + self.index_mut(index) + } + + fn iter_mut<'c>(&'c mut self) -> Self::IterMut<'c> { + self.iter_mut() + } + + fn apply_index(&mut self, indices: &[usize]) { + self.apply_permutation(&mut ::soa_derive::Permutation::oneline(indices).inverse()); + } + + fn as_ptr(&self) -> Self::Ptr { + self.as_ptr() + } + + fn as_mut_ptr(&mut self) -> Self::PtrMut { + self.as_mut_ptr() + } + } + }; + + return generated +} + +pub fn derive_vec(input: &Input) -> TokenStream { + let name = &input.name; + let vec_name = names::vec_name(&input.name); + let slice_name = names::slice_name(name); + let slice_mut_name = names::slice_mut_name(&input.name); + let ref_name = names::ref_name(&input.name); + let ref_mut_name = names::ref_mut_name(&input.name); + let ptr_name = names::ptr_name(&input.name); + let ptr_mut_name = names::ptr_mut_name(&input.name); + let iter_name = names::iter_name(name); + let iter_mut_name = names::iter_mut_name(name); + + let generated = quote! { + + impl ::soa_derive::SoAVec<#name> for #vec_name { + type Ref<'t> = #ref_name<'t>; + type Slice<'t> = #slice_name<'t>; + type Iter<'t> = #iter_name<'t>; + type Ptr = #ptr_name; + + type RefMut<'t> = #ref_mut_name<'t>; + type SliceMut<'t> = #slice_mut_name<'t>; + type IterMut<'t> = #iter_mut_name<'t>; + type PtrMut = #ptr_mut_name; + + fn len(&self) -> usize { + self.len() + } + + fn is_empty(&self) -> bool { + self.is_empty() + } + + fn as_slice<'c, 'a: 'c>(&'c self) -> Self::Slice<'c> where Self: 'a { + self.as_slice() + } + + fn slice<'c, 'a: 'c>(&'c self, index: impl core::ops::RangeBounds) -> Self::Slice<'c> where Self: 'a { + let start = match index.start_bound() { + std::ops::Bound::Included(i) | std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => 0, + }; + let n = self.len(); + let end = match index.end_bound() { + std::ops::Bound::Included(i) => (*i + 1).min(n), + std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => n, + }; + self.index(start..end) + } + + fn get<'c>(&'c self, index: usize) -> Option> { + self.get(index) + } + + fn index<'c>(&'c self, index: usize) -> Self::Ref<'c> { + self.index(index) + } + + fn iter<'c>(&'c self) -> Self::Iter<'c> { + self.iter() + } + + fn as_mut_slice<'c, 'a: 'c>(&'c mut self) -> Self::SliceMut<'c> where Self: 'a { + self.as_mut_slice() + } + + fn slice_mut<'c>(&'c mut self, index: impl core::ops::RangeBounds) -> Self::SliceMut<'c> { + let start = match index.start_bound() { + std::ops::Bound::Included(i) | std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => 0, + }; + let n = self.len(); + let end = match index.end_bound() { + std::ops::Bound::Included(i) => (*i + 1).min(n), + std::ops::Bound::Excluded(i) => *i, + std::ops::Bound::Unbounded => n, + }; + self.index_mut(start..end) + } + + fn get_mut<'c>(&'c mut self, index: usize) -> Option> { + self.get_mut(index) + } + + fn index_mut<'c>(&'c mut self, index: usize) -> Self::RefMut<'c> { + self.index_mut(index) + } + + fn iter_mut<'c>(&'c mut self) -> Self::IterMut<'c> { + self.iter_mut() + } + + fn apply_index(&mut self, indices: &[usize]) { + use ::soa_derive::SoASliceMut; + self.as_mut_slice().apply_index(indices); + } + + fn new() -> Self { + Self::new() + } + + fn with_capacity(capacity: usize) -> Self { + Self::with_capacity(capacity) + } + + fn capacity(&self) -> usize { + self.capacity() + } + + fn reserve(&mut self, additional: usize) { + self.reserve(additional); + } + + fn reserve_exact(&mut self, additional: usize) { + self.reserve_exact(additional); + } + + fn shrink_to_fit(&mut self) { + self.shrink_to_fit(); + } + + fn truncate(&mut self, len: usize) { + self.truncate(len); + } + + fn push(&mut self, value: #name) { + self.push(value); + } + + fn swap_remove(&mut self, index: usize) -> #name { + self.swap_remove(index) + } + + fn insert(&mut self, index: usize, element: #name) { + self.insert(index, element); + } + + fn replace(&mut self, index: usize, element: #name) -> #name { + self.replace(index, element) + } + + fn remove(&mut self, index: usize) -> #name { + self.remove(index) + } + + fn pop(&mut self) -> Option<#name> { + self.pop() + } + + fn append(&mut self, other: &mut Self) { + self.append(other); + } + + fn clear(&mut self) { + self.clear(); + } + + fn split_off(&mut self, at: usize) -> Self { + self.split_off(at) + } + + fn as_ptr(&self) -> Self::Ptr { + self.as_ptr() + } + + fn as_mut_ptr(&mut self) -> Self::PtrMut { + self.as_mut_ptr() + } + } + }; + + return generated +} \ No newline at end of file diff --git a/soa-derive-internal/src/input.rs b/soa-derive-internal/src/input.rs index faa0fac..0a272ad 100644 --- a/soa-derive-internal/src/input.rs +++ b/soa-derive-internal/src/input.rs @@ -2,7 +2,7 @@ use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::punctuated::Punctuated; -use syn::{Attribute, Data, DeriveInput, Field, Path, Visibility, Token}; +use syn::{Attribute, Data, DeriveInput, Field, Path, Token, Visibility}; use syn::{Meta, MetaList}; /// Representing the struct we are deriving @@ -170,7 +170,7 @@ impl Input { fields: fields, visibility: input.vis, attrs: extra_attrs, - field_is_nested + field_is_nested, } } diff --git a/soa-derive-internal/src/iter.rs b/soa-derive-internal/src/iter.rs index 1cdc71e..0d5e646 100644 --- a/soa-derive-internal/src/iter.rs +++ b/soa-derive-internal/src/iter.rs @@ -79,7 +79,7 @@ pub fn derive(input: &Input) -> TokenStream { } }).expect("should be Some"); - return quote! { + let generated = quote! { /// Iterator over #[doc = #doc_url] #[allow(missing_debug_implementations)] @@ -217,6 +217,8 @@ pub fn derive(input: &Input) -> TokenStream { } impl<'a> soa_derive::SoAIter<'a> for #name { + type Ref = #ref_name<'a>; + type RefMut = #ref_mut_name<'a>; type Iter = #iter_name<'a>; type IterMut = #iter_mut_name<'a>; } @@ -294,5 +296,9 @@ pub fn derive(input: &Input) -> TokenStream { self.extend(iter.into_iter().map(|item| item.to_owned())) } } + + impl<'a> ::soa_derive::IntoSoAIter<'a, #name> for #slice_name<'a> {} }; + + return generated; } diff --git a/soa-derive-internal/src/lib.rs b/soa-derive-internal/src/lib.rs index 64027ca..1b94626 100644 --- a/soa-derive-internal/src/lib.rs +++ b/soa-derive-internal/src/lib.rs @@ -6,7 +6,7 @@ extern crate proc_macro; -use proc_macro2::{TokenStream}; +use proc_macro2::TokenStream; use quote::TokenStreamExt; mod index; @@ -17,12 +17,13 @@ mod ptr; mod refs; mod slice; mod vec; +mod generic; pub(crate) mod names; #[proc_macro_derive(StructOfArray, attributes(soa_derive, soa_attr, nested_soa))] pub fn soa_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - let ast = syn::parse(input).unwrap(); + let ast = syn::parse(input).expect("Failed to parse derive macro for StructOfArray"); let input = input::Input::new(ast); let mut generated = TokenStream::new(); @@ -34,11 +35,16 @@ pub fn soa_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { generated.append_all(index::derive(&input)); generated.append_all(iter::derive(&input)); generated.append_all(derive_trait(&input)); + + generated.append_all(generic::derive_slice(&input)); + generated.append_all(generic::derive_slice_mut(&input)); + generated.append_all(generic::derive_vec(&input)); generated.into() } use crate::input::Input; use quote::quote; + fn derive_trait(input: &Input) -> TokenStream { let name = &input.name; let vec_name = names::vec_name(name); diff --git a/soa-derive-internal/src/ptr.rs b/soa-derive-internal/src/ptr.rs index 48627d6..65d2f81 100644 --- a/soa-derive-internal/src/ptr.rs +++ b/soa-derive-internal/src/ptr.rs @@ -187,6 +187,11 @@ pub fn derive(input: &Input) -> TokenStream { } } + impl ::soa_derive::SoAPointers for #name { + type Ptr = #ptr_name; + type MutPtr = #ptr_mut_name; + } + #[allow(dead_code)] #[allow(clippy::forget_non_drop)] impl #ptr_mut_name { diff --git a/soa-derive-internal/src/refs.rs b/soa-derive-internal/src/refs.rs index 9ab05b5..89eb921 100644 --- a/soa-derive-internal/src/refs.rs +++ b/soa-derive-internal/src/refs.rs @@ -142,6 +142,18 @@ pub fn derive(input: &Input) -> TokenStream { } } + impl<'a> From<#ref_name<'a>> for #name where #( for<'b> #fields_types: Clone, )* { + fn from(value: #ref_name<'a>) -> #name { + value.to_owned() + } + } + + impl<'a> From<&'a #ref_name<'a>> for #name where #( for<'b> #fields_types: Clone, )* { + fn from(value: &'a #ref_name<'a>) -> #name { + value.to_owned() + } + } + impl<'a> #ref_mut_name<'a> { /// Convert a mutable reference to #[doc = #doc_url] @@ -171,5 +183,17 @@ pub fn derive(input: &Input) -> TokenStream { #name{#(#fields_names: #fields_names_hygienic),*} } } + + impl<'a> From<#ref_mut_name<'a>> for #name where #( for<'b> #fields_types: Clone, )* { + fn from(value: #ref_mut_name<'a>) -> #name { + value.to_owned() + } + } + + impl<'a> From<&'a #ref_mut_name<'a>> for #name where #( for<'b> #fields_types: Clone, )* { + fn from(value: &'a #ref_mut_name<'a>) -> #name { + value.to_owned() + } + } } } diff --git a/soa-derive-internal/src/slice.rs b/soa-derive-internal/src/slice.rs index 832f163..e240006 100644 --- a/soa-derive-internal/src/slice.rs +++ b/soa-derive-internal/src/slice.rs @@ -7,6 +7,7 @@ use crate::input::Input; use crate::names; pub fn derive(input: &Input) -> TokenStream { + let name = &input.name; let visibility = &input.visibility; let slice_name = names::slice_name(&input.name); let attrs = &input.attrs.slice; @@ -233,6 +234,7 @@ pub fn derive(input: &Input) -> TokenStream { } } } + }; if input.attrs.derive_clone { @@ -249,12 +251,25 @@ pub fn derive(input: &Input) -> TokenStream { } } }); + + { + generated.append_all(quote! { + impl<'a> ::soa_derive::ToSoAVec<#name> for #slice_name<'a> { + type SoAVecType = #vec_name; + + fn to_vec(&self) -> Self::SoAVecType { + self.to_vec() + } + } + }); + } } return generated; } pub fn derive_mut(input: &Input) -> TokenStream { + let name = &input.name; let visibility = &input.visibility; let slice_name = names::slice_name(&input.name); let slice_mut_name = names::slice_mut_name(&input.name); @@ -673,6 +688,18 @@ pub fn derive_mut(input: &Input) -> TokenStream { } } }); + + { + generated.append_all(quote! { + impl<'a> ::soa_derive::ToSoAVec<#name> for #slice_mut_name<'a> { + type SoAVecType = #vec_name; + + fn to_vec(&self) -> Self::SoAVecType { + self.to_vec() + } + } + }); + } } return generated; diff --git a/soa-derive-internal/src/vec.rs b/soa-derive-internal/src/vec.rs index 8ae8307..1a15af9 100644 --- a/soa-derive-internal/src/vec.rs +++ b/soa-derive-internal/src/vec.rs @@ -468,6 +468,14 @@ pub fn derive(input: &Input) -> TokenStream { )* } } + + impl ::soa_derive::SoAAppendVec<#name> for #vec_name { + fn extend_from_slice(&mut self, other: Self::Slice<'_>) { + #( + self.#fields_names.extend_from_slice(other.#fields_names); + )* + } + } }); } diff --git a/src/lib.rs b/src/lib.rs index 63f8dfa..501a7e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -188,13 +188,13 @@ //! # } //! # } //! ``` -//! +//! //! ## Nested Struct of Arrays -//! +//! //! In order to nest a struct of arrays inside another struct of arrays, one can use the `#[nested_soa]` attribute. -//! +//! //! For example, the following code -//! +//! //! ``` //! # mod cheese { //! # use soa_derive::StructOfArray; @@ -211,9 +211,9 @@ //! } //! # } //! ``` -//! +//! //! will generate structs that looks like this: -//! +//! //! ``` //! pub struct PointVec { //! x: Vec, @@ -224,8 +224,31 @@ //! mass: Vec //! } //! ``` -//! +//! //! All helper structs will be also nested, for example `PointSlice` will be nested in `ParticleSlice`. +//! +//! # Use in a generic context +//! +//! `StructOfArray` does not provide a set of common operations by default. Thus if you wanted to use a `StructOfArray` +//! type in a generic context, there is no way to guarantee to the type system that any methods are available. +//! +//! This will also generate implementations of [`SoAVec`], [`SoASlice`], and [`SoASliceMut`] for the respective +//! `Vec`, `Slice` and `SliceMut` types. These rely on GATs, and so require Rust 1.65 or newer. +//! +//! ```ignore +//! # mod cheese { +//! # use soa_derive::{StructOfArray, prelude::*}; +//! #[derive(StructOfArray)] +//! pub struct Point { +//! x: f32, +//! y: f32, +//! } +//! +//! fn get_num_items>(values: &V) -> usize { +//! values.len() +//! } +//! # } +//! ``` // The proc macro is implemented in soa_derive_internal, and re-exported by this // crate. This is because a single crate can not define both a proc macro and a @@ -246,15 +269,17 @@ pub trait StructOfArray { } /// Any struct derived by StructOfArray will auto impl this trait. -/// +/// /// Useful for generic programming and implementation of attribute `nested_soa`. -/// +/// /// `CheeseVec::iter(&'a self)` returns an iterator which has a type `>::Iter` -/// +/// /// `CheeseVec::iter_mut(&mut 'a self)` returns an iterator which has a type `>::IterMut` pub trait SoAIter<'a> { - type Iter: 'a; - type IterMut: 'a; + type Ref; + type RefMut; + type Iter: 'a + Iterator; + type IterMut: 'a + Iterator; } mod private_soa_indexes { @@ -384,6 +409,383 @@ macro_rules! soa_zip { }}; } + +/// This trait is automatically implemented by the relevant generated by [`StructOfArray`]. +/// +/// Links a [`StructOfArray`] type to its raw pointer types, which is useful for generic +/// programming. +pub trait SoAPointers { + /// The immutable pointer type for an SoA type + type Ptr; + /// The mutable pointer type for an SoA type + type MutPtr; +} + + +mod generics { + use super::*; + + /** + The interface for the `Slice` immutable slice struct-of-arrays type. + */ + pub trait SoASlice { + /// The type that elements will be proxied with as + type Ref<'t> where Self: 't; + + /// The type representing immutable slices of elements + type Slice<'t>: SoASlice + IntoSoAIter<'t, T, Ref<'t> = Self::Ref<'t>> where Self: 't; + + /// The type used for iteration over [`Self::Ref`] + type Iter<'t>: Iterator> where Self: 't; + + /// The raw pointer type interface + type Ptr; + + /// Returns the number of elements in the arrays + fn len(&self) -> usize; + + /// Returns true if the arrays has a length of 0. + fn is_empty(&self) -> bool; + + /// Create an immutable slice of the arrays + fn as_slice<'c>(&'c self) -> Self::Slice<'c>; + + /// Create a slice of this vector matching the given `range`. This + /// is analogous to `Index>`. + fn slice<'c, 'a: 'c>(&'c self, index: impl core::ops::RangeBounds) -> Self::Slice<'c> where Self: 'a; + + /// Analogous to [`slice::get()`](https://doc.rust-lang.org/std/primitive.slice.html#method.get) + fn get<'c>(&'c self, index: usize) -> Option>; + + /// Analogous to [`std::ops::Index::index()`] for `usize` + fn index<'c>(&'c self, index: usize) -> Self::Ref<'c>; + + /// Create an immutable iterator + fn iter<'c>(&'c self) -> Self::Iter<'c>; + + /// Analogous to [`slice::first()`](https://doc.rust-lang.org/std/primitive.slice.html#method.first) + fn first<'c>(&'c self) -> Option> { + self.get(0) + } + + /// Analogous to [`slice::last()`](https://doc.rust-lang.org/std/primitive.slice.html#method.last) + fn last<'c>(&'c self) -> Option> { + self.get(self.len().saturating_sub(1)) + } + + /// Obtain a `const` pointer type for this data + fn as_ptr(&self) -> Self::Ptr; + } + + /** + The interface for the `SliceMut` mutable slice struct-of-arrays type. A generalization of [`SoASlice`] + whose methods can modify elements of the arrays + */ + pub trait SoASliceMut { + /// The type that elements will be proxied with as + type Ref<'t> where Self: 't; + + /// The type representing immutable slices of elements + type Slice<'t>: SoASlice + IntoSoAIter<'t, T, Ref<'t> = Self::Ref<'t>> where Self: 't; + + /// The type used for iteration over [`Self::Ref`] + type Iter<'t>: Iterator> where Self: 't; + + /// The const pointer type interface + type Ptr; + + /// The type that elements will be proxied with as when mutable + type RefMut<'t> where Self: 't; + + /// The type representing mutable slices of elements + type SliceMut<'t>: SoASliceMut where Self: 't; + + /// The type used for iteration over [`Self::RefMut`] + type IterMut<'t>: Iterator> where Self: 't; + + /// The mut pointer type interface + type PtrMut; + + /// Returns the number of elements in the arrays + fn len(&self) -> usize; + + /// Returns true if the arrays has a length of 0. + fn is_empty(&self) -> bool; + + /// Create an immutable slice of the arrays + fn as_slice<'c>(&'c self) -> Self::Slice<'c>; + + /// Create a slice of this vector matching the given `range`. This + /// is analogous to `Index>`. + fn slice<'c, 'a: 'c>(&'c self, index: impl core::ops::RangeBounds) -> Self::Slice<'c> where Self: 'a; + + /// Analogous to [`slice::get()`](https://doc.rust-lang.org/std/primitive.slice.html#method.get) + fn get<'c>(&'c self, index: usize) -> Option>; + + /// Analogous to [`std::ops::Index::index()`] for `usize` + fn index<'c>(&'c self, index: usize) -> Self::Ref<'c>; + + /// Create an immutable iterator + fn iter<'c>(&'c self) -> Self::Iter<'c>; + + /// Analogous to [`slice::first()`](https://doc.rust-lang.org/std/primitive.slice.html#method.first) + fn first<'c>(&'c self) -> Option> { + self.get(0) + } + + /// Analogous to [`slice::last()`](https://doc.rust-lang.org/std/primitive.slice.html#method.last) + fn last<'c>(&'c self) -> Option> { + self.get(self.len().saturating_sub(1)) + } + + /// Obtain a `const` pointer type for this data + fn as_ptr(&self) -> Self::Ptr; + + /// Analogous to [`Vec::as_mut_slice()`] + fn as_mut_slice<'c: 'b, 'b>(&'c mut self) -> Self::SliceMut<'c> where Self: 'b; + + /// Create a mutable slice of this vector matching the given + /// `range`. This is analogous to `IndexMut>`. + fn slice_mut<'c>(&'c mut self, index: impl core::ops::RangeBounds) -> Self::SliceMut<'c>; + + /// Analogous to [`slice::get_mut()`](https://doc.rust-lang.org/std/primitive.slice.html#method.get_mut) + fn get_mut<'c>(&'c mut self, index: usize) -> Option>; + + /// Analogous to [`std::ops::IndexMut::index_mut()`] for `usize` + fn index_mut<'c>(&'c mut self, index: usize) -> Self::RefMut<'c>; + + /// Creates a mutable iterator + fn iter_mut<'c>(&'c mut self) -> Self::IterMut<'c>; + + /** Re-order the arrays using the provided indices. This is provided so that generic sorting methods + can be implemented because closure-passing trait methods encounter difficulties with lifetimes. + */ + fn apply_index(&mut self, indices: &[usize]); + + /// `[slice::sort_by()`](). + fn sort_by(&mut self, mut f: F) where F: FnMut(Self::Ref<'_>, Self::Ref<'_>) -> std::cmp::Ordering { + let mut permutation: Vec = (0..self.len()).collect(); + permutation.sort_by(|j, k| f(self.index(*j), self.index(*k))); + + self.apply_index(&permutation); + } + + /// `[slice::sort_by()`](). + fn sort_by_key(&mut self, mut f: F) where + F: FnMut(Self::Ref<'_>) -> K, + K: Ord, + { + let mut permutation: Vec = (0..self.len()).collect(); + permutation.sort_by_key(|j| f(self.index(*j))); + + self.apply_index(&permutation); + } + + /// Analogous to [`slice::first_mut()`](). + fn first_mut<'c>(&'c mut self) -> Option> { + self.get_mut(0) + } + + /// Analogous to [`slice::last_mut()`](). + fn last_mut<'c>(&'c mut self) -> Option> { + self.get_mut(self.len().saturating_sub(1)) + } + + /// Obtain a `mut` pointer type for this data + fn as_mut_ptr(&mut self) -> Self::PtrMut; + } + + /** + The interface for the `Vec`-like struct-of-arrays type. A generalization of [`SoASliceMut`] whose methods can + also re-size the underlying arrays. + + **NOTE**: This interface is incomplete and additional methods may be added as needed. + */ + pub trait SoAVec { + /// The type that elements will be proxied with as + type Ref<'t> where Self: 't; + + /// The type representing immutable slices of elements + type Slice<'t>: SoASlice + IntoSoAIter<'t, T> where Self: 't; + + /// The type used for iteration over [`Self::Ref`] + type Iter<'t>: Iterator> where Self: 't; + + /// The const pointer type interface + type Ptr; + + /// The type that elements will be proxied with as when mutable + type RefMut<'t> where Self: 't; + + /// The type representing mutable slices of elements + type SliceMut<'t>: SoASliceMut where Self: 't; + + /// The type used for iteration over [`Self::RefMut`] + type IterMut<'t>: Iterator> where Self: 't; + + /// The mut pointer type interface + type PtrMut; + + /// Returns the number of elements in the arrays + fn len(&self) -> usize; + + /// Returns true if the arrays has a length of 0. + fn is_empty(&self) -> bool; + + /// Create an immutable slice of the arrays + fn as_slice<'c, 'a: 'c>(&'c self) -> Self::Slice<'c> where Self: 'a; + + /// Create a slice of this vector matching the given `range`. This + /// is analogous to `Index>`. + fn slice<'c, 'a: 'c>(&'c self, index: impl core::ops::RangeBounds) -> Self::Slice<'c> where Self: 'a; + + /// Analogous to [`slice::get()`](https://doc.rust-lang.org/std/primitive.slice.html#method.get) + fn get<'c>(&'c self, index: usize) -> Option>; + + /// Analogous to [`std::ops::Index::index()`] for `usize` + fn index<'c>(&'c self, index: usize) -> Self::Ref<'c>; + + /// Create an immutable iterator + fn iter<'c>(&'c self) -> Self::Iter<'c>; + + /// Analogous to [`slice::first()`](https://doc.rust-lang.org/std/primitive.slice.html#method.first) + fn first<'c>(&'c self) -> Option> { + self.get(0) + } + + /// Analogous to [`slice::last()`](https://doc.rust-lang.org/std/primitive.slice.html#method.last) + fn last<'c>(&'c self) -> Option> { + self.get(self.len().saturating_sub(1)) + } + + /// Obtain a `const` pointer type for this data + fn as_ptr(&self) -> Self::Ptr; + + /// Analogous to [`Vec::as_mut_slice()`] + fn as_mut_slice<'c, 'a: 'c>(&'c mut self) -> Self::SliceMut<'c> where Self: 'a; + + /// Create a mutable slice of this vector matching the given + /// `range`. This is analogous to `IndexMut>`. + fn slice_mut<'c>(&'c mut self, index: impl core::ops::RangeBounds) -> Self::SliceMut<'c>; + + /// Analogous to [`slice::get_mut()`](https://doc.rust-lang.org/std/primitive.slice.html#method.get_mut) + fn get_mut<'c>(&'c mut self, index: usize) -> Option>; + + /// Analogous to [`std::ops::IndexMut::index_mut()`] for `usize` + fn index_mut<'c>(&'c mut self, index: usize) -> Self::RefMut<'c>; + + /// Creates a mutable iterator + fn iter_mut<'c>(&'c mut self) -> Self::IterMut<'c>; + + /** Re-order the arrays using the provided indices. This is provided so that generic sorting methods + can be implemented because closure-passing trait methods encounter difficulties with lifetimes. + */ + fn apply_index(&mut self, indices: &[usize]); + + /// `[slice::sort_by()`](). + fn sort_by(&mut self, mut f: F) where F: FnMut(Self::Ref<'_>, Self::Ref<'_>) -> std::cmp::Ordering { + let mut permutation: Vec = (0..self.len()).collect(); + permutation.sort_by(|j, k| f(self.index(*j), self.index(*k))); + + self.apply_index(&permutation); + } + + /// `[slice::sort_by()`](). + fn sort_by_key(&mut self, mut f: F) where + F: FnMut(Self::Ref<'_>) -> K, + K: Ord, + { + let mut permutation: Vec = (0..self.len()).collect(); + permutation.sort_by_key(|j| f(self.index(*j))); + + self.apply_index(&permutation); + } + + /// Analogous to [`slice::first_mut()`]() + fn first_mut<'c>(&'c mut self) -> Option> { + self.get_mut(0) + } + + /// Analogous to [`slice::last_mut()`]() + fn last_mut<'c>(&'c mut self) -> Option> { + self.get_mut(self.len().saturating_sub(1)) + } + + /// Obtain a `mut` pointer type for this data + fn as_mut_ptr(&mut self) -> Self::PtrMut; + + /// Create a new, empty struct of arrays + fn new() -> Self; + + /// Create a new, empty struct of arrays with the specified capacity + fn with_capacity(capacity: usize) -> Self; + + /// Analogous to [`Vec::capacity`] + fn capacity(&self) -> usize; + + /// Analogous to [`Vec::reserve`] + fn reserve(&mut self, additional: usize); + + /// Analogous to [`Vec::reserve_exact`] + fn reserve_exact(&mut self, additional: usize); + + /// Analogous to [`Vec::shrink_to_fit`] + fn shrink_to_fit(&mut self); + + /// Analogous to [`Vec::truncate`] + fn truncate(&mut self, len: usize); + + /// Add a singular value of `T` to the arrays. Analogous to [`Vec::push`] + fn push(&mut self, value: T); + + /// Analogous to [`Vec::swap_remove`] + fn swap_remove(&mut self, index: usize) -> T; + + /// Analogous to [`Vec::insert`] + fn insert(&mut self, index: usize, element: T); + + /// Similar to [`std::mem::replace()`](https://doc.rust-lang.org/std/mem/fn.replace.html). + fn replace(&mut self, index: usize, element: T) -> T; + + /// Analogous to [`Vec::remove`] + fn remove(&mut self, index: usize) -> T; + + /// Analogous to [`Vec::pop`] + fn pop(&mut self) -> Option; + + /// Analogous to [`Vec::append`] + fn append(&mut self, other: &mut Self); + + /// Analogous to [`Vec::clear`] + fn clear(&mut self); + + /// Analogous to [`Vec::split_off`] + fn split_off(&mut self, at: usize) -> Self; + } + + /// A trait to implement `Clone`-dependent behavior to convert a non-owning SoA type into an + /// owning [`SoAVec`]. + pub trait ToSoAVec { + type SoAVecType: SoAVec; + + /// Similar to [`slice::to_vec()`](https://doc.rust-lang.org/std/primitive.slice.html#method.to_vec) + fn to_vec(&self) -> Self::SoAVecType; + } + + /// A trait to implement `Clone`-dependent behavior to extend an [`SoAVec`] with data copied + /// from its associated `Slice` type. + pub trait SoAAppendVec: SoAVec { + + /// Analogous to [`Vec::extend_from_slice`] + fn extend_from_slice(&mut self, other: Self::Slice<'_>); + } + + /// A trait to express the [`IntoIterator`] guarantee of [`SoASlice`] types in the type system. + pub trait IntoSoAIter<'a, T: StructOfArray>: SoASlice + IntoIterator> + 'a {} +} +pub use generics::*; + + #[macro_export] #[doc(hidden)] macro_rules! soa_zip_impl { diff --git a/tests/generic.rs b/tests/generic.rs new file mode 100644 index 0000000..7448151 --- /dev/null +++ b/tests/generic.rs @@ -0,0 +1,115 @@ +mod particles; +use std::marker::PhantomData; + +use particles::ParticleVec; +use soa_derive::{SoAVec, StructOfArray}; + +use self::particles::Particle; + +fn may_iter>(vec: &V) -> V::Iter<'_> { + let x= vec.iter(); + x +} + +fn may_push>(vec: &mut V, val: T) { + vec.push(val); +} + +fn may_sort_generic>(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, F>(vec: &mut V, mut f: F) where F: FnMut(V::Ref<'_>, V::Ref<'_>) -> std::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::(&x).collect(); + assert_eq!(x.len(), y.len()); + assert_eq!(x.get(0).as_ref(), y.get(0)); + 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 Swarm> { + entries: V, + _t: PhantomData +} + +impl> Swarm { + fn new() -> Self { + Self { + entries: V::new(), + _t: PhantomData + } + } + + fn push(&mut self, value: T) { + self.entries.push(value); + } + + fn iter(&self) -> V::Iter<'_> { + self.entries.iter() + } + + fn view(&self) -> V::Slice<'_> { + self.entries.as_slice() + } +} + +#[test] +fn test_wrapped() { + let mut this: Swarm = Swarm::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); +} diff --git a/tests/particles/mod.rs b/tests/particles/mod.rs index 52b815f..de0d589 100644 --- a/tests/particles/mod.rs +++ b/tests/particles/mod.rs @@ -1,4 +1,6 @@ -use soa_derive::StructOfArray; +use std::fmt::Debug; + +use soa_derive::*; #[derive(Debug, Clone, PartialOrd, PartialEq, StructOfArray)] #[soa_derive(Debug, Clone, PartialOrd, PartialEq)] @@ -15,3 +17,98 @@ impl Particle { } } } + + +mod impls { + use std::cmp::Ordering; + use std::marker::PhantomData; + + use super::*; + + fn iter_max_generic<'a, T: StructOfArray, V: SoASlice + 'a>(vec: &'a V) -> Option> 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: StructOfArray, V: SoAVec>(it: V::Iter<'a>) -> Option> 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<'a, T: StructOfArray, V: SoAVec>(vec: &V) -> usize { + let view = vec.as_slice(); + let n = view.iter().count(); + assert_eq!(view.into_iter().count(), n); + n + } + + pub struct VWrap> { + data: V, + _t: PhantomData + } + + impl> VWrap { + pub fn empty() -> Self { + let data = V::new(); + Self { data, _t: PhantomData } + } + + pub fn push(&mut self, value: T) { + self.data.push(value); + } + + pub fn sort_by(&mut self, f: F) where F: FnMut(V::Ref<'_>, V::Ref<'_>) -> Ordering { + self.data.sort_by(f); + } + + pub fn first(&self) -> Option> { + self.data.first() + } + } + + #[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::(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 pv = VWrap::::empty(); + pv.push(Particle::new("foo".into(), 100.0)); + pv.push(Particle::new("bar".into(), 1000.0)); + pv.push(Particle::new("baz".into(), 50.0)); + pv.sort_by(|a, b| a.mass.total_cmp(&b.mass)); + + assert_eq!(pv.first().unwrap().name, "baz"); + + } +} \ No newline at end of file