diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4a18b68..72fb915 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,9 +20,9 @@ jobs: uses: actions-rs/cargo@v1 with: command: test-all-features - args: --all-targets + args: --workspace --all-targets - name: Test documentation uses: actions-rs/cargo@v1 with: - command: test-all-features - args: --doc + command: test + args: --doc --all-features diff --git a/Cargo.toml b/Cargo.toml index 1556665..aeea7b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ default = ["arena", "query_mut", "dynamic"] query_mut = ["stecs-derive/query_mut"] hashstorage = [] arena = ["dep:slotmap"] -dynamic = ["dep:anymap3"] +dynamic = ["stecs-derive/dynamic", "dep:anymap3"] [workspace] members = ["crates/*"] @@ -28,9 +28,39 @@ anymap3 = { version = "1.0", optional = true } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } +[package.metadata."docs.rs"] +all-features = true + [[bench]] name = "pos_vel" harness = false +required-features = ["query_mut"] [[bench]] name = "simple" harness = false +required-features = ["query_mut"] + +[[example]] +name = "full" +doc-scrape-examples = true +required-features = ["query_mut"] + +[[example]] +name = "blogpost" +doc-scrape-examples = true +required-features = ["arena", "query_mut"] + +[[example]] +name = "mutations" +doc-scrape-examples = true +required-features = ["query_mut"] + +[[example]] +name = "generics" +doc-scrape-examples = true +required-features = [] + +[[example]] +name = "dynamic" +doc-scrape-examples = true +required-features = ["query_mut", "dynamic"] diff --git a/crates/stecs-derive/Cargo.toml b/crates/stecs-derive/Cargo.toml index 3b9faaf..c23f50c 100644 --- a/crates/stecs-derive/Cargo.toml +++ b/crates/stecs-derive/Cargo.toml @@ -16,6 +16,7 @@ proc-macro = true [features] default = [] query_mut = [] +dynamic = [] [dependencies] darling = "0.20" diff --git a/crates/stecs-derive/src/get/derive.rs b/crates/stecs-derive/src/get/derive.rs index 0677d77..a693441 100644 --- a/crates/stecs-derive/src/get/derive.rs +++ b/crates/stecs-derive/src/get/derive.rs @@ -29,7 +29,14 @@ impl StorageGetOpts { }; get_fields = match optic { - Optic::Dynamic { .. } | Optic::GetId => quote! { + #[cfg(feature = "dynamic")] + Optic::Dynamic { .. } => quote! { + { + let #name = #access; + #get_fields + } + }, + Optic::GetId => quote! { { let #name = #access; #get_fields diff --git a/crates/stecs-derive/src/optic.rs b/crates/stecs-derive/src/optic.rs index 8fbacb9..ad9a7c3 100644 --- a/crates/stecs-derive/src/optic.rs +++ b/crates/stecs-derive/src/optic.rs @@ -4,6 +4,7 @@ use quote::quote; #[derive(Debug, Clone)] pub enum Optic { + #[cfg(feature = "dynamic")] Dynamic { ty: syn::Type, component: OpticComponent, @@ -36,6 +37,7 @@ pub enum OpticComponent { #[derive(Debug, Clone, Copy)] enum Access { + #[cfg(feature = "dynamic")] Owned, Borrow, BorrowMut, @@ -64,6 +66,7 @@ impl Optic { fn access_impl(&self, is_mut: bool, id: TokenStream, archetype: TokenStream) -> TokenStream { match self { + #[cfg(feature = "dynamic")] Optic::Dynamic { ty, component } => { let value_name = quote! { __value }; let storage = if is_mut { @@ -113,6 +116,7 @@ impl Optic { #[cfg(feature = "query_mut")] pub fn access_many_mut(&self, ids: TokenStream, archetype: TokenStream) -> TokenStream { match self { + #[cfg(feature = "dynamic")] Optic::Dynamic { ty, component } => { let value_name = quote! { __value }; let access = if component.is_identity() { @@ -190,6 +194,7 @@ impl OpticComponent { }; let convert = match access { + #[cfg(feature = "dynamic")] Access::Owned => quote! {}, Access::Borrow => quote! { .as_ref() }, Access::BorrowMut => quote! { .as_mut() }, @@ -218,19 +223,32 @@ enum OpticPart { impl Parse for Optic { fn parse(input: syn::parse::ParseStream) -> syn::Result { - if input.parse::>()?.is_some() { - let ty: syn::Type = input.parse()?; - - let component = if input.parse::>()?.is_some() { - let parts = - Punctuated::::parse_separated_nonempty(input)?; - let parts: Vec<_> = parts.into_iter().collect(); - build_component_optic(&parts)? - } else { - OpticComponent::Identity - }; + if let Some(_dyn) = input.parse::>()? { + #[cfg(not(feature = "dynamic"))] + { + return Err(syn::Error::new_spanned( + _dyn, + "`dyn` components are not available because the `dynamic` feature is disabled", + )); + } - return Ok(Optic::Dynamic { ty, component }); + #[cfg(feature = "dynamic")] + { + let ty: syn::Type = input.parse()?; + + let component = if input.parse::>()?.is_some() { + let parts = + Punctuated::::parse_separated_nonempty( + input, + )?; + let parts: Vec<_> = parts.into_iter().collect(); + build_component_optic(&parts)? + } else { + OpticComponent::Identity + }; + + return Ok(Optic::Dynamic { ty, component }); + } } let parts = Punctuated::::parse_separated_nonempty(input)?; diff --git a/crates/stecs-derive/src/query.rs b/crates/stecs-derive/src/query.rs index 1e60107..3fa7d80 100644 --- a/crates/stecs-derive/src/query.rs +++ b/crates/stecs-derive/src/query.rs @@ -51,9 +51,8 @@ impl Parse for QueryOpts { ImageOpts::Tuple { fields } => fields.iter().any(|field| field.is_mut), }; if is_mut { - let span = _span_start - .join(input.span()) - .expect("spans are from the same input stream"); + // NOTE: sometimes in doc tests the `join` fails + let span = _span_start.join(input.span()).unwrap_or(input.span()); return Err(syn::Error::new( span, "enable the `query_mut` feature flag to allow mutable queries", @@ -99,7 +98,11 @@ impl QueryOpts { } } else { let component = optic.access(id_expr.clone(), quote! { #storage }); - if matches!(optic, Optic::Dynamic { .. }) { + #[cfg(not(feature = "dynamic"))] + let dynamic = false; + #[cfg(feature = "dynamic")] + let dynamic = matches!(optic, Optic::Dynamic { .. }); + if dynamic { quote! { let #name = #ids_expr.map(|#id_expr| { #component @@ -145,6 +148,7 @@ impl QueryOpts { .iter() .map(|(name, _, optic)| { let optional = match optic { + #[cfg(feature = "dynamic")] Optic::Dynamic { component, .. } => component.is_prism(), Optic::GetId => false, Optic::Access { component, .. } => component.is_prism(), diff --git a/crates/stecs-derive/src/split.rs b/crates/stecs-derive/src/split.rs index 2776090..eccc365 100644 --- a/crates/stecs-derive/src/split.rs +++ b/crates/stecs-derive/src/split.rs @@ -337,8 +337,8 @@ This struct is a version of `{struct_name}` that holds mutable references to its }) .collect::>(); fields.push(quote! { pub ids: #generic_family_name::Storage<()>, }); + #[cfg(feature = "dynamic")] fields.push(quote! { - #[cfg(feature = "dynamic")] pub r#dyn: ::stecs::dynamic::DynamicStorage<#generic_family_name::Id>, }); @@ -379,6 +379,7 @@ This struct is a version of `{struct_name}` that holds each field in its own [St }) .collect::>(); clone.push(quote! { ids: self.ids.clone(), }); + #[cfg(feature = "dynamic")] clone.push(quote! { r#dyn: self.r#dyn.clone(), }); quote! { @@ -430,15 +431,29 @@ This struct is a version of `{struct_name}` that holds each field in its own [St }) }); - let mut iter_mut = Vec::new(); - let mut get_many_mut = Vec::new(); - if fields.is_empty() { - // No fields - iter_mut.push(quote! { ::std::iter::empty() }); - get_many_mut.push(quote! { ::std::iter::empty() }); - } else { - // Collect fields - iter_mut = struct_fields + let get_doc = format!( + r#"Get an immutable reference to all components of this archetype, i.e. a [`{struct_ref_name}`]"# + ); + let get_mut_doc = format!( + r#"Get an mutable reference to all components of this archetype, i.e. a [`{struct_ref_mut_name}`]"# + ); + let iter_doc = format!( + r#"Iterate immutably over all components of this archetype, i.e. over [`{struct_ref_name}`]"# + ); + + #[cfg(not(feature = "query_mut"))] + let query_mut = quote! {}; + #[cfg(feature = "query_mut")] + let query_mut = { + let mut iter_mut = Vec::new(); + let mut get_many_mut = Vec::new(); + if fields.is_empty() { + // No fields + iter_mut.push(quote! { ::std::iter::empty() }); + get_many_mut.push(quote! { ::std::iter::empty() }); + } else { + // Collect fields + iter_mut = struct_fields .iter() .map(|field| { let name = &field.name; @@ -448,96 +463,104 @@ This struct is a version of `{struct_name}` that holds each field in its own [St }) .collect(); - // Zip fields - let zip = std::iter::once(quote! { self.ids.ids() }).chain( - struct_fields.iter().map(|field| { - let name = &field.name; - quote! { .zip(#name) } - }), - ); - iter_mut.extend(zip); - - // Construct the arguments for the lambda function - let mut args = quote! { id }; - for field in struct_fields.iter().map(|field| &field.name) { - args = quote! { (#args, #field) }; - } + // Zip fields + let zip = std::iter::once(quote! { self.ids.ids() }).chain( + struct_fields.iter().map(|field| { + let name = &field.name; + quote! { .zip(#name) } + }), + ); + iter_mut.extend(zip); + + // Construct the arguments for the lambda function + let mut args = quote! { id }; + for field in struct_fields.iter().map(|field| &field.name) { + args = quote! { (#args, #field) }; + } - // Construct the lambda function - iter_mut.push(quote! { - .filter_map(|#args| { - Some(( - id, - #struct_ref_mut_name { - #(#fields)* - } - )) - }) - }); + // Construct the lambda function + iter_mut.push(quote! { + .filter_map(|#args| { + Some(( + id, + #struct_ref_mut_name { + #(#fields)* + } + )) + }) + }); - // Get many mut + // Get many mut - let ids_expr = quote! { __ids }; + let ids_expr = quote! { __ids }; - // Collect fields - let get_fields = struct_fields.iter().map(|field| { + // Collect fields + let get_fields = struct_fields.iter().map(|field| { let name = &field.name; quote! { let #name = unsafe { self.#name.get_many_unchecked_mut(#ids_expr.clone()) }; } }); - get_many_mut.extend(get_fields); + get_many_mut.extend(get_fields); - // Zip fields - let mut zip = struct_fields.iter().map(|field| &field.name); - if let Some(name) = zip.next() { - get_many_mut.push(quote! { #name }); - } - for name in zip { - get_many_mut.push(quote! { .zip(#name) }); - } + // Zip fields + let mut zip = struct_fields.iter().map(|field| &field.name); + if let Some(name) = zip.next() { + get_many_mut.push(quote! { #name }); + } + for name in zip { + get_many_mut.push(quote! { .zip(#name) }); + } - // Construct the arguments for the lambda function - let mut args = quote! {}; - let mut args_iter = struct_fields.iter().map(|field| &field.name); - if let Some(field) = args_iter.next() { - args = quote! { #field } - } - for field in args_iter { - args = quote! { (#args, #field) }; - } + // Construct the arguments for the lambda function + let mut args = quote! {}; + let mut args_iter = struct_fields.iter().map(|field| &field.name); + if let Some(field) = args_iter.next() { + args = quote! { #field } + } + for field in args_iter { + args = quote! { (#args, #field) }; + } - // Construct the lambda function - get_many_mut.push(quote! { - .map(|#args| { - #struct_ref_mut_name { - #(#fields)* - } - }) - }); - } + // Construct the lambda function + get_many_mut.push(quote! { + .map(|#args| { + #struct_ref_mut_name { + #(#fields)* + } + }) + }); + } - let get_doc = format!( - r#"Get an immutable reference to all components of this archetype, i.e. a [`{struct_ref_name}`]"# - ); - let get_mut_doc = format!( - r#"Get an mutable reference to all components of this archetype, i.e. a [`{struct_ref_mut_name}`]"# - ); - let iter_doc = format!( - r#"Iterate immutably over all components of this archetype, i.e. over [`{struct_ref_name}`]"# - ); - let iter_mut_doc = format!( - r#"Iterate mutably over all components of this archetype, i.e. over [`{struct_ref_mut_name}`]"# - ); + let iter_mut_doc = format!( + r#"Iterate mutably over all components of this archetype, i.e. over [`{struct_ref_mut_name}`]"# + ); - let get_many_unchecked_mut_doc = format!( - r#"**NOTE**: This function is used internally by the proc macros, you should not call it manually. + let get_many_unchecked_mut_doc = format!( + r#"**NOTE**: This function is used internally by the proc macros, you should not call it manually. Get mutable references to all id's in the iterator, returning an iterator of [`{struct_ref_mut_name}`]. # Safety The given `ids` must not repeat and must be valid and present id's in the storage."# - ); + ); + + quote! { + #[doc = #iter_mut_doc] + pub fn iter_mut<#lifetime_ref_name>(&#lifetime_ref_name mut self) -> impl Iterator)> + #lifetime_ref_name { + use ::stecs::archetype::Archetype; + #(#iter_mut)* + } + + #[doc = #get_many_unchecked_mut_doc] + pub unsafe fn get_many_unchecked_mut<#lifetime_ref_name>( + &#lifetime_ref_name mut self, + __ids: impl Iterator + Clone, + ) -> impl Iterator> { + #(#get_many_mut)* + } + } + }; quote! { impl<#generics_family> #struct_of_name<#generics_family_use> { @@ -567,19 +590,7 @@ The given `ids` must not repeat and must be valid and present id's in the storag self.ids().filter_map(|id| self.get(id).map(move |item| (id, item))) } - #[doc = #iter_mut_doc] - pub fn iter_mut<#lifetime_ref_name>(&#lifetime_ref_name mut self) -> impl Iterator)> + #lifetime_ref_name { - use ::stecs::archetype::Archetype; - #(#iter_mut)* - } - - #[doc = #get_many_unchecked_mut_doc] - pub unsafe fn get_many_unchecked_mut<#lifetime_ref_name>( - &#lifetime_ref_name mut self, - __ids: impl Iterator + Clone, - ) -> impl Iterator> { - #(#get_many_mut)* - } + #query_mut } impl<#generics_family> IntoIterator for #struct_of_name<#generics_family_use> { @@ -625,6 +636,26 @@ The given `ids` must not repeat and must be valid and present id's in the storag .collect::>(); remove.push(quote! { Some( #struct_name { #(#fields),* } )}); + #[cfg(not(feature = "dynamic"))] + let dynamic = quote! {}; + #[cfg(feature = "dynamic")] + let dynamic = quote! { + fn insert_dyn(&mut self, id: #generic_family_name::Id, component: T) -> Option + where + #generic_family_name::Id: 'static + ::std::clone::Clone + ::std::hash::Hash + ::std::cmp::Eq + { + use ::stecs::storage::Storage; + self.r#dyn.insert(id, component) + } + fn remove_dyn(&mut self, id: #generic_family_name::Id) -> Option + where + #generic_family_name::Id: 'static + ::std::clone::Clone + ::std::hash::Hash + ::std::cmp::Eq + { + use ::stecs::storage::Storage; + self.r#dyn.remove(id) + } + }; + quote! { impl<#generics_family> ::stecs::archetype::Archetype<#generic_family_name> for #struct_of_name<#generics_family_use> { type Item = #struct_name<#generics_use>; @@ -640,22 +671,7 @@ The given `ids` must not repeat and must be valid and present id's in the storag use ::stecs::storage::Storage; #(#remove)* } - #[cfg(feature = "dynamic")] - fn insert_dyn(&mut self, id: #generic_family_name::Id, component: T) -> Option - where - #generic_family_name::Id: 'static + ::std::clone::Clone + ::std::hash::Hash + ::std::cmp::Eq - { - use ::stecs::storage::Storage; - self.r#dyn.insert(id, component) - } - #[cfg(feature = "dynamic")] - fn remove_dyn(&mut self, id: #generic_family_name::Id) -> Option - where - #generic_family_name::Id: 'static + ::std::clone::Clone + ::std::hash::Hash + ::std::cmp::Eq - { - use ::stecs::storage::Storage; - self.r#dyn.remove(id) - } + #dynamic } } }; @@ -671,12 +687,17 @@ The given `ids` must not repeat and must be valid and present id's in the storag }) .collect::>(); + #[cfg(not(feature = "dynamic"))] + let dynamic = quote! {}; + #[cfg(feature = "dynamic")] + let dynamic = quote! { r#dyn: Default::default(), }; + quote! { impl<#generics_family> Default for #struct_of_name<#generics_family_use> { fn default() -> Self { Self { ids: Default::default(), - r#dyn: Default::default(), + #dynamic #(#fields),* } } diff --git a/src/archetype/mod.rs b/src/archetype/mod.rs index 244d359..8d19b3a 100644 --- a/src/archetype/mod.rs +++ b/src/archetype/mod.rs @@ -4,8 +4,6 @@ pub use self::iter::*; use crate::storage::StorageFamily; -use std::hash::Hash; - /// A collection of components bundled together, or an entity type, or a generic SoA (struct of arrays). pub trait Archetype: Default { /// The type of the entity stored as components. @@ -20,12 +18,12 @@ pub trait Archetype: Default { #[cfg(feature = "dynamic")] fn insert_dyn(&mut self, id: F::Id, component: T) -> Option where - F::Id: 'static + Clone + Hash + Eq; + F::Id: 'static + Clone + std::hash::Hash + Eq; /// Remove a dynamic component from a specific entity. #[cfg(feature = "dynamic")] fn remove_dyn(&mut self, id: F::Id) -> Option where - F::Id: 'static + Clone + Hash + Eq; + F::Id: 'static + Clone + std::hash::Hash + Eq; } /// A type synonym for a specific implementor of [Archetype] for convenient usage in type definitions.