ambient_package_macro_common/
lib.rs

1extern crate proc_macro;
2
3pub use ambient_package_semantic::RetrievableFile;
4use ambient_package_semantic::{Item, ItemId, ItemMap, Package, Scope, Semantic, Type, TypeInner};
5use proc_macro2::TokenStream;
6use quote::{quote, ToTokens};
7use std::collections::HashMap;
8
9mod assets;
10mod components;
11mod concepts;
12mod context;
13mod enums;
14mod messages;
15
16pub use context::Context;
17
18pub async fn generate_code(
19    manifest: Option<RetrievableFile>,
20    context: context::Context,
21) -> anyhow::Result<TokenStream> {
22    let mut semantic = Semantic::new(false).await?;
23
24    let package_id = if let Some(manifest) = manifest {
25        Some(semantic.add_package(manifest, None).await?)
26    } else {
27        None
28    };
29
30    semantic.resolve_all()?;
31
32    let items = &semantic.items;
33    let root_scope = semantic.root_scope();
34    let type_printer = {
35        let mut map = HashMap::new();
36        for type_id in root_scope.types.values() {
37            let type_ = items.get(*type_id);
38            if let TypeInner::Primitive(pt) = type_.inner {
39                let ty_tokens = syn::parse_str::<syn::Type>(&pt.to_string())?.to_token_stream();
40                map.insert(*type_id, ty_tokens.clone());
41                map.insert(items.get_vec_id(*type_id), quote! {Vec::<#ty_tokens>});
42                map.insert(items.get_option_id(*type_id), quote! {Option::<#ty_tokens>});
43            }
44        }
45        TypePrinter(map)
46    };
47
48    let outputs = semantic
49        .packages
50        .values()
51        .map(|id| generate_package(context, items, &type_printer, *id))
52        .collect::<Result<Vec<_>, _>>()?;
53
54    let imports = if let Some(package_id) = package_id {
55        let package = items.get(package_id);
56
57        let dependencies = std::iter::once(anyhow::Ok(("this", package.data.id.to_string())))
58            .chain(package.dependencies.iter().map(|(alias, dependency)| {
59                let dependency = items.get(dependency.id);
60                anyhow::Ok((alias.as_str(), dependency.data.id.to_string()))
61            }))
62            .collect::<Result<Vec<_>, _>>()?;
63
64        let dependencies = dependencies.into_iter().map(|(alias, raw)| {
65            let alias = make_path(alias);
66            let raw = make_path(&raw);
67            quote! {
68                pub use raw::#raw as #alias;
69            }
70        });
71
72        Some(quote! {
73            #(#dependencies)*
74        })
75    } else {
76        None
77    };
78
79    let output = quote! {
80        #[allow(unused, clippy::unit_arg, clippy::let_and_return, clippy::approx_constant, clippy::unused_unit)]
81        mod raw {
82            #(#outputs)*
83        }
84
85        #imports
86    };
87
88    let output = if context == Context::GuestUser {
89        // In guest code, we wrap all generated output in an `packages` module to avoid polluting their
90        // global scope.
91        quote! {
92            pub mod packages {
93                #output
94            }
95        }
96    } else {
97        output
98    };
99
100    Ok(output)
101}
102
103fn generate_package(
104    context: context::Context,
105    items: &ItemMap,
106    type_printer: &TypePrinter,
107    package_id: ItemId<Package>,
108) -> anyhow::Result<TokenStream> {
109    let package = items.get(package_id);
110    let generate_from_scope = items.get(package.scope_id);
111
112    let entity = match context {
113        Context::GuestUser => {
114            let package_ambient_id = package.data().id.to_string();
115            quote! {
116                pub fn entity() -> ambient_api::global::EntityId {
117                    use ambient_api::once_cell::sync::Lazy;
118                    static ENTITY: Lazy<ambient_api::global::EntityId> = Lazy::new(|| {
119                        ambient_api::package::get_entity_for_package_id(#package_ambient_id).expect("Failed to get package entity - was it despawned?")
120                    });
121                    *ENTITY
122                }
123            }
124        }
125        _ => TokenStream::new(),
126    };
127    let generated_output = generate_scope(context, items, type_printer, generate_from_scope)?;
128    let assets = assets::generate(context, items, generate_from_scope)?;
129    let components_init = components::generate_init(context, items, generate_from_scope)?;
130
131    let id = make_path(package.data.id.as_str());
132    anyhow::Ok(quote! {
133        pub mod #id {
134            #entity
135            #generated_output
136            #assets
137            #components_init
138        }
139    })
140}
141
142fn generate_scope(
143    context: context::Context,
144    items: &ItemMap,
145    type_printer: &TypePrinter,
146    scope: &Scope,
147) -> anyhow::Result<TokenStream> {
148    let scopes = scope
149        .scopes
150        .values()
151        .map(|s| {
152            let scope = items.get(*s);
153            if !context.should_generate(scope.data()) {
154                return Ok(quote! {});
155            }
156
157            let id = make_path(scope.data.id.as_str());
158            let inner = generate_scope(context, items, type_printer, scope)?;
159            if inner.is_empty() {
160                return Ok(quote! {});
161            }
162
163            Ok(quote! {
164                pub mod #id {
165                    #inner
166                }
167            })
168        })
169        .collect::<anyhow::Result<Vec<_>>>()?;
170
171    let components = components::generate(context, items, type_printer, scope)?;
172    let concepts = concepts::generate(context, items, type_printer, scope)?;
173    let messages = messages::generate(context, items, type_printer, scope)?;
174    let types = enums::generate(context, items, scope)?;
175
176    Ok(quote! {
177        #(#scopes)*
178
179        #components
180        #concepts
181        #messages
182        #types
183    })
184}
185
186fn make_path(id: &str) -> syn::Path {
187    syn::parse_str(id).unwrap()
188}
189
190pub struct TypePrinter(HashMap<ItemId<Type>, TokenStream>);
191impl TypePrinter {
192    pub fn get(
193        &self,
194        context: context::Context,
195        items: &ItemMap,
196        prefix: Option<&str>,
197        id: ItemId<Type>,
198    ) -> anyhow::Result<TokenStream> {
199        match self.0.get(&id) {
200            Some(ts) => Ok(ts.clone()),
201            None => context.get_path(items, prefix, id),
202        }
203    }
204}