atagen

macltok

public
Star0
Watch0
a9c3136b0c3a2d048bdea38f40fac230e7b52793
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git
src/mirror.rsRust704 lines23.7 KB
1
use std::{
2
    collections::HashMap,
3
    fmt,
4
    pin::Pin,
5
    sync::{Arc, Mutex, MutexGuard, PoisonError},
6
    task::{Context, Poll},
7
    time::Duration,
8
};
9
10
use futures_util::{Stream, StreamExt};
11
use imbl::Vector;
12
use matrix_sdk::{
13
    SendOutsideWasm,
14
    executor::{AbortHandle, spawn},
15
};
16
use ruma::{OwnedDeviceId, OwnedRoomId, OwnedTransactionId, OwnedUserId};
17
#[cfg(feature = "serde")]
18
use serde::{Deserialize, Serialize};
19
use tokio::sync::broadcast;
20
21
use crate::{
22
    ClientHandle, Command, ConnectionState, EncryptionState, EngineFailure, ErrorKind, Event,
23
    HistoryState, ImagePacks, RoomSummary, TimelineCommand, TimelineDiff, TimelineId, TimelineItem,
24
    TypingState, UploadOutcome, VerificationState, VerificationTarget,
25
    engine::executor::EngineBoxFuture,
26
};
27
28
#[cfg(not(target_family = "wasm"))]
29
type EngineBoxStream<T> = futures_util::stream::BoxStream<'static, T>;
30
#[cfg(target_family = "wasm")]
31
type EngineBoxStream<T> = futures_util::stream::LocalBoxStream<'static, T>;
32
33
const DEFAULT_CAPACITY: usize = 256;
34
const RESYNC_BACKOFF: Duration = Duration::from_millis(100);
35
const RESYNC_BACKOFF_CAP: Duration = Duration::from_secs(10);
36
37
/// everything the engine has said so far, folded together
38
#[derive(Clone, Debug, PartialEq)]
39
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
40
#[non_exhaustive]
41
pub struct State {
42
    pub user_id: Option<OwnedUserId>,
43
    pub session: SessionStatus,
44
    pub connection: ConnectionState,
45
    pub encryption: Option<EncryptionState>,
46
    pub verification: Option<VerificationState>,
47
    pub verification_target: Option<VerificationTarget>,
48
    pub rooms: Vector<RoomSummary>,
49
    pub image_packs: ImagePacks,
50
    pub timelines: imbl::HashMap<TimelineId, TimelineState>,
51
    pub typing: imbl::HashMap<OwnedRoomId, Vec<OwnedUserId>>,
52
    pub uploads: imbl::HashMap<OwnedTransactionId, UploadState>,
53
    /// what stopped the engine, if something went wrong
54
    pub engine_failure: Option<EngineFailure>,
55
    /// the last save of the session record that failed, until one succeeds
56
    pub session_save_failure: Option<String>,
57
}
58
59
#[derive(Clone, Debug, PartialEq)]
60
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61
#[non_exhaustive]
62
pub struct TimelineState {
63
    pub items: Vector<TimelineItem>,
64
    pub history: HistoryState,
65
    /// a batch didn't fit, so `items` is the last good copy until the next reset
66
    pub diverged: bool,
67
}
68
69
#[derive(Clone, Debug, Eq, PartialEq)]
70
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
71
#[non_exhaustive]
72
pub struct UploadState {
73
    pub room_id: OwnedRoomId,
74
    pub uploaded_bytes: u64,
75
    pub total_bytes: u64,
76
    pub complete: bool,
77
    pub failure: Option<UploadFailure>,
78
}
79
80
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
81
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
82
#[non_exhaustive]
83
pub struct UploadFailure {
84
    pub kind: ErrorKind,
85
    pub will_retry: bool,
86
}
87
88
/// the `Termination` it'll end in, as far as the events tell
89
#[derive(Clone, Debug, Eq, PartialEq)]
90
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
91
pub enum SessionStatus {
92
    Active,
93
    SoftLoggedOut {
94
        device_id: OwnedDeviceId,
95
    },
96
    AccessRevoked,
97
    LoggedOut,
98
    /// stopped with the session still good
99
    Stopped,
100
}
101
102
#[derive(Clone, Debug)]
103
#[non_exhaustive]
104
pub struct Update {
105
    /// one more than the update before it, and a `State` carries the one it's current as of
106
    pub sequence: u64,
107
    pub change: StateChange,
108
}
109
110
#[derive(Clone, Debug)]
111
pub enum StateChange {
112
    /// start over from this. a subscription opens with one and gets another after lagging
113
    State(Arc<State>),
114
    Authenticated(OwnedUserId),
115
    Session(SessionStatus),
116
    /// the record on disk may now hold a spent refresh token
117
    SessionSaveFailed(String),
118
    /// saved again after `SessionSaveFailed`
119
    SessionSaved,
120
    Connection(ConnectionState),
121
    Encryption(EncryptionState),
122
    Verification(VerificationState),
123
    VerificationTarget(VerificationTarget),
124
    /// room modes arrive with the `Rooms` that follows
125
    NotificationSettings,
126
    Rooms(Vector<RoomSummary>),
127
    ImagePacks(ImagePacks),
128
    Timeline {
129
        timeline_id: TimelineId,
130
        change: TimelineChange,
131
    },
132
    History {
133
        timeline_id: TimelineId,
134
        state: HistoryState,
135
    },
136
    Typing(TypingState),
137
    Upload {
138
        transaction_id: OwnedTransactionId,
139
        upload: UploadState,
140
    },
141
    UploadFinished {
142
        room_id: OwnedRoomId,
143
        transaction_id: OwnedTransactionId,
144
        outcome: UploadOutcome,
145
    },
146
    Failed(EngineFailure),
147
}
148
149
#[derive(Clone, Debug)]
150
pub enum TimelineChange {
151
    Reset {
152
        items: Vector<TimelineItem>,
153
    },
154
    /// `items` is what applying `diffs` left
155
    Changed {
156
        diffs: Arc<[TimelineDiff]>,
157
        items: Vector<TimelineItem>,
158
    },
159
    /// a batch didn't fit. a resync is on its way and nothing changes until its reset
160
    Diverged,
161
    Closed,
162
}
163
164
/// drains the event stream into a `State` that any number of `MirrorHandle`s watch
165
pub struct Mirror {
166
    handle: ClientHandle,
167
    events: EngineBoxStream<Event>,
168
    shared: Arc<Shared>,
169
    resyncs: HashMap<TimelineId, AbortHandle>,
170
    rooms_fetch: Option<AbortHandle>,
171
    packs_fetch: Option<AbortHandle>,
172
}
173
174
impl fmt::Debug for Mirror {
175
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
176
        formatter.debug_struct("Mirror").finish_non_exhaustive()
177
    }
