Skip to main content

hydro_lang/live_collections/sliced/
style.rs

1//! Styled wrappers for live collections used with the `sliced!` macro.
2//!
3//! This module provides wrapper types that store both a collection and its associated
4//! non-determinism guard, allowing the nondet to be properly passed through during slicing.
5
6#[cfg(stageleft_runtime)]
7use std::marker::PhantomData;
8
9use super::Slicable;
10#[cfg(stageleft_runtime)]
11use crate::forward_handle::{CycleCollection, CycleCollectionWithInitial};
12use crate::forward_handle::{TickCycle, TickCycleHandle};
13use crate::live_collections::boundedness::{Bounded, Boundedness, Unbounded};
14use crate::live_collections::keyed_singleton::{BoundedValue, KeyedSingletonBound};
15use crate::live_collections::optional::OptionalBound;
16use crate::live_collections::singleton::SingletonBound;
17use crate::live_collections::stream::{Ordering, Retries};
18use crate::location::Location;
19use crate::location::tick::{DeferTick, Tick};
20use crate::nondet::{NonDet, nondet};
21
22/// Default style wrapper that stores a collection and its non-determinism guard.
23///
24/// This is used by the `sliced!` macro when no explicit style is specified. This style is
25/// deprecated; use the explicit [`batch`] or [`snapshot`] styles instead.
26pub struct Default<T> {
27    pub(crate) collection: T,
28    pub(crate) nondet: NonDet,
29}
30
31impl<T> Default<T> {
32    /// Creates a new default-styled wrapper.
33    pub fn new(collection: T, nondet: NonDet) -> Self {
34        Self { collection, nondet }
35    }
36}
37
38/// Helper function for unstyled `use` in `sliced!` macro - wraps the collection in Default style.
39#[doc(hidden)]
40#[deprecated(
41    note = "use `use::batch(...)` for stream-like collections or `use::snapshot(...)` for singleton-like collections instead"
42)]
43pub fn default<T>(t: T, nondet: NonDet) -> Default<T> {
44    Default::new(t, nondet)
45}
46
47/// Batch style wrapper that stores a stream-like collection and its non-determinism guard.
48///
49/// This is used by the `sliced!` macro when `use::batch(...)` is specified.
50pub struct Batch<T, H = ()> {
51    pub(crate) collection: T,
52    pub(crate) nondet: NonDet<H>,
53}
54
55impl<T, H> Batch<T, H> {
56    /// Creates a new batch-styled wrapper.
57    pub fn new(collection: T, nondet: NonDet<H>) -> Self {
58        Self { collection, nondet }
59    }
60}
61
62/// Wraps a stream-like live collection (such as a [`Stream`](crate::live_collections::Stream),
63/// [`KeyedStream`](crate::live_collections::KeyedStream), or a
64/// [`KeyedSingleton`](crate::live_collections::KeyedSingleton) with bounded values) to be
65/// sliced into non-deterministic batches of asynchronously arriving elements.
66pub fn batch<T, H>(t: T, nondet: NonDet<H>) -> Batch<T, H> {
67    Batch::new(t, nondet)
68}
69
70/// Snapshot style wrapper that stores a singleton-like collection and its non-determinism guard.
71///
72/// This is used by the `sliced!` macro when `use::snapshot(...)` is specified.
73pub struct Snapshot<T, H = ()> {
74    pub(crate) collection: T,
75    pub(crate) nondet: NonDet<H>,
76}
77
78impl<T, H> Snapshot<T, H> {
79    /// Creates a new snapshot-styled wrapper.
80    pub fn new(collection: T, nondet: NonDet<H>) -> Self {
81        Self { collection, nondet }
82    }
83}
84
85/// Wraps a singleton-like live collection (such as a
86/// [`Singleton`](crate::live_collections::Singleton),
87/// [`Optional`](crate::live_collections::Optional), or a
88/// [`KeyedSingleton`](crate::live_collections::KeyedSingleton) with asynchronously updated
89/// values) to be sliced into non-deterministic snapshots of its continuously changing value.
90pub fn snapshot<T, H>(t: T, nondet: NonDet<H>) -> Snapshot<T, H> {
91    Snapshot::new(t, nondet)
92}
93
94/// Atomic style wrapper that stores a collection and its non-determinism guard.
95///
96/// This is used by the `sliced!` macro when `use::atomic(...)` is specified.
97pub struct Atomic<T> {
98    pub(crate) collection: T,
99    pub(crate) nondet: NonDet,
100}
101
102impl<T> Atomic<T> {
103    /// Creates a new atomic-styled wrapper.
104    pub fn new(collection: T, nondet: NonDet) -> Self {
105        Self { collection, nondet }
106    }
107}
108
109/// Wraps a live collection to be treated atomically during slicing.
110pub fn atomic<T>(t: T, nondet: NonDet) -> Atomic<T> {
111    Atomic::new(t, nondet)
112}
113
114/// Creates a stateful cycle with an initial value for use in `sliced!`.
115///
116/// The tick (which is the source of truth for lifetimes) is bound first, returning a
117/// [`StateBuilder`] which accepts the user-provided initializer via [`StateBuilder::build`].
118/// This two-step layout ensures that type errors caused by a bad initializer are attributed
119/// to the initializer argument rather than the tick or the entire macro invocation.
120///
121/// The initial value is computed from a closure that receives the location
122/// for the body of the slice.
123///
124/// The initial value is used on the first iteration, and subsequent iterations receive
125/// the value assigned to the mutable binding at the end of the previous iteration.
126#[cfg(stageleft_runtime)]
127pub fn state<'t, S, L>(tick: &'t Tick<L>) -> StateBuilder<'t, S, L> {
128    StateBuilder {
129        tick,
130        _phantom: PhantomData,
131    }
132}
133
134/// Builder returned by [`state`], which accepts the user-provided initializer.
135#[cfg(stageleft_runtime)]
136pub struct StateBuilder<'t, S, L> {
137    tick: &'t Tick<L>,
138    _phantom: PhantomData<S>,
139}
140
141#[cfg(stageleft_runtime)]
142impl<'t, 'a, S, L: Location<'a>> StateBuilder<'t, S, L> {
143    /// Supplies the initializer closure and creates the stateful cycle.
144    ///
145    /// The initializer takes the tick at the builder's `'t` lifetime (rather than a
146    /// higher-ranked `for<'x>` bound), since the builder already stores the tick reference.
147    /// This way, an initializer that requires a specific tick reference lifetime produces a
148    /// borrow error directly on the tick, instead of a confusing "implementation of `Fn` is
149    /// not general enough" error that blames an unrelated variable.
150    #[expect(
151        private_bounds,
152        reason = "only Hydro collections can implement CycleCollectionWithInitial"
153    )]
154    pub fn build(self, initial_fn: impl FnOnce(&'t Tick<L>) -> S) -> (TickCycleHandle<'a, S>, S)
155    where
156        S: CycleCollectionWithInitial<'a, TickCycle, Location = Tick<L::DropConsistency>>,
157    {
158        let initial = initial_fn(self.tick);
159        initial.location().clone().cycle_with_initial(initial)
160    }
161}
162
163/// Creates a stateful cycle without an initial value for use in `sliced!`.
164///
165/// The tick (which is the source of truth for lifetimes) is bound first, returning a
166/// [`StateNullBuilder`] which creates the cycle via [`StateNullBuilder::build`].
167///
168/// On the first iteration, the state will be null/empty. Subsequent iterations receive
169/// the value assigned to the mutable binding at the end of the previous iteration.
170#[cfg(stageleft_runtime)]
171pub fn state_null<'t, S, L>(tick: &'t Tick<L>) -> StateNullBuilder<'t, S, L> {
172    StateNullBuilder {
173        tick,
174        _phantom: PhantomData,
175    }
176}
177
178/// Builder returned by [`state_null`], which creates the cycle.
179#[cfg(stageleft_runtime)]
180pub struct StateNullBuilder<'t, S, L> {
181    tick: &'t Tick<L>,
182    _phantom: PhantomData<S>,
183}
184
185#[cfg(stageleft_runtime)]
186impl<'t, 'a, S, L: Location<'a>> StateNullBuilder<'t, S, L> {
187    /// Creates the stateful cycle, which starts as null/empty on the first iteration.
188    #[expect(
189        private_bounds,
190        reason = "only Hydro collections can implement CycleCollection"
191    )]
192    pub fn build(self) -> (TickCycleHandle<'a, S>, S)
193    where
194        S: CycleCollection<'a, TickCycle, Location = Tick<L::DropConsistency>> + DeferTick,
195    {
196        self.tick.cycle::<S, _>()
197    }
198}
199
200// ============================================================================
201// Default style Slicable implementations
202//
203// All of these drop consistency because they are performing non-deterministic
204// batching / snapshotting.
205// ============================================================================
206
207impl<'a, T, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
208    Slicable<'a, L::DropConsistency> for Default<crate::live_collections::Stream<T, L, B, O, R>>
209{
210    type Slice = crate::live_collections::Stream<T, Tick<L::DropConsistency>, Bounded, O, R>;
211    type Backtrace = crate::compile::ir::backtrace::Backtrace;
212
213    fn get_location(&self) -> L::DropConsistency {
214        self.collection.location().drop_consistency()
215    }
216    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
217        let _ = self.nondet;
218        let out = self.collection.batch(
219            tick,
220            nondet!(/** justified by the guard stored in this style wrapper */),
221        );
222        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
223        out
224    }
225}
226
227impl<'a, T, L: Location<'a>, B: SingletonBound> Slicable<'a, L::DropConsistency>
228    for Default<crate::live_collections::Singleton<T, L, B>>
229{
230    type Slice = crate::live_collections::Singleton<T, Tick<L::DropConsistency>, Bounded>;
231    type Backtrace = crate::compile::ir::backtrace::Backtrace;
232
233    fn get_location(&self) -> L::DropConsistency {
234        self.collection.location().drop_consistency()
235    }
236    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
237        let _ = self.nondet;
238        let out = self.collection.snapshot(
239            tick,
240            nondet!(/** justified by the guard stored in this style wrapper */),
241        );
242        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
243        out
244    }
245}
246
247impl<'a, T, L: Location<'a>, B: OptionalBound> Slicable<'a, L::DropConsistency>
248    for Default<crate::live_collections::Optional<T, L, B>>
249{
250    type Slice = crate::live_collections::Optional<T, Tick<L::DropConsistency>, Bounded>;
251    type Backtrace = crate::compile::ir::backtrace::Backtrace;
252
253    fn get_location(&self) -> L::DropConsistency {
254        self.collection.location().drop_consistency()
255    }
256    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
257        let out = self.collection.snapshot(tick, self.nondet);
258        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
259        out
260    }
261}
262
263impl<'a, K, V, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
264    Slicable<'a, L::DropConsistency>
265    for Default<crate::live_collections::KeyedStream<K, V, L, B, O, R>>
266{
267    type Slice =
268        crate::live_collections::KeyedStream<K, V, Tick<L::DropConsistency>, Bounded, O, R>;
269    type Backtrace = crate::compile::ir::backtrace::Backtrace;
270
271    fn get_location(&self) -> L::DropConsistency {
272        self.collection.location().drop_consistency()
273    }
274    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
275        let out = self.collection.batch(tick, self.nondet);
276        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
277        out
278    }
279}
280
281impl<'a, K, V, L: Location<'a>, B: KeyedSingletonBound<ValueBound = Unbounded>>
282    Slicable<'a, L::DropConsistency>
283    for Default<crate::live_collections::KeyedSingleton<K, V, L, B>>
284{
285    type Slice = crate::live_collections::KeyedSingleton<K, V, Tick<L::DropConsistency>, Bounded>;
286    type Backtrace = crate::compile::ir::backtrace::Backtrace;
287
288    fn get_location(&self) -> L::DropConsistency {
289        self.collection.location().drop_consistency()
290    }
291    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
292        let out = self.collection.snapshot(tick, self.nondet);
293        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
294        out
295    }
296}
297
298impl<'a, K, V, L: Location<'a>> Slicable<'a, L::DropConsistency>
299    for Default<crate::live_collections::KeyedSingleton<K, V, L, BoundedValue>>
300{
301    type Slice = crate::live_collections::KeyedSingleton<K, V, Tick<L::DropConsistency>, Bounded>;
302    type Backtrace = crate::compile::ir::backtrace::Backtrace;
303
304    fn get_location(&self) -> L::DropConsistency {
305        self.collection.location().drop_consistency()
306    }
307    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
308        let out = self.collection.batch(tick, self.nondet);
309        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
310        out
311    }
312}
313
314// ============================================================================
315// Batch style Slicable implementations (stream-like collections)
316//
317// All of these drop consistency because they are performing non-deterministic
318// batching.
319// ============================================================================
320
321impl<'a, T, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
322    Slicable<'a, L::DropConsistency>
323    for Batch<
324        crate::live_collections::Stream<T, L, B, O, R>,
325        Option<crate::sim_hooks::BatchHook<T, O, R>>,
326    >
327{
328    type Slice = crate::live_collections::Stream<T, Tick<L::DropConsistency>, Bounded, O, R>;
329    type Backtrace = crate::compile::ir::backtrace::Backtrace;
330
331    fn get_location(&self) -> L::DropConsistency {
332        self.collection.location().drop_consistency()
333    }
334    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
335        let out = self.collection.batch(tick, self.nondet);
336        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
337        out
338    }
339}
340
341impl<'a, K, V, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
342    Slicable<'a, L::DropConsistency>
343    for Batch<crate::live_collections::KeyedStream<K, V, L, B, O, R>>
344{
345    type Slice =
346        crate::live_collections::KeyedStream<K, V, Tick<L::DropConsistency>, Bounded, O, R>;
347    type Backtrace = crate::compile::ir::backtrace::Backtrace;
348
349    fn get_location(&self) -> L::DropConsistency {
350        self.collection.location().drop_consistency()
351    }
352    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
353        let out = self.collection.batch(tick, self.nondet);
354        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
355        out
356    }
357}
358
359impl<'a, K, V, L: Location<'a>> Slicable<'a, L::DropConsistency>
360    for Batch<crate::live_collections::KeyedSingleton<K, V, L, BoundedValue>>
361{
362    type Slice = crate::live_collections::KeyedSingleton<K, V, Tick<L::DropConsistency>, Bounded>;
363    type Backtrace = crate::compile::ir::backtrace::Backtrace;
364
365    fn get_location(&self) -> L::DropConsistency {
366        self.collection.location().drop_consistency()
367    }
368    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
369        let out = self.collection.batch(tick, self.nondet);
370        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
371        out
372    }
373}
374
375// ============================================================================
376// Snapshot style Slicable implementations (singleton-like collections)
377//
378// All of these drop consistency because they are performing non-deterministic
379// snapshotting.
380// ============================================================================
381
382impl<'a, T, L: Location<'a>, B: SingletonBound> Slicable<'a, L::DropConsistency>
383    for Snapshot<
384        crate::live_collections::Singleton<T, L, B>,
385        Option<crate::sim_hooks::SnapshotHook<T>>,
386    >
387{
388    type Slice = crate::live_collections::Singleton<T, Tick<L::DropConsistency>, Bounded>;
389    type Backtrace = crate::compile::ir::backtrace::Backtrace;
390
391    fn get_location(&self) -> L::DropConsistency {
392        self.collection.location().drop_consistency()
393    }
394    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
395        let out = self.collection.snapshot(tick, self.nondet);
396        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
397        out
398    }
399}
400
401impl<'a, T, L: Location<'a>, B: OptionalBound> Slicable<'a, L::DropConsistency>
402    for Snapshot<crate::live_collections::Optional<T, L, B>>
403{
404    type Slice = crate::live_collections::Optional<T, Tick<L::DropConsistency>, Bounded>;
405    type Backtrace = crate::compile::ir::backtrace::Backtrace;
406
407    fn get_location(&self) -> L::DropConsistency {
408        self.collection.location().drop_consistency()
409    }
410    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
411        let out = self.collection.snapshot(tick, self.nondet);
412        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
413        out
414    }
415}
416
417impl<'a, K, V, L: Location<'a>, B: KeyedSingletonBound<ValueBound = Unbounded>>
418    Slicable<'a, L::DropConsistency>
419    for Snapshot<crate::live_collections::KeyedSingleton<K, V, L, B>>
420{
421    type Slice = crate::live_collections::KeyedSingleton<K, V, Tick<L::DropConsistency>, Bounded>;
422    type Backtrace = crate::compile::ir::backtrace::Backtrace;
423
424    fn get_location(&self) -> L::DropConsistency {
425        self.collection.location().drop_consistency()
426    }
427    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
428        let out = self.collection.snapshot(tick, self.nondet);
429        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
430        out
431    }
432}
433
434// ============================================================================
435// Atomic style Slicable implementations
436// ============================================================================
437
438impl<'a, T, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
439    Slicable<'a, L::DropConsistency>
440    for Atomic<crate::live_collections::Stream<T, crate::location::Atomic<L>, B, O, R>>
441{
442    type Slice = crate::live_collections::Stream<T, Tick<L::DropConsistency>, Bounded, O, R>;
443    type Backtrace = crate::compile::ir::backtrace::Backtrace;
444    fn get_location(&self) -> L::DropConsistency {
445        self.collection.location().tick.l.drop_consistency()
446    }
447
448    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
449        let _ = self.nondet;
450        let out = self.collection.batch_atomic(
451            tick,
452            nondet!(/** justified by the guard stored in this style wrapper */),
453        );
454        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
455        out
456    }
457}
458
459impl<'a, T, L: Location<'a>, B: SingletonBound> Slicable<'a, L::DropConsistency>
460    for Atomic<crate::live_collections::Singleton<T, crate::location::Atomic<L>, B>>
461{
462    type Slice = crate::live_collections::Singleton<T, Tick<L::DropConsistency>, Bounded>;
463    type Backtrace = crate::compile::ir::backtrace::Backtrace;
464    fn get_location(&self) -> L::DropConsistency {
465        self.collection.location().tick.l.drop_consistency()
466    }
467
468    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
469        let _ = self.nondet;
470        let out = self.collection.snapshot_atomic(
471            tick,
472            nondet!(/** justified by the guard stored in this style wrapper */),
473        );
474        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
475        out
476    }
477}
478
479impl<'a, T, L: Location<'a>, B: OptionalBound> Slicable<'a, L::DropConsistency>
480    for Atomic<crate::live_collections::Optional<T, crate::location::Atomic<L>, B>>
481{
482    type Slice = crate::live_collections::Optional<T, Tick<L::DropConsistency>, Bounded>;
483    type Backtrace = crate::compile::ir::backtrace::Backtrace;
484    fn get_location(&self) -> L::DropConsistency {
485        self.collection.location().tick.l.drop_consistency()
486    }
487
488    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
489        let out = self.collection.snapshot_atomic(tick, self.nondet);
490        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
491        out
492    }
493}
494
495impl<'a, K, V, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
496    Slicable<'a, L::DropConsistency>
497    for Atomic<crate::live_collections::KeyedStream<K, V, crate::location::Atomic<L>, B, O, R>>
498{
499    type Slice =
500        crate::live_collections::KeyedStream<K, V, Tick<L::DropConsistency>, Bounded, O, R>;
501    type Backtrace = crate::compile::ir::backtrace::Backtrace;
502    fn get_location(&self) -> L::DropConsistency {
503        self.collection.location().tick.l.drop_consistency()
504    }
505
506    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
507        let out = self.collection.batch_atomic(tick, self.nondet);
508        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
509        out
510    }
511}
512
513impl<'a, K, V, L: Location<'a>, B: KeyedSingletonBound<ValueBound = Unbounded>>
514    Slicable<'a, L::DropConsistency>
515    for Atomic<crate::live_collections::KeyedSingleton<K, V, crate::location::Atomic<L>, B>>
516{
517    type Slice = crate::live_collections::KeyedSingleton<K, V, Tick<L::DropConsistency>, Bounded>;
518    type Backtrace = crate::compile::ir::backtrace::Backtrace;
519    fn get_location(&self) -> L::DropConsistency {
520        self.collection.location().tick.l.drop_consistency()
521    }
522
523    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
524        let out = self.collection.snapshot_atomic(tick, self.nondet);
525        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
526        out
527    }
528}
529
530impl<'a, K, V, L: Location<'a>> Slicable<'a, L::DropConsistency>
531    for Atomic<
532        crate::live_collections::KeyedSingleton<K, V, crate::location::Atomic<L>, BoundedValue>,
533    >
534{
535    type Slice = crate::live_collections::KeyedSingleton<K, V, Tick<L::DropConsistency>, Bounded>;
536    type Backtrace = crate::compile::ir::backtrace::Backtrace;
537    fn get_location(&self) -> L::DropConsistency {
538        self.collection.location().tick.l.drop_consistency()
539    }
540
541    fn slice(self, tick: &Tick<L::DropConsistency>, backtrace: Self::Backtrace) -> Self::Slice {
542        let out = self.collection.batch_atomic(tick, self.nondet);
543        out.ir_node.borrow_mut().op_metadata_mut().backtrace = backtrace;
544        out
545    }
546}