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