1use proc_macro::TokenStream;
11use proc_macro2::Span;
12use proc_macro_crate::{crate_name, FoundCrate};
13use quote::{format_ident, quote, ToTokens};
14use std::collections::HashMap;
15use std::mem;
16use syn::{
17 braced,
18 parse::{Parse, ParseStream},
19 parse_macro_input,
20 punctuated::Punctuated,
21 spanned::Spanned,
22 Attribute, Block, Data, DeriveInput, Fields, FnArg, Ident, ImplItem, ImplItemFn, ItemFn,
23 ItemImpl, Lit, LitStr, Meta, Pat, PatIdent, PatType, Path, Result, Signature, Stmt, Token,
24 Type, Visibility,
25};
26
27fn get_crate_name() -> proc_macro2::TokenStream {
30 match crate_name("issun") {
31 Ok(FoundCrate::Itself) => quote!(crate),
32 Ok(FoundCrate::Name(name)) => {
33 let ident = format_ident!("{}", name);
34 quote!(::#ident)
35 }
36 Err(_) => quote!(::issun),
37 }
38}
39
40#[proc_macro_derive(Scene, attributes(scene))]
57pub fn derive_scene(input: TokenStream) -> TokenStream {
58 let input = parse_macro_input!(input as DeriveInput);
59
60 let scene_name = &input.ident;
61
62 let scene_attrs = parse_scene_attributes(&input.attrs);
64
65 let crate_name = get_crate_name();
66
67 let scene_impl = quote! {
69 #[::async_trait::async_trait]
70 impl #crate_name::scene::Scene for #scene_name {
71 async fn on_enter(
72 &mut self,
73 _services: &#crate_name::context::ServiceContext,
74 _systems: &mut #crate_name::context::SystemContext,
75 _resources: &mut #crate_name::context::ResourceContext,
76 ) {
77 }
79
80 async fn on_update(
81 &mut self,
82 _services: &#crate_name::context::ServiceContext,
83 _systems: &mut #crate_name::context::SystemContext,
84 _resources: &mut #crate_name::context::ResourceContext,
85 ) -> #crate_name::scene::SceneTransition<Self> {
86 #crate_name::scene::SceneTransition::Stay
88 }
89
90 async fn on_exit(
91 &mut self,
92 _services: &#crate_name::context::ServiceContext,
93 _systems: &mut #crate_name::context::SystemContext,
94 _resources: &mut #crate_name::context::ResourceContext,
95 ) {
96 }
98
99 async fn on_suspend(
100 &mut self,
101 _services: &#crate_name::context::ServiceContext,
102 _systems: &mut #crate_name::context::SystemContext,
103 _resources: &mut #crate_name::context::ResourceContext,
104 ) {
105 }
106
107 async fn on_resume(
108 &mut self,
109 _services: &#crate_name::context::ServiceContext,
110 _systems: &mut #crate_name::context::SystemContext,
111 _resources: &mut #crate_name::context::ResourceContext,
112 ) {
113 }
114 }
115 };
116
117 let game_state_gen =
119 if let (Some(context), Some(initial)) = (&scene_attrs.context, &scene_attrs.initial) {
120 let state_name = scene_attrs
121 .name
122 .as_ref()
123 .map(|n| format_ident!("{}", n))
124 .unwrap_or_else(|| format_ident!("GameState"));
125
126 let context_ident = format_ident!("{}", context);
127 let initial_expr: proc_macro2::TokenStream = initial.parse().unwrap();
128
129 quote! {
130 #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
132 pub struct #state_name {
133 pub scene: #scene_name,
134 pub ctx: #context_ident,
135 pub should_quit: bool,
136 }
137
138 impl #state_name {
139 pub fn new() -> Self {
140 Self {
141 scene: #scene_name::#initial_expr,
142 ctx: #context_ident::new(),
143 should_quit: false,
144 }
145 }
146 }
147
148 impl Default for #state_name {
149 fn default() -> Self {
150 Self::new()
151 }
152 }
153 }
154 } else {
155 quote! {}
156 };
157
158 let handler_gen = if let Some(params) = &scene_attrs.handler_params {
160 let handler_name = scene_attrs
161 .handler
162 .as_ref()
163 .map(|h| format_ident!("{}", h))
164 .unwrap_or_else(|| format_ident!("handle_input"));
165
166 let params_tokens: proc_macro2::TokenStream = params.parse().unwrap();
167
168 let param_names = extract_param_names(params);
170
171 let _return_type = scene_attrs
173 .handler_return
174 .as_ref()
175 .map(|r| r.parse::<proc_macro2::TokenStream>().unwrap())
176 .unwrap_or_else(|| {
177 quote! { (#scene_name, ::issun::scene::SceneTransition<#scene_name>) }
178 });
179
180 let variants = if let Data::Enum(data_enum) = &input.data {
182 &data_enum.variants
183 } else {
184 panic!("Scene derive macro only works on enums");
185 };
186
187 let match_arms = variants.iter().map(|variant| {
189 let variant_name = &variant.ident;
190 quote! {
191 #scene_name::#variant_name(data) => data.#handler_name(
192 services,
193 systems,
194 resources,
195 #(#param_names),*
196 ).await
197 }
198 });
199
200 quote! {
201 pub async fn handle_scene_input(
205 scene: &mut #scene_name,
206 services: &#crate_name::context::ServiceContext,
207 systems: &mut #crate_name::context::SystemContext,
208 resources: &mut #crate_name::context::ResourceContext,
209 #params_tokens,
210 ) -> ::issun::scene::SceneTransition<#scene_name> {
211 match scene {
212 #(#match_arms),*
213 }
214 }
215 }
216 } else {
217 quote! {}
218 };
219
220 let expanded = quote! {
221 #scene_impl
222 #game_state_gen
223 #handler_gen
224 };
225
226 TokenStream::from(expanded)
227}
228
229struct SceneAttributes {
231 context: Option<String>,
232 initial: Option<String>,
233 name: Option<String>,
234 handler: Option<String>,
235 handler_params: Option<String>,
236 handler_return: Option<String>,
237}
238
239fn parse_scene_attributes(attrs: &[syn::Attribute]) -> SceneAttributes {
240 let mut context = None;
241 let mut initial = None;
242 let mut name = None;
243 let mut handler = None;
244 let mut handler_params = None;
245 let mut handler_return = None;
246
247 for attr in attrs {
248 if attr.path().is_ident("scene") {
249 let _ = attr.parse_nested_meta(|meta| {
250 if meta.path.is_ident("context") {
251 if let Ok(value) = meta.value() {
252 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
253 context = Some(s.value());
254 }
255 }
256 } else if meta.path.is_ident("initial") {
257 if let Ok(value) = meta.value() {
258 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
259 initial = Some(s.value());
260 }
261 }
262 } else if meta.path.is_ident("name") {
263 if let Ok(value) = meta.value() {
264 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
265 name = Some(s.value());
266 }
267 }
268 } else if meta.path.is_ident("handler") {
269 if let Ok(value) = meta.value() {
270 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
271 handler = Some(s.value());
272 }
273 }
274 } else if meta.path.is_ident("handler_params") {
275 if let Ok(value) = meta.value() {
276 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
277 handler_params = Some(s.value());
278 }
279 }
280 } else if meta.path.is_ident("handler_return") {
281 if let Ok(value) = meta.value() {
282 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
283 handler_return = Some(s.value());
284 }
285 }
286 }
287 Ok(())
288 });
289 }
290 }
291
292 SceneAttributes {
293 context,
294 initial,
295 name,
296 handler,
297 handler_params,
298 handler_return,
299 }
300}
301
302fn extract_param_names(params_str: &str) -> Vec<proc_macro2::Ident> {
305 params_str
306 .split(',')
307 .filter_map(|param| {
308 let param = param.trim();
309 param.split(':').next().map(|name| {
311 let name = name.trim();
312 format_ident!("{}", name)
313 })
314 })
315 .collect()
316}
317
318#[proc_macro_derive(Entity, attributes(entity))]
330pub fn derive_entity(input: TokenStream) -> TokenStream {
331 let input = parse_macro_input!(input as DeriveInput);
332
333 let name = &input.ident;
334
335 let id_field = match &input.data {
337 Data::Struct(data) => {
338 match &data.fields {
339 Fields::Named(fields) => {
340 fields
342 .named
343 .iter()
344 .find(|field| {
345 field
346 .attrs
347 .iter()
348 .any(|attr| attr.path().is_ident("entity"))
349 })
350 .map(|field| field.ident.as_ref().unwrap())
351 }
352 _ => None,
353 }
354 }
355 _ => None,
356 };
357
358 let id_impl = if let Some(field_name) = id_field {
359 quote! {
360 fn id(&self) -> &str {
361 &self.#field_name
362 }
363 }
364 } else {
365 quote! {
367 fn id(&self) -> &str {
368 ""
369 }
370 }
371 };
372
373 let expanded = quote! {
374 #[async_trait::async_trait]
375 impl Entity for #name {
376 #id_impl
377
378 async fn update(&mut self, _ctx: &mut Context) {
379 }
381 }
382 };
383
384 TokenStream::from(expanded)
385}
386
387#[proc_macro_derive(Service, attributes(service))]
410pub fn derive_service(input: TokenStream) -> TokenStream {
411 let input = parse_macro_input!(input as DeriveInput);
412
413 let struct_name = &input.ident;
414
415 let service_name = parse_service_name(&input.attrs);
417 let service_name_lit = syn::LitStr::new(&service_name, proc_macro2::Span::call_site());
418
419 let crate_name = get_crate_name();
420
421 let expanded = quote! {
422 impl #struct_name {
423 pub const NAME: &'static str = #service_name_lit;
424 }
425
426 #[::async_trait::async_trait]
427 impl #crate_name::service::Service for #struct_name {
428 fn name(&self) -> &'static str {
429 #service_name
430 }
431
432 fn clone_box(&self) -> Box<dyn #crate_name::service::Service> {
433 Box::new(self.clone())
434 }
435
436 fn as_any(&self) -> &dyn ::std::any::Any {
437 self
438 }
439
440 fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
441 self
442 }
443 }
444 };
445
446 TokenStream::from(expanded)
447}
448
449fn parse_service_name(attrs: &[syn::Attribute]) -> String {
451 for attr in attrs {
452 if attr.path().is_ident("service") {
453 let mut name = None;
454 let _ = attr.parse_nested_meta(|meta| {
455 if meta.path.is_ident("name") {
456 if let Ok(value) = meta.value() {
457 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
458 name = Some(s.value());
459 }
460 }
461 }
462 Ok(())
463 });
464 if let Some(n) = name {
465 return n;
466 }
467 }
468 }
469
470 "unknown_service".to_string()
472}
473
474#[proc_macro_derive(System, attributes(system))]
498pub fn derive_system(input: TokenStream) -> TokenStream {
499 let input = parse_macro_input!(input as DeriveInput);
500
501 let struct_name = &input.ident;
502
503 let system_name = parse_system_name(&input.attrs);
505 let system_name_lit = syn::LitStr::new(&system_name, proc_macro2::Span::call_site());
506
507 let crate_name = get_crate_name();
508
509 let expanded = quote! {
510 impl #struct_name {
511 pub const NAME: &'static str = #system_name_lit;
512 }
513
514 #[::async_trait::async_trait]
515 impl #crate_name::system::System for #struct_name {
516 fn name(&self) -> &'static str {
517 #system_name
518 }
519
520 fn as_any(&self) -> &dyn ::std::any::Any {
521 self
522 }
523
524 fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
525 self
526 }
527 }
528 };
529
530 TokenStream::from(expanded)
531}
532
533fn parse_system_name(attrs: &[syn::Attribute]) -> String {
535 for attr in attrs {
536 if attr.path().is_ident("system") {
537 let mut name = None;
538 let _ = attr.parse_nested_meta(|meta| {
539 if meta.path.is_ident("name") {
540 if let Ok(value) = meta.value() {
541 if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
542 name = Some(s.value());
543 }
544 }
545 }
546 Ok(())
547 });
548 if let Some(n) = name {
549 return n;
550 }
551 }
552 }
553
554 "unknown_system".to_string()
556}
557
558#[proc_macro_derive(Asset)]
569pub fn derive_asset(input: TokenStream) -> TokenStream {
570 let input = parse_macro_input!(input as DeriveInput);
571
572 let name = &input.ident;
573
574 let expanded = quote! {
575 impl Asset for #name {
576 }
578 };
579
580 TokenStream::from(expanded)
581}
582
583#[proc_macro_derive(Resource)]
604pub fn derive_resource(input: TokenStream) -> TokenStream {
605 let input = parse_macro_input!(input as DeriveInput);
606
607 let name = &input.ident;
608 let crate_name = get_crate_name();
609
610 let expanded = quote! {
611 impl #crate_name::resources::Resource for #name {
612 }
614 };
615
616 TokenStream::from(expanded)
617}
618
619#[proc_macro_derive(Plugin, attributes(plugin, resource, state, system, service))]
631pub fn derive_plugin(input: TokenStream) -> TokenStream {
632 let input = parse_macro_input!(input as DeriveInput);
633 let name = &input.ident;
634 let crate_name = get_crate_name();
635
636 let mut plugin_name = None;
638 let mut services = Vec::new();
639 let mut systems = Vec::new();
640 let mut states = Vec::new();
641 let mut resources = Vec::new();
642
643 for attr in &input.attrs {
644 if !attr.path().is_ident("plugin") {
645 continue;
646 }
647
648 let result: Result<()> = attr.parse_nested_meta(|meta| {
649 if meta.path.is_ident("name") {
650 let value = meta.value()?;
651 let lit: LitStr = value.parse()?;
652 plugin_name = Some(lit.value());
653 Ok(())
654 } else if meta.path.is_ident("service") {
655 let value = meta.value()?;
656 let ty: Type = value.parse()?;
657 services.push(ty);
658 Ok(())
659 } else if meta.path.is_ident("system") {
660 let value = meta.value()?;
661 let ty: Type = value.parse()?;
662 systems.push(ty);
663 Ok(())
664 } else if meta.path.is_ident("state") {
665 let value = meta.value()?;
666 let ty: Type = value.parse()?;
667 states.push(ty);
668 Ok(())
669 } else if meta.path.is_ident("resource") {
670 let value = meta.value()?;
671 let ty: Type = value.parse()?;
672 resources.push(ty);
673 Ok(())
674 } else {
675 Err(meta.error("expected `name`, `service`, `system`, `state`, or `resource`"))
676 }
677 });
678
679 if let Err(err) = result {
680 return err.to_compile_error().into();
681 }
682 }
683
684 let plugin_name = plugin_name.unwrap_or_else(|| {
685 let name_str = name.to_string();
687 name_str
688 .trim_end_matches("Plugin")
689 .chars()
690 .enumerate()
691 .flat_map(|(i, c)| {
692 if i > 0 && c.is_uppercase() {
693 vec!['_', c.to_ascii_lowercase()]
694 } else {
695 vec![c.to_ascii_lowercase()]
696 }
697 })
698 .collect::<String>()
699 });
700
701 let attr_service_registrations = services.iter().map(|ty| {
703 quote! {
704 builder.register_service(Box::new(#ty::default()));
705 }
706 });
707
708 let attr_system_registrations = systems.iter().map(|ty| {
709 quote! {
710 builder.register_system(Box::new(#ty::default()));
711 }
712 });
713
714 let attr_state_registrations = states.iter().map(|ty| {
715 quote! {
716 builder.register_runtime_state(#ty::default());
717 }
718 });
719
720 let attr_resource_registrations = resources.iter().map(|ty| {
721 quote! {
722 builder.register_resource(#ty::default());
723 }
724 });
725
726 let mut field_registrations = Vec::new();
728
729 if let Data::Struct(data) = &input.data {
730 for field in &data.fields {
731 let field_name = &field.ident;
732
733 if field_name.is_none() {
736 continue;
737 }
738 let field_access = quote! { self.#field_name };
739
740 for attr in &field.attrs {
741 if attr.path().is_ident("plugin") {
743 let result: Result<()> = attr.parse_nested_meta(|meta| {
745 if meta.path.is_ident("resource") {
746 field_registrations.push(quote! {
747 builder.register_resource(#field_access.clone());
748 });
749 } else if meta.path.is_ident("state") || meta.path.is_ident("runtime_state") {
750 field_registrations.push(quote! {
751 builder.register_runtime_state(#field_access.clone());
752 });
753 } else if meta.path.is_ident("system") {
754 field_registrations.push(quote! {
755 builder.register_system(Box::new(#field_access.clone()));
756 });
757 } else if meta.path.is_ident("service") {
758 field_registrations.push(quote! {
759 builder.register_service(Box::new(#field_access.clone()));
760 });
761 } else if meta.path.is_ident("skip") {
762 } else {
764 return Err(meta.error("expected `resource`, `state`, `runtime_state`, `system`, `service`, or `skip`"));
765 }
766 Ok(())
767 });
768
769 if let Err(err) = result {
770 return err.to_compile_error().into();
771 }
772 } else if attr.path().is_ident("resource") {
773 field_registrations.push(quote! {
775 builder.register_resource(#field_access.clone());
776 });
777 } else if attr.path().is_ident("state") || attr.path().is_ident("runtime_state") {
778 field_registrations.push(quote! {
780 builder.register_runtime_state(#field_access.clone());
781 });
782 } else if attr.path().is_ident("system") {
783 field_registrations.push(quote! {
785 builder.register_system(Box::new(#field_access.clone()));
786 });
787 } else if attr.path().is_ident("service") {
788 field_registrations.push(quote! {
790 builder.register_service(Box::new(#field_access.clone()));
791 });
792 }
793 }
794 }
795 }
796
797 let expanded = quote! {
798 #[::async_trait::async_trait]
799 impl #crate_name::plugin::Plugin for #name {
800 fn name(&self) -> &'static str {
801 #plugin_name
802 }
803
804 fn build(&self, builder: &mut dyn #crate_name::plugin::PluginBuilder) {
805 use #crate_name::plugin::PluginBuilderExt;
806
807 #(#attr_service_registrations)*
809 #(#attr_system_registrations)*
810 #(#attr_state_registrations)*
811 #(#attr_resource_registrations)*
812
813 #(#field_registrations)*
815 }
816 }
817 };
818
819 TokenStream::from(expanded)
820}
821
822#[proc_macro_attribute]
824pub fn event_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
825 let args = parse_macro_input!(attr as EventHandlerArgs);
826 let mut item_impl = parse_macro_input!(item as ItemImpl);
827
828 if item_impl.trait_.is_some() {
829 return syn::Error::new(
830 item_impl.impl_token.span(),
831 "#[event_handler] can only be used on inherent impl blocks",
832 )
833 .to_compile_error()
834 .into();
835 }
836
837 let crate_name = get_crate_name();
838 let mut context = match EventHandlerContext::new(args) {
839 Ok(ctx) => ctx,
840 Err(err) => return err.to_compile_error().into(),
841 };
842
843 for impl_item in &mut item_impl.items {
844 if let ImplItem::Fn(method) = impl_item {
845 let mut subscribe_attr = None;
846 method.attrs.retain(|attr| {
847 if attr.path().is_ident("subscribe") {
848 subscribe_attr = Some(attr.clone());
849 false
850 } else {
851 true
852 }
853 });
854
855 if let Some(attr) = subscribe_attr {
856 if let Err(err) = context.register_handler(method, attr) {
857 return err.to_compile_error().into();
858 }
859 }
860 }
861 }
862
863 if context.handlers.is_empty() {
864 return syn::Error::new(
865 item_impl.impl_token.span(),
866 "#[event_handler] requires at least one #[subscribe] method",
867 )
868 .to_compile_error()
869 .into();
870 }
871
872 match context.generate_process_fn(&crate_name) {
873 Ok(process_fn) => {
874 item_impl.items.push(ImplItem::Fn(process_fn));
875 TokenStream::from(quote! { #item_impl })
876 }
877 Err(err) => err.to_compile_error().into(),
878 }
879}
880
881#[derive(Default)]
882struct EventHandlerArgs {
883 default_state: Option<DefaultState>,
884}
885
886struct DefaultState {
887 ty: Type,
888 repr: String,
889}
890
891impl Parse for EventHandlerArgs {
892 fn parse(input: ParseStream) -> Result<Self> {
893 let mut args = EventHandlerArgs::default();
894
895 while !input.is_empty() {
896 let ident: Ident = input.parse()?;
897 input.parse::<Token![=]>()?;
898
899 match ident.to_string().as_str() {
900 "state" | "default_state" => {
901 let ty = parse_type_value(input)?;
902 let repr = type_to_key(&ty);
903 args.default_state = Some(DefaultState { ty, repr });
904 }
905 "system" => {
906 let _ = parse_type_value(input)?;
908 }
909 other => {
910 return Err(syn::Error::new(
911 ident.span(),
912 format!("Unknown event_handler attribute key `{}`", other),
913 ));
914 }
915 }
916
917 if input.peek(Token![,]) {
918 input.parse::<Token![,]>()?;
919 }
920 }
921
922 Ok(args)
923 }
924}
925
926struct EventHandlerContext {
927 default_state: Option<DefaultState>,
928 events: Vec<EventCollection>,
929 event_lookup: HashMap<String, usize>,
930 handlers: Vec<Handler>,
931 uses_services: bool,
932}
933
934impl EventHandlerContext {
935 fn new(args: EventHandlerArgs) -> Result<Self> {
936 Ok(Self {
937 default_state: args.default_state,
938 events: Vec::new(),
939 event_lookup: HashMap::new(),
940 handlers: Vec::new(),
941 uses_services: false,
942 })
943 }
944
945 fn register_handler(
946 &mut self,
947 method: &mut ImplItemFn,
948 subscribe_attr: Attribute,
949 ) -> Result<()> {
950 if method.sig.asyncness.is_none() {
951 return Err(syn::Error::new(
952 method.sig.fn_token.span(),
953 "#[subscribe] handlers must be async",
954 ));
955 }
956
957 if method.sig.inputs.is_empty() {
958 return Err(syn::Error::new(
959 method.sig.fn_token.span(),
960 "Event handler must accept &mut self",
961 ));
962 }
963
964 let mut inputs_iter = method.sig.inputs.iter_mut();
965
966 match inputs_iter.next() {
967 Some(FnArg::Receiver(receiver)) => {
968 if receiver.mutability.is_none() {
969 return Err(syn::Error::new(
970 receiver.self_token.span(),
971 "event handlers must take `&mut self`",
972 ));
973 }
974 }
975 _ => {
976 return Err(syn::Error::new(
977 method.sig.fn_token.span(),
978 "event handlers must start with `&mut self`",
979 ));
980 }
981 }
982
983 let subscribe = parse_subscribe_attr(subscribe_attr)?;
984 let event_index = self.register_event(&subscribe.event_type);
985
986 let event_input = inputs_iter.next().ok_or_else(|| {
987 syn::Error::new(
988 method.sig.fn_token.span(),
989 "event handler must accept event parameter",
990 )
991 })?;
992
993 let event_ty = match event_input {
994 FnArg::Typed(pat_type) => match pat_type.ty.as_ref() {
995 Type::Reference(reference) => {
996 if reference.mutability.is_some() {
997 return Err(syn::Error::new(
998 reference.and_token.span(),
999 "event parameter must be `&EventType`",
1000 ));
1001 }
1002 reference.elem.as_ref().clone()
1003 }
1004 _ => {
1005 return Err(syn::Error::new(
1006 pat_type.ty.span(),
1007 "event parameter must be a reference",
1008 ))
1009 }
1010 },
1011 _ => {
1012 return Err(syn::Error::new(
1013 method.sig.fn_token.span(),
1014 "event parameter must be named",
1015 ))
1016 }
1017 };
1018
1019 let requested_event = subscribe.event_type.to_token_stream().to_string();
1020 let actual_event = event_ty.to_token_stream().to_string();
1021 if requested_event != actual_event {
1022 return Err(syn::Error::new(
1023 method.sig.ident.span(),
1024 "event parameter type must match #[subscribe(...)]",
1025 ));
1026 }
1027
1028 let mut args = Vec::new();
1029 for input in inputs_iter {
1030 let pat_type = match input {
1031 FnArg::Typed(pat_type) => pat_type,
1032 FnArg::Receiver(_) => {
1033 return Err(syn::Error::new(
1034 input.span(),
1035 "unexpected self parameter in handler",
1036 ))
1037 }
1038 };
1039
1040 let arg = parse_handler_arg(pat_type, self.default_state.as_ref())?;
1041 if matches!(arg.kind, HandlerArgKind::Service { .. }) {
1042 self.uses_services = true;
1043 }
1044 args.push(arg);
1045 }
1046
1047 self.handlers.push(Handler {
1048 method_ident: method.sig.ident.clone(),
1049 event_index,
1050 filter: subscribe.filter,
1051 args,
1052 });
1053
1054 Ok(())
1055 }
1056
1057 fn register_event(&mut self, ty: &Type) -> usize {
1058 let key = type_to_key(ty);
1059 if let Some(index) = self.event_lookup.get(&key) {
1060 *index
1061 } else {
1062 let ident = format_ident!("__events_{}", sanitize_ident(&key));
1063 let index = self.events.len();
1064 self.events.push(EventCollection {
1065 ty: ty.clone(),
1066 ident,
1067 });
1068 self.event_lookup.insert(key, index);
1069 index
1070 }
1071 }
1072
1073 fn generate_process_fn(&self, crate_name: &proc_macro2::TokenStream) -> Result<ImplItemFn> {
1074 let event_bus_ty = quote! { #crate_name::event::EventBus };
1075 let resource_ctx_ty = quote! { #crate_name::context::ResourceContext };
1076 let service_ctx_ty = quote! { #crate_name::context::ServiceContext };
1077
1078 let collects = self.events.iter().map(|event| {
1079 let ident = &event.ident;
1080 let ty = &event.ty;
1081 quote! {
1082 let #ident: ::std::vec::Vec<#ty> =
1083 event_bus.reader::<#ty>().iter().cloned().collect();
1084 }
1085 });
1086
1087 let empty_check = if self.events.is_empty() {
1088 quote! {}
1089 } else {
1090 let empties = self.events.iter().map(|event| {
1091 let ident = &event.ident;
1092 quote! { #ident.is_empty() }
1093 });
1094 quote! {
1095 if true #(&& #empties)* {
1096 return;
1097 }
1098 }
1099 };
1100
1101 let handler_blocks = self
1102 .handlers
1103 .iter()
1104 .map(|handler| handler.expand(self.events.as_slice()));
1105
1106 let service_usage = if self.uses_services {
1107 quote! {}
1108 } else {
1109 quote! { let _ = services; }
1110 };
1111
1112 let body = quote! {
1113 let mut event_bus = match resources.get_mut::<#event_bus_ty>().await {
1114 Some(bus) => bus,
1115 None => return,
1116 };
1117
1118 #(#collects)*
1119
1120 #empty_check
1121
1122 drop(event_bus);
1123
1124 #service_usage
1125 #(#handler_blocks)*
1126 };
1127
1128 let process_fn: ImplItemFn = syn::parse_quote! {
1129 pub async fn process_events(
1130 &mut self,
1131 services: &#service_ctx_ty,
1132 resources: &mut #resource_ctx_ty,
1133 ) {
1134 #body
1135 }
1136 };
1137
1138 Ok(process_fn)
1139 }
1140}
1141
1142struct EventCollection {
1143 ty: Type,
1144 ident: Ident,
1145}
1146
1147struct Handler {
1148 method_ident: Ident,
1149 event_index: usize,
1150 filter: Option<Ident>,
1151 args: Vec<HandlerArg>,
1152}
1153
1154impl Handler {
1155 fn expand(&self, events: &[EventCollection]) -> proc_macro2::TokenStream {
1156 let event_ident = &events[self.event_index].ident;
1157 let method_ident = &self.method_ident;
1158 let filter_check = if let Some(filter) = &self.filter {
1159 quote! {
1160 if !self.#filter(event) {
1161 continue;
1162 }
1163 }
1164 } else {
1165 quote! {}
1166 };
1167
1168 let arg_exprs: Vec<_> = self
1169 .args
1170 .iter()
1171 .map(|arg| arg.argument_expression())
1172 .collect();
1173
1174 let mut block = quote! {
1175 for event in #event_ident.iter() {
1176 #filter_check
1177 self.#method_ident(event #(, #arg_exprs)*).await;
1178 }
1179 };
1180
1181 for arg in self.args.iter().rev() {
1182 block = arg.wrap_block(block);
1183 }
1184
1185 quote! {
1186 if !#event_ident.is_empty() {
1187 #block
1188 }
1189 }
1190 }
1191}
1192
1193struct HandlerArg {
1194 ident: Ident,
1195 kind: HandlerArgKind,
1196}
1197
1198impl HandlerArg {
1199 fn argument_expression(&self) -> proc_macro2::TokenStream {
1200 let ident = &self.ident;
1201 match &self.kind {
1202 HandlerArgKind::State { mutable, .. } => {
1203 if *mutable {
1204 quote! { &mut *#ident }
1205 } else {
1206 quote! { &*#ident }
1207 }
1208 }
1209 HandlerArgKind::Service { .. } => quote! { #ident },
1210 }
1211 }
1212
1213 fn wrap_block(&self, block: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
1214 let ident = &self.ident;
1215 match &self.kind {
1216 HandlerArgKind::State { ty, mutable } => {
1217 if *mutable {
1218 quote! {
1219 if let Some(mut #ident) = resources.get_mut::<#ty>().await {
1220 #block
1221 }
1222 }
1223 } else {
1224 quote! {
1225 if let Some(#ident) = resources.get::<#ty>().await {
1226 #block
1227 }
1228 }
1229 }
1230 }
1231 HandlerArgKind::Service { ty, service_name } => {
1232 let name = service_name.as_str();
1233 quote! {
1234 if let Some(#ident) = services.get_as::<#ty>(#name) {
1235 #block
1236 }
1237 }
1238 }
1239 }
1240 }
1241}
1242
1243enum HandlerArgKind {
1244 State { ty: Type, mutable: bool },
1245 Service { ty: Type, service_name: String },
1246}
1247
1248struct SubscribeAttr {
1249 event_type: Type,
1250 filter: Option<Ident>,
1251}
1252
1253fn parse_subscribe_attr(attr: Attribute) -> Result<SubscribeAttr> {
1254 attr.parse_args_with(|input: ParseStream| {
1255 let event_type: Type = input.parse()?;
1256 let mut filter = None;
1257
1258 while input.peek(Token![,]) {
1259 input.parse::<Token![,]>()?;
1260 let key: Ident = input.parse()?;
1261 if key == "filter" {
1262 input.parse::<Token![=]>()?;
1263 let lit: LitStr = input.parse()?;
1264 let ident = Ident::new(&lit.value(), lit.span());
1265 filter = Some(ident);
1266 } else {
1267 return Err(syn::Error::new(key.span(), "unknown #[subscribe] option"));
1268 }
1269 }
1270
1271 Ok(SubscribeAttr { event_type, filter })
1272 })
1273}
1274
1275fn parse_handler_arg(
1276 pat_type: &mut PatType,
1277 default_state: Option<&DefaultState>,
1278) -> Result<HandlerArg> {
1279 let ident = extract_ident(&pat_type.pat)?;
1280 let mut kind = None;
1281 let attrs = mem::take(&mut pat_type.attrs);
1282
1283 for attr in attrs {
1284 if attr.path().is_ident("state") {
1285 if !matches!(attr.meta, Meta::Path(_)) {
1286 return Err(syn::Error::new(
1287 attr.span(),
1288 "#[state] does not take arguments",
1289 ));
1290 }
1291 kind = Some(create_state_arg(&pat_type.ty, attr.span())?);
1292 } else if attr.path().is_ident("service") {
1293 let service_name = parse_service_attr(&attr)?;
1294 kind = Some(create_service_arg(&pat_type.ty, service_name, attr.span())?);
1295 } else {
1296 pat_type.attrs.push(attr);
1297 }
1298 }
1299
1300 if let Some(kind) = kind {
1301 return Ok(HandlerArg { ident, kind });
1302 }
1303
1304 if let Some(default_state) = default_state {
1305 if state_matches(&pat_type.ty, default_state)? {
1306 return Ok(HandlerArg {
1307 ident,
1308 kind: HandlerArgKind::State {
1309 ty: default_state.ty.clone(),
1310 mutable: true,
1311 },
1312 });
1313 }
1314 }
1315
1316 Err(syn::Error::new(
1317 pat_type.ty.span(),
1318 "additional parameters must be marked with #[state] or #[service]",
1319 ))
1320}
1321
1322fn create_state_arg(ty: &Type, span: Span) -> Result<HandlerArgKind> {
1323 if let Type::Reference(reference) = ty {
1324 if reference.mutability.is_none() {
1325 return Err(syn::Error::new(span, "state parameters must be `&mut T`"));
1326 }
1327 Ok(HandlerArgKind::State {
1328 ty: reference.elem.as_ref().clone(),
1329 mutable: true,
1330 })
1331 } else {
1332 Err(syn::Error::new(span, "state parameters must be references"))
1333 }
1334}
1335
1336fn create_service_arg(ty: &Type, service_name: String, span: Span) -> Result<HandlerArgKind> {
1337 if let Type::Reference(reference) = ty {
1338 if reference.mutability.is_some() {
1339 return Err(syn::Error::new(
1340 span,
1341 "services must be borrowed immutably as `&T`",
1342 ));
1343 }
1344 Ok(HandlerArgKind::Service {
1345 ty: reference.elem.as_ref().clone(),
1346 service_name,
1347 })
1348 } else {
1349 Err(syn::Error::new(
1350 span,
1351 "service parameters must be references",
1352 ))
1353 }
1354}
1355
1356fn state_matches(param_ty: &Type, default: &DefaultState) -> Result<bool> {
1357 if let Type::Reference(reference) = param_ty {
1358 if reference.mutability.is_none() {
1359 return Err(syn::Error::new(
1360 reference.and_token.span(),
1361 "default state parameter must be `&mut` reference",
1362 ));
1363 }
1364 let repr = type_to_key(reference.elem.as_ref());
1365 Ok(repr == default.repr)
1366 } else {
1367 Err(syn::Error::new(
1368 param_ty.span(),
1369 "default state parameter must be a reference",
1370 ))
1371 }
1372}
1373
1374fn extract_ident(pat: &Pat) -> Result<Ident> {
1375 if let Pat::Ident(PatIdent { ident, .. }) = pat {
1376 Ok(ident.clone())
1377 } else {
1378 Err(syn::Error::new(
1379 pat.span(),
1380 "parameters must be simple identifiers",
1381 ))
1382 }
1383}
1384
1385fn parse_service_attr(attr: &Attribute) -> Result<String> {
1386 if matches!(attr.meta, Meta::Path(_)) {
1387 return Err(syn::Error::new(
1388 attr.span(),
1389 "#[service] requires a `name = \"...\"` argument",
1390 ));
1391 }
1392
1393 attr.parse_args_with(|input: ParseStream| {
1394 if input.peek(LitStr) {
1395 Ok(input.parse::<LitStr>()?.value())
1396 } else {
1397 let ident: Ident = input.parse()?;
1398 if ident == "name" {
1399 input.parse::<Token![=]>()?;
1400 Ok(input.parse::<LitStr>()?.value())
1401 } else {
1402 Err(syn::Error::new(
1403 ident.span(),
1404 "expected `name = \"...\"` for #[service]",
1405 ))
1406 }
1407 }
1408 })
1409}
1410
1411fn parse_type_value(input: ParseStream) -> Result<Type> {
1412 if input.peek(LitStr) {
1413 let lit: LitStr = input.parse()?;
1414 lit.parse()
1415 } else {
1416 input.parse()
1417 }
1418}
1419
1420fn type_to_key(ty: &Type) -> String {
1421 ty.to_token_stream().to_string().replace(' ', "")
1422}
1423
1424fn sanitize_ident(raw: &str) -> String {
1425 raw.chars()
1426 .map(|ch| match ch {
1427 '<' | '>' | ':' | ',' | '&' | '*' | '(' | ')' | '[' | ']' | '{' | '}' => '_',
1428 other if other.is_whitespace() => '_',
1429 other => other,
1430 })
1431 .collect()
1432}
1433
1434#[proc_macro]
1439pub fn event(input: TokenStream) -> TokenStream {
1440 let input = parse_macro_input!(input as EventMacroInput);
1441 let crate_name = get_crate_name();
1442
1443 let expanded = input
1444 .events
1445 .into_iter()
1446 .map(|event| event.expand(&crate_name));
1447
1448 TokenStream::from(quote! {
1449 #(#expanded)*
1450 })
1451}
1452
1453struct EventMacroInput {
1454 events: Vec<EventDefinition>,
1455}
1456
1457impl Parse for EventMacroInput {
1458 fn parse(input: ParseStream) -> Result<Self> {
1459 let mut events = Vec::new();
1460 while !input.is_empty() {
1461 events.push(input.parse()?);
1462
1463 if input.peek(Token![,]) {
1464 input.parse::<Token![,]>()?;
1465 }
1466 }
1467 Ok(Self { events })
1468 }
1469}
1470
1471struct EventDefinition {
1472 attrs: Vec<Attribute>,
1473 additional_derives: Vec<Path>,
1474 visibility: Visibility,
1475 name: Ident,
1476 fields: EventFields,
1477}
1478
1479impl EventDefinition {
1480 fn expand(self, crate_name: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
1481 let mut derives = vec![
1482 syn::parse_quote!(Debug),
1483 syn::parse_quote!(Clone),
1484 syn::parse_quote!(::serde::Serialize),
1485 syn::parse_quote!(::serde::Deserialize),
1486 ];
1487
1488 for path in self.additional_derives {
1489 push_unique_path(&mut derives, path);
1490 }
1491
1492 let derive_attr = quote! {
1493 #[derive(#(#derives),*)]
1494 };
1495
1496 let body = match self.fields {
1497 EventFields::Unit => quote!(;),
1498 EventFields::Struct(fields) => {
1499 let rendered_fields = fields.into_iter().map(|field| {
1500 let attrs = field.attrs;
1501 let vis = field.visibility;
1502 let name = field.name;
1503 let ty = field.ty;
1504 quote! {
1505 #(#attrs)*
1506 #vis #name: #ty
1507 }
1508 });
1509
1510 quote! {
1511 {
1512 #(#rendered_fields,)*
1513 }
1514 }
1515 }
1516 };
1517
1518 let attrs = self.attrs;
1519 let vis = self.visibility;
1520 let name = self.name;
1521
1522 quote! {
1523 #(#attrs)*
1524 #derive_attr
1525 #vis struct #name #body
1526
1527 impl #crate_name::event::Event for #name {}
1528 }
1529 }
1530}
1531
1532impl Parse for EventDefinition {
1533 fn parse(input: ParseStream) -> Result<Self> {
1534 let outer_attrs = input.call(Attribute::parse_outer)?;
1535 let mut attrs = Vec::new();
1536 let mut derives = Vec::new();
1537
1538 for attr in outer_attrs {
1539 if attr.path().is_ident("derive") {
1540 let parsed: Punctuated<Path, Token![,]> =
1541 attr.parse_args_with(Punctuated::parse_terminated)?;
1542 derives.extend(parsed.into_iter());
1543 } else {
1544 attrs.push(attr);
1545 }
1546 }
1547
1548 let visibility = if input.peek(Token![pub]) {
1549 input.parse()?
1550 } else {
1551 Visibility::Inherited
1552 };
1553
1554 if input.peek(Token![struct]) {
1555 input.parse::<Token![struct]>()?;
1556 }
1557
1558 let name: Ident = input.parse()?;
1559
1560 let fields = if input.peek(Token![;]) {
1561 input.parse::<Token![;]>()?;
1562 EventFields::Unit
1563 } else {
1564 let content;
1565 braced!(content in input);
1566
1567 let mut parsed_fields = Vec::new();
1568 while !content.is_empty() {
1569 let attrs = content.call(Attribute::parse_outer)?;
1570 if content.is_empty() {
1571 return Err(content.error("expected field definition after attributes"));
1572 }
1573
1574 let visibility = if content.peek(Token![pub]) {
1575 content.parse()?
1576 } else {
1577 Visibility::Inherited
1578 };
1579 let name: Ident = content.parse()?;
1580 content.parse::<Token![:]>()?;
1581 let ty: Type = content.parse()?;
1582
1583 if content.peek(Token![,]) {
1584 content.parse::<Token![,]>()?;
1585 } else if content.peek(Token![;]) {
1586 content.parse::<Token![;]>()?;
1587 } else if !content.is_empty() {
1588 return Err(content.error("expected `,` or `;` after field"));
1589 }
1590
1591 parsed_fields.push(EventField {
1592 attrs,
1593 visibility,
1594 name,
1595 ty,
1596 });
1597 }
1598
1599 EventFields::Struct(parsed_fields)
1600 };
1601
1602 if input.peek(Token![;]) {
1603 input.parse::<Token![;]>()?;
1604 }
1605
1606 Ok(Self {
1607 attrs,
1608 additional_derives: derives,
1609 visibility,
1610 name,
1611 fields,
1612 })
1613 }
1614}
1615
1616enum EventFields {
1617 Unit,
1618 Struct(Vec<EventField>),
1619}
1620
1621struct EventField {
1622 attrs: Vec<Attribute>,
1623 visibility: Visibility,
1624 name: Ident,
1625 ty: Type,
1626}
1627
1628fn push_unique_path(paths: &mut Vec<Path>, new_path: Path) {
1629 let repr = path_to_string(&new_path);
1630 if paths
1631 .iter()
1632 .all(|existing| path_to_string(existing) != repr)
1633 {
1634 paths.push(new_path);
1635 }
1636}
1637
1638fn path_to_string(path: &Path) -> String {
1639 path.to_token_stream().to_string()
1640}
1641
1642#[proc_macro_attribute]
1644pub fn auto_pump(attr: TokenStream, item: TokenStream) -> TokenStream {
1645 let args = parse_macro_input!(attr as AutoPumpArgs);
1646
1647 let item_clone = item.clone();
1648 if let Ok(mut function) = syn::parse::<ItemFn>(item_clone) {
1649 match apply_auto_pump(&function.sig, &mut function.block, &args) {
1650 Ok(()) => return TokenStream::from(quote! { #function }),
1651 Err(err) => return err.to_compile_error().into(),
1652 }
1653 }
1654
1655 let mut method = parse_macro_input!(item as ImplItemFn);
1656 match apply_auto_pump(&method.sig, &mut method.block, &args) {
1657 Ok(()) => TokenStream::from(quote! { #method }),
1658 Err(err) => err.to_compile_error().into(),
1659 }
1660}
1661
1662#[derive(Clone)]
1663struct AutoPumpArgs {
1664 before: bool,
1665 after: bool,
1666 pump_fn: Option<Path>,
1667 sides_specified: bool,
1668}
1669
1670impl Default for AutoPumpArgs {
1671 fn default() -> Self {
1672 Self {
1673 before: true,
1674 after: true,
1675 pump_fn: None,
1676 sides_specified: false,
1677 }
1678 }
1679}
1680
1681impl Parse for AutoPumpArgs {
1682 fn parse(input: ParseStream) -> Result<Self> {
1683 if input.is_empty() {
1684 return Ok(Self::default());
1685 }
1686
1687 let mut args = AutoPumpArgs {
1688 before: false,
1689 after: false,
1690 pump_fn: None,
1691 sides_specified: false,
1692 };
1693
1694 while !input.is_empty() {
1695 let ident: Ident = input.parse()?;
1696 match ident.to_string().as_str() {
1697 "before" => {
1698 args.before = true;
1699 args.sides_specified = true;
1700 }
1701 "after" => {
1702 args.after = true;
1703 args.sides_specified = true;
1704 }
1705 "pump_fn" => {
1706 input.parse::<Token![=]>()?;
1707 let path = if input.peek(LitStr) {
1708 input.parse::<LitStr>()?.parse::<Path>()?
1709 } else {
1710 input.parse::<Path>()?
1711 };
1712 args.pump_fn = Some(path);
1713 }
1714 other => {
1715 return Err(syn::Error::new(
1716 ident.span(),
1717 format!("Unknown auto_pump option `{}`", other),
1718 ));
1719 }
1720 }
1721
1722 if input.peek(Token![,]) {
1723 input.parse::<Token![,]>()?;
1724 }
1725 }
1726
1727 if !args.sides_specified {
1728 args.before = true;
1729 args.after = true;
1730 }
1731
1732 Ok(args)
1733 }
1734}
1735
1736struct PumpParams {
1737 services: Ident,
1738 systems: Ident,
1739 resources: Ident,
1740}
1741
1742fn apply_auto_pump(signature: &Signature, block: &mut Block, args: &AutoPumpArgs) -> Result<()> {
1743 if !args.before && !args.after {
1744 return Ok(());
1745 }
1746
1747 let params = extract_pump_params(signature)?;
1748 let pump_path = args.pump_fn.clone().unwrap_or_else(|| {
1749 syn::parse_str::<Path>("crate::plugins::pump_event_systems").expect("valid path")
1750 });
1751
1752 let mut original = mem::take(&mut block.stmts);
1753 let mut stmts = Vec::new();
1754
1755 if args.before {
1757 stmts.push(build_pump_stmt(&pump_path, ¶ms));
1758 }
1759
1760 let has_trailing_expr = original
1763 .last()
1764 .is_some_and(|stmt| matches!(stmt, Stmt::Expr(_, None)));
1765
1766 if args.after && has_trailing_expr {
1767 if original.len() > 1 {
1769 stmts.extend(original.drain(..original.len() - 1));
1770 }
1771 stmts.push(build_pump_stmt(&pump_path, ¶ms));
1773 stmts.extend(original);
1775 } else {
1776 stmts.extend(original);
1778 if args.after {
1779 stmts.push(build_pump_stmt(&pump_path, ¶ms));
1780 }
1781 }
1782
1783 block.stmts = stmts;
1784 Ok(())
1785}
1786
1787fn build_pump_stmt(path: &Path, params: &PumpParams) -> Stmt {
1788 let services = ¶ms.services;
1789 let systems = ¶ms.systems;
1790 let resources = ¶ms.resources;
1791
1792 let tokens = quote! {
1794 #path(#services, #systems, #resources).await;
1795 };
1796 syn::parse2(tokens).expect("failed to parse pump statement")
1797}
1798
1799fn extract_pump_params(signature: &Signature) -> Result<PumpParams> {
1800 let mut services = None;
1801 let mut systems = None;
1802 let mut resources = None;
1803
1804 for input in signature.inputs.iter() {
1805 if let FnArg::Typed(pat_type) = input {
1806 if let Ok(ident) = extract_ident(&pat_type.pat) {
1807 if matches_context(&pat_type.ty, "ServiceContext") {
1808 services = Some(ident);
1809 } else if matches_context(&pat_type.ty, "SystemContext") {
1810 systems = Some(ident);
1811 } else if matches_context(&pat_type.ty, "ResourceContext") {
1812 resources = Some(ident);
1813 }
1814 }
1815 }
1816 }
1817
1818 match (services, systems, resources) {
1819 (Some(sv), Some(sys), Some(res)) => Ok(PumpParams {
1820 services: sv,
1821 systems: sys,
1822 resources: res,
1823 }),
1824 _ => Err(syn::Error::new(
1825 signature.fn_token.span(),
1826 "#[auto_pump] requires parameters for ServiceContext, SystemContext, and ResourceContext",
1827 )),
1828 }
1829}
1830
1831fn matches_context(ty: &Type, expected: &str) -> bool {
1832 match ty {
1833 Type::Reference(reference) => match reference.elem.as_ref() {
1834 Type::Path(path) => path
1835 .path
1836 .segments
1837 .last()
1838 .map(|segment| segment.ident == expected)
1839 .unwrap_or(false),
1840 _ => false,
1841 },
1842 Type::Path(path) => path
1843 .path
1844 .segments
1845 .last()
1846 .map(|segment| segment.ident == expected)
1847 .unwrap_or(false),
1848 _ => false,
1849 }
1850}