1use std::any::type_name;
2use std::cell::RefCell;
3use std::marker::PhantomData;
4use std::rc::Rc;
5
6use slotmap::{SecondaryMap, SlotMap};
7
8#[cfg(feature = "build")]
9use super::compiled::CompiledFlow;
10#[cfg(feature = "build")]
11use super::deploy::{DeployFlow, DeployResult};
12#[cfg(feature = "build")]
13use super::deploy_provider::{ClusterSpec, Deploy, ExternalSpec, IntoProcessSpec};
14use super::ir::HydroRoot;
15use crate::location::{Cluster, External, LocationKey, LocationType, Process};
16#[cfg(feature = "sim")]
17#[cfg(stageleft_runtime)]
18use crate::sim::flow::SimFlow;
19use crate::staging_util::Invariant;
20
21#[stageleft::export(ExternalPortId, CycleId, ClockId)]
22crate::newtype_counter! {
23 pub struct ExternalPortId(usize);
25
26 pub struct CycleId(usize);
28
29 pub struct ClockId(usize);
31}
32
33impl CycleId {
34 #[cfg(feature = "build")]
35 pub(crate) fn as_ident(&self) -> syn::Ident {
36 syn::Ident::new(&format!("cycle_{}", self), proc_macro2::Span::call_site())
37 }
38}
39
40pub(crate) type FlowState = Rc<RefCell<FlowStateInner>>;
41
42pub(crate) struct FlowStateInner {
43 roots: Option<Vec<HydroRoot>>,
47
48 next_external_port: ExternalPortId,
50
51 next_cycle_id: CycleId,
53
54 next_clock_id: ClockId,
56}
57
58impl FlowStateInner {
59 pub fn next_external_port(&mut self) -> ExternalPortId {
60 self.next_external_port.get_and_increment()
61 }
62
63 pub fn next_cycle_id(&mut self) -> CycleId {
64 self.next_cycle_id.get_and_increment()
65 }
66
67 pub fn next_clock_id(&mut self) -> ClockId {
68 self.next_clock_id.get_and_increment()
69 }
70
71 pub fn push_root(&mut self, root: HydroRoot) {
72 self.roots
73 .as_mut()
74 .expect("Attempted to add a root to a flow that has already been finalized. No roots can be added after the flow has been compiled.")
75 .push(root);
76 }
77}
78
79pub struct FlowBuilder<'a> {
80 flow_state: FlowState,
82
83 locations: SlotMap<LocationKey, LocationType>,
85 location_names: SecondaryMap<LocationKey, String>,
87
88 #[cfg_attr(
90 not(feature = "build"),
91 expect(dead_code, reason = "unused without build")
92 )]
93 flow_name: String,
94
95 finalized: bool,
98
99 _phantom: Invariant<'a>,
104}
105
106impl Drop for FlowBuilder<'_> {
107 fn drop(&mut self) {
108 if !self.finalized && !std::thread::panicking() {
109 panic!(
110 "Dropped FlowBuilder without finalizing, you may have forgotten to call `with_default_optimize`, `optimize_with`, or `finalize`."
111 );
112 }
113 }
114}
115
116#[expect(missing_docs, reason = "TODO")]
117impl<'a> FlowBuilder<'a> {
118 #[expect(
120 clippy::new_without_default,
121 reason = "call `new` explicitly, not `default`"
122 )]
123 pub fn new() -> Self {
124 let mut name = std::env::var("CARGO_PKG_NAME").unwrap_or_else(|_| "unknown".to_owned());
125 if let Ok(bin_path) = std::env::current_exe()
126 && let Some(bin_name) = bin_path.file_stem()
127 {
128 name = format!("{}/{}", name, bin_name.display());
129 }
130 Self::with_name(name)
131 }
132
133 pub fn with_name(name: impl Into<String>) -> Self {
135 Self {
136 flow_state: Rc::new(RefCell::new(FlowStateInner {
137 roots: Some(vec![]),
138 next_external_port: ExternalPortId::default(),
139 next_cycle_id: CycleId::default(),
140 next_clock_id: ClockId::default(),
141 })),
142 locations: SlotMap::with_key(),
143 location_names: SecondaryMap::new(),
144 flow_name: name.into(),
145 finalized: false,
146 _phantom: PhantomData,
147 }
148 }
149
150 pub(crate) fn flow_state(&self) -> &FlowState {
151 &self.flow_state
152 }
153
154 pub fn process<P>(&mut self) -> Process<'a, P> {
155 let key = self.locations.insert(LocationType::Process);
156 self.location_names.insert(key, type_name::<P>().to_owned());
157 Process {
158 key,
159 flow_state: self.flow_state().clone(),
160 _phantom: PhantomData,
161 }
162 }
163
164 pub fn cluster<C>(&mut self) -> Cluster<'a, C> {
165 let key = self.locations.insert(LocationType::Cluster);
166 self.location_names.insert(key, type_name::<C>().to_owned());
167 Cluster {
168 key,
169 flow_state: self.flow_state().clone(),
170 _phantom: PhantomData,
171 }
172 }
173
174 pub fn external<E>(&mut self) -> External<'a, E> {
175 let key = self.locations.insert(LocationType::External);
176 self.location_names.insert(key, type_name::<E>().to_owned());
177 External {
178 key,
179 flow_state: self.flow_state().clone(),
180 _phantom: PhantomData,
181 }
182 }
183}
184
185#[cfg(feature = "build")]
186#[cfg_attr(docsrs, doc(cfg(feature = "build")))]
187#[expect(missing_docs, reason = "TODO")]
188impl<'a> FlowBuilder<'a> {
189 pub fn finalize(mut self) -> super::built::BuiltFlow<'a> {
190 self.finalized = true;
191
192 super::built::BuiltFlow {
193 ir: self.flow_state.borrow_mut().roots.take().unwrap(),
194 locations: std::mem::take(&mut self.locations),
195 location_names: std::mem::take(&mut self.location_names),
196 flow_name: std::mem::take(&mut self.flow_name),
197 _phantom: PhantomData,
198 }
199 }
200
201 pub fn with_default_optimize<D: Deploy<'a>>(self) -> DeployFlow<'a, D> {
202 self.finalize().with_default_optimize()
203 }
204
205 pub fn optimize_with(self, f: impl FnOnce(&mut [HydroRoot])) -> super::built::BuiltFlow<'a> {
206 self.finalize().optimize_with(f)
207 }
208
209 pub fn with_process<P, D: Deploy<'a>>(
210 self,
211 process: &Process<P>,
212 spec: impl IntoProcessSpec<'a, D>,
213 ) -> DeployFlow<'a, D> {
214 self.with_default_optimize().with_process(process, spec)
215 }
216
217 pub fn with_remaining_processes<D: Deploy<'a>, S: IntoProcessSpec<'a, D> + 'a>(
218 self,
219 spec: impl Fn() -> S,
220 ) -> DeployFlow<'a, D> {
221 self.with_default_optimize().with_remaining_processes(spec)
222 }
223
224 pub fn with_external<P, D: Deploy<'a>>(
225 self,
226 process: &External<P>,
227 spec: impl ExternalSpec<'a, D>,
228 ) -> DeployFlow<'a, D> {
229 self.with_default_optimize().with_external(process, spec)
230 }
231
232 pub fn with_remaining_externals<D: Deploy<'a>, S: ExternalSpec<'a, D> + 'a>(
233 self,
234 spec: impl Fn() -> S,
235 ) -> DeployFlow<'a, D> {
236 self.with_default_optimize().with_remaining_externals(spec)
237 }
238
239 pub fn with_cluster<C, D: Deploy<'a>>(
240 self,
241 cluster: &Cluster<C>,
242 spec: impl ClusterSpec<'a, D>,
243 ) -> DeployFlow<'a, D> {
244 self.with_default_optimize().with_cluster(cluster, spec)
245 }
246
247 pub fn with_remaining_clusters<D: Deploy<'a>, S: ClusterSpec<'a, D> + 'a>(
248 self,
249 spec: impl Fn() -> S,
250 ) -> DeployFlow<'a, D> {
251 self.with_default_optimize().with_remaining_clusters(spec)
252 }
253
254 pub fn compile<D: Deploy<'a, InstantiateEnv = ()>>(self) -> CompiledFlow<'a> {
255 self.with_default_optimize::<D>().compile()
256 }
257
258 pub fn deploy<D: Deploy<'a>>(self, env: &mut D::InstantiateEnv) -> DeployResult<'a, D> {
259 self.with_default_optimize().deploy(env)
260 }
261
262 #[cfg(feature = "sim")]
263 pub fn sim(self) -> SimFlow<'a> {
266 self.finalize().sim()
267 }
268
269 pub fn from_built<'b>(built: &super::built::BuiltFlow) -> FlowBuilder<'b> {
270 FlowBuilder {
271 flow_state: Rc::new(RefCell::new(FlowStateInner {
272 roots: None,
273 next_external_port: ExternalPortId::default(),
274 next_cycle_id: CycleId::default(),
275 next_clock_id: ClockId::default(),
276 })),
277 locations: built.locations.clone(),
278 location_names: built.location_names.clone(),
279 flow_name: built.flow_name.clone(),
280 finalized: false,
281 _phantom: PhantomData,
282 }
283 }
284
285 #[doc(hidden)] pub fn replace_ir(&mut self, roots: Vec<HydroRoot>) {
287 self.flow_state.borrow_mut().roots = Some(roots);
288 }
289}