issun_macros/
lib.rs

1//! Proc macros for ISSUN game engine
2//!
3//! This crate provides derive macros for:
4//! - `#[derive(Scene)]` - Auto-implement Scene trait
5//! - `#[derive(Entity)]` - Auto-generate entity methods
6//! - `#[derive(Service)]` - Auto-implement Service trait
7//! - `#[derive(System)]` - Auto-implement System trait
8//! - `#[derive(Asset)]` - Auto-generate asset loading
9//!
10//! Bevy-specific macros:
11//! - `#[derive(IssunEntity)]` - Auto-generate component getters for any entity-holding Resource
12//! - `#[derive(IssunQuery)]` - Query helper methods avoiding borrowing conflicts
13//! - `#[derive(IssunBevyPlugin)]` - Auto-generate Bevy Plugin boilerplate
14//! - `log!()` - Simplified EventLog macro
15
16mod bevy;
17
18use proc_macro::TokenStream;
19use proc_macro2::Span;
20use proc_macro_crate::{crate_name, FoundCrate};
21use quote::{format_ident, quote, ToTokens};
22use std::collections::HashMap;
23use std::mem;
24use syn::{
25    braced,
26    parse::{Parse, ParseStream},
27    parse_macro_input,
28    punctuated::Punctuated,
29    spanned::Spanned,
30    Attribute, Block, Data, DeriveInput, Fields, FnArg, Ident, ImplItem, ImplItemFn, ItemFn,
31    ItemImpl, Lit, LitStr, Meta, Pat, PatIdent, PatType, Path, Result, Signature, Stmt, Token,
32    Type, Visibility,
33};
34
35/// Helper function to get the issun crate identifier
36/// Returns `crate` if called from within issun crate, otherwise `::issun`
37fn get_crate_name() -> proc_macro2::TokenStream {
38    match crate_name("issun") {
39        Ok(FoundCrate::Itself) => quote!(crate),
40        Ok(FoundCrate::Name(name)) => {
41            let ident = format_ident!("{}", name);
42            quote!(::#ident)
43        }
44        Err(_) => quote!(::issun),
45    }
46}
47
48/// Derive macro for Scene trait
49///
50/// # Example
51/// ```ignore
52/// #[derive(Scene)]
53/// #[scene(context = "GameContext", initial = "Title(TitleSceneData::new())")]
54/// enum MyGameScene {
55///     Title(TitleSceneData),
56///     Combat(CombatData),
57/// }
58/// ```
59///
60/// This will auto-generate:
61/// - Scene trait implementation
62/// - GameState struct (or custom name via `name` attribute)
63/// - GameState::new() with initial scene and context
64#[proc_macro_derive(Scene, attributes(scene))]
65pub fn derive_scene(input: TokenStream) -> TokenStream {
66    let input = parse_macro_input!(input as DeriveInput);
67
68    let scene_name = &input.ident;
69
70    // Parse #[scene(...)] attributes
71    let scene_attrs = parse_scene_attributes(&input.attrs);
72
73    let crate_name = get_crate_name();
74
75    // Generate Scene trait implementation
76    let scene_impl = quote! {
77        #[::async_trait::async_trait]
78        impl #crate_name::scene::Scene for #scene_name {
79            async fn on_enter(
80                &mut self,
81                _services: &#crate_name::context::ServiceContext,
82                _systems: &mut #crate_name::context::SystemContext,
83                _resources: &mut #crate_name::context::ResourceContext,
84            ) {
85                // Default implementation: do nothing
86            }
87
88            async fn on_update(
89                &mut self,
90                _services: &#crate_name::context::ServiceContext,
91                _systems: &mut #crate_name::context::SystemContext,
92                _resources: &mut #crate_name::context::ResourceContext,
93            ) -> #crate_name::scene::SceneTransition<Self> {
94                // Default implementation: stay in current scene
95                #crate_name::scene::SceneTransition::Stay
96            }
97
98            async fn on_exit(
99                &mut self,
100                _services: &#crate_name::context::ServiceContext,
101                _systems: &mut #crate_name::context::SystemContext,
102                _resources: &mut #crate_name::context::ResourceContext,
103            ) {
104                // Default implementation: do nothing
105            }
106
107            async fn on_suspend(
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            async fn on_resume(
116                &mut self,
117                _services: &#crate_name::context::ServiceContext,
118                _systems: &mut #crate_name::context::SystemContext,
119                _resources: &mut #crate_name::context::ResourceContext,
120            ) {
121            }
122        }
123    };
124
125    // Generate GameState struct if context and initial are specified
126    let game_state_gen =
127        if let (Some(context), Some(initial)) = (&scene_attrs.context, &scene_attrs.initial) {
128            let state_name = scene_attrs
129                .name
130                .as_ref()
131                .map(|n| format_ident!("{}", n))
132                .unwrap_or_else(|| format_ident!("GameState"));
133
134            let context_ident = format_ident!("{}", context);
135            let initial_expr: proc_macro2::TokenStream = initial.parse().unwrap();
136
137            quote! {
138                /// Auto-generated game state combining scene and context
139                #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
140                pub struct #state_name {
141                    pub scene: #scene_name,
142                    pub ctx: #context_ident,
143                    pub should_quit: bool,
144                }
145
146                impl #state_name {
147                    pub fn new() -> Self {
148                        Self {
149                            scene: #scene_name::#initial_expr,
150                            ctx: #context_ident::new(),
151                            should_quit: false,
152                        }
153                    }
154                }
155
156                impl Default for #state_name {
157                    fn default() -> Self {
158                        Self::new()
159                    }
160                }
161            }
162        } else {
163            quote! {}
164        };
165
166    // Generate handler dispatcher if handler_params is specified
167    let handler_gen = if let Some(params) = &scene_attrs.handler_params {
168        let handler_name = scene_attrs
169            .handler
170            .as_ref()
171            .map(|h| format_ident!("{}", h))
172            .unwrap_or_else(|| format_ident!("handle_input"));
173
174        let params_tokens: proc_macro2::TokenStream = params.parse().unwrap();
175
176        // Extract parameter names from "ctx: &mut GameContext, input: InputEvent" -> ["ctx", "input"]
177        let param_names = extract_param_names(params);
178
179        // Default return type based on scene name (kept for potential future use)
180        let _return_type = scene_attrs
181            .handler_return
182            .as_ref()
183            .map(|r| r.parse::<proc_macro2::TokenStream>().unwrap())
184            .unwrap_or_else(|| {
185                quote! { (#scene_name, ::issun::scene::SceneTransition<#scene_name>) }
186            });
187
188        // Extract variants from enum
189        let variants = if let Data::Enum(data_enum) = &input.data {
190            &data_enum.variants
191        } else {
192            panic!("Scene derive macro only works on enums");
193        };
194
195        // Generate match arms for each variant
196        let match_arms = variants.iter().map(|variant| {
197            let variant_name = &variant.ident;
198            quote! {
199                #scene_name::#variant_name(data) => data.#handler_name(
200                    services,
201                    systems,
202                    resources,
203                    #(#param_names),*
204                ).await
205            }
206        });
207
208        quote! {
209            /// Auto-generated scene input handler dispatcher
210            ///
211            /// Takes a mutable reference to the scene and returns a transition.
212            pub async fn handle_scene_input(
213                scene: &mut #scene_name,
214                services: &#crate_name::context::ServiceContext,
215                systems: &mut #crate_name::context::SystemContext,
216                resources: &mut #crate_name::context::ResourceContext,
217                #params_tokens,
218            ) -> ::issun::scene::SceneTransition<#scene_name> {
219                match scene {
220                    #(#match_arms),*
221                }
222            }
223        }
224    } else {
225        quote! {}
226    };
227
228    let expanded = quote! {
229        #scene_impl
230        #game_state_gen
231        #handler_gen
232    };
233
234    TokenStream::from(expanded)
235}
236
237/// Parse #[scene(...)] attributes
238struct SceneAttributes {
239    context: Option<String>,
240    initial: Option<String>,
241    name: Option<String>,
242    handler: Option<String>,
243    handler_params: Option<String>,
244    handler_return: Option<String>,
245}
246
247fn parse_scene_attributes(attrs: &[syn::Attribute]) -> SceneAttributes {
248    let mut context = None;
249    let mut initial = None;
250    let mut name = None;
251    let mut handler = None;
252    let mut handler_params = None;
253    let mut handler_return = None;
254
255    for attr in attrs {
256        if attr.path().is_ident("scene") {
257            let _ = attr.parse_nested_meta(|meta| {
258                if meta.path.is_ident("context") {
259                    if let Ok(value) = meta.value() {
260                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
261                            context = Some(s.value());
262                        }
263                    }
264                } else if meta.path.is_ident("initial") {
265                    if let Ok(value) = meta.value() {
266                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
267                            initial = Some(s.value());
268                        }
269                    }
270                } else if meta.path.is_ident("name") {
271                    if let Ok(value) = meta.value() {
272                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
273                            name = Some(s.value());
274                        }
275                    }
276                } else if meta.path.is_ident("handler") {
277                    if let Ok(value) = meta.value() {
278                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
279                            handler = Some(s.value());
280                        }
281                    }
282                } else if meta.path.is_ident("handler_params") {
283                    if let Ok(value) = meta.value() {
284                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
285                            handler_params = Some(s.value());
286                        }
287                    }
288                } else if meta.path.is_ident("handler_return") {
289                    if let Ok(value) = meta.value() {
290                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
291                            handler_return = Some(s.value());
292                        }
293                    }
294                }
295                Ok(())
296            });
297        }
298    }
299
300    SceneAttributes {
301        context,
302        initial,
303        name,
304        handler,
305        handler_params,
306        handler_return,
307    }
308}
309
310/// Extract parameter names from function parameters string
311/// "ctx: &mut GameContext, input: InputEvent" -> vec![ident("ctx"), ident("input")]
312fn extract_param_names(params_str: &str) -> Vec<proc_macro2::Ident> {
313    params_str
314        .split(',')
315        .filter_map(|param| {
316            let param = param.trim();
317            // Extract the identifier before the colon
318            param.split(':').next().map(|name| {
319                let name = name.trim();
320                format_ident!("{}", name)
321            })
322        })
323        .collect()
324}
325
326/// Derive macro for Entity trait
327///
328/// # Example
329/// ```ignore
330/// #[derive(Entity)]
331/// pub struct Player {
332///     #[entity(id)]
333///     pub name: String,
334///     pub hp: i32,
335/// }
336/// ```
337#[proc_macro_derive(Entity, attributes(entity))]
338pub fn derive_entity(input: TokenStream) -> TokenStream {
339    let input = parse_macro_input!(input as DeriveInput);
340
341    let name = &input.ident;
342
343    // Extract fields to find #[entity(id)]
344    let id_field = match &input.data {
345        Data::Struct(data) => {
346            match &data.fields {
347                Fields::Named(fields) => {
348                    // Find field with #[entity(id)] attribute
349                    fields
350                        .named
351                        .iter()
352                        .find(|field| {
353                            field
354                                .attrs
355                                .iter()
356                                .any(|attr| attr.path().is_ident("entity"))
357                        })
358                        .map(|field| field.ident.as_ref().unwrap())
359                }
360                _ => None,
361            }
362        }
363        _ => None,
364    };
365
366    let id_impl = if let Some(field_name) = id_field {
367        quote! {
368            fn id(&self) -> &str {
369                &self.#field_name
370            }
371        }
372    } else {
373        // No #[entity(id)] found - use default implementation
374        quote! {
375            fn id(&self) -> &str {
376                ""
377            }
378        }
379    };
380
381    let expanded = quote! {
382        #[async_trait::async_trait]
383        impl Entity for #name {
384            #id_impl
385
386            async fn update(&mut self, _ctx: &mut Context) {
387                // Default implementation: do nothing
388            }
389        }
390    };
391
392    TokenStream::from(expanded)
393}
394
395/// Derive macro for Service trait
396///
397/// Auto-generates the boilerplate Service trait implementation.
398/// Requires #[service(name = "service_name")] attribute.
399///
400/// # Example
401/// ```ignore
402/// use crate::service::Service; // or use issun::service::Service;
403///
404/// #[derive(Service)]
405/// #[service(name = "combat_service")]
406/// pub struct CombatService {
407///     min_damage: i32,
408/// }
409/// ```
410///
411/// This generates:
412/// - name() method returning the specified service name
413/// - as_any() and as_any_mut() for downcasting
414/// - async_trait wrapper
415///
416/// Note: You must have `Service` trait in scope via `use` statement.
417#[proc_macro_derive(Service, attributes(service))]
418pub fn derive_service(input: TokenStream) -> TokenStream {
419    let input = parse_macro_input!(input as DeriveInput);
420
421    let struct_name = &input.ident;
422
423    // Parse #[service(name = "...")] attribute
424    let service_name = parse_service_name(&input.attrs);
425    let service_name_lit = syn::LitStr::new(&service_name, proc_macro2::Span::call_site());
426
427    let crate_name = get_crate_name();
428
429    let expanded = quote! {
430        impl #struct_name {
431            pub const NAME: &'static str = #service_name_lit;
432        }
433
434        #[::async_trait::async_trait]
435        impl #crate_name::service::Service for #struct_name {
436            fn name(&self) -> &'static str {
437                #service_name
438            }
439
440            fn clone_box(&self) -> Box<dyn #crate_name::service::Service> {
441                Box::new(self.clone())
442            }
443
444            fn as_any(&self) -> &dyn ::std::any::Any {
445                self
446            }
447
448            fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
449                self
450            }
451        }
452    };
453
454    TokenStream::from(expanded)
455}
456
457/// Parse #[service(name = "service_name")] attribute
458fn parse_service_name(attrs: &[syn::Attribute]) -> String {
459    for attr in attrs {
460        if attr.path().is_ident("service") {
461            let mut name = None;
462            let _ = attr.parse_nested_meta(|meta| {
463                if meta.path.is_ident("name") {
464                    if let Ok(value) = meta.value() {
465                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
466                            name = Some(s.value());
467                        }
468                    }
469                }
470                Ok(())
471            });
472            if let Some(n) = name {
473                return n;
474            }
475        }
476    }
477
478    // Default: use lowercase struct name
479    "unknown_service".to_string()
480}
481
482/// Derive macro for System trait
483///
484/// Auto-generates the boilerplate System trait implementation.
485/// Requires #[system(name = "system_name")] attribute.
486///
487/// # Example
488/// ```ignore
489/// use crate::system::System; // or use issun::system::System;
490///
491/// #[derive(System)]
492/// #[system(name = "combat_engine")]
493/// pub struct CombatSystem {
494///     turn_count: u32,
495///     log: Vec<String>,
496/// }
497/// ```
498///
499/// This generates:
500/// - name() method returning the specified system name
501/// - as_any() and as_any_mut() for downcasting
502/// - async_trait wrapper
503///
504/// Note: You must have `System` trait in scope via `use` statement.
505#[proc_macro_derive(System, attributes(system))]
506pub fn derive_system(input: TokenStream) -> TokenStream {
507    let input = parse_macro_input!(input as DeriveInput);
508
509    let struct_name = &input.ident;
510
511    // Parse #[system(name = "...")] attribute
512    let system_name = parse_system_name(&input.attrs);
513    let system_name_lit = syn::LitStr::new(&system_name, proc_macro2::Span::call_site());
514
515    let crate_name = get_crate_name();
516
517    let expanded = quote! {
518        impl #struct_name {
519            pub const NAME: &'static str = #system_name_lit;
520        }
521
522        #[::async_trait::async_trait]
523        impl #crate_name::system::System for #struct_name {
524            fn name(&self) -> &'static str {
525                #system_name
526            }
527
528            fn as_any(&self) -> &dyn ::std::any::Any {
529                self
530            }
531
532            fn as_any_mut(&mut self) -> &mut dyn ::std::any::Any {
533                self
534            }
535        }
536    };
537
538    TokenStream::from(expanded)
539}
540
541/// Parse #[system(name = "system_name")] attribute
542fn parse_system_name(attrs: &[syn::Attribute]) -> String {
543    for attr in attrs {
544        if attr.path().is_ident("system") {
545            let mut name = None;
546            let _ = attr.parse_nested_meta(|meta| {
547                if meta.path.is_ident("name") {
548                    if let Ok(value) = meta.value() {
549                        if let Ok(Lit::Str(s)) = value.parse::<Lit>() {
550                            name = Some(s.value());
551                        }
552                    }
553                }
554                Ok(())
555            });
556            if let Some(n) = name {
557                return n;
558            }
559        }
560    }
561
562    // Default: use lowercase struct name
563    "unknown_system".to_string()
564}
565
566/// Derive macro for Asset trait
567///
568/// # Example
569/// ```ignore
570/// #[derive(Asset)]
571/// pub struct EnemyAsset {
572///     pub name: &'static str,
573///     pub hp: i32,
574/// }
575/// ```
576#[proc_macro_derive(Asset)]
577pub fn derive_asset(input: TokenStream) -> TokenStream {
578    let input = parse_macro_input!(input as DeriveInput);
579
580    let name = &input.ident;
581
582    let expanded = quote! {
583        impl Asset for #name {
584            // Uses default implementation from trait
585        }
586    };
587
588    TokenStream::from(expanded)
589}
590
591/// Derive macro for Resource trait
592///
593/// Automatically implements the `Resource` marker trait for types that
594/// can be stored in the global resource registry.
595///
596/// # Example
597/// ```ignore
598/// use issun::prelude::*;
599///
600/// #[derive(Resource)]
601/// pub struct GameConfig {
602///     pub fps: u32,
603///     pub difficulty: f32,
604/// }
605///
606/// #[derive(Resource)]
607/// pub struct EnemyDatabase {
608///     pub enemies: Vec<EnemyAsset>,
609/// }
610/// ```
611#[proc_macro_derive(Resource)]
612pub fn derive_resource(input: TokenStream) -> TokenStream {
613    let input = parse_macro_input!(input as DeriveInput);
614
615    let name = &input.ident;
616    let crate_name = get_crate_name();
617
618    let expanded = quote! {
619        impl #crate_name::resources::Resource for #name {
620            // Uses default implementation from trait
621        }
622    };
623
624    TokenStream::from(expanded)
625}
626
627/// Derive macro for Plugin trait
628///
629/// # Example
630/// ```ignore
631/// #[derive(Default, Plugin)]
632/// #[plugin(name = "my_plugin")]
633/// #[plugin(service = MyService)]
634/// #[plugin(system = MySystem)]
635/// #[plugin(state = MyState)]
636/// pub struct MyPlugin;
637/// ```
638#[proc_macro_derive(Plugin, attributes(plugin, resource, state, system, service))]
639pub fn derive_plugin(input: TokenStream) -> TokenStream {
640    let input = parse_macro_input!(input as DeriveInput);
641    let name = &input.ident;
642    let crate_name = get_crate_name();
643
644    // Parse plugin attributes (struct level)
645    let mut plugin_name = None;
646    let mut services = Vec::new();
647    let mut systems = Vec::new();
648    let mut states = Vec::new();
649    let mut resources = Vec::new();
650
651    for attr in &input.attrs {
652        if !attr.path().is_ident("plugin") {
653            continue;
654        }
655
656        let result: Result<()> = attr.parse_nested_meta(|meta| {
657            if meta.path.is_ident("name") {
658                let value = meta.value()?;
659                let lit: LitStr = value.parse()?;
660                plugin_name = Some(lit.value());
661                Ok(())
662            } else if meta.path.is_ident("service") {
663                let value = meta.value()?;
664                let ty: Type = value.parse()?;
665                services.push(ty);
666                Ok(())
667            } else if meta.path.is_ident("system") {
668                let value = meta.value()?;
669                let ty: Type = value.parse()?;
670                systems.push(ty);
671                Ok(())
672            } else if meta.path.is_ident("state") {
673                let value = meta.value()?;
674                let ty: Type = value.parse()?;
675                states.push(ty);
676                Ok(())
677            } else if meta.path.is_ident("resource") {
678                let value = meta.value()?;
679                let ty: Type = value.parse()?;
680                resources.push(ty);
681                Ok(())
682            } else {
683                Err(meta.error("expected `name`, `service`, `system`, `state`, or `resource`"))
684            }
685        });
686
687        if let Err(err) = result {
688            return err.to_compile_error().into();
689        }
690    }
691
692    let plugin_name = plugin_name.unwrap_or_else(|| {
693        // Default: convert MyPlugin -> "my_plugin"
694        let name_str = name.to_string();
695        name_str
696            .trim_end_matches("Plugin")
697            .chars()
698            .enumerate()
699            .flat_map(|(i, c)| {
700                if i > 0 && c.is_uppercase() {
701                    vec!['_', c.to_ascii_lowercase()]
702                } else {
703                    vec![c.to_ascii_lowercase()]
704                }
705            })
706            .collect::<String>()
707    });
708
709    // Generate registrations from attributes (Type::default())
710    let attr_service_registrations = services.iter().map(|ty| {
711        quote! {
712            builder.register_service(Box::new(#ty::default()));
713        }
714    });
715
716    let attr_system_registrations = systems.iter().map(|ty| {
717        quote! {
718            builder.register_system(Box::new(#ty::default()));
719        }
720    });
721
722    let attr_state_registrations = states.iter().map(|ty| {
723        quote! {
724            builder.register_runtime_state(#ty::default());
725        }
726    });
727
728    let attr_resource_registrations = resources.iter().map(|ty| {
729        quote! {
730            builder.register_resource(#ty::default());
731        }
732    });
733
734    // Parse field attributes (instance registration)
735    let mut field_registrations = Vec::new();
736
737    if let Data::Struct(data) = &input.data {
738        for field in &data.fields {
739            let field_name = &field.ident;
740
741            // Skip fields without identifiers (tuple structs) for now, or handle them if needed.
742            // The requirement implies named fields for clarity, but let's support named fields primarily.
743            if field_name.is_none() {
744                continue;
745            }
746            let field_access = quote! { self.#field_name };
747
748            for attr in &field.attrs {
749                // Handle both #[plugin(...)] and #[...] attribute formats
750                if attr.path().is_ident("plugin") {
751                    // Handle #[plugin(...)] format using parse_nested_meta
752                    let result: Result<()> = attr.parse_nested_meta(|meta| {
753                        if meta.path.is_ident("resource") {
754                            field_registrations.push(quote! {
755                                builder.register_resource(#field_access.clone());
756                            });
757                        } else if meta.path.is_ident("state") || meta.path.is_ident("runtime_state") {
758                            field_registrations.push(quote! {
759                                builder.register_runtime_state(#field_access.clone());
760                            });
761                        } else if meta.path.is_ident("system") {
762                            field_registrations.push(quote! {
763                                builder.register_system(Box::new(#field_access.clone()));
764                            });
765                        } else if meta.path.is_ident("service") {
766                            field_registrations.push(quote! {
767                                builder.register_service(Box::new(#field_access.clone()));
768                            });
769                        } else if meta.path.is_ident("skip") {
770                            // Explicitly skip this field - no registration
771                        } else {
772                            return Err(meta.error("expected `resource`, `state`, `runtime_state`, `system`, `service`, or `skip`"));
773                        }
774                        Ok(())
775                    });
776
777                    if let Err(err) = result {
778                        return err.to_compile_error().into();
779                    }
780                } else if attr.path().is_ident("resource") {
781                    // Handle legacy #[resource] format
782                    field_registrations.push(quote! {
783                        builder.register_resource(#field_access.clone());
784                    });
785                } else if attr.path().is_ident("state") || attr.path().is_ident("runtime_state") {
786                    // Handle legacy #[state] or #[runtime_state] format
787                    field_registrations.push(quote! {
788                        builder.register_runtime_state(#field_access.clone());
789                    });
790                } else if attr.path().is_ident("system") {
791                    // Handle legacy #[system] format
792                    field_registrations.push(quote! {
793                        builder.register_system(Box::new(#field_access.clone()));
794                    });
795                } else if attr.path().is_ident("service") {
796                    // Handle legacy #[service] format
797                    field_registrations.push(quote! {
798                        builder.register_service(Box::new(#field_access.clone()));
799                    });
800                }
801            }
802        }
803    }
804
805    let expanded = quote! {
806        #[::async_trait::async_trait]
807        impl #crate_name::plugin::Plugin for #name {
808            fn name(&self) -> &'static str {
809                #plugin_name
810            }
811
812            fn build(&self, builder: &mut dyn #crate_name::plugin::PluginBuilder) {
813                use #crate_name::plugin::PluginBuilderExt;
814
815                // Attribute-based registrations (Types)
816                #(#attr_service_registrations)*
817                #(#attr_system_registrations)*
818                #(#attr_state_registrations)*
819                #(#attr_resource_registrations)*
820
821                // Field-based registrations (Instances)
822                #(#field_registrations)*
823            }
824        }
825    };
826
827    TokenStream::from(expanded)
828}
829
830/// Attribute macro that generates `process_events` for systems reacting to events.
831#[proc_macro_attribute]
832pub fn event_handler(attr: TokenStream, item: TokenStream) -> TokenStream {
833    let args = parse_macro_input!(attr as EventHandlerArgs);
834    let mut item_impl = parse_macro_input!(item as ItemImpl);
835
836    if item_impl.trait_.is_some() {
837        return syn::Error::new(
838            item_impl.impl_token.span(),
839            "#[event_handler] can only be used on inherent impl blocks",
840        )
841        .to_compile_error()
842        .into();
843    }
844
845    let crate_name = get_crate_name();
846    let mut context = match EventHandlerContext::new(args) {
847        Ok(ctx) => ctx,
848        Err(err) => return err.to_compile_error().into(),
849    };
850
851    for impl_item in &mut item_impl.items {
852        if let ImplItem::Fn(method) = impl_item {
853            let mut subscribe_attr = None;
854            method.attrs.retain(|attr| {
855                if attr.path().is_ident("subscribe") {
856                    subscribe_attr = Some(attr.clone());
857                    false
858                } else {
859                    true
860                }
861            });
862
863            if let Some(attr) = subscribe_attr {
864                if let Err(err) = context.register_handler(method, attr) {
865                    return err.to_compile_error().into();
866                }
867            }
868        }
869    }
870
871    if context.handlers.is_empty() {
872        return syn::Error::new(
873            item_impl.impl_token.span(),
874            "#[event_handler] requires at least one #[subscribe] method",
875        )
876        .to_compile_error()
877        .into();
878    }
879
880    match context.generate_process_fn(&crate_name) {
881        Ok(process_fn) => {
882            item_impl.items.push(ImplItem::Fn(process_fn));
883            TokenStream::from(quote! { #item_impl })
884        }
885        Err(err) => err.to_compile_error().into(),
886    }
887}
888
889#[derive(Default)]
890struct EventHandlerArgs {
891    default_state: Option<DefaultState>,
892}
893
894struct DefaultState {
895    ty: Type,
896    repr: String,
897}
898
899impl Parse for EventHandlerArgs {
900    fn parse(input: ParseStream) -> Result<Self> {
901        let mut args = EventHandlerArgs::default();
902
903        while !input.is_empty() {
904            let ident: Ident = input.parse()?;
905            input.parse::<Token![=]>()?;
906
907            match ident.to_string().as_str() {
908                "state" | "default_state" => {
909                    let ty = parse_type_value(input)?;
910                    let repr = type_to_key(&ty);
911                    args.default_state = Some(DefaultState { ty, repr });
912                }
913                "system" => {
914                    // Consume the value for validation but ignore it for now.
915                    let _ = parse_type_value(input)?;
916                }
917                other => {
918                    return Err(syn::Error::new(
919                        ident.span(),
920                        format!("Unknown event_handler attribute key `{}`", other),
921                    ));
922                }
923            }
924
925            if input.peek(Token![,]) {
926                input.parse::<Token![,]>()?;
927            }
928        }
929
930        Ok(args)
931    }
932}
933
934struct EventHandlerContext {
935    default_state: Option<DefaultState>,
936    events: Vec<EventCollection>,
937    event_lookup: HashMap<String, usize>,
938    handlers: Vec<Handler>,
939    uses_services: bool,
940}
941
942impl EventHandlerContext {
943    fn new(args: EventHandlerArgs) -> Result<Self> {
944        Ok(Self {
945            default_state: args.default_state,
946            events: Vec::new(),
947            event_lookup: HashMap::new(),
948            handlers: Vec::new(),
949            uses_services: false,
950        })
951    }
952
953    fn register_handler(
954        &mut self,
955        method: &mut ImplItemFn,
956        subscribe_attr: Attribute,
957    ) -> Result<()> {
958        if method.sig.asyncness.is_none() {
959            return Err(syn::Error::new(
960                method.sig.fn_token.span(),
961                "#[subscribe] handlers must be async",
962            ));
963        }
964
965        if method.sig.inputs.is_empty() {
966            return Err(syn::Error::new(
967                method.sig.fn_token.span(),
968                "Event handler must accept &mut self",
969            ));
970        }
971
972        let mut inputs_iter = method.sig.inputs.iter_mut();
973
974        match inputs_iter.next() {
975            Some(FnArg::Receiver(receiver)) => {
976                if receiver.mutability.is_none() {
977                    return Err(syn::Error::new(
978                        receiver.self_token.span(),
979                        "event handlers must take `&mut self`",
980                    ));
981                }
982            }
983            _ => {
984                return Err(syn::Error::new(
985                    method.sig.fn_token.span(),
986                    "event handlers must start with `&mut self`",
987                ));
988            }
989        }
990
991        let subscribe = parse_subscribe_attr(subscribe_attr)?;
992        let event_index = self.register_event(&subscribe.event_type);
993
994        let event_input = inputs_iter.next().ok_or_else(|| {
995            syn::Error::new(
996                method.sig.fn_token.span(),
997                "event handler must accept event parameter",
998            )
999        })?;
1000
1001        let event_ty = match event_input {
1002            FnArg::Typed(pat_type) => match pat_type.ty.as_ref() {
1003                Type::Reference(reference) => {
1004                    if reference.mutability.is_some() {
1005                        return Err(syn::Error::new(
1006                            reference.and_token.span(),
1007                            "event parameter must be `&EventType`",
1008                        ));
1009                    }
1010                    reference.elem.as_ref().clone()
1011                }
1012                _ => {
1013                    return Err(syn::Error::new(
1014                        pat_type.ty.span(),
1015                        "event parameter must be a reference",
1016                    ))
1017                }
1018            },
1019            _ => {
1020                return Err(syn::Error::new(
1021                    method.sig.fn_token.span(),
1022                    "event parameter must be named",
1023                ))
1024            }
1025        };
1026
1027        let requested_event = subscribe.event_type.to_token_stream().to_string();
1028        let actual_event = event_ty.to_token_stream().to_string();
1029        if requested_event != actual_event {
1030            return Err(syn::Error::new(
1031                method.sig.ident.span(),
1032                "event parameter type must match #[subscribe(...)]",
1033            ));
1034        }
1035
1036        let mut args = Vec::new();
1037        for input in inputs_iter {
1038            let pat_type = match input {
1039                FnArg::Typed(pat_type) => pat_type,
1040                FnArg::Receiver(_) => {
1041                    return Err(syn::Error::new(
1042                        input.span(),
1043                        "unexpected self parameter in handler",
1044                    ))
1045                }
1046            };
1047
1048            let arg = parse_handler_arg(pat_type, self.default_state.as_ref())?;
1049            if matches!(arg.kind, HandlerArgKind::Service { .. }) {
1050                self.uses_services = true;
1051            }
1052            args.push(arg);
1053        }
1054
1055        self.handlers.push(Handler {
1056            method_ident: method.sig.ident.clone(),
1057            event_index,
1058            filter: subscribe.filter,
1059            args,
1060        });
1061
1062        Ok(())
1063    }
1064
1065    fn register_event(&mut self, ty: &Type) -> usize {
1066        let key = type_to_key(ty);
1067        if let Some(index) = self.event_lookup.get(&key) {
1068            *index
1069        } else {
1070            let ident = format_ident!("__events_{}", sanitize_ident(&key));
1071            let index = self.events.len();
1072            self.events.push(EventCollection {
1073                ty: ty.clone(),
1074                ident,
1075            });
1076            self.event_lookup.insert(key, index);
1077            index
1078        }
1079    }
1080
1081    fn generate_process_fn(&self, crate_name: &proc_macro2::TokenStream) -> Result<ImplItemFn> {
1082        let event_bus_ty = quote! { #crate_name::event::EventBus };
1083        let resource_ctx_ty = quote! { #crate_name::context::ResourceContext };
1084        let service_ctx_ty = quote! { #crate_name::context::ServiceContext };
1085
1086        let collects = self.events.iter().map(|event| {
1087            let ident = &event.ident;
1088            let ty = &event.ty;
1089            quote! {
1090                let #ident: ::std::vec::Vec<#ty> =
1091                    event_bus.reader::<#ty>().iter().cloned().collect();
1092            }
1093        });
1094
1095        let empty_check = if self.events.is_empty() {
1096            quote! {}
1097        } else {
1098            let empties = self.events.iter().map(|event| {
1099                let ident = &event.ident;
1100                quote! { #ident.is_empty() }
1101            });
1102            quote! {
1103                if true #(&& #empties)* {
1104                    return;
1105                }
1106            }
1107        };
1108
1109        let handler_blocks = self
1110            .handlers
1111            .iter()
1112            .map(|handler| handler.expand(self.events.as_slice()));
1113
1114        let service_usage = if self.uses_services {
1115            quote! {}
1116        } else {
1117            quote! { let _ = services; }
1118        };
1119
1120        let body = quote! {
1121            let mut event_bus = match resources.get_mut::<#event_bus_ty>().await {
1122                Some(bus) => bus,
1123                None => return,
1124            };
1125
1126            #(#collects)*
1127
1128            #empty_check
1129
1130            drop(event_bus);
1131
1132            #service_usage
1133            #(#handler_blocks)*
1134        };
1135
1136        let process_fn: ImplItemFn = syn::parse_quote! {
1137            pub async fn process_events(
1138                &mut self,
1139                services: &#service_ctx_ty,
1140                resources: &mut #resource_ctx_ty,
1141            ) {
1142                #body
1143            }
1144        };
1145
1146        Ok(process_fn)
1147    }
1148}
1149
1150struct EventCollection {
1151    ty: Type,
1152    ident: Ident,
1153}
1154
1155struct Handler {
1156    method_ident: Ident,
1157    event_index: usize,
1158    filter: Option<Ident>,
1159    args: Vec<HandlerArg>,
1160}
1161
1162impl Handler {
1163    fn expand(&self, events: &[EventCollection]) -> proc_macro2::TokenStream {
1164        let event_ident = &events[self.event_index].ident;
1165        let method_ident = &self.method_ident;
1166        let filter_check = if let Some(filter) = &self.filter {
1167            quote! {
1168                if !self.#filter(event) {
1169                    continue;
1170                }
1171            }
1172        } else {
1173            quote! {}
1174        };
1175
1176        let arg_exprs: Vec<_> = self
1177            .args
1178            .iter()
1179            .map(|arg| arg.argument_expression())
1180            .collect();
1181
1182        let mut block = quote! {
1183            for event in #event_ident.iter() {
1184                #filter_check
1185                self.#method_ident(event #(, #arg_exprs)*).await;
1186            }
1187        };
1188
1189        for arg in self.args.iter().rev() {
1190            block = arg.wrap_block(block);
1191        }
1192
1193        quote! {
1194            if !#event_ident.is_empty() {
1195                #block
1196            }
1197        }
1198    }
1199}
1200
1201struct HandlerArg {
1202    ident: Ident,
1203    kind: HandlerArgKind,
1204}
1205
1206impl HandlerArg {
1207    fn argument_expression(&self) -> proc_macro2::TokenStream {
1208        let ident = &self.ident;
1209        match &self.kind {
1210            HandlerArgKind::State { mutable, .. } => {
1211                if *mutable {
1212                    quote! { &mut *#ident }
1213                } else {
1214                    quote! { &*#ident }
1215                }
1216            }
1217            HandlerArgKind::Service { .. } => quote! { #ident },
1218        }
1219    }
1220
1221    fn wrap_block(&self, block: proc_macro2::TokenStream) -> proc_macro2::TokenStream {
1222        let ident = &self.ident;
1223        match &self.kind {
1224            HandlerArgKind::State { ty, mutable } => {
1225                if *mutable {
1226                    quote! {
1227                        if let Some(mut #ident) = resources.get_mut::<#ty>().await {
1228                            #block
1229                        }
1230                    }
1231                } else {
1232                    quote! {
1233                        if let Some(#ident) = resources.get::<#ty>().await {
1234                            #block
1235                        }
1236                    }
1237                }
1238            }
1239            HandlerArgKind::Service { ty, service_name } => {
1240                let name = service_name.as_str();
1241                quote! {
1242                    if let Some(#ident) = services.get_as::<#ty>(#name) {
1243                        #block
1244                    }
1245                }
1246            }
1247        }
1248    }
1249}
1250
1251enum HandlerArgKind {
1252    State { ty: Type, mutable: bool },
1253    Service { ty: Type, service_name: String },
1254}
1255
1256struct SubscribeAttr {
1257    event_type: Type,
1258    filter: Option<Ident>,
1259}
1260
1261fn parse_subscribe_attr(attr: Attribute) -> Result<SubscribeAttr> {
1262    attr.parse_args_with(|input: ParseStream| {
1263        let event_type: Type = input.parse()?;
1264        let mut filter = None;
1265
1266        while input.peek(Token![,]) {
1267            input.parse::<Token![,]>()?;
1268            let key: Ident = input.parse()?;
1269            if key == "filter" {
1270                input.parse::<Token![=]>()?;
1271                let lit: LitStr = input.parse()?;
1272                let ident = Ident::new(&lit.value(), lit.span());
1273                filter = Some(ident);
1274            } else {
1275                return Err(syn::Error::new(key.span(), "unknown #[subscribe] option"));
1276            }
1277        }
1278
1279        Ok(SubscribeAttr { event_type, filter })
1280    })
1281}
1282
1283fn parse_handler_arg(
1284    pat_type: &mut PatType,
1285    default_state: Option<&DefaultState>,
1286) -> Result<HandlerArg> {
1287    let ident = extract_ident(&pat_type.pat)?;
1288    let mut kind = None;
1289    let attrs = mem::take(&mut pat_type.attrs);
1290
1291    for attr in attrs {
1292        if attr.path().is_ident("state") {
1293            if !matches!(attr.meta, Meta::Path(_)) {
1294                return Err(syn::Error::new(
1295                    attr.span(),
1296                    "#[state] does not take arguments",
1297                ));
1298            }
1299            kind = Some(create_state_arg(&pat_type.ty, attr.span())?);
1300        } else if attr.path().is_ident("service") {
1301            let service_name = parse_service_attr(&attr)?;
1302            kind = Some(create_service_arg(&pat_type.ty, service_name, attr.span())?);
1303        } else {
1304            pat_type.attrs.push(attr);
1305        }
1306    }
1307
1308    if let Some(kind) = kind {
1309        return Ok(HandlerArg { ident, kind });
1310    }
1311
1312    if let Some(default_state) = default_state {
1313        if state_matches(&pat_type.ty, default_state)? {
1314            return Ok(HandlerArg {
1315                ident,
1316                kind: HandlerArgKind::State {
1317                    ty: default_state.ty.clone(),
1318                    mutable: true,
1319                },
1320            });
1321        }
1322    }
1323
1324    Err(syn::Error::new(
1325        pat_type.ty.span(),
1326        "additional parameters must be marked with #[state] or #[service]",
1327    ))
1328}
1329
1330fn create_state_arg(ty: &Type, span: Span) -> Result<HandlerArgKind> {
1331    if let Type::Reference(reference) = ty {
1332        if reference.mutability.is_none() {
1333            return Err(syn::Error::new(span, "state parameters must be `&mut T`"));
1334        }
1335        Ok(HandlerArgKind::State {
1336            ty: reference.elem.as_ref().clone(),
1337            mutable: true,
1338        })
1339    } else {
1340        Err(syn::Error::new(span, "state parameters must be references"))
1341    }
1342}
1343
1344fn create_service_arg(ty: &Type, service_name: String, span: Span) -> Result<HandlerArgKind> {
1345    if let Type::Reference(reference) = ty {
1346        if reference.mutability.is_some() {
1347            return Err(syn::Error::new(
1348                span,
1349                "services must be borrowed immutably as `&T`",
1350            ));
1351        }
1352        Ok(HandlerArgKind::Service {
1353            ty: reference.elem.as_ref().clone(),
1354            service_name,
1355        })
1356    } else {
1357        Err(syn::Error::new(
1358            span,
1359            "service parameters must be references",
1360        ))
1361    }
1362}
1363
1364fn state_matches(param_ty: &Type, default: &DefaultState) -> Result<bool> {
1365    if let Type::Reference(reference) = param_ty {
1366        if reference.mutability.is_none() {
1367            return Err(syn::Error::new(
1368                reference.and_token.span(),
1369                "default state parameter must be `&mut` reference",
1370            ));
1371        }
1372        let repr = type_to_key(reference.elem.as_ref());
1373        Ok(repr == default.repr)
1374    } else {
1375        Err(syn::Error::new(
1376            param_ty.span(),
1377            "default state parameter must be a reference",
1378        ))
1379    }
1380}
1381
1382fn extract_ident(pat: &Pat) -> Result<Ident> {
1383    if let Pat::Ident(PatIdent { ident, .. }) = pat {
1384        Ok(ident.clone())
1385    } else {
1386        Err(syn::Error::new(
1387            pat.span(),
1388            "parameters must be simple identifiers",
1389        ))
1390    }
1391}
1392
1393fn parse_service_attr(attr: &Attribute) -> Result<String> {
1394    if matches!(attr.meta, Meta::Path(_)) {
1395        return Err(syn::Error::new(
1396            attr.span(),
1397            "#[service] requires a `name = \"...\"` argument",
1398        ));
1399    }
1400
1401    attr.parse_args_with(|input: ParseStream| {
1402        if input.peek(LitStr) {
1403            Ok(input.parse::<LitStr>()?.value())
1404        } else {
1405            let ident: Ident = input.parse()?;
1406            if ident == "name" {
1407                input.parse::<Token![=]>()?;
1408                Ok(input.parse::<LitStr>()?.value())
1409            } else {
1410                Err(syn::Error::new(
1411                    ident.span(),
1412                    "expected `name = \"...\"` for #[service]",
1413                ))
1414            }
1415        }
1416    })
1417}
1418
1419fn parse_type_value(input: ParseStream) -> Result<Type> {
1420    if input.peek(LitStr) {
1421        let lit: LitStr = input.parse()?;
1422        lit.parse()
1423    } else {
1424        input.parse()
1425    }
1426}
1427
1428fn type_to_key(ty: &Type) -> String {
1429    ty.to_token_stream().to_string().replace(' ', "")
1430}
1431
1432fn sanitize_ident(raw: &str) -> String {
1433    raw.chars()
1434        .map(|ch| match ch {
1435            '<' | '>' | ':' | ',' | '&' | '*' | '(' | ')' | '[' | ']' | '{' | '}' => '_',
1436            other if other.is_whitespace() => '_',
1437            other => other,
1438        })
1439        .collect()
1440}
1441
1442/// Function-like macro that declares ISSUN events with common derives.
1443///
1444/// Generates a struct definition for each event along with the required derives
1445/// and an implementation of [`issun::event::Event`].
1446#[proc_macro]
1447pub fn event(input: TokenStream) -> TokenStream {
1448    let input = parse_macro_input!(input as EventMacroInput);
1449    let crate_name = get_crate_name();
1450
1451    let expanded = input
1452        .events
1453        .into_iter()
1454        .map(|event| event.expand(&crate_name));
1455
1456    TokenStream::from(quote! {
1457        #(#expanded)*
1458    })
1459}
1460
1461struct EventMacroInput {
1462    events: Vec<EventDefinition>,
1463}
1464
1465impl Parse for EventMacroInput {
1466    fn parse(input: ParseStream) -> Result<Self> {
1467        let mut events = Vec::new();
1468        while !input.is_empty() {
1469            events.push(input.parse()?);
1470
1471            if input.peek(Token![,]) {
1472                input.parse::<Token![,]>()?;
1473            }
1474        }
1475        Ok(Self { events })
1476    }
1477}
1478
1479struct EventDefinition {
1480    attrs: Vec<Attribute>,
1481    additional_derives: Vec<Path>,
1482    visibility: Visibility,
1483    name: Ident,
1484    fields: EventFields,
1485}
1486
1487impl EventDefinition {
1488    fn expand(self, crate_name: &proc_macro2::TokenStream) -> proc_macro2::TokenStream {
1489        let mut derives = vec![
1490            syn::parse_quote!(Debug),
1491            syn::parse_quote!(Clone),
1492            syn::parse_quote!(::serde::Serialize),
1493            syn::parse_quote!(::serde::Deserialize),
1494        ];
1495
1496        for path in self.additional_derives {
1497            push_unique_path(&mut derives, path);
1498        }
1499
1500        let derive_attr = quote! {
1501            #[derive(#(#derives),*)]
1502        };
1503
1504        let body = match self.fields {
1505            EventFields::Unit => quote!(;),
1506            EventFields::Struct(fields) => {
1507                let rendered_fields = fields.into_iter().map(|field| {
1508                    let attrs = field.attrs;
1509                    let vis = field.visibility;
1510                    let name = field.name;
1511                    let ty = field.ty;
1512                    quote! {
1513                        #(#attrs)*
1514                        #vis #name: #ty
1515                    }
1516                });
1517
1518                quote! {
1519                    {
1520                        #(#rendered_fields,)*
1521                    }
1522                }
1523            }
1524        };
1525
1526        let attrs = self.attrs;
1527        let vis = self.visibility;
1528        let name = self.name;
1529
1530        quote! {
1531            #(#attrs)*
1532            #derive_attr
1533            #vis struct #name #body
1534
1535            impl #crate_name::event::Event for #name {}
1536        }
1537    }
1538}
1539
1540impl Parse for EventDefinition {
1541    fn parse(input: ParseStream) -> Result<Self> {
1542        let outer_attrs = input.call(Attribute::parse_outer)?;
1543        let mut attrs = Vec::new();
1544        let mut derives = Vec::new();
1545
1546        for attr in outer_attrs {
1547            if attr.path().is_ident("derive") {
1548                let parsed: Punctuated<Path, Token![,]> =
1549                    attr.parse_args_with(Punctuated::parse_terminated)?;
1550                derives.extend(parsed.into_iter());
1551            } else {
1552                attrs.push(attr);
1553            }
1554        }
1555
1556        let visibility = if input.peek(Token![pub]) {
1557            input.parse()?
1558        } else {
1559            Visibility::Inherited
1560        };
1561
1562        if input.peek(Token![struct]) {
1563            input.parse::<Token![struct]>()?;
1564        }
1565
1566        let name: Ident = input.parse()?;
1567
1568        let fields = if input.peek(Token![;]) {
1569            input.parse::<Token![;]>()?;
1570            EventFields::Unit
1571        } else {
1572            let content;
1573            braced!(content in input);
1574
1575            let mut parsed_fields = Vec::new();
1576            while !content.is_empty() {
1577                let attrs = content.call(Attribute::parse_outer)?;
1578                if content.is_empty() {
1579                    return Err(content.error("expected field definition after attributes"));
1580                }
1581
1582                let visibility = if content.peek(Token![pub]) {
1583                    content.parse()?
1584                } else {
1585                    Visibility::Inherited
1586                };
1587                let name: Ident = content.parse()?;
1588                content.parse::<Token![:]>()?;
1589                let ty: Type = content.parse()?;
1590
1591                if content.peek(Token![,]) {
1592                    content.parse::<Token![,]>()?;
1593                } else if content.peek(Token![;]) {
1594                    content.parse::<Token![;]>()?;
1595                } else if !content.is_empty() {
1596                    return Err(content.error("expected `,` or `;` after field"));
1597                }
1598
1599                parsed_fields.push(EventField {
1600                    attrs,
1601                    visibility,
1602                    name,
1603                    ty,
1604                });
1605            }
1606
1607            EventFields::Struct(parsed_fields)
1608        };
1609
1610        if input.peek(Token![;]) {
1611            input.parse::<Token![;]>()?;
1612        }
1613
1614        Ok(Self {
1615            attrs,
1616            additional_derives: derives,
1617            visibility,
1618            name,
1619            fields,
1620        })
1621    }
1622}
1623
1624enum EventFields {
1625    Unit,
1626    Struct(Vec<EventField>),
1627}
1628
1629struct EventField {
1630    attrs: Vec<Attribute>,
1631    visibility: Visibility,
1632    name: Ident,
1633    ty: Type,
1634}
1635
1636fn push_unique_path(paths: &mut Vec<Path>, new_path: Path) {
1637    let repr = path_to_string(&new_path);
1638    if paths
1639        .iter()
1640        .all(|existing| path_to_string(existing) != repr)
1641    {
1642        paths.push(new_path);
1643    }
1644}
1645
1646fn path_to_string(path: &Path) -> String {
1647    path.to_token_stream().to_string()
1648}
1649
1650// =============================================================================
1651// Bevy-specific macros
1652// =============================================================================
1653
1654/// Derive macro for entity component getters (Bevy)
1655///
1656/// Works with any Resource that holds an Entity field.
1657///
1658/// # Basic Example (defaults to ActionPoints)
1659/// ```ignore
1660/// #[derive(Resource, IssunEntity)]
1661/// pub struct Player {
1662///     #[primary]
1663///     pub entity: Entity,
1664/// }
1665/// ```
1666///
1667/// # Example with custom components
1668/// ```ignore
1669/// #[derive(Resource, IssunEntity)]
1670/// #[components(ActionPoints, Health, ContagionInfection)]
1671/// pub struct Player {
1672///     #[primary]
1673///     pub entity: Entity,
1674/// }
1675/// ```
1676///
1677/// This generates getter methods like `action_points()`, `health()`, etc.
1678#[proc_macro_derive(IssunEntity, attributes(primary, components))]
1679pub fn derive_issun_entity(input: TokenStream) -> TokenStream {
1680    bevy::derive_issun_entity_impl(input)
1681}
1682
1683/// Derive macro for Query helpers (Bevy)
1684///
1685/// # Example
1686/// ```ignore
1687/// #[derive(IssunQuery)]
1688/// #[query(read = [ContagionInfection, ContagionNode])]
1689/// pub struct InfectionQuery;
1690/// ```
1691#[proc_macro_derive(IssunQuery, attributes(query))]
1692pub fn derive_issun_query(input: TokenStream) -> TokenStream {
1693    bevy::derive_issun_query_impl(input)
1694}
1695
1696/// Simplified EventLog macro (Bevy)
1697///
1698/// # Example
1699/// ```ignore
1700/// log!(app, "✅ {} quarantined", city.name);
1701/// ```
1702#[proc_macro]
1703pub fn log(input: TokenStream) -> TokenStream {
1704    bevy::log_impl(input)
1705}
1706
1707/// Derive macro for Bevy Plugin boilerplate generation
1708///
1709/// Auto-generates Plugin implementation with resource registration and builder methods.
1710///
1711/// # Requirements
1712///
1713/// **All fields marked with `#[resource]` or `#[config]` must implement `Clone`.**
1714///
1715/// This is because `Plugin::build(&self, app)` takes `&self`, so resources must be cloned.
1716///
1717/// # Example
1718/// ```ignore
1719/// use bevy::prelude::*;
1720/// use issun_macros::IssunBevyPlugin;
1721///
1722/// #[derive(Resource, Clone, Default)]
1723/// pub struct MyConfig { pub difficulty: f32 }
1724///
1725/// #[derive(Resource, Clone, Default)]
1726/// pub struct GameStats { pub score: u32 }
1727///
1728/// #[derive(Default, IssunBevyPlugin)]
1729/// #[plugin(name = "my_plugin")]
1730/// pub struct MyPlugin {
1731///     #[config]
1732///     pub config: MyConfig,
1733///
1734///     #[resource]
1735///     pub stats: GameStats,
1736/// }
1737///
1738/// // Usage
1739/// App::new()
1740///     .add_plugins(MyPlugin::default()
1741///         .with_config(MyConfig { difficulty: 2.0 })
1742///     );
1743/// ```
1744///
1745/// # Attributes
1746///
1747/// ## Struct-level
1748/// - `#[plugin(name = "...")]` - Custom plugin name
1749/// - `#[plugin(auto_register_types = true)]` - Auto-register types for Reflection
1750/// - `#[plugin(messages = [Type1, Type2, ...])]` - Auto-register messages
1751/// - `#[plugin(components = [Type1, Type2, ...])]` - Auto-register component types (Phase 2.2)
1752/// - `#[plugin(startup_systems = [fn1, fn2, ...])]` - Auto-register Startup systems (Phase 2.2)
1753/// - `#[plugin(update_systems = [fn1, fn2, ...])]` - Auto-register Update systems (Phase 2.2)
1754/// - `#[plugin(requires = [Plugin1, Plugin2, ...])]` - Declare issun-bevy plugin dependencies (Phase 2.3)
1755/// - `#[plugin(requires_bevy = [BevyPlugin1, ...])]` - Declare Bevy standard plugin dependencies (Phase 2.3)
1756/// - `#[plugin(auto_require_core = true)]` - Auto-require IssunCorePlugin (default: true, Phase 2.3)
1757///
1758/// ## Field-level
1759/// - `#[config]` - Config resource (insert_resource + builder method)
1760/// - `#[resource]` - Resource (insert_resource + builder method)
1761/// - `#[skip]` - Skip this field
1762///
1763/// # Generated Code
1764///
1765/// - `Plugin::build()` implementation with resource registration
1766/// - Builder methods: `with_config()`, `with_stats()`
1767/// - Type registration (if `auto_register_types = true`)
1768#[proc_macro_derive(IssunBevyPlugin, attributes(plugin, config, resource, skip))]
1769pub fn derive_issun_bevy_plugin(input: TokenStream) -> TokenStream {
1770    bevy::derive_issun_bevy_plugin_impl(input)
1771}
1772
1773/// Attribute macro that injects `pump_event_systems` calls before/after input handlers.
1774#[proc_macro_attribute]
1775pub fn auto_pump(attr: TokenStream, item: TokenStream) -> TokenStream {
1776    let args = parse_macro_input!(attr as AutoPumpArgs);
1777
1778    let item_clone = item.clone();
1779    if let Ok(mut function) = syn::parse::<ItemFn>(item_clone) {
1780        match apply_auto_pump(&function.sig, &mut function.block, &args) {
1781            Ok(()) => return TokenStream::from(quote! { #function }),
1782            Err(err) => return err.to_compile_error().into(),
1783        }
1784    }
1785
1786    let mut method = parse_macro_input!(item as ImplItemFn);
1787    match apply_auto_pump(&method.sig, &mut method.block, &args) {
1788        Ok(()) => TokenStream::from(quote! { #method }),
1789        Err(err) => err.to_compile_error().into(),
1790    }
1791}
1792
1793#[derive(Clone)]
1794struct AutoPumpArgs {
1795    before: bool,
1796    after: bool,
1797    pump_fn: Option<Path>,
1798    sides_specified: bool,
1799}
1800
1801impl Default for AutoPumpArgs {
1802    fn default() -> Self {
1803        Self {
1804            before: true,
1805            after: true,
1806            pump_fn: None,
1807            sides_specified: false,
1808        }
1809    }
1810}
1811
1812impl Parse for AutoPumpArgs {
1813    fn parse(input: ParseStream) -> Result<Self> {
1814        if input.is_empty() {
1815            return Ok(Self::default());
1816        }
1817
1818        let mut args = AutoPumpArgs {
1819            before: false,
1820            after: false,
1821            pump_fn: None,
1822            sides_specified: false,
1823        };
1824
1825        while !input.is_empty() {
1826            let ident: Ident = input.parse()?;
1827            match ident.to_string().as_str() {
1828                "before" => {
1829                    args.before = true;
1830                    args.sides_specified = true;
1831                }
1832                "after" => {
1833                    args.after = true;
1834                    args.sides_specified = true;
1835                }
1836                "pump_fn" => {
1837                    input.parse::<Token![=]>()?;
1838                    let path = if input.peek(LitStr) {
1839                        input.parse::<LitStr>()?.parse::<Path>()?
1840                    } else {
1841                        input.parse::<Path>()?
1842                    };
1843                    args.pump_fn = Some(path);
1844                }
1845                other => {
1846                    return Err(syn::Error::new(
1847                        ident.span(),
1848                        format!("Unknown auto_pump option `{}`", other),
1849                    ));
1850                }
1851            }
1852
1853            if input.peek(Token![,]) {
1854                input.parse::<Token![,]>()?;
1855            }
1856        }
1857
1858        if !args.sides_specified {
1859            args.before = true;
1860            args.after = true;
1861        }
1862
1863        Ok(args)
1864    }
1865}
1866
1867struct PumpParams {
1868    services: Ident,
1869    systems: Ident,
1870    resources: Ident,
1871}
1872
1873fn apply_auto_pump(signature: &Signature, block: &mut Block, args: &AutoPumpArgs) -> Result<()> {
1874    if !args.before && !args.after {
1875        return Ok(());
1876    }
1877
1878    let params = extract_pump_params(signature)?;
1879    let pump_path = args.pump_fn.clone().unwrap_or_else(|| {
1880        syn::parse_str::<Path>("crate::plugins::pump_event_systems").expect("valid path")
1881    });
1882
1883    let mut original = mem::take(&mut block.stmts);
1884    let mut stmts = Vec::new();
1885
1886    // Add 'before' pump call
1887    if args.before {
1888        stmts.push(build_pump_stmt(&pump_path, &params));
1889    }
1890
1891    // Check if the last statement is a trailing expression (no semicolon)
1892    // If so, we need to insert 'after' pump BEFORE it to preserve return value
1893    let has_trailing_expr = original
1894        .last()
1895        .is_some_and(|stmt| matches!(stmt, Stmt::Expr(_, None)));
1896
1897    if args.after && has_trailing_expr {
1898        // Insert all but last statement
1899        if original.len() > 1 {
1900            stmts.extend(original.drain(..original.len() - 1));
1901        }
1902        // Insert 'after' pump
1903        stmts.push(build_pump_stmt(&pump_path, &params));
1904        // Insert trailing expression last
1905        stmts.extend(original);
1906    } else {
1907        // No trailing expression, just append everything
1908        stmts.extend(original);
1909        if args.after {
1910            stmts.push(build_pump_stmt(&pump_path, &params));
1911        }
1912    }
1913
1914    block.stmts = stmts;
1915    Ok(())
1916}
1917
1918fn build_pump_stmt(path: &Path, params: &PumpParams) -> Stmt {
1919    let services = &params.services;
1920    let systems = &params.systems;
1921    let resources = &params.resources;
1922
1923    // Use quote! to generate tokens, then parse into Stmt
1924    let tokens = quote! {
1925        #path(#services, #systems, #resources).await;
1926    };
1927    syn::parse2(tokens).expect("failed to parse pump statement")
1928}
1929
1930fn extract_pump_params(signature: &Signature) -> Result<PumpParams> {
1931    let mut services = None;
1932    let mut systems = None;
1933    let mut resources = None;
1934
1935    for input in signature.inputs.iter() {
1936        if let FnArg::Typed(pat_type) = input {
1937            if let Ok(ident) = extract_ident(&pat_type.pat) {
1938                if matches_context(&pat_type.ty, "ServiceContext") {
1939                    services = Some(ident);
1940                } else if matches_context(&pat_type.ty, "SystemContext") {
1941                    systems = Some(ident);
1942                } else if matches_context(&pat_type.ty, "ResourceContext") {
1943                    resources = Some(ident);
1944                }
1945            }
1946        }
1947    }
1948
1949    match (services, systems, resources) {
1950        (Some(sv), Some(sys), Some(res)) => Ok(PumpParams {
1951            services: sv,
1952            systems: sys,
1953            resources: res,
1954        }),
1955        _ => Err(syn::Error::new(
1956            signature.fn_token.span(),
1957            "#[auto_pump] requires parameters for ServiceContext, SystemContext, and ResourceContext",
1958        )),
1959    }
1960}
1961
1962fn matches_context(ty: &Type, expected: &str) -> bool {
1963    match ty {
1964        Type::Reference(reference) => match reference.elem.as_ref() {
1965            Type::Path(path) => path
1966                .path
1967                .segments
1968                .last()
1969                .map(|segment| segment.ident == expected)
1970                .unwrap_or(false),
1971            _ => false,
1972        },
1973        Type::Path(path) => path
1974            .path
1975            .segments
1976            .last()
1977            .map(|segment| segment.ident == expected)
1978            .unwrap_or(false),
1979        _ => false,
1980    }
1981}