1use proc_macro::TokenStream;
2use quote::{quote, ToTokens};
3use syn::{parse::Parse, parse::ParseStream, parse_macro_input, Ident, ItemStruct, Result, Token};
4
5#[proc_macro_attribute]
25pub fn native_node_wrapper(attr: TokenStream, item: TokenStream) -> TokenStream {
26 let args = parse_macro_input!(attr as NativeNodeArgs);
27 let item_struct = parse_macro_input!(item as ItemStruct);
28
29 match expand_native_node(args, item_struct) {
30 Ok(ts) => ts,
31 Err(e) => e.to_compile_error().into(),
32 }
33}
34
35struct NativeNodeArgs {
36 native: syn::LitStr,
37 field: Ident,
38 gen_as_node: bool,
39 inputs: Vec<Ident>,
40 outputs: Vec<Ident>,
41}
42
43impl Parse for NativeNodeArgs {
44 fn parse(input: ParseStream<'_>) -> Result<Self> {
45 let mut native: Option<syn::LitStr> = None;
46 let mut field: Option<Ident> = None;
47 let mut gen_as_node: Option<bool> = None;
48 let mut inputs: Vec<Ident> = Vec::new();
49 let mut outputs: Vec<Ident> = Vec::new();
50
51 while !input.is_empty() {
52 let key: Ident = input.parse()?;
53
54 if input.peek(Token![=]) {
55 input.parse::<Token![=]>()?;
56 if key == "native" {
57 native = Some(input.parse()?);
58 } else if key == "field" {
59 field = Some(input.parse()?);
60 } else if key == "as_node" {
61 let v: syn::LitBool = input.parse()?;
62 gen_as_node = Some(v.value);
63 } else {
64 return Err(syn::Error::new_spanned(key, "unknown argument; expected `native`, `field`, or `as_node`"));
65 }
66 } else if input.peek(syn::token::Paren) {
67 let content;
68 syn::parenthesized!(content in input);
69 while !content.is_empty() {
70 let id: Ident = content.parse()?;
71 if key == "inputs" {
72 inputs.push(id);
73 } else if key == "outputs" {
74 outputs.push(id);
75 } else {
76 return Err(syn::Error::new_spanned(key, "unknown argument; expected `inputs` or `outputs`"));
77 }
78 if content.peek(Token![,]) {
79 content.parse::<Token![,]>()?;
80 }
81 }
82 } else {
83 return Err(syn::Error::new_spanned(key, "expected `=` or `(...)`"));
84 }
85
86 if input.peek(Token![,]) {
87 input.parse::<Token![,]>()?;
88 }
89 }
90
91 let native = native.ok_or_else(|| syn::Error::new(input.span(), "missing required argument: `native`"))?;
92
93 Ok(Self {
94 native,
95 field: field.unwrap_or_else(|| Ident::new("node", proc_macro2::Span::call_site())),
96 gen_as_node: gen_as_node.unwrap_or(true),
97 inputs,
98 outputs,
99 })
100 }
101}
102
103fn expand_native_node(args: NativeNodeArgs, item_struct: ItemStruct) -> Result<TokenStream> {
104 let field_ident = args.field.clone();
106 let has_field = match &item_struct.fields {
107 syn::Fields::Named(named) => named
108 .named
109 .iter()
110 .any(|f| f.ident.as_ref().is_some_and(|id| *id == field_ident)),
111 _ => false,
112 };
113
114 if !has_field {
115 return Err(syn::Error::new_spanned(
116 item_struct.fields.to_token_stream(),
117 format!(
118 "expected a struct with a named field `{}` (or pass `field = ...`)",
119 field_ident
120 ),
121 ));
122 }
123
124 let ty_ident = item_struct.ident.clone();
125 let native_name = args.native;
126
127 let create_expr = quote! {
128 ::depthai::pipeline::node::create_node_by_name(pipeline.inner_arc(), #native_name)?
129 };
130
131 let gen_as_node = args.gen_as_node;
132
133 let as_node_impl = if gen_as_node {
134 quote! {
135 impl #ty_ident {
136 pub fn as_node(&self) -> &::depthai::pipeline::Node {
138 &self.#field_ident
139 }
140 }
141 }
142 } else {
143 quote! {}
144 };
145
146 let inputs = args.inputs;
147 let outputs = args.outputs;
148
149 let input_methods = inputs.iter().map(|id| {
150 let name = id.to_string();
151 quote! {
152 #[allow(non_snake_case)]
153 pub fn #id(&self) -> ::depthai::Result<::depthai::output::Input> {
154 self.as_node().input(#name)
155 }
156 }
157 });
158
159 let output_methods = outputs.iter().map(|id| {
160 let name = id.to_string();
161 quote! {
162 #[allow(non_snake_case)]
163 pub fn #id(&self) -> ::depthai::Result<::depthai::output::Output> {
164 self.as_node().output(#name)
165 }
166 }
167 });
168
169 let expanded = quote! {
171 #item_struct
172
173 #as_node_impl
174
175 impl #ty_ident {
176 #(#input_methods)*
177 #(#output_methods)*
178 }
179
180 unsafe impl ::depthai::pipeline::DeviceNode for #ty_ident {
181 fn create_in_pipeline(pipeline: &::depthai::pipeline::Pipeline) -> ::depthai::Result<Self> {
182 let node = #create_expr;
183 Ok(Self { #field_ident: node })
184 }
185 }
186 };
187
188 Ok(expanded.into())
189}
190
191#[proc_macro_attribute]
199pub fn depthai_composite(_args: TokenStream, item: TokenStream) -> TokenStream {
200 let item_struct = parse_macro_input!(item as ItemStruct);
201 let ty_ident = item_struct.ident.clone();
202
203 let expanded = quote! {
204 #item_struct
205
206 impl ::depthai::pipeline::device_node::CreateInPipeline for #ty_ident {
207 fn create(pipeline: &::depthai::pipeline::Pipeline) -> ::depthai::Result<Self> {
208 Self::new(pipeline)
209 }
210 }
211 };
212
213 expanded.into()
214}
215
216#[proc_macro_attribute]
220pub fn depthai_host_node(_args: TokenStream, item: TokenStream) -> TokenStream {
221 let item_struct = parse_macro_input!(item as ItemStruct);
222 let ty_ident = item_struct.ident.clone();
223
224 let expanded = quote! {
225 #item_struct
226
227 impl ::depthai::host_node::HostNodeImpl for #ty_ident {
228 fn process_group(&mut self, group: &::depthai::host_node::MessageGroup) -> Option<::depthai::host_node::Buffer> {
229 self.process(group)
230 }
231 }
232 };
233
234 expanded.into()
235}
236
237#[proc_macro_attribute]
241pub fn depthai_threaded_host_node(_args: TokenStream, item: TokenStream) -> TokenStream {
242 let item_struct = parse_macro_input!(item as ItemStruct);
243 let ty_ident = item_struct.ident.clone();
244
245 let expanded = quote! {
246 #item_struct
247
248 impl ::depthai::threaded_host_node::ThreadedHostNodeImpl for #ty_ident {
249 fn run(&mut self, ctx: &::depthai::threaded_host_node::ThreadedHostNodeContext) {
250 self.run(ctx)
251 }
252 }
253 };
254
255 expanded.into()
256}