1use proc_macro::TokenStream;
2use proc_macro2::Ident;
3use quote::quote;
4use syn::{DeriveInput, LitStr, parse_macro_input};
5
6#[proc_macro_derive(PipeKind, attributes(pipe_kind))]
7pub fn derive_pipe_kind(input: TokenStream) -> TokenStream {
8 let input = parse_macro_input!(input as DeriveInput);
10 let struct_name = input.ident;
11
12 let mut found: Option<Ident> = None;
14 for attr in input.attrs.into_iter() {
15 if attr.path().is_ident("pipe_kind") {
16 if found.is_some() {
17 panic!("Multiple `#[pipe_kind]` attributes found. Only one is allowed.");
18 }
19 let kind_ident = attr.parse_args::<Ident>().expect(
21 "`#[pipe_kind(...)]` must contain exactly one bare identifier, e.g. `Control` or \
22 `Data`",
23 );
24 found = Some(kind_ident);
25 }
26 }
27
28 let kind_ident =
29 found.expect("Missing `#[pipe_kind(Control)]` or `#[pipe_kind(Data)]` attribute.");
30
31 let bus_variant = match kind_ident.to_string().as_str() {
33 "Control" => quote! { BusKind::Control },
34 "Data" => quote! { BusKind::Data },
35 other => panic!(
36 "`#[pipe_kind(...)]` must be either `Control` or `Data`, got `{}`",
37 other
38 ),
39 };
40
41 let expanded = quote! {
44 impl #struct_name {
45 pub fn new(bounds: Option<usize>) -> Self {
46 use crossbeam::channel::Sender;
47 use crossbeam::channel::Receiver;
48 use crossbeam::channel::bounded;
49 let (source_tx, source_rx) = crossbeam::channel::bounded(bounds.unwrap_or(100));
50 let (sink_tx, sink_rx) = crossbeam::channel::bounded(bounds.unwrap_or(100));
51 let source = NetworkSource{rx: source_rx, tx: source_tx};
52 let sink = NetworkSink{tx: sink_tx, rx: sink_rx};
53 #struct_name(Arc::new(crate::networking::p2p::NetworkPipe{source,
54 sink, bus_kind: #bus_variant,
55 bounds}))
56 }
57 }
58 };
59 TokenStream::from(expanded)
60}
61
62#[proc_macro]
63pub fn subject(input: TokenStream) -> TokenStream {
64 let subject_name_tokens = parse_macro_input!(input as LitStr);
66 let span = subject_name_tokens.span();
67 let literal_str = subject_name_tokens.value();
68
69 let parts: Vec<_> = literal_str.split('/').collect();
71 if parts.len() != 4 {
72 return syn::Error::new_spanned(
73 &subject_name_tokens,
74 "Subject must look like \"workspace/<workspace_id>/object/<object_id>\"",
75 )
76 .to_compile_error()
77 .into();
78 }
79
80 if parts[0] != "workspace" || parts[2] != "object" {
82 return syn::Error::new_spanned(
83 &subject_name_tokens,
84 "Subject must look like \"workspace/<workspace_id>/object/<object_id>\"",
85 )
86 .to_compile_error()
87 .into();
88 }
89
90 let ws_id_str = parts[1]; let obj_id_str = parts[3]; if ws_id_str.is_empty() || obj_id_str.is_empty() {
95 return syn::Error::new_spanned(
96 &subject_name_tokens,
97 "workspace_id and object_id cannot be empty",
98 )
99 .to_compile_error()
100 .into();
101 }
102
103 let mut h = blake3::Hasher::new();
110 h.update(ws_id_str.as_bytes());
111 let ws_hash = h.finalize();
112 let ws_bytes = ws_hash.as_bytes();
113
114 let mut h = blake3::Hasher::new();
115 h.update(obj_id_str.as_bytes());
116 let obj_hash = h.finalize();
117 let obj_bytes = obj_hash.as_bytes();
118
119 let mut h = blake3::Hasher::new();
120 h.update(ws_bytes);
121 h.update(obj_bytes);
122 let subject_hash = h.finalize();
123 let subject_bytes = subject_hash.as_bytes();
124
125 let subject_bytes_tokens = subject_bytes
127 .iter()
128 .map(|b| quote! { #b })
129 .collect::<Vec<_>>();
130
131 let ws_bytes_tokens = ws_bytes.iter().map(|b| quote! { #b }).collect::<Vec<_>>();
132
133 let obj_bytes_tokens = obj_bytes.iter().map(|b| quote! { #b }).collect::<Vec<_>>();
134
135 let ws_id_lit = LitStr::new(ws_id_str, span);
137 let obj_id_lit = LitStr::new(obj_id_str, span);
138
139 let expanded = quote! {
141 {
142 use crate::subject::SubjectHash;
144 use xaeroflux_core::event::{ScanWindow, XaeroEvent};
145 use crate::subject::Subject;
146 use xaeroflux_core::event::{EventType, SystemEventKind};
147 use xaeroflux_core::date_time::emit_secs;
148 use xaeroflux_core::pool::XaeroPoolManager;
149 XaeroPoolManager::init();
151
152 let subject = Subject::new_with_workspace(
154 #subject_name_tokens.to_string(), [ #(#subject_bytes_tokens),* ], #ws_id_lit.to_string(), #obj_id_lit.to_string(), );
159
160 let workspace_data = vec![ #(#ws_bytes_tokens),* ];
162 let wc_evt = XaeroPoolManager::create_xaero_event(
163 &workspace_data, EventType::SystemEvent(SystemEventKind::WorkspaceCreated).to_u8(),
165 None, None, None, emit_secs(), ).unwrap_or_else(|e| {
170 tracing::error!("Critical: Failed to create WorkspaceCreated event: {:?}", e);
171 panic!("Cannot bootstrap subject without WorkspaceCreated event - ring buffer pool exhausted");
172 });
173
174 subject.control.sink.tx.send(wc_evt)
176 .expect("failed to bootstrap: WorkspaceCreated");
177
178 let object_data = vec![ #(#obj_bytes_tokens),* ];
180 let oc_evt = XaeroPoolManager::create_xaero_event(
181 &object_data, EventType::SystemEvent(SystemEventKind::ObjectCreated).to_u8(),
183 None, None, None, emit_secs(), ).unwrap_or_else(|e| {
188 tracing::error!("Critical: Failed to create ObjectCreated event: {:?}", e);
189 panic!("Cannot bootstrap subject without ObjectCreated event - ring buffer pool exhausted");
190 });
191
192 subject.control.sink.tx.send(oc_evt)
194 .expect("failed to bootstrap: ObjectCreated");
195
196 subject
198 }
199 };
200
201 expanded.into()
203}