Experiments in applying Entity-Component-System patterns to durable data storage APIs.
1extern crate proc_macro;
2use proc_macro::TokenStream;
3use quote::quote;
4use syn::{punctuated::Punctuated, Data, Expr, Fields, Lit, Meta, Token};
5
6#[proc_macro_derive(Component, attributes(component))]
7pub fn derive_component_fn(input: TokenStream) -> TokenStream {
8 let ast = syn::parse(input).unwrap();
9 impl_derive_component(&ast)
10}
11
12#[proc_macro_derive(Resource, attributes(component))]
13pub fn derive_resource_fn(input: TokenStream) -> TokenStream {
14 let ast = syn::parse(input).unwrap();
15 impl_derive_resource(&ast)
16}
17
18#[derive(Debug)]
19enum Storage {
20 Json,
21 Blob,
22 Null,
23}
24
25fn impl_derive_component(ast: &syn::DeriveInput) -> TokenStream {
26 let name = ast.ident.clone();
27
28 let mut storage = Storage::Json;
29
30 if let Data::Struct(ref struc) = ast.data {
31 if matches!(struc.fields, Fields::Unit) {
32 storage = Storage::Null;
33 }
34 }
35
36 if let Some(component_attribute) = ast.attrs.iter().find(|a| a.path().is_ident("component")) {
37 let nested = component_attribute
38 .parse_args_with(Punctuated::<Meta, Token![,]>::parse_terminated)
39 .unwrap();
40 for meta in nested.clone() {
41 match meta {
42 Meta::NameValue(mnv) if mnv.path.is_ident("storage") => {
43 if let Expr::Lit(expr_lit) = &mnv.value {
44 if let Lit::Str(lit) = &expr_lit.lit {
45 match lit.value().as_str() {
46 "json" => storage = Storage::Json,
47 "blob" => storage = Storage::Blob,
48 other => panic!("storage {other} not supported"),
49 }
50 }
51 }
52 }
53 other => panic!(
54 "Unsupported attribute {}",
55 other.path().get_ident().unwrap()
56 ),
57 }
58 }
59 };
60
61 let gen = match storage {
62 Storage::Json => {
63 quote! {
64 impl ecsdb::component::Component for #name {
65 type Storage =ecsdb::component::JsonStorage;
66 const NAME: &'static str = concat!(std::module_path!(), "::", stringify!(#name));
67 }
68 }
69 }
70
71 Storage::Null => {
72 quote! {
73 impl ecsdb::component::Component for #name {
74 type Storage = ecsdb::component::NullStorage;
75 const NAME: &'static str = concat!(std::module_path!(), "::", stringify!(#name));
76 }
77 }
78 }
79
80 Storage::Blob => {
81 quote! {
82 impl ecsdb::component::Component for #name {
83 type Storage = ecsdb::component::BlobStorage;
84 const NAME: &'static str = concat!(std::module_path!(), "::", stringify!(#name));
85 }
86
87 impl Into<Vec<u8>> for #name {
88 fn into(self) -> Vec<u8> {
89 self.0
90 }
91 }
92
93 impl From<Vec<u8>> for #name {
94 fn from(value: Vec<u8>) -> Self {
95 Self(value)
96 }
97 }
98 }
99 }
100 };
101
102 gen.into()
103}
104
105fn impl_derive_resource(ast: &syn::DeriveInput) -> TokenStream {
106 let name = ast.ident.clone();
107
108 let component_derive: proc_macro2::TokenStream = impl_derive_component(ast).into();
109
110 let gen = quote! {
111 #component_derive
112
113 impl ecsdb::resource::Resource for #name { }
114 };
115
116 gen.into()
117}