atagen

macltok

public
Star0
Watch0
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/engine/room.rs

31ce2453c82fatagen2026-09-24 13:07 UTC1
use std::collections::{HashMap, VecDeque};
31ce2453c82fatagen2026-09-24 13:07 UTC2
31ce2453c82fatagen2026-09-24 13:07 UTC3
use matrix_sdk::{Room, RoomState};
31ce2453c82fatagen2026-09-24 13:07 UTC4
use matrix_sdk_ui::timeline::{RoomExt, Timeline, TimelineFocus, TimelineReadReceiptTracking};
fa5b4e09943aatagen2026-10-24 09:33 UTC5
use ruma::OwnedRoomId;
1fe480c16a3catagen2026-09-26 08:44 UTC6
use tokio::sync::mpsc;
31ce2453c82fatagen2026-09-24 13:07 UTC7
1fe480c16a3catagen2026-09-26 08:44 UTC8
use super::executor::{AbortHandle, JoinError, TaskSet, is_panic, resume_unwind};
80381e06a573atagen2026-10-25 03:02 UTC9
use super::repair::{InstallRequest, RepairController};
5a52012c9b1datagen2026-09-24 23:45 UTC10
use super::timeline::{SessionContext, TaskGuard, TimelineSession};
31ce2453c82fatagen2026-09-24 13:07 UTC11
use crate::{
80381e06a573atagen2026-10-25 03:02 UTC12
    Error, ErrorKind, Event, HistoryRepairCommand, TimelineCommand, TimelineId, TypingState,
80381e06a573atagen2026-10-25 03:02 UTC13
    transport::Responder,
31ce2453c82fatagen2026-09-24 13:07 UTC14
};
31ce2453c82fatagen2026-09-24 13:07 UTC15
31ce2453c82fatagen2026-09-24 13:07 UTC16
pub(super) enum ActorRequest {
31ce2453c82fatagen2026-09-24 13:07 UTC17
    /// opening an open room answers with a fresh reset
31ce2453c82fatagen2026-09-24 13:07 UTC18
    Open(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC19
    Execute(TimelineCommand, Responder),
80381e06a573atagen2026-10-25 03:02 UTC20
    Repair(HistoryRepairCommand, Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC21
    Close(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC22
}
31ce2453c82fatagen2026-09-24 13:07 UTC23
31ce2453c82fatagen2026-09-24 13:07 UTC24
impl ActorRequest {
31ce2453c82fatagen2026-09-24 13:07 UTC25
    pub(super) fn into_reply(self) -> Responder {
31ce2453c82fatagen2026-09-24 13:07 UTC26
        match self {
80381e06a573atagen2026-10-25 03:02 UTC27
            Self::Open(reply)
80381e06a573atagen2026-10-25 03:02 UTC28
            | Self::Execute(_, reply)
80381e06a573atagen2026-10-25 03:02 UTC29
            | Self::Repair(_, reply)
80381e06a573atagen2026-10-25 03:02 UTC30
            | Self::Close(reply) => reply,
31ce2453c82fatagen2026-09-24 13:07 UTC31
        }
31ce2453c82fatagen2026-09-24 13:07 UTC32
    }
31ce2453c82fatagen2026-09-24 13:07 UTC33
}
31ce2453c82fatagen2026-09-24 13:07 UTC34
5a52012c9b1datagen2026-09-24 23:45 UTC35
pub(super) enum ActorNote {
5a52012c9b1datagen2026-09-24 23:45 UTC36
    /// stopping on its own, after a leave or a kick. anything sent its way now comes back in `Exited`
5a52012c9b1datagen2026-09-24 23:45 UTC37
    Leaving {
5a52012c9b1datagen2026-09-24 23:45 UTC38
        room_id: OwnedRoomId,
5a52012c9b1datagen2026-09-24 23:45 UTC39
        generation: u64,
5a52012c9b1datagen2026-09-24 23:45 UTC40
    },
5a52012c9b1datagen2026-09-24 23:45 UTC41
    Exited {
5a52012c9b1datagen2026-09-24 23:45 UTC42
        room_id: OwnedRoomId,
5a52012c9b1datagen2026-09-24 23:45 UTC43
        generation: u64,
5a52012c9b1datagen2026-09-24 23:45 UTC44
        /// requests that arrived too late to be served, for the engine to answer or replay
5a52012c9b1datagen2026-09-24 23:45 UTC45
        leftovers: Vec<ActorRequest>,
5a52012c9b1datagen2026-09-24 23:45 UTC46
    },
31ce2453c82fatagen2026-09-24 13:07 UTC47
}
31ce2453c82fatagen2026-09-24 13:07 UTC48
31ce2453c82fatagen2026-09-24 13:07 UTC49
pub(super) struct RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC50
    pub(super) room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC51
    pub(super) generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC52
    pub(super) room: Room,
31ce2453c82fatagen2026-09-24 13:07 UTC53
    pub(super) requests: mpsc::Receiver<ActorRequest>,
5a52012c9b1datagen2026-09-24 23:45 UTC54
    pub(super) notes: mpsc::UnboundedSender<ActorNote>,
31ce2453c82fatagen2026-09-24 13:07 UTC55
    pub(super) context: SessionContext,
31ce2453c82fatagen2026-09-24 13:07 UTC56
    pub(super) panics: mpsc::UnboundedReceiver<&'static str>,
31ce2453c82fatagen2026-09-24 13:07 UTC57
    pub(super) opening: Vec<Responder>,
31ce2453c82fatagen2026-09-24 13:07 UTC58
}
31ce2453c82fatagen2026-09-24 13:07 UTC59
31ce2453c82fatagen2026-09-24 13:07 UTC60
impl RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC61
    pub(super) async fn run(mut self) {
5a52012c9b1datagen2026-09-24 23:45 UTC62
        self.serve().await;
5a52012c9b1datagen2026-09-24 23:45 UTC63
        self.requests.close();
5a52012c9b1datagen2026-09-24 23:45 UTC64
        let mut leftovers = Vec::new();
5a52012c9b1datagen2026-09-24 23:45 UTC65
        while let Ok(request) = self.requests.try_recv() {
5a52012c9b1datagen2026-09-24 23:45 UTC66
            leftovers.push(request);
5a52012c9b1datagen2026-09-24 23:45 UTC67
        }
5a52012c9b1datagen2026-09-24 23:45 UTC68
        let _ = self.notes.send(ActorNote::Exited {
31ce2453c82fatagen2026-09-24 13:07 UTC69
            room_id: self.room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC70
            generation: self.generation,
5a52012c9b1datagen2026-09-24 23:45 UTC71
            leftovers,
5a52012c9b1datagen2026-09-24 23:45 UTC72
        });
31ce2453c82fatagen2026-09-24 13:07 UTC73
    }
31ce2453c82fatagen2026-09-24 13:07 UTC74
31ce2453c82fatagen2026-09-24 13:07 UTC75
    async fn serve(&mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC76
        let timeline = match build_timeline(
31ce2453c82fatagen2026-09-24 13:07 UTC77
            &self.room,
31ce2453c82fatagen2026-09-24 13:07 UTC78
            TimelineFocus::Live {
31ce2453c82fatagen2026-09-24 13:07 UTC79
                hide_threaded_events: true,
31ce2453c82fatagen2026-09-24 13:07 UTC80
            },
31ce2453c82fatagen2026-09-24 13:07 UTC81
        )
31ce2453c82fatagen2026-09-24 13:07 UTC82
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC83
        {
31ce2453c82fatagen2026-09-24 13:07 UTC84
            Ok(timeline) => timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC85
            Err(error) => return self.refuse_everything(error).await,
31ce2453c82fatagen2026-09-24 13:07 UTC86
        };
31ce2453c82fatagen2026-09-24 13:07 UTC87
        let Ok(room_timeline) = TimelineSession::start(
31ce2453c82fatagen2026-09-24 13:07 UTC88
            TimelineId::Room(self.room_id.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC89
            timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC90
            self.context.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC91
        )
31ce2453c82fatagen2026-09-24 13:07 UTC92
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC93
        else {
31ce2453c82fatagen2026-09-24 13:07 UTC94
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC95
        };
31ce2453c82fatagen2026-09-24 13:07 UTC96
        for reply in self.opening.drain(..) {
31ce2453c82fatagen2026-09-24 13:07 UTC97
            reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC98
        }
31ce2453c82fatagen2026-09-24 13:07 UTC99
        let (typing_guard, typing) = self.room.subscribe_to_typing_notifications();
0bfa3f46f0beatagen2026-09-26 09:12 UTC100
        let mut typing_watcher = TaskGuard(self.context.supervisor.spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC101
            "typing watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC102
            watch_typing(self.room_id.clone(), typing, self.context.events.clone()),
5a52012c9b1datagen2026-09-24 23:45 UTC103
        ));
31ce2453c82fatagen2026-09-24 13:07 UTC104
        let mut timelines = Timelines {
31ce2453c82fatagen2026-09-24 13:07 UTC105
            room: room_timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC106
            threads: Threads::new(self.requests.max_capacity()),
1fe480c16a3catagen2026-09-26 08:44 UTC107
            work: TaskSet::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC108
        };
80381e06a573atagen2026-10-25 03:02 UTC109
        let mut repair = RepairController::new(self.room.clone(), self.context.clone());
80381e06a573atagen2026-10-25 03:02 UTC110
        let close = self.serve_open(&mut timelines, &mut repair).await;
80381e06a573atagen2026-10-25 03:02 UTC111
        repair.shutdown().await;
5a52012c9b1datagen2026-09-24 23:45 UTC112
        if close.is_none() {
5a52012c9b1datagen2026-09-24 23:45 UTC113
            self.requests.close();
5a52012c9b1datagen2026-09-24 23:45 UTC114
            let _ = self.notes.send(ActorNote::Leaving {
5a52012c9b1datagen2026-09-24 23:45 UTC115
                room_id: self.room_id.clone(),
5a52012c9b1datagen2026-09-24 23:45 UTC116
                generation: self.generation,
5a52012c9b1datagen2026-09-24 23:45 UTC117
            });
5a52012c9b1datagen2026-09-24 23:45 UTC118
        }
31ce2453c82fatagen2026-09-24 13:07 UTC119
31ce2453c82fatagen2026-09-24 13:07 UTC120
        // let redactions and receipts that already left finish rather than lie about them
31ce2453c82fatagen2026-09-24 13:07 UTC121
        while timelines.work.join_next().await.is_some() {}
31ce2453c82fatagen2026-09-24 13:07 UTC122
        drop(typing_guard);
0bfa3f46f0beatagen2026-09-26 09:12 UTC123
        typing_watcher.0.abort();
0bfa3f46f0beatagen2026-09-26 09:12 UTC124
        let _ = (&mut typing_watcher.0).await;
0bfa3f46f0beatagen2026-09-26 09:12 UTC125
        let _ = self
0bfa3f46f0beatagen2026-09-26 09:12 UTC126
            .context
0bfa3f46f0beatagen2026-09-26 09:12 UTC127
            .events
0bfa3f46f0beatagen2026-09-26 09:12 UTC128
            .send(Event::TypingChanged(TypingState {
0bfa3f46f0beatagen2026-09-26 09:12 UTC129
                room_id: self.room_id.clone(),
0bfa3f46f0beatagen2026-09-26 09:12 UTC130
                users: Vec::new(),
0bfa3f46f0beatagen2026-09-26 09:12 UTC131
            }))
0bfa3f46f0beatagen2026-09-26 09:12 UTC132
            .await;
31ce2453c82fatagen2026-09-24 13:07 UTC133
        timelines.threads.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC134
        timelines.room.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC135
        if let Some(reply) = close {
31ce2453c82fatagen2026-09-24 13:07 UTC136
            reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC137
        }
31ce2453c82fatagen2026-09-24 13:07 UTC138
    }
31ce2453c82fatagen2026-09-24 13:07 UTC139
31ce2453c82fatagen2026-09-24 13:07 UTC140
    /// returns the close request's responder, if a close is why we stopped
80381e06a573atagen2026-10-25 03:02 UTC141
    async fn serve_open(
80381e06a573atagen2026-10-25 03:02 UTC142
        &mut self,
80381e06a573atagen2026-10-25 03:02 UTC143
        timelines: &mut Timelines,
80381e06a573atagen2026-10-25 03:02 UTC144
        repair: &mut RepairController,
80381e06a573atagen2026-10-25 03:02 UTC145
    ) -> Option<Responder> {
31ce2453c82fatagen2026-09-24 13:07 UTC146
        let mut room_info = self.room.subscribe_info();
5a52012c9b1datagen2026-09-24 23:45 UTC147
        // a leave that landed while the timeline was building won't come round again
5a52012c9b1datagen2026-09-24 23:45 UTC148
        if matches!(self.room.state(), RoomState::Left | RoomState::Banned) {
5a52012c9b1datagen2026-09-24 23:45 UTC149
            return None;
5a52012c9b1datagen2026-09-24 23:45 UTC150
        }
31ce2453c82fatagen2026-09-24 13:07 UTC151
        loop {
31ce2453c82fatagen2026-09-24 13:07 UTC152
            tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC153
                Some(info) = room_info.next() => {
31ce2453c82fatagen2026-09-24 13:07 UTC154
                    if matches!(info.state(), RoomState::Left | RoomState::Banned) {
31ce2453c82fatagen2026-09-24 13:07 UTC155
                        tracing::debug!(room_id = %self.room_id, "room actor stopped because we left the room");
31ce2453c82fatagen2026-09-24 13:07 UTC156
                        return None;
31ce2453c82fatagen2026-09-24 13:07 UTC157
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC158
                }
31ce2453c82fatagen2026-09-24 13:07 UTC159
                request = self.requests.recv() => match request {
31ce2453c82fatagen2026-09-24 13:07 UTC160
                    None => return None,
31ce2453c82fatagen2026-09-24 13:07 UTC161
                    Some(ActorRequest::Close(reply)) => return Some(reply),
a951cb7cb7caatagen2026-10-24 23:17 UTC162
                    Some(ActorRequest::Open(reply)) => reply.done(timelines.room.resync().await),
31ce2453c82fatagen2026-09-24 13:07 UTC163
                    Some(ActorRequest::Execute(command, reply)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC164
                        timelines.execute(command, reply, &self.room).await;
31ce2453c82fatagen2026-09-24 13:07 UTC165
                    }
80381e06a573atagen2026-10-25 03:02 UTC166
                    Some(ActorRequest::Repair(command, reply)) => {
80381e06a573atagen2026-10-25 03:02 UTC167
                        repair.execute(command, reply).await;
80381e06a573atagen2026-10-25 03:02 UTC168
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC169
                },
80381e06a573atagen2026-10-25 03:02 UTC170
                install = repair.next_install(), if repair.is_running() => {
80381e06a573atagen2026-10-25 03:02 UTC171
                    if let Some(install) = install {
80381e06a573atagen2026-10-25 03:02 UTC172
                        timelines.install_history(install, &self.room, &self.context).await;
80381e06a573atagen2026-10-25 03:02 UTC173
                    }
80381e06a573atagen2026-10-25 03:02 UTC174
                }
31ce2453c82fatagen2026-09-24 13:07 UTC175
                Some(built) = timelines.threads.builds.join_next(), if !timelines.threads.builds.is_empty() => {
31ce2453c82fatagen2026-09-24 13:07 UTC176
                    timelines.thread_built(built, &self.room_id, &self.context).await;
31ce2453c82fatagen2026-09-24 13:07 UTC177
                }
31ce2453c82fatagen2026-09-24 13:07 UTC178
                Some(label) = self.panics.recv() => panic!("{label} panicked in room {}", self.room_id),
31ce2453c82fatagen2026-09-24 13:07 UTC179
                Some(done) = timelines.work.join_next(), if !timelines.work.is_empty() => {
1fe480c16a3catagen2026-09-26 08:44 UTC180
                    if let Err(error) = done && is_panic(&error) {
31ce2453c82fatagen2026-09-24 13:07 UTC181
                        tracing::warn!(room_id = %self.room_id, %error, "timeline operation panicked");
31ce2453c82fatagen2026-09-24 13:07 UTC182
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC183
                }
31ce2453c82fatagen2026-09-24 13:07 UTC184
            }
31ce2453c82fatagen2026-09-24 13:07 UTC185
        }
31ce2453c82fatagen2026-09-24 13:07 UTC186
    }
31ce2453c82fatagen2026-09-24 13:07 UTC187
31ce2453c82fatagen2026-09-24 13:07 UTC188
    async fn refuse_everything(&mut self, error: matrix_sdk_ui::timeline::Error) {
31ce2453c82fatagen2026-09-24 13:07 UTC189
        tracing::warn!(%error, room_id = %self.room_id, "room actor could not build its timeline");
31ce2453c82fatagen2026-09-24 13:07 UTC190
        let error = Error::with_source(ErrorKind::Timeline, "room timeline build failed", error);
31ce2453c82fatagen2026-09-24 13:07 UTC191
        for reply in self.opening.drain(..) {
31ce2453c82fatagen2026-09-24 13:07 UTC192
            reply.done(Err(error.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC193
        }
31ce2453c82fatagen2026-09-24 13:07 UTC194
        self.requests.close();
31ce2453c82fatagen2026-09-24 13:07 UTC195
        while let Some(request) = self.requests.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC196
            request.into_reply().done(Err(error.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC197
        }
31ce2453c82fatagen2026-09-24 13:07 UTC198
    }
31ce2453c82fatagen2026-09-24 13:07 UTC199
}
31ce2453c82fatagen2026-09-24 13:07 UTC200
31ce2453c82fatagen2026-09-24 13:07 UTC201
async fn build_timeline(
31ce2453c82fatagen2026-09-24 13:07 UTC202
    room: &Room,
31ce2453c82fatagen2026-09-24 13:07 UTC203
    focus: TimelineFocus,
31ce2453c82fatagen2026-09-24 13:07 UTC204
) -> Result<Timeline, matrix_sdk_ui::timeline::Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC205
    room.timeline_builder()
31ce2453c82fatagen2026-09-24 13:07 UTC206
        .with_focus(focus)
31ce2453c82fatagen2026-09-24 13:07 UTC207
        .track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
31ce2453c82fatagen2026-09-24 13:07 UTC208
        .build()
31ce2453c82fatagen2026-09-24 13:07 UTC209
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC210
}
31ce2453c82fatagen2026-09-24 13:07 UTC211
31ce2453c82fatagen2026-09-24 13:07 UTC212
async fn watch_typing(
31ce2453c82fatagen2026-09-24 13:07 UTC213
    room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC214
    mut typing: tokio::sync::broadcast::Receiver<Vec<ruma::OwnedUserId>>,
31ce2453c82fatagen2026-09-24 13:07 UTC215
    events: crate::transport::EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC216
) {
31ce2453c82fatagen2026-09-24 13:07 UTC217
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC218
        match typing.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC219
            Ok(users) => {
31ce2453c82fatagen2026-09-24 13:07 UTC220
                let state = TypingState {
31ce2453c82fatagen2026-09-24 13:07 UTC221
                    room_id: room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC222
                    users,
31ce2453c82fatagen2026-09-24 13:07 UTC223
                };
31ce2453c82fatagen2026-09-24 13:07 UTC224
                if events.send(Event::TypingChanged(state)).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC225
                    return;
31ce2453c82fatagen2026-09-24 13:07 UTC226
                }
31ce2453c82fatagen2026-09-24 13:07 UTC227
            }
31ce2453c82fatagen2026-09-24 13:07 UTC228
            Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC229
                tracing::warn!(%room_id, skipped, "typing watcher lagged");
31ce2453c82fatagen2026-09-24 13:07 UTC230
            }
31ce2453c82fatagen2026-09-24 13:07 UTC231
            Err(tokio::sync::broadcast::error::RecvError::Closed) => return,
31ce2453c82fatagen2026-09-24 13:07 UTC232
        }
31ce2453c82fatagen2026-09-24 13:07 UTC233
    }
31ce2453c82fatagen2026-09-24 13:07 UTC234
}
31ce2453c82fatagen2026-09-24 13:07 UTC235
31ce2453c82fatagen2026-09-24 13:07 UTC236
struct Timelines {
31ce2453c82fatagen2026-09-24 13:07 UTC237
    room: TimelineSession,
31ce2453c82fatagen2026-09-24 13:07 UTC238
    threads: Threads,
1fe480c16a3catagen2026-09-26 08:44 UTC239
    work: TaskSet<()>,
31ce2453c82fatagen2026-09-24 13:07 UTC240
}
31ce2453c82fatagen2026-09-24 13:07 UTC241
31ce2453c82fatagen2026-09-24 13:07 UTC242
impl Timelines {
80381e06a573atagen2026-10-25 03:02 UTC243
    async fn install_history(
80381e06a573atagen2026-10-25 03:02 UTC244
        &mut self,
80381e06a573atagen2026-10-25 03:02 UTC245
        install: InstallRequest,
80381e06a573atagen2026-10-25 03:02 UTC246
        room: &Room,
80381e06a573atagen2026-10-25 03:02 UTC247
        context: &SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC248
    ) {
80381e06a573atagen2026-10-25 03:02 UTC249
        let InstallRequest {
80381e06a573atagen2026-10-25 03:02 UTC250
            events,
80381e06a573atagen2026-10-25 03:02 UTC251
            prev_batch,
80381e06a573atagen2026-10-25 03:02 UTC252
            reply,
80381e06a573atagen2026-10-25 03:02 UTC253
        } = install;
80381e06a573atagen2026-10-25 03:02 UTC254
        let result = self
80381e06a573atagen2026-10-25 03:02 UTC255
            .install_history_inner(events, prev_batch, room, context)
80381e06a573atagen2026-10-25 03:02 UTC256
            .await;
80381e06a573atagen2026-10-25 03:02 UTC257
        let _ = reply.send(result);
80381e06a573atagen2026-10-25 03:02 UTC258
    }
80381e06a573atagen2026-10-25 03:02 UTC259
80381e06a573atagen2026-10-25 03:02 UTC260
    async fn install_history_inner(
80381e06a573atagen2026-10-25 03:02 UTC261
        &mut self,
80381e06a573atagen2026-10-25 03:02 UTC262
        events: Vec<matrix_sdk::deserialized_responses::TimelineEvent>,
80381e06a573atagen2026-10-25 03:02 UTC263
        prev_batch: Option<String>,
80381e06a573atagen2026-10-25 03:02 UTC264
        room: &Room,
80381e06a573atagen2026-10-25 03:02 UTC265
        context: &SessionContext,
80381e06a573atagen2026-10-25 03:02 UTC266
    ) -> Result<(), Error> {
80381e06a573atagen2026-10-25 03:02 UTC267
        let threads_capacity = self.threads.capacity;
80381e06a573atagen2026-10-25 03:02 UTC268
        self.threads.close().await;
80381e06a573atagen2026-10-25 03:02 UTC269
        self.threads = Threads::new(threads_capacity);
80381e06a573atagen2026-10-25 03:02 UTC270
        self.room.pause_for_reseed().await;
80381e06a573atagen2026-10-25 03:02 UTC271
80381e06a573atagen2026-10-25 03:02 UTC272
        let reseed = async {
80381e06a573atagen2026-10-25 03:02 UTC273
            let (cache, _drop_handles) = room.event_cache().await.map_err(|source| {
80381e06a573atagen2026-10-25 03:02 UTC274
                Error::with_source(ErrorKind::Timeline, "history event cache failed", source)
80381e06a573atagen2026-10-25 03:02 UTC275
            })?;
80381e06a573atagen2026-10-25 03:02 UTC276
            cache.reseed(events, prev_batch).await.map_err(|source| {
80381e06a573atagen2026-10-25 03:02 UTC277
                Error::with_source(ErrorKind::Timeline, "history cache reseed failed", source)
80381e06a573atagen2026-10-25 03:02 UTC278
            })
80381e06a573atagen2026-10-25 03:02 UTC279
        }
80381e06a573atagen2026-10-25 03:02 UTC280
        .await;
80381e06a573atagen2026-10-25 03:02 UTC281
        if let Err(error) = reseed {
80381e06a573atagen2026-10-25 03:02 UTC282
            return match self.room.resume_after_failed_reseed().await {
80381e06a573atagen2026-10-25 03:02 UTC283
                Ok(()) => Err(error),
80381e06a573atagen2026-10-25 03:02 UTC284
                Err(restart_error) => Err(restart_error),
80381e06a573atagen2026-10-25 03:02 UTC285
            };
80381e06a573atagen2026-10-25 03:02 UTC286
        }
80381e06a573atagen2026-10-25 03:02 UTC287
80381e06a573atagen2026-10-25 03:02 UTC288
        let timeline = match build_timeline(
80381e06a573atagen2026-10-25 03:02 UTC289
            room,
80381e06a573atagen2026-10-25 03:02 UTC290
            TimelineFocus::Live {
80381e06a573atagen2026-10-25 03:02 UTC291
                hide_threaded_events: true,
80381e06a573atagen2026-10-25 03:02 UTC292
            },
80381e06a573atagen2026-10-25 03:02 UTC293
        )
80381e06a573atagen2026-10-25 03:02 UTC294
        .await
80381e06a573atagen2026-10-25 03:02 UTC295
        {
80381e06a573atagen2026-10-25 03:02 UTC296
            Ok(timeline) => timeline,
80381e06a573atagen2026-10-25 03:02 UTC297
            Err(source) => {
80381e06a573atagen2026-10-25 03:02 UTC298
                let error =
80381e06a573atagen2026-10-25 03:02 UTC299
                    Error::with_source(ErrorKind::Timeline, "room timeline rebuild failed", source);
80381e06a573atagen2026-10-25 03:02 UTC300
                return match self.room.resume_after_failed_reseed().await {
80381e06a573atagen2026-10-25 03:02 UTC301
                    Ok(()) => Err(error),
80381e06a573atagen2026-10-25 03:02 UTC302
                    Err(restart_error) => Err(restart_error),
80381e06a573atagen2026-10-25 03:02 UTC303
                };
80381e06a573atagen2026-10-25 03:02 UTC304
            }
80381e06a573atagen2026-10-25 03:02 UTC305
        };
80381e06a573atagen2026-10-25 03:02 UTC306
        let timeline_id = self.room.id.clone();
80381e06a573atagen2026-10-25 03:02 UTC307
        let mut context = context.clone();
80381e06a573atagen2026-10-25 03:02 UTC308
        context.initial_history_limit = 0;
80381e06a573atagen2026-10-25 03:02 UTC309
        let timeline = match TimelineSession::start(timeline_id, timeline, context).await {
80381e06a573atagen2026-10-25 03:02 UTC310
            Ok(timeline) => timeline,
80381e06a573atagen2026-10-25 03:02 UTC311
            Err(source) => {
80381e06a573atagen2026-10-25 03:02 UTC312
                let error =
80381e06a573atagen2026-10-25 03:02 UTC313
                    Error::with_source(ErrorKind::Timeline, "room timeline reset failed", source);
80381e06a573atagen2026-10-25 03:02 UTC314
                return match self.room.resume_after_failed_reseed().await {
80381e06a573atagen2026-10-25 03:02 UTC315
                    Ok(()) => Err(error),
80381e06a573atagen2026-10-25 03:02 UTC316
                    Err(restart_error) => Err(restart_error),
80381e06a573atagen2026-10-25 03:02 UTC317
                };
80381e06a573atagen2026-10-25 03:02 UTC318
            }
80381e06a573atagen2026-10-25 03:02 UTC319
        };
80381e06a573atagen2026-10-25 03:02 UTC320
        let previous = std::mem::replace(&mut self.room, timeline);
80381e06a573atagen2026-10-25 03:02 UTC321
        drop(previous);
80381e06a573atagen2026-10-25 03:02 UTC322
        Ok(())
80381e06a573atagen2026-10-25 03:02 UTC323
    }
80381e06a573atagen2026-10-25 03:02 UTC324
31ce2453c82fatagen2026-09-24 13:07 UTC325
    async fn execute(&mut self, command: TimelineCommand, reply: Responder, room: &Room) {
31ce2453c82fatagen2026-09-24 13:07 UTC326
        match command {
fa5b4e09943aatagen2026-10-24 09:33 UTC327
            TimelineCommand::OpenThread {
fa5b4e09943aatagen2026-10-24 09:33 UTC328
                room_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC329
                root_event_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC330
            } => self
fa5b4e09943aatagen2026-10-24 09:33 UTC331
                .threads
fa5b4e09943aatagen2026-10-24 09:33 UTC332
                .open(TimelineId::thread(room_id, root_event_id), reply, room),
fa5b4e09943aatagen2026-10-24 09:33 UTC333
            TimelineCommand::CloseThread {
fa5b4e09943aatagen2026-10-24 09:33 UTC334
                room_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC335
                root_event_id,
fa5b4e09943aatagen2026-10-24 09:33 UTC336
            } => {
fa5b4e09943aatagen2026-10-24 09:33 UTC337
                let id = TimelineId::thread(room_id, root_event_id);
fa5b4e09943aatagen2026-10-24 09:33 UTC338
                reply.done(self.threads.close_one(&id).await);
31ce2453c82fatagen2026-09-24 13:07 UTC339
            }
fa5b4e09943aatagen2026-10-24 09:33 UTC340
            TimelineCommand::OpenPinned(room_id) => {
fa5b4e09943aatagen2026-10-24 09:33 UTC341
                self.threads.open(TimelineId::Pinned(room_id), reply, room);
fa5b4e09943aatagen2026-10-24 09:33 UTC342
            }
fa5b4e09943aatagen2026-10-24 09:33 UTC343
            TimelineCommand::ClosePinned(room_id) => {
fa5b4e09943aatagen2026-10-24 09:33 UTC344
                reply.done(self.threads.close_one(&TimelineId::Pinned(room_id)).await);
31ce2453c82fatagen2026-09-24 13:07 UTC345
            }
31ce2453c82fatagen2026-09-24 13:07 UTC346
            TimelineCommand::Resync {
31ce2453c82fatagen2026-09-24 13:07 UTC347
                timeline_id: TimelineId::Room(_),
31ce2453c82fatagen2026-09-24 13:07 UTC348
            } => reply.done(self.room.resync().await),
31ce2453c82fatagen2026-09-24 13:07 UTC349
            command => {
fa5b4e09943aatagen2026-10-24 09:33 UTC350
                let Some(id) = command
31ce2453c82fatagen2026-09-24 13:07 UTC351
                    .timeline_id()
fa5b4e09943aatagen2026-10-24 09:33 UTC352
                    .filter(|id| !matches!(id, TimelineId::Room(_)))
fa5b4e09943aatagen2026-10-24 09:33 UTC353
                    .cloned()
fa5b4e09943aatagen2026-10-24 09:33 UTC354
                else {
31ce2453c82fatagen2026-09-24 13:07 UTC355
                    return self.room.execute(command, reply, &mut self.work).await;
31ce2453c82fatagen2026-09-24 13:07 UTC356
                };
fa5b4e09943aatagen2026-10-24 09:33 UTC357
                if matches!(id, TimelineId::Pinned(_))
fa5b4e09943aatagen2026-10-24 09:33 UTC358
                    && matches!(
fa5b4e09943aatagen2026-10-24 09:33 UTC359
                        command,
fa5b4e09943aatagen2026-10-24 09:33 UTC360
                        TimelineCommand::Send { .. } | TimelineCommand::Paginate { .. }
fa5b4e09943aatagen2026-10-24 09:33 UTC361
                    )
fa5b4e09943aatagen2026-10-24 09:33 UTC362
                {
fa5b4e09943aatagen2026-10-24 09:33 UTC363
                    return reply.done(Err(Error::new(
fa5b4e09943aatagen2026-10-24 09:33 UTC364
                        ErrorKind::Invalid,
fa5b4e09943aatagen2026-10-24 09:33 UTC365
                        "the pinned timeline can't send or paginate",
fa5b4e09943aatagen2026-10-24 09:33 UTC366
                    )));
fa5b4e09943aatagen2026-10-24 09:33 UTC367
                }
5a52012c9b1datagen2026-09-24 23:45 UTC368
                if let TimelineCommand::MarkRead {
5a52012c9b1datagen2026-09-24 23:45 UTC369
                    timeline_id,
5a52012c9b1datagen2026-09-24 23:45 UTC370
                    event_id,
5a52012c9b1datagen2026-09-24 23:45 UTC371
                } = &command
fa5b4e09943aatagen2026-10-24 09:33 UTC372
                    && !self.threads.open.contains_key(&id)
5a52012c9b1datagen2026-09-24 23:45 UTC373
                {
5a52012c9b1datagen2026-09-24 23:45 UTC374
                    let client = room.client();
5a52012c9b1datagen2026-09-24 23:45 UTC375
                    let (timeline_id, event_id) = (timeline_id.clone(), event_id.clone());
5a52012c9b1datagen2026-09-24 23:45 UTC376
                    self.work.spawn(async move {
5a52012c9b1datagen2026-09-24 23:45 UTC377
                        reply.done(
5a52012c9b1datagen2026-09-24 23:45 UTC378
                            super::commands::mark_read(&client, &timeline_id, event_id).await,
5a52012c9b1datagen2026-09-24 23:45 UTC379
                        );
5a52012c9b1datagen2026-09-24 23:45 UTC380
                    });
5a52012c9b1datagen2026-09-24 23:45 UTC381
                    return;
5a52012c9b1datagen2026-09-24 23:45 UTC382
                }
fa5b4e09943aatagen2026-10-24 09:33 UTC383
                match self.threads.route(id, command, reply) {
31ce2453c82fatagen2026-09-24 13:07 UTC384
                    Routed::Open(session, TimelineCommand::Resync { .. }, reply) => {
31ce2453c82fatagen2026-09-24 13:07 UTC385
                        reply.done(session.resync().await);
31ce2453c82fatagen2026-09-24 13:07 UTC386
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC387
                    Routed::Open(session, command, reply) => {
31ce2453c82fatagen2026-09-24 13:07 UTC388
                        session.execute(command, reply, &mut self.work).await;
31ce2453c82fatagen2026-09-24 13:07 UTC389
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC390
                    Routed::Queued => {}
31ce2453c82fatagen2026-09-24 13:07 UTC391
                }
31ce2453c82fatagen2026-09-24 13:07 UTC392
            }
31ce2453c82fatagen2026-09-24 13:07 UTC393
        }
31ce2453c82fatagen2026-09-24 13:07 UTC394
    }
31ce2453c82fatagen2026-09-24 13:07 UTC395
31ce2453c82fatagen2026-09-24 13:07 UTC396
    async fn thread_built(
31ce2453c82fatagen2026-09-24 13:07 UTC397
        &mut self,
1fe480c16a3catagen2026-09-26 08:44 UTC398
        built: Result<Built, JoinError>,
31ce2453c82fatagen2026-09-24 13:07 UTC399
        room_id: &OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC400
        context: &SessionContext,
31ce2453c82fatagen2026-09-24 13:07 UTC401
    ) {
fa5b4e09943aatagen2026-10-24 09:33 UTC402
        let Some((id, queued, timeline)) = self.threads.built(built) else {
31ce2453c82fatagen2026-09-24 13:07 UTC403
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC404
        };
31ce2453c82fatagen2026-09-24 13:07 UTC405
        let timeline = match timeline {
31ce2453c82fatagen2026-09-24 13:07 UTC406
            Ok(timeline) => timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC407
            Err(error) => {
fa5b4e09943aatagen2026-10-24 09:33 UTC408
                tracing::warn!(%error, %room_id, ?id, "timeline build failed");
fa5b4e09943aatagen2026-10-24 09:33 UTC409
                let error = Error::with_source(ErrorKind::Timeline, "timeline build failed", error);
31ce2453c82fatagen2026-09-24 13:07 UTC410
                for (_, reply) in queued {
31ce2453c82fatagen2026-09-24 13:07 UTC411
                    reply.done(Err(error.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC412
                }
31ce2453c82fatagen2026-09-24 13:07 UTC413
                return;
31ce2453c82fatagen2026-09-24 13:07 UTC414
            }
31ce2453c82fatagen2026-09-24 13:07 UTC415
        };
fa5b4e09943aatagen2026-10-24 09:33 UTC416
        let Ok(session) = TimelineSession::start(id.clone(), timeline, context.clone()).await
fa5b4e09943aatagen2026-10-24 09:33 UTC417
        else {
31ce2453c82fatagen2026-09-24 13:07 UTC418
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC419
        };
fa5b4e09943aatagen2026-10-24 09:33 UTC420
        let session = self.threads.open.entry(id).insert_entry(session).into_mut();
31ce2453c82fatagen2026-09-24 13:07 UTC421
        for (command, reply) in queued {
31ce2453c82fatagen2026-09-24 13:07 UTC422
            match command {
31ce2453c82fatagen2026-09-24 13:07 UTC423
                None => reply.done(Ok(())),
31ce2453c82fatagen2026-09-24 13:07 UTC424
                Some(TimelineCommand::Resync { .. }) => reply.done(session.resync().await),
31ce2453c82fatagen2026-09-24 13:07 UTC425
                Some(command) => session.execute(command, reply, &mut self.work).await,
31ce2453c82fatagen2026-09-24 13:07 UTC426
            }
31ce2453c82fatagen2026-09-24 13:07 UTC427
        }
31ce2453c82fatagen2026-09-24 13:07 UTC428
    }
31ce2453c82fatagen2026-09-24 13:07 UTC429
}
31ce2453c82fatagen2026-09-24 13:07 UTC430
31ce2453c82fatagen2026-09-24 13:07 UTC431
type Built = (
fa5b4e09943aatagen2026-10-24 09:33 UTC432
    TimelineId,
31ce2453c82fatagen2026-09-24 13:07 UTC433
    u64,
31ce2453c82fatagen2026-09-24 13:07 UTC434
    Result<Timeline, matrix_sdk_ui::timeline::Error>,
31ce2453c82fatagen2026-09-24 13:07 UTC435
);
31ce2453c82fatagen2026-09-24 13:07 UTC436
31ce2453c82fatagen2026-09-24 13:07 UTC437
/// `None` is the open itself, everything else waits behind it
31ce2453c82fatagen2026-09-24 13:07 UTC438
type Queued = VecDeque<(Option<TimelineCommand>, Responder)>;
31ce2453c82fatagen2026-09-24 13:07 UTC439
31ce2453c82fatagen2026-09-24 13:07 UTC440
enum Routed<'a> {
31ce2453c82fatagen2026-09-24 13:07 UTC441
    Open(&'a mut TimelineSession, TimelineCommand, Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC442
    Queued,
31ce2453c82fatagen2026-09-24 13:07 UTC443
}
31ce2453c82fatagen2026-09-24 13:07 UTC444
31ce2453c82fatagen2026-09-24 13:07 UTC445
struct OpeningThread {
31ce2453c82fatagen2026-09-24 13:07 UTC446
    generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC447
    build: AbortHandle,
31ce2453c82fatagen2026-09-24 13:07 UTC448
    queued: Queued,
31ce2453c82fatagen2026-09-24 13:07 UTC449
}
31ce2453c82fatagen2026-09-24 13:07 UTC450
fa5b4e09943aatagen2026-10-24 09:33 UTC451
/// threads and the pinned timeline, which build off the actor and queue what arrives meanwhile
31ce2453c82fatagen2026-09-24 13:07 UTC452
struct Threads {
fa5b4e09943aatagen2026-10-24 09:33 UTC453
    open: HashMap<TimelineId, TimelineSession>,
fa5b4e09943aatagen2026-10-24 09:33 UTC454
    opening: HashMap<TimelineId, OpeningThread>,
1fe480c16a3catagen2026-09-26 08:44 UTC455
    builds: TaskSet<Built>,
31ce2453c82fatagen2026-09-24 13:07 UTC456
    next_generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC457
    queued: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC458
    capacity: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC459
}
31ce2453c82fatagen2026-09-24 13:07 UTC460
31ce2453c82fatagen2026-09-24 13:07 UTC461
impl Threads {
31ce2453c82fatagen2026-09-24 13:07 UTC462
    fn new(capacity: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC463
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC464
            open: HashMap::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC465
            opening: HashMap::new(),
1fe480c16a3catagen2026-09-26 08:44 UTC466
            builds: TaskSet::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC467
            next_generation: 0,
31ce2453c82fatagen2026-09-24 13:07 UTC468
            queued: 0,
31ce2453c82fatagen2026-09-24 13:07 UTC469
            capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC470
        }
31ce2453c82fatagen2026-09-24 13:07 UTC471
    }
31ce2453c82fatagen2026-09-24 13:07 UTC472
fa5b4e09943aatagen2026-10-24 09:33 UTC473
    fn enqueue(&mut self, id: &TimelineId, command: Option<TimelineCommand>, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC474
        if self.queued >= self.capacity {
31ce2453c82fatagen2026-09-24 13:07 UTC475
            return reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC476
                ErrorKind::QueueFull,
31ce2453c82fatagen2026-09-24 13:07 UTC477
                "thread command queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC478
            )));
31ce2453c82fatagen2026-09-24 13:07 UTC479
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC480
        let Some(opening) = self.opening.get_mut(id) else {
31ce2453c82fatagen2026-09-24 13:07 UTC481
            return reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC482
                ErrorKind::TimelineNotOpen,
fa5b4e09943aatagen2026-10-24 09:33 UTC483
                "timeline is not open",
31ce2453c82fatagen2026-09-24 13:07 UTC484
            )));
31ce2453c82fatagen2026-09-24 13:07 UTC485
        };
31ce2453c82fatagen2026-09-24 13:07 UTC486
        opening.queued.push_back((command, reply));
31ce2453c82fatagen2026-09-24 13:07 UTC487
        self.queued += 1;
31ce2453c82fatagen2026-09-24 13:07 UTC488
    }