178
}
179
180
impl Mirror {
181
    /// takes any stream of the engine's events, so a host can still look at them on the way past
182
    pub fn new(
183
        handle: ClientHandle,
184
        events: impl Stream<Item = Event> + SendOutsideWasm + 'static,
185
    ) -> (Self, MirrorHandle) {
186
        Self::with_capacity(handle, events, DEFAULT_CAPACITY)
187
    }
188
189
    /// `capacity` updates can queue for a subscriber before it lags and starts over. a zero
190
    /// capacity counts as one
191
    pub fn with_capacity(
192
        handle: ClientHandle,
193
        events: impl Stream<Item = Event> + SendOutsideWasm + 'static,
194
        capacity: usize,
195
    ) -> (Self, MirrorHandle) {
196
        let state = State {
197
            user_id: None,
198
            session: SessionStatus::Active,
199
            connection: *handle.connection_state().borrow(),
200
            encryption: handle.encryption_state(),
201
            verification: None,
202
            verification_target: None,
203
            rooms: Vector::new(),
204
            image_packs: ImagePacks::default(),
205
            timelines: imbl::HashMap::new(),
206
            typing: imbl::HashMap::new(),
207
            uploads: imbl::HashMap::new(),
208
            engine_failure: None,
209
            session_save_failure: None,
210
        };
211
        let shared = Arc::new(Shared {
212
            published: Mutex::new(Published {
213
                state: Arc::new(state),
214
                sequence: 0,
215
                updates: Some(broadcast::channel(capacity.max(1)).0),
216
                rooms_fetch: Fetch::Idle,
217
                packs_fetch: Fetch::Idle,
218
            }),
219
        });
220
        #[cfg(not(target_family = "wasm"))]
221
        let events = events.boxed();
222
        #[cfg(target_family = "wasm")]
223
        let events = events.boxed_local();
224
        let mirror = Self {
225
            handle,
226
            events,
227
            shared: shared.clone(),
228
            resyncs: HashMap::new(),
229
            rooms_fetch: None,
230
            packs_fetch: None,
231
        };
232
        (mirror, MirrorHandle { shared })
233
    }
234
235
    /// resolves once the event stream ends, which is just after `Stopped`
236
    pub fn run(mut self) -> EngineBoxFuture<'static, ()> {
237
        Box::pin(async move {
238
            self.refetch_rooms();
239
            self.refetch_image_packs();
240
            while let Some(event) = self.events.next().await {
241
                self.apply(event);
242
            }
243
        })
244
    }
