Skip to content
Snippets Groups Projects
Verified Commit 9a36d975 authored by Louis's avatar Louis :fire:
Browse files

Implement tag finder macro

parent 0db0afa9
Branches trunk
No related tags found
No related merge requests found
[package] [package]
name = "micro_games_macros" name = "micro_games_macros"
version = "0.5.0" version = "0.6.0"
edition = "2021" edition = "2021"
authors = ["Louis Capitanchik <contact@louiscap.co>"] authors = ["Louis Capitanchik <contact@louiscap.co>"]
description = "Utility macros to make it easier to build complex systems with Bevy" description = "Utility macros to make it easier to build complex systems with Bevy"
......
...@@ -3,9 +3,14 @@ ...@@ -3,9 +3,14 @@
[![docs.rs](https://img.shields.io/docsrs/micro_games_macros?style=for-the-badge)](https://docs.rs/micro_games_macros) [![docs.rs](https://img.shields.io/docsrs/micro_games_macros?style=for-the-badge)](https://docs.rs/micro_games_macros)
[![Crates.io](https://img.shields.io/crates/v/micro_games_macros?style=for-the-badge)](https://crates.io/crates/micro_games_macros) [![Crates.io](https://img.shields.io/crates/v/micro_games_macros?style=for-the-badge)](https://crates.io/crates/micro_games_macros)
A collection of utility macros for building games A collection of utility macros for building games. While this library should theoretically work with
any version of Bevy that includes the required traits and structs for a given macro, it is worth checking the
version of bevy listed as a dev dependency in `Cargo.toml`, as that will be the version tested against.
**Current Version Support: 0.5.x -> Bevy 0.14** This works because the library does not directly depend on bevy, but instead just generates fully qualified paths
to derives, traits, and structs that will resolve to the version of Bevy used in your downstream project
**Current Version Support: 0.6.x -> Bevy 0.14**
## Macros ## Macros
...@@ -82,4 +87,21 @@ struct MyComponent; ...@@ -82,4 +87,21 @@ struct MyComponent;
// ... Other Kayak Setup ... // ... Other Kayak Setup ...
```
### Tag Finder
Create a system param for checking whether an entity has one of a specified number of tags. The tag finder will also
create the tag components, which can use either table or sparse storage
```rust
use micro_games_macros::tag_finder;
#[tag_finder]
pub struct TagFinder {
player: Player,
item: Item,
#[sparse]
hover: Hover,
}
``` ```
\ No newline at end of file
...@@ -20,6 +20,7 @@ fq!(FQVec => ::std::vec::Vec); ...@@ -20,6 +20,7 @@ fq!(FQVec => ::std::vec::Vec);
fq!(FQString => ::std::string::String); fq!(FQString => ::std::string::String);
fq!(FQHashMap => ::std::collections::HashMap); fq!(FQHashMap => ::std::collections::HashMap);
fq!(FQClone => ::core::clone::Clone); fq!(FQClone => ::core::clone::Clone);
fq!(FQCopy => ::core::marker::Copy);
fq!(FQDebug => ::core::fmt::Debug); fq!(FQDebug => ::core::fmt::Debug);
fq!(FQDisplay => ::core::fmt::Display); fq!(FQDisplay => ::core::fmt::Display);
fq!(FQDefault => ::core::default::Default); fq!(FQDefault => ::core::default::Default);
...@@ -44,9 +45,13 @@ fq!(BevyWorld => ::bevy::ecs::world::World); ...@@ -44,9 +45,13 @@ fq!(BevyWorld => ::bevy::ecs::world::World);
fq!(BevyEvent => ::bevy::ecs::event::Event); fq!(BevyEvent => ::bevy::ecs::event::Event);
fq!(BevyRes => ::bevy::ecs::system::Res); fq!(BevyRes => ::bevy::ecs::system::Res);
fq!(BevyResMut => ::bevy::ecs::system::ResMut); fq!(BevyResMut => ::bevy::ecs::system::ResMut);
fq!(BevyWith => ::bevy::ecs::query::With);
fq!(BevyQuery => ::bevy::ecs::system::Query);
fq!(BevyEntity => ::bevy::ecs::entity::Entity);
fq!(BevyEventReader => ::bevy::ecs::event::EventReader); fq!(BevyEventReader => ::bevy::ecs::event::EventReader);
fq!(BevySystemParam => ::bevy::ecs::system::SystemParam); fq!(BevySystemParam => ::bevy::ecs::system::SystemParam);
fq!(BevyResource => ::bevy::ecs::system::Resource); fq!(BevyResource => ::bevy::ecs::system::Resource);
fq!(BevyComponent => ::bevy::ecs::component::Component);
fq!(BevyTypePath=> ::bevy::reflect::TypePath); fq!(BevyTypePath=> ::bevy::reflect::TypePath);
fq!(BevyTypeUuid => ::bevy::reflect::TypeUuid); fq!(BevyTypeUuid => ::bevy::reflect::TypeUuid);
fq!(BevyDeref => ::bevy::prelude::Deref); fq!(BevyDeref => ::bevy::prelude::Deref);
......
...@@ -104,6 +104,7 @@ pub(crate) mod json_loader; ...@@ -104,6 +104,7 @@ pub(crate) mod json_loader;
#[cfg(feature = "kayak")] #[cfg(feature = "kayak")]
pub(crate) mod kayak; pub(crate) mod kayak;
pub(crate) mod std_traits; pub(crate) mod std_traits;
pub(crate) mod tag_finder;
/// Generate loader and handler implementations for keyed JSON resources. The asset must implement `bevy::asset::Asset`, as well as /// Generate loader and handler implementations for keyed JSON resources. The asset must implement `bevy::asset::Asset`, as well as
/// `serde::Deserialize` and `serde::Serialize` /// `serde::Deserialize` and `serde::Serialize`
...@@ -369,3 +370,48 @@ pub fn derive_from_inner(input: TokenStream) -> TokenStream { ...@@ -369,3 +370,48 @@ pub fn derive_from_inner(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput); let input = parse_macro_input!(input as DeriveInput);
std_traits::from_inner::derive(input).into() std_traits::from_inner::derive(input).into()
} }
/// Create a TagFinder type, used for checking what tag components might contain
///
/// ## Examples
///
/// Spawns a number of entities, and then perform different behaviour based on tags
///
/// ```
/// # use bevy::prelude::*;
/// # use micro_games_macros::tag_finder;
///
/// #[tag_finder]
/// struct TagFinder {
/// ally: Ally,
/// enemy: Enemy,
/// }
///
/// pub fn spawn_entities(mut commands: Commands) {
/// commands.spawn((Ally, SpatialBundle::default()));
/// commands.spawn((Enemy, SpatialBundle::default()));
/// commands.spawn((Enemy, SpatialBundle::default()));
/// }
///
/// pub fn my_checking_system(query: Query<(Entity, &Transform)>, tags: TagFinder) {
/// for (e, t) in &query {
/// if tags.is_ally(e) {
/// println!("Celebrate");
/// } else if tags.is_enemy(e) {
/// println!("Run away")
/// }
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn tag_finder(_: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
tag_finder::tag_finder(input).into()
}
/// Marker attribute, used exclusively by other proc macros
#[proc_macro_attribute]
#[doc(hidden)]
pub fn sparse(_: TokenStream, input: TokenStream) -> TokenStream {
input
}
use crate::fqpath::{
BevyComponent, BevyEntity, BevyQuery, BevySystemParam, BevyWith, FQClone, FQCopy, FQDebug,
FQDefault,
};
use crate::utils::{ident_prefix, only_named_struct, only_struct};
use proc_macro2::TokenStream;
use quote::{quote, quote_spanned};
use syn::spanned::Spanned;
use syn::{DeriveInput, Field};
macro_rules! or_return {
($expr: expr) => {
match $expr {
Ok(value) => value,
Err(err) => return err,
}
};
}
pub fn tag_finder(input: DeriveInput) -> TokenStream {
let struct_data = or_return!(only_struct(&input));
let _ = or_return!(only_named_struct(struct_data));
let struct_fields: TokenStream = struct_data.fields.iter().map(map_named_field).collect();
let struct_methods: TokenStream = struct_data.fields.iter().map(map_named_method).collect();
let tag_types: TokenStream = struct_data.fields.iter().map(map_tag_types).collect();
let DeriveInput { vis, ident, .. } = input;
quote! {
#tag_types
#[derive(#BevySystemParam)]
#vis struct #ident <'w, 's> {
#struct_fields
}
impl <'w, 's> #ident<'w, 's> {
#struct_methods
}
}
}
fn map_tag_types(field: &Field) -> TokenStream {
let Field { ty, attrs, .. } = field;
let is_sparse = attrs.iter().any(|attr| attr.path().is_ident("sparse"));
if is_sparse {
quote! {
#[derive(#FQCopy, #FQClone, #FQDefault, #FQDebug, #BevyComponent)]
#[component(storage = "SparseSet")]
pub struct #ty;
}
} else {
quote! {
#[derive(#FQCopy, #FQClone, #FQDefault, #FQDebug, #BevyComponent)]
pub struct #ty;
}
}
}
fn map_named_field(field: &Field) -> TokenStream {
let Field { ident, ty, .. } = field;
quote! {
#ident: #BevyQuery<'w, 's, (), #BevyWith<#ty>>,
}
}
fn map_named_method(field: &Field) -> TokenStream {
let Field { ident, ty, .. } = field;
match ident {
None => {
quote_spanned! { ty.span() => compile_error!("Unable to map field with type {} that does not have a name"); }
}
Some(ident) => {
let name = ident_prefix(ident, "is_");
quote! {
pub fn #name(&self, entity: #BevyEntity) -> bool {
self.#ident.contains(entity)
}
}
}
}
}
mod components;
pub use components::tag_finder;
...@@ -2,7 +2,10 @@ use proc_macro2::{Ident, TokenStream, TokenTree}; ...@@ -2,7 +2,10 @@ use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::{quote, quote_spanned, ToTokens}; use quote::{quote, quote_spanned, ToTokens};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Display; use std::fmt::Display;
use syn::{spanned::Spanned, Attribute, DataStruct, Field, Meta, MetaNameValue, Visibility}; use syn::{
spanned::Spanned, Attribute, Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Meta,
MetaNameValue, Visibility,
};
/// Convert a list of characters to snake case /// Convert a list of characters to snake case
pub fn snake_case(chars: impl Iterator<Item = char>) -> String { pub fn snake_case(chars: impl Iterator<Item = char>) -> String {
...@@ -160,6 +163,30 @@ pub fn module_wrapped( ...@@ -160,6 +163,30 @@ pub fn module_wrapped(
} }
} }
pub fn only_struct<'a>(input: &'a DeriveInput) -> Result<&'a DataStruct, TokenStream> {
match &input.data {
Data::Struct(at) => Ok(at),
Data::Enum(en) => Err(
quote_spanned!(en.enum_token.span() => compile_error!("Unsupported for Enum, only for Struct");),
),
Data::Union(un) => Err(
quote_spanned!(un.union_token.span() => compile_error!("Unsupported for Union, only for Struct");),
),
}
}
pub fn only_named_struct(data: &DataStruct) -> Result<&FieldsNamed, TokenStream> {
match &data.fields {
Fields::Named(named) => Ok(named),
Fields::Unnamed(uf) => Err(
quote_spanned!(uf.paren_token.span => compile_error!("Unsupported for tuple structs, must use named structs");),
),
Fields::Unit => Err(
quote_spanned!(data.semi_token.span() => compile_error!("Unsupported for unit structs, must use named structs");),
),
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::utils::{snake_case, unquote}; use crate::utils::{snake_case, unquote};
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment