Skip to main content

tilo_app/framework/
live.rs

1//! The resource state views read, kept current as the document changes.
2
3use std::cell::RefCell;
4use std::rc::Rc;
5
6use tilo_resource::{Delta, Demand, Doc, ModelError, Place, Reader, Resource};
7
8/// A shared handle to the current state of one resource.
9///
10/// The framework brings the state up to date when the document's heads move
11/// — a local action, a collaborator's edit, an undo — reading only what the
12/// change touched ([`Resource::refresh`]), and bumps the revision so bound
13/// widgets refresh. Views never write it: edits are actions.
14///
15/// A view of a large resource says what it shows with
16/// [`demand`](Self::demand), and only that is read
17/// ([`Resource::load`]).
18pub struct Live<R: Resource> {
19    inner: Rc<RefCell<Reader<R>>>,
20    changes: Rc<RefCell<Changes>>,
21    places: Rc<RefCell<Places<R>>>,
22}
23
24/// Where each participant's last action left them ([`Resource::lands`]):
25/// the framework's record of where an agent or a collaborator works, which
26/// presence draws and a surface reads instead of keeping its own copy.
27struct Places<R: Resource> {
28    /// Applied for a participant, not yet placed: placed once the state
29    /// holds what they did.
30    applied: Vec<(tilo_actions::ParticipantId, R::Action)>,
31    landed: Landings,
32}
33
34impl<R: Resource> Default for Places<R> {
35    fn default() -> Self {
36        Self {
37            applied: Vec::new(),
38            landed: Landings::new(),
39        }
40    }
41}
42
43/// Where each participant was placed, and the latest placings in order.
44/// Apart from [`Places`] because it does not name the resource type, so its
45/// bookkeeping is compiled once rather than for every resource.
46#[derive(Default)]
47struct Landings {
48    at: std::collections::HashMap<tilo_actions::ParticipantId, Place>,
49    /// The latest landings in order, numbered, for surfaces to follow.
50    log: std::collections::VecDeque<(u64, tilo_actions::ParticipantId, Place)>,
51    next: u64,
52}
53
54impl Landings {
55    /// Nobody placed yet. Out of line, so every app's [`Live`] calls the
56    /// one copy in the runtime.
57    #[inline(never)]
58    fn new() -> Self {
59        Self::default()
60    }
61
62    #[inline(never)]
63    fn land(&mut self, participant: tilo_actions::ParticipantId, place: Place) {
64        let number = self.next;
65        self.next = number.saturating_add(1);
66        self.at.insert(participant.clone(), place.clone());
67        self.log.push_back((number, participant, place));
68        if self.log.len() > LANDINGS_KEPT {
69            self.log.pop_front();
70        }
71    }
72
73    #[inline(never)]
74    fn since(&self, from: u64) -> (Vec<(tilo_actions::ParticipantId, Place)>, u64) {
75        let landed = self
76            .log
77            .iter()
78            .filter(|(number, _, _)| *number >= from)
79            .map(|(_, participant, place)| (participant.clone(), place.clone()))
80            .collect();
81        (landed, self.next)
82    }
83}
84
85/// How many landings a surface that missed some can still catch up on.
86const LANDINGS_KEPT: usize = 64;
87
88/// What changed since bound controls last followed the state: `None` when
89/// nothing did.
90#[derive(Default)]
91struct Changes {
92    pending: Option<Delta>,
93}
94
95impl Changes {
96    /// None yet, shared. Out of line, so every app's [`Live`] calls the one
97    /// copy in the runtime.
98    #[inline(never)]
99    fn shared() -> Rc<RefCell<Self>> {
100        Rc::default()
101    }
102
103    fn note(&mut self, delta: Delta) {
104        match &mut self.pending {
105            Some(pending) => pending.merge(delta),
106            None => self.pending = Some(delta),
107        }
108    }
109}
110
111/// Note in `changes` what a sync applied, `delta`. Out of line, so every
112/// app's [`Live::sync`] calls the one copy in the runtime.
113#[inline(never)]
114fn note_synced(changes: &RefCell<Changes>, delta: &Delta) {
115    changes.borrow_mut().note(delta.clone());
116}
117
118impl<R: Resource> Clone for Live<R> {
119    fn clone(&self) -> Self {
120        Self {
121            inner: Rc::clone(&self.inner),
122            changes: Rc::clone(&self.changes),
123            places: Rc::clone(&self.places),
124        }
125    }
126}
127
128impl<R: Resource> Live<R> {
129    pub(crate) fn new(reader: Reader<R>) -> Self {
130        Self {
131            inner: Rc::new(RefCell::new(reader)),
132            changes: Changes::shared(),
133            places: Rc::default(),
134        }
135    }
136
137    /// Note that `action` was applied for `participant`; where it left them
138    /// is read once the state follows it ([`Self::place_landings`]).
139    pub(crate) fn note_applied(&self, participant: tilo_actions::ParticipantId, action: R::Action) {
140        self.places.borrow_mut().applied.push((participant, action));
141    }
142
143    /// Place every participant whose action the state now holds, where
144    /// [`Resource::lands`] says it left them.
145    #[inline(never)]
146    pub(crate) fn place_landings(&self) {
147        let applied = std::mem::take(&mut self.places.borrow_mut().applied);
148        if applied.is_empty() {
149            return;
150        }
151        let state = self.get();
152        for (participant, action) in applied {
153            if let Some(place) = R::lands(&state, &action) {
154                self.land(participant, place);
155            }
156        }
157    }
158
159    /// Place `participant` at `place`: where an effect they told where to
160    /// act acted ([`EffectCx::place`](super::EffectCx::place)).
161    pub(crate) fn land(&self, participant: tilo_actions::ParticipantId, place: Place) {
162        self.places.borrow_mut().landed.land(participant, place);
163    }
164
165    /// Where `participant`'s last action left them, as the resource places
166    /// it ([`Resource::lands`]): the paragraph an agent appended, the table
167    /// it inserted. `None` before they did anything that lands somewhere.
168    pub fn place_of(&self, participant: &tilo_actions::ParticipantId) -> Option<Place> {
169        self.places.borrow().landed.at.get(participant).cloned()
170    }
171
172    /// The landings numbered `from` on, and the number to ask from next.
173    pub(crate) fn landings_since(
174        &self,
175        from: u64,
176    ) -> (Vec<(tilo_actions::ParticipantId, Place)>, u64) {
177        self.places.borrow().landed.since(from)
178    }
179
180    /// The number the next landing will have: where a follower that has
181    /// seen every landing so far asks from.
182    pub(crate) fn next_landing(&self) -> u64 {
183        self.places.borrow().landed.next
184    }
185
186    /// What changed since the last call, for the readers bound to the
187    /// state: `None` when nothing did.
188    pub(crate) fn take_change(&self) -> Option<Delta> {
189        self.changes.borrow_mut().pending.take()
190    }
191
192    /// The current state. Cheap: shares the materialised value.
193    ///
194    /// Keep it only as long as a frame needs it. A state still shared when
195    /// the document next changes cannot be updated in place, so the
196    /// framework reads the whole resource again instead; read through
197    /// [`with`](Self::with) or call this again when you need it.
198    pub fn get(&self) -> Rc<R::State> {
199        Rc::clone(self.inner.borrow().state())
200    }
201
202    /// The current state, when the document reads as the resource: `None`
203    /// while it does not, so a caller publishing what the state says never
204    /// reports the last state that read as if it were what the document
205    /// holds.
206    pub fn get_checked(&self) -> Option<Rc<R::State>> {
207        let inner = self.inner.borrow();
208        inner
209            .unreadable()
210            .is_none()
211            .then(|| Rc::clone(inner.state()))
212    }
213
214    /// Read the current state.
215    pub fn with<T>(&self, read: impl FnOnce(&R::State) -> T) -> T {
216        read(self.inner.borrow().state())
217    }
218
219    /// Why the document stopped reading as the resource, if it did: the
220    /// state is then the last one that read, and views show this instead.
221    pub fn unreadable(&self) -> Option<tilo_resource::Unreadable> {
222        self.inner.borrow().unreadable().cloned()
223    }
224
225    /// Increases whenever what the state says changes. A view reading
226    /// more of the same document ([`Self::demand`]) is no new revision.
227    pub fn revision(&self) -> u64 {
228        self.inner.borrow().revision()
229    }
230
231    /// Increases whenever a demand read parts of the document the state did
232    /// not hold yet: what a view of a large resource holds moved, not what
233    /// the resource says.
234    pub fn loads(&self) -> u64 {
235        self.inner.borrow().loads()
236    }
237
238    /// The state, when it changed or read more since `seen` (a window's
239    /// record of what it last showed, `None` before the first time), which
240    /// it updates: what a window built over a view shows of the document it
241    /// follows.
242    pub fn changed_since(&self, seen: &mut Option<(u64, u64)>) -> Option<Rc<R::State>> {
243        let held = (self.revision(), self.loads());
244        if *seen == Some(held) {
245            return None;
246        }
247        *seen = Some(held);
248        Some(self.get())
249    }
250
251    /// Whether the state is `doc` as it stands: no change arrived since the
252    /// framework last synced it.
253    pub fn follows(&self, doc: &Doc) -> bool {
254        self.inner.borrow().follows(doc)
255    }
256
257    /// Show the parts of a large resource `demand` names, reading those not
258    /// read yet from `doc` — the document the view was synced with, which in
259    /// History mode is the past version on screen. Returns whether the state
260    /// read more ([`Self::loads`]). What the resource says is unchanged, so
261    /// this is no revision and no change to follow.
262    #[inline(never)]
263    pub fn demand(&self, doc: &Doc, demand: Demand) -> Result<bool, ModelError>
264    where
265        R::State: Clone,
266    {
267        self.inner.borrow_mut().demand(doc, demand)
268    }
269
270    /// Take `state` as the state now, read elsewhere than from a document
271    /// the framework syncs (a settings section, as the settings document
272    /// holds it): everything bound to it takes it again.
273    pub(crate) fn replace(&self, state: R::State) {
274        self.inner.borrow_mut().replace(state);
275        self.changes.borrow_mut().note(Delta::everything());
276    }
277
278    /// Ask everything bound to the state to take it again, unchanged. After
279    /// an edit was refused or is waiting for confirmation, a surface that
280    /// previewed it optimistically is re-presented the stored state.
281    pub(crate) fn represent(&self) {
282        self.inner.borrow_mut().represent();
283        self.changes.borrow_mut().note(Delta::everything());
284    }
285
286    /// What views currently demand.
287    pub fn current_demand(&self) -> Demand {
288        self.inner.borrow().current_demand().clone()
289    }
290
291    /// Apply what changed in `doc` since the last sync, reading only what
292    /// changed. The framework does this before every frame; a surface calls
293    /// it after an immediate edit ([`Dispatcher::apply_now`](super::Dispatcher::apply_now))
294    /// to read its own result within the same event. Returns whether the
295    /// state changed.
296    #[inline(never)]
297    pub fn sync(&self, doc: &Doc) -> Result<bool, ModelError> {
298        let _profile = tilo_resource::profiling::Phase::start("framework.live_sync");
299        let mut reader = self.inner.borrow_mut();
300        let moved = reader.sync(doc)?;
301        if moved {
302            note_synced(&self.changes, reader.last_delta());
303        }
304        Ok(moved)
305    }
306
307    /// Read the state whole from `doc`, whatever the last sync said, so a
308    /// change this reader cannot apply whole is noticed and reported: for a
309    /// resource kept small enough to read that way, which nothing else
310    /// would find out about. Returns whether the state changed.
311    pub fn reread(&self, doc: &Doc) -> Result<bool, ModelError> {
312        let mut reader = self.inner.borrow_mut();
313        let moved = reader.reread_whole(doc)?;
314        if moved {
315            note_synced(&self.changes, reader.last_delta());
316        }
317        Ok(moved)
318    }
319}