245
246
    fn apply(&mut self, event: Event) {
247
        match event {
248
            Event::ConnectionChanged(connection) => self.publish(|state| {
249
                state.connection = connection;
250
                Some(StateChange::Connection(connection))
251
            }),
252
            Event::Authenticated { user_id } => self.publish(|state| {
253
                state.user_id = Some(user_id.clone());
254
                Some(StateChange::Authenticated(user_id))
255
            }),
256
            Event::SessionSaveFailed { message } => self.publish(|state| {
257
                state.session_save_failure = Some(message.clone());
258
                Some(StateChange::SessionSaveFailed(message))
259
            }),
260
            Event::SessionTokensChanged => self.publish(|state| {
261
                state.session_save_failure.take()?;
262
                Some(StateChange::SessionSaved)
263
            }),
264
            Event::SessionAccessRejected { soft_logout } => self.publish(|state| {
265
                state.session = match soft_logout {
266
                    Some(device_id) => SessionStatus::SoftLoggedOut { device_id },
267
                    None => SessionStatus::AccessRevoked,
268
                };
269
                Some(StateChange::Session(state.session.clone()))
270
            }),
271
            Event::LoggedOut => self.publish(|state| {
272
                state.session = SessionStatus::LoggedOut;
273
                Some(StateChange::Session(SessionStatus::LoggedOut))
274
            }),
275
            Event::Stopped => {
276
                self.cancel_resyncs();
277
                self.publish(|state| {
278
                    if state.session != SessionStatus::Active {
279
                        return None;
280
                    }
281
                    state.session = SessionStatus::Stopped;
282
                    Some(StateChange::Session(SessionStatus::Stopped))
283
                });
284
            }
285
            Event::Failed(failure) => self.publish(|state| {
286
                state.engine_failure = Some(failure.clone());
287
                Some(StateChange::Failed(failure))
288
            }),
289
            Event::NotificationSettingsChanged => {
290
                self.publish(|_| Some(StateChange::NotificationSettings));
291
            }
292
            Event::RoomsChanged => self.refetch_rooms(),
293
            Event::ImagePacksChanged => self.refetch_image_packs(),
294
            Event::EncryptionChanged(encryption) => self.publish(|state| {
295
                state.encryption = Some(encryption.clone());
296
                Some(StateChange::Encryption(encryption))
297
            }),
298
            Event::VerificationChanged(verification) => self.publish(|state| {
299
                state.verification = Some(verification.clone());
300
                Some(StateChange::Verification(verification))
301
            }),
302
            Event::VerificationTargetChanged(target) => self.publish(|state| {
303
                state.verification_target = Some(target.clone());
304
                Some(StateChange::VerificationTarget(target))
305
            }),
306
            Event::TimelineReset { timeline_id, items } => {
307
                self.cancel_resync(&timeline_id);
308
                self.publish(|state| {
309
                    let history = state
310
                        .timelines
311
                        .get(&timeline_id)
312
                        .map_or(HistoryState::Idle, |timeline| timeline.history.clone());
313
                    let timeline = TimelineState {
314
                        items: items.clone(),
315
                        history,
316
                        diverged: false,
317
                    };
318
                    state.timelines.insert(timeline_id.clone(), timeline);
319
                    Some(StateChange::Timeline {
320
                        timeline_id,
321
                        change: TimelineChange::Reset { items },
322
                    })
323
                });
324
            }
325
            Event::TimelineChanged { timeline_id, diffs } => self.apply_diffs(timeline_id, diffs),
326
            Event::HistoryChanged {
327
                timeline_id,
328
                state: history,
329
            } => self.publish(|state| {
330
                state.timelines.get_mut(&timeline_id)?.history = history.clone();
331
                Some(StateChange::History {
332
                    timeline_id,
333
                    state: history,
334
                })
335
            }),
336
            Event::TimelineClosed { timeline_id } => {
337
                self.cancel_resync(&timeline_id);
338
                self.publish(|state| {
339
                    state.timelines.remove(&timeline_id)?;
340
                    Some(StateChange::Timeline {
341
                        timeline_id,
342
                        change: TimelineChange::Closed,
343
                    })
344
                });
345
            }
346
            Event::TypingChanged(typing) => self.publish(|state| {
347
                if typing.users.is_empty() {
348
                    state.typing.remove(&typing.room_id);
349
                } else {
350
                    state
351
                        .typing
352
                        .insert(typing.room_id.clone(), typing.users.clone());
353
                }
354
                Some(StateChange::Typing(typing))
355
            }),
356
            Event::MediaUploadProgress {
357
                room_id,
358
                transaction_id,
359
                uploaded_bytes,
360
                total_bytes,
361
                complete,
362
            } => self.publish(|state| {
363
                let upload = UploadState {
364
                    room_id,
365
                    uploaded_bytes,
366
                    total_bytes,
367
                    complete,
368
                    failure: None,
369
                };
370
                state.uploads.insert(transaction_id.clone(), upload.clone());
371
                Some(StateChange::Upload {
372
                    transaction_id,
373
                    upload,
374
                })
375
            }),
376
            Event::MediaUploadFailed {
377
                room_id,
378
                transaction_id,
379
                kind,
380
                will_retry,
381
            } => self.publish(|state| {
382
                let upload = state
383
                    .uploads
384
                    .entry(transaction_id.clone())
385
                    .or_insert_with(|| UploadState {
386
                        room_id,
387
                        uploaded_bytes: 0,
388
                        total_bytes: 0,
389
                        complete: false,
390
                        failure: None,
391
                    });
392
                upload.failure = Some(UploadFailure { kind, will_retry });
393
                Some(StateChange::Upload {
394
                    transaction_id,
395
                    upload: upload.clone(),
396
                })
397
            }),
398
            Event::MediaUploadFinished {
399
                room_id,
400
                transaction_id,
401
                outcome,
402
            } => self.publish(|state| {
403
                state.uploads.remove(&transaction_id);
404
                Some(StateChange::UploadFinished {
405
                    room_id,
406
                    transaction_id,
407
                    outcome,
408
                })
409
            }),
410
        }
411
    }
