Skip to main content

orbit_core/fleet/
cursor.rs

1use std::marker::PhantomData;
2
3use crate::OrbitTyped;
4use crate::fleet::{Fleet, NodeId};
5use crate::ring::cursor::{
6    RingCursor, RingFrameSource, RingLoss, RingPoll, RingRead, poll_ring,
7};
8use crate::ring::{Frame, RingTopology};
9
10struct FleetRingSource<'a, T: OrbitTyped> {
11    fleet: &'a Fleet,
12    _t: PhantomData<T>,
13}
14
15struct FleetLaneSource<'a, T: OrbitTyped> {
16    fleet: &'a Fleet,
17    node_id: NodeId,
18    _t: PhantomData<T>,
19}
20
21impl<'a, T: OrbitTyped> FleetLaneSource<'a, T> {
22    fn new(
23        fleet: &'a Fleet,
24        node_id: NodeId,
25    ) -> Self {
26        Self { fleet, node_id, _t: PhantomData }
27    }
28}
29
30impl<T: OrbitTyped> RingFrameSource for FleetLaneSource<'_, T> {
31    fn kind(&self) -> u8 {
32        T::KIND
33    }
34
35    fn head(&self) -> u64 {
36        self.fleet.lane_head::<T>(self.node_id)
37    }
38
39    fn capacity(&self) -> usize {
40        self.fleet.ring_capacity::<T>()
41    }
42
43    fn read_at(
44        &self,
45        counter: u64,
46    ) -> Option<Frame> {
47        self.fleet.read_lane_at::<T>(self.node_id, counter)
48    }
49
50    fn read_state_at(
51        &self,
52        counter: u64,
53    ) -> RingRead {
54        self.fleet.read_lane_state_at::<T>(self.node_id, counter)
55    }
56}
57
58/// Caller-owned positions for every node lane of one ring type.
59#[derive(Clone, Debug, PartialEq, Eq)]
60pub struct FleetLaneCursor {
61    lanes: Vec<RingCursor>,
62    initial_counter: u64,
63}
64
65impl FleetLaneCursor {
66    pub const fn from_counter(initial_counter: u64) -> Self {
67        Self { lanes: Vec::new(), initial_counter }
68    }
69
70    pub fn minimum_next_counter(&self) -> u64 {
71        self.lanes
72            .iter()
73            .map(|cursor| cursor.next_counter())
74            .min()
75            .unwrap_or(self.initial_counter)
76    }
77}
78
79/// Combined result of walking every node lane once.
80#[derive(Clone, Debug, Default, PartialEq, Eq)]
81pub struct FleetLanePoll {
82    pub frames: Vec<Frame>,
83    pub loss: RingLoss,
84}
85
86impl<'a, T: OrbitTyped> FleetRingSource<'a, T> {
87    fn new(fleet: &'a Fleet) -> Self {
88        Self { fleet, _t: PhantomData }
89    }
90}
91
92impl<T: OrbitTyped> RingFrameSource for FleetRingSource<'_, T> {
93    fn kind(&self) -> u8 {
94        T::KIND
95    }
96
97    fn head(&self) -> u64 {
98        self.fleet.head::<T>()
99    }
100
101    fn capacity(&self) -> usize {
102        self.fleet.ring_capacity::<T>()
103    }
104
105    fn read_at(
106        &self,
107        counter: u64,
108    ) -> Option<Frame> {
109        self.fleet.read_at::<T>(counter)
110    }
111
112    fn read_state_at(
113        &self,
114        counter: u64,
115    ) -> RingRead {
116        self.fleet.read_state_at::<T>(counter)
117    }
118}
119
120impl Fleet {
121    /// Cursor that starts after every counter currently claimed for `T`.
122    /// Useful for subscribers that only want future writes.
123    pub fn cursor_at_head<T: OrbitTyped>(&self) -> RingCursor {
124        RingCursor::from_counter(self.head::<T>())
125    }
126
127    /// Cursor that starts at counter 0 for `T`.
128    pub const fn cursor_from_start<T: OrbitTyped>(&self) -> RingCursor {
129        let _ = self;
130        let _ = PhantomData::<T>;
131        RingCursor::from_start()
132    }
133
134    /// Walk `cursor` toward the current claim head for `T`, stopping at
135    /// an in-flight counter and reporting definitive losses as
136    /// [`crate::ring::cursor::RingLoss`].
137    pub fn poll_ring<T: OrbitTyped>(
138        &self,
139        cursor: &mut RingCursor,
140    ) -> RingPoll {
141        poll_ring(&FleetRingSource::<T>::new(self), cursor)
142    }
143
144    /// Create one caller-owned cursor per physical node lane, starting at each
145    /// lane's current head.
146    pub fn lane_cursor_at_head<T: OrbitTyped>(&self) -> FleetLaneCursor {
147        self.assert_per_node::<T>();
148        let lanes = (0..self.fleet_capacity())
149            .map(|node| {
150                RingCursor::from_counter(self.lane_head::<T>(NodeId::new(node)))
151            })
152            .collect();
153        FleetLaneCursor { lanes, initial_counter: 0 }
154    }
155
156    /// Create one caller-owned cursor per physical node lane, starting at
157    /// counter zero.
158    pub fn lane_cursor_from_start<T: OrbitTyped>(&self) -> FleetLaneCursor {
159        self.assert_per_node::<T>();
160        FleetLaneCursor {
161            lanes: vec![
162                RingCursor::from_start();
163                usize::from(self.fleet_capacity())
164            ],
165            initial_counter: 0,
166        }
167    }
168
169    /// Poll every physical node lane and combine the retained frames and loss
170    /// counters into one result. Frames retain their writer node in `id`;
171    /// callers that need semantic ordering across lanes must provide it.
172    pub fn poll_lanes<T: OrbitTyped>(
173        &self,
174        cursor: &mut FleetLaneCursor,
175    ) -> FleetLanePoll {
176        self.assert_per_node::<T>();
177        if cursor.lanes.is_empty() {
178            cursor.lanes = vec![
179                RingCursor::from_counter(cursor.initial_counter);
180                usize::from(self.fleet_capacity())
181            ];
182        }
183        assert_eq!(
184            cursor.lanes.len(),
185            usize::from(self.fleet_capacity()),
186            "lane cursor belongs to a different fleet capacity"
187        );
188
189        let mut combined = FleetLanePoll::default();
190        for (node, lane_cursor) in cursor.lanes.iter_mut().enumerate() {
191            let source =
192                FleetLaneSource::<T>::new(self, NodeId::new(node as u16));
193            let poll = poll_ring(&source, lane_cursor);
194            combined.frames.extend(poll.frames);
195            combined.loss.overwritten =
196                combined.loss.overwritten.saturating_add(poll.loss.overwritten);
197            combined.loss.unavailable =
198                combined.loss.unavailable.saturating_add(poll.loss.unavailable);
199        }
200        combined
201    }
202
203    fn assert_per_node<T: OrbitTyped>(&self) {
204        assert_eq!(
205            T::RING_SPEC.topology,
206            RingTopology::PerNode,
207            "lane cursor requires a per-node ring"
208        );
209    }
210}