ambient_package_macro_common/
lib.rs1extern 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 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}