412
413
    fn apply_diffs(&mut self, timeline_id: TimelineId, diffs: Vec<TimelineDiff>) {
414
        let mut diverged = false;
415
        self.publish(|state| {
416
            let Some(timeline) = state.timelines.get_mut(&timeline_id) else {
417
                diverged = true;
418
                state.timelines.insert(
419
                    timeline_id.clone(),
420
                    TimelineState {
421
                        items: Vector::new(),
422
                        history: HistoryState::Idle,
423
                        diverged: true,
424
                    },
425
                );
426
                return Some(StateChange::Timeline {
427
                    timeline_id: timeline_id.clone(),
428
                    change: TimelineChange::Diverged,
429
                });
430
            };
431
            if timeline.diverged {
432
                return None;
433
            }
434
            let diffs: Arc<[TimelineDiff]> = diffs.into();
435
            let change = match TimelineDiff::try_apply_all(diffs.to_vec(), &mut timeline.items) {
436
                Ok(()) => TimelineChange::Changed {
437
                    diffs,
438
                    items: timeline.items.clone(),
439
                },
440
                Err(error) => {
441
                    tracing::debug!(?timeline_id, length = error.length, "timeline diverged");
442
                    timeline.diverged = true;
443
                    diverged = true;
444
                    TimelineChange::Diverged
445
                }
446
            };
447
            Some(StateChange::Timeline {
448
                timeline_id: timeline_id.clone(),
449
                change,
450
            })
451
        });
452
        if diverged {
453
            let task = spawn(resync(self.handle.clone(), timeline_id.clone()));
454
            self.resyncs.insert(timeline_id, task.abort_handle());
455
        }
456
    }
