bevy_auto_plugin_nightly_shared/
lib.rs

1#![cfg_attr(feature = "nightly_proc_macro_span", feature(proc_macro_span))]
2use bevy_auto_plugin_shared::AutoPluginContext;
3use bevy_auto_plugin_shared::util::{Target, path_to_string};
4use quote::quote;
5use std::cell::RefCell;
6use std::collections::HashMap;
7use syn::Path;
8use thiserror::Error;
9
10thread_local! {
11    static FILE_STATE_MAP: RefCell<HashMap<String, FileState>> = RefCell::new(HashMap::new());
12}
13
14// TODO: is there a better way? this originally was using Path instead of String
15//  but apparently static references to Path creates "use after free" errors
16#[derive(Default)]
17pub struct FileState {
18    pub plugin_registered: bool,
19    pub context: AutoPluginContext,
20}
21
22pub fn get_file_path() -> String {
23    {
24        #[cfg(feature = "nightly_proc_macro_span")]
25        {
26            #[cfg(all(feature = "nightly_pre_2025_04_16", not(feature = "nightly")))]
27            {
28                use proc_macro2::Span;
29                Span::call_site()
30                    .unwrap()
31                    .source_file()
32                    .path()
33                    .display()
34                    .to_string()
35            }
36
37            #[cfg(all(
38                feature = "nightly",
39                any(not(feature = "nightly_pre_2025_04_16"), feature = "_all")
40            ))]
41            {
42                use proc_macro2::Span;
43                Span::call_site()
44                    .unwrap()
45                    .local_file()
46                    .expect("failed to resolve local file from span")
47                    .display()
48                    .to_string()
49            }
50        }
51        #[cfg(all(
52            feature = "nightly",
53            feature = "nightly_pre_2025_04_16",
54            not(feature = "_all")
55        ))]
56        {
57            panic!("nightly_pre_2025_04_16 feature is not compatible with nightly");
58        }
59        #[cfg(not(feature = "nightly_proc_macro_span"))]
60        {
61            panic!("proc_macro_span feature is required for this crate");
62        }
63    }
64}
65
66pub fn update_file_state<R>(file_path: String, update_fn: impl FnOnce(&mut FileState) -> R) -> R {
67    FILE_STATE_MAP.with(|map| {
68        let mut map = map.borrow_mut();
69        let file_state = map.entry(file_path).or_default();
70        update_fn(file_state)
71    })
72}
73
74pub fn update_state(
75    file_path: String,
76    path: Path,
77    target: Target,
78) -> std::result::Result<(), UpdateStateError> {
79    FILE_STATE_MAP.with(|map| {
80        let mut map = map.borrow_mut();
81        let entry = map.entry(file_path).or_default();
82        if entry.plugin_registered {
83            return Err(UpdateStateError::PluginAlreadyRegistered);
84        }
85        let path = path_to_string(&path, false);
86        let inserted = match target {
87            Target::RegisterTypes => entry.context.register_types.insert(path),
88            Target::RegisterStateTypes => entry.context.register_state_types.insert(path),
89            Target::AddEvents => entry.context.add_events.insert(path),
90            Target::InitResources => entry.context.init_resources.insert(path),
91            Target::InitStates => entry.context.init_states.insert(path),
92            Target::RequiredComponentAutoName => entry.context.auto_names.insert(path),
93        };
94        if !inserted {
95            return Err(UpdateStateError::Duplicate);
96        }
97        Ok(())
98    })
99}
100
101fn get_files_missing_plugin() -> Vec<String> {
102    FILE_STATE_MAP.with(|map| {
103        let map = map.borrow();
104        let mut files_missing_plugin = Vec::new();
105        for (file_path, file_state) in map.iter() {
106            if file_state.plugin_registered {
107                continue;
108            }
109            files_missing_plugin.push(file_path.clone());
110        }
111        files_missing_plugin
112    })
113}
114
115pub fn files_missing_plugin_ts() -> proc_macro2::TokenStream {
116    #[allow(unused_mut)]
117    let mut output = quote! {};
118    let missing_plugin_files = get_files_missing_plugin();
119    if !missing_plugin_files.is_empty() {
120        #[allow(unused_variables)]
121        let messages = missing_plugin_files
122            .into_iter()
123            .map(|file_path| format!("missing #[auto_plugin(...)] attribute in file: {file_path}"))
124            .collect::<Vec<_>>();
125        #[cfg(feature = "missing_auto_plugin_is_error")]
126        {
127            output.extend(messages.iter().map(|message| {
128                quote! {
129                    log::error!(#message);
130                }
131            }));
132        }
133        #[cfg(feature = "missing_auto_plugin_is_warning")]
134        {
135            output.extend(messages.iter().map(|message| {
136                quote! {
137                    log::warn!(#message);
138                }
139            }));
140        }
141        #[cfg(feature = "missing_auto_plugin_is_compile_error")]
142        return syn::Error::new(Span::call_site(), messages.join("\n")).to_compile_error();
143    }
144    output
145}
146
147#[derive(Error, Debug)]
148pub enum UpdateStateError {
149    #[error("duplicate attribute")]
150    Duplicate,
151    #[error("plugin already registered above, move plugin fn to the bottom of the file")]
152    PluginAlreadyRegistered,
153}