1use stageleft::{QuotedWithContext, q};
18
19#[cfg(stageleft_runtime)]
20use super::dynamic::DynLocation;
21use super::{Location, LocationId};
22use crate::compile::builder::{ClockId, FlowState};
23use crate::compile::ir::{HydroNode, HydroSource};
24#[cfg(stageleft_runtime)]
25use crate::forward_handle::{CycleCollection, CycleCollectionWithInitial};
26use crate::forward_handle::{TickCycle, TickCycleHandle};
27#[cfg(feature = "tokio")]
28use crate::live_collections::Singleton;
29use crate::live_collections::boundedness::Bounded;
30use crate::live_collections::optional::Optional;
31use crate::live_collections::stream::{ExactlyOnce, Stream, TotalOrder};
32use crate::location::TopLevel;
33#[cfg(feature = "tokio")]
34use crate::nondet::NonDet;
35use crate::nondet::nondet;
36
37#[derive(Clone)]
48pub struct Atomic<Loc> {
49 pub(crate) tick: Tick<Loc>,
50}
51
52impl<L: DynLocation> DynLocation for Atomic<L> {
53 fn dyn_id(&self) -> LocationId {
54 LocationId::Atomic(Box::new(self.tick.dyn_id()))
55 }
56
57 fn flow_state(&self) -> &FlowState {
58 self.tick.flow_state()
59 }
60
61 fn is_top_level() -> bool {
62 L::is_top_level()
63 }
64
65 fn multiversioned(&self) -> bool {
66 self.tick.multiversioned()
67 }
68
69 fn cluster_consistency() -> Option<super::dynamic::ClusterConsistency> {
70 L::cluster_consistency()
71 }
72}
73
74impl<'a, L> Location<'a> for Atomic<L>
75where
76 L: Location<'a>,
77{
78 type Root = L::Root;
79
80 type DropConsistency = Atomic<L::DropConsistency>;
81
82 fn consistency() -> Option<super::dynamic::ClusterConsistency> {
83 L::consistency()
84 }
85
86 fn root(&self) -> Self::Root {
87 self.tick.root()
88 }
89
90 fn drop_consistency(&self) -> Self::DropConsistency {
91 Atomic {
92 tick: self.tick.drop_consistency(),
93 }
94 }
95
96 fn from_drop_consistency(l2: Self::DropConsistency) -> Self {
97 Atomic {
98 tick: Tick::from_drop_consistency(l2.tick),
99 }
100 }
101}
102
103pub trait DeferTick {
110 fn defer_tick(self) -> Self;
112}
113
114#[derive(Clone)]
116pub struct Tick<L> {
117 pub(crate) id: Option<ClockId>,
119 pub(crate) l: L,
121}
122
123impl<L: DynLocation> DynLocation for Tick<L> {
124 fn dyn_id(&self) -> LocationId {
125 LocationId::Tick {
126 tick: self.id,
127 parent_location: Box::new(self.l.dyn_id()),
128 }
129 }
130
131 fn flow_state(&self) -> &FlowState {
132 self.l.flow_state()
133 }
134
135 fn is_top_level() -> bool {
136 false
137 }
138
139 fn multiversioned(&self) -> bool {
140 self.l.multiversioned()
141 }
142
143 fn cluster_consistency() -> Option<super::dynamic::ClusterConsistency> {
144 L::cluster_consistency()
145 }
146}
147
148impl<'a, L> Location<'a> for Tick<L>
149where
150 L: Location<'a>,
151{
152 type Root = L::Root;
153
154 type DropConsistency = Tick<L::DropConsistency>;
155
156 fn consistency() -> Option<super::dynamic::ClusterConsistency> {
157 L::consistency()
158 }
159
160 fn root(&self) -> Self::Root {
161 self.l.root()
162 }
163
164 fn drop_consistency(&self) -> Self::DropConsistency {
165 Tick {
166 id: self.id,
167 l: self.l.drop_consistency(),
168 }
169 }
170
171 fn from_drop_consistency(l2: Self::DropConsistency) -> Self {
172 Tick {
173 id: l2.id,
174 l: L::from_drop_consistency(l2.l),
175 }
176 }
177}
178
179impl<'a, L> Tick<L>
180where
181 L: Location<'a>,
182{
183 pub fn parent_location(&self) -> &L {
188 &self.l
189 }
190
191 #[deprecated(note = "use `.parent_location()` instead")]
193 pub fn outer(&self) -> &L {
194 self.parent_location()
195 }
196
197 pub fn spin_batch(
203 &self,
204 batch_size: impl QuotedWithContext<
205 'a,
206 usize,
207 crate::live_collections::OperatorContext<
208 L,
209 crate::live_collections::boundedness::Unbounded,
210 >,
211 > + Copy
212 + 'a,
213 ) -> Stream<(), Self, Bounded, TotalOrder, ExactlyOnce>
214 where
215 L: TopLevel<'a>,
216 {
217 let out = self
218 .l
219 .spin()
220 .flat_map_ordered(q!(move |_| 0..batch_size))
221 .map(q!(|_| ()));
222
223 let inner = out.batch(self, nondet!());
224 Stream::new(self.clone(), inner.ir_node.replace(HydroNode::Placeholder))
225 }
226
227 pub fn none<T>(&self) -> Optional<T, Self, Bounded> {
246 let e = q!([]);
247 let e = QuotedWithContext::<'a, [(); 0], Self>::splice_typed_ctx(e, self);
248
249 let unit_optional: Optional<(), Self, Bounded> = Optional::new(
250 self.clone(),
251 HydroNode::Source {
252 source: HydroSource::Iter(e.into()),
253 metadata: self.new_node_metadata(Optional::<(), Self, Bounded>::collection_kind()),
254 },
255 );
256
257 unit_optional.map(q!(|_| unreachable!())) }
259
260 pub fn optional_first_tick<T: Clone>(
286 &self,
287 e: impl QuotedWithContext<'a, T, Tick<L>>,
288 ) -> Optional<T, Self, Bounded> {
289 let e = e.splice_untyped_ctx(self);
290
291 Optional::new(
292 self.clone(),
293 HydroNode::SingletonSource {
294 value: e.into(),
295 first_tick_only: true,
296 metadata: self.new_node_metadata(Optional::<T, Self, Bounded>::collection_kind()),
297 },
298 )
299 }
300
301 #[cfg(feature = "tokio")]
309 pub fn current_tick_instant(
310 &self,
311 _nondet: NonDet,
312 ) -> Singleton<tokio::time::Instant, Tick<L::DropConsistency>, Bounded>
313 where
314 Self: Sized,
315 {
316 self.singleton(q!(tokio::time::Instant::now()))
318 }
319
320 #[expect(
329 private_bounds,
330 reason = "only Hydro collections can implement ReceiverComplete"
331 )]
332 pub fn cycle<S, L2: Location<'a, DropConsistency = Tick<L::DropConsistency>>>(
333 &self,
334 ) -> (TickCycleHandle<'a, S>, S)
335 where
336 S: CycleCollection<'a, TickCycle, Location = L2> + DeferTick,
337 {
338 let cycle_id = self.flow_state().borrow_mut().next_cycle_id();
339 (
340 TickCycleHandle::new(cycle_id, Location::id(self)),
341 S::create_source(cycle_id, self.clone().with_consistency_of()).defer_tick(),
342 )
343 }
344
345 #[expect(
352 private_bounds,
353 reason = "only Hydro collections can implement ReceiverComplete"
354 )]
355 pub fn cycle_with_initial<S, L2: Location<'a, DropConsistency = Tick<L::DropConsistency>>>(
356 &self,
357 initial: S,
358 ) -> (TickCycleHandle<'a, S>, S)
359 where
360 S: CycleCollectionWithInitial<'a, TickCycle, Location = L2>,
361 {
362 let cycle_id = self.flow_state().borrow_mut().next_cycle_id();
363 (
364 TickCycleHandle::new(cycle_id, Location::id(self)),
365 S::create_source_with_initial(cycle_id, initial, self.clone().with_consistency_of()),
367 )
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 #[cfg(feature = "sim")]
374 use stageleft::q;
375
376 #[cfg(feature = "sim")]
377 use crate::live_collections::sliced::sliced;
378 #[cfg(feature = "sim")]
379 use crate::location::Location;
380 #[cfg(feature = "sim")]
381 use crate::nondet::nondet;
382 #[cfg(feature = "sim")]
383 use crate::prelude::FlowBuilder;
384
385 #[cfg(feature = "sim")]
386 #[test]
387 fn sim_atomic_stream() {
388 let mut flow = FlowBuilder::new();
389 let node = flow.process::<()>();
390
391 let (write_send, write_req) = node.sim_input();
392 let (read_send, read_req) = node.sim_input::<(), _, _>();
393
394 let atomic_write = write_req.atomic();
395 let current_state = atomic_write.clone().fold(
396 q!(|| 0),
397 q!(|state: &mut i32, v: i32| {
398 *state += v;
399 }),
400 );
401
402 let write_ack_recv = atomic_write.end_atomic().sim_output();
403 let read_response_recv = sliced! {
404 let batch_of_req = use::batch(read_req, nondet!());
405 let latest_singleton = use::atomic(current_state, nondet!());
406 batch_of_req.cross_singleton(latest_singleton)
407 }
408 .sim_output();
409
410 let sim_compiled = flow.sim().compiled();
411 let instances = sim_compiled.exhaustive(async || {
412 write_send.send(1);
413 write_ack_recv.assert_yields([1]).await;
414 read_send.send(());
415 assert!(read_response_recv.next().await.1 >= 1);
416 });
417
418 assert_eq!(instances, 1);
419
420 let instances_read_before_write = sim_compiled.exhaustive(async || {
421 write_send.send(1);
422 read_send.send(());
423 write_ack_recv.assert_yields([1]).await;
424 let _ = read_response_recv.next().await;
425 });
426
427 assert_eq!(instances_read_before_write, 3); }
429
430 #[cfg(feature = "sim")]
431 #[test]
432 #[should_panic]
433 fn sim_non_atomic_stream() {
434 let mut flow = FlowBuilder::new();
436 let node = flow.process::<()>();
437
438 let (write_send, write_req) = node.sim_input();
439 let (read_send, read_req) = node.sim_input::<(), _, _>();
440
441 let current_state = write_req.clone().fold(
442 q!(|| 0),
443 q!(|state: &mut i32, v: i32| {
444 *state += v;
445 }),
446 );
447
448 let write_ack_recv = write_req.sim_output();
449
450 let read_response_recv = sliced! {
451 let batch_of_req = use::batch(read_req, nondet!());
452 let latest_singleton = use::snapshot(current_state, nondet!());
453 batch_of_req.cross_singleton(latest_singleton)
454 }
455 .sim_output();
456
457 flow.sim().exhaustive(async || {
458 write_send.send(1);
459 write_ack_recv.assert_yields([1]).await;
460 read_send.send(());
461
462 let (_, v) = read_response_recv.next().await;
463 assert_eq!(v, 1);
464 });
465 }
466}