Skip to content

derive Joined to implement JoinedValue and BufferMapLayout #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 22 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1137c08
derive joined
koonpeng Feb 5, 2025
9bc7cfe
remove use of unwrap
koonpeng Feb 5, 2025
83d6595
use full path
koonpeng Feb 5, 2025
5dbfa63
cleanup
koonpeng Feb 5, 2025
68681e4
derive BufferKeyMap
koonpeng Feb 6, 2025
1deada0
comments
koonpeng Feb 6, 2025
b612a05
Merge remote-tracking branch 'origin/buffer_map' into koonpeng/derive…
koonpeng Feb 11, 2025
af2caf7
support generics
koonpeng Feb 12, 2025
cff7138
add `select_buffers` to avoid the need to directly reference generate…
koonpeng Feb 12, 2025
bb88c8d
remove support for customizing buffers, select_buffers allows any buf…
koonpeng Feb 12, 2025
957f17d
revert changes to AnyBuffer
koonpeng Feb 12, 2025
dde0e6d
add helper attribute to customized generated buffer struct ident
koonpeng Feb 13, 2025
01af23f
remove builder argument in select_buffers; add test for select_buffer…
koonpeng Feb 13, 2025
25ec4f2
wip buffer_type helper but without auto downcasting, doesnt work due …
koonpeng Feb 13, 2025
943ea33
check for any
koonpeng Feb 13, 2025
37ee9b9
allow buffer_downcast to downcast back to the original Buffer<T>
koonpeng Feb 14, 2025
a00f09f
move test_select_buffers_json
koonpeng Feb 14, 2025
e61c3ff
put unused code into its own mod; to_phantom_data uses fn(...) to all…
koonpeng Feb 14, 2025
5299c25
rename helper attributes
koonpeng Feb 17, 2025
81687f9
Merge remote-tracking branch 'origin/buffer_map' into koonpeng/derive…
koonpeng Feb 17, 2025
b110e87
Test for generics in buffers, and fix Clone/Copy semantics
mxgrey Feb 17, 2025
31b3927
Fix style
mxgrey Feb 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ proc-macro = true
[dependencies]
syn = "2.0"
quote = "1.0"
proc-macro2 = "1.0.93"
175 changes: 175 additions & 0 deletions macros/src/buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_quote, Ident, ItemStruct, Type};

use crate::Result;

pub(crate) fn impl_joined_value(input_struct: &ItemStruct) -> Result<TokenStream> {
let struct_ident = &input_struct.ident;
let (impl_generics, ty_generics, where_clause) = input_struct.generics.split_for_impl();
let (field_ident, field_type) = get_fields_map(&input_struct.fields)?;
let BufferStructConfig {
struct_name: buffer_struct_ident,
} = BufferStructConfig::from_data_struct(&input_struct);
let buffer_struct_vis = &input_struct.vis;

let buffer_struct: ItemStruct = parse_quote! {
#[derive(Clone)]
#[allow(non_camel_case_types)]
#buffer_struct_vis struct #buffer_struct_ident #impl_generics #where_clause {
#(
#buffer_struct_vis #field_ident: ::bevy_impulse::Buffer<#field_type>,
)*
}
};

let impl_buffer_map_layout = impl_buffer_map_layout(&buffer_struct, &input_struct)?;
let impl_joined = impl_joined(&buffer_struct, &input_struct)?;

let gen = quote! {
impl #impl_generics ::bevy_impulse::JoinedValue for #struct_ident #ty_generics #where_clause {
type Buffers = #buffer_struct_ident #ty_generics;
}

#buffer_struct

impl #impl_generics #struct_ident #ty_generics #where_clause {
fn select_buffers(
builder: &mut ::bevy_impulse::Builder,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather not have builder as an argument. I think we should leave it to users to call output.into_buffer(builder) from outside of select_buffers and have select_buffers just take in the specific buffer types that are expected. This will especially matter for supporting specialized buffer types like AnyBuffer and JsonBuffer.

#(
#field_ident: impl ::bevy_impulse::Bufferable<BufferType = ::bevy_impulse::Buffer<#field_type>>,
)*
) -> #buffer_struct_ident #ty_generics {
#buffer_struct_ident {
#(
#field_ident: #field_ident.into_buffer(builder),
)*
}
}
}

#impl_buffer_map_layout

#impl_joined
};

Ok(gen.into())
}

struct BufferStructConfig {
struct_name: Ident,
}

