atagen

macltok

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