457
458
    fn refetch_rooms(&mut self) {
459
        let fetch = fetch_rooms(self.handle.clone(), self.shared.clone());
460
        if let Some(task) = self
461
            .shared
462
            .start(|published| &mut published.rooms_fetch, fetch)
463
        {
464
            self.rooms_fetch = Some(task);
465
        }
466
    }
467
468
    fn refetch_image_packs(&mut self) {
469
        let fetch = fetch_image_packs(self.handle.clone(), self.shared.clone());
470
        if let Some(task) = self
471
            .shared
472
            .start(|published| &mut published.packs_fetch, fetch)
473
        {
474
            self.packs_fetch = Some(task);
475
        }
476
    }
477
478
    fn cancel_resync(&mut self, timeline_id: &TimelineId) {
479
        if let Some(task) = self.resyncs.remove(timeline_id) {
480
            task.abort();
481
        }
482
    }
483
484
    fn cancel_resyncs(&mut self) {
485
        for (_, task) in self.resyncs.drain() {
486
            task.abort();
487
        }
488
    }
489
490
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
491
        self.shared.publish(change);
492
    }
493
}
494
495
impl Drop for Mirror {
496
    fn drop(&mut self) {
497
        self.cancel_resyncs();
498
        for task in [self.rooms_fetch.take(), self.packs_fetch.take()]
499
            .into_iter()
500
            .flatten()
501
        {
502
            task.abort();
503
        }
504
        self.shared.lock().updates = None;
505
    }
506
}
507
508
/// asks until the engine answers, since a busy room or a full queue is only ever for now.
509
/// a reset or a close for the timeline aborts it, and the engine sends a reset before it answers
510
async fn resync(handle: ClientHandle, timeline_id: TimelineId) {
511
    let mut backoff = RESYNC_BACKOFF;
512
    loop {
513
        let command = Command::Timeline(TimelineCommand::Resync {
514
            timeline_id: timeline_id.clone(),
515
        });
516
        match handle.execute(command).await {
517
            Ok(_) => return,
518
            Err(error) if error.kind == ErrorKind::Interrupted => return,
519
            Err(error) => {
520
                tracing::debug!(%error, ?timeline_id, "resync refused, trying again");
521
                matrix_sdk::sleep::sleep(backoff).await;
522
                backoff = (backoff * 2).min(RESYNC_BACKOFF_CAP);
523
            }
524
        }
525
    }
526
}
527
528
async fn fetch_rooms(handle: ClientHandle, shared: Arc<Shared>) {
529
    loop {
530
        let rooms: Vector<RoomSummary> = handle.rooms().await.into();
531
        shared.publish(|state| {
532
            state.rooms = rooms.clone();
533
            Some(StateChange::Rooms(rooms))
534
        });
535
        if !shared.fetched(|published| &mut published.rooms_fetch) {
536
            return;
537
        }
538
    }
539
}
540
541
/// a failed read keeps the packs from before, the next change asks again
542
async fn fetch_image_packs(handle: ClientHandle, shared: Arc<Shared>) {
543
    loop {
544
        match handle.image_packs().await {
545
            Ok(packs) => shared.publish(|state| {
546
                if state.image_packs == packs {
547
                    return None;
548
                }
549
                state.image_packs = packs.clone();
550
                Some(StateChange::ImagePacks(packs))
551
            }),
552
            Err(error) => tracing::warn!(%error, "reading image packs failed"),
553
        }
554
        if !shared.fetched(|published| &mut published.packs_fetch) {
555
            return;
556
        }
557
    }
558
}
559
560
/// cheap to clone, one per frontend
561
#[derive(Clone, Debug)]
562
pub struct MirrorHandle {
563
    shared: Arc<Shared>,
564
}
565
566
impl MirrorHandle {
567
    #[must_use]
568
    pub fn snapshot(&self) -> Arc<State> {
569
        self.shared.lock().state.clone()
570
    }
571
572
    /// opens with the current state, then every update after it. ends once the mirror stops
573
    #[must_use]
574
    pub fn subscribe(&self) -> Subscription {
575
        let (first, receiving) = self.shared.clone().subscribe();
576
        let rest = futures_util::stream::unfold(receiving, |(shared, receiver)| async move {
577
            let mut receiver = receiver?;
578
            match receiver.recv().await {
579
                Ok(update) => Some((update, (shared, Some(receiver)))),
580
                Err(broadcast::error::RecvError::Lagged(missed)) => {
581
                    tracing::debug!(missed, "mirror subscriber lagged, starting it over");
582
                    Some(shared.subscribe())
583
                }
584
                Err(broadcast::error::RecvError::Closed) => None,
585
            }
586
        });
587
        let updates = futures_util::stream::once(std::future::ready(first)).chain(rest);
588
        #[cfg(not(target_family = "wasm"))]
589
        let updates = updates.boxed();
590
        #[cfg(target_family = "wasm")]
591
        let updates = updates.boxed_local();
592
        Subscription(updates)
593
    }
594
}
595
596
pub struct Subscription(EngineBoxStream<Update>);
597
598
impl fmt::Debug for Subscription {
599
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
600
        formatter
601
            .debug_struct("Subscription")
602
            .finish_non_exhaustive()
603
    }
