atagen

macltok

public
Star0
Watch0
31ce2453c82f470aa8b5d3861ab0eec4273afc5d
3
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};
31ce2453c82fatagen2026-09-24 13:07 UTC5
use ruma::{OwnedEventId, OwnedRoomId};
31ce2453c82fatagen2026-09-24 13:07 UTC6
use tokio::{
31ce2453c82fatagen2026-09-24 13:07 UTC7
    sync::mpsc,
31ce2453c82fatagen2026-09-24 13:07 UTC8
    task::{AbortHandle, JoinSet},
31ce2453c82fatagen2026-09-24 13:07 UTC9
};
31ce2453c82fatagen2026-09-24 13:07 UTC10
31ce2453c82fatagen2026-09-24 13:07 UTC11
use super::timeline::{SessionContext, TimelineSession};
31ce2453c82fatagen2026-09-24 13:07 UTC12
use crate::{
31ce2453c82fatagen2026-09-24 13:07 UTC13
    Error, ErrorKind, Event, TimelineCommand, TimelineId, TypingState, 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),
31ce2453c82fatagen2026-09-24 13:07 UTC20
    Close(Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC21
}
31ce2453c82fatagen2026-09-24 13:07 UTC22
31ce2453c82fatagen2026-09-24 13:07 UTC23
impl ActorRequest {
31ce2453c82fatagen2026-09-24 13:07 UTC24
    pub(super) fn into_reply(self) -> Responder {
31ce2453c82fatagen2026-09-24 13:07 UTC25
        match self {
31ce2453c82fatagen2026-09-24 13:07 UTC26
            Self::Open(reply) | Self::Execute(_, reply) | Self::Close(reply) => reply,
31ce2453c82fatagen2026-09-24 13:07 UTC27
        }
31ce2453c82fatagen2026-09-24 13:07 UTC28
    }
31ce2453c82fatagen2026-09-24 13:07 UTC29
}
31ce2453c82fatagen2026-09-24 13:07 UTC30
31ce2453c82fatagen2026-09-24 13:07 UTC31
pub(super) struct Exited {
31ce2453c82fatagen2026-09-24 13:07 UTC32
    pub(super) room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC33
    pub(super) generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC34
}
31ce2453c82fatagen2026-09-24 13:07 UTC35
31ce2453c82fatagen2026-09-24 13:07 UTC36
pub(super) struct RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC37
    pub(super) room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC38
    pub(super) generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC39
    pub(super) room: Room,
31ce2453c82fatagen2026-09-24 13:07 UTC40
    pub(super) requests: mpsc::Receiver<ActorRequest>,
31ce2453c82fatagen2026-09-24 13:07 UTC41
    pub(super) exits: mpsc::UnboundedSender<Exited>,
31ce2453c82fatagen2026-09-24 13:07 UTC42
    pub(super) context: SessionContext,
31ce2453c82fatagen2026-09-24 13:07 UTC43
    pub(super) panics: mpsc::UnboundedReceiver<&'static str>,
31ce2453c82fatagen2026-09-24 13:07 UTC44
    pub(super) opening: Vec<Responder>,
