diff options
| author | mat <27899617+mat-1@users.noreply.github.com> | 2023-03-07 14:14:36 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-03-07 14:14:36 -0600 |
| commit | 719379a8a76ab0685f2bd14bebe2f0cd1e97f06b (patch) | |
| tree | ce5d6c62bc36fb1d1ec31083bc8e81a0109c12df /azalea-ecs | |
| parent | bf4ff517890cad3ff4e36b4b78959504192e5374 (diff) | |
| download | azalea-drasl-719379a8a76ab0685f2bd14bebe2f0cd1e97f06b.tar.xz | |
Bevy 0.10 (#79)
* replace 0.9.1 with 0.10.0
* start migrating to bevy .10
* well it compiles
* doesn't immediately panic
* remove unused imports
* fmt
* delete azalea-ecs
* make RelativeEntityUpdate an EntityCommand
* fix a doc test
* explain what FixedUpdate does
Diffstat (limited to 'azalea-ecs')
| -rw-r--r-- | azalea-ecs/Cargo.toml | 14 | ||||
| -rwxr-xr-x | azalea-ecs/azalea-ecs-macros/Cargo.toml | 15 | ||||
| -rw-r--r-- | azalea-ecs/azalea-ecs-macros/src/component.rs | 125 | ||||
| -rw-r--r-- | azalea-ecs/azalea-ecs-macros/src/fetch.rs | 466 | ||||
| -rwxr-xr-x | azalea-ecs/azalea-ecs-macros/src/lib.rs | 525 | ||||
| -rw-r--r-- | azalea-ecs/azalea-ecs-macros/src/utils/attrs.rs | 45 | ||||
| -rw-r--r-- | azalea-ecs/azalea-ecs-macros/src/utils/mod.rs | 227 | ||||
| -rw-r--r-- | azalea-ecs/azalea-ecs-macros/src/utils/shape.rs | 21 | ||||
| -rw-r--r-- | azalea-ecs/azalea-ecs-macros/src/utils/symbol.rs | 35 | ||||
| -rw-r--r-- | azalea-ecs/src/lib.rs | 157 |
10 files changed, 0 insertions, 1630 deletions
diff --git a/azalea-ecs/Cargo.toml b/azalea-ecs/Cargo.toml deleted file mode 100644 index a596fd42..00000000 --- a/azalea-ecs/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -description = "ECS stuff used in Azalea" -edition = "2021" -license = "MIT" -name = "azalea-ecs" -version = "0.6.0" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -azalea-ecs-macros = {path = "./azalea-ecs-macros", version = "^0.6.0"} -bevy_app = "0.9.1" -bevy_ecs = {version = "0.9.1", default-features = false} -tokio = {version = "1.25.0", features = ["time"]} diff --git a/azalea-ecs/azalea-ecs-macros/Cargo.toml b/azalea-ecs/azalea-ecs-macros/Cargo.toml deleted file mode 100755 index cd7b2c8d..00000000 --- a/azalea-ecs/azalea-ecs-macros/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[package] -description = "Azalea ECS Macros" -edition = "2021" -license = "MIT OR Apache-2.0" -name = "azalea-ecs-macros" -version = "0.6.0" - -[lib] -proc-macro = true - -[dependencies] -proc-macro2 = "1.0" -quote = "1.0" -syn = "1.0" -toml = "0.7.0" diff --git a/azalea-ecs/azalea-ecs-macros/src/component.rs b/azalea-ecs/azalea-ecs-macros/src/component.rs deleted file mode 100644 index e076bbe1..00000000 --- a/azalea-ecs/azalea-ecs-macros/src/component.rs +++ /dev/null @@ -1,125 +0,0 @@ -use crate::utils::{get_lit_str, Symbol}; -use proc_macro::TokenStream; -use proc_macro2::{Span, TokenStream as TokenStream2}; -use quote::{quote, ToTokens}; -use syn::{parse_macro_input, parse_quote, DeriveInput, Error, Ident, Path, Result}; - -use crate::utils; - -pub fn derive_resource(input: TokenStream) -> TokenStream { - let mut ast = parse_macro_input!(input as DeriveInput); - let azalea_ecs_path: Path = crate::azalea_ecs_path(); - - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { Self: Send + Sync + 'static }); - - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - - TokenStream::from(quote! { - impl #impl_generics #azalea_ecs_path::system::_BevyResource for #struct_name #type_generics #where_clause { - } - }) -} - -pub fn derive_component(input: TokenStream) -> TokenStream { - let mut ast = parse_macro_input!(input as DeriveInput); - let azalea_ecs_path: Path = crate::azalea_ecs_path(); - - let attrs = match parse_component_attr(&ast) { - Ok(attrs) => attrs, - Err(e) => return e.into_compile_error().into(), - }; - - let storage = storage_path(&azalea_ecs_path, attrs.storage); - - ast.generics - .make_where_clause() - .predicates - .push(parse_quote! { Self: Send + Sync + 'static }); - - let struct_name = &ast.ident; - let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); - - TokenStream::from(quote! { - impl #impl_generics #azalea_ecs_path::component::_BevyComponent for #struct_name #type_generics #where_clause { - type Storage = #storage; - } - }) -} - -pub const COMPONENT: Symbol = Symbol("component"); -pub const STORAGE: Symbol = Symbol("storage"); - -struct Attrs { - storage: StorageTy, -} - -#[derive(Clone, Copy)] -enum StorageTy { - Table, - SparseSet, -} - -// values for `storage` attribute -const TABLE: &str = "Table"; -const SPARSE_SET: &str = "SparseSet"; - -fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> { - let meta_items = utils::parse_attrs(ast, COMPONENT)?; - - let mut attrs = Attrs { - storage: StorageTy::Table, - }; - - for meta in meta_items { - use syn::{ - Meta::NameValue, - NestedMeta::{Lit, Meta}, - }; - match meta { - Meta(NameValue(m)) if m.path == STORAGE => { - attrs.storage = match get_lit_str(STORAGE, &m.lit)?.value().as_str() { - TABLE => StorageTy::Table, - SPARSE_SET => StorageTy::SparseSet, - s => { - return Err(Error::new_spanned( - m.lit, - format!( - "Invalid storage type `{s}`, expected '{TABLE}' or '{SPARSE_SET}'." - ), - )) - } - }; - } - Meta(meta_item) => { - return Err(Error::new_spanned( - meta_item.path(), - format!( - "unknown component attribute `{}`", - meta_item.path().into_token_stream() - ), - )); - } - Lit(lit) => { - return Err(Error::new_spanned( - lit, - "unexpected literal in component attribute", - )) - } - } - } - - Ok(attrs) -} - -fn storage_path(azalea_ecs_path: &Path, ty: StorageTy) -> TokenStream2 { - let typename = match ty { - StorageTy::Table => Ident::new("TableStorage", Span::call_site()), - StorageTy::SparseSet => Ident::new("SparseStorage", Span::call_site()), - }; - - quote! { #azalea_ecs_path::component::#typename } -} diff --git a/azalea-ecs/azalea-ecs-macros/src/fetch.rs b/azalea-ecs/azalea-ecs-macros/src/fetch.rs deleted file mode 100644 index 8a6b93ba..00000000 --- a/azalea-ecs/azalea-ecs-macros/src/fetch.rs +++ /dev/null @@ -1,466 +0,0 @@ -use proc_macro::TokenStream; -use proc_macro2::{Ident, Span}; -use quote::{quote, ToTokens}; -use syn::{ - parse::{Parse, ParseStream}, - parse_quote, - punctuated::Punctuated, - Attribute, Data, DataStruct, DeriveInput, Field, Fields, -}; - -use crate::azalea_ecs_path; - -#[derive(Default)] -struct FetchStructAttributes { - pub is_mutable: bool, - pub derive_args: Punctuated<syn::NestedMeta, syn::token::Comma>, -} - -static MUTABLE_ATTRIBUTE_NAME: &str = "mutable"; -static DERIVE_ATTRIBUTE_NAME: &str = "derive"; - -mod field_attr_keywords { - syn::custom_keyword!(ignore); -} - -pub static WORLD_QUERY_ATTRIBUTE_NAME: &str = "world_query"; - -pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream { - let visibility = ast.vis; - - let mut fetch_struct_attributes = FetchStructAttributes::default(); - for attr in &ast.attrs { - if !attr - .path - .get_ident() - .map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME) - { - continue; - } - - attr.parse_args_with(|input: ParseStream| { - let meta = input.parse_terminated::<syn::Meta, syn::token::Comma>(syn::Meta::parse)?; - for meta in meta { - let ident = meta.path().get_ident().unwrap_or_else(|| { - panic!( - "Unrecognized attribute: `{}`", - meta.path().to_token_stream() - ) - }); - if ident == MUTABLE_ATTRIBUTE_NAME { - if let syn::Meta::Path(_) = meta { - fetch_struct_attributes.is_mutable = true; - } else { - panic!( - "The `{MUTABLE_ATTRIBUTE_NAME}` attribute is expected to have no value or arguments" - ); - } - } else if ident == DERIVE_ATTRIBUTE_NAME { - if let syn::Meta::List(meta_list) = meta { - fetch_struct_attributes - .derive_args - .extend(meta_list.nested.iter().cloned()); - } else { - panic!( - "Expected a structured list within the `{DERIVE_ATTRIBUTE_NAME}` attribute" - ); - } - } else { - panic!( - "Unrecognized attribute: `{}`", - meta.path().to_token_stream() - ); - } - } - Ok(()) - }) - .unwrap_or_else(|_| panic!("Invalid `{WORLD_QUERY_ATTRIBUTE_NAME}` attribute format")); - } - - let path = azalea_ecs_path(); - - let user_generics = ast.generics.clone(); - let (user_impl_generics, user_ty_generics, user_where_clauses) = user_generics.split_for_impl(); - let user_generics_with_world = { - let mut generics = ast.generics.clone(); - generics.params.insert(0, parse_quote!('__w)); - generics - }; - let (user_impl_generics_with_world, user_ty_generics_with_world, user_where_clauses_with_world) = - user_generics_with_world.split_for_impl(); - - let struct_name = ast.ident.clone(); - let read_only_struct_name = if fetch_struct_attributes.is_mutable { - Ident::new(&format!("{struct_name}ReadOnly"), Span::call_site()) - } else { - struct_name.clone() - }; - - let item_struct_name = Ident::new(&format!("{struct_name}Item"), Span::call_site()); - let read_only_item_struct_name = if fetch_struct_attributes.is_mutable { - Ident::new(&format!("{struct_name}ReadOnlyItem"), Span::call_site()) - } else { - item_struct_name.clone() - }; - - let fetch_struct_name = Ident::new(&format!("{struct_name}Fetch"), Span::call_site()); - let read_only_fetch_struct_name = if fetch_struct_attributes.is_mutable { - Ident::new(&format!("{struct_name}ReadOnlyFetch"), Span::call_site()) - } else { - fetch_struct_name.clone() - }; - - let state_struct_name = Ident::new(&format!("{struct_name}State"), Span::call_site()); - - let fields = match &ast.data { - Data::Struct(DataStruct { - fields: Fields::Named(fields), - .. - }) => &fields.named, - _ => panic!("Expected a struct with named fields"), - }; - - let mut ignored_field_attrs = Vec::new(); - let mut ignored_field_visibilities = Vec::new(); - let mut ignored_field_idents = Vec::new(); - let mut ignored_field_types = Vec::new(); - let mut field_attrs = Vec::new(); - let mut field_visibilities = Vec::new(); - let mut field_idents = Vec::new(); - let mut field_types = Vec::new(); - let mut read_only_field_types = Vec::new(); - - for field in fields { - let WorldQueryFieldInfo { is_ignored, attrs } = read_world_query_field_info(field); - - let field_ident = field.ident.as_ref().unwrap().clone(); - if is_ignored { - ignored_field_attrs.push(attrs); - ignored_field_visibilities.push(field.vis.clone()); - ignored_field_idents.push(field_ident.clone()); - ignored_field_types.push(field.ty.clone()); - } else { - field_attrs.push(attrs); - field_visibilities.push(field.vis.clone()); - field_idents.push(field_ident.clone()); - let field_ty = field.ty.clone(); - field_types.push(quote!(#field_ty)); - read_only_field_types.push(quote!(<#field_ty as #path::query::WorldQuery>::ReadOnly)); - } - } - - let derive_args = &fetch_struct_attributes.derive_args; - // `#[derive()]` is valid syntax - let derive_macro_call = quote! { #[derive(#derive_args)] }; - - let impl_fetch = |is_readonly: bool| { - let struct_name = if is_readonly { - &read_only_struct_name - } else { - &struct_name - }; - let item_struct_name = if is_readonly { - &read_only_item_struct_name - } else { - &item_struct_name - }; - let fetch_struct_name = if is_readonly { - &read_only_fetch_struct_name - } else { - &fetch_struct_name - }; - - let field_types = if is_readonly { - &read_only_field_types - } else { - &field_types - }; - - quote! { - #derive_macro_call - #[doc = "Automatically generated [`WorldQuery`] item type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], returned when iterating over query results."] - #[automatically_derived] - #visibility struct #item_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#(#field_attrs)* #field_visibilities #field_idents: <#field_types as #path::query::WorldQuery>::Item<'__w>,)* - #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* - } - - #[doc(hidden)] - #[doc = "Automatically generated internal [`WorldQuery`] fetch type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], used to define the world data accessed by this query."] - #[automatically_derived] - #visibility struct #fetch_struct_name #user_impl_generics_with_world #user_where_clauses_with_world { - #(#field_idents: <#field_types as #path::query::WorldQuery>::Fetch<'__w>,)* - #(#ignored_field_idents: #ignored_field_types,)* - } - - // SAFETY: `update_component_access` and `update_archetype_component_access` are called on every field - unsafe impl #user_impl_generics #path::query::WorldQuery - for #struct_name #user_ty_generics #user_where_clauses { - - type Item<'__w> = #item_struct_name #user_ty_generics_with_world; - type Fetch<'__w> = #fetch_struct_name #user_ty_generics_with_world; - type ReadOnly = #read_only_struct_name #user_ty_generics; - type State = #state_struct_name #user_ty_generics; - - fn shrink<'__wlong: '__wshort, '__wshort>( - item: <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wlong> - ) -> <#struct_name #user_ty_generics as #path::query::WorldQuery>::Item<'__wshort> { - #item_struct_name { - #( - #field_idents: <#field_types>::shrink(item.#field_idents), - )* - #( - #ignored_field_idents: item.#ignored_field_idents, - )* - } - } - - unsafe fn init_fetch<'__w>( - _world: &'__w #path::world::World, - state: &Self::State, - _last_change_tick: u32, - _change_tick: u32 - ) -> <Self as #path::query::WorldQuery>::Fetch<'__w> { - #fetch_struct_name { - #(#field_idents: - <#field_types>::init_fetch( - _world, - &state.#field_idents, - _last_change_tick, - _change_tick - ), - )* - #(#ignored_field_idents: Default::default(),)* - } - } - - unsafe fn clone_fetch<'__w>( - _fetch: &<Self as #path::query::WorldQuery>::Fetch<'__w> - ) -> <Self as #path::query::WorldQuery>::Fetch<'__w> { - #fetch_struct_name { - #( - #field_idents: <#field_types>::clone_fetch(& _fetch. #field_idents), - )* - #( - #ignored_field_idents: Default::default(), - )* - } - } - - const IS_DENSE: bool = true #(&& <#field_types>::IS_DENSE)*; - - const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*; - - /// SAFETY: we call `set_archetype` for each member that implements `Fetch` - #[inline] - unsafe fn set_archetype<'__w>( - _fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>, - _state: &Self::State, - _archetype: &'__w #path::archetype::Archetype, - _table: &'__w #path::storage::Table - ) { - #(<#field_types>::set_archetype(&mut _fetch.#field_idents, &_state.#field_idents, _archetype, _table);)* - } - - /// SAFETY: we call `set_table` for each member that implements `Fetch` - #[inline] - unsafe fn set_table<'__w>( - _fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>, - _state: &Self::State, - _table: &'__w #path::storage::Table - ) { - #(<#field_types>::set_table(&mut _fetch.#field_idents, &_state.#field_idents, _table);)* - } - - /// SAFETY: we call `fetch` for each member that implements `Fetch`. - #[inline(always)] - unsafe fn fetch<'__w>( - _fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>, - _entity: Entity, - _table_row: usize - ) -> <Self as #path::query::WorldQuery>::Item<'__w> { - Self::Item { - #(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)* - #(#ignored_field_idents: Default::default(),)* - } - } - - #[allow(unused_variables)] - #[inline(always)] - unsafe fn filter_fetch<'__w>( - _fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>, - _entity: Entity, - _table_row: usize - ) -> bool { - true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))* - } - - fn update_component_access(state: &Self::State, _access: &mut #path::query::FilteredAccess<#path::component::ComponentId>) { - #( <#field_types>::update_component_access(&state.#field_idents, _access); )* - } - - fn update_archetype_component_access( - state: &Self::State, - _archetype: &#path::archetype::Archetype, - _access: &mut #path::query::Access<#path::archetype::ArchetypeComponentId> - ) { - #( - <#field_types>::update_archetype_component_access(&state.#field_idents, _archetype, _access); - )* - } - - fn init_state(world: &mut #path::world::World) -> #state_struct_name #user_ty_generics { - #state_struct_name { - #(#field_idents: <#field_types>::init_state(world),)* - #(#ignored_field_idents: Default::default(),)* - } - } - - fn matches_component_set(state: &Self::State, _set_contains_id: &impl Fn(#path::component::ComponentId) -> bool) -> bool { - true #(&& <#field_types>::matches_component_set(&state.#field_idents, _set_contains_id))* - } - } - } - }; - - let mutable_impl = impl_fetch(false); - let readonly_impl = if fetch_struct_attributes.is_mutable { - let world_query_impl = impl_fetch(true); - quote! { - #[doc(hidden)] - #[doc = "Automatically generated internal [`WorldQuery`] type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], used for read-only access."] - #[automatically_derived] - #visibility struct #read_only_struct_name #user_impl_generics #user_where_clauses { - #( #field_idents: #read_only_field_types, )* - #(#(#ignored_field_attrs)* #ignored_field_visibilities #ignored_field_idents: #ignored_field_types,)* - } - - #world_query_impl - } - } else { - quote! {} - }; - - let read_only_asserts = if fetch_struct_attributes.is_mutable { - quote! { - // Double-check that the data fetched by `<_ as WorldQuery>::ReadOnly` is read-only. - // This is technically unnecessary as `<_ as WorldQuery>::ReadOnly: ReadOnlyWorldQuery` - // but to protect against future mistakes we assert the assoc type implements `ReadOnlyWorldQuery` anyway - #( assert_readonly::<#read_only_field_types>(); )* - } - } else { - quote! { - // Statically checks that the safety guarantee of `ReadOnlyWorldQuery` for `$fetch_struct_name` actually holds true. - // We need this to make sure that we don't compile `ReadOnlyWorldQuery` if our struct contains nested `WorldQuery` - // members that don't implement it. I.e.: - // ``` - // #[derive(WorldQuery)] - // pub struct Foo { a: &'static mut MyComponent } - // ``` - #( assert_readonly::<#field_types>(); )* - } - }; - - TokenStream::from(quote! { - #mutable_impl - - #readonly_impl - - #[doc(hidden)] - #[doc = "Automatically generated internal [`WorldQuery`] state type for [`"] - #[doc = stringify!(#struct_name)] - #[doc = "`], used for caching."] - #[automatically_derived] - #visibility struct #state_struct_name #user_impl_generics #user_where_clauses { - #(#field_idents: <#field_types as #path::query::WorldQuery>::State,)* - #(#ignored_field_idents: #ignored_field_types,)* - } - - /// SAFETY: we assert fields are readonly below - unsafe impl #user_impl_generics #path::query::ReadOnlyWorldQuery - for #read_only_struct_name #user_ty_generics #user_where_clauses {} - - #[allow(dead_code)] - const _: () = { - fn assert_readonly<T>() - where - T: #path::query::ReadOnlyWorldQuery, - { - } - - // We generate a readonly assertion for every struct member. - fn assert_all #user_impl_generics_with_world () #user_where_clauses_with_world { - #read_only_asserts - } - }; - - // The original struct will most likely be left unused. As we don't want our users having - // to specify `#[allow(dead_code)]` for their custom queries, we are using this cursed - // workaround. - #[allow(dead_code)] - const _: () = { - fn dead_code_workaround #user_impl_generics ( - q: #struct_name #user_ty_generics, - q2: #read_only_struct_name #user_ty_generics - ) #user_where_clauses { - #(q.#field_idents;)* - #(q.#ignored_field_idents;)* - #(q2.#field_idents;)* - #(q2.#ignored_field_idents;)* - - } - }; - }) -} - -struct WorldQueryFieldInfo { - /// Has `#[fetch(ignore)]` or `#[filter_fetch(ignore)]` attribute. - is_ignored: bool, - /// All field attributes except for `world_query` ones. - attrs: Vec<Attribute>, -} - -fn read_world_query_field_info(field: &Field) -> WorldQueryFieldInfo { - let is_ignored = field - .attrs - .iter() - .find(|attr| { - attr.path - .get_ident() - .map_or(false, |ident| ident == WORLD_QUERY_ATTRIBUTE_NAME) - }) - .map_or(false, |attr| { - let mut is_ignored = false; - attr.parse_args_with(|input: ParseStream| { - if input - .parse::<Option<field_attr_keywords::ignore>>()? - .is_some() - { - is_ignored = true; - } - Ok(()) - }) - .unwrap_or_else(|_| panic!("Invalid `{WORLD_QUERY_ATTRIBUTE_NAME}` attribute format")); - - is_ignored - }); - - let attrs = field - .attrs - .iter() - .filter(|attr| { - attr.path - .get_ident() - .map_or(true, |ident| ident != WORLD_QUERY_ATTRIBUTE_NAME) - }) - .cloned() - .collect(); - - WorldQueryFieldInfo { is_ignored, attrs } -} diff --git a/azalea-ecs/azalea-ecs-macros/src/lib.rs b/azalea-ecs/azalea-ecs-macros/src/lib.rs deleted file mode 100755 index 9d4e9b2d..00000000 --- a/azalea-ecs/azalea-ecs-macros/src/lib.rs +++ /dev/null @@ -1,525 +0,0 @@ -//! A fork of bevy_ecs_macros that uses azalea_ecs instead of bevy_ecs. - -extern crate proc_macro; - -mod component; -mod fetch; -pub(crate) mod utils; - -use crate::fetch::derive_world_query_impl; -use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::{format_ident, quote}; -use syn::{ - parse::{Parse, ParseStream}, - parse_macro_input, - punctuated::Punctuated, - spanned::Spanned, - token::Comma, - DeriveInput, Field, GenericParam, Ident, Index, LitInt, Meta, MetaList, NestedMeta, Result, - Token, TypeParam, -}; -use utils::{derive_label, get_named_struct_fields, BevyManifest}; - -struct AllTuples { - macro_ident: Ident, - start: usize, - end: usize, - idents: Vec<Ident>, -} - -impl Parse for AllTuples { - fn parse(input: ParseStream) -> Result<Self> { - let macro_ident = input.parse::<Ident>()?; - input.parse::<Comma>()?; - let start = input.parse::<LitInt>()?.base10_parse()?; - input.parse::<Comma>()?; - let end = input.parse::<LitInt>()?.base10_parse()?; - input.parse::<Comma>()?; - let mut idents = vec![input.parse::<Ident>()?]; - while input.parse::<Comma>().is_ok() { - idents.push(input.parse::<Ident>()?); - } - - Ok(AllTuples { - macro_ident, - start, - end, - idents, - }) - } -} - -#[proc_macro] -pub fn all_tuples(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as AllTuples); - let len = input.end - input.start; - let mut ident_tuples = Vec::with_capacity(len); - for i in input.start..=input.end { - let idents = input - .idents - .iter() - .map(|ident| format_ident!("{}{}", ident, i)); - if input.idents.len() < 2 { - ident_tuples.push(quote! { - #(#idents)* - }); - } else { - ident_tuples.push(quote! { - (#(#idents),*) - }); - } - } - - let macro_ident = &input.macro_ident; - let invocations = (input.start..=input.end).map(|i| { - let ident_tuples = &ident_tuples[..i]; - quote! { - #macro_ident!(#(#ident_tuples),*); - } - }); - TokenStream::from(quote! { - #( - #invocations - )* - }) -} - -enum BundleFieldKind { - Component, - Ignore, -} - -const BUNDLE_ATTRIBUTE_NAME: &str = "bundle"; -const BUNDLE_ATTRIBUTE_IGNORE_NAME: &str = "ignore"; - -#[proc_macro_derive(Bundle, attributes(bundle))] -pub fn derive_bundle(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let ecs_path = azalea_ecs_path(); - - let named_fields = match get_named_struct_fields(&ast.data) { - Ok(fields) => &fields.named, - Err(e) => return e.into_compile_error().into(), - }; - - let mut field_kind = Vec::with_capacity(named_fields.len()); - - 'field_loop: for field in named_fields.iter() { - for attr in &field.attrs { - if attr.path.is_ident(BUNDLE_ATTRIBUTE_NAME) { - if let Ok(Meta::List(MetaList { nested, .. })) = attr.parse_meta() { - if let Some(&NestedMeta::Meta(Meta::Path(ref path))) = nested.first() { - if path.is_ident(BUNDLE_ATTRIBUTE_IGNORE_NAME) { - field_kind.push(BundleFieldKind::Ignore); - continue 'field_loop; - } - - return syn::Error::new( - path.span(), - format!( - "Invalid bundle attribute. Use `{BUNDLE_ATTRIBUTE_IGNORE_NAME}`" - ), - ) - .into_compile_error() - .into(); - } - - return syn::Error::new(attr.span(), format!("Invalid bundle attribute. Use `#[{BUNDLE_ATTRIBUTE_NAME}({BUNDLE_ATTRIBUTE_IGNORE_NAME})]`")).into_compile_error().into(); - } - } - } - - field_kind.push(BundleFieldKind::Component); - } - - let field = named_fields - .iter() - .map(|field| field.ident.as_ref().unwrap()) - .collect::<Vec<_>>(); - let field_type = named_fields - .iter() - .map(|field| &field.ty) - .collect::<Vec<_>>(); - - let mut field_component_ids = Vec::new(); - let mut field_get_components = Vec::new(); - let mut field_from_components = Vec::new(); - for ((field_type, field_kind), field) in - field_type.iter().zip(field_kind.iter()).zip(field.iter()) - { - match field_kind { - BundleFieldKind::Component => { - field_component_ids.push(quote! { - <#field_type as #ecs_path::bundle::_BevyBundle>::component_ids(components, storages, &mut *ids); - }); - field_get_components.push(quote! { - self.#field.get_components(&mut *func); - }); - field_from_components.push(quote! { - #field: <#field_type as #ecs_path::bundle::_BevyBundle>::from_components(ctx, &mut *func), - }); - } - - BundleFieldKind::Ignore => { - field_from_components.push(quote! { - #field: ::std::default::Default::default(), - }); - } - } - } - let generics = ast.generics; - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - let struct_name = &ast.ident; - - TokenStream::from(quote! { - /// SAFETY: ComponentId is returned in field-definition-order. [from_components] and [get_components] use field-definition-order - unsafe impl #impl_generics #ecs_path::bundle::_BevyBundle for #struct_name #ty_generics #where_clause { - fn component_ids( - components: &mut #ecs_path::component::Components, - storages: &mut #ecs_path::storage::Storages, - ids: &mut impl FnMut(#ecs_path::component::ComponentId) - ){ - #(#field_component_ids)* - } - - #[allow(unused_variables, non_snake_case)] - unsafe fn from_components<__T, __F>(ctx: &mut __T, func: &mut __F) -> Self - where - __F: FnMut(&mut __T) -> #ecs_path::ptr::OwningPtr<'_> - { - Self { - #(#field_from_components)* - } - } - - #[allow(unused_variables)] - fn get_components(self, func: &mut impl FnMut(#ecs_path::ptr::OwningPtr<'_>)) { - #(#field_get_components)* - } - } - }) -} - -fn get_idents(fmt_string: fn(usize) -> String, count: usize) -> Vec<Ident> { - (0..count) - .map(|i| Ident::new(&fmt_string(i), Span::call_site())) - .collect::<Vec<Ident>>() -} - -#[proc_macro] -pub fn impl_param_set(_input: TokenStream) -> TokenStream { - let mut tokens = TokenStream::new(); - let max_params = 8; - let params = get_idents(|i| format!("P{i}"), max_params); - let params_fetch = get_idents(|i| format!("PF{i}"), max_params); - let metas = get_idents(|i| format!("m{i}"), max_params); - let mut param_fn_muts = Vec::new(); - for (i, param) in params.iter().enumerate() { - let fn_name = Ident::new(&format!("p{i}"), Span::call_site()); - let index = Index::from(i); - param_fn_muts.push(quote! { - pub fn #fn_name<'a>(&'a mut self) -> <#param::Fetch as SystemParamFetch<'a, 'a>>::Item { - // SAFETY: systems run without conflicts with other systems. - // Conflicting params in ParamSet are not accessible at the same time - // ParamSets are guaranteed to not conflict with other SystemParams - unsafe { - <#param::Fetch as SystemParamFetch<'a, 'a>>::get_param(&mut self.param_states.#index, &self.system_meta, self.world, self.change_tick) - } - } - }); - } - - for param_count in 1..=max_params { - let param = ¶ms[0..param_count]; - let param_fetch = ¶ms_fetch[0..param_count]; - let meta = &metas[0..param_count]; - let param_fn_mut = ¶m_fn_muts[0..param_count]; - tokens.extend(TokenStream::from(quote! { - impl<'w, 's, #(#param: SystemParam,)*> SystemParam for ParamSet<'w, 's, (#(#param,)*)> - { - type Fetch = ParamSetState<(#(#param::Fetch,)*)>; - } - - // SAFETY: All parameters are constrained to ReadOnlyFetch, so World is only read - - unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> ReadOnlySystemParamFetch for ParamSetState<(#(#param_fetch,)*)> - where #(#param_fetch: ReadOnlySystemParamFetch,)* - { } - - // SAFETY: Relevant parameter ComponentId and ArchetypeComponentId access is applied to SystemMeta. If any ParamState conflicts - // with any prior access, a panic will occur. - - unsafe impl<#(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamState for ParamSetState<(#(#param_fetch,)*)> - { - fn init(world: &mut World, system_meta: &mut SystemMeta) -> Self { - #( - // Pretend to add each param to the system alone, see if it conflicts - let mut #meta = system_meta.clone(); - #meta.component_access_set.clear(); - #meta.archetype_component_access.clear(); - #param_fetch::init(world, &mut #meta); - let #param = #param_fetch::init(world, &mut system_meta.clone()); - )* - #( - system_meta - .component_access_set - .extend(#meta.component_access_set); - system_meta - .archetype_component_access - .extend(&#meta.archetype_component_access); - )* - ParamSetState((#(#param,)*)) - } - - fn new_archetype(&mut self, archetype: &Archetype, system_meta: &mut SystemMeta) { - let (#(#param,)*) = &mut self.0; - #( - #param.new_archetype(archetype, system_meta); - )* - } - - fn apply(&mut self, world: &mut World) { - self.0.apply(world) - } - } - - - - impl<'w, 's, #(#param_fetch: for<'w1, 's1> SystemParamFetch<'w1, 's1>,)*> SystemParamFetch<'w, 's> for ParamSetState<(#(#param_fetch,)*)> - { - type Item = ParamSet<'w, 's, (#(<#param_fetch as SystemParamFetch<'w, 's>>::Item,)*)>; - - #[inline] - unsafe fn get_param( - state: &'s mut Self, - system_meta: &SystemMeta, - world: &'w World, - change_tick: u32, - ) -> Self::Item { - ParamSet { - param_states: &mut state.0, - system_meta: system_meta.clone(), - world, - change_tick, - } - } - } - - impl<'w, 's, #(#param: SystemParam,)*> ParamSet<'w, 's, (#(#param,)*)> - { - - #(#param_fn_mut)* - } - })); - } - - tokens -} - -#[derive(Default)] -struct SystemParamFieldAttributes { - pub ignore: bool, -} - -static SYSTEM_PARAM_ATTRIBUTE_NAME: &str = "system_param"; - -/// Implement `SystemParam` to use a struct as a parameter in a system -#[proc_macro_derive(SystemParam, attributes(system_param))] -pub fn derive_system_param(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - let fields = match get_named_struct_fields(&ast.data) { - Ok(fields) => &fields.named, - Err(e) => return e.into_compile_error().into(), - }; - let path = azalea_ecs_path(); - - let field_attributes = fields - .iter() - .map(|field| { - ( - field, - field - .attrs - .iter() - .find(|a| *a.path.get_ident().as_ref().unwrap() == SYSTEM_PARAM_ATTRIBUTE_NAME) - .map_or_else(SystemParamFieldAttributes::default, |a| { - syn::custom_keyword!(ignore); - let mut attributes = SystemParamFieldAttributes::default(); - a.parse_args_with(|input: ParseStream| { - if input.parse::<Option<ignore>>()?.is_some() { - attributes.ignore = true; - } - Ok(()) - }) - .expect("Invalid 'system_param' attribute format."); - - attributes - }), - ) - }) - .collect::<Vec<(&Field, SystemParamFieldAttributes)>>(); - let mut fields = Vec::new(); - let mut field_indices = Vec::new(); - let mut field_types = Vec::new(); - let mut ignored_fields = Vec::new(); - let mut ignored_field_types = Vec::new(); - for (i, (field, attrs)) in field_attributes.iter().enumerate() { - if attrs.ignore { - ignored_fields.push(field.ident.as_ref().unwrap()); - ignored_field_types.push(&field.ty); - } else { - fields.push(field.ident.as_ref().unwrap()); - field_types.push(&field.ty); - field_indices.push(Index::from(i)); - } - } - - let generics = ast.generics; - let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); - - let lifetimeless_generics: Vec<_> = generics - .params - .iter() - .filter(|g| matches!(g, GenericParam::Type(_))) - .collect(); - - let mut punctuated_generics = Punctuated::<_, Token![,]>::new(); - punctuated_generics.extend(lifetimeless_generics.iter().map(|g| match g { - GenericParam::Type(g) => GenericParam::Type(TypeParam { - default: None, - ..g.clone() - }), - _ => unreachable!(), - })); - - let mut punctuated_generic_idents = Punctuated::<_, Token![,]>::new(); - punctuated_generic_idents.extend(lifetimeless_generics.iter().map(|g| match g { - GenericParam::Type(g) => &g.ident, - _ => unreachable!(), - })); - - let struct_name = &ast.ident; - let fetch_struct_visibility = &ast.vis; - - TokenStream::from(quote! { - // We define the FetchState struct in an anonymous scope to avoid polluting the user namespace. - // The struct can still be accessed via SystemParam::Fetch, e.g. EventReaderState can be accessed via - // <EventReader<'static, 'static, T> as SystemParam>::Fetch - const _: () = { - impl #impl_generics #path::system::SystemParam for #struct_name #ty_generics #where_clause { - type Fetch = FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents>; - } - - #[doc(hidden)] - #fetch_struct_visibility struct FetchState <TSystemParamState, #punctuated_generic_idents> { - state: TSystemParamState, - marker: std::marker::PhantomData<fn()->(#punctuated_generic_idents)> - } - - unsafe impl<TSystemParamState: #path::system::SystemParamState, #punctuated_generics> #path::system::SystemParamState for FetchState <TSystemParamState, #punctuated_generic_idents> #where_clause { - fn init(world: &mut #path::world::World, system_meta: &mut #path::system::SystemMeta) -> Self { - Self { - state: TSystemParamState::init(world, system_meta), - marker: std::marker::PhantomData, - } - } - - fn new_archetype(&mut self, archetype: &#path::archetype::Archetype, system_meta: &mut #path::system::SystemMeta) { - self.state.new_archetype(archetype, system_meta) - } - - fn apply(&mut self, world: &mut #path::world::World) { - self.state.apply(world) - } - } - - impl #impl_generics #path::system::SystemParamFetch<'w, 's> for FetchState <(#(<#field_types as #path::system::SystemParam>::Fetch,)*), #punctuated_generic_idents> #where_clause { - type Item = #struct_name #ty_generics; - unsafe fn get_param( - state: &'s mut Self, - system_meta: &#path::system::SystemMeta, - world: &'w #path::world::World, - change_tick: u32, - ) -> Self::Item { - #struct_name { - #(#fields: <<#field_types as #path::system::SystemParam>::Fetch as #path::system::SystemParamFetch>::get_param(&mut state.state.#field_indices, system_meta, world, change_tick),)* - #(#ignored_fields: <#ignored_field_types>::default(),)* - } - } - } - - // Safety: The `ParamState` is `ReadOnlySystemParamFetch`, so this can only read from the `World` - unsafe impl<TSystemParamState: #path::system::SystemParamState + #path::system::ReadOnlySystemParamFetch, #punctuated_generics> #path::system::ReadOnlySystemParamFetch for FetchState <TSystemParamState, #punctuated_generic_idents> #where_clause {} - }; - }) -} - -/// Implement `WorldQuery` to use a struct as a parameter in a query -#[proc_macro_derive(WorldQuery, attributes(world_query))] -pub fn derive_world_query(input: TokenStream) -> TokenStream { - let ast = parse_macro_input!(input as DeriveInput); - derive_world_query_impl(ast) -} - -/// Generates an impl of the `SystemLabel` trait. -/// -/// This works only for unit structs, or enums with only unit variants. -/// You may force a struct or variant to behave as if it were fieldless with -/// `#[system_label(ignore_fields)]`. -#[proc_macro_derive(SystemLabel, attributes(system_label))] -pub fn derive_system_label(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let mut trait_path = azalea_ecs_path(); - trait_path.segments.push(format_ident!("schedule").into()); - trait_path - .segments - .push(format_ident!("SystemLabel").into()); - derive_label(input, &trait_path, "system_label") -} - -/// Generates an impl of the `StageLabel` trait. -/// -/// This works only for unit structs, or enums with only unit variants. -/// You may force a struct or variant to behave as if it were fieldless with -/// `#[stage_label(ignore_fields)]`. -#[proc_macro_derive(StageLabel, attributes(stage_label))] -pub fn derive_stage_label(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let mut trait_path = azalea_ecs_path(); - trait_path.segments.push(format_ident!("schedule").into()); - trait_path - .segments - .push(format_ident!("_BevyStageLabel").into()); - derive_label(input, &trait_path, "stage_label") -} - -/// Generates an impl of the `RunCriteriaLabel` trait. -/// -/// This works only for unit structs, or enums with only unit variants. -/// You may force a struct or variant to behave as if it were fieldless with -/// `#[run_criteria_label(ignore_fields)]`. -#[proc_macro_derive(RunCriteriaLabel, attributes(run_criteria_label))] -pub fn derive_run_criteria_label(input: TokenStream) -> TokenStream { - let input = parse_macro_input!(input as DeriveInput); - let mut trait_path = azalea_ecs_path(); - trait_path.segments.push(format_ident!("schedule").into()); - trait_path - .segments - .push(format_ident!("RunCriteriaLabel").into()); - derive_label(input, &trait_path, "run_criteria_label") -} - -pub(crate) fn azalea_ecs_path() -> syn::Path { - BevyManifest::default().get_path("azalea_ecs") -} - -#[proc_macro_derive(Resource)] -pub fn derive_resource(input: TokenStream) -> TokenStream { - component::derive_resource(input) -} - -#[proc_macro_derive(Component, attributes(component))] -pub fn derive_component(input: TokenStream) -> TokenStream { - component::derive_component(input) -} diff --git a/azalea-ecs/azalea-ecs-macros/src/utils/attrs.rs b/azalea-ecs/azalea-ecs-macros/src/utils/attrs.rs deleted file mode 100644 index 05f0712a..00000000 --- a/azalea-ecs/azalea-ecs-macros/src/utils/attrs.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![allow(dead_code)] - -use syn::DeriveInput; - -use super::symbol::Symbol; - -pub fn parse_attrs(ast: &DeriveInput, attr_name: Symbol) -> syn::Result<Vec<syn::NestedMeta>> { - let mut list = Vec::new(); - for attr in ast.attrs.iter().filter(|a| a.path == attr_name) { - match attr.parse_meta()? { - syn::Meta::List(meta) => list.extend(meta.nested.into_iter()), - other => { - return Err(syn::Error::new_spanned( - other, - format!("expected #[{attr_name}(...)]"), - )) - } - } - } - Ok(list) -} - -pub fn get_lit_str(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<&syn::LitStr> { - if let syn::Lit::Str(lit) = lit { - Ok(lit) - } else { - Err(syn::Error::new_spanned( - lit, - format!("expected {attr_name} attribute to be a string: `{attr_name} = \"...\"`"), - )) - } -} - -pub fn get_lit_bool(attr_name: Symbol, lit: &syn::Lit) -> syn::Result<bool> { - if let syn::Lit::Bool(lit) = lit { - Ok(lit.value()) - } else { - Err(syn::Error::new_spanned( - lit, - format!( - "expected {attr_name} attribute to be a bool value, `true` or `false`: `{attr_name} = ...`" - ), - )) - } -} diff --git a/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs b/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs deleted file mode 100644 index c79e3efe..00000000 --- a/azalea-ecs/azalea-ecs-macros/src/utils/mod.rs +++ /dev/null @@ -1,227 +0,0 @@ -#![allow(dead_code)] - -extern crate proc_macro; - -mod attrs; -mod shape; -mod symbol; - -pub use attrs::*; -pub use shape::*; -pub use symbol::*; - -use proc_macro::TokenStream; -use quote::{quote, quote_spanned}; -use std::{env, path::PathBuf}; -use syn::spanned::Spanned; -use toml::{map::Map, Value}; - -pub struct BevyManifest { - manifest: Map<String, Value>, -} - -impl Default for BevyManifest { - fn default() -> Self { - Self { - manifest: env::var_os("CARGO_MANIFEST_DIR") - .map(PathBuf::from) - .map(|mut path| { - path.push("Cargo.toml"); - let manifest = std::fs::read_to_string(path).unwrap(); - toml::from_str(&manifest).unwrap() - }) - .unwrap(), - } - } -} - -impl BevyManifest { - pub fn maybe_get_path(&self, name: &str) -> Option<syn::Path> { - const AZALEA: &str = "azalea"; - const AZALEA_ECS: &str = "azalea_ecs"; - const BEVY_ECS: &str = "bevy_ecs"; - const BEVY: &str = "bevy"; - - fn dep_package(dep: &Value) -> Option<&str> { - if dep.as_str().is_some() { - None - } else { - dep.as_table() - .unwrap() - .get("package") - .map(|name| name.as_str().unwrap()) - } - } - - let find_in_deps = |deps: &Map<String, Value>| -> Option<syn::Path> { - let package = if let Some(dep) = deps.get(name) { - return Some(Self::parse_str(dep_package(dep).unwrap_or(name))); - } else if let Some(dep) = deps.get(AZALEA) { - dep_package(dep).unwrap_or(AZALEA) - } else if let Some(dep) = deps.get(AZALEA_ECS) { - dep_package(dep).unwrap_or(AZALEA_ECS) - } else if let Some(dep) = deps.get(BEVY_ECS) { - dep_package(dep).unwrap_or(BEVY_ECS) - } else if let Some(dep) = deps.get(BEVY) { - dep_package(dep).unwrap_or(BEVY) - } else { - return None; - }; - - let mut path = Self::parse_str::<syn::Path>(package); - if let Some(module) = name.strip_prefix("azalea_") { - path.segments.push(Self::parse_str(module)); - } - Some(path) - }; - - let deps = self - .manifest - .get("dependencies") - .map(|deps| deps.as_table().unwrap()); - let deps_dev = self - .manifest - .get("dev-dependencies") - .map(|deps| deps.as_table().unwrap()); - - deps.and_then(find_in_deps) - .or_else(|| deps_dev.and_then(find_in_deps)) - } - - /// Returns the path for the crate with the given name. - /// - /// This is a convenience method for constructing a [manifest] and - /// calling the [`get_path`] method. - /// - /// This method should only be used where you just need the path and can't - /// cache the [manifest]. If caching is possible, it's recommended to create - /// the [manifest] yourself and use the [`get_path`] method. - /// - /// [`get_path`]: Self::get_path - /// [manifest]: Self - pub fn get_path_direct(name: &str) -> syn::Path { - Self::default().get_path(name) - } - - pub fn get_path(&self, name: &str) -> syn::Path { - self.maybe_get_path(name) - .unwrap_or_else(|| Self::parse_str(name)) - } - - pub fn parse_str<T: syn::parse::Parse>(path: &str) -> T { - syn::parse(path.parse::<TokenStream>().unwrap()).unwrap() - } -} - -/// Derive a label trait -/// -/// # Args -/// -/// - `input`: The [`syn::DeriveInput`] for struct that is deriving the label -/// trait -/// - `trait_path`: The path [`syn::Path`] to the label trait -pub fn derive_label( - input: syn::DeriveInput, - trait_path: &syn::Path, - attr_name: &str, -) -> TokenStream { - // return true if the variant specified is an `ignore_fields` attribute - fn is_ignore(attr: &syn::Attribute, attr_name: &str) -> bool { - if attr.path.get_ident().as_ref().unwrap() != &attr_name { - return false; - } - - syn::custom_keyword!(ignore_fields); - attr.parse_args_with(|input: syn::parse::ParseStream| { - let ignore = input.parse::<Option<ignore_fields>>()?.is_some(); - Ok(ignore) - }) - .unwrap() - } - - let ident = input.ident.clone(); - - let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); - let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause { - where_token: Default::default(), - predicates: Default::default(), - }); - where_clause - .predicates - .push(syn::parse2(quote! { Self: 'static }).unwrap()); - - let as_str = match input.data { - syn::Data::Struct(d) => { - // see if the user tried to ignore fields incorrectly - if let Some(attr) = d - .fields - .iter() - .flat_map(|f| &f.attrs) - .find(|a| is_ignore(a, attr_name)) - { - let err_msg = format!("`#[{attr_name}(ignore_fields)]` cannot be applied to fields individually: add it to the struct declaration"); - return quote_spanned! { - attr.span() => compile_error!(#err_msg); - } - .into(); - } - // Structs must either be fieldless, or explicitly ignore the fields. - let ignore_fields = input.attrs.iter().any(|a| is_ignore(a, attr_name)); - if matches!(d.fields, syn::Fields::Unit) || ignore_fields { - let lit = ident.to_string(); - quote! { #lit } - } else { - let err_msg = format!("Labels cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); - return quote_spanned! { - d.fields.span() => compile_error!(#err_msg); - } - .into(); - } - } - syn::Data::Enum(d) => { - // check if the user put #[label(ignore_fields)] in the wrong place - if let Some(attr) = input.attrs.iter().find(|a| is_ignore(a, attr_name)) { - let err_msg = format!("`#[{attr_name}(ignore_fields)]` can only be applied to enum variants or struct declarations"); - return quote_spanned! { - attr.span() => compile_error!(#err_msg); - } - .into(); - } - let arms = d.variants.iter().map(|v| { - // Variants must either be fieldless, or explicitly ignore the fields. - let ignore_fields = v.attrs.iter().any(|a| is_ignore(a, attr_name)); - if matches!(v.fields, syn::Fields::Unit) | ignore_fields { - let mut path = syn::Path::from(ident.clone()); - path.segments.push(v.ident.clone().into()); - let lit = format!("{ident}::{}", v.ident.clone()); - quote! { #path { .. } => #lit } - } else { - let err_msg = format!("Label variants cannot contain data, unless explicitly ignored with `#[{attr_name}(ignore_fields)]`"); - quote_spanned! { - v.fields.span() => _ => { compile_error!(#err_msg); } - } - } - }); - quote! { - match self { - #(#arms),* - } - } - } - syn::Data::Union(_) => { - return quote_spanned! { - input.span() => compile_error!("Unions cannot be used as labels."); - } - .into(); - } - }; - - (quote! { - impl #impl_generics #trait_path for #ident #ty_generics #where_clause { - fn as_str(&self) -> &'static str { - #as_str - } - } - }) - .into() -} diff --git a/azalea-ecs/azalea-ecs-macros/src/utils/shape.rs b/azalea-ecs/azalea-ecs-macros/src/utils/shape.rs deleted file mode 100644 index 98230749..00000000 --- a/azalea-ecs/azalea-ecs-macros/src/utils/shape.rs +++ /dev/null @@ -1,21 +0,0 @@ -use proc_macro::Span; -use syn::{Data, DataStruct, Error, Fields, FieldsNamed}; - -/// Get the fields of a data structure if that structure is a struct with named -/// fields; otherwise, return a compile error that points to the site of the -/// macro invocation. -pub fn get_named_struct_fields(data: &syn::Data) -> syn::Result<&FieldsNamed> { - match data { - Data::Struct(DataStruct { - fields: Fields::Named(fields), - .. - }) => Ok(fields), - _ => Err(Error::new( - // This deliberately points to the call site rather than the structure - // body; marking the entire body as the source of the error makes it - // impossible to figure out which `derive` has a problem. - Span::call_site().into(), - "Only structs with named fields are supported", - )), - } -} diff --git a/azalea-ecs/azalea-ecs-macros/src/utils/symbol.rs b/azalea-ecs/azalea-ecs-macros/src/utils/symbol.rs deleted file mode 100644 index dc639f4d..00000000 --- a/azalea-ecs/azalea-ecs-macros/src/utils/symbol.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::fmt::{self, Display}; -use syn::{Ident, Path}; - -#[derive(Copy, Clone)] -pub struct Symbol(pub &'static str); - -impl PartialEq<Symbol> for Ident { - fn eq(&self, word: &Symbol) -> bool { - self == word.0 - } -} - -impl<'a> PartialEq<Symbol> for &'a Ident { - fn eq(&self, word: &Symbol) -> bool { - *self == word.0 - } -} - -impl PartialEq<Symbol> for Path { - fn eq(&self, word: &Symbol) -> bool { - self.is_ident(word.0) - } -} - -impl<'a> PartialEq<Symbol> for &'a Path { - fn eq(&self, word: &Symbol) -> bool { - self.is_ident(word.0) - } -} - -impl Display for Symbol { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str(self.0) - } -} diff --git a/azalea-ecs/src/lib.rs b/azalea-ecs/src/lib.rs deleted file mode 100644 index bc374e45..00000000 --- a/azalea-ecs/src/lib.rs +++ /dev/null @@ -1,157 +0,0 @@ -#![feature(trait_alias)] - -//! Re-export important parts of [`bevy_ecs`] and [`bevy_app`] and make them -//! more compatible with Azalea. -//! -//! This is completely compatible with `bevy_ecs`, so it won't cause issues if -//! you use plugins meant for Bevy. -//! -//! Changes: -//! - Add [`TickPlugin`], [`TickStage`] and [`AppTickExt`] (which adds -//! `app.add_tick_system` and `app.add_tick_system_set`). -//! - Change the macros to use azalea/azalea_ecs instead of bevy/bevy_ecs -//! - Rename `world::World` to [`ecs::Ecs`] -//! - Re-export `bevy_app` in the [`app`] module. -//! -//! [`bevy_ecs`]: https://docs.rs/bevy_ecs -//! [`bevy_app`]: https://docs.rs/bevy_app - -use std::time::{Duration, Instant}; - -pub mod ecs { - pub use bevy_ecs::world::World as Ecs; - pub use bevy_ecs::world::{EntityMut, EntityRef, Mut}; -} -pub mod component { - pub use azalea_ecs_macros::Component; - pub use bevy_ecs::component::{ComponentId, ComponentStorage, Components, TableStorage}; - - // we do this because re-exporting Component would re-export the macro as well, - // which is bad (since we have our own Component macro) - // instead, we have to do this so Component is a trait alias and the original - // impl-able trait is still available as _BevyComponent - pub trait Component = bevy_ecs::component::Component; - pub use bevy_ecs::component::Component as _BevyComponent; -} -pub mod bundle { - pub use azalea_ecs_macros::Bundle; - pub trait Bundle = bevy_ecs::bundle::Bundle; - pub use bevy_ecs::bundle::Bundle as _BevyBundle; -} -pub mod system { - pub use azalea_ecs_macros::Resource; - pub use bevy_ecs::system::{ - Command, Commands, EntityCommands, Query, Res, ResMut, SystemState, - }; - pub trait Resource = bevy_ecs::system::Resource; - pub use bevy_ecs::system::Resource as _BevyResource; -} -pub mod schedule { - pub use azalea_ecs_macros::StageLabel; - pub use bevy_ecs::schedule::{ - IntoRunCriteria, IntoSystemDescriptor, ReportExecutionOrderAmbiguities, Schedule, Stage, - SystemSet, SystemStage, - }; - pub trait StageLabel = bevy_ecs::schedule::StageLabel; - pub use bevy_ecs::schedule::StageLabel as _BevyStageLabel; -} -pub use bevy_app as app; -pub use bevy_ecs::{entity, event, ptr, query, storage}; - -use app::{App, CoreStage, Plugin}; -use bevy_ecs::schedule::*; -use ecs::Ecs; - -pub struct TickPlugin { - /// How often a tick should happen. 50 milliseconds by default. Set to 0 to - /// tick every update. - pub tick_interval: Duration, -} -impl Plugin for TickPlugin { - fn build(&self, app: &mut App) { - app.add_stage_before( - CoreStage::Update, - TickLabel, - TickStage { - interval: self.tick_interval, - next_tick: Instant::now(), - stage: Box::new(SystemStage::parallel()), - }, - ); - } -} -impl Default for TickPlugin { - fn default() -> Self { - Self { - tick_interval: Duration::from_millis(50), - } - } -} - -#[derive(StageLabel)] -struct TickLabel; - -/// A [`Stage`] that runs every 50 milliseconds. -pub struct TickStage { - pub interval: Duration, - pub next_tick: Instant, - stage: Box<dyn Stage>, -} - -impl Stage for TickStage { - fn run(&mut self, ecs: &mut Ecs) { - // if the interval is 0, that means it runs every tick - if self.interval.is_zero() { - self.stage.run(ecs); - return; - } - // keep calling run until it's caught up - // TODO: Minecraft bursts up to 10 ticks and then skips, we should too (but - // check the source so we do it right) - while Instant::now() > self.next_tick { - self.next_tick += self.interval; - self.stage.run(ecs); - } - } -} - -pub trait AppTickExt { - fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App; - fn add_tick_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut App; -} - -impl AppTickExt for App { - /// Adds a set of ECS systems that will run every 50 milliseconds. - /// - /// Note that you should NOT have `EventReader`s in tick systems, as this - /// will make them sometimes be missed. - fn add_tick_system_set(&mut self, system_set: SystemSet) -> &mut App { - let tick_stage = self - .schedule - .get_stage_mut::<TickStage>(TickLabel) - .expect("Tick Stage not found"); - let stage = tick_stage - .stage - .downcast_mut::<SystemStage>() - .expect("Fixed Timestep sub-stage is not a SystemStage"); - stage.add_system_set(system_set); - self - } - - /// Adds a new ECS system that will run every 50 milliseconds. - /// - /// Note that you should NOT have `EventReader`s in tick systems, as this - /// will make them sometimes be missed. - fn add_tick_system<Params>(&mut self, system: impl IntoSystemDescriptor<Params>) -> &mut App { - let tick_stage = self - .schedule - .get_stage_mut::<TickStage>(TickLabel) - .expect("Tick Stage not found"); - let stage = tick_stage - .stage - .downcast_mut::<SystemStage>() - .expect("Fixed Timestep sub-stage is not a SystemStage"); - stage.add_system(system); - self - } -} |
