Files
figcraft/crates/collab-net/src/sessions.rs
T
bflatastic 94349d86b0 M9: live sessions per document
Live sessions are per document instead of one for the whole app.

- **Engine** (`crates/engine/src/live.rs`): `Session.live` is now `Lives`: the app-wide parts (the
  installed link, the invite-link registration) plus one `Live` per session. One place resolves
  "the session of this document": `Session::live()` (the active document's; status off when it
  has none), `live_mut()`, `live_of(uid)`. Sync, undo, read-only checks, follow, cursor chat,
  spotlight and presence all go through the session of the document they are about, so edits and
  presence never cross. Sessions behind the active tab keep syncing; your presence there is empty.
- **Link** (`LiveLink`): every call takes the `SessionId` the engine picked and `poll` returns
  `(SessionId, Event)`, so one link carries several sessions. The desktop link and the browser
  link keep them in a shared table with one mailbox per session
  (`crates/collab-net/src/sessions.rs`); each hosted document listens on its own port.
- **Commands**: `collab.*` act on the active document's session; no parameters changed.
  `collab.peers` keeps its shape and gains `session` (its id, null when the document isn't in
  one) and `sessions` (every session: `{session, document, status, reason, role, fileName, link,
  peers}`). `collab.join` returns the new session and opens its file in a tab of its own without
  touching other sessions; until then (or if it fails) it shows on the tab it was started from.
  Starting a session for a document replaces only that document's own session. Closing a document
  ends its session only.
- **UI** (`crates/ui-egui/src/live.rs`): the Share popover, avatars, cursors, chat, spotlight
  banner, follow frame and read-only reason read the session of the tab in front, per window. What
  belongs to one session in the window (cursors shown, your chat box, a report being written) is
  dropped when another tab comes to the front. `ui.inspect`'s `live` gains `session` and
  `sessions`. No new strings or command ids.
- **Docs**: `docs/collab-protocol.md`, `docs/mcp.md`, `docs/control-protocol.md`,
  `docs/collaboration-parity.md`, `docs/gaps.md` (gap 2), `ROADMAP.md`.
2026-10-12 17:54:18 +07:00

53 lines
1.8 KiB
Rust

//! The sessions a link carries at once (the engine has one per document): each under the id the
//! engine gave it, with its own [`Inbox`], so what happens in one never reaches another and a
//! session that ended can't reach the next. The desktop link and the browser link both keep
//! theirs here.
use std::collections::BTreeMap;
use figcraft_engine::live::{Event, SessionId};
use crate::inbox::{Inbox, Waker};
/// Running sessions of kind `T` (a host or a guest connection) by id.
pub(crate) struct Sessions<T> {
wake: Waker,
running: BTreeMap<SessionId, (T, Inbox)>,
}
impl<T> Sessions<T> {
pub(crate) fn new(wake: Waker) -> Self {
Self { wake, running: BTreeMap::new() }
}
/// A mailbox for a session about to start.
pub(crate) fn inbox(&self) -> Inbox {
Inbox::new(self.wake.clone())
}
/// Session `id` started, with the mailbox its threads fill. Returns the session that had
/// the id, to be ended.
pub(crate) fn insert(&mut self, id: SessionId, session: T, inbox: Inbox) -> Option<T> {
self.running.insert(id, (session, inbox)).map(|(old, _)| old)
}
/// Session `id` is over: it comes back to be ended, and its mailbox is dropped.
pub(crate) fn remove(&mut self, id: SessionId) -> Option<T> {
self.running.remove(&id).map(|(session, _)| session)
}
pub(crate) fn get(&self, id: SessionId) -> Option<&T> {
self.running.get(&id).map(|(session, _)| session)
}
/// Every session's id, oldest first.
pub(crate) fn ids(&self) -> Vec<SessionId> {
self.running.keys().copied().collect()
}
/// What happened in each session since the last call, with its id.
pub(crate) fn poll(&self) -> Vec<(SessionId, Event)> {
self.running.iter().flat_map(|(id, (_, inbox))| inbox.drain().into_iter().map(|e| (*id, e))).collect()
}
}