31ce2453c82fatagen2026-09-24 13:07 UTC45
}
31ce2453c82fatagen2026-09-24 13:07 UTC46
31ce2453c82fatagen2026-09-24 13:07 UTC47
impl RoomActor {
31ce2453c82fatagen2026-09-24 13:07 UTC48
    pub(super) async fn run(mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC49
        let exits = self.exits.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC50
        let exited = Exited {
31ce2453c82fatagen2026-09-24 13:07 UTC51
            room_id: self.room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC52
            generation: self.generation,
31ce2453c82fatagen2026-09-24 13:07 UTC53
        };
31ce2453c82fatagen2026-09-24 13:07 UTC54
        self.serve().await;
31ce2453c82fatagen2026-09-24 13:07 UTC55
        let _ = exits.send(exited);
31ce2453c82fatagen2026-09-24 13:07 UTC56
    }
31ce2453c82fatagen2026-09-24 13:07 UTC57
31ce2453c82fatagen2026-09-24 13:07 UTC58
    async fn serve(&mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC59
        let timeline = match build_timeline(
31ce2453c82fatagen2026-09-24 13:07 UTC60
            &self.room,
31ce2453c82fatagen2026-09-24 13:07 UTC61
            TimelineFocus::Live {
31ce2453c82fatagen2026-09-24 13:07 UTC62
                hide_threaded_events: true,
31ce2453c82fatagen2026-09-24 13:07 UTC63
            },
31ce2453c82fatagen2026-09-24 13:07 UTC64
        )
31ce2453c82fatagen2026-09-24 13:07 UTC65
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC66
        {
31ce2453c82fatagen2026-09-24 13:07 UTC67
            Ok(timeline) => timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC68
            Err(error) => return self.refuse_everything(error).await,
31ce2453c82fatagen2026-09-24 13:07 UTC69
        };
31ce2453c82fatagen2026-09-24 13:07 UTC70
        let Ok(room_timeline) = TimelineSession::start(
31ce2453c82fatagen2026-09-24 13:07 UTC71
            TimelineId::Room(self.room_id.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC72
            timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC73
            self.context.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC74
        )
31ce2453c82fatagen2026-09-24 13:07 UTC75
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC76
        else {
31ce2453c82fatagen2026-09-24 13:07 UTC77
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC78
        };
31ce2453c82fatagen2026-09-24 13:07 UTC79
        for reply in self.opening.drain(..) {
31ce2453c82fatagen2026-09-24 13:07 UTC80
            reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC81
        }
31ce2453c82fatagen2026-09-24 13:07 UTC82
        let (typing_guard, typing) = self.room.subscribe_to_typing_notifications();
31ce2453c82fatagen2026-09-24 13:07 UTC83
        let typing_watcher = self.context.supervisor.spawn(
31ce2453c82fatagen2026-09-24 13:07 UTC84
            "typing watcher",
31ce2453c82fatagen2026-09-24 13:07 UTC85
            watch_typing(self.room_id.clone(), typing, self.context.events.clone()),
31ce2453c82fatagen2026-09-24 13:07 UTC86
        );
31ce2453c82fatagen2026-09-24 13:07 UTC87
        let mut timelines = Timelines {
31ce2453c82fatagen2026-09-24 13:07 UTC88
            room: room_timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC89
            threads: Threads::new(self.requests.max_capacity()),
31ce2453c82fatagen2026-09-24 13:07 UTC90
            work: JoinSet::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC91
        };
31ce2453c82fatagen2026-09-24 13:07 UTC92
        let close = self.serve_open(&mut timelines).await;
31ce2453c82fatagen2026-09-24 13:07 UTC93
31ce2453c82fatagen2026-09-24 13:07 UTC94
        // let redactions and receipts that already left finish rather than lie about them
31ce2453c82fatagen2026-09-24 13:07 UTC95
        while timelines.work.join_next().await.is_some() {}
31ce2453c82fatagen2026-09-24 13:07 UTC96
        drop(typing_guard);
31ce2453c82fatagen2026-09-24 13:07 UTC97
        typing_watcher.abort();
31ce2453c82fatagen2026-09-24 13:07 UTC98
        timelines.threads.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC99
        timelines.room.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC100
        if let Some(reply) = close {
31ce2453c82fatagen2026-09-24 13:07 UTC101
            reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC102
        }
31ce2453c82fatagen2026-09-24 13:07 UTC103
    }
31ce2453c82fatagen2026-09-24 13:07 UTC104
31ce2453c82fatagen2026-09-24 13:07 UTC105
    /// returns the close request's responder, if a close is why we stopped
31ce2453c82fatagen2026-09-24 13:07 UTC106
    async fn serve_open(&mut self, timelines: &mut Timelines) -> Option<Responder> {
31ce2453c82fatagen2026-09-24 13:07 UTC107
        let mut room_info = self.room.subscribe_info();
31ce2453c82fatagen2026-09-24 13:07 UTC108
        loop {
31ce2453c82fatagen2026-09-24 13:07 UTC109
            tokio::select! {
31ce2453c82fatagen2026-09-24 13:07 UTC110
                Some(info) = room_info.next() => {
31ce2453c82fatagen2026-09-24 13:07 UTC111
                    if matches!(info.state(), RoomState::Left | RoomState::Banned) {
31ce2453c82fatagen2026-09-24 13:07 UTC112
                        tracing::debug!(room_id = %self.room_id, "room actor stopped because we left the room");
31ce2453c82fatagen2026-09-24 13:07 UTC113
                        return None;
31ce2453c82fatagen2026-09-24 13:07 UTC114
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC115
                }
31ce2453c82fatagen2026-09-24 13:07 UTC116
                request = self.requests.recv() => match request {
31ce2453c82fatagen2026-09-24 13:07 UTC117
                    None => return None,
31ce2453c82fatagen2026-09-24 13:07 UTC118
                    Some(ActorRequest::Close(reply)) => return Some(reply),
31ce2453c82fatagen2026-09-24 13:07 UTC119
                    Some(ActorRequest::Open(reply)) => reply.done(timelines.room.resync().await),
31ce2453c82fatagen2026-09-24 13:07 UTC120
                    Some(ActorRequest::Execute(command, reply)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC121
                        timelines.execute(command, reply, &self.room).await;
31ce2453c82fatagen2026-09-24 13:07 UTC122
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC123
                },
31ce2453c82fatagen2026-09-24 13:07 UTC124
                Some(built) = timelines.threads.builds.join_next(), if !timelines.threads.builds.is_empty() => {
31ce2453c82fatagen2026-09-24 13:07 UTC125
                    timelines.thread_built(built, &self.room_id, &self.context).await;
31ce2453c82fatagen2026-09-24 13:07 UTC126
                }
31ce2453c82fatagen2026-09-24 13:07 UTC127
                Some(label) = self.panics.recv() => panic!("{label} panicked in room {}", self.room_id),
31ce2453c82fatagen2026-09-24 13:07 UTC128
                Some(done) = timelines.work.join_next(), if !timelines.work.is_empty() => {
31ce2453c82fatagen2026-09-24 13:07 UTC129
                    if let Err(error) = done && error.is_panic() {
31ce2453c82fatagen2026-09-24 13:07 UTC130
                        tracing::warn!(room_id = %self.room_id, %error, "timeline operation panicked");
31ce2453c82fatagen2026-09-24 13:07 UTC131
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC132
                }
31ce2453c82fatagen2026-09-24 13:07 UTC133
            }
31ce2453c82fatagen2026-09-24 13:07 UTC134
        }
31ce2453c82fatagen2026-09-24 13:07 UTC135
    }
31ce2453c82fatagen2026-09-24 13:07 UTC136
31ce2453c82fatagen2026-09-24 13:07 UTC137
    async fn refuse_everything(&mut self, error: matrix_sdk_ui::timeline::Error) {
31ce2453c82fatagen2026-09-24 13:07 UTC138
        tracing::warn!(%error, room_id = %self.room_id, "room actor could not build its timeline");
31ce2453c82fatagen2026-09-24 13:07 UTC139
        let error = Error::with_source(ErrorKind::Timeline, "room timeline build failed", error);
31ce2453c82fatagen2026-09-24 13:07 UTC140
        for reply in self.opening.drain(..) {
31ce2453c82fatagen2026-09-24 13:07 UTC141
            reply.done(Err(error.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC142
        }
31ce2453c82fatagen2026-09-24 13:07 UTC143
        self.requests.close();
31ce2453c82fatagen2026-09-24 13:07 UTC144
        while let Some(request) = self.requests.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC145
            request.into_reply().done(Err(error.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC146
        }
31ce2453c82fatagen2026-09-24 13:07 UTC147
    }
31ce2453c82fatagen2026-09-24 13:07 UTC148
}
31ce2453c82fatagen2026-09-24 13:07 UTC149
31ce2453c82fatagen2026-09-24 13:07 UTC150
async fn build_timeline(
31ce2453c82fatagen2026-09-24 13:07 UTC151
    room: &Room,
31ce2453c82fatagen2026-09-24 13:07 UTC152
    focus: TimelineFocus,
31ce2453c82fatagen2026-09-24 13:07 UTC153
) -> Result<Timeline, matrix_sdk_ui::timeline::Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC154
    room.timeline_builder()
31ce2453c82fatagen2026-09-24 13:07 UTC155
        .with_focus(focus)
31ce2453c82fatagen2026-09-24 13:07 UTC156
        .track_read_marker_and_receipts(TimelineReadReceiptTracking::AllEvents)
31ce2453c82fatagen2026-09-24 13:07 UTC157
        .build()
31ce2453c82fatagen2026-09-24 13:07 UTC158
        .await
31ce2453c82fatagen2026-09-24 13:07 UTC159
}
31ce2453c82fatagen2026-09-24 13:07 UTC160
31ce2453c82fatagen2026-09-24 13:07 UTC161
async fn watch_typing(
31ce2453c82fatagen2026-09-24 13:07 UTC162
    room_id: OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC163
    mut typing: tokio::sync::broadcast::Receiver<Vec<ruma::OwnedUserId>>,
31ce2453c82fatagen2026-09-24 13:07 UTC164
    events: crate::transport::EventSender,
31ce2453c82fatagen2026-09-24 13:07 UTC165
) {
31ce2453c82fatagen2026-09-24 13:07 UTC166
    loop {
31ce2453c82fatagen2026-09-24 13:07 UTC167
        match typing.recv().await {
31ce2453c82fatagen2026-09-24 13:07 UTC168
            Ok(users) => {
31ce2453c82fatagen2026-09-24 13:07 UTC169
                let state = TypingState {
31ce2453c82fatagen2026-09-24 13:07 UTC170
                    room_id: room_id.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC171
                    users,
31ce2453c82fatagen2026-09-24 13:07 UTC172
                };
31ce2453c82fatagen2026-09-24 13:07 UTC173
                if events.send(Event::TypingChanged(state)).await.is_err() {
31ce2453c82fatagen2026-09-24 13:07 UTC174
                    return;
31ce2453c82fatagen2026-09-24 13:07 UTC175
                }
31ce2453c82fatagen2026-09-24 13:07 UTC176
            }
31ce2453c82fatagen2026-09-24 13:07 UTC177
            Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
31ce2453c82fatagen2026-09-24 13:07 UTC178
                tracing::warn!(%room_id, skipped, "typing watcher lagged");
31ce2453c82fatagen2026-09-24 13:07 UTC179
            }
31ce2453c82fatagen2026-09-24 13:07 UTC180
            Err(tokio::sync::broadcast::error::RecvError::Closed) => return,
31ce2453c82fatagen2026-09-24 13:07 UTC181
        }
31ce2453c82fatagen2026-09-24 13:07 UTC182
    }
31ce2453c82fatagen2026-09-24 13:07 UTC183
}
31ce2453c82fatagen2026-09-24 13:07 UTC184
31ce2453c82fatagen2026-09-24 13:07 UTC185
struct Timelines {
31ce2453c82fatagen2026-09-24 13:07 UTC186
    room: TimelineSession,
31ce2453c82fatagen2026-09-24 13:07 UTC187
    threads: Threads,
31ce2453c82fatagen2026-09-24 13:07 UTC188
    work: JoinSet<()>,
31ce2453c82fatagen2026-09-24 13:07 UTC189
}
31ce2453c82fatagen2026-09-24 13:07 UTC190
31ce2453c82fatagen2026-09-24 13:07 UTC191
impl Timelines {
31ce2453c82fatagen2026-09-24 13:07 UTC192
    async fn execute(&mut self, command: TimelineCommand, reply: Responder, room: &Room) {
31ce2453c82fatagen2026-09-24 13:07 UTC193
        match command {
31ce2453c82fatagen2026-09-24 13:07 UTC194
            TimelineCommand::OpenThread { root_event_id, .. } => {
31ce2453c82fatagen2026-09-24 13:07 UTC195
                self.threads.open(root_event_id, reply, room);
31ce2453c82fatagen2026-09-24 13:07 UTC196
            }
31ce2453c82fatagen2026-09-24 13:07 UTC197
            TimelineCommand::CloseThread { root_event_id, .. } => {
31ce2453c82fatagen2026-09-24 13:07 UTC198
                reply.done(self.threads.close_one(&root_event_id).await);
31ce2453c82fatagen2026-09-24 13:07 UTC199
            }
31ce2453c82fatagen2026-09-24 13:07 UTC200
            TimelineCommand::Resync {
31ce2453c82fatagen2026-09-24 13:07 UTC201
                timeline_id: TimelineId::Room(_),
31ce2453c82fatagen2026-09-24 13:07 UTC202
            } => reply.done(self.room.resync().await),
31ce2453c82fatagen2026-09-24 13:07 UTC203
            command => {
31ce2453c82fatagen2026-09-24 13:07 UTC204
                let root = command
31ce2453c82fatagen2026-09-24 13:07 UTC205
                    .timeline_id()
31ce2453c82fatagen2026-09-24 13:07 UTC206
                    .and_then(TimelineId::thread_root)
31ce2453c82fatagen2026-09-24 13:07 UTC207
                    .cloned();
31ce2453c82fatagen2026-09-24 13:07 UTC208
                let Some(root) = root else {
31ce2453c82fatagen2026-09-24 13:07 UTC209
                    return self.room.execute(command, reply, &mut self.work).await;
31ce2453c82fatagen2026-09-24 13:07 UTC210
                };
31ce2453c82fatagen2026-09-24 13:07 UTC211
                match self.threads.route(root, command, reply) {
31ce2453c82fatagen2026-09-24 13:07 UTC212
                    Routed::Open(session, TimelineCommand::Resync { .. }, reply) => {
31ce2453c82fatagen2026-09-24 13:07 UTC213
                        reply.done(session.resync().await);
31ce2453c82fatagen2026-09-24 13:07 UTC214
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC215
                    Routed::Open(session, command, reply) => {
31ce2453c82fatagen2026-09-24 13:07 UTC216
                        session.execute(command, reply, &mut self.work).await;
31ce2453c82fatagen2026-09-24 13:07 UTC217
                    }
31ce2453c82fatagen2026-09-24 13:07 UTC218
                    Routed::Queued => {}
31ce2453c82fatagen2026-09-24 13:07 UTC219
                }
31ce2453c82fatagen2026-09-24 13:07 UTC220
            }
31ce2453c82fatagen2026-09-24 13:07 UTC221
        }
31ce2453c82fatagen2026-09-24 13:07 UTC222
    }
31ce2453c82fatagen2026-09-24 13:07 UTC223
31ce2453c82fatagen2026-09-24 13:07 UTC224
    async fn thread_built(
31ce2453c82fatagen2026-09-24 13:07 UTC225
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC226
        built: Result<Built, tokio::task::JoinError>,
31ce2453c82fatagen2026-09-24 13:07 UTC227
        room_id: &OwnedRoomId,
31ce2453c82fatagen2026-09-24 13:07 UTC228
        context: &SessionContext,
31ce2453c82fatagen2026-09-24 13:07 UTC229
    ) {
31ce2453c82fatagen2026-09-24 13:07 UTC230
        let Some((root, queued, timeline)) = self.threads.built(built) else {
31ce2453c82fatagen2026-09-24 13:07 UTC231
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC232
        };
31ce2453c82fatagen2026-09-24 13:07 UTC233
        let timeline = match timeline {
31ce2453c82fatagen2026-09-24 13:07 UTC234
            Ok(timeline) => timeline,
31ce2453c82fatagen2026-09-24 13:07 UTC235
            Err(error) => {
31ce2453c82fatagen2026-09-24 13:07 UTC236
                tracing::warn!(%error, %room_id, %root, "thread timeline build failed");
31ce2453c82fatagen2026-09-24 13:07 UTC237
                let error =
31ce2453c82fatagen2026-09-24 13:07 UTC238
                    Error::with_source(ErrorKind::Timeline, "thread timeline build failed", error);
31ce2453c82fatagen2026-09-24 13:07 UTC239
                for (_, reply) in queued {
31ce2453c82fatagen2026-09-24 13:07 UTC240
                    reply.done(Err(error.clone()));
31ce2453c82fatagen2026-09-24 13:07 UTC241
                }
31ce2453c82fatagen2026-09-24 13:07 UTC242
                return;
31ce2453c82fatagen2026-09-24 13:07 UTC243
            }
31ce2453c82fatagen2026-09-24 13:07 UTC244
        };
31ce2453c82fatagen2026-09-24 13:07 UTC245
        let id = TimelineId::thread(room_id.clone(), root.clone());
31ce2453c82fatagen2026-09-24 13:07 UTC246
        let Ok(session) = TimelineSession::start(id, timeline, context.clone()).await else {
31ce2453c82fatagen2026-09-24 13:07 UTC247
            return;
31ce2453c82fatagen2026-09-24 13:07 UTC248
        };
31ce2453c82fatagen2026-09-24 13:07 UTC249
        let session = self
31ce2453c82fatagen2026-09-24 13:07 UTC250
            .threads
31ce2453c82fatagen2026-09-24 13:07 UTC251
            .open
31ce2453c82fatagen2026-09-24 13:07 UTC252
            .entry(root)
31ce2453c82fatagen2026-09-24 13:07 UTC253
            .insert_entry(session)
31ce2453c82fatagen2026-09-24 13:07 UTC254
            .into_mut();
31ce2453c82fatagen2026-09-24 13:07 UTC255
        for (command, reply) in queued {
31ce2453c82fatagen2026-09-24 13:07 UTC256
            match command {
31ce2453c82fatagen2026-09-24 13:07 UTC257
                None => reply.done(Ok(())),
31ce2453c82fatagen2026-09-24 13:07 UTC258
                Some(TimelineCommand::Resync { .. }) => reply.done(session.resync().await),
31ce2453c82fatagen2026-09-24 13:07 UTC259
                Some(command) => session.execute(command, reply, &mut self.work).await,
31ce2453c82fatagen2026-09-24 13:07 UTC260
            }
31ce2453c82fatagen2026-09-24 13:07 UTC261
        }
31ce2453c82fatagen2026-09-24 13:07 UTC262
    }
31ce2453c82fatagen2026-09-24 13:07 UTC263
}
31ce2453c82fatagen2026-09-24 13:07 UTC264
31ce2453c82fatagen2026-09-24 13:07 UTC265
type Built = (
31ce2453c82fatagen2026-09-24 13:07 UTC266
    OwnedEventId,
31ce2453c82fatagen2026-09-24 13:07 UTC267
    u64,
31ce2453c82fatagen2026-09-24 13:07 UTC268
    Result<Timeline, matrix_sdk_ui::timeline::Error>,
31ce2453c82fatagen2026-09-24 13:07 UTC269
);
31ce2453c82fatagen2026-09-24 13:07 UTC270
31ce2453c82fatagen2026-09-24 13:07 UTC271
/// `None` is the open itself, everything else waits behind it
31ce2453c82fatagen2026-09-24 13:07 UTC272
type Queued = VecDeque<(Option<TimelineCommand>, Responder)>;
31ce2453c82fatagen2026-09-24 13:07 UTC273
31ce2453c82fatagen2026-09-24 13:07 UTC274
enum Routed<'a> {
31ce2453c82fatagen2026-09-24 13:07 UTC275
    Open(&'a mut TimelineSession, TimelineCommand, Responder),
31ce2453c82fatagen2026-09-24 13:07 UTC276
    Queued,
31ce2453c82fatagen2026-09-24 13:07 UTC277
}
31ce2453c82fatagen2026-09-24 13:07 UTC278
31ce2453c82fatagen2026-09-24 13:07 UTC279
struct OpeningThread {
31ce2453c82fatagen2026-09-24 13:07 UTC280
    generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC281
    build: AbortHandle,
31ce2453c82fatagen2026-09-24 13:07 UTC282
    queued: Queued,
31ce2453c82fatagen2026-09-24 13:07 UTC283
}
31ce2453c82fatagen2026-09-24 13:07 UTC284
31ce2453c82fatagen2026-09-24 13:07 UTC285
struct Threads {
31ce2453c82fatagen2026-09-24 13:07 UTC286
    open: HashMap<OwnedEventId, TimelineSession>,
31ce2453c82fatagen2026-09-24 13:07 UTC287
    opening: HashMap<OwnedEventId, OpeningThread>,
31ce2453c82fatagen2026-09-24 13:07 UTC288
    builds: JoinSet<Built>,
31ce2453c82fatagen2026-09-24 13:07 UTC289
    next_generation: u64,
31ce2453c82fatagen2026-09-24 13:07 UTC290
    queued: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC291
    capacity: usize,
31ce2453c82fatagen2026-09-24 13:07 UTC292
}
31ce2453c82fatagen2026-09-24 13:07 UTC293
31ce2453c82fatagen2026-09-24 13:07 UTC294
impl Threads {
31ce2453c82fatagen2026-09-24 13:07 UTC295
    fn new(capacity: usize) -> Self {
31ce2453c82fatagen2026-09-24 13:07 UTC296
        Self {
31ce2453c82fatagen2026-09-24 13:07 UTC297
            open: HashMap::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC298
            opening: HashMap::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC299
            builds: JoinSet::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC300
            next_generation: 0,
31ce2453c82fatagen2026-09-24 13:07 UTC301
            queued: 0,
31ce2453c82fatagen2026-09-24 13:07 UTC302
            capacity,
31ce2453c82fatagen2026-09-24 13:07 UTC303
        }
31ce2453c82fatagen2026-09-24 13:07 UTC304
    }
31ce2453c82fatagen2026-09-24 13:07 UTC305
31ce2453c82fatagen2026-09-24 13:07 UTC306
    fn enqueue(&mut self, root: &OwnedEventId, command: Option<TimelineCommand>, reply: Responder) {
31ce2453c82fatagen2026-09-24 13:07 UTC307
        if self.queued >= self.capacity {
31ce2453c82fatagen2026-09-24 13:07 UTC308
            return reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC309
                ErrorKind::QueueFull,
31ce2453c82fatagen2026-09-24 13:07 UTC310
                "thread command queue is full",
31ce2453c82fatagen2026-09-24 13:07 UTC311
            )));
31ce2453c82fatagen2026-09-24 13:07 UTC312
        }
31ce2453c82fatagen2026-09-24 13:07 UTC313
        let Some(opening) = self.opening.get_mut(root) else {
31ce2453c82fatagen2026-09-24 13:07 UTC314
            return reply.done(Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC315
                ErrorKind::TimelineNotOpen,
31ce2453c82fatagen2026-09-24 13:07 UTC316
                "thread timeline is not open",
31ce2453c82fatagen2026-09-24 13:07 UTC317
            )));
31ce2453c82fatagen2026-09-24 13:07 UTC318
        };
31ce2453c82fatagen2026-09-24 13:07 UTC319
        opening.queued.push_back((command, reply));
31ce2453c82fatagen2026-09-24 13:07 UTC320
        self.queued += 1;
31ce2453c82fatagen2026-09-24 13:07 UTC321
    }
31ce2453c82fatagen2026-09-24 13:07 UTC322
31ce2453c82fatagen2026-09-24 13:07 UTC323
    fn open(&mut self, root: OwnedEventId, reply: Responder, room: &Room) {
31ce2453c82fatagen2026-09-24 13:07 UTC324
        if self.open.contains_key(&root) {
31ce2453c82fatagen2026-09-24 13:07 UTC325
            return reply.done(Ok(()));
31ce2453c82fatagen2026-09-24 13:07 UTC326
        }
31ce2453c82fatagen2026-09-24 13:07 UTC327
        if !self.opening.contains_key(&root) {
31ce2453c82fatagen2026-09-24 13:07 UTC328
            let generation = self.next_generation;
31ce2453c82fatagen2026-09-24 13:07 UTC329
            self.next_generation = self.next_generation.wrapping_add(1);
31ce2453c82fatagen2026-09-24 13:07 UTC330
            let room = room.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC331
            let build_root = root.clone();
31ce2453c82fatagen2026-09-24 13:07 UTC332
            let build = self.builds.spawn(async move {
31ce2453c82fatagen2026-09-24 13:07 UTC333
                let focus = TimelineFocus::Thread {
31ce2453c82fatagen2026-09-24 13:07 UTC334
                    root_event_id: build_root.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC335
                };
31ce2453c82fatagen2026-09-24 13:07 UTC336
                (build_root, generation, build_timeline(&room, focus).await)
31ce2453c82fatagen2026-09-24 13:07 UTC337
            });
31ce2453c82fatagen2026-09-24 13:07 UTC338
            self.opening.insert(
31ce2453c82fatagen2026-09-24 13:07 UTC339
                root.clone(),
31ce2453c82fatagen2026-09-24 13:07 UTC340
                OpeningThread {
31ce2453c82fatagen2026-09-24 13:07 UTC341
                    generation,
31ce2453c82fatagen2026-09-24 13:07 UTC342
                    build,
31ce2453c82fatagen2026-09-24 13:07 UTC343
                    queued: VecDeque::new(),
31ce2453c82fatagen2026-09-24 13:07 UTC344
                },
31ce2453c82fatagen2026-09-24 13:07 UTC345
            );
31ce2453c82fatagen2026-09-24 13:07 UTC346
        }
31ce2453c82fatagen2026-09-24 13:07 UTC347
        self.enqueue(&root, None, reply);
31ce2453c82fatagen2026-09-24 13:07 UTC348
    }
31ce2453c82fatagen2026-09-24 13:07 UTC349
31ce2453c82fatagen2026-09-24 13:07 UTC350
    fn route(
31ce2453c82fatagen2026-09-24 13:07 UTC351
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC352
        root: OwnedEventId,
31ce2453c82fatagen2026-09-24 13:07 UTC353
        command: TimelineCommand,
31ce2453c82fatagen2026-09-24 13:07 UTC354
        reply: Responder,
31ce2453c82fatagen2026-09-24 13:07 UTC355
    ) -> Routed<'_> {
31ce2453c82fatagen2026-09-24 13:07 UTC356
        if !self.open.contains_key(&root) {
31ce2453c82fatagen2026-09-24 13:07 UTC357
            self.enqueue(&root, Some(command), reply);
31ce2453c82fatagen2026-09-24 13:07 UTC358
            return Routed::Queued;
31ce2453c82fatagen2026-09-24 13:07 UTC359
        }
31ce2453c82fatagen2026-09-24 13:07 UTC360
        let session = self.open.get_mut(&root).expect("thread was just checked");
31ce2453c82fatagen2026-09-24 13:07 UTC361
        Routed::Open(session, command, reply)
31ce2453c82fatagen2026-09-24 13:07 UTC362
    }
31ce2453c82fatagen2026-09-24 13:07 UTC363
31ce2453c82fatagen2026-09-24 13:07 UTC364
    /// `None` when the build belongs to a thread that was closed while it ran
31ce2453c82fatagen2026-09-24 13:07 UTC365
    fn built(
31ce2453c82fatagen2026-09-24 13:07 UTC366
        &mut self,
31ce2453c82fatagen2026-09-24 13:07 UTC367
        built: Result<Built, tokio::task::JoinError>,
31ce2453c82fatagen2026-09-24 13:07 UTC368
    ) -> Option<(
31ce2453c82fatagen2026-09-24 13:07 UTC369
        OwnedEventId,
31ce2453c82fatagen2026-09-24 13:07 UTC370
        Queued,
31ce2453c82fatagen2026-09-24 13:07 UTC371
        Result<Timeline, matrix_sdk_ui::timeline::Error>,
31ce2453c82fatagen2026-09-24 13:07 UTC372
    )> {
31ce2453c82fatagen2026-09-24 13:07 UTC373
        let (root, generation, timeline) = match built {
31ce2453c82fatagen2026-09-24 13:07 UTC374
            Ok(built) => built,
31ce2453c82fatagen2026-09-24 13:07 UTC375
            Err(error) if error.is_cancelled() => return None,
31ce2453c82fatagen2026-09-24 13:07 UTC376
            Err(error) => std::panic::resume_unwind(error.into_panic()),
31ce2453c82fatagen2026-09-24 13:07 UTC377
        };
31ce2453c82fatagen2026-09-24 13:07 UTC378
        if self
31ce2453c82fatagen2026-09-24 13:07 UTC379
            .opening
31ce2453c82fatagen2026-09-24 13:07 UTC380
            .get(&root)
31ce2453c82fatagen2026-09-24 13:07 UTC381
            .is_none_or(|opening| opening.generation != generation)
31ce2453c82fatagen2026-09-24 13:07 UTC382
        {
31ce2453c82fatagen2026-09-24 13:07 UTC383
            return None;
31ce2453c82fatagen2026-09-24 13:07 UTC384
        }
31ce2453c82fatagen2026-09-24 13:07 UTC385
        let opening = self.opening.remove(&root)?;
31ce2453c82fatagen2026-09-24 13:07 UTC386
        self.queued -= opening.queued.len();
31ce2453c82fatagen2026-09-24 13:07 UTC387
        Some((root, opening.queued, timeline))
31ce2453c82fatagen2026-09-24 13:07 UTC388
    }
31ce2453c82fatagen2026-09-24 13:07 UTC389
31ce2453c82fatagen2026-09-24 13:07 UTC390
    async fn close_one(&mut self, root: &OwnedEventId) -> Result<(), Error> {
31ce2453c82fatagen2026-09-24 13:07 UTC391
        if let Some(session) = self.open.remove(root) {
31ce2453c82fatagen2026-09-24 13:07 UTC392
            session.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC393
            return Ok(());
31ce2453c82fatagen2026-09-24 13:07 UTC394
        }
31ce2453c82fatagen2026-09-24 13:07 UTC395
        let Some(opening) = self.opening.remove(root) else {
31ce2453c82fatagen2026-09-24 13:07 UTC396
            return Err(Error::new(
31ce2453c82fatagen2026-09-24 13:07 UTC397
                ErrorKind::TimelineNotOpen,
31ce2453c82fatagen2026-09-24 13:07 UTC398
                "thread timeline is not open",
31ce2453c82fatagen2026-09-24 13:07 UTC399
            ));
31ce2453c82fatagen2026-09-24 13:07 UTC400
        };
31ce2453c82fatagen2026-09-24 13:07 UTC401
        opening.build.abort();
31ce2453c82fatagen2026-09-24 13:07 UTC402
        self.queued -= opening.queued.len();
31ce2453c82fatagen2026-09-24 13:07 UTC403
        Ok(())
31ce2453c82fatagen2026-09-24 13:07 UTC404
    }
31ce2453c82fatagen2026-09-24 13:07 UTC405
31ce2453c82fatagen2026-09-24 13:07 UTC406
    async fn close(&mut self) {
31ce2453c82fatagen2026-09-24 13:07 UTC407
        self.builds.abort_all();
31ce2453c82fatagen2026-09-24 13:07 UTC408
        while self.builds.join_next().await.is_some() {}
31ce2453c82fatagen2026-09-24 13:07 UTC409
        self.opening.clear();
31ce2453c82fatagen2026-09-24 13:07 UTC410
        for (_, session) in self.open.drain() {
31ce2453c82fatagen2026-09-24 13:07 UTC411
            session.close().await;
31ce2453c82fatagen2026-09-24 13:07 UTC412
        }
31ce2453c82fatagen2026-09-24 13:07 UTC413
    }
31ce2453c82fatagen2026-09-24 13:07 UTC414
}