31ce2453c82fatagen2026-09-24 13:07 UTC489
fa5b4e09943aatagen2026-10-24 09:33 UTC490
    fn open(&mut self, id: TimelineId, reply: Responder, room: &Room) {
fa5b4e09943aatagen2026-10-24 09:33 UTC491
        if self.open.contains_key(&id) {
31ce2453c82fatagen2026-09-24 13:07 UTC492
            return reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC493
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC494
        if !self.opening.contains_key(&id) {
31ce2453c82fatagen2026-09-24 13:07 UTC495
            let generation = self.next_generation;
31ce2453c82fatagen2026-09-24 13:07 UTC496
            self.next_generation = self.next_generation.wrapping_add(1);
31ce2453c82fatagen2026-09-24 13:07 UTC497
            let room = room.clone();
fa5b4e09943aatagen2026-10-24 09:33 UTC498
            let build_id = id.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC499
            let build = self.builds.spawn(async move {
fa5b4e09943aatagen2026-10-24 09:33 UTC500
                let focus = match &build_id {
fa5b4e09943aatagen2026-10-24 09:33 UTC501
                    TimelineId::Thread { root_event_id, .. } => TimelineFocus::Thread {
fa5b4e09943aatagen2026-10-24 09:33 UTC502
                        root_event_id: root_event_id.clone(),
fa5b4e09943aatagen2026-10-24 09:33 UTC503
                    },
fa5b4e09943aatagen2026-10-24 09:33 UTC504
                    _ => TimelineFocus::PinnedEvents,
31ce2453c82fatagen2026-09-24 13:07 UTC505
                };
fa5b4e09943aatagen2026-10-24 09:33 UTC506
                (build_id, generation, build_timeline(&room, focus).await)
31ce2453c82fatagen2026-09-24 13:07 UTC507
            });
31ce2453c82fatagen2026-09-24 13:07 UTC508
            self.opening.insert(
fa5b4e09943aatagen2026-10-24 09:33 UTC509
                id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC510
                OpeningThread {
31ce2453c82fatagen2026-09-24 13:07 UTC511
                    generation,
31ce2453c82fatagen2026-09-24 13:07 UTC512
                    build,
31ce2453c82fatagen2026-09-24 13:07 UTC513
                    queued: VecDeque::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC514
                },
31ce2453c82fatagen2026-09-24 13:07 UTC515
            );
31ce2453c82fatagen2026-09-24 13:07 UTC516
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC517
        self.enqueue(&id, None, reply);
31ce2453c82fatagen2026-09-24 13:07 UTC518
    }
31ce2453c82fatagen2026-09-24 13:07 UTC519
fa5b4e09943aatagen2026-10-24 09:33 UTC520
    fn route(&mut self, id: TimelineId, command: TimelineCommand, reply: Responder) -> Routed<'_> {
fa5b4e09943aatagen2026-10-24 09:33 UTC521
        if !self.open.contains_key(&id) {
fa5b4e09943aatagen2026-10-24 09:33 UTC522
            self.enqueue(&id, Some(command), reply);
31ce2453c82fatagen2026-09-24 13:07 UTC523
            return Routed::Queued;
31ce2453c82fatagen2026-09-24 13:07 UTC524
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC525
        let session = self.open.get_mut(&id).expect("timeline was just checked");
31ce2453c82fatagen2026-09-24 13:07 UTC526
        Routed::Open(session, command, reply)
31ce2453c82fatagen2026-09-24 13:07 UTC527
    }
31ce2453c82fatagen2026-09-24 13:07 UTC528
31ce2453c82fatagen2026-09-24 13:07 UTC529
    /// `None` when the build belongs to a thread that was closed while it ran
31ce2453c82fatagen2026-09-24 13:07 UTC530
    fn built(
31ce2453c82fatagen2026-09-24 13:07 UTC531
        &mut self,
1fe480c16a3catagen2026-09-26 08:44 UTC532
        built: Result<Built, JoinError>,
31ce2453c82fatagen2026-09-24 13:07 UTC533
    ) -> Option<(
fa5b4e09943aatagen2026-10-24 09:33 UTC534
        TimelineId,
31ce2453c82fatagen2026-09-24 13:07 UTC535
        Queued,
31ce2453c82fatagen2026-09-24 13:07 UTC536
        Result<Timeline, matrix_sdk_ui::timeline::Error>,
31ce2453c82fatagen2026-09-24 13:07 UTC537
    )> {
fa5b4e09943aatagen2026-10-24 09:33 UTC538
        let (id, generation, timeline) = match built {
31ce2453c82fatagen2026-09-24 13:07 UTC539
            Ok(built) => built,
31ce2453c82fatagen2026-09-24 13:07 UTC540
            Err(error) if error.is_cancelled() => return None,
1fe480c16a3catagen2026-09-26 08:44 UTC541
            Err(error) => resume_unwind(error),
31ce2453c82fatagen2026-09-24 13:07 UTC542
        };
31ce2453c82fatagen2026-09-24 13:07 UTC543
        if self
31ce2453c82fatagen2026-09-24 13:07 UTC544
            .opening
fa5b4e09943aatagen2026-10-24 09:33 UTC545
            .get(&id)
31ce2453c82fatagen2026-09-24 13:07 UTC546
            .is_none_or(|opening| opening.generation != generation)
31ce2453c82fatagen2026-09-24 13:07 UTC547
        {
31ce2453c82fatagen2026-09-24 13:07 UTC548
            return None;
31ce2453c82fatagen2026-09-24 13:07 UTC549
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC550
        let opening = self.opening.remove(&id)?;
31ce2453c82fatagen2026-09-24 13:07 UTC551
        self.queued -= opening.queued.len();
fa5b4e09943aatagen2026-10-24 09:33 UTC552
        Some((id, opening.queued, timeline))
31ce2453c82fatagen2026-09-24 13:07 UTC553
    }
31ce2453c82fatagen2026-09-24 13:07 UTC554
fa5b4e09943aatagen2026-10-24 09:33 UTC555
    async fn close_one(&mut self, id: &TimelineId) -> Result<(), Error> {
fa5b4e09943aatagen2026-10-24 09:33 UTC556
        if let Some(session) = self.open.remove(id) {
31ce2453c82fatagen2026-09-24 13:07 UTC557
            session.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC558
            return Ok(());
31ce2453c82fatagen2026-09-24 13:07 UTC559
        }
fa5b4e09943aatagen2026-10-24 09:33 UTC560
        let Some(opening) = self.opening.remove(id) else {
31ce2453c82fatagen2026-09-24 13:07 UTC561
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC562
                ErrorKind::TimelineNotOpen,
fa5b4e09943aatagen2026-10-24 09:33 UTC563
                "timeline is not open",
31ce2453c82fatagen2026-09-24 13:07 UTC564
            ));
31ce2453c82fatagen2026-09-24 13:07 UTC565
        };
31ce2453c82fatagen2026-09-24 13:07 UTC566
        opening.build.abort();
31ce2453c82fatagen2026-09-24 13:07 UTC567
        self.queued -= opening.queued.len();
31ce2453c82fatagen2026-09-24 13:07 UTC568
        Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC569
    }
31ce2453c82fatagen2026-09-24 13:07 UTC570
31ce2453c82fatagen2026-09-24 13:07 UTC571
    async fn close(&mut self) {
1fe480c16a3catagen2026-09-26 08:44 UTC572
        self.builds.shutdown().await;
31ce2453c82fatagen2026-09-24 13:07 UTC573
        self.opening.clear();
31ce2453c82fatagen2026-09-24 13:07 UTC574
        for (_, session) in self.open.drain() {
31ce2453c82fatagen2026-09-24 13:07 UTC575
            session.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC576
        }
31ce2453c82fatagen2026-09-24 13:07 UTC577
    }
31ce2453c82fatagen2026-09-24 13:07 UTC578
}