1use std::future::Future;
35use std::io::Error;
36use std::pin::Pin;
37
38use bytes::{Bytes, BytesMut};
39use dfir_lang::diagnostic::Diagnostics;
40use dfir_lang::graph::{AsCodeOptions, DfirGraph};
41use futures::{Sink, Stream};
42use proc_macro2::Span;
43use quote::quote;
44use serde::Serialize;
45use serde::de::DeserializeOwned;
46use slotmap::SparseSecondaryMap;
47use stageleft::{QuotedWithContext, q};
48
49use super::deploy_provider::{ClusterSpec, Deploy, ExternalSpec, Node, ProcessSpec, RegisterPort};
50use crate::compile::builder::ExternalPortId;
51use crate::location::dynamic::LocationId;
52use crate::location::member_id::TaglessMemberId;
53use crate::location::{LocationKey, MembershipEvent, NetworkHint};
54
55pub enum EmbeddedDeploy {}
59
60#[derive(Clone)]
62pub struct EmbeddedNode {
63 pub fn_name: String,
65 pub location_key: LocationKey,
67}
68
69impl Node for EmbeddedNode {
70 type Port = ();
71 type Meta = ();
72 type InstantiateEnv = EmbeddedInstantiateEnv;
73
74 fn next_port(&self) -> Self::Port {}
75
76 fn update_meta(&self, _meta: &Self::Meta) {}
77
78 fn instantiate(
79 &self,
80 _env: &mut Self::InstantiateEnv,
81 _meta: &mut Self::Meta,
82 _graph: DfirGraph,
83 _extra_stmts: &[syn::Stmt],
84 _sidecars: &[syn::Expr],
85 _as_code_options: &AsCodeOptions,
86 ) {
87 }
89}
90
91impl<'a> RegisterPort<'a, EmbeddedDeploy> for EmbeddedNode {
92 fn register(&self, _external_port_id: ExternalPortId, _port: Self::Port) {
93 panic!("EmbeddedDeploy does not support external ports");
94 }
95
96 #[expect(clippy::manual_async_fn, reason = "false positive, involves lifetimes")]
97 fn as_bytes_bidi(
98 &self,
99 _external_port_id: ExternalPortId,
100 ) -> impl Future<
101 Output = super::deploy_provider::DynSourceSink<Result<BytesMut, Error>, Bytes, Error>,
102 > + 'a {
103 async { panic!("EmbeddedDeploy does not support external ports") }
104 }
105
106 #[expect(clippy::manual_async_fn, reason = "false positive, involves lifetimes")]
107 fn as_bincode_bidi<InT, OutT>(
108 &self,
109 _external_port_id: ExternalPortId,
110 ) -> impl Future<Output = super::deploy_provider::DynSourceSink<OutT, InT, Error>> + 'a
111 where
112 InT: Serialize + 'static,
113 OutT: DeserializeOwned + 'static,
114 {
115 async { panic!("EmbeddedDeploy does not support external ports") }
116 }
117
118 #[expect(clippy::manual_async_fn, reason = "false positive, involves lifetimes")]
119 fn as_bincode_sink<T>(
120 &self,
121 _external_port_id: ExternalPortId,
122 ) -> impl Future<Output = Pin<Box<dyn Sink<T, Error = Error>>>> + 'a
123 where
124 T: Serialize + 'static,
125 {
126 async { panic!("EmbeddedDeploy does not support external ports") }
127 }
128
129 #[expect(clippy::manual_async_fn, reason = "false positive, involves lifetimes")]
130 fn as_bincode_source<T>(
131 &self,
132 _external_port_id: ExternalPortId,
133 ) -> impl Future<Output = Pin<Box<dyn Stream<Item = T>>>> + 'a
134 where
135 T: DeserializeOwned + 'static,
136 {
137 async { panic!("EmbeddedDeploy does not support external ports") }
138 }
139}
140
141impl<S: Into<String>> ProcessSpec<'_, EmbeddedDeploy> for S {
142 fn build(self, location_key: LocationKey, _name_hint: &str) -> EmbeddedNode {
143 EmbeddedNode {
144 fn_name: self.into(),
145 location_key,
146 }
147 }
148}
149
150impl<S: Into<String>> ClusterSpec<'_, EmbeddedDeploy> for S {
151 fn build(self, location_key: LocationKey, _name_hint: &str) -> EmbeddedNode {
152 EmbeddedNode {
153 fn_name: self.into(),
154 location_key,
155 }
156 }
157}
158
159impl<S: Into<String>> ExternalSpec<'_, EmbeddedDeploy> for S {
160 fn build(self, location_key: LocationKey, _name_hint: &str) -> EmbeddedNode {
161 EmbeddedNode {
162 fn_name: self.into(),
163 location_key,
164 }
165 }
166}
167
168#[derive(Default)]
175pub struct EmbeddedInstantiateEnv {
176 pub inputs: SparseSecondaryMap<LocationKey, Vec<(syn::Ident, syn::Type)>>,
178 pub singleton_inputs: SparseSecondaryMap<LocationKey, Vec<(syn::Ident, syn::Type)>>,
180 pub outputs: SparseSecondaryMap<LocationKey, Vec<(syn::Ident, syn::Type)>>,
182 pub network_outputs: SparseSecondaryMap<LocationKey, Vec<(String, bool, Option<syn::Type>)>>,
190 pub network_inputs: SparseSecondaryMap<LocationKey, Vec<(String, bool, Option<syn::Type>)>>,
201 pub membership_streams: SparseSecondaryMap<LocationKey, Vec<LocationKey>>,
204}
205
206impl<'a> Deploy<'a> for EmbeddedDeploy {
207 type Meta = ();
208 type InstantiateEnv = EmbeddedInstantiateEnv;
209
210 type Process = EmbeddedNode;
211 type Cluster = EmbeddedNode;
212 type External = EmbeddedNode;
213
214 const SUPPORTS_EXTERNAL_SERIALIZATION: bool = true;
215
216 fn o2o_sink_source(
217 env: &mut Self::InstantiateEnv,
218 p1: &Self::Process,
219 _p1_port: &(),
220 p2: &Self::Process,
221 _p2_port: &(),
222 name: Option<&str>,
223 _networking_info: &crate::networking::NetworkingInfo,
224 external_types: Option<(&syn::Type, &syn::Type)>,
225 ) -> (syn::Expr, syn::Expr) {
226 let name = name.expect(
227 "EmbeddedDeploy o2o networking requires a channel name. Use `TCP.name(\"my_channel\")` to provide one.",
228 );
229
230 let sink_ident = syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
231 let source_ident = syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
232
233 env.network_outputs
234 .entry(p1.location_key)
235 .unwrap()
236 .or_default()
237 .push((
238 name.to_owned(),
239 false,
240 external_types.map(|(i, _)| i.clone()),
241 ));
242 env.network_inputs
243 .entry(p2.location_key)
244 .unwrap()
245 .or_default()
246 .push((
247 name.to_owned(),
248 false,
249 external_types.map(|(_, o)| o.clone()),
250 ));
251
252 (
253 syn::parse_quote!(__root_dfir_rs::sinktools::for_each(#sink_ident)),
254 syn::parse_quote!(#source_ident),
255 )
256 }
257
258 fn o2o_connect(
259 _p1: &Self::Process,
260 _p1_port: &(),
261 _p2: &Self::Process,
262 _p2_port: &(),
263 ) -> Box<dyn FnOnce()> {
264 Box::new(|| {})
265 }
266
267 fn o2m_sink_source(
268 env: &mut Self::InstantiateEnv,
269 p1: &Self::Process,
270 _p1_port: &(),
271 c2: &Self::Cluster,
272 _c2_port: &(),
273 name: Option<&str>,
274 _networking_info: &crate::networking::NetworkingInfo,
275 external_types: Option<(&syn::Type, &syn::Type)>,
276 ) -> (syn::Expr, syn::Expr) {
277 let name = name.expect("EmbeddedDeploy o2m networking requires a channel name.");
278 let sink_ident = syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
279 let source_ident = syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
280 env.network_outputs
281 .entry(p1.location_key)
282 .unwrap()
283 .or_default()
284 .push((
285 name.to_owned(),
286 true,
287 external_types.map(|(i, _)| i.clone()),
288 ));
289 env.network_inputs
290 .entry(c2.location_key)
291 .unwrap()
292 .or_default()
293 .push((
294 name.to_owned(),
295 false,
296 external_types.map(|(_, o)| o.clone()),
297 ));
298 (
299 syn::parse_quote!(__root_dfir_rs::sinktools::for_each(#sink_ident)),
300 syn::parse_quote!(#source_ident),
301 )
302 }
303
304 fn o2m_connect(
305 _p1: &Self::Process,
306 _p1_port: &(),
307 _c2: &Self::Cluster,
308 _c2_port: &(),
309 ) -> Box<dyn FnOnce()> {
310 Box::new(|| {})
311 }
312
313 fn m2o_sink_source(
314 env: &mut Self::InstantiateEnv,
315 c1: &Self::Cluster,
316 _c1_port: &(),
317 p2: &Self::Process,
318 _p2_port: &(),
319 name: Option<&str>,
320 _networking_info: &crate::networking::NetworkingInfo,
321 external_types: Option<(&syn::Type, &syn::Type)>,
322 ) -> (syn::Expr, syn::Expr) {
323 let name = name.expect("EmbeddedDeploy m2o networking requires a channel name.");
324 let sink_ident = syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
325 let source_ident = syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
326 env.network_outputs
327 .entry(c1.location_key)
328 .unwrap()
329 .or_default()
330 .push((
331 name.to_owned(),
332 false,
333 external_types.map(|(i, _)| i.clone()),
334 ));
335 env.network_inputs
336 .entry(p2.location_key)
337 .unwrap()
338 .or_default()
339 .push((
340 name.to_owned(),
341 true,
342 external_types.map(|(_, o)| o.clone()),
343 ));
344 (
345 syn::parse_quote!(__root_dfir_rs::sinktools::for_each(#sink_ident)),
346 syn::parse_quote!(#source_ident),
347 )
348 }
349
350 fn m2o_connect(
351 _c1: &Self::Cluster,
352 _c1_port: &(),
353 _p2: &Self::Process,
354 _p2_port: &(),
355 ) -> Box<dyn FnOnce()> {
356 Box::new(|| {})
357 }
358
359 fn m2m_sink_source(
360 env: &mut Self::InstantiateEnv,
361 c1: &Self::Cluster,
362 _c1_port: &(),
363 c2: &Self::Cluster,
364 _c2_port: &(),
365 name: Option<&str>,
366 _networking_info: &crate::networking::NetworkingInfo,
367 external_types: Option<(&syn::Type, &syn::Type)>,
368 ) -> (syn::Expr, syn::Expr) {
369 let name = name.expect("EmbeddedDeploy m2m networking requires a channel name.");
370 let sink_ident = syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
371 let source_ident = syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
372 env.network_outputs
373 .entry(c1.location_key)
374 .unwrap()
375 .or_default()
376 .push((
377 name.to_owned(),
378 true,
379 external_types.map(|(i, _)| i.clone()),
380 ));
381 env.network_inputs
382 .entry(c2.location_key)
383 .unwrap()
384 .or_default()
385 .push((
386 name.to_owned(),
387 true,
388 external_types.map(|(_, o)| o.clone()),
389 ));
390 (
391 syn::parse_quote!(__root_dfir_rs::sinktools::for_each(#sink_ident)),
392 syn::parse_quote!(#source_ident),
393 )
394 }
395
396 fn m2m_connect(
397 _c1: &Self::Cluster,
398 _c1_port: &(),
399 _c2: &Self::Cluster,
400 _c2_port: &(),
401 ) -> Box<dyn FnOnce()> {
402 Box::new(|| {})
403 }
404
405 fn e2o_many_source(
406 _extra_stmts: &mut Vec<syn::Stmt>,
407 _p2: &Self::Process,
408 _p2_port: &(),
409 _codec_type: &syn::Type,
410 _shared_handle: String,
411 ) -> syn::Expr {
412 panic!("EmbeddedDeploy does not support networking (e2o)")
413 }
414
415 fn e2o_many_sink(_shared_handle: String) -> syn::Expr {
416 panic!("EmbeddedDeploy does not support networking (e2o)")
417 }
418
419 fn e2o_source(
420 _extra_stmts: &mut Vec<syn::Stmt>,
421 _p1: &Self::External,
422 _p1_port: &(),
423 _p2: &Self::Process,
424 _p2_port: &(),
425 _codec_type: &syn::Type,
426 _shared_handle: String,
427 ) -> syn::Expr {
428 panic!("EmbeddedDeploy does not support networking (e2o)")
429 }
430
431 fn e2o_connect(
432 _p1: &Self::External,
433 _p1_port: &(),
434 _p2: &Self::Process,
435 _p2_port: &(),
436 _many: bool,
437 _server_hint: NetworkHint,
438 ) -> Box<dyn FnOnce()> {
439 panic!("EmbeddedDeploy does not support networking (e2o)")
440 }
441
442 fn o2e_sink(
443 _p1: &Self::Process,
444 _p1_port: &(),
445 _p2: &Self::External,
446 _p2_port: &(),
447 _shared_handle: String,
448 ) -> syn::Expr {
449 panic!("EmbeddedDeploy does not support networking (o2e)")
450 }
451
452 #[expect(
453 unreachable_code,
454 reason = "panic before q! which is only for return type"
455 )]
456 fn cluster_ids(
457 _of_cluster: LocationKey,
458 ) -> impl QuotedWithContext<'a, &'a [TaglessMemberId], ()> + Clone + 'a {
459 panic!("EmbeddedDeploy does not support cluster IDs");
460 q!(unreachable!("EmbeddedDeploy does not support cluster IDs"))
461 }
462
463 fn cluster_self_id() -> impl QuotedWithContext<'a, TaglessMemberId, ()> + Clone + 'a {
464 super::embedded_runtime::embedded_cluster_self_id()
465 }
466
467 fn cluster_membership_stream(
468 env: &mut Self::InstantiateEnv,
469 at_location: &LocationId,
470 location_id: &LocationId,
471 ) -> impl QuotedWithContext<'a, Box<dyn Stream<Item = (TaglessMemberId, MembershipEvent)> + Unpin>, ()>
472 {
473 let at_key = match at_location {
474 LocationId::Process(key) | LocationId::Cluster(key) => *key,
475 _ => panic!("cluster_membership_stream must be called from a process or cluster"),
476 };
477 let cluster_key = match location_id {
478 LocationId::Cluster(key) => *key,
479 _ => panic!("cluster_membership_stream target must be a cluster"),
480 };
481 let vec = env.membership_streams.entry(at_key).unwrap().or_default();
482 let idx = if let Some(pos) = vec.iter().position(|k| *k == cluster_key) {
483 pos
484 } else {
485 vec.push(cluster_key);
486 vec.len() - 1
487 };
488
489 super::embedded_runtime::embedded_cluster_membership_stream(idx)
490 }
491
492 fn register_embedded_stream_input(
493 env: &mut Self::InstantiateEnv,
494 location_key: LocationKey,
495 ident: &syn::Ident,
496 element_type: &syn::Type,
497 ) {
498 env.inputs
499 .entry(location_key)
500 .unwrap()
501 .or_default()
502 .push((ident.clone(), element_type.clone()));
503 }
504
505 fn register_embedded_singleton_input(
506 env: &mut Self::InstantiateEnv,
507 location_key: LocationKey,
508 ident: &syn::Ident,
509 element_type: &syn::Type,
510 ) {
511 env.singleton_inputs
512 .entry(location_key)
513 .unwrap()
514 .or_default()
515 .push((ident.clone(), element_type.clone()));
516 }
517
518 fn register_embedded_output(
519 env: &mut Self::InstantiateEnv,
520 location_key: LocationKey,
521 ident: &syn::Ident,
522 element_type: &syn::Type,
523 ) {
524 env.outputs
525 .entry(location_key)
526 .unwrap()
527 .or_default()
528 .push((ident.clone(), element_type.clone()));
529 }
530}
531
532impl super::deploy::DeployFlow<'_, EmbeddedDeploy> {
533 pub fn generate_embedded(mut self, crate_name: &str) -> syn::File {
564 let mut env = EmbeddedInstantiateEnv::default();
565 let compiled = self.compile_internal(&mut env);
566
567 let root = crate::staging_util::get_this_crate();
568 let orig_crate_name = quote::format_ident!("{}", crate_name.replace('-', "_"));
569
570 let mut items: Vec<syn::Item> = Vec::new();
571
572 let mut location_keys: Vec<_> = compiled.all_dfir().keys().collect();
574 location_keys.sort();
575
576 let fn_names: SparseSecondaryMap<LocationKey, &str> = location_keys
578 .iter()
579 .map(|&k| {
580 let name = self
581 .processes
582 .get(k)
583 .map(|n| n.fn_name.as_str())
584 .or_else(|| self.clusters.get(k).map(|n| n.fn_name.as_str()))
585 .or_else(|| self.externals.get(k).map(|n| n.fn_name.as_str()))
586 .expect("location key not found in any node map");
587 (k, name)
588 })
589 .collect();
590
591 for location_key in location_keys {
592 let graph = compiled.all_dfir()[location_key]
593 .as_ref()
594 .unwrap_or_else(|err| {
595 panic!(
596 "Failed to partition DFIR graph for location {location_key}: {}",
597 err.diagnostic
598 )
599 });
600
601 let fn_name = fn_names[location_key];
603 let fn_ident = syn::Ident::new(fn_name, Span::call_site());
604
605 let mut loc_inputs = env.inputs.get(location_key).cloned().unwrap_or_default();
607 loc_inputs.sort_by(|a, b| a.0.cmp(&b.0));
608
609 let mut loc_outputs = env.outputs.get(location_key).cloned().unwrap_or_default();
611 loc_outputs.sort_by(|a, b| a.0.cmp(&b.0));
612
613 let mut diagnostics = Diagnostics::new();
614 let default_as_code_options = AsCodeOptions::default();
617 let as_code_options = compiled
618 .as_code_options
619 .get(location_key)
620 .unwrap_or(&default_as_code_options);
621 let dfir_tokens = graph
622 .as_code_with_options(
623 "e! { __root_dfir_rs },
624 as_code_options,
625 quote!(),
626 &mut diagnostics,
627 )
628 .expect("DFIR inline code generation failed with diagnostics.");
629
630 let mut mod_items: Vec<proc_macro2::TokenStream> = Vec::new();
632 let mut extra_fn_generics: Vec<proc_macro2::TokenStream> = Vec::new();
633 let mut cluster_params: Vec<proc_macro2::TokenStream> = Vec::new();
634 let mut output_params: Vec<proc_macro2::TokenStream> = Vec::new();
635 let mut net_out_params: Vec<proc_macro2::TokenStream> = Vec::new();
636 let mut net_in_params: Vec<proc_macro2::TokenStream> = Vec::new();
637 let mut extra_destructure: Vec<proc_macro2::TokenStream> = Vec::new();
638
639 if self.clusters.contains_key(location_key) {
641 cluster_params.push(quote! {
642 __cluster_self_id: &'a #root::location::member_id::TaglessMemberId
643 });
644 let self_id_ident = syn::Ident::new(
646 &format!("__hydro_lang_cluster_self_id_{}", location_key),
647 Span::call_site(),
648 );
649 extra_destructure.push(quote! {
650 let #self_id_ident = __cluster_self_id;
651 });
652 }
653
654 if let Some(loc_memberships) = env.membership_streams.get(location_key) {
656 let membership_struct_ident =
657 syn::Ident::new("EmbeddedMembershipStreams", Span::call_site());
658
659 let mem_generic_idents: Vec<syn::Ident> = loc_memberships
660 .iter()
661 .enumerate()
662 .map(|(i, _)| quote::format_ident!("__Mem{}", i))
663 .collect();
664
665 let mem_field_names: Vec<syn::Ident> = loc_memberships
666 .iter()
667 .map(|k| {
668 let cluster_fn_name = fn_names[*k];
669 syn::Ident::new(cluster_fn_name, Span::call_site())
670 })
671 .collect();
672
673 let struct_fields: Vec<proc_macro2::TokenStream> = mem_field_names
674 .iter()
675 .zip(mem_generic_idents.iter())
676 .map(|(field, generic)| {
677 quote! { pub #field: #generic }
678 })
679 .collect();
680
681 let struct_generics: Vec<proc_macro2::TokenStream> = mem_generic_idents
682 .iter()
683 .map(|generic| {
684 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #root::location::MembershipEvent)> + Unpin }
685 })
686 .collect();
687
688 for generic in &mem_generic_idents {
689 extra_fn_generics.push(
690 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #root::location::MembershipEvent)> + Unpin + 'a },
691 );
692 }
693
694 cluster_params.push(quote! {
695 __membership: #fn_ident::#membership_struct_ident<#(#mem_generic_idents),*>
696 });
697
698 for (i, field) in mem_field_names.iter().enumerate() {
699 let var_ident =
700 syn::Ident::new(&format!("__membership_{}", i), Span::call_site());
701 extra_destructure.push(quote! {
702 let #var_ident = __membership.#field;
703 });
704 }
705
706 mod_items.push(quote! {
707 pub struct #membership_struct_ident<#(#struct_generics),*> {
708 #(#struct_fields),*
709 }
710 });
711 }
712
713 let mut loc_singleton_inputs = env
716 .singleton_inputs
717 .get(location_key)
718 .cloned()
719 .unwrap_or_default();
720 loc_singleton_inputs.sort_by(|a, b| a.0.cmp(&b.0));
721
722 let mut input_params: Vec<proc_macro2::TokenStream> = Vec::new();
723 if !loc_inputs.is_empty() || !loc_singleton_inputs.is_empty() {
724 let input_struct_ident = syn::Ident::new("EmbeddedInputs", Span::call_site());
725
726 let input_generic_idents: Vec<syn::Ident> = loc_inputs
727 .iter()
728 .enumerate()
729 .map(|(i, _)| quote::format_ident!("__In{}", i))
730 .collect();
731
732 let struct_fields: Vec<proc_macro2::TokenStream> = loc_inputs
733 .iter()
734 .zip(input_generic_idents.iter())
735 .map(|((ident, _), generic)| quote! { pub #ident: #generic })
736 .chain(
737 loc_singleton_inputs
738 .iter()
739 .map(|(ident, element_type)| quote! { pub #ident: #element_type }),
740 )
741 .collect();
742
743 let struct_generics: Vec<proc_macro2::TokenStream> = loc_inputs
744 .iter()
745 .zip(input_generic_idents.iter())
746 .map(|((_, element_type), generic)| {
747 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #element_type> + Unpin }
748 })
749 .collect();
750
751 for ((_, element_type), generic) in
752 loc_inputs.iter().zip(input_generic_idents.iter())
753 {
754 extra_fn_generics.push(
755 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #element_type> + Unpin + 'a },
756 );
757 }
758
759 input_params.push(quote! {
760 __inputs: #fn_ident::#input_struct_ident<#(#input_generic_idents),*>
761 });
762
763 for (ident, _) in loc_inputs.iter().chain(&loc_singleton_inputs) {
764 extra_destructure.push(quote! { let #ident = __inputs.#ident; });
765 }
766
767 mod_items.push(quote! {
768 pub struct #input_struct_ident<#(#struct_generics),*> {
769 #(#struct_fields),*
770 }
771 });
772 }
773
774 if !loc_outputs.is_empty() {
776 let output_struct_ident = syn::Ident::new("EmbeddedOutputs", Span::call_site());
777
778 let output_generic_idents: Vec<syn::Ident> = loc_outputs
779 .iter()
780 .enumerate()
781 .map(|(i, _)| quote::format_ident!("__Out{}", i))
782 .collect();
783
784 let struct_fields: Vec<proc_macro2::TokenStream> = loc_outputs
785 .iter()
786 .zip(output_generic_idents.iter())
787 .map(|((ident, _), generic)| {
788 quote! { pub #ident: #generic }
789 })
790 .collect();
791
792 let struct_generics: Vec<proc_macro2::TokenStream> = loc_outputs
793 .iter()
794 .zip(output_generic_idents.iter())
795 .map(|((_, element_type), generic)| {
796 quote! { #generic: FnMut(#element_type) }
797 })
798 .collect();
799
800 for ((_, element_type), generic) in
801 loc_outputs.iter().zip(output_generic_idents.iter())
802 {
803 extra_fn_generics.push(quote! { #generic: FnMut(#element_type) + 'a });
804 }
805
806 output_params.push(quote! {
807 __outputs: &'a mut #fn_ident::#output_struct_ident<#(#output_generic_idents),*>
808 });
809
810 for (ident, _) in &loc_outputs {
811 extra_destructure.push(quote! { let mut #ident = &mut __outputs.#ident; });
812 }
813
814 mod_items.push(quote! {
815 pub struct #output_struct_ident<#(#struct_generics),*> {
816 #(#struct_fields),*
817 }
818 });
819 }
820
821 if let Some(mut loc_net_outputs) = env.network_outputs.remove(location_key) {
823 loc_net_outputs.sort_by(|a, b| a.0.cmp(&b.0));
824
825 let net_out_struct_ident = syn::Ident::new("EmbeddedNetworkOut", Span::call_site());
826
827 let net_out_generic_idents: Vec<syn::Ident> = loc_net_outputs
828 .iter()
829 .enumerate()
830 .map(|(i, _)| quote::format_ident!("__NetOut{}", i))
831 .collect();
832
833 let struct_fields: Vec<proc_macro2::TokenStream> = loc_net_outputs
834 .iter()
835 .zip(net_out_generic_idents.iter())
836 .map(|((name, _, _), generic)| {
837 let field_ident = syn::Ident::new(name, Span::call_site());
838 quote! { pub #field_ident: #generic }
839 })
840 .collect();
841
842 let struct_generics: Vec<proc_macro2::TokenStream> = loc_net_outputs
843 .iter()
844 .zip(net_out_generic_idents.iter())
845 .map(|((_, is_tagged, ext_ty), generic)| {
846 let payload = if let Some(ty) = ext_ty {
847 quote! { #ty }
848 } else {
849 quote! { #root::runtime_support::dfir_rs::bytes::Bytes }
850 };
851 if *is_tagged {
852 quote! { #generic: FnMut((#root::location::member_id::TaglessMemberId, #payload)) }
853 } else {
854 quote! { #generic: FnMut(#payload) }
855 }
856 })
857 .collect();
858
859 for ((_, is_tagged, ext_ty), generic) in
860 loc_net_outputs.iter().zip(net_out_generic_idents.iter())
861 {
862 let payload = if let Some(ty) = ext_ty {
863 quote! { #ty }
864 } else {
865 quote! { #root::runtime_support::dfir_rs::bytes::Bytes }
866 };
867 if *is_tagged {
868 extra_fn_generics.push(
869 quote! { #generic: FnMut((#root::location::member_id::TaglessMemberId, #payload)) + 'a },
870 );
871 } else {
872 extra_fn_generics.push(quote! { #generic: FnMut(#payload) + 'a });
873 }
874 }
875
876 net_out_params.push(quote! {
877 __network_out: &'a mut #fn_ident::#net_out_struct_ident<#(#net_out_generic_idents),*>
878 });
879
880 for (name, _, _) in &loc_net_outputs {
881 let field_ident = syn::Ident::new(name, Span::call_site());
882 let var_ident =
883 syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
884 extra_destructure
885 .push(quote! { let mut #var_ident = &mut __network_out.#field_ident; });
886 }
887
888 mod_items.push(quote! {
889 pub struct #net_out_struct_ident<#(#struct_generics),*> {
890 #(#struct_fields),*
891 }
892 });
893 }
894
895 if let Some(mut loc_net_inputs) = env.network_inputs.remove(location_key) {
897 loc_net_inputs.sort_by(|a, b| a.0.cmp(&b.0));
898
899 let net_in_struct_ident = syn::Ident::new("EmbeddedNetworkIn", Span::call_site());
900
901 let net_in_generic_idents: Vec<syn::Ident> = loc_net_inputs
902 .iter()
903 .enumerate()
904 .map(|(i, _)| quote::format_ident!("__NetIn{}", i))
905 .collect();
906
907 let struct_fields: Vec<proc_macro2::TokenStream> = loc_net_inputs
908 .iter()
909 .zip(net_in_generic_idents.iter())
910 .map(|((name, _, _), generic)| {
911 let field_ident = syn::Ident::new(name, Span::call_site());
912 quote! { pub #field_ident: #generic }
913 })
914 .collect();
915
916 let struct_generics: Vec<proc_macro2::TokenStream> = loc_net_inputs
917 .iter()
918 .zip(net_in_generic_idents.iter())
919 .map(|((_, is_tagged, ext_ty), generic)| {
920 match ext_ty {
921 Some(ty) => {
924 if *is_tagged {
925 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #ty)> + Unpin }
926 } else {
927 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #ty> + Unpin }
928 }
929 }
930 None => {
931 if *is_tagged {
932 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<(#root::location::member_id::TaglessMemberId, __root_dfir_rs::bytes::BytesMut), std::io::Error>> + Unpin }
933 } else {
934 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<__root_dfir_rs::bytes::BytesMut, std::io::Error>> + Unpin }
935 }
936 }
937 }
938 })
939 .collect();
940
941 for ((_, is_tagged, ext_ty), generic) in
942 loc_net_inputs.iter().zip(net_in_generic_idents.iter())
943 {
944 match ext_ty {
945 Some(ty) => {
946 if *is_tagged {
947 extra_fn_generics.push(
948 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #ty)> + Unpin + 'a },
949 );
950 } else {
951 extra_fn_generics.push(
952 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #ty> + Unpin + 'a },
953 );
954 }
955 }
956 None => {
957 if *is_tagged {
958 extra_fn_generics.push(
959 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<(#root::location::member_id::TaglessMemberId, __root_dfir_rs::bytes::BytesMut), std::io::Error>> + Unpin + 'a },
960 );
961 } else {
962 extra_fn_generics.push(
963 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<__root_dfir_rs::bytes::BytesMut, std::io::Error>> + Unpin + 'a },
964 );
965 }
966 }
967 }
968 }
969
970 net_in_params.push(quote! {
971 __network_in: #fn_ident::#net_in_struct_ident<#(#net_in_generic_idents),*>
972 });
973
974 for (name, _, _) in &loc_net_inputs {
975 let field_ident = syn::Ident::new(name, Span::call_site());
976 let var_ident =
977 syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
978 extra_destructure.push(quote! { let #var_ident = __network_in.#field_ident; });
979 }
980
981 mod_items.push(quote! {
982 pub struct #net_in_struct_ident<#(#struct_generics),*> {
983 #(#struct_fields),*
984 }
985 });
986 }
987
988 if !mod_items.is_empty() {
990 let output_mod: syn::Item = syn::parse_quote! {
991 pub mod #fn_ident {
992 use super::*;
993 #(#mod_items)*
994 }
995 };
996 items.push(output_mod);
997 }
998
999 let all_params: Vec<proc_macro2::TokenStream> = cluster_params
1001 .into_iter()
1002 .chain(input_params)
1003 .chain(output_params)
1004 .chain(net_in_params)
1005 .chain(net_out_params)
1006 .collect();
1007
1008 let ret_type: syn::Type = syn::parse_quote! { #root::runtime_support::dfir_rs::scheduled::context::Dfir<impl #root::runtime_support::dfir_rs::scheduled::context::TickClosure + 'a> };
1009
1010 let func = if !extra_fn_generics.is_empty() {
1011 syn::parse_quote! {
1012 #[allow(unused, non_snake_case, clippy::suspicious_else_formatting)]
1013 pub fn #fn_ident<'a, #(#extra_fn_generics),*>(#(#all_params),*) -> #ret_type {
1014 #(#extra_destructure)*
1015 #dfir_tokens
1016 }
1017 }
1018 } else {
1019 syn::parse_quote! {
1020 #[allow(unused, non_snake_case, clippy::suspicious_else_formatting)]
1021 pub fn #fn_ident<'a>(#(#all_params),*) -> #ret_type {
1022 #dfir_tokens
1023 }
1024 }
1025 };
1026
1027 items.push(func);
1028 }
1029
1030 syn::parse_quote! {
1031 use #orig_crate_name::__staged::__deps::*;
1032 use #root::prelude::*;
1033 use #root::runtime_support::dfir_rs as __root_dfir_rs;
1034 pub use #orig_crate_name::__staged;
1035
1036 #( #items )*
1037 }
1038 }
1039}