projectx_client/realtime/
event_flow.rs1use std::{collections::VecDeque, time::Duration};
7
8use super::{
9 Arc, AtomicBool, AtomicUsize, Instant, Notify, Ordering, ParkingMutex, RealtimeError,
10 RealtimeEvent, fmt, mpsc,
11};
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct RealtimeGeneration(pub(super) u64);
16
17#[derive(Clone, Debug, PartialEq)]
19#[non_exhaustive]
20pub struct RealtimeMessage {
21 pub generation: RealtimeGeneration,
23 pub event: RealtimeEvent,
25}
26
27pub(super) struct EventFlow {
28 pub(super) state: ParkingMutex<EventFlowState>,
29 pub(super) queued_weight: AtomicUsize,
30 gap_pending: AtomicBool,
31 changed: Notify,
32}
33
34#[derive(Default)]
35pub(super) struct EventFlowState {
36 pub(super) active_generation: Option<u64>,
37 queued_at: VecDeque<Instant>,
38 gap: Option<GapFence>,
39}
40
41struct GapFence {
44 generation: u64,
45 start_event: Option<RealtimeEvent>,
46 terminal_event: Option<RealtimeEvent>,
47 generation_ended: bool,
48 acknowledged: bool,
49}
50
51struct EventReservation {
52 flow: Arc<EventFlow>,
53 weight: usize,
54}
55
56impl Drop for EventReservation {
57 fn drop(&mut self) {
58 let previous = self
59 .flow
60 .queued_weight
61 .fetch_sub(self.weight, Ordering::AcqRel);
62 debug_assert!(previous >= self.weight);
63 }
64}
65
66pub(super) struct EventEnvelope {
67 message: RealtimeMessage,
68 reservation: EventReservation,
69}
70
71#[derive(Clone, Copy, Debug, Eq, PartialEq)]
72pub(super) enum PublishOutcome {
73 Published,
74 GapFenced,
75 StaleGeneration,
76}
77
78impl EventEnvelope {
79 fn into_message(self) -> RealtimeMessage {
80 let Self {
81 message,
82 reservation,
83 } = self;
84 drop(reservation);
85 message
86 }
87}
88
89impl EventFlow {
90 pub(super) fn new() -> Arc<Self> {
91 Arc::new(Self {
92 state: ParkingMutex::new(EventFlowState::default()),
93 queued_weight: AtomicUsize::new(0),
94 gap_pending: AtomicBool::new(false),
95 changed: Notify::new(),
96 })
97 }
98
99 pub(super) fn has_unacknowledged_gap(&self) -> bool {
100 self.gap_pending.load(Ordering::Acquire)
101 }
102
103 pub(super) fn start_generation(&self, generation: u64) -> Result<(), RealtimeError> {
104 let mut state = self.state.lock();
105 if state.gap.is_some() {
106 return Err(RealtimeError::TransportGapPending);
107 }
108 if state.active_generation.is_some() {
109 return Err(RealtimeError::ConnectionCancelled);
110 }
111 state.active_generation = Some(generation);
112 Ok(())
113 }
114
115 pub(super) fn publish(
116 self: &Arc<Self>,
117 events: &mpsc::UnboundedSender<EventEnvelope>,
118 generation: u64,
119 event: RealtimeEvent,
120 weight: usize,
121 ) -> Result<PublishOutcome, RealtimeError> {
122 let mut state = self.state.lock();
123 if state.active_generation != Some(generation) {
124 return Ok(PublishOutcome::StaleGeneration);
125 }
126 if state.gap.is_some() {
127 return Ok(self.retain_gap_boundary(&mut state, generation, event));
128 }
129 self.queued_weight.fetch_add(weight, Ordering::AcqRel);
130 let envelope = EventEnvelope {
131 message: RealtimeMessage {
132 generation: RealtimeGeneration(generation),
133 event,
134 },
135 reservation: EventReservation {
136 flow: Arc::clone(self),
137 weight,
138 },
139 };
140 match events.send(envelope) {
141 Ok(()) => {
142 state.queued_at.push_back(Instant::now());
143 Ok(PublishOutcome::Published)
144 }
145 Err(_) => Err(RealtimeError::EventReceiverClosed),
146 }
147 }
148
149 fn retain_gap_boundary(
150 &self,
151 state: &mut EventFlowState,
152 generation: u64,
153 event: RealtimeEvent,
154 ) -> PublishOutcome {
155 let fence = state.gap.get_or_insert(GapFence {
156 generation,
157 start_event: None,
158 terminal_event: None,
159 generation_ended: false,
160 acknowledged: false,
161 });
162 let retained = match event {
163 RealtimeEvent::Connected | RealtimeEvent::Reconnected => {
164 fence.start_event = Some(event);
165 true
166 }
167 RealtimeEvent::Disconnected => {
168 fence.terminal_event = Some(event);
169 true
170 }
171 _ => false,
172 };
173 self.gap_pending.store(true, Ordering::Release);
174 self.changed.notify_waiters();
175 if retained {
176 PublishOutcome::Published
177 } else {
178 PublishOutcome::GapFenced
179 }
180 }
181
182 pub(super) fn mark_gap(&self, generation: u64) {
183 let mut state = self.state.lock();
184 if state.active_generation == Some(generation) {
185 let _ = self.retain_gap_boundary(&mut state, generation, RealtimeEvent::TransportGap);
186 }
187 }
188
189 pub(super) fn finish_generation(&self, generation: u64) {
190 let mut state = self.state.lock();
191 if state.active_generation != Some(generation) {
192 return;
193 }
194 state.active_generation = None;
195 if let Some(fence) = state.gap.as_mut() {
196 fence.generation_ended = true;
197 }
198 drop(state);
199 self.changed.notify_waiters();
200 }
201
202 fn release_acknowledged(&self, state: &mut EventFlowState) {
203 if state.gap.as_ref().is_some_and(|fence| {
204 fence.acknowledged && fence.start_event.is_none() && fence.terminal_event.is_none()
205 }) {
206 state.gap = None;
207 self.gap_pending.store(false, Ordering::Release);
208 }
209 }
210
211 fn acknowledge_gap(&self) {
212 let mut state = self.state.lock();
213 if let Some(fence) = state.gap.as_mut() {
214 fence.acknowledged = true;
215 }
216 self.release_acknowledged(&mut state);
217 drop(state);
218 self.changed.notify_waiters();
219 }
220}
221
222pub struct RealtimeEventReceiver {
224 pub(super) events: mpsc::UnboundedReceiver<EventEnvelope>,
225 pub(super) flow: Arc<EventFlow>,
226 pub(super) gap_reported: bool,
227}
228
229impl RealtimeEventReceiver {
230 #[must_use]
232 pub fn queued_event_count(&self) -> usize {
233 self.flow.state.lock().queued_at.len()
234 }
235
236 #[must_use]
238 pub fn oldest_event_age(&self) -> Option<Duration> {
239 self.flow
240 .state
241 .lock()
242 .queued_at
243 .front()
244 .map(Instant::elapsed)
245 }
246
247 #[must_use]
249 pub fn is_closed(&self) -> bool {
250 self.events.is_closed()
251 }
252
253 pub async fn recv(&mut self) -> Option<RealtimeEvent> {
256 self.recv_message().await.map(|message| message.event)
257 }
258
259 pub async fn recv_message(&mut self) -> Option<RealtimeMessage> {
262 loop {
263 let changed = self.flow.changed.notified();
264 tokio::pin!(changed);
265 let _ = changed.as_mut().enable();
266 {
267 let mut state = self.flow.state.lock();
268 if let Ok(envelope) = self.events.try_recv() {
269 state.queued_at.pop_front();
270 return Some(envelope.into_message());
271 }
272 if let Some(fence) = state.gap.as_mut() {
273 let event = if let Some(event) = fence.start_event.take() {
274 Some(event)
275 } else if !fence.acknowledged && !self.gap_reported {
276 self.gap_reported = true;
277 Some(RealtimeEvent::TransportGap)
278 } else if fence.generation_ended {
279 fence.terminal_event.take()
280 } else {
281 None
282 };
283 if let Some(event) = event {
284 let message = RealtimeMessage {
285 generation: RealtimeGeneration(fence.generation),
286 event,
287 };
288 self.flow.release_acknowledged(&mut state);
289 return Some(message);
290 }
291 }
292 if self.events.is_closed() {
293 return None;
294 }
295 }
296 tokio::select! {
297 biased;
298 envelope = self.events.recv() => {
299 if let Some(envelope) = envelope {
300 self.flow.state.lock().queued_at.pop_front();
301 return Some(envelope.into_message());
302 }
303 }
304 () = &mut changed => {}
305 }
306 }
307 }
308
309 pub fn acknowledge_transport_gap(&mut self) {
312 if self.gap_reported {
313 self.flow.acknowledge_gap();
314 self.gap_reported = false;
315 }
316 }
317}
318
319impl Drop for RealtimeEventReceiver {
320 fn drop(&mut self) {
321 let mut state = self.flow.state.lock();
322 self.events.close();
323 state.queued_at.clear();
324 }
325}
326
327impl fmt::Debug for RealtimeEventReceiver {
328 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
329 formatter
330 .debug_struct("RealtimeEventReceiver")
331 .field("gap_pending", &self.flow.has_unacknowledged_gap())
332 .field("queued_events", &self.queued_event_count())
333 .field("oldest_event_age", &self.oldest_event_age())
334 .field(
335 "queued_weight",
336 &self.flow.queued_weight.load(Ordering::Acquire),
337 )
338 .field("gap_reported", &self.gap_reported)
339 .finish_non_exhaustive()
340 }
341}