impl BufferStructConfig {
fn from_data_struct(data_struct: &ItemStruct) -> Self {
let mut config = Self {
struct_name: format_ident!("__bevy_impulse_{}_Buffers", data_struct.ident),
};

let attr = data_struct
.attrs
.iter()
.find(|attr| attr.path().is_ident("buffers"));

if let Some(attr) = attr {
attr.parse_nested_meta(|meta| {
if meta.path.is_ident("struct_name") {
config.struct_name = meta.value()?.parse()?;
}
Ok(())
})
// panic if attribute is malformed, this will result in a compile error which is intended.
.unwrap();
}

config
}
}

fn get_fields_map(fields: &syn::Fields) -> Result<(Vec<&Ident>, Vec<&Type>)> {
match fields {
syn::Fields::Named(data) => {
let mut idents = Vec::new();
let mut types = Vec::new();
for field in &data.named {
let ident = field
.ident
.as_ref()
.ok_or("expected named fields".to_string())?;
idents.push(ident);
types.push(&field.ty);
}
Ok((idents, types))
}
_ => return Err("expected named fields".to_string()),
}
}

/// Params:
/// buffer_struct: The struct to implement `BufferMapLayout`.
/// item_struct: The struct which `buffer_struct` is derived from.
fn impl_buffer_map_layout(
buffer_struct: &ItemStruct,
item_struct: &ItemStruct,
) -> Result<proc_macro2::TokenStream> {
let struct_ident = &buffer_struct.ident;
let (impl_generics, ty_generics, where_clause) = buffer_struct.generics.split_for_impl();
let (field_ident, field_type) = get_fields_map(&item_struct.fields)?;
let map_key: Vec<String> = field_ident.iter().map(|v| v.to_string()).collect();

Ok(quote! {
impl #impl_generics ::bevy_impulse::BufferMapLayout for #struct_ident #ty_generics #where_clause {
fn buffer_list(&self) -> ::smallvec::SmallVec<[AnyBuffer; 8]> {
use smallvec::smallvec;
smallvec![#(
self.#field_ident.as_any_buffer(),
)*]
}

fn try_from_buffer_map(buffers: &::bevy_impulse::BufferMap) -> Result<Self, ::bevy_impulse::IncompatibleLayout> {
let mut compatibility = ::bevy_impulse::IncompatibleLayout::default();
#(
let #field_ident = if let Ok(buffer) = compatibility.require_message_type::<#field_type>(#map_key, buffers) {
buffer
} else {
return Err(compatibility);
};
)*

Ok(Self {#(
#field_ident,
)*})
}
}
}
.into())
}

/// Params:
/// joined_struct: The struct to implement `Joined`.
/// item_struct: The associated `Item` type to use for the `Joined` implementation.
fn impl_joined(
joined_struct: &ItemStruct,
item_struct: &ItemStruct,
) -> Result<proc_macro2::TokenStream> {
let struct_ident = &joined_struct.ident;
let item_struct_ident = &item_struct.ident;
let (impl_generics, ty_generics, where_clause) = item_struct.generics.split_for_impl();
let (field_ident, _) = get_fields_map(&item_struct.fields)?;

Ok(quote! {
impl #impl_generics ::bevy_impulse::Joined for #struct_ident #ty_generics #where_clause {
type Item = #item_struct_ident #ty_generics;

fn pull(&self, session: ::bevy_ecs::prelude::Entity, world: &mut ::bevy_ecs::prelude::World) -> Result<Self::Item, ::bevy_impulse::OperationError> {
#(
let #field_ident = self.#field_ident.pull(session, world)?;
)*

Ok(Self::Item {#(
#field_ident,
)*})
}
}
}.into())
}
20 changes: 19 additions & 1 deletion macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
*
*/

mod buffer;
use buffer::impl_joined_value;

use proc_macro::TokenStream;
use quote::quote;
use syn::DeriveInput;
use syn::{parse_macro_input, DeriveInput, ItemStruct};

#[proc_macro_derive(Stream)]
pub fn simple_stream_macro(item: TokenStream) -> TokenStream {
Expand Down Expand Up @@ -58,3 +61,18 @@ pub fn delivery_label_macro(item: TokenStream) -> TokenStream {
}
.into()
}

/// The result error is the compiler error message to be displayed.
type Result<T> = std::result::Result<T, String>;

#[proc_macro_derive(JoinedValue, attributes(buffers))]
pub fn derive_joined_value(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemStruct);
match impl_joined_value(&input) {
Ok(tokens) => tokens.into(),
Err(msg) => quote! {
compile_error!(#msg);
}
.into(),
}
}
Loading
Loading