derive_asset_container/
lib.rs

1//! This crate provides a derive macro for the AssetManager trait.
2//!
3//!
4//! # Example
5//!
6//! ```rust
7//! use derive_asset_container::AssetManager;
8//!
9//! #[derive(Clone, AssetManager)]
10//! #[asset(asset(TestAsset))]
11//! struct Struct {
12//!   field: TestAsset,
13//!   inner: InnerStruct,
14//! }
15//!
16//! #[derive(Clone, AssetManager)]
17//! #[asset(asset(TestAsset), lazy)]
18//! struct InnerStruct {
19//!   field: TestAsset,
20//! }
21//! ```
22
23// !!START_LINTS
24// Wick lints
25// Do not change anything between the START_LINTS and END_LINTS line.
26// This is automatically generated. Add exceptions after this section.
27#![allow(unknown_lints)]
28#![deny(
29  clippy::await_holding_lock,
30  clippy::borrow_as_ptr,
31  clippy::branches_sharing_code,
32  clippy::cast_lossless,
33  clippy::clippy::collection_is_never_read,
34  clippy::cloned_instead_of_copied,
35  clippy::cognitive_complexity,
36  clippy::create_dir,
37  clippy::deref_by_slicing,
38  clippy::derivable_impls,
39  clippy::derive_partial_eq_without_eq,
40  clippy::equatable_if_let,
41  clippy::exhaustive_structs,
42  clippy::expect_used,
43  clippy::expl_impl_clone_on_copy,
44  clippy::explicit_deref_methods,
45  clippy::explicit_into_iter_loop,
46  clippy::explicit_iter_loop,
47  clippy::filetype_is_file,
48  clippy::flat_map_option,
49  clippy::format_push_string,
50  clippy::fn_params_excessive_bools,
51  clippy::future_not_send,
52  clippy::get_unwrap,
53  clippy::implicit_clone,
54  clippy::if_then_some_else_none,
55  clippy::impl_trait_in_params,
56  clippy::implicit_clone,
57  clippy::inefficient_to_string,
58  clippy::inherent_to_string,
59  clippy::iter_not_returning_iterator,
60  clippy::large_types_passed_by_value,
61  clippy::large_include_file,
62  clippy::let_and_return,
63  clippy::manual_assert,
64  clippy::manual_ok_or,
65  clippy::manual_split_once,
66  clippy::manual_let_else,
67  clippy::manual_string_new,
68  clippy::map_flatten,
69  clippy::map_unwrap_or,
70  clippy::missing_enforced_import_renames,
71  clippy::missing_assert_message,
72  clippy::missing_const_for_fn,
73  clippy::must_use_candidate,
74  clippy::mut_mut,
75  clippy::needless_for_each,
76  clippy::needless_option_as_deref,
77  clippy::needless_pass_by_value,
78  clippy::needless_collect,
79  clippy::needless_continue,
80  clippy::non_send_fields_in_send_ty,
81  clippy::nonstandard_macro_braces,
82  clippy::option_if_let_else,
83  clippy::option_option,
84  clippy::rc_mutex,
85  clippy::redundant_else,
86  clippy::same_name_method,
87  clippy::semicolon_if_nothing_returned,
88  clippy::str_to_string,
89  clippy::string_to_string,
90  clippy::too_many_lines,
91  clippy::trivially_copy_pass_by_ref,
92  clippy::trivial_regex,
93  clippy::try_err,
94  clippy::unnested_or_patterns,
95  clippy::unused_async,
96  clippy::unwrap_or_else_default,
97  clippy::useless_let_if_seq,
98  bad_style,
99  clashing_extern_declarations,
100  dead_code,
101  deprecated,
102  explicit_outlives_requirements,
103  improper_ctypes,
104  invalid_value,
105  missing_copy_implementations,
106  missing_debug_implementations,
107  mutable_transmutes,
108  no_mangle_generic_items,
109  non_shorthand_field_patterns,
110  overflowing_literals,
111  path_statements,
112  patterns_in_fns_without_body,
113  private_in_public,
114  trivial_bounds,
115  trivial_casts,
116  trivial_numeric_casts,
117  type_alias_bounds,
118  unconditional_recursion,
119  unreachable_pub,
120  unsafe_code,
121  unstable_features,
122  unused,
123  unused_allocation,
124  unused_comparisons,
125  unused_import_braces,
126  unused_parens,
127  unused_qualifications,
128  while_true,
129  missing_docs
130)]
131#![warn(clippy::exhaustive_enums)]
132#![allow(unused_attributes, clippy::derive_partial_eq_without_eq, clippy::box_default)]
133// !!END_LINTS
134// Add exceptions here
135#![allow(clippy::expect_used)]
136
137use asset_container::AssetFlags;
138use proc_macro::TokenStream;
139use proc_macro2::Ident;
140use quote::quote;
141use structmeta::{NameArgs, StructMeta};
142use syn::{parse_macro_input, Attribute, DataEnum, DataStruct, DeriveInput, Type};
143
144#[derive(Debug, StructMeta)]
145struct TypeOpts {
146  lazy: bool,
147  #[struct_meta(name = "asset")]
148  ty: NameArgs<Type>,
149}
150
151/// The derive macro for the AssetManager trait.
152#[proc_macro_derive(AssetManager, attributes(asset_managers, asset))]
153pub fn derive_asset_container(input: TokenStream) -> TokenStream {
154  // Parse the input tokens into a syntax tree.
155  let ast = parse_macro_input!(input as DeriveInput);
156
157  // Extract the name of the struct we're deriving the trait for.
158  let name = &ast.ident;
159
160  // Parse the attribute arguments.
161  let opts = ast
162    .attrs
163    .iter()
164    .find(|attr| attr.path().is_ident("asset"))
165    .map(|attribute| attribute.parse_args::<TypeOpts>().expect("invalid attribute arguments"))
166    .expect("no asset attribute");
167
168  match ast.data {
169    syn::Data::Struct(ref data) => impl_struct(name, data, opts),
170    syn::Data::Enum(ref data) => impl_enum(name, data, opts),
171    _ => panic!("Only structs and enums can derive the Assets trait."),
172  }
173}
174
175fn has_skip(attr: &[Attribute]) -> bool {
176  attr
177    .iter()
178    .find(|attr| attr.path().is_ident("asset"))
179    .map_or(false, |attr| {
180      let ident = attr.parse_args::<Ident>().expect("invalid attribute arguments");
181      ident == "skip"
182    })
183}
184
185fn impl_struct(name: &Ident, data: &DataStruct, opts: TypeOpts) -> TokenStream {
186  let fields = &data.fields;
187  // Generate a list of field names as strings.
188  let asset_fields = fields
189    .iter()
190    .filter(|field| field.ty == opts.ty.args)
191    .filter(|field| !has_skip(&field.attrs))
192    .map(|field| {
193      field
194        .ident
195        .as_ref()
196        .map_or_else(|| panic!("Unnamed fields are not supported."), |ident| ident.clone())
197    })
198    .collect::<Vec<Ident>>();
199
200  // Generate a list of field names as strings.
201  let inner_managers = fields
202    .iter()
203    .filter(|field| field.ty != opts.ty.args)
204    .filter(|field| !has_skip(&field.attrs))
205    .map(|field| {
206      field
207        .ident
208        .as_ref()
209        .map_or_else(|| panic!("Unnamed fields are not supported."), |ident| ident.clone())
210    })
211    .collect::<Vec<Ident>>();
212  let flags = if opts.lazy {
213    AssetFlags::Lazy
214  } else {
215    AssetFlags::empty()
216  }
217  .bits();
218  let flags = quote! {#flags};
219  let asset_type = opts.ty.args;
220
221  // Generate an implementation of the Assets trait for the struct.
222  let output = quote! {
223      impl asset_container::AssetManager for #name {
224          type Asset = #asset_type;
225
226          fn set_baseurl(&self, baseurl: &std::path::Path) {
227            use asset_container::Asset;
228            #(self.#asset_fields.update_baseurl(baseurl);)*
229            #(self.#inner_managers.set_baseurl(baseurl);)*
230          }
231
232          fn assets(&self) -> asset_container::Assets<#asset_type> {
233            let mut assets = asset_container::Assets::new(vec![],self.get_asset_flags());
234            #(assets.push(&self.#asset_fields);)*
235            #(assets.extend(self.#inner_managers.assets());)*
236            assets
237          }
238
239          fn get_asset_flags(&self) -> u32 {
240            #flags
241          }
242      }
243  };
244  TokenStream::from(output)
245}
246
247fn impl_enum(name: &Ident, data: &DataEnum, opts: TypeOpts) -> TokenStream {
248  let variants = &data.variants;
249
250  let asset_variants = variants
251    .iter()
252    .filter(|v| v.fields.iter().any(|f| f.ty == opts.ty.args))
253    .filter(|field| !has_skip(&field.attrs))
254    .map(|field| field.ident.clone())
255    .collect::<Vec<Ident>>();
256
257  let inner_managers = variants
258    .iter()
259    .filter(|v| v.fields.iter().any(|f| f.ty != opts.ty.args))
260    .filter(|field| !has_skip(&field.attrs))
261    .map(|field| field.ident.clone())
262    .collect::<Vec<Ident>>();
263
264  let flags = if opts.lazy {
265    AssetFlags::Lazy
266  } else {
267    AssetFlags::empty()
268  }
269  .bits();
270  let flags = quote! {#flags};
271  let asset_type = opts.ty.args;
272
273  // Generate an implementation of the Assets trait for the struct.
274  let output = quote! {
275      impl asset_container::AssetManager for #name {
276          type Asset = #asset_type;
277
278          fn set_baseurl(&self, baseurl: &std::path::Path) {
279            use asset_container::Asset;
280            match self {
281              #(Self::#asset_variants(v) => {
282                v.update_baseurl(baseurl);
283              })*
284              #(Self::#inner_managers(v) => {
285                v.set_baseurl(baseurl);
286              })*
287              _ => {}
288            }
289          }
290
291          fn assets(&self) -> asset_container::Assets<#asset_type> {
292            let mut assets = asset_container::Assets::new(vec![],self.get_asset_flags());
293            match self {
294              #(Self::#asset_variants(v) => {
295                assets.push(v);
296              })*
297              #(Self::#inner_managers(v) => {
298                assets.extend(asset_container::AssetManager::assets(v));
299              })*
300              _ => {}
301            }
302            assets
303          }
304
305          fn get_asset_flags(&self) -> u32 {
306            #flags
307          }
308      }
309  };
310  TokenStream::from(output)
311}