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