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 .as_ref()
593 .unwrap_or_else(|err| {
594 panic!(
595 "Failed to partition DFIR graph for location {location_key}: {}",
596 err.diagnostic
597 )
598 });
599
600 let fn_name = fn_names[location_key];
602 let fn_ident = syn::Ident::new(fn_name, Span::call_site());
603
604 let mut loc_inputs = env.inputs.get(location_key).cloned().unwrap_or_default();
606 loc_inputs.sort_by(|a, b| a.0.cmp(&b.0));
607
608 let mut loc_outputs = env.outputs.get(location_key).cloned().unwrap_or_default();
610 loc_outputs.sort_by(|a, b| a.0.cmp(&b.0));
611
612 let mut diagnostics = Diagnostics::new();
613 let dfir_tokens = graph
614 .as_code("e! { __root_dfir_rs }, true, quote!(), &mut diagnostics)
615 .expect("DFIR inline code generation failed with diagnostics.");
616
617 let mut mod_items: Vec<proc_macro2::TokenStream> = Vec::new();
619 let mut extra_fn_generics: Vec<proc_macro2::TokenStream> = Vec::new();
620 let mut cluster_params: Vec<proc_macro2::TokenStream> = Vec::new();
621 let mut output_params: Vec<proc_macro2::TokenStream> = Vec::new();
622 let mut net_out_params: Vec<proc_macro2::TokenStream> = Vec::new();
623 let mut net_in_params: Vec<proc_macro2::TokenStream> = Vec::new();
624 let mut extra_destructure: Vec<proc_macro2::TokenStream> = Vec::new();
625
626 if self.clusters.contains_key(location_key) {
628 cluster_params.push(quote! {
629 __cluster_self_id: &'a #root::location::member_id::TaglessMemberId
630 });
631 let self_id_ident = syn::Ident::new(
633 &format!("__hydro_lang_cluster_self_id_{}", location_key),
634 Span::call_site(),
635 );
636 extra_destructure.push(quote! {
637 let #self_id_ident = __cluster_self_id;
638 });
639 }
640
641 if let Some(loc_memberships) = env.membership_streams.get(location_key) {
643 let membership_struct_ident =
644 syn::Ident::new("EmbeddedMembershipStreams", Span::call_site());
645
646 let mem_generic_idents: Vec<syn::Ident> = loc_memberships
647 .iter()
648 .enumerate()
649 .map(|(i, _)| quote::format_ident!("__Mem{}", i))
650 .collect();
651
652 let mem_field_names: Vec<syn::Ident> = loc_memberships
653 .iter()
654 .map(|k| {
655 let cluster_fn_name = fn_names[*k];
656 syn::Ident::new(cluster_fn_name, Span::call_site())
657 })
658 .collect();
659
660 let struct_fields: Vec<proc_macro2::TokenStream> = mem_field_names
661 .iter()
662 .zip(mem_generic_idents.iter())
663 .map(|(field, generic)| {
664 quote! { pub #field: #generic }
665 })
666 .collect();
667
668 let struct_generics: Vec<proc_macro2::TokenStream> = mem_generic_idents
669 .iter()
670 .map(|generic| {
671 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #root::location::MembershipEvent)> + Unpin }
672 })
673 .collect();
674
675 for generic in &mem_generic_idents {
676 extra_fn_generics.push(
677 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #root::location::MembershipEvent)> + Unpin + 'a },
678 );
679 }
680
681 cluster_params.push(quote! {
682 __membership: #fn_ident::#membership_struct_ident<#(#mem_generic_idents),*>
683 });
684
685 for (i, field) in mem_field_names.iter().enumerate() {
686 let var_ident =
687 syn::Ident::new(&format!("__membership_{}", i), Span::call_site());
688 extra_destructure.push(quote! {
689 let #var_ident = __membership.#field;
690 });
691 }
692
693 mod_items.push(quote! {
694 pub struct #membership_struct_ident<#(#struct_generics),*> {
695 #(#struct_fields),*
696 }
697 });
698 }
699
700 let input_params: Vec<proc_macro2::TokenStream> = loc_inputs
702 .iter()
703 .map(|(ident, element_type)| {
704 quote! { #ident: impl __root_dfir_rs::futures::Stream<Item = #element_type> + Unpin + 'a }
705 })
706 .collect();
707
708 let mut loc_singleton_inputs = env
710 .singleton_inputs
711 .get(location_key)
712 .cloned()
713 .unwrap_or_default();
714 loc_singleton_inputs.sort_by(|a, b| a.0.cmp(&b.0));
715
716 let singleton_input_params: Vec<proc_macro2::TokenStream> = loc_singleton_inputs
717 .iter()
718 .map(|(ident, element_type)| {
719 quote! { #ident: #element_type }
720 })
721 .collect();
722
723 if !loc_outputs.is_empty() {
725 let output_struct_ident = syn::Ident::new("EmbeddedOutputs", Span::call_site());
726
727 let output_generic_idents: Vec<syn::Ident> = loc_outputs
728 .iter()
729 .enumerate()
730 .map(|(i, _)| quote::format_ident!("__Out{}", i))
731 .collect();
732
733 let struct_fields: Vec<proc_macro2::TokenStream> = loc_outputs
734 .iter()
735 .zip(output_generic_idents.iter())
736 .map(|((ident, _), generic)| {
737 quote! { pub #ident: #generic }
738 })
739 .collect();
740
741 let struct_generics: Vec<proc_macro2::TokenStream> = loc_outputs
742 .iter()
743 .zip(output_generic_idents.iter())
744 .map(|((_, element_type), generic)| {
745 quote! { #generic: FnMut(#element_type) }
746 })
747 .collect();
748
749 for ((_, element_type), generic) in
750 loc_outputs.iter().zip(output_generic_idents.iter())
751 {
752 extra_fn_generics.push(quote! { #generic: FnMut(#element_type) + 'a });
753 }
754
755 output_params.push(quote! {
756 __outputs: &'a mut #fn_ident::#output_struct_ident<#(#output_generic_idents),*>
757 });
758
759 for (ident, _) in &loc_outputs {
760 extra_destructure.push(quote! { let mut #ident = &mut __outputs.#ident; });
761 }
762
763 mod_items.push(quote! {
764 pub struct #output_struct_ident<#(#struct_generics),*> {
765 #(#struct_fields),*
766 }
767 });
768 }
769
770 if let Some(mut loc_net_outputs) = env.network_outputs.remove(location_key) {
772 loc_net_outputs.sort_by(|a, b| a.0.cmp(&b.0));
773
774 let net_out_struct_ident = syn::Ident::new("EmbeddedNetworkOut", Span::call_site());
775
776 let net_out_generic_idents: Vec<syn::Ident> = loc_net_outputs
777 .iter()
778 .enumerate()
779 .map(|(i, _)| quote::format_ident!("__NetOut{}", i))
780 .collect();
781
782 let struct_fields: Vec<proc_macro2::TokenStream> = loc_net_outputs
783 .iter()
784 .zip(net_out_generic_idents.iter())
785 .map(|((name, _, _), generic)| {
786 let field_ident = syn::Ident::new(name, Span::call_site());
787 quote! { pub #field_ident: #generic }
788 })
789 .collect();
790
791 let struct_generics: Vec<proc_macro2::TokenStream> = loc_net_outputs
792 .iter()
793 .zip(net_out_generic_idents.iter())
794 .map(|((_, is_tagged, ext_ty), generic)| {
795 let payload = if let Some(ty) = ext_ty {
796 quote! { #ty }
797 } else {
798 quote! { #root::runtime_support::dfir_rs::bytes::Bytes }
799 };
800 if *is_tagged {
801 quote! { #generic: FnMut((#root::location::member_id::TaglessMemberId, #payload)) }
802 } else {
803 quote! { #generic: FnMut(#payload) }
804 }
805 })
806 .collect();
807
808 for ((_, is_tagged, ext_ty), generic) in
809 loc_net_outputs.iter().zip(net_out_generic_idents.iter())
810 {
811 let payload = if let Some(ty) = ext_ty {
812 quote! { #ty }
813 } else {
814 quote! { #root::runtime_support::dfir_rs::bytes::Bytes }
815 };
816 if *is_tagged {
817 extra_fn_generics.push(
818 quote! { #generic: FnMut((#root::location::member_id::TaglessMemberId, #payload)) + 'a },
819 );
820 } else {
821 extra_fn_generics.push(quote! { #generic: FnMut(#payload) + 'a });
822 }
823 }
824
825 net_out_params.push(quote! {
826 __network_out: &'a mut #fn_ident::#net_out_struct_ident<#(#net_out_generic_idents),*>
827 });
828
829 for (name, _, _) in &loc_net_outputs {
830 let field_ident = syn::Ident::new(name, Span::call_site());
831 let var_ident =
832 syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
833 extra_destructure
834 .push(quote! { let mut #var_ident = &mut __network_out.#field_ident; });
835 }
836
837 mod_items.push(quote! {
838 pub struct #net_out_struct_ident<#(#struct_generics),*> {
839 #(#struct_fields),*
840 }
841 });
842 }
843
844 if let Some(mut loc_net_inputs) = env.network_inputs.remove(location_key) {
846 loc_net_inputs.sort_by(|a, b| a.0.cmp(&b.0));
847
848 let net_in_struct_ident = syn::Ident::new("EmbeddedNetworkIn", Span::call_site());
849
850 let net_in_generic_idents: Vec<syn::Ident> = loc_net_inputs
851 .iter()
852 .enumerate()
853 .map(|(i, _)| quote::format_ident!("__NetIn{}", i))
854 .collect();
855
856 let struct_fields: Vec<proc_macro2::TokenStream> = loc_net_inputs
857 .iter()
858 .zip(net_in_generic_idents.iter())
859 .map(|((name, _, _), generic)| {
860 let field_ident = syn::Ident::new(name, Span::call_site());
861 quote! { pub #field_ident: #generic }
862 })
863 .collect();
864
865 let struct_generics: Vec<proc_macro2::TokenStream> = loc_net_inputs
866 .iter()
867 .zip(net_in_generic_idents.iter())
868 .map(|((_, is_tagged, ext_ty), generic)| {
869 match ext_ty {
870 Some(ty) => {
873 if *is_tagged {
874 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #ty)> + Unpin }
875 } else {
876 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #ty> + Unpin }
877 }
878 }
879 None => {
880 if *is_tagged {
881 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<(#root::location::member_id::TaglessMemberId, __root_dfir_rs::bytes::BytesMut), std::io::Error>> + Unpin }
882 } else {
883 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<__root_dfir_rs::bytes::BytesMut, std::io::Error>> + Unpin }
884 }
885 }
886 }
887 })
888 .collect();
889
890 for ((_, is_tagged, ext_ty), generic) in
891 loc_net_inputs.iter().zip(net_in_generic_idents.iter())
892 {
893 match ext_ty {
894 Some(ty) => {
895 if *is_tagged {
896 extra_fn_generics.push(
897 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #ty)> + Unpin + 'a },
898 );
899 } else {
900 extra_fn_generics.push(
901 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #ty> + Unpin + 'a },
902 );
903 }
904 }
905 None => {
906 if *is_tagged {
907 extra_fn_generics.push(
908 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<(#root::location::member_id::TaglessMemberId, __root_dfir_rs::bytes::BytesMut), std::io::Error>> + Unpin + 'a },
909 );
910 } else {
911 extra_fn_generics.push(
912 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<__root_dfir_rs::bytes::BytesMut, std::io::Error>> + Unpin + 'a },
913 );
914 }
915 }
916 }
917 }
918
919 net_in_params.push(quote! {
920 __network_in: #fn_ident::#net_in_struct_ident<#(#net_in_generic_idents),*>
921 });
922
923 for (name, _, _) in &loc_net_inputs {
924 let field_ident = syn::Ident::new(name, Span::call_site());
925 let var_ident =
926 syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
927 extra_destructure.push(quote! { let #var_ident = __network_in.#field_ident; });
928 }
929
930 mod_items.push(quote! {
931 pub struct #net_in_struct_ident<#(#struct_generics),*> {
932 #(#struct_fields),*
933 }
934 });
935 }
936
937 if !mod_items.is_empty() {
939 let output_mod: syn::Item = syn::parse_quote! {
940 pub mod #fn_ident {
941 use super::*;
942 #(#mod_items)*
943 }
944 };
945 items.push(output_mod);
946 }
947
948 let all_params: Vec<proc_macro2::TokenStream> = cluster_params
950 .into_iter()
951 .chain(singleton_input_params)
952 .chain(input_params)
953 .chain(output_params)
954 .chain(net_in_params)
955 .chain(net_out_params)
956 .collect();
957
958 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> };
959
960 let func = if !extra_fn_generics.is_empty() {
961 syn::parse_quote! {
962 #[allow(unused, non_snake_case, clippy::suspicious_else_formatting)]
963 pub fn #fn_ident<'a, #(#extra_fn_generics),*>(#(#all_params),*) -> #ret_type {
964 #(#extra_destructure)*
965 #dfir_tokens
966 }
967 }
968 } else {
969 syn::parse_quote! {
970 #[allow(unused, non_snake_case, clippy::suspicious_else_formatting)]
971 pub fn #fn_ident<'a>(#(#all_params),*) -> #ret_type {
972 #dfir_tokens
973 }
974 }
975 };
976
977 items.push(func);
978 }
979
980 syn::parse_quote! {
981 use #orig_crate_name::__staged::__deps::*;
982 use #root::prelude::*;
983 use #root::runtime_support::dfir_rs as __root_dfir_rs;
984 pub use #orig_crate_name::__staged;
985
986 #( #items )*
987 }
988 }
989}