Skip to content

Commit

Permalink
Generics working but its iffy
Browse files Browse the repository at this point in the history
  • Loading branch information
TGRCdev committed Jan 15, 2025
1 parent d1a9653 commit b811f7d
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 77 deletions.
176 changes: 112 additions & 64 deletions bevy-butler-proc-macro/src/system_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,45 @@
use proc_macro2::{Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::{
parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, Attribute, Error, Expr, ExprPath, Ident, ItemFn, Macro, Meta, MetaList, Token
parse::{Parse, ParseStream, Parser}, punctuated::Punctuated, spanned::Spanned, Attribute, Error, Expr, ExprPath, Ident, ItemFn, Macro, Meta, MetaList, Path, PathArguments, Token, Type
};

#[derive(Clone)]
#[derive(Clone, Debug)]
pub(crate) struct SystemArgs {
pub schedule: Option<ExprPath>,
pub plugin: Option<ExprPath>,
pub generics: Option<Punctuated<Type, Token![,]>>,
pub transforms: Vec<(ExprPath, Option<Expr>)>,
pub span: Span,
}

pub(crate) fn register_system_set_block(
systems: &[(Ident, Option<SystemArgs>)],
args: &SystemArgs,
systems: &[(Path, Option<SystemArgs>)],
set_args: &SystemArgs,
) -> TokenStream {
let plugin = args.require_plugin().unwrap();
let schedule = args.require_schedule().unwrap();
let plugin = set_args.require_plugin().unwrap();
let schedule = set_args.require_schedule().unwrap();

let mut sys_set = Punctuated::<TokenStream, syn::token::Comma>::new();
for (system, args) in systems {
match args {
None => sys_set.push(quote!(#system)),
Some(args) => {
sys_set.push(args.transform_system(&syn::parse2(system.to_token_stream()).unwrap()))
sys_set.push(args.transform_system(&system.to_token_stream()));
}
}
}

let systems_strings: Vec<u8> = systems
.iter()
.map(|(ident, _)| ident.to_string().into_bytes())
.map(|(path, args)| [
path.into_token_stream().to_string().into_bytes(),
args.as_ref().map(|args| {
let argstr = format!("{:?}{:?}{:?}{:?}", set_args, args.plugin, args.schedule, args.generics);
argstr.into_bytes()
}).unwrap_or(format!("{set_args:?}").into_bytes())
])
.flatten()
.flatten()
.collect();
let hash = sha256::digest(systems_strings);
Expand All @@ -51,7 +59,7 @@ pub(crate) fn register_system_set_block(
tokens: quote! { #static_name, #butler_sys_struct },
};

let sys_transform = args.transform_system(&sys_set.to_token_stream());
let sys_transform = set_args.transform_system(&sys_set.to_token_stream());

quote! {
#[allow(non_camel_case_types)]
Expand All @@ -71,29 +79,67 @@ pub(crate) fn register_system_set_block(
}
}

fn parse_meta_args_and_check_for_generics(input: ParseStream) -> syn::Result<Punctuated<Meta, Token![,]>> {
// Like [Meta] but also checks for `generics = <...>` cause I'm dumb and want special syntax
let mut args = Punctuated::new();

while !input.is_empty() {
match input.fork().parse::<Meta>() {
Ok(_) => args.push(input.parse::<Meta>()?),
Err(e) => {
// Try to parse `generics = <...>`
let ident = input.parse::<Ident>();
if ident.map_err(|_| e.clone())? != "generics" {
return Err(e);
}
let _ = input.parse::<Token![=]>();
let _ = input.parse::<Token![<]>();
let mut generics: Punctuated<Type, Token![,]> = Punctuated::new();
while !input.peek(Token![>]) {
generics.push(input.parse()?);
if input.peek(Token![,]) {
input.parse::<Token![,]>().unwrap();
}
}
input.parse::<Token![>]>().unwrap();
args.push(Meta::List(MetaList {
path: syn::parse_quote!(generics),
delimiter: syn::MacroDelimiter::Paren(Default::default()),
tokens: generics.to_token_stream()
}));
}
}
if input.peek(Token![,]) {
input.parse::<Token![,]>().unwrap();
}
}

Ok(args)
}

impl Parse for SystemArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut args = Self {
schedule: None,
plugin: None,
generics: None,
transforms: Default::default(),
span: input.span(),
};

loop {
if input.is_empty() {
break;
}
let meta = parse_meta_args_and_check_for_generics(input)?;

match input.parse::<Meta>()? {
for meta in meta {

match meta {
Meta::Path(path) => {
// Just a function to call on the system, no args
let ident = path.get_ident();
if let Some(ident) = ident {
if ident == "schedule" || ident == "plugin" {
if ident == "schedule" || ident == "plugin" || ident == "generics" {
return Err(Error::new(
ident.span(),
&format!("Expected `{ident}` to be a name-value attribute"),
&format!("Expected `{ident}` to be a name-value or list attribute"),
));
}
}
Expand All @@ -108,53 +154,30 @@ impl Parse for SystemArgs {
));
}
Meta::List(list) => {
// A proper function call
// An attribute like `after(previous_system)`
let ident = list.path.get_ident();
if let Some(ident) = ident {
if ident == "schedule" || ident == "plugin" {
return Err(Error::new(
ident.span(),
&format!("Expected `{ident}` to be a name-value attribute"),
match ident {
Some(ident) if ident == "plugin" => { args.with_plugin(syn::parse2(list.tokens)?)?; }
Some(ident) if ident == "schedule" => { args.with_schedule(syn::parse2(list.tokens)?)?; }
Some(ident) if ident == "generics" => { args.with_generics(Punctuated::parse_terminated.parse2(list.tokens)?)?; }
Some(_) | None => {
args.transforms.push((
ExprPath {
attrs: vec![],
qself: None,
path: list.path,
},
Some(syn::Expr::Verbatim(list.tokens)),
));
}
}

let exprs = list.tokens;

args.transforms.push((
ExprPath {
attrs: vec![],
qself: None,
path: list.path,
},
Some(syn::Expr::Verbatim(exprs)),
));
}
Meta::NameValue(name_value) => {
// An attribute like `run_if = || true`
match name_value.path.get_ident() {
Some(ident) if ident == "schedule" => {
if args.schedule.is_some() {
return Err(Error::new(
ident.span(),
"`schedule` defined more than once",
));
} else {
args.schedule =
Some(syn::parse2(name_value.value.to_token_stream())?);
}
}
Some(ident) if ident == "plugin" => {
if args.plugin.is_some() {
return Err(Error::new(
ident.span(),
"`schedule` defined more than once",
));
} else {
args.plugin =
Some(syn::parse2(name_value.value.to_token_stream())?);
}
}
Some(ident) if ident == "plugin" => { args.with_plugin(syn::parse2(name_value.value.to_token_stream())?)?; }
Some(ident) if ident == "schedule" => { args.with_schedule(syn::parse2(name_value.value.to_token_stream())?)?; }
Some(ident) if ident == "generics" => { args.with_generics(Punctuated::parse_terminated.parse2(name_value.to_token_stream())?)?; }
Some(_) | None => {
args.transforms.push((
ExprPath {
Expand All @@ -168,11 +191,6 @@ impl Parse for SystemArgs {
}
}
}

if input.is_empty() {
break;
}
input.parse::<Token![,]>()?;
}

Ok(args)
Expand All @@ -186,14 +204,44 @@ impl SystemArgs {
Self {
plugin: new_args.plugin.clone().or(self.plugin.clone()),
schedule: new_args.schedule.clone().or(self.schedule.clone()),
generics: new_args.generics.clone(),
transforms: [self.transforms.clone(), new_args.transforms.clone()].concat(),
span: new_args.span,
}
}

pub fn with_plugin(&mut self, plugin: ExprPath) -> syn::Result<&mut Self> {
if self.plugin.is_some() {
return Err(Error::new_spanned(plugin, "Multiple declarations of \"plugin\""));
}
self.plugin = Some(plugin);
Ok(self)
}

pub fn with_schedule(&mut self, schedule: ExprPath) -> syn::Result<&mut Self> {
if self.schedule.is_some() {
return Err(Error::new_spanned(schedule, "Multiple declarations of \"schedule\""));
}
self.schedule = Some(schedule);
Ok(self)
}

pub fn with_generics(&mut self, generics: Punctuated<Type, Token![,]>) -> syn::Result<&mut Self> {
if !self.generics.is_none() {
return Err(Error::new_spanned(generics, "Multiple declarations of \"generics\""));
}
self.generics = Some(generics);
Ok(self)
}

pub fn transform_system(&self, system: &TokenStream) -> TokenStream {
let mut punct = Punctuated::<TokenStream, syn::token::Dot>::new();
punct.push(quote! { (#system) });
let mut punct = Punctuated::<TokenStream, Token![.]>::new();
if let Some(generics) = &self.generics {
punct.push(quote! { (#system::<#generics>) });
}
else {
punct.push(quote! { (#system) });
}

self.transforms
.iter()
Expand Down Expand Up @@ -327,9 +375,9 @@ pub(crate) fn free_standing_impl(
args.require_plugin()?;
args.require_schedule()?;

let sys_name = &item.sig.ident;
let sys_name: Path = syn::parse2(item.sig.ident.to_token_stream()).map_err(|e| e.to_compile_error())?;

let register_block = register_system_set_block(&vec![(sys_name.clone(), None)], &args);
let register_block = register_system_set_block(&[(sys_name, None)], &args);

Ok(quote! {
#item
Expand Down
9 changes: 4 additions & 5 deletions bevy-butler-proc-macro/src/system_set_impl.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use proc_macro2::TokenStream;
use quote::quote;
use quote::{quote, ToTokens};
use syn::{
parse::{Parse, ParseStream},
Error, Ident, Item, Stmt,
parse::{Parse, ParseStream}, Error, Ident, Item, Path, Stmt
};

use crate::{
Expand Down Expand Up @@ -32,7 +31,7 @@ impl Parse for SystemSetInput {
}

pub(crate) fn macro_impl(mut input: SystemSetInput) -> Result<TokenStream, TokenStream> {
let mut systems: Vec<(Ident, Option<SystemArgs>)> = Vec::new();
let mut systems: Vec<(Path, Option<SystemArgs>)> = Vec::new();

input.args.require_plugin()?;
input.args.require_schedule()?;
Expand All @@ -59,7 +58,7 @@ pub(crate) fn macro_impl(mut input: SystemSetInput) -> Result<TokenStream, Token
}
}

systems.push((item.sig.ident.clone(), sys_attr.args));
systems.push((syn::parse2(item.sig.ident.to_token_stream()).unwrap(), sys_attr.args));
}
}
_ => (),
Expand Down
4 changes: 4 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
#[cfg(not(feature="nightly"))]
println!("cargo:rustc-link-arg=-znostart-stop-gc");
}
12 changes: 7 additions & 5 deletions tests/butler_plugin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
use bevy::prelude::{Res, Resource};
use bevy_app::{App, Plugin, PostStartup, Startup};
use bevy_butler::*;
use bevy_log::{Level, LogPlugin};
use wasm_bindgen_test::wasm_bindgen_test;

mod utils;
use utils::log_plugin;

#[wasm_bindgen_test(unsupported = test)]
fn butler_plugin_struct() {
#[derive(Resource)]
Expand All @@ -16,7 +18,7 @@ fn butler_plugin_struct() {
struct MyPlugin;

App::new()
.add_plugins((LogPlugin {filter: "bevy_butler".to_string(), level: Level::TRACE, ..Default::default() }, MyPlugin))
.add_plugins((log_plugin(), MyPlugin))
.add_systems(Startup, |marker: Res<Marker>| assert_eq!(marker.0, 12))
.run();
}
Expand All @@ -36,7 +38,7 @@ fn butler_plugin_impl() {
}

let mut app = App::new();
app.add_plugins((LogPlugin {filter: "bevy_butler".to_string(), level: Level::TRACE, ..Default::default() }, MyPlugin));
app.add_plugins((log_plugin(), MyPlugin));
app.add_systems(PostStartup, |marker: Res<Marker>| {
assert_eq!(marker.0, "MyMarker");
});
Expand All @@ -62,7 +64,7 @@ fn butler_advanced_plugin_impl() {
}

let mut app = App::new();
app.add_plugins((LogPlugin {filter: "bevy_butler".to_string(), level: Level::TRACE, ..Default::default() }, MyPlugin));
app.add_plugins((log_plugin(), MyPlugin));
app.add_systems(
PostStartup,
|marker1: Res<MarkerOne>, marker2: Res<MarkerTwo>| {
Expand Down Expand Up @@ -93,7 +95,7 @@ fn butler_advanced_plugin_single_attr_impl() {
}

let mut app = App::new();
app.add_plugins((LogPlugin {filter: "bevy_butler".to_string(), level: Level::TRACE, ..Default::default() }, MyPlugin));
app.add_plugins((log_plugin(), MyPlugin));
app.add_systems(
PostStartup,
|marker1: Res<MarkerOne>, marker2: Res<MarkerTwo>| {
Expand Down
8 changes: 5 additions & 3 deletions tests/config_systems.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

use bevy_butler::*;
use bevy_ecs::system::Resource;
use bevy_log::{Level, LogPlugin};
use wasm_bindgen_test::wasm_bindgen_test;

mod utils;
use utils::*;

#[cfg(feature = "nightly")]
#[wasm_bindgen_test(unsupported = test)]
fn config_systems_block_attr() {
Expand Down Expand Up @@ -40,7 +42,7 @@ fn config_systems_block_attr() {
}

App::new()
.add_plugins((LogPlugin {filter: "bevy_butler".to_string(), level: Level::TRACE, ..Default::default() }, MyPlugin))
.add_plugins((log_plugin(), MyPlugin))
.add_systems(
PostStartup,
|marker: Res<Marker>| {
Expand Down Expand Up @@ -89,7 +91,7 @@ fn config_systems_function_macro() {
}

App::new()
.add_plugins((LogPlugin {filter: "bevy_butler".to_string(), level: Level::TRACE, ..Default::default() }, MyPlugin))
.add_plugins((log_plugin(), MyPlugin))
.add_systems(
PostStartup,
|marker: Res<Marker>| {
Expand Down
Loading

0 comments on commit b811f7d

Please sign in to comment.