604
}
605
606
impl Stream for Subscription {
607
    type Item = Update;
608
609
    fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Update>> {
610
        self.0.poll_next_unpin(context)
611
    }
612
}
613
614
#[derive(Debug)]
615
struct Shared {
616
    published: Mutex<Published>,
617
}
618
619
#[derive(Debug)]
620
struct Published {
621
    state: Arc<State>,
622
    sequence: u64,
623
    /// gone once the mirror stops, which ends every subscription
624
    updates: Option<broadcast::Sender<Update>>,
625
    rooms_fetch: Fetch,
626
    packs_fetch: Fetch,
627
}
628
629
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
630
enum Fetch {
631
    Idle,
632
    Running,
633
    Again,
634
}
635
636
impl Shared {
637
    fn lock(&self) -> MutexGuard<'_, Published> {
638
        self.published
639
            .lock()
640
            .unwrap_or_else(PoisonError::into_inner)
641
    }
642
643
    /// one fetch at a time, with at most one more owed
644
    fn start(
645
        &self,
646
        fetch: impl FnOnce(&mut Published) -> &mut Fetch,
647
        task: impl Future<Output = ()> + SendOutsideWasm + 'static,
648
    ) -> Option<AbortHandle> {
649
        let mut published = self.lock();
650
        let fetch = fetch(&mut published);
651
        match *fetch {
652
            Fetch::Idle => {
653
                *fetch = Fetch::Running;
654
                drop(published);
655
                Some(spawn(task).abort_handle())
656
            }
657
            Fetch::Running => {
658
                *fetch = Fetch::Again;
659
                None
660
            }
661
            Fetch::Again => None,
662
        }
663
    }
664
665
    /// whether another fetch was asked for while this one ran
666
    fn fetched(&self, fetch: impl FnOnce(&mut Published) -> &mut Fetch) -> bool {
667
        let mut published = self.lock();
668
        let fetch = fetch(&mut published);
669
        if *fetch == Fetch::Again {
670
            *fetch = Fetch::Running;
671
            return true;
672
        }
673
        *fetch = Fetch::Idle;
674
        false
675
    }
676
677
    /// state, sequence and send move together under the one lock, which is what lets
678
    /// `subscribe` take a snapshot and a receiver with nothing between them
679
    fn publish(&self, change: impl FnOnce(&mut State) -> Option<StateChange>) {
680
        let mut published = self.lock();
681
        let Some(change) = change(Arc::make_mut(&mut published.state)) else {
682
            return;
683
        };
684
        published.sequence += 1;
685
        let update = Update {
686
            sequence: published.sequence,
687
            change,
688
        };
689
        if let Some(updates) = &published.updates {
690
            let _ = updates.send(update);
691
        }
692
    }
693
694
    fn subscribe(self: Arc<Self>) -> (Update, (Arc<Self>, Option<broadcast::Receiver<Update>>)) {
695
        let published = self.lock();
696
        let update = Update {
697
            sequence: published.sequence,
698
            change: StateChange::State(published.state.clone()),
699
        };
700
        let receiver = published.updates.as_ref().map(broadcast::Sender::subscribe);
701
        drop(published);
702
        (update, (self, receiver))
703
    }
704
}