diff --git a/crates/bevy_openxr/examples/3d_scene.rs b/crates/bevy_openxr/examples/3d_scene.rs index 6cb939d..fe150d9 100644 --- a/crates/bevy_openxr/examples/3d_scene.rs +++ b/crates/bevy_openxr/examples/3d_scene.rs @@ -7,25 +7,13 @@ use bevy_openxr::{ actions::{create_action_sets, ActionApp}, add_xr_plugins, resources::{TypedAction, XrActions, XrInstance}, }; -use bevy_xr::actions::{Action, ActionInfo, ActionState, ActionType}; +use bevy_xr::actions::{Action, ActionState}; use openxr::Binding; +#[derive(Action)] +#[action(action_type = bool, name = "jump")] pub struct Jump; -impl Action for Jump { - type ActionType = bool; - - fn info() -> ActionInfo { - ActionInfo { - pretty_name: "jump", - name: "jump", - action_type: ActionType::Bool, - type_id: TypeId::of::(), - } - } -} - - fn main() { App::new() .add_plugins(add_xr_plugins(DefaultPlugins)) diff --git a/crates/bevy_xr/Cargo.toml b/crates/bevy_xr/Cargo.toml index 9b586c6..d15550c 100644 --- a/crates/bevy_xr/Cargo.toml +++ b/crates/bevy_xr/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] bevy.workspace = true +bevy_xr_macros.path = "macros" \ No newline at end of file diff --git a/crates/bevy_xr/macros/Cargo.toml b/crates/bevy_xr/macros/Cargo.toml new file mode 100644 index 0000000..38074e9 --- /dev/null +++ b/crates/bevy_xr/macros/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bevy_xr_macros" +version = "0.1.0" +edition = "2021" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0" +syn = "2.0" diff --git a/crates/bevy_xr/macros/src/lib.rs b/crates/bevy_xr/macros/src/lib.rs new file mode 100644 index 0000000..a51475d --- /dev/null +++ b/crates/bevy_xr/macros/src/lib.rs @@ -0,0 +1,117 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::quote; +use syn::punctuated::Punctuated; +use syn::spanned::Spanned; +use syn::token::{Comma, Eq}; +use syn::{parse_macro_input, parse_quote, AttrStyle, DeriveInput, Expr, Type}; +use syn::parse::{Parse, ParseStream}; + +mod kw { + syn::custom_keyword!(action_type); + syn::custom_keyword!(name); + syn::custom_keyword!(pretty_name); +} + +enum AttributeInput { + Kind(Type, Span), + Name(Expr, Span), + PrettyName(Expr, Span), +} + +impl Parse for AttributeInput { + fn parse(input: ParseStream) -> syn::Result { + if let Ok(field) = input.parse::() { + input.parse::()?; + Ok(Self::Kind(input.parse()?, field.span())) + } else if let Ok(field) = input.parse::() { + input.parse::()?; + Ok(Self::Name(input.parse()?, field.span())) + } else if let Ok(field) = input.parse::() { + input.parse::()?; + Ok(Self::PrettyName(input.parse()?, field.span())) + } else { + Err(input.error("expected 'action_type', 'name' or 'pretty_name'")) + } + } +} + + +#[proc_macro_derive(Action, attributes(action))] +pub fn derive_action(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + let item = input.ident; + let mut attributes = vec![]; + for attribute in input.attrs { + if let AttrStyle::Inner(token) = attribute.style { + return TokenStream::from(syn::Error::new(token.span, "This derive macro does not accept inner attributes").to_compile_error()); + }; + let parsed_attributes = match attribute.parse_args_with(Punctuated::::parse_terminated) { + Ok(inner) => inner, + Err(e) => return TokenStream::from(e.to_compile_error()), + }; + for attr in parsed_attributes { + attributes.push(attr); + } + } + let mut kind: Option = None; + let mut name: Option = None; + let mut pretty_name: Option = None; + for attribute in attributes { + match attribute { + AttributeInput::Kind(ty, span) => { + if kind.is_some() { + return syn::Error::new(span, "attribute 'action_type' is defined more than once").to_compile_error().into(); + } + + kind = Some(ty); + }, + AttributeInput::Name(expr, span) => { + if name.is_some() { + return syn::Error::new(span, "attribute 'name' is defined more than once").to_compile_error().into(); + } + + name = Some(expr); + }, + AttributeInput::PrettyName(expr, span) => { + if pretty_name.is_some() { + return syn::Error::new(span, "attribute 'pretty_name' is defined more than once").to_compile_error().into(); + } + + pretty_name = Some(expr); + }, + } + }; + + if kind.is_none() { + panic!("action_type isn't specified") + } + if name.is_none() { + name = Some(parse_quote! { + std::stringify!(#item) + }); + } + if pretty_name.is_none() { + pretty_name = name.clone(); + } + let kind = kind.unwrap(); + let name = name.unwrap(); + let pretty_name = pretty_name.unwrap(); + + let expanded = quote! { + impl bevy_xr::actions::Action for #item { + type ActionType = #kind; + + fn info() -> bevy_xr::actions::ActionInfo { + bevy_xr::actions::ActionInfo { + pretty_name: #pretty_name, + name: #name, + action_type: ::TYPE, + type_id: std::any::TypeId::of::(), + } + } + } + }; + + TokenStream::from(expanded) +} \ No newline at end of file diff --git a/crates/bevy_xr/src/actions.rs b/crates/bevy_xr/src/actions.rs index bde0def..cb256cb 100644 --- a/crates/bevy_xr/src/actions.rs +++ b/crates/bevy_xr/src/actions.rs @@ -3,6 +3,7 @@ use std::{any::TypeId, marker::PhantomData}; use bevy::app::{App, Plugin}; use bevy::ecs::system::Resource; use bevy::math::Vec2; +pub use bevy_xr_macros::Action; pub struct ActionPlugin(PhantomData);