Skip to main content

agave_event_system_derive/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use {
4    proc_macro::TokenStream,
5    proc_macro_crate::{FoundCrate, crate_name},
6    proc_macro2::Span,
7    quote::{format_ident, quote},
8    syn::{DeriveInput, LitStr, parse::Parser, parse_macro_input},
9};
10
11/// Implements `agave_event_system::Event` for a type.
12///
13/// Types with dynamically sized fields must provide `max_serialized_size`.
14/// The value must be greater than the lower bound reported by wincode's type
15/// metadata.
16///
17/// ```rust,ignore
18/// #[agave_event_system::event]
19/// #[derive(Debug, PartialEq, Eq)]
20/// enum SlotEvents {
21///     Completed { slot: u64 },
22///     Dropped { slot: u64 },
23/// }
24///
25/// #[agave_event_system::event(max_serialized_size = 1024)]
26/// struct Message {
27///     contents: String,
28/// }
29/// ```
30#[proc_macro_attribute]
31pub fn event(args: TokenStream, item: TokenStream) -> TokenStream {
32    event_impl(args.into(), parse_macro_input!(item as DeriveInput))
33        .unwrap_or_else(syn::Error::into_compile_error)
34        .into()
35}
36
37fn event_impl(
38    args: proc_macro2::TokenStream,
39    input: DeriveInput,
40) -> syn::Result<proc_macro2::TokenStream> {
41    if !input.generics.params.is_empty() {
42        return Err(syn::Error::new_spanned(
43            &input.generics,
44            "Event cannot be derived for generic types on stable Rust; implement it by hand \
45             instead (see `agave_event_system::Event::QueueCell`)",
46        ));
47    }
48
49    let EventArgs {
50        max_serialized_size,
51    } = event_args(args)?;
52    let ident = &input.ident;
53    let event_system_crate = event_system_crate()?;
54    let event_macro_path = quote!(#event_system_crate::__private::event_macro);
55    let event_macro_path = LitStr::new(&event_macro_path.to_string(), Span::call_site());
56    let max_serialized_size = match max_serialized_size {
57        Some(max_serialized_size) => quote!(::core::option::Option::Some(#max_serialized_size)),
58        None => quote!(::core::option::Option::None),
59    };
60
61    Ok(quote! {
62        #[derive(
63            #event_system_crate::wincode::SchemaRead,
64            #event_system_crate::wincode::SchemaWrite,
65            #event_system_crate::wincode_dynamic::SchemaDynamic,
66        )]
67        #[wincode(crate = #event_macro_path)]
68        #input
69
70        impl #event_system_crate::Event for #ident {
71            type QueueCell = [u8; #event_system_crate::event_queue_cell_size(
72                <Self as #event_system_crate::wincode_dynamic::SchemaDynamic>::SERIALIZED_SIZE,
73                #max_serialized_size,
74            )];
75        }
76    })
77}
78
79fn event_system_crate() -> syn::Result<proc_macro2::TokenStream> {
80    match crate_name("agave-event-system") {
81        Ok(FoundCrate::Itself) => Ok(quote!(::agave_event_system)),
82        Ok(FoundCrate::Name(name)) => {
83            let ident = format_ident!("{}", name.replace('-', "_"));
84            Ok(quote!(::#ident))
85        }
86        Err(error) => Err(syn::Error::new(Span::call_site(), error)),
87    }
88}
89
90struct EventArgs {
91    max_serialized_size: Option<usize>,
92}
93
94fn event_args(args: proc_macro2::TokenStream) -> syn::Result<EventArgs> {
95    let mut max_serialized_size = None;
96    let parser = syn::meta::parser(|meta| {
97        if meta.path.is_ident("max_serialized_size") {
98            if max_serialized_size.is_some() {
99                return Err(meta.error("duplicate `max_serialized_size`"));
100            }
101            let value = meta.value()?.parse::<syn::LitInt>()?;
102            max_serialized_size = Some(value.base10_parse()?);
103            Ok(())
104        } else {
105            Err(meta.error("unsupported event attribute"))
106        }
107    });
108    parser.parse2(args)?;
109    Ok(EventArgs {
110        max_serialized_size,
111    })
112}