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 input_params: Vec<proc_macro2::TokenStream> = loc_inputs
715 .iter()
716 .map(|(ident, element_type)| {
717 quote! { #ident: impl __root_dfir_rs::futures::Stream<Item = #element_type> + Unpin + 'a }
718 })
719 .collect();
720
721 let mut loc_singleton_inputs = env
723 .singleton_inputs
724 .get(location_key)
725 .cloned()
726 .unwrap_or_default();
727 loc_singleton_inputs.sort_by(|a, b| a.0.cmp(&b.0));
728
729 let singleton_input_params: Vec<proc_macro2::TokenStream> = loc_singleton_inputs
730 .iter()
731 .map(|(ident, element_type)| {
732 quote! { #ident: #element_type }
733 })
734 .collect();
735
736 if !loc_outputs.is_empty() {
738 let output_struct_ident = syn::Ident::new("EmbeddedOutputs", Span::call_site());
739
740 let output_generic_idents: Vec<syn::Ident> = loc_outputs
741 .iter()
742 .enumerate()
743 .map(|(i, _)| quote::format_ident!("__Out{}", i))
744 .collect();
745
746 let struct_fields: Vec<proc_macro2::TokenStream> = loc_outputs
747 .iter()
748 .zip(output_generic_idents.iter())
749 .map(|((ident, _), generic)| {
750 quote! { pub #ident: #generic }
751 })
752 .collect();
753
754 let struct_generics: Vec<proc_macro2::TokenStream> = loc_outputs
755 .iter()
756 .zip(output_generic_idents.iter())
757 .map(|((_, element_type), generic)| {
758 quote! { #generic: FnMut(#element_type) }
759 })
760 .collect();
761
762 for ((_, element_type), generic) in
763 loc_outputs.iter().zip(output_generic_idents.iter())
764 {
765 extra_fn_generics.push(quote! { #generic: FnMut(#element_type) + 'a });
766 }
767
768 output_params.push(quote! {
769 __outputs: &'a mut #fn_ident::#output_struct_ident<#(#output_generic_idents),*>
770 });
771
772 for (ident, _) in &loc_outputs {
773 extra_destructure.push(quote! { let mut #ident = &mut __outputs.#ident; });
774 }
775
776 mod_items.push(quote! {
777 pub struct #output_struct_ident<#(#struct_generics),*> {
778 #(#struct_fields),*
779 }
780 });
781 }
782
783 if let Some(mut loc_net_outputs) = env.network_outputs.remove(location_key) {
785 loc_net_outputs.sort_by(|a, b| a.0.cmp(&b.0));
786
787 let net_out_struct_ident = syn::Ident::new("EmbeddedNetworkOut", Span::call_site());
788
789 let net_out_generic_idents: Vec<syn::Ident> = loc_net_outputs
790 .iter()
791 .enumerate()
792 .map(|(i, _)| quote::format_ident!("__NetOut{}", i))
793 .collect();
794
795 let struct_fields: Vec<proc_macro2::TokenStream> = loc_net_outputs
796 .iter()
797 .zip(net_out_generic_idents.iter())
798 .map(|((name, _, _), generic)| {
799 let field_ident = syn::Ident::new(name, Span::call_site());
800 quote! { pub #field_ident: #generic }
801 })
802 .collect();
803
804 let struct_generics: Vec<proc_macro2::TokenStream> = loc_net_outputs
805 .iter()
806 .zip(net_out_generic_idents.iter())
807 .map(|((_, is_tagged, ext_ty), generic)| {
808 let payload = if let Some(ty) = ext_ty {
809 quote! { #ty }
810 } else {
811 quote! { #root::runtime_support::dfir_rs::bytes::Bytes }
812 };
813 if *is_tagged {
814 quote! { #generic: FnMut((#root::location::member_id::TaglessMemberId, #payload)) }
815 } else {
816 quote! { #generic: FnMut(#payload) }
817 }
818 })
819 .collect();
820
821 for ((_, is_tagged, ext_ty), generic) in
822 loc_net_outputs.iter().zip(net_out_generic_idents.iter())
823 {
824 let payload = if let Some(ty) = ext_ty {
825 quote! { #ty }
826 } else {
827 quote! { #root::runtime_support::dfir_rs::bytes::Bytes }
828 };
829 if *is_tagged {
830 extra_fn_generics.push(
831 quote! { #generic: FnMut((#root::location::member_id::TaglessMemberId, #payload)) + 'a },
832 );
833 } else {
834 extra_fn_generics.push(quote! { #generic: FnMut(#payload) + 'a });
835 }
836 }
837
838 net_out_params.push(quote! {
839 __network_out: &'a mut #fn_ident::#net_out_struct_ident<#(#net_out_generic_idents),*>
840 });
841
842 for (name, _, _) in &loc_net_outputs {
843 let field_ident = syn::Ident::new(name, Span::call_site());
844 let var_ident =
845 syn::Ident::new(&format!("__network_out_{name}"), Span::call_site());
846 extra_destructure
847 .push(quote! { let mut #var_ident = &mut __network_out.#field_ident; });
848 }
849
850 mod_items.push(quote! {
851 pub struct #net_out_struct_ident<#(#struct_generics),*> {
852 #(#struct_fields),*
853 }
854 });
855 }
856
857 if let Some(mut loc_net_inputs) = env.network_inputs.remove(location_key) {
859 loc_net_inputs.sort_by(|a, b| a.0.cmp(&b.0));
860
861 let net_in_struct_ident = syn::Ident::new("EmbeddedNetworkIn", Span::call_site());
862
863 let net_in_generic_idents: Vec<syn::Ident> = loc_net_inputs
864 .iter()
865 .enumerate()
866 .map(|(i, _)| quote::format_ident!("__NetIn{}", i))
867 .collect();
868
869 let struct_fields: Vec<proc_macro2::TokenStream> = loc_net_inputs
870 .iter()
871 .zip(net_in_generic_idents.iter())
872 .map(|((name, _, _), generic)| {
873 let field_ident = syn::Ident::new(name, Span::call_site());
874 quote! { pub #field_ident: #generic }
875 })
876 .collect();
877
878 let struct_generics: Vec<proc_macro2::TokenStream> = loc_net_inputs
879 .iter()
880 .zip(net_in_generic_idents.iter())
881 .map(|((_, is_tagged, ext_ty), generic)| {
882 match ext_ty {
883 Some(ty) => {
886 if *is_tagged {
887 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #ty)> + Unpin }
888 } else {
889 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #ty> + Unpin }
890 }
891 }
892 None => {
893 if *is_tagged {
894 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<(#root::location::member_id::TaglessMemberId, __root_dfir_rs::bytes::BytesMut), std::io::Error>> + Unpin }
895 } else {
896 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<__root_dfir_rs::bytes::BytesMut, std::io::Error>> + Unpin }
897 }
898 }
899 }
900 })
901 .collect();
902
903 for ((_, is_tagged, ext_ty), generic) in
904 loc_net_inputs.iter().zip(net_in_generic_idents.iter())
905 {
906 match ext_ty {
907 Some(ty) => {
908 if *is_tagged {
909 extra_fn_generics.push(
910 quote! { #generic: __root_dfir_rs::futures::Stream<Item = (#root::location::member_id::TaglessMemberId, #ty)> + Unpin + 'a },
911 );
912 } else {
913 extra_fn_generics.push(
914 quote! { #generic: __root_dfir_rs::futures::Stream<Item = #ty> + Unpin + 'a },
915 );
916 }
917 }
918 None => {
919 if *is_tagged {
920 extra_fn_generics.push(
921 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<(#root::location::member_id::TaglessMemberId, __root_dfir_rs::bytes::BytesMut), std::io::Error>> + Unpin + 'a },
922 );
923 } else {
924 extra_fn_generics.push(
925 quote! { #generic: __root_dfir_rs::futures::Stream<Item = Result<__root_dfir_rs::bytes::BytesMut, std::io::Error>> + Unpin + 'a },
926 );
927 }
928 }
929 }
930 }
931
932 net_in_params.push(quote! {
933 __network_in: #fn_ident::#net_in_struct_ident<#(#net_in_generic_idents),*>
934 });
935
936 for (name, _, _) in &loc_net_inputs {
937 let field_ident = syn::Ident::new(name, Span::call_site());
938 let var_ident =
939 syn::Ident::new(&format!("__network_in_{name}"), Span::call_site());
940 extra_destructure.push(quote! { let #var_ident = __network_in.#field_ident; });
941 }
942
943 mod_items.push(quote! {
944 pub struct #net_in_struct_ident<#(#struct_generics),*> {
945 #(#struct_fields),*
946 }
947 });
948 }
949
950 if !mod_items.is_empty() {
952 let output_mod: syn::Item = syn::parse_quote! {
953 pub mod #fn_ident {
954 use super::*;
955 #(#mod_items)*
956 }
957 };
958 items.push(output_mod);
959 }
960
961 let all_params: Vec<proc_macro2::TokenStream> = cluster_params
963 .into_iter()
964 .chain(singleton_input_params)
965 .chain(input_params)
966 .chain(output_params)
967 .chain(net_in_params)
968 .chain(net_out_params)
969 .collect();
970
971 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> };
972
973 let func = if !extra_fn_generics.is_empty() {
974 syn::parse_quote! {
975 #[allow(unused, non_snake_case, clippy::suspicious_else_formatting)]
976 pub fn #fn_ident<'a, #(#extra_fn_generics),*>(#(#all_params),*) -> #ret_type {
977 #(#extra_destructure)*
978 #dfir_tokens
979 }
980 }
981 } else {
982 syn::parse_quote! {
983 #[allow(unused, non_snake_case, clippy::suspicious_else_formatting)]
984 pub fn #fn_ident<'a>(#(#all_params),*) -> #ret_type {
985 #dfir_tokens
986 }
987 }
988 };
989
990 items.push(func);
991 }
992
993 syn::parse_quote! {
994 use #orig_crate_name::__staged::__deps::*;
995 use #root::prelude::*;
996 use #root::runtime_support::dfir_rs as __root_dfir_rs;
997 pub use #orig_crate_name::__staged;
998
999 #( #items )*
1000 }
1001 }
1002}